diff options
author | James McCoy <jamessan@debian.org> | 2018-07-31 22:26:52 -0400 |
---|---|---|
committer | James McCoy <jamessan@debian.org> | 2018-07-31 22:26:52 -0400 |
commit | e20a507113ff1126aeb4a97b806390ea377fe292 (patch) | |
tree | 0260b3a40387d7f994fbadaf22f1e9d3c080b09f /subversion/tests/cmdline/revert_tests.py | |
parent | c64debffb81d2fa17e9a72af7199ccf88b3cc556 (diff) |
New upstream version 1.10.2
Diffstat (limited to 'subversion/tests/cmdline/revert_tests.py')
-rwxr-xr-x | subversion/tests/cmdline/revert_tests.py | 1690 |
1 files changed, 1690 insertions, 0 deletions
diff --git a/subversion/tests/cmdline/revert_tests.py b/subversion/tests/cmdline/revert_tests.py new file mode 100755 index 0000000..39ce3c6 --- /dev/null +++ b/subversion/tests/cmdline/revert_tests.py @@ -0,0 +1,1690 @@ +#!/usr/bin/env python +# +# revert_tests.py: testing 'svn revert'. +# +# Subversion is a tool for revision control. +# See http://subversion.apache.org for more information. +# +# ==================================================================== +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +###################################################################### + +# General modules +import re, os, stat, shutil + +# Our testing module +import svntest +from svntest import wc, main, actions +from svntest.actions import run_and_verify_svn +from svntest.main import file_append, file_write, run_svn + +# (abbreviation) +Skip = svntest.testcase.Skip_deco +SkipUnless = svntest.testcase.SkipUnless_deco +XFail = svntest.testcase.XFail_deco +Issues = svntest.testcase.Issues_deco +Issue = svntest.testcase.Issue_deco +Wimp = svntest.testcase.Wimp_deco +Item = svntest.wc.StateItem + + +###################################################################### +# Helpers + +def revert_replacement_with_props(sbox, wc_copy): + """Helper implementing the core of + revert_{repos,wc}_to_wc_replace_with_props(). + + Uses a working copy (when wc_copy == True) or a URL (when wc_copy == + False) source to copy from.""" + + sbox.build() + wc_dir = sbox.wc_dir + + # Use a temp file to set properties with wildcards in their values + # otherwise Win32/VS2005 will expand them + prop_path = os.path.join(wc_dir, 'proptmp') + svntest.main.file_append(prop_path, '*') + + # Set props on file which is copy-source later on + pi_path = os.path.join(wc_dir, 'A', 'D', 'G', 'pi') + rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') + svntest.actions.run_and_verify_svn(None, [], + 'ps', 'phony-prop', '-F', prop_path, + pi_path) + os.remove(prop_path) + svntest.actions.run_and_verify_svn(None, [], + 'ps', 'svn:eol-style', 'LF', rho_path) + + # Verify props having been set + expected_disk = svntest.main.greek_state.copy() + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_disk.tweak('A/D/G/pi', + props={ 'phony-prop': '*' }) + expected_disk.tweak('A/D/G/rho', + props={ 'svn:eol-style': 'LF' }) + + svntest.actions.verify_disk(wc_dir, expected_disk, True) + + # Commit props + expected_output = svntest.wc.State(wc_dir, { + 'A/D/G/pi': Item(verb='Sending'), + 'A/D/G/rho': Item(verb='Sending'), + }) + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.tweak('A/D/G/pi', wc_rev='2') + expected_status.tweak('A/D/G/rho', wc_rev='2') + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status) + + # Bring wc into sync + svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) + + # File scheduled for deletion + svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path) + + # Status before attempting copies + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak('A/D/G/rho', status='D ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # The copy shouldn't fail + if wc_copy: + pi_src = os.path.join(wc_dir, 'A', 'D', 'G', 'pi') + else: + pi_src = sbox.repo_url + '/A/D/G/pi' + + svntest.actions.run_and_verify_svn(None, [], + 'cp', pi_src, rho_path) + + # Verify both content and props have been copied + if wc_copy: + props = { 'phony-prop' : '*' } + else: + props = { 'phony-prop' : '*' } + + expected_disk.tweak('A/D/G/rho', + contents="This is the file 'pi'.\n", + props=props) + svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True) + + # Now revert + expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + expected_status.tweak('A/D/G/rho', status=' ', copied=None, wc_rev='2') + expected_output = ["Reverted '" + rho_path + "'\n"] + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', '-R', wc_dir) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Check disk status + expected_disk = svntest.main.greek_state.copy() + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_disk.tweak('A/D/G/pi', + props={ 'phony-prop': '*' }) + expected_disk.tweak('A/D/G/rho', + props={ 'svn:eol-style': 'LF' }) + svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True) + + + + +###################################################################### +# Tests +# +# Each test must return on success or raise on failure. + + +#---------------------------------------------------------------------- + +def revert_from_wc_root(sbox): + "revert relative to wc root" + + sbox.build(read_only = True) + wc_dir = sbox.wc_dir + + os.chdir(wc_dir) + + # Mostly taken from basic_revert + # Modify some files and props. + beta_path = os.path.join('A', 'B', 'E', 'beta') + gamma_path = os.path.join('A', 'D', 'gamma') + iota_path = 'iota' + rho_path = os.path.join('A', 'D', 'G', 'rho') + zeta_path = os.path.join('A', 'D', 'H', 'zeta') + svntest.main.file_append(beta_path, "Added some text to 'beta'.\n") + svntest.main.file_append(iota_path, "Added some text to 'iota'.\n") + svntest.main.file_append(rho_path, "Added some text to 'rho'.\n") + svntest.main.file_append(zeta_path, "Added some text to 'zeta'.\n") + + svntest.actions.run_and_verify_svn(None, [], + 'add', zeta_path) + svntest.actions.run_and_verify_svn(None, [], + 'ps', 'random-prop', 'propvalue', + gamma_path) + svntest.actions.run_and_verify_svn(None, [], + 'ps', 'random-prop', 'propvalue', + iota_path) + svntest.actions.run_and_verify_svn(None, [], + 'ps', 'random-prop', 'propvalue', + '.') + svntest.actions.run_and_verify_svn(None, [], + 'ps', 'random-prop', 'propvalue', + 'A') + + # Verify modified status. + expected_output = svntest.actions.get_virginal_state('', 1) + expected_output.tweak('A/B/E/beta', 'A/D/G/rho', status='M ') + expected_output.tweak('iota', status='MM') + expected_output.tweak('', 'A/D/gamma', 'A', status=' M') + expected_output.add({ + 'A/D/H/zeta' : Item(status='A ', wc_rev=0), + }) + + svntest.actions.run_and_verify_status('', expected_output) + + # Run revert + svntest.actions.run_and_verify_svn(None, [], + 'revert', beta_path) + + svntest.actions.run_and_verify_svn(None, [], + 'revert', gamma_path) + + svntest.actions.run_and_verify_svn(None, [], + 'revert', iota_path) + + svntest.actions.run_and_verify_svn(None, [], + 'revert', rho_path) + + svntest.actions.run_and_verify_svn(None, [], + 'revert', zeta_path) + + svntest.actions.run_and_verify_svn(None, [], + 'revert', '.') + + svntest.actions.run_and_verify_svn(None, [], + 'revert', 'A') + + # Verify unmodified status. + expected_output = svntest.actions.get_virginal_state('', 1) + + svntest.actions.run_and_verify_status('', expected_output) + +@Issue(1663) +def revert_reexpand_keyword(sbox): + "revert reexpands manually contracted keyword" + + # This is for issue #1663. The bug is that if the only difference + # between a locally modified working file and the base version of + # same was that the former had a contracted keyword that would be + # expanded in the latter, then 'svn revert' wouldn't notice the + # difference, and therefore wouldn't revert. And why wouldn't it + # notice? Since text bases are always stored with keywords + # contracted, and working files are contracted before comparison + # with text base, there would appear to be no difference when the + # contraction is the only difference. For most commands, this is + # correct -- but revert's job is to restore the working file, not + # the text base. + + sbox.build() + wc_dir = sbox.wc_dir + newfile_path = os.path.join(wc_dir, "newfile") + unexpanded_contents = "This is newfile: $Rev$.\n" + + # Put an unexpanded keyword into iota. + svntest.main.file_write(newfile_path, unexpanded_contents) + + # Commit, without svn:keywords property set. + svntest.main.run_svn(None, 'add', newfile_path) + svntest.main.run_svn(None, + 'commit', '-m', 'r2', newfile_path) + + # Set the property and commit. This should expand the keyword. + svntest.main.run_svn(None, 'propset', 'svn:keywords', 'rev', newfile_path) + svntest.main.run_svn(None, + 'commit', '-m', 'r3', newfile_path) + + # Verify that the keyword got expanded. + def check_expanded(path): + fp = open(path, 'r') + lines = fp.readlines() + fp.close() + if lines[0] != "This is newfile: $Rev: 3 $.\n": + raise svntest.Failure + + check_expanded(newfile_path) + + # Now un-expand the keyword again. + svntest.main.file_write(newfile_path, unexpanded_contents) + + # Revert the file. The keyword should reexpand. + svntest.main.run_svn(None, 'revert', newfile_path) + + # Verify that the keyword got re-expanded. + check_expanded(newfile_path) + + # Ok, the first part of this test was written in 2004. We are now in 2011 + # and note that there is more to test: + + # If the recorded timestamp and size match the file then revert won't + # reinstall the file as the file was not modified when last compared in + # the repository normal form. + # + # The easiest way to get the information recorded would be calling cleanup, + # because that 'repairs' the recorded information. But some developers + # (including me) would call that cheating, so I just use a failed commit. + + # Un-expand the keyword again. + svntest.main.file_write(newfile_path, unexpanded_contents) + + # And now we trick svn in ignoring the file on newfile_path + newfile2_path = newfile_path + '2' + svntest.main.file_write(newfile2_path, 'This is file 2') + svntest.main.run_svn(None, 'add', newfile2_path) + os.remove(newfile2_path) + + # This commit fails because newfile2_path is missing, but only after + # we call svn_wc__internal_file_modified_p() on new_file. + svntest.actions.run_and_verify_commit(wc_dir, None, None, ".*2' is scheduled"+ + " for addition, but is missing.*", + newfile_path, newfile2_path, + '-m', "Shouldn't be committed") + + # Revert the file. The file is not reverted! + svntest.actions.run_and_verify_svn([], [], 'revert', newfile_path) + + +#---------------------------------------------------------------------- +# Regression test for issue #1775: +# Should be able to revert a file with no properties i.e. no prop-base +@Issue(1775) +def revert_replaced_file_without_props(sbox): + "revert a replaced file with no properties" + + sbox.build() + wc_dir = sbox.wc_dir + + file1_path = os.path.join(wc_dir, 'file1') + + # Add a new file, file1, that has no prop-base + svntest.main.file_append(file1_path, "This is the file 'file1' revision 2.") + svntest.actions.run_and_verify_svn(None, [], 'add', file1_path) + + # commit file1 + expected_output = svntest.wc.State(wc_dir, { + 'file1' : Item(verb='Adding') + }) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'file1' : Item(status=' ', wc_rev=2), + }) + + svntest.actions.run_and_verify_commit(wc_dir, expected_output, + expected_status) + + # delete file1 + svntest.actions.run_and_verify_svn(None, [], 'rm', file1_path) + + # test that file1 is scheduled for deletion. + expected_status.tweak('file1', status='D ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # recreate and add file1 + svntest.main.file_append(file1_path, "This is the file 'file1' revision 3.") + svntest.actions.run_and_verify_svn(None, [], 'add', file1_path) + + # Test to see if file1 is schedule for replacement + expected_status.tweak('file1', status='R ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # revert file1 + svntest.actions.run_and_verify_svn(["Reverted '" + file1_path + "'\n"], + [], 'revert', file1_path) + + # test that file1 really was reverted + expected_status.tweak('file1', status=' ', wc_rev=2) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +#---------------------------------------------------------------------- +# Note that issue #876 has been rejected. This now basically tests that +# reverting the delete side of a move does *not* also revert the copy side. +@Issue(876) +def revert_moved_file(sbox): + "revert a moved file" + + # svntest.factory.make(sbox, """svn mv iota iota_moved + # svn st + # svn revert iota + # svn st + # """) + + sbox.build() + wc_dir = sbox.wc_dir + + iota = os.path.join(wc_dir, 'iota') + iota_moved = os.path.join(wc_dir, 'iota_moved') + + # svn mv iota iota_moved + expected_stdout = svntest.verify.UnorderedOutput([ + 'A ' + iota_moved + '\n', + 'D ' + iota + '\n', + ]) + + actions.run_and_verify_svn2(expected_stdout, [], 0, 'mv', iota, + iota_moved) + + # svn st + expected_status = actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'iota_moved' : Item(status='A ', copied='+', wc_rev='-', + moved_from='iota'), + }) + expected_status.tweak('iota', status='D ', moved_to='iota_moved') + + actions.run_and_verify_unquiet_status(wc_dir, expected_status) + + # svn revert iota + expected_stdout = ["Reverted '" + iota + "'\n"] + + actions.run_and_verify_svn2(expected_stdout, [], 0, 'revert', + iota) + + # svn st + expected_status.tweak('iota', status=' ', moved_to=None) + expected_status.tweak('iota_moved', moved_from=None) + + actions.run_and_verify_unquiet_status(wc_dir, expected_status) + + +#---------------------------------------------------------------------- +# Test for issue 2135 +# +# It is like merge_file_replace (in merge_tests.py), but reverts file +# instead of commit. +@Issue(2135) +def revert_file_merge_replace_with_history(sbox): + "revert a merge replacement of file with history" + + sbox.build() + wc_dir = sbox.wc_dir + + # File scheduled for deletion + rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') + svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.tweak('A/D/G/rho', status='D ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + expected_output = svntest.wc.State(wc_dir, { + 'A/D/G/rho': Item(verb='Deleting'), + }) + + expected_status.remove('A/D/G/rho') + + # Commit rev 2 + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status) + # create new rho file + svntest.main.file_write(rho_path, "new rho\n") + + # Add the new file + svntest.actions.run_and_verify_svn(None, [], 'add', rho_path) + + # Commit revsion 3 + expected_status.add({ + 'A/D/G/rho' : Item(status='A ', wc_rev='0') + }) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + expected_output = svntest.wc.State(wc_dir, { + 'A/D/G/rho': Item(verb='Adding'), + }) + + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + None) + + # Update working copy + expected_output = svntest.wc.State(wc_dir, {}) + expected_disk = svntest.main.greek_state.copy() + expected_disk.tweak('A/D/G/rho', contents='new rho\n' ) + expected_status.tweak(wc_rev='3') + expected_status.tweak('A/D/G/rho', status=' ') + + svntest.actions.run_and_verify_update(wc_dir, + expected_output, + expected_disk, + expected_status) + + # merge changes from r3:1 + expected_output = svntest.wc.State(wc_dir, { + 'A/D/G/rho': Item(status='R ') + }) + expected_mergeinfo_output = svntest.wc.State(wc_dir, { + '' : Item(status=' U') + }) + expected_elision_output = svntest.wc.State(wc_dir, { + '' : Item(status=' U') + }) + expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-') + expected_skip = wc.State(wc_dir, { }) + expected_disk.tweak('A/D/G/rho', contents="This is the file 'rho'.\n") + svntest.actions.run_and_verify_merge(wc_dir, '3', '1', + sbox.repo_url, None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip) + + # Now revert + svntest.actions.run_and_verify_svn(None, + [], 'revert', rho_path) + + # test that rho really was reverted + expected_status.tweak('A/D/G/rho', copied=None, status=' ', wc_rev=3) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + expected_disk.tweak('A/D/G/rho', contents="new rho\n") + svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True) + + # Make sure the revert removed the copy from information. + expected_infos = [ + { 'Copied' : None } + ] + svntest.actions.run_and_verify_info(expected_infos, rho_path) + +def revert_wc_to_wc_replace_with_props(sbox): + "revert svn cp PATH PATH replace file with props" + + revert_replacement_with_props(sbox, 1) + +def revert_repos_to_wc_replace_with_props(sbox): + "revert svn cp URL PATH replace file with props" + + revert_replacement_with_props(sbox, 0) + +def revert_after_second_replace(sbox): + "revert file after second replace" + + sbox.build(read_only = True) + wc_dir = sbox.wc_dir + + # File scheduled for deletion + rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho') + svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path) + + # Status before attempting copy + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.tweak('A/D/G/rho', status='D ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Replace file for the first time + pi_src = os.path.join(wc_dir, 'A', 'D', 'G', 'pi') + + svntest.actions.run_and_verify_svn(None, [], + 'cp', pi_src, rho_path) + + expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Now delete replaced file. + svntest.actions.run_and_verify_svn(None, [], 'rm', '--force', rho_path) + + # Status should be same as after first delete + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.tweak('A/D/G/rho', status='D ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Replace file for the second time + pi_src = os.path.join(wc_dir, 'A', 'D', 'G', 'pi') + + svntest.actions.run_and_verify_svn(None, [], 'cp', pi_src, rho_path) + + expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Now revert + svntest.actions.run_and_verify_svn(None, [], + 'revert', '-R', wc_dir) + + # Check disk status + expected_disk = svntest.main.greek_state.copy() + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True) + + +#---------------------------------------------------------------------- +# Tests for issue #2517. +# +# Manual conflict resolution leads to spurious revert report. +@Issue(2517) +def revert_after_manual_conflict_resolution__text(sbox): + "revert after manual text-conflict resolution" + + # Make two working copies + sbox.build() + wc_dir_1 = sbox.wc_dir + wc_dir_2 = sbox.add_wc_path('other') + svntest.actions.duplicate_dir(wc_dir_1, wc_dir_2) + + # Cause a (text) conflict + iota_path_1 = os.path.join(wc_dir_1, 'iota') + iota_path_2 = os.path.join(wc_dir_2, 'iota') + svntest.main.file_write(iota_path_1, 'Modified iota text') + svntest.main.file_write(iota_path_2, 'Conflicting iota text') + svntest.main.run_svn(None, + 'commit', '-m', 'r2', wc_dir_1) + svntest.main.run_svn(None, + 'update', wc_dir_2) + + # Resolve the conflict "manually" + svntest.main.file_write(iota_path_2, 'Modified iota text') + os.remove(iota_path_2 + '.mine') + os.remove(iota_path_2 + '.r1') + os.remove(iota_path_2 + '.r2') + + # Verify no output from status, diff, or revert + svntest.actions.run_and_verify_svn([], [], "status", wc_dir_2) + svntest.actions.run_and_verify_svn([], [], "diff", wc_dir_2) + svntest.actions.run_and_verify_svn([], [], "revert", "-R", wc_dir_2) + +def revert_after_manual_conflict_resolution__prop(sbox): + "revert after manual property-conflict resolution" + + # Make two working copies + sbox.build() + wc_dir_1 = sbox.wc_dir + wc_dir_2 = sbox.add_wc_path('other') + svntest.actions.duplicate_dir(wc_dir_1, wc_dir_2) + + # Cause a (property) conflict + iota_path_1 = os.path.join(wc_dir_1, 'iota') + iota_path_2 = os.path.join(wc_dir_2, 'iota') + svntest.main.run_svn(None, 'propset', 'foo', '1', iota_path_1) + svntest.main.run_svn(None, 'propset', 'foo', '2', iota_path_2) + svntest.main.run_svn(None, + 'commit', '-m', 'r2', wc_dir_1) + svntest.main.run_svn(None, + 'update', wc_dir_2) + + # Resolve the conflict "manually" + svntest.main.run_svn(None, 'propset', 'foo', '1', iota_path_2) + os.remove(iota_path_2 + '.prej') + + # Verify no output from status, diff, or revert + svntest.actions.run_and_verify_svn([], [], "status", wc_dir_2) + svntest.actions.run_and_verify_svn([], [], "diff", wc_dir_2) + svntest.actions.run_and_verify_svn([], [], "revert", "-R", wc_dir_2) + +def revert_propset__dir(sbox): + "revert a simple propset on a dir" + + sbox.build(read_only = True) + wc_dir = sbox.wc_dir + a_path = os.path.join(wc_dir, 'A') + svntest.main.run_svn(None, 'propset', 'foo', 'x', a_path) + expected_output = re.escape("Reverted '" + a_path + "'") + svntest.actions.run_and_verify_svn(expected_output, [], "revert", + a_path) + +def revert_propset__file(sbox): + "revert a simple propset on a file" + + sbox.build(read_only = True) + wc_dir = sbox.wc_dir + iota_path = os.path.join(wc_dir, 'iota') + svntest.main.run_svn(None, 'propset', 'foo', 'x', iota_path) + expected_output = re.escape("Reverted '" + iota_path + "'") + svntest.actions.run_and_verify_svn(expected_output, [], "revert", + iota_path) + +def revert_propdel__dir(sbox): + "revert a simple propdel on a dir" + + sbox.build() + wc_dir = sbox.wc_dir + a_path = os.path.join(wc_dir, 'A') + svntest.main.run_svn(None, 'propset', 'foo', 'x', a_path) + svntest.main.run_svn(None, + 'commit', '-m', 'ps', a_path) + svntest.main.run_svn(None, 'propdel', 'foo', a_path) + expected_output = re.escape("Reverted '" + a_path + "'") + svntest.actions.run_and_verify_svn(expected_output, [], "revert", + a_path) + +def revert_propdel__file(sbox): + "revert a simple propdel on a file" + + sbox.build() + wc_dir = sbox.wc_dir + iota_path = os.path.join(wc_dir, 'iota') + svntest.main.run_svn(None, 'propset', 'foo', 'x', iota_path) + svntest.main.run_svn(None, + 'commit', '-m', 'ps', iota_path) + svntest.main.run_svn(None, 'propdel', 'foo', iota_path) + expected_output = re.escape("Reverted '" + iota_path + "'") + svntest.actions.run_and_verify_svn(expected_output, [], "revert", + iota_path) + +def revert_replaced_with_history_file_1(sbox): + "revert a committed replace-with-history == no-op" + + sbox.build() + wc_dir = sbox.wc_dir + iota_path = os.path.join(wc_dir, 'iota') + mu_path = os.path.join(wc_dir, 'A', 'mu') + + # Remember the original text of 'mu' + exit_code, text_r1, err = svntest.actions.run_and_verify_svn(None, [], + 'cat', mu_path) + # delete mu and replace it with a copy of iota + svntest.main.run_svn(None, 'rm', mu_path) + svntest.main.run_svn(None, 'mv', iota_path, mu_path) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.tweak('A/mu', status=' ', wc_rev=2) + expected_status.remove('iota') + expected_output = svntest.wc.State(wc_dir, { + 'iota': Item(verb='Deleting'), + 'A/mu': Item(verb='Replacing'), + }) + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status) + + # update the working copy + svntest.main.run_svn(None, 'up', wc_dir) + + # now revert back to the state in r1 + expected_output = svntest.wc.State(wc_dir, { + 'A/mu': Item(status='R '), + 'iota': Item(status='A ') + }) + expected_mergeinfo_output = svntest.wc.State(wc_dir, { + '': Item(status=' U'), + }) + expected_elision_output = svntest.wc.State(wc_dir, { + '': Item(status=' U'), + }) + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak('A/mu', status='R ', copied='+', wc_rev='-') + expected_status.tweak('iota', status='A ', copied='+', wc_rev='-') + expected_skip = wc.State(wc_dir, { }) + expected_disk = svntest.main.greek_state.copy() + svntest.actions.run_and_verify_merge(wc_dir, '2', '1', + sbox.repo_url, None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip) + + # and commit in r3 + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak('A/mu', status=' ', wc_rev=3) + expected_status.tweak('iota', status=' ', wc_rev=3) + expected_output = svntest.wc.State(wc_dir, { + 'iota': Item(verb='Adding'), + 'A/mu': Item(verb='Replacing'), + }) + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status) + + # Verify the content of 'mu' + svntest.actions.run_and_verify_svn(text_r1, [], 'cat', mu_path) + + # situation: no local modifications, mu has its original content again. + + # revert 'mu' locally, shouldn't change a thing. + svntest.actions.run_and_verify_svn([], [], "revert", + mu_path) + + # Verify the content of 'mu' + svntest.actions.run_and_verify_svn(text_r1, [], 'cat', mu_path) + +#---------------------------------------------------------------------- +# Test for issue #2804. +@Issue(2804) +def status_of_missing_dir_after_revert(sbox): + "status after schedule-delete, revert, and local rm" + + sbox.build(read_only = True) + wc_dir = sbox.wc_dir + A_D_G_path = os.path.join(wc_dir, "A", "D", "G") + + svntest.actions.run_and_verify_svn(None, [], "rm", A_D_G_path) + expected_output = re.escape("Reverted '" + A_D_G_path + "'") + svntest.actions.run_and_verify_svn(expected_output, [], "revert", + A_D_G_path) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.tweak('A/D/G/rho', 'A/D/G/pi', 'A/D/G/tau', + status='D ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + svntest.main.safe_rmtree(A_D_G_path) + expected_status.tweak('A/D/G', status='! ') + + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # When using single-db, we can get back to the virginal state. + svntest.actions.run_and_verify_svn(None, [], "revert", + "-R", A_D_G_path) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +#---------------------------------------------------------------------- +# Test for issue #2804 with replaced directory +@Issue(2804) +def status_of_missing_dir_after_revert_replaced_with_history_dir(sbox): + "status after replace+, revert, and local rm" + + sbox.build() + wc_dir = sbox.wc_dir + repo_url = sbox.repo_url + + # delete A/D/G and commit + G_path = os.path.join(wc_dir, "A", "D", "G") + svntest.actions.run_and_verify_svn(None, [], "rm", G_path) + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.remove('A/D/G', 'A/D/G/rho', 'A/D/G/pi', 'A/D/G/tau') + expected_output = svntest.wc.State(wc_dir, { + 'A/D/G': Item(verb='Deleting'), + }) + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status) + + # copy A/D/G from A/B/E and commit + E_path = os.path.join(wc_dir, "A", "B", "E") + svntest.actions.run_and_verify_svn(None, [], "cp", E_path, G_path) + expected_status.add({ + 'A/D/G' : Item(status=' ', wc_rev='3'), + 'A/D/G/alpha' : Item(status=' ', wc_rev='3'), + 'A/D/G/beta' : Item(status=' ', wc_rev='3') + }) + expected_output = svntest.wc.State(wc_dir, { + 'A/D/G': Item(verb='Adding'), + }) + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status) + + # update the working copy + svntest.main.run_svn(None, 'up', wc_dir) + + # now rollback to r1, thereby reinstating the old 'G' + expected_output = svntest.wc.State(wc_dir, { + 'A/D/G': Item(status='R '), + 'A/D/G/rho': Item(status='A '), + 'A/D/G/pi': Item(status='A '), + 'A/D/G/tau': Item(status='A '), + }) + expected_mergeinfo_output = svntest.wc.State(wc_dir, { + '': Item(status=' U'), + }) + expected_elision_output = svntest.wc.State(wc_dir, { + '': Item(status=' U'), + }) + expected_status = svntest.actions.get_virginal_state(wc_dir, 3) + expected_status.tweak('A/D/G', status='R ', copied='+', wc_rev='-') + expected_status.tweak('A/D/G/rho', + 'A/D/G/pi', + 'A/D/G/tau', + copied='+', wc_rev='-') + expected_status.add({ + 'A/D/G/alpha' : Item(status='D ', wc_rev='3'), + 'A/D/G/beta' : Item(status='D ', wc_rev='3'), + }) + + expected_skip = wc.State(wc_dir, { }) + expected_disk = svntest.main.greek_state.copy() + svntest.actions.run_and_verify_merge(wc_dir, '3', '1', + sbox.repo_url, None, + expected_output, + expected_mergeinfo_output, + expected_elision_output, + expected_disk, + expected_status, + expected_skip, + dry_run = 0) + + # now test if the revert works ok + revert_paths = [G_path] + [os.path.join(G_path, child) + for child in ['alpha', 'beta', 'pi', 'rho', 'tau']] + + expected_output = svntest.verify.UnorderedOutput([ + "Reverted '%s'\n" % path for path in revert_paths]) + + svntest.actions.run_and_verify_svn(expected_output, [], "revert", "-R", + G_path) + + svntest.actions.run_and_verify_svn([], [], + "status", wc_dir) + + svntest.main.safe_rmtree(G_path) + + expected_output = svntest.verify.UnorderedOutput( + ["! " + G_path + "\n", + "! " + os.path.join(G_path, "alpha") + "\n", + "! " + os.path.join(G_path, "beta") + "\n"]) + svntest.actions.run_and_verify_svn(expected_output, [], "status", + wc_dir) + +# Test for issue #2928. +@Issue(2928) +def revert_replaced_with_history_file_2(sbox): + "reverted replace with history restores checksum" + + sbox.build() + wc_dir = sbox.wc_dir + iota_path = os.path.join(wc_dir, 'iota') + mu_path = os.path.join(wc_dir, 'A', 'mu') + + # Delete mu and replace it with a copy of iota + svntest.main.run_svn(None, 'rm', mu_path) + svntest.main.run_svn(None, 'cp', iota_path, mu_path) + + # Revert mu. + svntest.main.run_svn(None, 'revert', mu_path) + + # If we make local mods to the reverted mu the commit will + # fail if the checksum is incorrect. + svntest.main.file_write(mu_path, "new text") + expected_output = svntest.wc.State(wc_dir, { + 'A/mu': Item(verb='Sending'), + }) + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.tweak('A/mu', status=' ', wc_rev=2) + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status) + +#---------------------------------------------------------------------- + +def revert_tree_conflicts_in_updated_files(sbox): + "revert tree conflicts in updated files" + + # See use cases 1-3 in notes/tree-conflicts/use-cases.txt for background. + + svntest.actions.build_greek_tree_conflicts(sbox) + wc_dir = sbox.wc_dir + G = os.path.join(wc_dir, 'A', 'D', 'G') + G_pi = os.path.join(G, 'pi') + G_rho = os.path.join(G, 'rho') + G_tau = os.path.join(G, 'tau') + + # Duplicate wc for tests + wc_dir_2 = sbox.add_wc_path('2') + svntest.actions.duplicate_dir(wc_dir, wc_dir_2) + G2 = os.path.join(wc_dir_2, 'A', 'D', 'G') + G2_pi = os.path.join(G2, 'pi') + G2_rho = os.path.join(G2, 'rho') + G2_tau = os.path.join(G2, 'tau') + + # Expectations + expected_output = svntest.verify.UnorderedOutput( + ["Reverted '%s'\n" % G_pi, + "Reverted '%s'\n" % G_rho, + "Reverted '%s'\n" % G_tau, + ]) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.tweak('A/D/G/pi', status=' ') + expected_status.remove('A/D/G/rho') + expected_status.remove('A/D/G/tau') + + expected_disk = svntest.main.greek_state.copy() + expected_disk.remove('A/D/G/rho') + expected_disk.tweak('A/D/G/pi', + contents="This is the file 'pi'.\nIncoming edit.\n") + expected_disk.remove('A/D/G/tau') + + # Revert individually in wc + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', G_pi, G_rho, G_tau) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + svntest.actions.verify_disk(wc_dir, expected_disk) + + # Expectations + expected_output = svntest.verify.UnorderedOutput( + ["Reverted '%s'\n" % G2_pi, + "Reverted '%s'\n" % G2_rho, + "Reverted '%s'\n" % G2_tau, + ]) + + expected_status.wc_dir = wc_dir_2 + + # Revert recursively in wc 2 + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', '-R', G2) + svntest.actions.run_and_verify_status(wc_dir_2, expected_status) + svntest.actions.verify_disk(wc_dir_2, expected_disk) + +def revert_add_over_not_present_dir(sbox): + "reverting an add over not present directory" + + sbox.build() + wc_dir = sbox.wc_dir + + main.run_svn(None, 'rm', os.path.join(wc_dir, 'A/C')) + sbox.simple_commit(message='Deleted dir') + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.remove('A/C') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + main.run_svn(None, 'mkdir', os.path.join(wc_dir, 'A/C')) + + # This failed in some WC-NG intermediate format (r927318-r958992). + main.run_svn(None, 'revert', os.path.join(wc_dir, 'A/C')) + + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + +def revert_added_tree(sbox): + "revert an added tree fails" + + sbox.build() + wc_dir = sbox.wc_dir + svntest.actions.run_and_verify_svn(None, [], + 'mkdir', sbox.ospath('X'), sbox.ospath('X/Y')) + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'X' : Item(status='A ', wc_rev=0), + 'X/Y' : Item(status='A ', wc_rev=0), + }) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Revert is non-recursive and fails, status is unchanged + expected_error = '.*Try \'svn revert --depth infinity\'.*' + svntest.actions.run_and_verify_svn(None, expected_error, + 'revert', sbox.ospath('X')) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + +@Issue(3834) +def revert_child_of_copy(sbox): + "revert a child of a copied directory" + + sbox.build() + wc_dir = sbox.wc_dir + svntest.actions.run_and_verify_svn(None, [], + 'cp', + sbox.ospath('A/B/E'), + sbox.ospath('A/B/E2')) + + + svntest.main.file_append(sbox.ospath('A/B/E2/beta'), 'extra text\n') + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'A/B/E2' : Item(status='A ', copied='+', wc_rev='-'), + 'A/B/E2/alpha' : Item(status=' ', copied='+', wc_rev='-'), + 'A/B/E2/beta' : Item(status='M ', copied='+', wc_rev='-'), + }) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # First revert removes text change, child is still copied + expected_output = ["Reverted '%s'\n" % sbox.ospath('A/B/E2/beta')] + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', sbox.ospath('A/B/E2/beta')) + expected_status.tweak('A/B/E2/beta', status=' ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Second revert of child does nothing, child is still copied + svntest.actions.run_and_verify_svn(None, [], + 'revert', sbox.ospath('A/B/E2/beta')) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +@Issue(3783) +def revert_non_recusive_after_delete(sbox): + "non-recursive revert after delete" + + sbox.build(read_only=True) + wc_dir = sbox.wc_dir + + svntest.actions.run_and_verify_svn(None, [], 'rm', sbox.ospath('A/B')) + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.tweak('A/B', 'A/B/E', 'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', + 'A/B/lambda', status='D ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # This appears to work but gets the op-depth wrong + expected_output = ["Reverted '%s'\n" % sbox.ospath('A/B')] + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', sbox.ospath('A/B')) + expected_status.tweak('A/B', status=' ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + svntest.actions.run_and_verify_svn(None, [], + 'mkdir', sbox.ospath('A/B/E')) + expected_status.tweak('A/B/E', status='R ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Since the op-depth was wrong A/B/E erroneously remains deleted + expected_output = ["Reverted '%s'\n" % sbox.ospath('A/B/E')] + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', sbox.ospath('A/B/E')) + expected_status.tweak('A/B/E', status=' ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +def revert_permissions_only(sbox): + "permission-only reverts" + + sbox.build() + wc_dir = sbox.wc_dir + + # Helpers pinched/adapted from lock_tests.py. Put them somewhere common? + def check_writability(path, writable): + bits = stat.S_IWGRP | stat.S_IWOTH | stat.S_IWRITE + mode = os.stat(path)[0] + if bool(mode & bits) != writable: + raise svntest.Failure("path '%s' is unexpectedly %s (mode %o)" + % (path, ["writable", "read-only"][writable], mode)) + + def is_writable(path): + "Raise if PATH is not writable." + check_writability(path, True) + + def is_readonly(path): + "Raise if PATH is not readonly." + check_writability(path, False) + + def check_executability(path, executable): + bits = stat.S_IXGRP | stat.S_IXOTH | stat.S_IEXEC + mode = os.stat(path)[0] + if bool(mode & bits) != executable: + raise svntest.Failure("path '%s' is unexpectedly %s (mode %o)" + % (path, + ["executable", "non-executable"][executable], + mode)) + + def is_executable(path): + "Raise if PATH is not executable." + check_executability(path, True) + + def is_non_executable(path): + "Raise if PATH is executable." + check_executability(path, False) + + + os.chmod(sbox.ospath('A/B/E/alpha'), svntest.main.S_ALL_READ) # read-only + is_readonly(sbox.ospath('A/B/E/alpha')) + expected_output = ["Reverted '%s'\n" % sbox.ospath('A/B/E/alpha')] + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', sbox.ospath('A/B/E/alpha')) + is_writable(sbox.ospath('A/B/E/alpha')) + + if svntest.main.is_posix_os(): + os.chmod(sbox.ospath('A/B/E/beta'), svntest.main.S_ALL_RWX) # executable + is_executable(sbox.ospath('A/B/E/beta')) + expected_output = ["Reverted '%s'\n" % sbox.ospath('A/B/E/beta')] + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', sbox.ospath('A/B/E/beta')) + is_non_executable(sbox.ospath('A/B/E/beta')) + + svntest.actions.run_and_verify_svn(None, [], + 'propset', 'svn:needs-lock', '1', + sbox.ospath('A/B/E/alpha')) + svntest.actions.run_and_verify_svn(None, [], + 'propset', 'svn:executable', '1', + sbox.ospath('A/B/E/beta')) + + expected_output = svntest.wc.State(wc_dir, { + 'A/B/E/alpha': Item(verb='Sending'), + 'A/B/E/beta': Item(verb='Sending'), + }) + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.tweak('A/B/E/alpha', wc_rev='2') + expected_status.tweak('A/B/E/beta', wc_rev='2') + svntest.actions.run_and_verify_commit(wc_dir, + expected_output, + expected_status) + + os.chmod(sbox.ospath('A/B/E/alpha'), svntest.main.S_ALL_RW) # not read-only + is_writable(sbox.ospath('A/B/E/alpha')) + expected_output = ["Reverted '%s'\n" % sbox.ospath('A/B/E/alpha')] + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', sbox.ospath('A/B/E/alpha')) + is_readonly(sbox.ospath('A/B/E/alpha')) + + if svntest.main.is_posix_os(): + os.chmod(sbox.ospath('A/B/E/beta'), svntest.main.S_ALL_RW) # not executable + is_non_executable(sbox.ospath('A/B/E/beta')) + expected_output = ["Reverted '%s'\n" % sbox.ospath('A/B/E/beta')] + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', sbox.ospath('A/B/E/beta')) + is_executable(sbox.ospath('A/B/E/beta')) + + # copied file is always writeable + sbox.simple_update() + expected_output = ["A %s\n" % sbox.ospath('A/B/E2')] + svntest.actions.run_and_verify_svn(expected_output, [], 'copy', + sbox.ospath('A/B/E'), + sbox.ospath('A/B/E2')) + is_writable(sbox.ospath('A/B/E2/alpha')) + svntest.actions.run_and_verify_svn([], [], + 'revert', sbox.ospath('A/B/E2/alpha')) + is_writable(sbox.ospath('A/B/E2/alpha')) + +@XFail() +@Issue(3851) +def revert_copy_depth_files(sbox): + "revert a copy with depth=files" + + sbox.build(read_only=True) + wc_dir = sbox.wc_dir + + svntest.actions.run_and_verify_svn(None, [], + 'copy', + sbox.ospath('A/B/E'), + sbox.ospath('A/B/E2')) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'A/B/E2' : Item(status='A ', copied='+', wc_rev='-'), + 'A/B/E2/alpha' : Item(status=' ', copied='+', wc_rev='-'), + 'A/B/E2/beta' : Item(status=' ', copied='+', wc_rev='-'), + }) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + expected_output = svntest.verify.UnorderedOutput([ + "Reverted '%s'\n" % sbox.ospath(path) for path in ['A/B/E2', + 'A/B/E2/alpha', + 'A/B/E2/beta']]) + + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', '--depth', 'files', + sbox.ospath('A/B/E2')) + + expected_status.remove('A/B/E2', 'A/B/E2/alpha', 'A/B/E2/beta') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +@XFail() +@Issue(3851) +def revert_nested_add_depth_immediates(sbox): + "revert a nested add with depth=immediates" + + sbox.build(read_only=True) + wc_dir = sbox.wc_dir + + svntest.actions.run_and_verify_svn(None, [], + 'mkdir', '--parents', sbox.ospath('A/X/Y')) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'A/X' : Item(status='A ', wc_rev='0'), + 'A/X/Y' : Item(status='A ', wc_rev='0'), + }) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + expected_output = svntest.verify.UnorderedOutput([ + "Reverted '%s'\n" % sbox.ospath(path) for path in ['A/X', 'A/X/Y']]) + + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', '--depth', 'immediates', + sbox.ospath('A/X')) + + expected_status.remove('A/X', 'A/X/Y') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +def create_superflous_actual_node(sbox): + "create a superfluous actual node" + + sbox.build() + wc_dir = sbox.wc_dir + + svntest.main.file_append(sbox.ospath('A/B/E/alpha'), 'their text\n') + sbox.simple_commit() + sbox.simple_update() + + # Create a NODES row with op-depth>0 + svntest.actions.run_and_verify_svn(None, [], + 'copy', '-r', '1', + sbox.repo_url + '/A/B/E/alpha', + sbox.ospath('alpha')) + + # Merge to create an ACTUAL with a conflict + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + expected_status.add({ + 'alpha' : Item(status='A ', copied='+', wc_rev='-'), + }) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + svntest.main.file_append(sbox.ospath('alpha'), 'my text\n') + svntest.actions.run_and_verify_svn(None, [], + 'merge', '--accept', 'postpone', + '^/A/B/E/alpha', sbox.ospath('alpha')) + expected_status.tweak('alpha', status='CM', entry_status='A ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Clear merge property and remove conflict files + sbox.simple_propdel('svn:mergeinfo', 'alpha') + os.remove(sbox.ospath('alpha.merge-left.r1')) + os.remove(sbox.ospath('alpha.merge-right.r2')) + os.remove(sbox.ospath('alpha.working')) + + expected_status.tweak('alpha', status='A ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +@Issue(3859) +@SkipUnless(svntest.main.server_has_mergeinfo) +def revert_empty_actual(sbox): + "revert with superfluous actual node" + + create_superflous_actual_node(sbox) + wc_dir = sbox.wc_dir + + # Non-recursive code path works + svntest.actions.run_and_verify_svn(["Reverted '%s'\n" % sbox.ospath('alpha')], + [], + 'revert', sbox.ospath('alpha')) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +@Issue(3859) +@SkipUnless(svntest.main.server_has_mergeinfo) +def revert_empty_actual_recursive(sbox): + "recursive revert with superfluous actual node" + + create_superflous_actual_node(sbox) + wc_dir = sbox.wc_dir + + # Recursive code path fails, the superfluous actual node suppresses the + # notification + svntest.actions.run_and_verify_svn(["Reverted '%s'\n" % sbox.ospath('alpha')], + [], + 'revert', '-R', sbox.ospath('alpha')) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 2) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +@Issue(3879) +def revert_tree_conflicts_with_replacements(sbox): + "revert tree conflicts with replacements" + + sbox.build() + wc_dir = sbox.wc_dir + wc = sbox.ospath + + # Use case 1: local replace, incoming replace + # A/mu + # A/D/H --> A/D/H/chi, A/D/H/{loc,inc}_psi + + # Use case 2: local edit, incoming replace + # A/D/gamma + # A/D/G --> A/D/G/pi, A/D/G/inc_rho + + # Use case 3: local replace, incoming edit + # A/B/lambda + # A/B/E --> A/B/E/alpha, A/B/E/loc_beta + + # Case 1: incoming replacements + sbox.simple_rm('A/mu', 'A/D/H') + file_write(wc('A/mu'), "A fresh file.\n") + os.mkdir(wc('A/D/H')) + file_write(wc('A/D/H/chi'), "A fresh file.\n") + file_write(wc('A/D/H/inc_psi'), "A fresh file.\n") + sbox.simple_add('A/mu', 'A/D/H') + + # Case 2: incoming replacements + sbox.simple_rm('A/D/gamma', 'A/D/G') + file_write(wc('A/D/gamma'), "A fresh file.\n") + os.mkdir(wc('A/D/G')) + file_write(wc('A/D/G/pi'), "A fresh file.\n") + file_write(wc('A/D/G/inc_rho'), "A fresh file.\n") + sbox.simple_add('A/D/gamma','A/D/G') + + # Case 3: incoming edits + file_append(wc('A/B/lambda'), "Incoming!\n") + file_write(wc('A/B/E/alpha'), "Incoming!.\n") + + # Commit and roll back to r1. + sbox.simple_commit() + run_svn(None, 'up', wc_dir, '-r1', '-q') + + # Case 1: local replacements + sbox.simple_rm('A/mu', 'A/D/H') + file_write(wc('A/mu'), "A fresh file.\n") + os.mkdir(wc('A/D/H')) + file_write(wc('A/D/H/chi'), "A fresh local file.\n") + file_write(wc('A/D/H/loc_psi'), "A fresh local file.\n") + sbox.simple_add('A/mu', 'A/D/H') + + # Case 2: local edits + file_append(wc('A/D/gamma'), "Local change.\n") + file_append(wc('A/D/G/pi'), "Local change.\n") + + # Case 3: local replacements + sbox.simple_rm('A/B/lambda', 'A/B/E') + file_write(wc('A/B/lambda'), "A fresh local file.\n") + os.mkdir(wc('A/B/E')) + file_write(wc('A/B/E/alpha'), "A fresh local file.\n") + file_write(wc('A/B/E/loc_beta'), "A fresh local file.\n") + sbox.simple_add('A/B/lambda', 'A/B/E') + + # Update and check tree conflict status. + run_svn(None, 'up', wc_dir) + expected_status = svntest.wc.State(wc_dir, { + '' : Item(status=' ', wc_rev=2), + 'A' : Item(status=' ', wc_rev=2), + 'A/B' : Item(status=' ', wc_rev=2), + 'A/B/E' : Item(status='R ', wc_rev=2, treeconflict='C'), + 'A/B/E/alpha' : Item(status='A ', wc_rev='-'), + 'A/B/E/beta' : Item(status='D ', wc_rev=2), + 'A/B/E/loc_beta' : Item(status='A ', wc_rev='-'), + 'A/B/F' : Item(status=' ', wc_rev=2), + 'A/B/lambda' : Item(status='R ', wc_rev=2, treeconflict='C'), + 'A/C' : Item(status=' ', wc_rev=2), + 'A/D' : Item(status=' ', wc_rev=2), + 'A/D/G' : Item(status='R ', wc_rev='-', copied='+', + treeconflict='C'), + 'A/D/G/inc_rho' : Item(status='D ', wc_rev=2), + 'A/D/G/pi' : Item(status='M ', wc_rev='-', copied='+'), + 'A/D/G/rho' : Item(status=' ', wc_rev='-', copied='+'), + 'A/D/G/tau' : Item(status=' ', wc_rev='-', copied='+'), + 'A/D/H' : Item(status='R ', wc_rev=2, treeconflict='C'), + 'A/D/H/chi' : Item(status='A ', wc_rev='-'), + 'A/D/H/inc_psi' : Item(status='D ', wc_rev=2), + 'A/D/H/loc_psi' : Item(status='A ', wc_rev='-'), + 'A/D/gamma' : Item(status='R ', wc_rev='-', copied='+', + treeconflict='C'), + 'A/mu' : Item(status='R ', wc_rev=2, treeconflict='C'), + 'iota' : Item(status=' ', wc_rev=2), + }) + svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status) + + def cd_and_status_u(dir_target): + was_cwd = os.getcwd() + os.chdir(os.path.abspath(wc(dir_target))) + run_svn(None, 'status', '-u') + os.chdir(was_cwd) + + cd_and_status_u('A') + cd_and_status_u('A/D') + + # Until r1102143, the following 'status -u' commands failed with "svn: + # E165004: Two top-level reports with no target". + cd_and_status_u('A/D/G') + cd_and_status_u('A/D/H') + + # Revert everything (i.e., accept "theirs-full"). + svntest.actions.run_and_verify_revert([ + wc('A/B/E'), + wc('A/B/E/alpha'), # incoming & local + wc('A/B/E/beta'), + wc('A/B/E/loc_beta'), + wc('A/B/lambda'), + wc('A/D/G'), + wc('A/D/G/pi'), + wc('A/D/G/inc_rho'), # incoming + wc('A/D/G/rho'), + wc('A/D/G/tau'), + wc('A/D/H'), + wc('A/D/H/chi'), + wc('A/D/H/inc_psi'), # incoming + wc('A/D/H/loc_psi'), + wc('A/D/gamma'), + wc('A/mu'), + ], '-R', wc_dir) + + # Remove a few unversioned files that revert left behind. + os.remove(wc('A/B/E/loc_beta')) + os.remove(wc('A/D/H/loc_psi')) + + # The update operation should have put all incoming items in place. + expected_status = svntest.wc.State(wc_dir, { + '' : Item(status=' ', wc_rev=2), + 'A' : Item(status=' ', wc_rev=2), + 'A/B' : Item(status=' ', wc_rev=2), + 'A/B/E' : Item(status=' ', wc_rev=2), + 'A/B/E/alpha' : Item(status=' ', wc_rev=2), + 'A/B/E/beta' : Item(status=' ', wc_rev=2), + 'A/B/F' : Item(status=' ', wc_rev=2), + 'A/B/lambda' : Item(status=' ', wc_rev=2), + 'A/C' : Item(status=' ', wc_rev=2), + 'A/D' : Item(status=' ', wc_rev=2), + 'A/D/G' : Item(status=' ', wc_rev=2), + 'A/D/G/inc_rho' : Item(status=' ', wc_rev=2), + 'A/D/G/pi' : Item(status=' ', wc_rev=2), + 'A/D/H' : Item(status=' ', wc_rev=2), + 'A/D/H/chi' : Item(status=' ', wc_rev=2), + 'A/D/H/inc_psi' : Item(status=' ', wc_rev=2), + 'A/D/gamma' : Item(status=' ', wc_rev=2), + 'A/mu' : Item(status=' ', wc_rev=2), + 'iota' : Item(status=' ', wc_rev=2), + }) + svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status) + +def create_no_text_change_conflict(sbox): + "create conflict with no text change" + + sbox.build() + wc_dir = sbox.wc_dir + + shutil.copyfile(sbox.ospath('A/B/E/alpha'), sbox.ospath('A/B/E/alpha-copy')) + svntest.main.file_append(sbox.ospath('A/B/E/alpha'), 'their text\n') + sbox.simple_commit() + sbox.simple_update() + + # Update to create a conflict + svntest.main.file_append(sbox.ospath('A/B/E/alpha'), 'my text\n') + svntest.actions.run_and_verify_svn(None, [], + 'up', '-r1', '--accept', 'postpone', + wc_dir) + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.tweak('A/B/E/alpha', status='C ') + svntest.actions.run_and_verify_status(wc_dir, expected_status) + + # Reset the text with the file still marked as a conflict + os.remove(sbox.ospath('A/B/E/alpha')) + shutil.move(sbox.ospath('A/B/E/alpha-copy'), sbox.ospath('A/B/E/alpha')) + +@Issue(3859) +def revert_no_text_change_conflict(sbox): + "revert conflict with no text change" + + create_no_text_change_conflict(sbox) + wc_dir = sbox.wc_dir + + svntest.actions.run_and_verify_svn(["Reverted '%s'\n" + % sbox.ospath('A/B/E/alpha')], + [], + 'revert', sbox.ospath('A/B/E/alpha')) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +@Issue(3859) +def revert_no_text_change_conflict_recursive(sbox): + "revert -R conflict with no text change" + + create_no_text_change_conflict(sbox) + wc_dir = sbox.wc_dir + + svntest.actions.run_and_verify_svn(["Reverted '%s'\n" + % sbox.ospath('A/B/E/alpha')], + [], + 'revert', '-R', wc_dir) + + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + svntest.actions.run_and_verify_status(wc_dir, expected_status) + +@Issue(3938) +def revert_with_unversioned_targets(sbox): + "revert with unversioned targets" + + sbox.build() + wc_dir = sbox.wc_dir + + chi_path = sbox.ospath('A/D/H/chi') + delta_path = sbox.ospath('A/D/H/delta') + psi_path = sbox.ospath('A/D/H/psi') + + chi_contents = "modified chi\n" + delta_contents = "This is the unversioned file 'delta'.\n" + psi_contents = "modified psi\n" + + # touch delta + open(delta_path, 'w').write(delta_contents) + + # modify chi psi + open(chi_path, 'w').write(chi_contents) + open(psi_path, 'w').write(psi_contents) + + # revert + expected_output = svntest.verify.UnorderedOutput([ + "Reverted '%s'\n" % sbox.ospath('A/D/H/chi'), + "Skipped '%s'\n" % sbox.ospath('A/D/H/delta'), + "Reverted '%s'\n" % sbox.ospath('A/D/H/psi'), + ]) + svntest.actions.run_and_verify_svn(expected_output, [], + 'revert', chi_path, delta_path, psi_path) + + # verify status + expected_status = svntest.actions.get_virginal_state(wc_dir, 1) + expected_status.add({ + 'A/D/H/delta': Item(status='? '), + }) + svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status) + + # verify disk + expected_disk = svntest.main.greek_state.copy() + expected_disk.add({ + 'A/D/H/delta': Item(delta_contents), + }) + svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True) + +def revert_nonexistent(sbox): + 'svn revert -R nonexistent' + sbox.build(read_only=True) + svntest.actions.run_and_verify_svn('Skipped.*nonexistent', [], + 'revert', '-R', sbox.ospath('nonexistent')) + +@Issue(4168) +def revert_obstructing_wc(sbox): + "revert with an obstructing working copy" + + sbox.build(create_wc=False, read_only=True) + wc_dir = sbox.wc_dir + + expected_output = svntest.wc.State(wc_dir, {}) + expected_disk = svntest.wc.State(wc_dir, {}) + + # Checkout wc as depth empty + svntest.actions.run_and_verify_checkout(sbox.repo_url, wc_dir, + expected_output, expected_disk, + [], + '--depth', 'empty') + + # And create an obstructing working copy as A + svntest.actions.run_and_verify_checkout(sbox.repo_url, wc_dir + '/A', + expected_output, expected_disk, + [], + '--depth', 'empty') + + # Now try to fetch the entire wc, which will find an obstruction + expected_output = svntest.wc.State(wc_dir, { + 'A' : Item(verb='Skipped'), + 'iota' : Item(status='A '), + }) + expected_status = svntest.wc.State(wc_dir, { + '' : Item(status=' ', wc_rev='1'), + 'iota' : Item(status=' ', wc_rev='1'), + # A is not versioned but exists + }) + + svntest.actions.run_and_verify_update(wc_dir, + expected_output, None, + expected_status, + [], False, + wc_dir, '--set-depth', 'infinity') + + # Revert should do nothing (no local changes), and report the obstruction + # (reporting the obstruction is nice for debugging, but not really required + # in this specific case, as the node was not modified) + svntest.actions.run_and_verify_svn("Skipped '.*A' -- .*obstruct.*", [], + 'revert', '-R', wc_dir) + +def revert_moved_dir_partial(sbox): + "partial revert moved_dir" + + sbox.build(read_only = True) + + sbox.simple_move('A', 'A_') + svntest.actions.run_and_verify_svn(None, [], 'revert', sbox.ospath('A')) + + +######################################################################## +# Run the tests + + +# list all tests here, starting with None: +test_list = [ None, + revert_from_wc_root, + revert_reexpand_keyword, + revert_replaced_file_without_props, + revert_moved_file, + revert_wc_to_wc_replace_with_props, + revert_file_merge_replace_with_history, + revert_repos_to_wc_replace_with_props, + revert_after_second_replace, + revert_after_manual_conflict_resolution__text, + revert_after_manual_conflict_resolution__prop, + revert_propset__dir, + revert_propset__file, + revert_propdel__dir, + revert_propdel__file, + revert_replaced_with_history_file_1, + status_of_missing_dir_after_revert, + status_of_missing_dir_after_revert_replaced_with_history_dir, + revert_replaced_with_history_file_2, + revert_tree_conflicts_in_updated_files, + revert_add_over_not_present_dir, + revert_added_tree, + revert_child_of_copy, + revert_non_recusive_after_delete, + revert_permissions_only, + revert_copy_depth_files, + revert_nested_add_depth_immediates, + revert_empty_actual, + revert_tree_conflicts_with_replacements, + revert_empty_actual_recursive, + revert_no_text_change_conflict, + revert_no_text_change_conflict_recursive, + revert_with_unversioned_targets, + revert_nonexistent, + revert_obstructing_wc, + revert_moved_dir_partial, + ] + +if __name__ == '__main__': + svntest.main.run_tests(test_list) + # NOTREACHED + + +### End of file. |