1#!/usr/bin/env python 2# 3# revert_tests.py: testing 'svn revert'. 4# 5# Subversion is a tool for revision control. 6# See http://subversion.apache.org for more information. 7# 8# ==================================================================== 9# Licensed to the Apache Software Foundation (ASF) under one 10# or more contributor license agreements. See the NOTICE file 11# distributed with this work for additional information 12# regarding copyright ownership. The ASF licenses this file 13# to you under the Apache License, Version 2.0 (the 14# "License"); you may not use this file except in compliance 15# with the License. You may obtain a copy of the License at 16# 17# http://www.apache.org/licenses/LICENSE-2.0 18# 19# Unless required by applicable law or agreed to in writing, 20# software distributed under the License is distributed on an 21# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 22# KIND, either express or implied. See the License for the 23# specific language governing permissions and limitations 24# under the License. 25###################################################################### 26 27# General modules 28import re, os, stat, shutil 29 30# Our testing module 31import svntest 32from svntest import wc, main, actions 33from svntest.actions import run_and_verify_svn 34from svntest.main import file_append, file_write, run_svn 35 36# (abbreviation) 37Skip = svntest.testcase.Skip_deco 38SkipUnless = svntest.testcase.SkipUnless_deco 39XFail = svntest.testcase.XFail_deco 40Issues = svntest.testcase.Issues_deco 41Issue = svntest.testcase.Issue_deco 42Wimp = svntest.testcase.Wimp_deco 43Item = svntest.wc.StateItem 44 45 46###################################################################### 47# Helpers 48 49def expected_output_revert(reverted_paths, skipped_paths=[]): 50 return svntest.verify.UnorderedRegexListOutput( 51 ["Reverted '%s'\n" % re.escape(path) for path in reverted_paths] + 52 ["Skipped '%s'.*\n" % re.escape(path) for path in skipped_paths]) 53 54def run_and_verify_revert(targets, options=[], 55 reverted_paths=None, skipped_paths=[]): 56 """Run 'svn revert OPTIONS TARGETS'. Verify that the printed output matches 57 REVERTED_PATHS and SKIPPED_PATHS. If REVERTED_PATHS is None, it defaults 58 to TARGETS. 59 """ 60 if reverted_paths is None: 61 reverted_paths = targets 62 expected_output = expected_output_revert(reverted_paths, skipped_paths) 63 svntest.actions.run_and_verify_svn(expected_output, [], 64 *(['revert'] + options + targets)) 65 66def revert_replacement_with_props(sbox, wc_copy): 67 """Helper implementing the core of 68 revert_{repos,wc}_to_wc_replace_with_props(). 69 70 Uses a working copy (when wc_copy == True) or a URL (when wc_copy == 71 False) source to copy from.""" 72 73 sbox.build() 74 wc_dir = sbox.wc_dir 75 76 # Use a temp file to set properties with wildcards in their values 77 # otherwise Win32/VS2005 will expand them 78 prop_path = os.path.join(wc_dir, 'proptmp') 79 svntest.main.file_append(prop_path, '*') 80 81 # Set props on file which is copy-source later on 82 pi_path = os.path.join(wc_dir, 'A', 'D', 'G', 'pi') 83 rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') 84 svntest.actions.run_and_verify_svn(None, [], 85 'ps', 'phony-prop', '-F', prop_path, 86 pi_path) 87 os.remove(prop_path) 88 svntest.actions.run_and_verify_svn(None, [], 89 'ps', 'svn:eol-style', 'LF', rho_path) 90 91 # Verify props having been set 92 expected_disk = svntest.main.greek_state.copy() 93 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 94 expected_disk.tweak('A/D/G/pi', 95 props={ 'phony-prop': '*' }) 96 expected_disk.tweak('A/D/G/rho', 97 props={ 'svn:eol-style': 'LF' }) 98 99 svntest.actions.verify_disk(wc_dir, expected_disk, True) 100 101 # Commit props 102 expected_output = svntest.wc.State(wc_dir, { 103 'A/D/G/pi': Item(verb='Sending'), 104 'A/D/G/rho': Item(verb='Sending'), 105 }) 106 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 107 expected_status.tweak('A/D/G/pi', wc_rev='2') 108 expected_status.tweak('A/D/G/rho', wc_rev='2') 109 svntest.actions.run_and_verify_commit(wc_dir, 110 expected_output, 111 expected_status) 112 113 # Bring wc into sync 114 svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) 115 116 # File scheduled for deletion 117 svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path) 118 119 # Status before attempting copies 120 expected_status = svntest.actions.get_virginal_state(wc_dir, 2) 121 expected_status.tweak('A/D/G/rho', status='D ') 122 svntest.actions.run_and_verify_status(wc_dir, expected_status) 123 124 # The copy shouldn't fail 125 if wc_copy: 126 pi_src = os.path.join(wc_dir, 'A', 'D', 'G', 'pi') 127 else: 128 pi_src = sbox.repo_url + '/A/D/G/pi' 129 130 svntest.actions.run_and_verify_svn(None, [], 131 'cp', pi_src, rho_path) 132 133 # Verify both content and props have been copied 134 if wc_copy: 135 props = { 'phony-prop' : '*' } 136 else: 137 props = { 'phony-prop' : '*' } 138 139 expected_disk.tweak('A/D/G/rho', 140 contents="This is the file 'pi'.\n", 141 props=props) 142 svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True) 143 144 # Now revert 145 expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-') 146 svntest.actions.run_and_verify_status(wc_dir, expected_status) 147 148 expected_status.tweak('A/D/G/rho', status=' ', copied=None, wc_rev='2') 149 run_and_verify_revert([wc_dir], ['-R'], [rho_path]) 150 svntest.actions.run_and_verify_status(wc_dir, expected_status) 151 152 # Check disk status 153 expected_disk = svntest.main.greek_state.copy() 154 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 155 expected_disk.tweak('A/D/G/pi', 156 props={ 'phony-prop': '*' }) 157 expected_disk.tweak('A/D/G/rho', 158 props={ 'svn:eol-style': 'LF' }) 159 svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True) 160 161 162 163 164###################################################################### 165# Tests 166# 167# Each test must return on success or raise on failure. 168 169 170#---------------------------------------------------------------------- 171 172def revert_from_wc_root(sbox): 173 "revert relative to wc root" 174 175 sbox.build(read_only = True) 176 wc_dir = sbox.wc_dir 177 178 os.chdir(wc_dir) 179 180 # Mostly taken from basic_revert 181 # Modify some files and props. 182 beta_path = os.path.join('A', 'B', 'E', 'beta') 183 gamma_path = os.path.join('A', 'D', 'gamma') 184 iota_path = 'iota' 185 rho_path = os.path.join('A', 'D', 'G', 'rho') 186 zeta_path = os.path.join('A', 'D', 'H', 'zeta') 187 svntest.main.file_append(beta_path, "Added some text to 'beta'.\n") 188 svntest.main.file_append(iota_path, "Added some text to 'iota'.\n") 189 svntest.main.file_append(rho_path, "Added some text to 'rho'.\n") 190 svntest.main.file_append(zeta_path, "Added some text to 'zeta'.\n") 191 192 svntest.actions.run_and_verify_svn(None, [], 193 'add', zeta_path) 194 svntest.actions.run_and_verify_svn(None, [], 195 'ps', 'random-prop', 'propvalue', 196 gamma_path) 197 svntest.actions.run_and_verify_svn(None, [], 198 'ps', 'random-prop', 'propvalue', 199 iota_path) 200 svntest.actions.run_and_verify_svn(None, [], 201 'ps', 'random-prop', 'propvalue', 202 '.') 203 svntest.actions.run_and_verify_svn(None, [], 204 'ps', 'random-prop', 'propvalue', 205 'A') 206 207 # Verify modified status. 208 expected_output = svntest.actions.get_virginal_state('', 1) 209 expected_output.tweak('A/B/E/beta', 'A/D/G/rho', status='M ') 210 expected_output.tweak('iota', status='MM') 211 expected_output.tweak('', 'A/D/gamma', 'A', status=' M') 212 expected_output.add({ 213 'A/D/H/zeta' : Item(status='A ', wc_rev=0), 214 }) 215 216 svntest.actions.run_and_verify_status('', expected_output) 217 218 # Run revert 219 svntest.actions.run_and_verify_svn(None, [], 220 'revert', beta_path) 221 222 svntest.actions.run_and_verify_svn(None, [], 223 'revert', gamma_path) 224 225 svntest.actions.run_and_verify_svn(None, [], 226 'revert', iota_path) 227 228 svntest.actions.run_and_verify_svn(None, [], 229 'revert', rho_path) 230 231 svntest.actions.run_and_verify_svn(None, [], 232 'revert', zeta_path) 233 234 svntest.actions.run_and_verify_svn(None, [], 235 'revert', '.') 236 237 svntest.actions.run_and_verify_svn(None, [], 238 'revert', 'A') 239 240 # Verify unmodified status. 241 expected_output = svntest.actions.get_virginal_state('', 1) 242 243 svntest.actions.run_and_verify_status('', expected_output) 244 245@Issue(1663) 246def revert_reexpand_keyword(sbox): 247 "revert reexpands manually contracted keyword" 248 249 # This is for issue #1663. The bug is that if the only difference 250 # between a locally modified working file and the base version of 251 # same was that the former had a contracted keyword that would be 252 # expanded in the latter, then 'svn revert' wouldn't notice the 253 # difference, and therefore wouldn't revert. And why wouldn't it 254 # notice? Since text bases are always stored with keywords 255 # contracted, and working files are contracted before comparison 256 # with text base, there would appear to be no difference when the 257 # contraction is the only difference. For most commands, this is 258 # correct -- but revert's job is to restore the working file, not 259 # the text base. 260 261 sbox.build() 262 wc_dir = sbox.wc_dir 263 newfile_path = os.path.join(wc_dir, "newfile") 264 unexpanded_contents = "This is newfile: $Rev$.\n" 265 266 # Put an unexpanded keyword into iota. 267 svntest.main.file_write(newfile_path, unexpanded_contents) 268 269 # Commit, without svn:keywords property set. 270 svntest.main.run_svn(None, 'add', newfile_path) 271 svntest.main.run_svn(None, 272 'commit', '-m', 'r2', newfile_path) 273 274 # Set the property and commit. This should expand the keyword. 275 svntest.main.run_svn(None, 'propset', 'svn:keywords', 'rev', newfile_path) 276 svntest.main.run_svn(None, 277 'commit', '-m', 'r3', newfile_path) 278 279 # Verify that the keyword got expanded. 280 def check_expanded(path): 281 fp = open(path, 'r') 282 lines = fp.readlines() 283 fp.close() 284 if lines[0] != "This is newfile: $Rev: 3 $.\n": 285 raise svntest.Failure 286 287 check_expanded(newfile_path) 288 289 # Now un-expand the keyword again. 290 svntest.main.file_write(newfile_path, unexpanded_contents) 291 292 # Revert the file. The keyword should reexpand. 293 svntest.main.run_svn(None, 'revert', newfile_path) 294 295 # Verify that the keyword got re-expanded. 296 check_expanded(newfile_path) 297 298 # Ok, the first part of this test was written in 2004. We are now in 2011 299 # and note that there is more to test: 300 301 # If the recorded timestamp and size match the file then revert won't 302 # reinstall the file as the file was not modified when last compared in 303 # the repository normal form. 304 # 305 # The easiest way to get the information recorded would be calling cleanup, 306 # because that 'repairs' the recorded information. But some developers 307 # (including me) would call that cheating, so I just use a failed commit. 308 309 # Un-expand the keyword again. 310 svntest.main.file_write(newfile_path, unexpanded_contents) 311 312 # And now we trick svn in ignoring the file on newfile_path 313 newfile2_path = newfile_path + '2' 314 svntest.main.file_write(newfile2_path, 'This is file 2') 315 svntest.main.run_svn(None, 'add', newfile2_path) 316 os.remove(newfile2_path) 317 318 # This commit fails because newfile2_path is missing, but only after 319 # we call svn_wc__internal_file_modified_p() on new_file. 320 svntest.actions.run_and_verify_commit(wc_dir, None, None, ".*2' is scheduled"+ 321 " for addition, but is missing.*", 322 newfile_path, newfile2_path, 323 '-m', "Shouldn't be committed") 324 325 # Revert the file. The file is not reverted! 326 svntest.actions.run_and_verify_svn([], [], 'revert', newfile_path) 327 328 329#---------------------------------------------------------------------- 330# Regression test for issue #1775: 331# Should be able to revert a file with no properties i.e. no prop-base 332@Issue(1775) 333def revert_replaced_file_without_props(sbox): 334 "revert a replaced file with no properties" 335 336 sbox.build() 337 wc_dir = sbox.wc_dir 338 339 file1_path = os.path.join(wc_dir, 'file1') 340 341 # Add a new file, file1, that has no prop-base 342 svntest.main.file_append(file1_path, "This is the file 'file1' revision 2.") 343 svntest.actions.run_and_verify_svn(None, [], 'add', file1_path) 344 345 # commit file1 346 expected_output = svntest.wc.State(wc_dir, { 347 'file1' : Item(verb='Adding') 348 }) 349 350 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 351 expected_status.add({ 352 'file1' : Item(status=' ', wc_rev=2), 353 }) 354 355 svntest.actions.run_and_verify_commit(wc_dir, expected_output, 356 expected_status) 357 358 # delete file1 359 svntest.actions.run_and_verify_svn(None, [], 'rm', file1_path) 360 361 # test that file1 is scheduled for deletion. 362 expected_status.tweak('file1', status='D ') 363 svntest.actions.run_and_verify_status(wc_dir, expected_status) 364 365 # recreate and add file1 366 svntest.main.file_append(file1_path, "This is the file 'file1' revision 3.") 367 svntest.actions.run_and_verify_svn(None, [], 'add', file1_path) 368 369 # Test to see if file1 is schedule for replacement 370 expected_status.tweak('file1', status='R ') 371 svntest.actions.run_and_verify_status(wc_dir, expected_status) 372 373 # revert file1 374 run_and_verify_revert([file1_path]) 375 376 # test that file1 really was reverted 377 expected_status.tweak('file1', status=' ', wc_rev=2) 378 svntest.actions.run_and_verify_status(wc_dir, expected_status) 379 380#---------------------------------------------------------------------- 381# Note that issue #876 has been rejected. This now basically tests that 382# reverting the delete side of a move does *not* also revert the copy side. 383@Issue(876) 384def revert_moved_file(sbox): 385 "revert a moved file" 386 387 # svntest.factory.make(sbox, """svn mv iota iota_moved 388 # svn st 389 # svn revert iota 390 # svn st 391 # """) 392 393 sbox.build() 394 wc_dir = sbox.wc_dir 395 396 iota = os.path.join(wc_dir, 'iota') 397 iota_moved = os.path.join(wc_dir, 'iota_moved') 398 399 # svn mv iota iota_moved 400 expected_stdout = svntest.verify.UnorderedOutput([ 401 'A ' + iota_moved + '\n', 402 'D ' + iota + '\n', 403 ]) 404 405 actions.run_and_verify_svn2(expected_stdout, [], 0, 'mv', iota, 406 iota_moved) 407 408 # svn st 409 expected_status = actions.get_virginal_state(wc_dir, 1) 410 expected_status.add({ 411 'iota_moved' : Item(status='A ', copied='+', wc_rev='-', 412 moved_from='iota'), 413 }) 414 expected_status.tweak('iota', status='D ', moved_to='iota_moved') 415 416 actions.run_and_verify_unquiet_status(wc_dir, expected_status) 417 418 # svn revert iota 419 run_and_verify_revert([iota]) 420 421 # svn st 422 expected_status.tweak('iota', status=' ', moved_to=None) 423 expected_status.tweak('iota_moved', moved_from=None) 424 425 actions.run_and_verify_unquiet_status(wc_dir, expected_status) 426 427 428#---------------------------------------------------------------------- 429# Test for issue 2135 430# 431# It is like merge_file_replace (in merge_tests.py), but reverts file 432# instead of commit. 433@Issue(2135) 434def revert_file_merge_replace_with_history(sbox): 435 "revert a merge replacement of file with history" 436 437 sbox.build() 438 wc_dir = sbox.wc_dir 439 440 # File scheduled for deletion 441 rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') 442 svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path) 443 444 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 445 expected_status.tweak('A/D/G/rho', status='D ') 446 svntest.actions.run_and_verify_status(wc_dir, expected_status) 447 448 expected_output = svntest.wc.State(wc_dir, { 449 'A/D/G/rho': Item(verb='Deleting'), 450 }) 451 452 expected_status.remove('A/D/G/rho') 453 454 # Commit rev 2 455 svntest.actions.run_and_verify_commit(wc_dir, 456 expected_output, 457 expected_status) 458 # create new rho file 459 svntest.main.file_write(rho_path, "new rho\n") 460 461 # Add the new file 462 svntest.actions.run_and_verify_svn(None, [], 'add', rho_path) 463 464 # Commit revsion 3 465 expected_status.add({ 466 'A/D/G/rho' : Item(status='A ', wc_rev='0') 467 }) 468 svntest.actions.run_and_verify_status(wc_dir, expected_status) 469 expected_output = svntest.wc.State(wc_dir, { 470 'A/D/G/rho': Item(verb='Adding'), 471 }) 472 473 svntest.actions.run_and_verify_commit(wc_dir, 474 expected_output, 475 None) 476 477 # Update working copy 478 expected_output = svntest.wc.State(wc_dir, {}) 479 expected_disk = svntest.main.greek_state.copy() 480 expected_disk.tweak('A/D/G/rho', contents='new rho\n' ) 481 expected_status.tweak(wc_rev='3') 482 expected_status.tweak('A/D/G/rho', status=' ') 483 484 svntest.actions.run_and_verify_update(wc_dir, 485 expected_output, 486 expected_disk, 487 expected_status) 488 489 # merge changes from r3:1 490 expected_output = svntest.wc.State(wc_dir, { 491 'A/D/G/rho': Item(status='R ') 492 }) 493 expected_mergeinfo_output = svntest.wc.State(wc_dir, { 494 '' : Item(status=' U') 495 }) 496 expected_elision_output = svntest.wc.State(wc_dir, { 497 '' : Item(status=' U') 498 }) 499 expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-') 500 expected_skip = wc.State(wc_dir, { }) 501 expected_disk.tweak('A/D/G/rho', contents="This is the file 'rho'.\n") 502 svntest.actions.run_and_verify_merge(wc_dir, '3', '1', 503 sbox.repo_url, None, 504 expected_output, 505 expected_mergeinfo_output, 506 expected_elision_output, 507 expected_disk, 508 expected_status, 509 expected_skip) 510 511 # Now revert 512 svntest.actions.run_and_verify_svn(None, 513 [], 'revert', rho_path) 514 515 # test that rho really was reverted 516 expected_status.tweak('A/D/G/rho', copied=None, status=' ', wc_rev=3) 517 svntest.actions.run_and_verify_status(wc_dir, expected_status) 518 519 expected_disk.tweak('A/D/G/rho', contents="new rho\n") 520 svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True) 521 522 # Make sure the revert removed the copy from information. 523 expected_infos = [ 524 { 'Copied' : None } 525 ] 526 svntest.actions.run_and_verify_info(expected_infos, rho_path) 527 528def revert_wc_to_wc_replace_with_props(sbox): 529 "revert svn cp PATH PATH replace file with props" 530 531 revert_replacement_with_props(sbox, 1) 532 533def revert_repos_to_wc_replace_with_props(sbox): 534 "revert svn cp URL PATH replace file with props" 535 536 revert_replacement_with_props(sbox, 0) 537 538def revert_after_second_replace(sbox): 539 "revert file after second replace" 540 541 sbox.build(read_only = True) 542 wc_dir = sbox.wc_dir 543 544 # File scheduled for deletion 545 rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') 546 svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path) 547 548 # Status before attempting copy 549 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 550 expected_status.tweak('A/D/G/rho', status='D ') 551 svntest.actions.run_and_verify_status(wc_dir, expected_status) 552 553 # Replace file for the first time 554 pi_src = os.path.join(wc_dir, 'A', 'D', 'G', 'pi') 555 556 svntest.actions.run_and_verify_svn(None, [], 557 'cp', pi_src, rho_path) 558 559 expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-') 560 svntest.actions.run_and_verify_status(wc_dir, expected_status) 561 562 # Now delete replaced file. 563 svntest.actions.run_and_verify_svn(None, [], 'rm', '--force', rho_path) 564 565 # Status should be same as after first delete 566 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 567 expected_status.tweak('A/D/G/rho', status='D ') 568 svntest.actions.run_and_verify_status(wc_dir, expected_status) 569 570 # Replace file for the second time 571 pi_src = os.path.join(wc_dir, 'A', 'D', 'G', 'pi') 572 573 svntest.actions.run_and_verify_svn(None, [], 'cp', pi_src, rho_path) 574 575 expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-') 576 svntest.actions.run_and_verify_status(wc_dir, expected_status) 577 578 # Now revert 579 svntest.actions.run_and_verify_svn(None, [], 580 'revert', '-R', wc_dir) 581 582 # Check disk status 583 expected_disk = svntest.main.greek_state.copy() 584 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 585 svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True) 586 587 588#---------------------------------------------------------------------- 589# Tests for issue #2517. 590# 591# Manual conflict resolution leads to spurious revert report. 592@Issue(2517) 593def revert_after_manual_conflict_resolution__text(sbox): 594 "revert after manual text-conflict resolution" 595 596 # Make two working copies 597 sbox.build() 598 wc_dir_1 = sbox.wc_dir 599 wc_dir_2 = sbox.add_wc_path('other') 600 svntest.actions.duplicate_dir(wc_dir_1, wc_dir_2) 601 602 # Cause a (text) conflict 603 iota_path_1 = os.path.join(wc_dir_1, 'iota') 604 iota_path_2 = os.path.join(wc_dir_2, 'iota') 605 svntest.main.file_write(iota_path_1, 'Modified iota text') 606 svntest.main.file_write(iota_path_2, 'Conflicting iota text') 607 svntest.main.run_svn(None, 608 'commit', '-m', 'r2', wc_dir_1) 609 svntest.main.run_svn(None, 610 'update', wc_dir_2) 611 612 # Resolve the conflict "manually" 613 svntest.main.file_write(iota_path_2, 'Modified iota text') 614 os.remove(iota_path_2 + '.mine') 615 os.remove(iota_path_2 + '.r1') 616 os.remove(iota_path_2 + '.r2') 617 618 # Verify no output from status, diff, or revert 619 svntest.actions.run_and_verify_svn([], [], "status", wc_dir_2) 620 svntest.actions.run_and_verify_svn([], [], "diff", wc_dir_2) 621 svntest.actions.run_and_verify_svn([], [], "revert", "-R", wc_dir_2) 622 623def revert_after_manual_conflict_resolution__prop(sbox): 624 "revert after manual property-conflict resolution" 625 626 # Make two working copies 627 sbox.build() 628 wc_dir_1 = sbox.wc_dir 629 wc_dir_2 = sbox.add_wc_path('other') 630 svntest.actions.duplicate_dir(wc_dir_1, wc_dir_2) 631 632 # Cause a (property) conflict 633 iota_path_1 = os.path.join(wc_dir_1, 'iota') 634 iota_path_2 = os.path.join(wc_dir_2, 'iota') 635 svntest.main.run_svn(None, 'propset', 'foo', '1', iota_path_1) 636 svntest.main.run_svn(None, 'propset', 'foo', '2', iota_path_2) 637 svntest.main.run_svn(None, 638 'commit', '-m', 'r2', wc_dir_1) 639 svntest.main.run_svn(None, 640 'update', wc_dir_2) 641 642 # Resolve the conflict "manually" 643 svntest.main.run_svn(None, 'propset', 'foo', '1', iota_path_2) 644 os.remove(iota_path_2 + '.prej') 645 646 # Verify no output from status, diff, or revert 647 svntest.actions.run_and_verify_svn([], [], "status", wc_dir_2) 648 svntest.actions.run_and_verify_svn([], [], "diff", wc_dir_2) 649 svntest.actions.run_and_verify_svn([], [], "revert", "-R", wc_dir_2) 650 651def revert_propset__dir(sbox): 652 "revert a simple propset on a dir" 653 654 sbox.build(read_only = True) 655 wc_dir = sbox.wc_dir 656 a_path = os.path.join(wc_dir, 'A') 657 svntest.main.run_svn(None, 'propset', 'foo', 'x', a_path) 658 run_and_verify_revert([a_path]) 659 660def revert_propset__file(sbox): 661 "revert a simple propset on a file" 662 663 sbox.build(read_only = True) 664 wc_dir = sbox.wc_dir 665 iota_path = os.path.join(wc_dir, 'iota') 666 svntest.main.run_svn(None, 'propset', 'foo', 'x', iota_path) 667 run_and_verify_revert([iota_path]) 668 669def revert_propdel__dir(sbox): 670 "revert a simple propdel on a dir" 671 672 sbox.build() 673 wc_dir = sbox.wc_dir 674 a_path = os.path.join(wc_dir, 'A') 675 svntest.main.run_svn(None, 'propset', 'foo', 'x', a_path) 676 svntest.main.run_svn(None, 677 'commit', '-m', 'ps', a_path) 678 svntest.main.run_svn(None, 'propdel', 'foo', a_path) 679 run_and_verify_revert([a_path]) 680 681def revert_propdel__file(sbox): 682 "revert a simple propdel on a file" 683 684 sbox.build() 685 wc_dir = sbox.wc_dir 686 iota_path = os.path.join(wc_dir, 'iota') 687 svntest.main.run_svn(None, 'propset', 'foo', 'x', iota_path) 688 svntest.main.run_svn(None, 689 'commit', '-m', 'ps', iota_path) 690 svntest.main.run_svn(None, 'propdel', 'foo', iota_path) 691 run_and_verify_revert([iota_path]) 692 693def revert_replaced_with_history_file_1(sbox): 694 "revert a committed replace-with-history == no-op" 695 696 sbox.build() 697 wc_dir = sbox.wc_dir 698 iota_path = os.path.join(wc_dir, 'iota') 699 mu_path = os.path.join(wc_dir, 'A', 'mu') 700 701 # Remember the original text of 'mu' 702 exit_code, text_r1, err = svntest.actions.run_and_verify_svn(None, [], 703 'cat', mu_path) 704 # delete mu and replace it with a copy of iota 705 svntest.main.run_svn(None, 'rm', mu_path) 706 svntest.main.run_svn(None, 'mv', iota_path, mu_path) 707 708 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 709 expected_status.tweak('A/mu', status=' ', wc_rev=2) 710 expected_status.remove('iota') 711 expected_output = svntest.wc.State(wc_dir, { 712 'iota': Item(verb='Deleting'), 713 'A/mu': Item(verb='Replacing'), 714 }) 715 svntest.actions.run_and_verify_commit(wc_dir, 716 expected_output, 717 expected_status) 718 719 # update the working copy 720 svntest.main.run_svn(None, 'up', wc_dir) 721 722 # now revert back to the state in r1 723 expected_output = svntest.wc.State(wc_dir, { 724 'A/mu': Item(status='R '), 725 'iota': Item(status='A ') 726 }) 727 expected_mergeinfo_output = svntest.wc.State(wc_dir, { 728 '': Item(status=' U'), 729 }) 730 expected_elision_output = svntest.wc.State(wc_dir, { 731 '': Item(status=' U'), 732 }) 733 expected_status = svntest.actions.get_virginal_state(wc_dir, 2) 734 expected_status.tweak('A/mu', status='R ', copied='+', wc_rev='-') 735 expected_status.tweak('iota', status='A ', copied='+', wc_rev='-') 736 expected_skip = wc.State(wc_dir, { }) 737 expected_disk = svntest.main.greek_state.copy() 738 svntest.actions.run_and_verify_merge(wc_dir, '2', '1', 739 sbox.repo_url, None, 740 expected_output, 741 expected_mergeinfo_output, 742 expected_elision_output, 743 expected_disk, 744 expected_status, 745 expected_skip) 746 747 # and commit in r3 748 expected_status = svntest.actions.get_virginal_state(wc_dir, 2) 749 expected_status.tweak('A/mu', status=' ', wc_rev=3) 750 expected_status.tweak('iota', status=' ', wc_rev=3) 751 expected_output = svntest.wc.State(wc_dir, { 752 'iota': Item(verb='Adding'), 753 'A/mu': Item(verb='Replacing'), 754 }) 755 svntest.actions.run_and_verify_commit(wc_dir, 756 expected_output, 757 expected_status) 758 759 # Verify the content of 'mu' 760 svntest.actions.run_and_verify_svn(text_r1, [], 'cat', mu_path) 761 762 # situation: no local modifications, mu has its original content again. 763 764 # revert 'mu' locally, shouldn't change a thing. 765 svntest.actions.run_and_verify_svn([], [], "revert", 766 mu_path) 767 768 # Verify the content of 'mu' 769 svntest.actions.run_and_verify_svn(text_r1, [], 'cat', mu_path) 770 771#---------------------------------------------------------------------- 772# Test for issue #2804. 773@Issue(2804) 774def status_of_missing_dir_after_revert(sbox): 775 "status after schedule-delete, revert, and local rm" 776 777 sbox.build(read_only = True) 778 wc_dir = sbox.wc_dir 779 A_D_G_path = os.path.join(wc_dir, "A", "D", "G") 780 781 svntest.actions.run_and_verify_svn(None, [], "rm", A_D_G_path) 782 run_and_verify_revert([A_D_G_path]) 783 784 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 785 expected_status.tweak('A/D/G/rho', 'A/D/G/pi', 'A/D/G/tau', 786 status='D ') 787 svntest.actions.run_and_verify_status(wc_dir, expected_status) 788 789 svntest.main.safe_rmtree(A_D_G_path) 790 expected_status.tweak('A/D/G', status='! ') 791 792 svntest.actions.run_and_verify_status(wc_dir, expected_status) 793 794 # When using single-db, we can get back to the virginal state. 795 svntest.actions.run_and_verify_svn(None, [], "revert", 796 "-R", A_D_G_path) 797 798 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 799 svntest.actions.run_and_verify_status(wc_dir, expected_status) 800 801#---------------------------------------------------------------------- 802# Test for issue #2804 with replaced directory 803@Issue(2804) 804def status_of_missing_dir_after_revert_replaced_with_history_dir(sbox): 805 "status after replace+, revert, and local rm" 806 807 sbox.build() 808 wc_dir = sbox.wc_dir 809 repo_url = sbox.repo_url 810 811 # delete A/D/G and commit 812 G_path = os.path.join(wc_dir, "A", "D", "G") 813 svntest.actions.run_and_verify_svn(None, [], "rm", G_path) 814 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 815 expected_status.remove('A/D/G', 'A/D/G/rho', 'A/D/G/pi', 'A/D/G/tau') 816 expected_output = svntest.wc.State(wc_dir, { 817 'A/D/G': Item(verb='Deleting'), 818 }) 819 svntest.actions.run_and_verify_commit(wc_dir, 820 expected_output, 821 expected_status) 822 823 # copy A/D/G from A/B/E and commit 824 E_path = os.path.join(wc_dir, "A", "B", "E") 825 svntest.actions.run_and_verify_svn(None, [], "cp", E_path, G_path) 826 expected_status.add({ 827 'A/D/G' : Item(status=' ', wc_rev='3'), 828 'A/D/G/alpha' : Item(status=' ', wc_rev='3'), 829 'A/D/G/beta' : Item(status=' ', wc_rev='3') 830 }) 831 expected_output = svntest.wc.State(wc_dir, { 832 'A/D/G': Item(verb='Adding'), 833 }) 834 svntest.actions.run_and_verify_commit(wc_dir, 835 expected_output, 836 expected_status) 837 838 # update the working copy 839 svntest.main.run_svn(None, 'up', wc_dir) 840 841 # now rollback to r1, thereby reinstating the old 'G' 842 expected_output = svntest.wc.State(wc_dir, { 843 'A/D/G': Item(status='R '), 844 'A/D/G/rho': Item(status='A '), 845 'A/D/G/pi': Item(status='A '), 846 'A/D/G/tau': Item(status='A '), 847 }) 848 expected_mergeinfo_output = svntest.wc.State(wc_dir, { 849 '': Item(status=' U'), 850 }) 851 expected_elision_output = svntest.wc.State(wc_dir, { 852 '': Item(status=' U'), 853 }) 854 expected_status = svntest.actions.get_virginal_state(wc_dir, 3) 855 expected_status.tweak('A/D/G', status='R ', copied='+', wc_rev='-') 856 expected_status.tweak('A/D/G/rho', 857 'A/D/G/pi', 858 'A/D/G/tau', 859 copied='+', wc_rev='-') 860 expected_status.add({ 861 'A/D/G/alpha' : Item(status='D ', wc_rev='3'), 862 'A/D/G/beta' : Item(status='D ', wc_rev='3'), 863 }) 864 865 expected_skip = wc.State(wc_dir, { }) 866 expected_disk = svntest.main.greek_state.copy() 867 svntest.actions.run_and_verify_merge(wc_dir, '3', '1', 868 sbox.repo_url, None, 869 expected_output, 870 expected_mergeinfo_output, 871 expected_elision_output, 872 expected_disk, 873 expected_status, 874 expected_skip, 875 dry_run = 0) 876 877 # now test if the revert works ok 878 revert_paths = [G_path] + [os.path.join(G_path, child) 879 for child in ['alpha', 'beta', 'pi', 'rho', 'tau']] 880 881 run_and_verify_revert([G_path], ["-R"], revert_paths) 882 883 svntest.actions.run_and_verify_svn([], [], 884 "status", wc_dir) 885 886 svntest.main.safe_rmtree(G_path) 887 888 expected_output = svntest.verify.UnorderedOutput( 889 ["! " + G_path + "\n", 890 "! " + os.path.join(G_path, "alpha") + "\n", 891 "! " + os.path.join(G_path, "beta") + "\n"]) 892 svntest.actions.run_and_verify_svn(expected_output, [], "status", 893 wc_dir) 894 895# Test for issue #2928. 896@Issue(2928) 897def revert_replaced_with_history_file_2(sbox): 898 "reverted replace with history restores checksum" 899 900 sbox.build() 901 wc_dir = sbox.wc_dir 902 iota_path = os.path.join(wc_dir, 'iota') 903 mu_path = os.path.join(wc_dir, 'A', 'mu') 904 905 # Delete mu and replace it with a copy of iota 906 svntest.main.run_svn(None, 'rm', mu_path) 907 svntest.main.run_svn(None, 'cp', iota_path, mu_path) 908 909 # Revert mu. 910 svntest.main.run_svn(None, 'revert', mu_path) 911 912 # If we make local mods to the reverted mu the commit will 913 # fail if the checksum is incorrect. 914 svntest.main.file_write(mu_path, "new text") 915 expected_output = svntest.wc.State(wc_dir, { 916 'A/mu': Item(verb='Sending'), 917 }) 918 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 919 expected_status.tweak('A/mu', status=' ', wc_rev=2) 920 svntest.actions.run_and_verify_commit(wc_dir, 921 expected_output, 922 expected_status) 923 924#---------------------------------------------------------------------- 925 926def revert_tree_conflicts_in_updated_files(sbox): 927 "revert tree conflicts in updated files" 928 929 # See use cases 1-3 in notes/tree-conflicts/use-cases.txt for background. 930 931 svntest.actions.build_greek_tree_conflicts(sbox) 932 wc_dir = sbox.wc_dir 933 G = os.path.join(wc_dir, 'A', 'D', 'G') 934 G_pi = os.path.join(G, 'pi') 935 G_rho = os.path.join(G, 'rho') 936 G_tau = os.path.join(G, 'tau') 937 938 # Duplicate wc for tests 939 wc_dir_2 = sbox.add_wc_path('2') 940 svntest.actions.duplicate_dir(wc_dir, wc_dir_2) 941 G2 = os.path.join(wc_dir_2, 'A', 'D', 'G') 942 G2_pi = os.path.join(G2, 'pi') 943 G2_rho = os.path.join(G2, 'rho') 944 G2_tau = os.path.join(G2, 'tau') 945 946 # Expectations 947 expected_status = svntest.actions.get_virginal_state(wc_dir, 2) 948 expected_status.tweak('A/D/G/pi', status=' ') 949 expected_status.remove('A/D/G/rho') 950 expected_status.remove('A/D/G/tau') 951 952 expected_disk = svntest.main.greek_state.copy() 953 expected_disk.remove('A/D/G/rho') 954 expected_disk.tweak('A/D/G/pi', 955 contents="This is the file 'pi'.\nIncoming edit.\n") 956 expected_disk.remove('A/D/G/tau') 957 958 # Revert individually in wc 959 run_and_verify_revert([G_pi, G_rho, G_tau]) 960 svntest.actions.run_and_verify_status(wc_dir, expected_status) 961 svntest.actions.verify_disk(wc_dir, expected_disk) 962 963 # Expectations 964 expected_status.wc_dir = wc_dir_2 965 966 # Revert recursively in wc 2 967 run_and_verify_revert([G2], ['-R'], [G2_pi, G2_rho, G2_tau]) 968 svntest.actions.run_and_verify_status(wc_dir_2, expected_status) 969 svntest.actions.verify_disk(wc_dir_2, expected_disk) 970 971def revert_add_over_not_present_dir(sbox): 972 "reverting an add over not present directory" 973 974 sbox.build() 975 wc_dir = sbox.wc_dir 976 977 main.run_svn(None, 'rm', os.path.join(wc_dir, 'A/C')) 978 sbox.simple_commit(message='Deleted dir') 979 980 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 981 expected_status.remove('A/C') 982 svntest.actions.run_and_verify_status(wc_dir, expected_status) 983 984 main.run_svn(None, 'mkdir', os.path.join(wc_dir, 'A/C')) 985 986 # This failed in some WC-NG intermediate format (r927318-r958992). 987 main.run_svn(None, 'revert', os.path.join(wc_dir, 'A/C')) 988 989 svntest.actions.run_and_verify_status(wc_dir, expected_status) 990 991 992def revert_added_tree(sbox): 993 "revert an added tree fails" 994 995 sbox.build() 996 wc_dir = sbox.wc_dir 997 svntest.actions.run_and_verify_svn(None, [], 998 'mkdir', sbox.ospath('X'), sbox.ospath('X/Y')) 999 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 1000 expected_status.add({ 1001 'X' : Item(status='A ', wc_rev=0), 1002 'X/Y' : Item(status='A ', wc_rev=0), 1003 }) 1004 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1005 1006 # Revert is non-recursive and fails, status is unchanged 1007 expected_error = '.*Try \'svn revert --depth infinity\'.*' 1008 svntest.actions.run_and_verify_svn(None, expected_error, 1009 'revert', sbox.ospath('X')) 1010 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1011 1012 1013@Issue(3834) 1014def revert_child_of_copy(sbox): 1015 "revert a child of a copied directory" 1016 1017 sbox.build() 1018 wc_dir = sbox.wc_dir 1019 svntest.actions.run_and_verify_svn(None, [], 1020 'cp', 1021 sbox.ospath('A/B/E'), 1022 sbox.ospath('A/B/E2')) 1023 1024 1025 svntest.main.file_append(sbox.ospath('A/B/E2/beta'), 'extra text\n') 1026 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 1027 expected_status.add({ 1028 'A/B/E2' : Item(status='A ', copied='+', wc_rev='-'), 1029 'A/B/E2/alpha' : Item(status=' ', copied='+', wc_rev='-'), 1030 'A/B/E2/beta' : Item(status='M ', copied='+', wc_rev='-'), 1031 }) 1032 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1033 1034 # First revert removes text change, child is still copied 1035 run_and_verify_revert(sbox.ospaths(['A/B/E2/beta'])) 1036 expected_status.tweak('A/B/E2/beta', status=' ') 1037 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1038 1039 # Second revert of child does nothing, child is still copied 1040 svntest.actions.run_and_verify_svn(None, [], 1041 'revert', sbox.ospath('A/B/E2/beta')) 1042 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1043 1044@Issue(3783) 1045def revert_non_recusive_after_delete(sbox): 1046 "non-recursive revert after delete" 1047 1048 sbox.build(read_only=True) 1049 wc_dir = sbox.wc_dir 1050 1051 svntest.actions.run_and_verify_svn(None, [], 'rm', sbox.ospath('A/B')) 1052 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 1053 expected_status.tweak('A/B', 'A/B/E', 'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', 1054 'A/B/lambda', status='D ') 1055 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1056 1057 # This appears to work but gets the op-depth wrong 1058 run_and_verify_revert(sbox.ospaths(['A/B'])) 1059 expected_status.tweak('A/B', status=' ') 1060 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1061 1062 svntest.actions.run_and_verify_svn(None, [], 1063 'mkdir', sbox.ospath('A/B/E')) 1064 expected_status.tweak('A/B/E', status='R ') 1065 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1066 1067 # Since the op-depth was wrong A/B/E erroneously remains deleted 1068 run_and_verify_revert(sbox.ospaths(['A/B/E'])) 1069 expected_status.tweak('A/B/E', status=' ') 1070 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1071 1072def revert_permissions_only(sbox): 1073 "permission-only reverts" 1074 1075 sbox.build() 1076 wc_dir = sbox.wc_dir 1077 1078 # Helpers pinched/adapted from lock_tests.py. Put them somewhere common? 1079 def check_writability(path, writable): 1080 bits = stat.S_IWGRP | stat.S_IWOTH | stat.S_IWRITE 1081 mode = os.stat(path)[0] 1082 if bool(mode & bits) != writable: 1083 raise svntest.Failure("path '%s' is unexpectedly %s (mode %o)" 1084 % (path, ["writable", "read-only"][writable], mode)) 1085 1086 def is_writable(path): 1087 "Raise if PATH is not writable." 1088 check_writability(path, True) 1089 1090 def is_readonly(path): 1091 "Raise if PATH is not readonly." 1092 check_writability(path, False) 1093 1094 def check_executability(path, executable): 1095 bits = stat.S_IXGRP | stat.S_IXOTH | stat.S_IEXEC 1096 mode = os.stat(path)[0] 1097 if bool(mode & bits) != executable: 1098 raise svntest.Failure("path '%s' is unexpectedly %s (mode %o)" 1099 % (path, 1100 ["executable", "non-executable"][executable], 1101 mode)) 1102 1103 def is_executable(path): 1104 "Raise if PATH is not executable." 1105 check_executability(path, True) 1106 1107 def is_non_executable(path): 1108 "Raise if PATH is executable." 1109 check_executability(path, False) 1110 1111 1112 os.chmod(sbox.ospath('A/B/E/alpha'), svntest.main.S_ALL_READ) # read-only 1113 is_readonly(sbox.ospath('A/B/E/alpha')) 1114 run_and_verify_revert(sbox.ospaths(['A/B/E/alpha'])) 1115 is_writable(sbox.ospath('A/B/E/alpha')) 1116 1117 if svntest.main.is_posix_os(): 1118 os.chmod(sbox.ospath('A/B/E/beta'), svntest.main.S_ALL_RWX) # executable 1119 is_executable(sbox.ospath('A/B/E/beta')) 1120 run_and_verify_revert(sbox.ospaths(['A/B/E/beta'])) 1121 is_non_executable(sbox.ospath('A/B/E/beta')) 1122 1123 svntest.actions.run_and_verify_svn(None, [], 1124 'propset', 'svn:needs-lock', '1', 1125 sbox.ospath('A/B/E/alpha')) 1126 svntest.actions.run_and_verify_svn(None, [], 1127 'propset', 'svn:executable', '1', 1128 sbox.ospath('A/B/E/beta')) 1129 1130 expected_output = svntest.wc.State(wc_dir, { 1131 'A/B/E/alpha': Item(verb='Sending'), 1132 'A/B/E/beta': Item(verb='Sending'), 1133 }) 1134 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 1135 expected_status.tweak('A/B/E/alpha', wc_rev='2') 1136 expected_status.tweak('A/B/E/beta', wc_rev='2') 1137 svntest.actions.run_and_verify_commit(wc_dir, 1138 expected_output, 1139 expected_status) 1140 1141 os.chmod(sbox.ospath('A/B/E/alpha'), svntest.main.S_ALL_RW) # not read-only 1142 is_writable(sbox.ospath('A/B/E/alpha')) 1143 run_and_verify_revert(sbox.ospaths(['A/B/E/alpha'])) 1144 is_readonly(sbox.ospath('A/B/E/alpha')) 1145 1146 if svntest.main.is_posix_os(): 1147 os.chmod(sbox.ospath('A/B/E/beta'), svntest.main.S_ALL_RW) # not executable 1148 is_non_executable(sbox.ospath('A/B/E/beta')) 1149 run_and_verify_revert(sbox.ospaths(['A/B/E/beta'])) 1150 is_executable(sbox.ospath('A/B/E/beta')) 1151 1152 # copied file is always writeable 1153 sbox.simple_update() 1154 expected_output = ["A %s\n" % sbox.ospath('A/B/E2')] 1155 svntest.actions.run_and_verify_svn(expected_output, [], 'copy', 1156 sbox.ospath('A/B/E'), 1157 sbox.ospath('A/B/E2')) 1158 is_writable(sbox.ospath('A/B/E2/alpha')) 1159 svntest.actions.run_and_verify_svn([], [], 1160 'revert', sbox.ospath('A/B/E2/alpha')) 1161 is_writable(sbox.ospath('A/B/E2/alpha')) 1162 1163@XFail() 1164@Issue(3851) 1165def revert_copy_depth_files(sbox): 1166 "revert a copy with depth=files" 1167 1168 sbox.build(read_only=True) 1169 wc_dir = sbox.wc_dir 1170 1171 svntest.actions.run_and_verify_svn(None, [], 1172 'copy', 1173 sbox.ospath('A/B/E'), 1174 sbox.ospath('A/B/E2')) 1175 1176 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 1177 expected_status.add({ 1178 'A/B/E2' : Item(status='A ', copied='+', wc_rev='-'), 1179 'A/B/E2/alpha' : Item(status=' ', copied='+', wc_rev='-'), 1180 'A/B/E2/beta' : Item(status=' ', copied='+', wc_rev='-'), 1181 }) 1182 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1183 1184 run_and_verify_revert(sbox.ospaths(['A/B/E2']), ['--depth', 'files'], 1185 sbox.ospaths(['A/B/E2', 'A/B/E2/alpha', 'A/B/E2/beta'])) 1186 1187 expected_status.remove('A/B/E2', 'A/B/E2/alpha', 'A/B/E2/beta') 1188 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1189 1190@XFail() 1191@Issue(3851) 1192def revert_nested_add_depth_immediates(sbox): 1193 "revert a nested add with depth=immediates" 1194 1195 sbox.build(read_only=True) 1196 wc_dir = sbox.wc_dir 1197 1198 svntest.actions.run_and_verify_svn(None, [], 1199 'mkdir', '--parents', sbox.ospath('A/X/Y')) 1200 1201 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 1202 expected_status.add({ 1203 'A/X' : Item(status='A ', wc_rev='0'), 1204 'A/X/Y' : Item(status='A ', wc_rev='0'), 1205 }) 1206 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1207 1208 run_and_verify_revert(sbox.ospaths(['A/X']), ['--depth', 'immediates'], 1209 sbox.ospaths(['A/X', 'A/X/Y'])) 1210 1211 expected_status.remove('A/X', 'A/X/Y') 1212 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1213 1214def create_superflous_actual_node(sbox): 1215 "create a superfluous actual node" 1216 1217 sbox.build() 1218 wc_dir = sbox.wc_dir 1219 1220 svntest.main.file_append(sbox.ospath('A/B/E/alpha'), 'their text\n') 1221 sbox.simple_commit() 1222 sbox.simple_update() 1223 1224 # Create a NODES row with op-depth>0 1225 svntest.actions.run_and_verify_svn(None, [], 1226 'copy', '-r', '1', 1227 sbox.repo_url + '/A/B/E/alpha', 1228 sbox.ospath('alpha')) 1229 1230 # Merge to create an ACTUAL with a conflict 1231 expected_status = svntest.actions.get_virginal_state(wc_dir, 2) 1232 expected_status.add({ 1233 'alpha' : Item(status='A ', copied='+', wc_rev='-'), 1234 }) 1235 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1236 svntest.main.file_append(sbox.ospath('alpha'), 'my text\n') 1237 svntest.actions.run_and_verify_svn(None, [], 1238 'merge', '--accept', 'postpone', 1239 '^/A/B/E/alpha', sbox.ospath('alpha')) 1240 expected_status.tweak('alpha', status='CM', entry_status='A ') 1241 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1242 1243 # Clear merge property and remove conflict files 1244 sbox.simple_propdel('svn:mergeinfo', 'alpha') 1245 os.remove(sbox.ospath('alpha.merge-left.r1')) 1246 os.remove(sbox.ospath('alpha.merge-right.r2')) 1247 os.remove(sbox.ospath('alpha.working')) 1248 1249 expected_status.tweak('alpha', status='A ') 1250 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1251 1252@Issue(3859) 1253@SkipUnless(svntest.main.server_has_mergeinfo) 1254def revert_empty_actual(sbox): 1255 "revert with superfluous actual node" 1256 1257 create_superflous_actual_node(sbox) 1258 wc_dir = sbox.wc_dir 1259 1260 # Non-recursive code path works 1261 run_and_verify_revert(sbox.ospaths(['alpha'])) 1262 1263 expected_status = svntest.actions.get_virginal_state(wc_dir, 2) 1264 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1265 1266@Issue(3859) 1267@SkipUnless(svntest.main.server_has_mergeinfo) 1268def revert_empty_actual_recursive(sbox): 1269 "recursive revert with superfluous actual node" 1270 1271 create_superflous_actual_node(sbox) 1272 wc_dir = sbox.wc_dir 1273 1274 # Recursive code path fails, the superfluous actual node suppresses the 1275 # notification 1276 run_and_verify_revert(sbox.ospaths(['alpha']), ['-R']) 1277 1278 expected_status = svntest.actions.get_virginal_state(wc_dir, 2) 1279 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1280 1281@Issue(3879) 1282def revert_tree_conflicts_with_replacements(sbox): 1283 "revert tree conflicts with replacements" 1284 1285 sbox.build() 1286 wc_dir = sbox.wc_dir 1287 wc = sbox.ospath 1288 1289 # Use case 1: local replace, incoming replace 1290 # A/mu 1291 # A/D/H --> A/D/H/chi, A/D/H/{loc,inc}_psi 1292 1293 # Use case 2: local edit, incoming replace 1294 # A/D/gamma 1295 # A/D/G --> A/D/G/pi, A/D/G/inc_rho 1296 1297 # Use case 3: local replace, incoming edit 1298 # A/B/lambda 1299 # A/B/E --> A/B/E/alpha, A/B/E/loc_beta 1300 1301 # Case 1: incoming replacements 1302 sbox.simple_rm('A/mu', 'A/D/H') 1303 file_write(wc('A/mu'), "A fresh file.\n") 1304 os.mkdir(wc('A/D/H')) 1305 file_write(wc('A/D/H/chi'), "A fresh file.\n") 1306 file_write(wc('A/D/H/inc_psi'), "A fresh file.\n") 1307 sbox.simple_add('A/mu', 'A/D/H') 1308 1309 # Case 2: incoming replacements 1310 sbox.simple_rm('A/D/gamma', 'A/D/G') 1311 file_write(wc('A/D/gamma'), "A fresh file.\n") 1312 os.mkdir(wc('A/D/G')) 1313 file_write(wc('A/D/G/pi'), "A fresh file.\n") 1314 file_write(wc('A/D/G/inc_rho'), "A fresh file.\n") 1315 sbox.simple_add('A/D/gamma','A/D/G') 1316 1317 # Case 3: incoming edits 1318 file_append(wc('A/B/lambda'), "Incoming!\n") 1319 file_write(wc('A/B/E/alpha'), "Incoming!.\n") 1320 1321 # Commit and roll back to r1. 1322 sbox.simple_commit() 1323 run_svn(None, 'up', wc_dir, '-r1', '-q') 1324 1325 # Case 1: local replacements 1326 sbox.simple_rm('A/mu', 'A/D/H') 1327 file_write(wc('A/mu'), "A fresh file.\n") 1328 os.mkdir(wc('A/D/H')) 1329 file_write(wc('A/D/H/chi'), "A fresh local file.\n") 1330 file_write(wc('A/D/H/loc_psi'), "A fresh local file.\n") 1331 sbox.simple_add('A/mu', 'A/D/H') 1332 1333 # Case 2: local edits 1334 file_append(wc('A/D/gamma'), "Local change.\n") 1335 file_append(wc('A/D/G/pi'), "Local change.\n") 1336 1337 # Case 3: local replacements 1338 sbox.simple_rm('A/B/lambda', 'A/B/E') 1339 file_write(wc('A/B/lambda'), "A fresh local file.\n") 1340 os.mkdir(wc('A/B/E')) 1341 file_write(wc('A/B/E/alpha'), "A fresh local file.\n") 1342 file_write(wc('A/B/E/loc_beta'), "A fresh local file.\n") 1343 sbox.simple_add('A/B/lambda', 'A/B/E') 1344 1345 # Update and check tree conflict status. 1346 run_svn(None, 'up', wc_dir) 1347 expected_status = svntest.wc.State(wc_dir, { 1348 '' : Item(status=' ', wc_rev=2), 1349 'A' : Item(status=' ', wc_rev=2), 1350 'A/B' : Item(status=' ', wc_rev=2), 1351 'A/B/E' : Item(status='R ', wc_rev=2, treeconflict='C'), 1352 'A/B/E/alpha' : Item(status='A ', wc_rev='-'), 1353 'A/B/E/beta' : Item(status='D ', wc_rev=2), 1354 'A/B/E/loc_beta' : Item(status='A ', wc_rev='-'), 1355 'A/B/F' : Item(status=' ', wc_rev=2), 1356 'A/B/lambda' : Item(status='R ', wc_rev=2, treeconflict='C'), 1357 'A/C' : Item(status=' ', wc_rev=2), 1358 'A/D' : Item(status=' ', wc_rev=2), 1359 'A/D/G' : Item(status='R ', wc_rev='-', copied='+', 1360 treeconflict='C'), 1361 'A/D/G/inc_rho' : Item(status='D ', wc_rev=2), 1362 'A/D/G/pi' : Item(status='M ', wc_rev='-', copied='+'), 1363 'A/D/G/rho' : Item(status=' ', wc_rev='-', copied='+'), 1364 'A/D/G/tau' : Item(status=' ', wc_rev='-', copied='+'), 1365 'A/D/H' : Item(status='R ', wc_rev=2, treeconflict='C'), 1366 'A/D/H/chi' : Item(status='A ', wc_rev='-'), 1367 'A/D/H/inc_psi' : Item(status='D ', wc_rev=2), 1368 'A/D/H/loc_psi' : Item(status='A ', wc_rev='-'), 1369 'A/D/gamma' : Item(status='R ', wc_rev='-', copied='+', 1370 treeconflict='C'), 1371 'A/mu' : Item(status='R ', wc_rev=2, treeconflict='C'), 1372 'iota' : Item(status=' ', wc_rev=2), 1373 }) 1374 svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status) 1375 1376 def cd_and_status_u(dir_target): 1377 was_cwd = os.getcwd() 1378 os.chdir(os.path.abspath(wc(dir_target))) 1379 run_svn(None, 'status', '-u') 1380 os.chdir(was_cwd) 1381 1382 cd_and_status_u('A') 1383 cd_and_status_u('A/D') 1384 1385 # Until r1102143, the following 'status -u' commands failed with "svn: 1386 # E165004: Two top-level reports with no target". 1387 cd_and_status_u('A/D/G') 1388 cd_and_status_u('A/D/H') 1389 1390 # Revert everything (i.e., accept "theirs-full"). 1391 reverted_paths = [ 1392 wc('A/B/E'), 1393 wc('A/B/E/alpha'), # incoming & local 1394 wc('A/B/E/beta'), 1395 wc('A/B/E/loc_beta'), 1396 wc('A/B/lambda'), 1397 wc('A/D/G'), 1398 wc('A/D/G/pi'), 1399 wc('A/D/G/inc_rho'), # incoming 1400 wc('A/D/G/rho'), 1401 wc('A/D/G/tau'), 1402 wc('A/D/H'), 1403 wc('A/D/H/chi'), 1404 wc('A/D/H/inc_psi'), # incoming 1405 wc('A/D/H/loc_psi'), 1406 wc('A/D/gamma'), 1407 wc('A/mu'), 1408 ] 1409 run_and_verify_revert([wc_dir], ['-R'], reverted_paths) 1410 1411 # Remove a few unversioned files that revert left behind. 1412 os.remove(wc('A/B/E/loc_beta')) 1413 os.remove(wc('A/D/H/loc_psi')) 1414 1415 # The update operation should have put all incoming items in place. 1416 expected_status = svntest.wc.State(wc_dir, { 1417 '' : Item(status=' ', wc_rev=2), 1418 'A' : Item(status=' ', wc_rev=2), 1419 'A/B' : Item(status=' ', wc_rev=2), 1420 'A/B/E' : Item(status=' ', wc_rev=2), 1421 'A/B/E/alpha' : Item(status=' ', wc_rev=2), 1422 'A/B/E/beta' : Item(status=' ', wc_rev=2), 1423 'A/B/F' : Item(status=' ', wc_rev=2), 1424 'A/B/lambda' : Item(status=' ', wc_rev=2), 1425 'A/C' : Item(status=' ', wc_rev=2), 1426 'A/D' : Item(status=' ', wc_rev=2), 1427 'A/D/G' : Item(status=' ', wc_rev=2), 1428 'A/D/G/inc_rho' : Item(status=' ', wc_rev=2), 1429 'A/D/G/pi' : Item(status=' ', wc_rev=2), 1430 'A/D/H' : Item(status=' ', wc_rev=2), 1431 'A/D/H/chi' : Item(status=' ', wc_rev=2), 1432 'A/D/H/inc_psi' : Item(status=' ', wc_rev=2), 1433 'A/D/gamma' : Item(status=' ', wc_rev=2), 1434 'A/mu' : Item(status=' ', wc_rev=2), 1435 'iota' : Item(status=' ', wc_rev=2), 1436 }) 1437 svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status) 1438 1439def create_no_text_change_conflict(sbox): 1440 "create conflict with no text change" 1441 1442 sbox.build() 1443 wc_dir = sbox.wc_dir 1444 1445 shutil.copyfile(sbox.ospath('A/B/E/alpha'), sbox.ospath('A/B/E/alpha-copy')) 1446 svntest.main.file_append(sbox.ospath('A/B/E/alpha'), 'their text\n') 1447 sbox.simple_commit() 1448 sbox.simple_update() 1449 1450 # Update to create a conflict 1451 svntest.main.file_append(sbox.ospath('A/B/E/alpha'), 'my text\n') 1452 svntest.actions.run_and_verify_svn(None, [], 1453 'up', '-r1', '--accept', 'postpone', 1454 wc_dir) 1455 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 1456 expected_status.tweak('A/B/E/alpha', status='C ') 1457 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1458 1459 # Reset the text with the file still marked as a conflict 1460 os.remove(sbox.ospath('A/B/E/alpha')) 1461 shutil.move(sbox.ospath('A/B/E/alpha-copy'), sbox.ospath('A/B/E/alpha')) 1462 1463@Issue(3859) 1464def revert_no_text_change_conflict(sbox): 1465 "revert conflict with no text change" 1466 1467 create_no_text_change_conflict(sbox) 1468 wc_dir = sbox.wc_dir 1469 1470 run_and_verify_revert(sbox.ospaths(['A/B/E/alpha'])) 1471 1472 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 1473 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1474 1475@Issue(3859) 1476def revert_no_text_change_conflict_recursive(sbox): 1477 "revert -R conflict with no text change" 1478 1479 create_no_text_change_conflict(sbox) 1480 wc_dir = sbox.wc_dir 1481 1482 run_and_verify_revert(sbox.ospaths(['A/B/E/alpha']), ['-R']) 1483 1484 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 1485 svntest.actions.run_and_verify_status(wc_dir, expected_status) 1486 1487@Issue(3938) 1488def revert_with_unversioned_targets(sbox): 1489 "revert with unversioned targets" 1490 1491 sbox.build() 1492 wc_dir = sbox.wc_dir 1493 1494 chi_path = sbox.ospath('A/D/H/chi') 1495 delta_path = sbox.ospath('A/D/H/delta') 1496 psi_path = sbox.ospath('A/D/H/psi') 1497 1498 chi_contents = "modified chi\n" 1499 delta_contents = "This is the unversioned file 'delta'.\n" 1500 psi_contents = "modified psi\n" 1501 1502 # touch delta 1503 with open(delta_path, 'w') as f: 1504 f.write(delta_contents) 1505 1506 # modify chi psi 1507 with open(chi_path, 'w') as f: 1508 f.write(chi_contents) 1509 with open(psi_path, 'w') as f: 1510 f.write(psi_contents) 1511 1512 # revert 1513 run_and_verify_revert([chi_path, delta_path, psi_path], [], 1514 [chi_path, psi_path], [delta_path]) 1515 1516 # verify status 1517 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 1518 expected_status.add({ 1519 'A/D/H/delta': Item(status='? '), 1520 }) 1521 svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status) 1522 1523 # verify disk 1524 expected_disk = svntest.main.greek_state.copy() 1525 expected_disk.add({ 1526 'A/D/H/delta': Item(delta_contents), 1527 }) 1528 svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True) 1529 1530def revert_nonexistent(sbox): 1531 'svn revert -R nonexistent' 1532 sbox.build(read_only=True) 1533 run_and_verify_revert(sbox.ospaths(['nonexistent']), ['-R'], 1534 [], sbox.ospaths(['nonexistent'])) 1535 1536@Issue(4168) 1537def revert_obstructing_wc(sbox): 1538 "revert with an obstructing working copy" 1539 1540 sbox.build(create_wc=False, read_only=True) 1541 wc_dir = sbox.wc_dir 1542 1543 expected_output = svntest.wc.State(wc_dir, {}) 1544 expected_disk = svntest.wc.State(wc_dir, {}) 1545 1546 # Checkout wc as depth empty 1547 svntest.actions.run_and_verify_checkout(sbox.repo_url, wc_dir, 1548 expected_output, expected_disk, 1549 [], 1550 '--depth', 'empty') 1551 1552 # And create an obstructing working copy as A 1553 svntest.actions.run_and_verify_checkout(sbox.repo_url, wc_dir + '/A', 1554 expected_output, expected_disk, 1555 [], 1556 '--depth', 'empty') 1557 1558 # Now try to fetch the entire wc, which will find an obstruction 1559 expected_output = svntest.wc.State(wc_dir, { 1560 'A' : Item(verb='Skipped'), 1561 'iota' : Item(status='A '), 1562 }) 1563 expected_status = svntest.wc.State(wc_dir, { 1564 '' : Item(status=' ', wc_rev='1'), 1565 'iota' : Item(status=' ', wc_rev='1'), 1566 # A is not versioned but exists 1567 }) 1568 1569 svntest.actions.run_and_verify_update(wc_dir, 1570 expected_output, None, 1571 expected_status, 1572 [], False, 1573 wc_dir, '--set-depth', 'infinity') 1574 1575 # Revert should do nothing (no local changes), and report the obstruction 1576 # (reporting the obstruction is nice for debugging, but not really required 1577 # in this specific case, as the node was not modified) 1578 svntest.actions.run_and_verify_svn("Skipped '.*A' -- .*obstruct.*", [], 1579 'revert', '-R', wc_dir) 1580 1581def revert_moved_dir_partial(sbox): 1582 "partial revert moved_dir" 1583 1584 sbox.build(read_only = True) 1585 1586 sbox.simple_move('A', 'A_') 1587 svntest.actions.run_and_verify_svn(None, [], 'revert', sbox.ospath('A')) 1588 1589@XFail() 1590@Issue(4798) 1591def revert_remove_added(sbox): 1592 "revert_remove_added" 1593 1594 sbox.build(empty=True, read_only=True) 1595 1596 # We'll test the items named with a '1' as direct targets to 'revert', 1597 # and items named with a '2' as items found by recursion. 1598 sbox.simple_mkdir('D1', 'D2') 1599 sbox.simple_add_text('This is a new file.', 1600 'D1/file', 'file1', 1601 'D2/file', 'file2') 1602 1603 run_and_verify_revert(sbox.ospaths(['D1']), ['--remove-added', '-R'], 1604 sbox.ospaths(['D1/file', 'D1'])) 1605 assert(not os.path.exists(sbox.ospath('D1'))) 1606 1607 run_and_verify_revert(sbox.ospaths(['file1']), ['--remove-added'], 1608 sbox.ospaths(['file1'])) 1609 assert(not os.path.exists(sbox.ospath('file1'))) 1610 1611 run_and_verify_revert(sbox.ospaths(['.']), ['--remove-added', '-R'], 1612 sbox.ospaths(['D2/file', 'D2', 'file2'])) 1613 assert(not os.path.exists(sbox.ospath('file2'))) 1614 assert(not os.path.exists(sbox.ospath('D2'))) 1615 1616 1617######################################################################## 1618# Run the tests 1619 1620 1621# list all tests here, starting with None: 1622test_list = [ None, 1623 revert_from_wc_root, 1624 revert_reexpand_keyword, 1625 revert_replaced_file_without_props, 1626 revert_moved_file, 1627 revert_wc_to_wc_replace_with_props, 1628 revert_file_merge_replace_with_history, 1629 revert_repos_to_wc_replace_with_props, 1630 revert_after_second_replace, 1631 revert_after_manual_conflict_resolution__text, 1632 revert_after_manual_conflict_resolution__prop, 1633 revert_propset__dir, 1634 revert_propset__file, 1635 revert_propdel__dir, 1636 revert_propdel__file, 1637 revert_replaced_with_history_file_1, 1638 status_of_missing_dir_after_revert, 1639 status_of_missing_dir_after_revert_replaced_with_history_dir, 1640 revert_replaced_with_history_file_2, 1641 revert_tree_conflicts_in_updated_files, 1642 revert_add_over_not_present_dir, 1643 revert_added_tree, 1644 revert_child_of_copy, 1645 revert_non_recusive_after_delete, 1646 revert_permissions_only, 1647 revert_copy_depth_files, 1648 revert_nested_add_depth_immediates, 1649 revert_empty_actual, 1650 revert_tree_conflicts_with_replacements, 1651 revert_empty_actual_recursive, 1652 revert_no_text_change_conflict, 1653 revert_no_text_change_conflict_recursive, 1654 revert_with_unversioned_targets, 1655 revert_nonexistent, 1656 revert_obstructing_wc, 1657 revert_moved_dir_partial, 1658 revert_remove_added, 1659 ] 1660 1661if __name__ == '__main__': 1662 svntest.main.run_tests(test_list) 1663 # NOTREACHED 1664 1665 1666### End of file. 1667