| #!/usr/bin/env python |
| # |
| # merge_tests.py: testing merge |
| # |
| # 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 shutil, sys, re, os |
| import time |
| |
| # Our testing module |
| import svntest |
| from svntest import main, wc, verify, actions |
| |
| from prop_tests import binary_mime_type_on_text_file_warning |
| |
| # (abbreviation) |
| Item = wc.StateItem |
| 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 |
| exp_noop_up_out = svntest.actions.expected_noop_update_output |
| |
| from svntest.main import SVN_PROP_MERGEINFO |
| from svntest.main import server_has_mergeinfo |
| from svntest.actions import fill_file_with_lines |
| from svntest.actions import make_conflict_marker_text |
| from svntest.actions import inject_conflict_into_expected_state |
| from svntest.verify import RegexListOutput |
| |
| from svntest.mergetrees import expected_merge_output, \ |
| check_mergeinfo_recursively, \ |
| set_up_dir_replace, \ |
| set_up_branch, \ |
| local_path, \ |
| svn_mkfile, \ |
| svn_modfile, \ |
| svn_copy, \ |
| svn_merge, \ |
| noninheritable_mergeinfo_test_set_up |
| |
| ###################################################################### |
| # Tests |
| # |
| # Each test must return on success or raise on failure. |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def textual_merges_galore(sbox): |
| "performing a merge, with mixed results" |
| |
| ## The Plan: |
| ## |
| ## The goal is to test that "svn merge" does the right thing in the |
| ## following cases: |
| ## |
| ## 1 : _ : Received changes already present in unmodified local file |
| ## 2 : U : No local mods, received changes folded in without trouble |
| ## 3 : G : Received changes already exist as local mods |
| ## 4 : G : Received changes do not conflict with local mods |
| ## 5 : C : Received changes conflict with local mods |
| ## |
| ## So first modify these files and commit: |
| ## |
| ## Revision 2: |
| ## ----------- |
| ## A/mu ............... add ten or so lines |
| ## A/D/G/rho .......... add ten or so lines |
| ## |
| ## Now check out an "other" working copy, from revision 2. |
| ## |
| ## Next further modify and commit some files from the original |
| ## working copy: |
| ## |
| ## Revision 3: |
| ## ----------- |
| ## A/B/lambda ......... add ten or so lines |
| ## A/D/G/pi ........... add ten or so lines |
| ## A/D/G/tau .......... add ten or so lines |
| ## A/D/G/rho .......... add an additional ten or so lines |
| ## |
| ## In the other working copy (which is at rev 2), update rho back |
| ## to revision 1, while giving other files local mods. This sets |
| ## things up so that "svn merge -r 1:3" will test all of the above |
| ## cases except case 4: |
| ## |
| ## case 1: A/mu .......... do nothing, the only change was in rev 2 |
| ## case 2: A/B/lambda .... do nothing, so we accept the merge easily |
| ## case 3: A/D/G/pi ...... add same ten lines as committed in rev 3 |
| ## case 5: A/D/G/tau ..... add ten or so lines at the end |
| ## [none]: A/D/G/rho ..... ignore what happens to this file for now |
| ## |
| ## Now run |
| ## |
| ## $ cd wc.other |
| ## $ svn merge -r 1:3 url-to-repo |
| ## |
| ## ...and expect the right output. |
| ## |
| ## Now revert rho, then update it to revision 2, then *prepend* a |
| ## bunch of lines, which will be separated by enough distance from |
| ## the changes about to be received that the merge will be clean. |
| ## |
| ## $ cd wc.other/A/D/G |
| ## $ svn merge -r 2:3 url-to-repo/A/D/G |
| ## |
| ## Which tests case 4. (Ignore the changes to the other files, |
| ## we're only interested in rho here.) |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| # url = os.path.join(svntest.main.test_area_url, sbox.repo_dir) |
| |
| # Change mu and rho for revision 2 |
| mu_path = sbox.ospath('A/mu') |
| rho_path = sbox.ospath('A/D/G/rho') |
| mu_text = fill_file_with_lines(mu_path, 2) |
| rho_text = fill_file_with_lines(rho_path, 2) |
| |
| # Create expected output tree for initial commit |
| expected_output = wc.State(wc_dir, { |
| 'A/mu' : Item(verb='Sending'), |
| 'A/D/G/rho' : Item(verb='Sending'), |
| }) |
| |
| # Create expected status tree; all local revisions should be at 1, |
| # but mu and rho should be at revision 2. |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/mu', 'A/D/G/rho', wc_rev=2) |
| |
| # Initial commit. |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Make the "other" working copy |
| other_wc = sbox.add_wc_path('other') |
| svntest.actions.duplicate_dir(wc_dir, other_wc) |
| |
| # Now commit some more mods from the original working copy, to |
| # produce revision 3. |
| lambda_path = sbox.ospath('A/B/lambda') |
| pi_path = sbox.ospath('A/D/G/pi') |
| tau_path = sbox.ospath('A/D/G/tau') |
| |
| lambda_text = fill_file_with_lines(lambda_path, 2) |
| pi_text = fill_file_with_lines(pi_path, 2) |
| tau_text = fill_file_with_lines(tau_path, 2) |
| additional_rho_text = fill_file_with_lines(rho_path, 2) |
| |
| # Created expected output tree for 'svn ci' |
| expected_output = wc.State(wc_dir, { |
| 'A/B/lambda' : Item(verb='Sending'), |
| 'A/D/G/pi' : Item(verb='Sending'), |
| 'A/D/G/tau' : Item(verb='Sending'), |
| 'A/D/G/rho' : Item(verb='Sending'), |
| }) |
| |
| # Create expected status tree. |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/mu', wc_rev=2) |
| expected_status.tweak('A/B/lambda', 'A/D/G/pi', 'A/D/G/tau', 'A/D/G/rho', |
| wc_rev=3) |
| |
| # Commit revision 3. |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Make local mods in wc.other |
| other_pi_path = os.path.join(other_wc, 'A', 'D', 'G', 'pi') |
| other_rho_path = os.path.join(other_wc, 'A', 'D', 'G', 'rho') |
| other_tau_path = os.path.join(other_wc, 'A', 'D', 'G', 'tau') |
| |
| # For A/mu and A/B/lambda, we do nothing. For A/D/G/pi, we add the |
| # same ten lines as were already committed in revision 3. |
| # (Remember, wc.other is only at revision 2, so it doesn't have |
| # these changes.) |
| svntest.main.file_append(other_pi_path, pi_text) |
| |
| # We skip A/D/G/rho in this merge; it will be tested with a separate |
| # merge command. Temporarily put it back to revision 1, so this |
| # merge succeeds cleanly. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'up', '-r', '1', other_rho_path) |
| |
| # For A/D/G/tau, we append few different lines, to conflict with the |
| # few lines appended in revision 3. |
| other_tau_text = fill_file_with_lines(other_tau_path, 2, |
| line_descrip="Conflicting line") |
| |
| # Do the first merge, revs 1:3. This tests all the cases except |
| # case 4, which we'll handle in a second pass. |
| expected_output = wc.State(other_wc, {'A/B/lambda' : Item(status='U '), |
| 'A/D/G/rho' : Item(status='U '), |
| 'A/D/G/tau' : Item(status='C '), |
| }) |
| expected_mergeinfo_output = wc.State(other_wc, {'' : Item(status=' U')}) |
| expected_elision_output = wc.State(other_wc, {}) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.tweak('A/mu', |
| contents=expected_disk.desc['A/mu'].contents |
| + mu_text) |
| expected_disk.tweak('A/B/lambda', |
| contents=expected_disk.desc['A/B/lambda'].contents |
| + lambda_text) |
| expected_disk.tweak('A/D/G/rho', |
| contents=expected_disk.desc['A/D/G/rho'].contents |
| + rho_text + additional_rho_text) |
| expected_disk.tweak('A/D/G/pi', |
| contents=expected_disk.desc['A/D/G/pi'].contents |
| + pi_text) |
| |
| expected_status = svntest.actions.get_virginal_state(other_wc, 1) |
| expected_status.tweak('', status=' M') |
| expected_status.tweak('A/mu', wc_rev=2) |
| expected_status.tweak('A/B/lambda', status='M ') |
| expected_status.tweak('A/D/G/pi', status='M ') |
| expected_status.tweak('A/D/G/rho', status='M ') |
| |
| inject_conflict_into_expected_state('A/D/G/tau', expected_disk, |
| expected_status, other_tau_text, tau_text, |
| 1, 3) |
| |
| expected_skip = wc.State('', { }) |
| |
| tau_conflict_support_files = ["tau\.working", |
| "tau\.merge-right\.r3", |
| "tau\.merge-left\.r1"] |
| |
| svntest.actions.run_and_verify_merge(other_wc, '1', '3', |
| sbox.repo_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], False, True, |
| '--allow-mixed-revisions', other_wc, |
| extra_files=list(tau_conflict_support_files)) |
| |
| # Now reverse merge r3 into A/D/G/rho, give it non-conflicting local |
| # mods, then merge in the 2:3 change. ### Not bothering to do the |
| # whole expected_foo routine for these intermediate operations; |
| # they're not what we're here to test, after all, so it's enough to |
| # know that they worked. Is this a bad practice? ### |
| # |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[-3]], |
| ['G ' + other_rho_path + '\n', |
| ' G ' + other_rho_path + '\n',]), |
| [], 'merge', '-c-3', |
| sbox.repo_url + '/A/D/G/rho', |
| other_rho_path) |
| |
| # Now *prepend* ten or so lines to A/D/G/rho. Since rho had ten |
| # lines appended in revision 2, and then another ten in revision 3, |
| # these new local mods will be separated from the rev 3 changes by |
| # enough distance that they won't conflict, so the merge should be |
| # clean. |
| other_rho_text = "" |
| for x in range(1,10): |
| other_rho_text = other_rho_text + 'Unobtrusive line ' + repr(x) + ' in rho\n' |
| current_other_rho_text = open(other_rho_path).read() |
| svntest.main.file_write(other_rho_path, |
| other_rho_text + current_other_rho_text) |
| |
| # We expect no merge attempt for pi and tau because they inherit |
| # mergeinfo from the WC root. There is explicit mergeinfo on rho |
| # ('/A/D/G/rho:2') so expect it to be merged (cleanly). |
| G_path = os.path.join(other_wc, 'A', 'D', 'G') |
| expected_output = wc.State(os.path.join(other_wc, 'A', 'D', 'G'), |
| {'rho' : Item(status='G ')}) |
| expected_mergeinfo_output = wc.State(G_path, { |
| '' : Item(status=' G'), |
| 'rho' : Item(status=' G') |
| }) |
| expected_elision_output = wc.State(G_path, { |
| '' : Item(status=' U'), |
| 'rho' : Item(status=' U') |
| }) |
| expected_disk = wc.State("", { |
| 'pi' : Item("This is the file 'pi'.\n"), |
| 'rho' : Item("This is the file 'rho'.\n"), |
| 'tau' : Item("This is the file 'tau'.\n"), |
| }) |
| expected_disk.tweak('rho', |
| contents=other_rho_text |
| + expected_disk.desc['rho'].contents |
| + rho_text |
| + additional_rho_text) |
| expected_disk.tweak('pi', |
| contents=expected_disk.desc['pi'].contents |
| + pi_text) |
| |
| expected_status = wc.State(os.path.join(other_wc, 'A', 'D', 'G'), |
| { '' : Item(wc_rev=1, status=' '), |
| 'rho' : Item(wc_rev=1, status='M '), |
| 'pi' : Item(wc_rev=1, status='M '), |
| 'tau' : Item(wc_rev=1, status='C '), |
| }) |
| |
| inject_conflict_into_expected_state('tau', expected_disk, expected_status, |
| other_tau_text, tau_text, 1, 3) |
| |
| # Do the merge, but check svn:mergeinfo props separately since |
| # run_and_verify_merge would attempt to proplist tau's conflict |
| # files if we asked it to check props. |
| svntest.actions.run_and_verify_merge( |
| os.path.join(other_wc, 'A', 'D', 'G'), |
| '2', '3', |
| sbox.repo_url + '/A/D/G', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| extra_files=list(tau_conflict_support_files)) |
| |
| |
| svntest.actions.run_and_verify_svn([], '.*W200017: Property.*not found', |
| 'propget', SVN_PROP_MERGEINFO, |
| os.path.join(other_wc, |
| "A", "D", "G", "rho")) |
| |
| |
| #---------------------------------------------------------------------- |
| # Merge should copy-with-history when adding files or directories |
| @SkipUnless(server_has_mergeinfo) |
| def add_with_history(sbox): |
| "merge and add new files/dirs with history" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| C_path = sbox.ospath('A/C') |
| F_path = sbox.ospath('A/B/F') |
| F_url = sbox.repo_url + '/A/B/F' |
| |
| Q_path = os.path.join(F_path, 'Q') |
| Q2_path = os.path.join(F_path, 'Q2') |
| foo_path = os.path.join(F_path, 'foo') |
| foo2_path = os.path.join(F_path, 'foo2') |
| bar_path = os.path.join(F_path, 'Q', 'bar') |
| bar2_path = os.path.join(F_path, 'Q', 'bar2') |
| |
| svntest.main.run_svn(None, 'mkdir', Q_path) |
| svntest.main.run_svn(None, 'mkdir', Q2_path) |
| svntest.main.file_append(foo_path, "foo") |
| svntest.main.file_append(foo2_path, "foo2") |
| svntest.main.file_append(bar_path, "bar") |
| svntest.main.file_append(bar2_path, "bar2") |
| svntest.main.run_svn(None, 'add', foo_path, foo2_path, bar_path, bar2_path) |
| svntest.main.run_svn(None, 'propset', 'x', 'x', Q2_path) |
| svntest.main.run_svn(None, 'propset', 'y', 'y', foo2_path) |
| svntest.main.run_svn(None, 'propset', 'z', 'z', bar2_path) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/B/F/Q' : Item(verb='Adding'), |
| 'A/B/F/Q2' : Item(verb='Adding'), |
| 'A/B/F/Q/bar' : Item(verb='Adding'), |
| 'A/B/F/Q/bar2': Item(verb='Adding'), |
| 'A/B/F/foo' : Item(verb='Adding'), |
| 'A/B/F/foo2' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B/F/Q' : Item(status=' ', wc_rev=2), |
| 'A/B/F/Q2' : Item(status=' ', wc_rev=2), |
| 'A/B/F/Q/bar' : Item(status=' ', wc_rev=2), |
| 'A/B/F/Q/bar2': Item(status=' ', wc_rev=2), |
| 'A/B/F/foo' : Item(status=' ', wc_rev=2), |
| 'A/B/F/foo2' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| expected_output = wc.State(C_path, { |
| 'Q' : Item(status='A '), |
| 'Q2' : Item(status='A '), |
| 'Q/bar' : Item(status='A '), |
| 'Q/bar2' : Item(status='A '), |
| 'foo' : Item(status='A '), |
| 'foo2' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(C_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(C_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B/F:2'}), |
| 'Q' : Item(), |
| 'Q2' : Item(props={'x' : 'x'}), |
| 'Q/bar' : Item("bar"), |
| 'Q/bar2' : Item("bar2", props={'z' : 'z'}), |
| 'foo' : Item("foo"), |
| 'foo2' : Item("foo2", props={'y' : 'y'}), |
| }) |
| expected_status = wc.State(C_path, { |
| '' : Item(status=' M', wc_rev=1), |
| 'Q' : Item(status='A ', wc_rev='-', copied='+'), |
| 'Q2' : Item(status='A ', wc_rev='-', copied='+'), |
| 'Q/bar' : Item(status=' ', wc_rev='-', copied='+'), |
| 'Q/bar2' : Item(status=' ', wc_rev='-', copied='+'), |
| 'foo' : Item(status='A ', wc_rev='-', copied='+'), |
| 'foo2' : Item(status='A ', wc_rev='-', copied='+'), |
| }) |
| |
| expected_skip = wc.State(C_path, { }) |
| |
| # Add some unversioned directory obstructions to the incoming |
| # additions. This should be tolerated and *not* result in any |
| # difference between the --dry-run and actual merge. |
| # See http://svn.haxx.se/dev/archive-2012-11/0696.shtml |
| os.mkdir(sbox.ospath('A/C/Q')) |
| os.mkdir(sbox.ospath('A/C/Q2')) |
| |
| svntest.actions.run_and_verify_merge(C_path, '1', '2', F_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/C' : Item(verb='Sending'), |
| 'A/C/Q' : Item(verb='Adding'), |
| 'A/C/Q2' : Item(verb='Adding'), |
| 'A/C/foo' : Item(verb='Adding'), |
| 'A/C/foo2' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/C' : Item(status=' ', wc_rev=3), |
| 'A/B/F/Q' : Item(status=' ', wc_rev=2), |
| 'A/B/F/Q2' : Item(status=' ', wc_rev=2), |
| 'A/B/F/Q/bar' : Item(status=' ', wc_rev=2), |
| 'A/B/F/Q/bar2': Item(status=' ', wc_rev=2), |
| 'A/B/F/foo' : Item(status=' ', wc_rev=2), |
| 'A/B/F/foo2' : Item(status=' ', wc_rev=2), |
| 'A/C/Q' : Item(status=' ', wc_rev=3), |
| 'A/C/Q2' : Item(status=' ', wc_rev=3), |
| 'A/C/Q/bar' : Item(status=' ', wc_rev=3), |
| 'A/C/Q/bar2' : Item(status=' ', wc_rev=3), |
| 'A/C/foo' : Item(status=' ', wc_rev=3), |
| 'A/C/foo2' : Item(status=' ', wc_rev=3), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| #---------------------------------------------------------------------- |
| # Issue 953 |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(953) |
| def simple_property_merges(sbox): |
| "some simple property merges" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Add a property to a file and a directory |
| alpha_path = sbox.ospath('A/B/E/alpha') |
| beta_path = sbox.ospath('A/B/E/beta') |
| E_path = sbox.ospath('A/B/E') |
| |
| svntest.actions.set_prop('foo', 'foo_val', alpha_path) |
| # A binary, non-UTF8 property value |
| svntest.actions.set_prop('foo', b'foo\201val', beta_path) |
| svntest.actions.set_prop('foo', 'foo_val', E_path) |
| |
| # Commit change as rev 2 |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B/E' : Item(verb='Sending'), |
| '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', 'A/B/E/alpha', 'A/B/E/beta', |
| wc_rev=2, status=' ') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Copy B to B2 as rev 3 |
| B_url = sbox.repo_url + '/A/B' |
| B2_url = sbox.repo_url + '/A/B2' |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'copy', '-m', 'copy B to B2', |
| B_url, B2_url) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Modify a property and add a property for the file and directory |
| svntest.actions.set_prop('foo', 'mod_foo', alpha_path) |
| svntest.actions.set_prop('bar', 'bar_val', alpha_path) |
| svntest.actions.set_prop('foo', b'mod\201foo', beta_path) |
| svntest.actions.set_prop('bar', b'bar\201val', beta_path) |
| svntest.actions.set_prop('foo', 'mod_foo', E_path) |
| svntest.actions.set_prop('bar', 'bar_val', E_path) |
| |
| # Commit change as rev 4 |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 3) |
| expected_status.tweak('A/B/E', 'A/B/E/alpha', 'A/B/E/beta', |
| wc_rev=4, status=' ') |
| expected_status.add({ |
| 'A/B2' : Item(status=' ', wc_rev=3), |
| 'A/B2/E' : Item(status=' ', wc_rev=3), |
| 'A/B2/E/alpha' : Item(status=' ', wc_rev=3), |
| 'A/B2/E/beta' : Item(status=' ', wc_rev=3), |
| 'A/B2/F' : Item(status=' ', wc_rev=3), |
| 'A/B2/lambda' : Item(status=' ', wc_rev=3), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| pristine_status = expected_status |
| pristine_status.tweak(wc_rev=4) |
| |
| # Merge B 3:4 into B2 |
| B2_path = sbox.ospath('A/B2') |
| expected_output = wc.State(B2_path, { |
| 'E' : Item(status=' U'), |
| 'E/alpha' : Item(status=' U'), |
| 'E/beta' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(B2_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(B2_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:4'}), |
| 'E' : Item(), |
| 'E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'E/beta' : Item("This is the file 'beta'.\n"), |
| 'F' : Item(), |
| 'lambda' : Item("This is the file 'lambda'.\n"), |
| }) |
| expected_disk.tweak('E', 'E/alpha', |
| props={'foo' : 'mod_foo', 'bar' : 'bar_val'}) |
| expected_disk.tweak('E/beta', |
| props={'foo' : b'mod\201foo', 'bar' : b'bar\201val'}) |
| expected_status = wc.State(B2_path, { |
| '' : Item(status=' M'), |
| 'E' : Item(status=' M'), |
| 'E/alpha' : Item(status=' M'), |
| 'E/beta' : Item(status=' M'), |
| 'F' : Item(status=' '), |
| 'lambda' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=4) |
| expected_skip = wc.State('', { }) |
| svntest.actions.run_and_verify_merge(B2_path, '3', '4', B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Revert merge |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '--recursive', wc_dir) |
| svntest.actions.run_and_verify_status(wc_dir, pristine_status) |
| |
| # Merge B 2:1 into B2 (B2's mergeinfo should get elided away) |
| expected_status.tweak('', status=' ') |
| expected_disk.remove('') |
| expected_disk.tweak('E', 'E/alpha', 'E/beta', props={}) |
| expected_elision_output = wc.State(B2_path, { |
| '' : Item(status=' U'), |
| }) |
| svntest.actions.run_and_verify_merge(B2_path, '2', '1', B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| def error_message(property, old_value, new_value): |
| return "Trying to change property '%s'\n" \ |
| "but the property has been locally deleted.\n" \ |
| "<<<<<<< (local property value)\n" \ |
| "||||||| (incoming 'changed from' value)\n" \ |
| "%s=======\n" \ |
| "%s>>>>>>> (incoming 'changed to' value)\n" % (property, old_value, new_value) |
| |
| # Merge B 3:4 into B2 now causes a conflict |
| expected_disk.add({ |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:4'}), |
| 'E/dir_conflicts.prej' |
| : Item(error_message('foo', 'foo_val', 'mod_foo')), |
| 'E/alpha.prej' |
| : Item(error_message('foo', 'foo_val', 'mod_foo')), |
| 'E/beta.prej' |
| : Item(error_message('foo', 'foo?\\81val', 'mod?\\81foo')), |
| }) |
| expected_disk.tweak('E', 'E/alpha', props={'bar' : 'bar_val'}) |
| expected_disk.tweak('E/beta', props={'bar' : b'bar\201val'}) |
| expected_status.tweak('', status=' M') |
| expected_status.tweak('E', 'E/alpha', 'E/beta', status=' C') |
| expected_output.tweak('E', 'E/alpha', 'E/beta', status=' C') |
| expected_elision_output = wc.State(B2_path, { |
| }) |
| svntest.actions.run_and_verify_merge(B2_path, '3', '4', B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # issue 1109 : single file property merge. This test performs a merge |
| # that should be a no-op (adding properties that are already present). |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '--recursive', wc_dir) |
| svntest.actions.run_and_verify_status(wc_dir, pristine_status) |
| |
| # Copy A at rev 4 to A2 to make revision 5. |
| A_url = sbox.repo_url + '/A' |
| A2_url = sbox.repo_url + '/A2' |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 5.\n'], [], |
| 'copy', '-m', 'copy A to A2', |
| A_url, A2_url) |
| |
| # Re-root the WC at A2. |
| svntest.main.safe_rmtree(wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'checkout', |
| A2_url, wc_dir) |
| |
| # Attempt to re-merge rev 4 of the original A's alpha. Mergeinfo |
| # inherited from A2 (created by its copy from A) allows us to avoid |
| # a repeated merge. |
| alpha_url = sbox.repo_url + '/A/B/E/alpha' |
| alpha_path = sbox.ospath('B/E/alpha') |
| |
| # Cannot use run_and_verify_merge with a file target |
| svntest.actions.run_and_verify_svn([], [], 'merge', '-r', '3:4', |
| alpha_url, alpha_path) |
| |
| exit_code, output, err = svntest.actions.run_and_verify_svn(None, [], |
| 'pl', alpha_path) |
| |
| saw_foo = 0 |
| saw_bar = 0 |
| for line in output: |
| if re.match("\\s*foo\\s*$", line): |
| saw_foo = 1 |
| if re.match("\\s*bar\\s*$", line): |
| saw_bar = 1 |
| |
| if not saw_foo or not saw_bar: |
| raise svntest.Failure("Expected properties not found") |
| |
| #---------------------------------------------------------------------- |
| # This is a regression for issue #1176. |
| @Issue(1176) |
| def merge_similar_unrelated_trees(sbox): |
| "merging similar trees ancestrally unrelated" |
| |
| ## See https://issues.apache.org/jira/browse/SVN-1249. ## |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Simple test. Make three directories with the same content. |
| # Modify some stuff in the second one. Now merge |
| # (firstdir:seconddir->thirddir). |
| |
| base1_path = sbox.ospath('base1') |
| base2_path = sbox.ospath('base2') |
| apply_path = sbox.ospath('apply') |
| |
| base1_url = os.path.join(sbox.repo_url + '/base1') |
| base2_url = os.path.join(sbox.repo_url + '/base2') |
| |
| # Make a tree of stuff ... |
| os.mkdir(base1_path) |
| svntest.main.file_append(os.path.join(base1_path, 'iota'), |
| "This is the file iota\n") |
| os.mkdir(os.path.join(base1_path, 'A')) |
| svntest.main.file_append(os.path.join(base1_path, 'A', 'mu'), |
| "This is the file mu\n") |
| os.mkdir(os.path.join(base1_path, 'A', 'B')) |
| svntest.main.file_append(os.path.join(base1_path, 'A', 'B', 'alpha'), |
| "This is the file alpha\n") |
| svntest.main.file_append(os.path.join(base1_path, 'A', 'B', 'beta'), |
| "This is the file beta\n") |
| |
| # ... Copy it twice ... |
| shutil.copytree(base1_path, base2_path) |
| shutil.copytree(base1_path, apply_path) |
| |
| # ... Gonna see if merge is naughty or nice! |
| svntest.main.file_append(os.path.join(base2_path, 'A', 'mu'), |
| "A new line in mu.\n") |
| os.rename(os.path.join(base2_path, 'A', 'B', 'beta'), |
| os.path.join(base2_path, 'A', 'B', 'zeta')) |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'add', base1_path, base2_path, apply_path) |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', 'rev 2', wc_dir) |
| |
| expected_output = wc.State(apply_path, { |
| 'A/mu' : Item(status='U '), |
| 'A/B/zeta' : Item(status='A '), |
| 'A/B/beta' : Item(status='D '), |
| }) |
| |
| # run_and_verify_merge doesn't support 'svn merge URL URL path' |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', |
| '--ignore-ancestry', |
| base1_url, base2_url, |
| apply_path) |
| |
| expected_status = wc.State(apply_path, { |
| '' : Item(status=' '), |
| 'A' : Item(status=' '), |
| 'A/mu' : Item(status='M '), |
| 'A/B' : Item(status=' '), |
| 'A/B/zeta' : Item(status='A ', copied='+'), |
| 'A/B/alpha' : Item(status=' '), |
| 'A/B/beta' : Item(status='D '), |
| 'iota' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=2) |
| expected_status.tweak('A/B/zeta', wc_rev='-') |
| svntest.actions.run_and_verify_status(apply_path, expected_status) |
| |
| #---------------------------------------------------------------------- |
| def merge_one_file_helper(sbox, arg_flav, record_only = 0): |
| """ARG_FLAV is one of 'r' (revision range) or 'c' (single change) or |
| '*' (no revision specified).""" |
| |
| if arg_flav not in ('r', 'c', '*'): |
| raise svntest.Failure("Unrecognized flavor of merge argument") |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| rho_rel_path = os.path.join('A', 'D', 'G', 'rho') |
| rho_path = os.path.join(wc_dir, rho_rel_path) |
| G_path = sbox.ospath('A/D/G') |
| rho_url = sbox.repo_url + '/A/D/G/rho' |
| |
| # Change rho for revision 2 |
| svntest.main.file_append(rho_path, 'A new line in rho.\n') |
| |
| expected_output = wc.State(wc_dir, { rho_rel_path : Item(verb='Sending'), }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/D/G/rho', wc_rev=2) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Backdate rho to revision 1, so we can merge in the rev 2 changes. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'up', '-r', '1', rho_path) |
| |
| # Try one merge with an explicit target; it should succeed. |
| ### Yes, it would be nice to use run_and_verify_merge(), but it |
| # appears to be impossible to get the expected_foo trees working |
| # right. I think something is still assuming a directory target. |
| if arg_flav == 'r': |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[2]], |
| ['U ' + rho_path + '\n', |
| ' U ' + rho_path + '\n']), |
| [], 'merge', '-r', '1:2', rho_url, rho_path) |
| elif arg_flav == 'c': |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[2]], |
| ['U ' + rho_path + '\n', |
| ' U ' + rho_path + '\n']), |
| [], 'merge', '-c', '2', rho_url, rho_path) |
| elif arg_flav == '*': |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[2]], |
| ['U ' + rho_path + '\n', |
| ' U ' + rho_path + '\n']), |
| [], 'merge', rho_url, rho_path) |
| |
| expected_status.tweak(wc_rev=1) |
| expected_status.tweak('A/D/G/rho', status='MM') |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| |
| # Inspect rho, make sure it's right. |
| rho_text = svntest.tree.get_text(rho_path) |
| if rho_text != "This is the file 'rho'.\nA new line in rho.\n": |
| raise svntest.Failure("Unexpected text in merged '" + rho_path + "'") |
| |
| # Restore rho to pristine revision 1, for another merge. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', rho_path) |
| expected_status.tweak('A/D/G/rho', status=' ') |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| |
| # Cd into the directory and run merge with no targets. |
| # It should still merge into rho. |
| saved_cwd = os.getcwd() |
| os.chdir(G_path) |
| |
| # Cannot use run_and_verify_merge with a file target |
| merge_cmd = ['merge'] |
| if arg_flav == 'r': |
| merge_cmd += ['-r', '1:2'] |
| elif arg_flav == 'c': |
| merge_cmd += ['-c', '2'] |
| |
| if record_only: |
| expected_output = expected_merge_output([[2]], |
| [' U rho\n']) |
| merge_cmd.append('--record-only') |
| rho_expected_status = ' M' |
| else: |
| expected_output = expected_merge_output([[2]], |
| ['U rho\n', |
| ' U rho\n']) |
| rho_expected_status = 'MM' |
| merge_cmd.append(rho_url) |
| |
| svntest.actions.run_and_verify_svn(expected_output, [], *merge_cmd) |
| |
| # Inspect rho, make sure it's right. |
| rho_text = svntest.tree.get_text('rho') |
| if record_only: |
| expected_text = "This is the file 'rho'.\n" |
| else: |
| expected_text = "This is the file 'rho'.\nA new line in rho.\n" |
| if rho_text != expected_text: |
| print("") |
| raise svntest.Failure("Unexpected text merged to 'rho' in '" + |
| G_path + "'") |
| os.chdir(saved_cwd) |
| |
| expected_status.tweak('A/D/G/rho', status=rho_expected_status) |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(1150) |
| def merge_one_file_using_r(sbox): |
| "merge one file using the -r option" |
| merge_one_file_helper(sbox, 'r') |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(1150) |
| def merge_one_file_using_c(sbox): |
| "merge one file using the -c option" |
| merge_one_file_helper(sbox, 'c') |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def merge_one_file_using_implicit_revs(sbox): |
| "merge one file without explicit revisions" |
| merge_one_file_helper(sbox, '*') |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def merge_record_only(sbox): |
| "mark a revision range as merged" |
| merge_one_file_helper(sbox, 'r', 1) |
| |
| #---------------------------------------------------------------------- |
| # This is a regression test for the enhancement added in issue #785 "add |
| # friendly enhancement to 'svn merge'", which is about inferring that |
| # the default target of "svn merge [-r...] FILE" should not be "." but |
| # rather should be "FILE". |
| def merge_with_implicit_target_helper(sbox, arg_flav): |
| """ARG_FLAV is one of 'r' (revision range) or 'c' (single change) or |
| '*' (no revision specified).""" |
| |
| if arg_flav not in ('r', 'c', '*'): |
| raise svntest.Failure("Unrecognized flavor of merge argument") |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Change mu for revision 2 |
| mu_path = sbox.ospath('A/mu') |
| orig_mu_text = svntest.tree.get_text(mu_path) |
| added_mu_text = "" |
| for x in range(2,11): |
| added_mu_text = added_mu_text + 'This is line ' + repr(x) + ' in mu\n' |
| svntest.main.file_append(mu_path, added_mu_text) |
| |
| # Create expected output tree for initial commit |
| expected_output = wc.State(wc_dir, { |
| 'A/mu' : Item(verb='Sending'), |
| }) |
| |
| # Create expected status tree; all local revisions should be at 1, |
| # but mu should be at revision 2. |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/mu', wc_rev=2) |
| |
| # Initial commit. |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Make the "other" working copy, at r1 |
| other_wc = sbox.add_wc_path('other') |
| svntest.actions.duplicate_dir(wc_dir, other_wc) |
| svntest.main.run_svn(None, 'up', '-r', 1, other_wc) |
| |
| # Try the merge without an explicit target; it should succeed. |
| # Can't use run_and_verify_merge cuz it expects a directory argument. |
| mu_url = sbox.repo_url + '/A/mu' |
| |
| os.chdir(os.path.join(other_wc, 'A')) |
| |
| # merge using filename for sourcepath |
| # Cannot use run_and_verify_merge with a file target |
| if arg_flav == 'r': |
| svntest.actions.run_and_verify_svn(expected_merge_output([[2]], |
| ['U mu\n', |
| ' U mu\n']), |
| [], |
| 'merge', '-r', '1:2', 'mu') |
| elif arg_flav == 'c': |
| svntest.actions.run_and_verify_svn(expected_merge_output([[2]], |
| ['U mu\n', |
| ' U mu\n']), |
| [], |
| 'merge', '-c', '2', 'mu') |
| |
| elif arg_flav == '*': |
| # Without a peg revision, the default merge range of BASE:1 (which |
| # is a no-op) will be chosen. Let's do it both ways (no-op first, |
| # of course). |
| svntest.actions.run_and_verify_svn(None, [], 'merge', 'mu') |
| svntest.actions.run_and_verify_svn(expected_merge_output([[2]], |
| ['U mu\n', |
| ' U mu\n']), |
| [], |
| 'merge', 'mu@2') |
| |
| # sanity-check resulting file |
| if svntest.tree.get_text('mu') != orig_mu_text + added_mu_text: |
| raise svntest.Failure("Unexpected text in 'mu'") |
| |
| # merge using URL for sourcepath |
| if arg_flav == 'r': |
| svntest.actions.run_and_verify_svn(expected_merge_output([[-2]], |
| ['G mu\n', |
| ' U mu\n', |
| ' G mu\n',], |
| elides=True), |
| [], |
| 'merge', '-r', '2:1', mu_url) |
| elif arg_flav == 'c': |
| svntest.actions.run_and_verify_svn(expected_merge_output([[-2]], |
| ['G mu\n', |
| ' U mu\n', |
| ' G mu\n'], |
| elides=True), |
| [], |
| 'merge', '-c', '-2', mu_url) |
| elif arg_flav == '*': |
| # Implicit merge source URL and revision range detection is for |
| # forward merges only (e.g. non-reverts). Undo application of |
| # r2 to enable continuation of the test case. |
| svntest.actions.run_and_verify_svn(expected_merge_output([[-2]], |
| ['G mu\n', |
| ' U mu\n', |
| ' G mu\n'], |
| elides=True), |
| [], |
| 'merge', '-c', '-2', mu_url) |
| |
| # sanity-check resulting file |
| if svntest.tree.get_text('mu') != orig_mu_text: |
| raise svntest.Failure("Unexpected text '%s' in 'mu', expected '%s'" % |
| (svntest.tree.get_text('mu'), orig_mu_text)) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(785) |
| def merge_with_implicit_target_using_r(sbox): |
| "merging a file w/no explicit target path using -r" |
| merge_with_implicit_target_helper(sbox, 'r') |
| |
| #---------------------------------------------------------------------- |
| @Issue(785) |
| def merge_with_implicit_target_using_c(sbox): |
| "merging a file w/no explicit target path using -c" |
| merge_with_implicit_target_helper(sbox, 'c') |
| |
| #---------------------------------------------------------------------- |
| @Issue(785) |
| def merge_with_implicit_target_and_revs(sbox): |
| "merging a file w/no explicit target path or revs" |
| merge_with_implicit_target_helper(sbox, '*') |
| |
| #---------------------------------------------------------------------- |
| def merge_with_prev(sbox): |
| "merge operations using PREV revision" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Change mu for revision 2 |
| mu_path = sbox.ospath('A/mu') |
| orig_mu_text = svntest.tree.get_text(mu_path) |
| added_mu_text = "" |
| for x in range(2,11): |
| added_mu_text = added_mu_text + '\nThis is line ' + repr(x) + ' in mu' |
| added_mu_text += "\n" |
| svntest.main.file_append(mu_path, added_mu_text) |
| |
| zot_path = sbox.ospath('A/zot') |
| |
| svntest.main.file_append(zot_path, "bar") |
| svntest.main.run_svn(None, 'add', zot_path) |
| |
| # Create expected output tree for initial commit |
| expected_output = wc.State(wc_dir, { |
| 'A/mu' : Item(verb='Sending'), |
| 'A/zot' : Item(verb='Adding'), |
| }) |
| |
| # Create expected status tree; all local revisions should be at 1, |
| # but mu should be at revision 2. |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/mu', wc_rev=2) |
| expected_status.add({'A/zot' : Item(status=' ', wc_rev=2)}) |
| |
| # Initial commit. |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Make some other working copies |
| other_wc = sbox.add_wc_path('other') |
| svntest.actions.duplicate_dir(wc_dir, other_wc) |
| |
| another_wc = sbox.add_wc_path('another') |
| svntest.actions.duplicate_dir(wc_dir, another_wc) |
| |
| was_cwd = os.getcwd() |
| |
| os.chdir(os.path.join(other_wc, 'A')) |
| |
| # Try to revert the last change to mu via svn merge |
| # Cannot use run_and_verify_merge with a file target |
| svntest.actions.run_and_verify_svn(expected_merge_output([[-2]], |
| ['U mu\n', |
| ' U mu\n'], |
| elides=True), |
| [], |
| 'merge', '-r', 'HEAD:PREV', 'mu') |
| |
| # sanity-check resulting file |
| if svntest.tree.get_text('mu') != orig_mu_text: |
| raise svntest.Failure("Unexpected text in 'mu'") |
| |
| os.chdir(was_cwd) |
| |
| other_status = expected_status |
| other_status.wc_dir = other_wc |
| other_status.tweak('A/mu', status='M ', wc_rev=2) |
| other_status.tweak('A/zot', wc_rev=2) |
| svntest.actions.run_and_verify_status(other_wc, other_status) |
| |
| os.chdir(another_wc) |
| |
| # ensure 'A' will be at revision 2 |
| svntest.actions.run_and_verify_svn(None, [], 'up') |
| |
| # now try a revert on a directory, and verify that it removed the zot |
| # file we had added previously |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '-r', 'COMMITTED:PREV', |
| 'A', 'A') |
| |
| if svntest.tree.get_text('A/zot') != None: |
| raise svntest.Failure("Unexpected text in 'A/zot'") |
| |
| os.chdir(was_cwd) |
| |
| another_status = expected_status |
| another_status.wc_dir = another_wc |
| another_status.tweak(wc_rev=2) |
| another_status.tweak('A/mu', status='M ') |
| another_status.tweak('A/zot', status='D ') |
| svntest.actions.run_and_verify_status(another_wc, another_status) |
| |
| #---------------------------------------------------------------------- |
| # Regression test for issue #1319: 'svn merge' should *not* 'C' when |
| # merging a change into a binary file, unless it has local mods, or has |
| # different contents from the left side of the merge. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(1319) |
| def merge_binary_file(sbox): |
| "merge change into unchanged binary file" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Add a binary file to the project |
| theta_contents = open(os.path.join(sys.path[0], "theta.bin"), 'rb').read() |
| # Write PNG file data into 'A/theta'. |
| theta_path = sbox.ospath('A/theta') |
| svntest.main.file_write(theta_path, theta_contents, 'wb') |
| |
| svntest.main.run_svn(None, 'add', theta_path) |
| |
| # Commit the new binary file, creating revision 2. |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/theta' : Item(verb='Adding (bin)'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/theta' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Make the "other" working copy |
| other_wc = sbox.add_wc_path('other') |
| svntest.actions.duplicate_dir(wc_dir, other_wc) |
| |
| # Change the binary file in first working copy, commit revision 3. |
| svntest.main.file_append(theta_path, "some extra junk") |
| expected_output = wc.State(wc_dir, { |
| 'A/theta' : Item(verb='Sending'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/theta' : Item(status=' ', wc_rev=3), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # In second working copy, attempt to 'svn merge -r 2:3'. |
| # We should *not* see a conflict during the update, but a 'U'. |
| # And after the merge, the status should be 'M'. |
| expected_output = wc.State(other_wc, { |
| 'A/theta' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(other_wc, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(other_wc, { |
| }) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.add({ |
| '' : Item(props={SVN_PROP_MERGEINFO : '/:3'}), |
| 'A/theta' : Item(theta_contents + b"some extra junk", |
| props={'svn:mime-type' : 'application/octet-stream'}), |
| }) |
| expected_status = svntest.actions.get_virginal_state(other_wc, 1) |
| expected_status.add({ |
| '' : Item(status=' M', wc_rev=1), |
| 'A/theta' : Item(status='M ', wc_rev=2), |
| }) |
| expected_skip = wc.State('', { }) |
| |
| svntest.actions.run_and_verify_merge(other_wc, '2', '3', |
| sbox.repo_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], |
| True, True, '--allow-mixed-revisions', |
| other_wc) |
| |
| #---------------------------------------------------------------------- |
| # Regression test for Issue #1297: |
| # A merge that creates a new file followed by an immediate diff |
| # The diff should succeed. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(1297) |
| def merge_in_new_file_and_diff(sbox): |
| "diff after merge that creates a new file" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| trunk_url = sbox.repo_url + '/A/B/E' |
| |
| # Create a branch |
| svntest.actions.run_and_verify_svn(None, [], 'cp', |
| trunk_url, |
| sbox.repo_url + '/branch', |
| '-m', "Creating the Branch") |
| |
| # Update to revision 2. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'update', wc_dir) |
| |
| new_file_path = sbox.ospath('A/B/E/newfile') |
| svntest.main.file_write(new_file_path, "newfile\n") |
| |
| # Add the new file, and commit revision 3. |
| svntest.actions.run_and_verify_svn(None, [], "add", new_file_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', |
| "Changing the trunk.", wc_dir) |
| |
| branch_path = sbox.ospath('branch') |
| url_branch_path = branch_path.replace(os.path.sep, '/') |
| |
| # Merge our addition into the branch. |
| expected_output = svntest.wc.State(branch_path, { |
| 'newfile' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = svntest.wc.State(branch_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(branch_path, { |
| }) |
| expected_disk = wc.State('', { |
| 'alpha' : Item("This is the file 'alpha'.\n"), |
| 'beta' : Item("This is the file 'beta'.\n"), |
| 'newfile' : Item("newfile\n"), |
| }) |
| expected_status = wc.State(branch_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'alpha' : Item(status=' ', wc_rev=2), |
| 'beta' : Item(status=' ', wc_rev=2), |
| 'newfile' : Item(status='A ', wc_rev='-', copied='+') |
| }) |
| expected_skip = wc.State('', { }) |
| |
| svntest.actions.run_and_verify_merge(branch_path, |
| '1', 'HEAD', trunk_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip) |
| |
| # Finally, run diff. |
| expected_output = [ |
| "Index: " + url_branch_path + "/newfile\n", |
| "===================================================================\n", |
| "--- "+ url_branch_path + "/newfile (nonexistent)\n", |
| "+++ "+ url_branch_path + "/newfile (working copy)\n", |
| "@@ -0,0 +1 @@\n", |
| "+newfile\n", |
| |
| "Index: " + url_branch_path + "\n", |
| "===================================================================\n", |
| "--- "+ url_branch_path + "\t(revision 2)\n", |
| "+++ "+ url_branch_path + "\t(working copy)\n", |
| "\n", |
| "Property changes on: " + url_branch_path + "\n", |
| "___________________________________________________________________\n", |
| "Added: " + SVN_PROP_MERGEINFO + "\n", |
| "## -0,0 +0,1 ##\n", |
| " Merged /A/B/E:r2-3\n", |
| ] |
| svntest.actions.run_and_verify_svn(expected_output, [], 'diff', |
| '--show-copies-as-adds', branch_path) |
| |
| |
| #---------------------------------------------------------------------- |
| # Issue #1425: 'svn merge' should skip over any unversioned obstructions. |
| # This test involves tree conflicts. - but attempting to test for |
| # pre-tree-conflict behaviour |
| @SkipUnless(server_has_mergeinfo) |
| @Issues(1425, 2898) |
| def merge_skips_obstructions(sbox): |
| "merge should skip over unversioned obstructions" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| C_path = sbox.ospath('A/C') |
| F_path = sbox.ospath('A/B/F') |
| F_url = sbox.repo_url + '/A/B/F' |
| |
| Q_path = os.path.join(F_path, 'Q') |
| foo_path = os.path.join(F_path, 'foo') |
| bar_path = os.path.join(F_path, 'Q', 'bar') |
| |
| svntest.main.run_svn(None, 'mkdir', Q_path) |
| svntest.main.file_append(foo_path, "foo") |
| svntest.main.file_append(bar_path, "bar") |
| svntest.main.run_svn(None, 'add', foo_path, bar_path) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/B/F/Q' : Item(verb='Adding'), |
| 'A/B/F/Q/bar' : Item(verb='Adding'), |
| 'A/B/F/foo' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B/F/Q' : Item(status=' ', wc_rev=2), |
| 'A/B/F/Q/bar' : Item(status=' ', wc_rev=2), |
| 'A/B/F/foo' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| pre_merge_status = expected_status |
| |
| # Revision 2 now has A/B/F/foo, A/B/F/Q, A/B/F/Q/bar. Let's merge |
| # those 'F' changes into empty dir 'C'. But first, create an |
| # unversioned 'foo' within C, and make sure 'svn merge' doesn't |
| # error when the addition of foo is obstructed. |
| |
| expected_output = wc.State(C_path, { |
| 'Q' : Item(status='A '), |
| 'Q/bar' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(C_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(C_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B/F:2'}), |
| 'Q' : Item(), |
| 'Q/bar' : Item("bar"), |
| 'foo' : Item("foo"), |
| }) |
| expected_status = wc.State(C_path, { |
| '' : Item(status=' M', wc_rev=1), |
| 'Q' : Item(status='A ', wc_rev='-', copied='+'), |
| 'Q/bar' : Item(status=' ', wc_rev='-', copied='+'), |
| }) |
| expected_skip = wc.State(C_path, { |
| 'foo' : Item(verb='Skipped'), |
| }) |
| # Unversioned: |
| svntest.main.file_append(os.path.join(C_path, "foo"), "foo") |
| |
| svntest.actions.run_and_verify_merge(C_path, '1', '2', F_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True) |
| |
| # Revert the local mods, and this time make "Q" obstructed. An |
| # unversioned file called "Q" will obstruct the adding of the |
| # directory of the same name. |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '-R', wc_dir) |
| os.unlink(os.path.join(C_path, "foo")) |
| svntest.main.safe_rmtree(os.path.join(C_path, "Q")) |
| svntest.main.file_append(os.path.join(C_path, "Q"), "foo") # unversioned |
| svntest.actions.run_and_verify_status(wc_dir, pre_merge_status) |
| |
| expected_output = wc.State(C_path, { |
| 'foo' : Item(status='A '), |
| 'Q/bar' : Item(status=' ', treeconflict='A'), # Skipped |
| }) |
| expected_mergeinfo_output = wc.State(C_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(C_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B/F:2'}), |
| 'Q' : Item("foo"), |
| 'foo' : Item("foo"), |
| }) |
| expected_status = wc.State(C_path, { |
| '' : Item(status=' M', wc_rev=1), |
| 'foo' : Item(status='A ', wc_rev='-', copied='+'), |
| }) |
| expected_skip = wc.State(C_path, { |
| 'Q' : Item(verb='Skipped'), |
| }) |
| |
| svntest.actions.run_and_verify_merge(C_path, '1', '2', F_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True) |
| |
| # Revert the local mods, and commit the deletion of iota and A/D/G. (r3) |
| os.unlink(os.path.join(C_path, "foo")) |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| svntest.actions.run_and_verify_status(wc_dir, pre_merge_status) |
| |
| iota_path = sbox.ospath('iota') |
| G_path = sbox.ospath('A/D/G') |
| svntest.actions.run_and_verify_svn(None, [], 'rm', iota_path, G_path) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/D/G' : Item(verb='Deleting'), |
| 'iota' : Item(verb='Deleting'), |
| }) |
| expected_status = pre_merge_status |
| expected_status.remove('iota', 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Now create unversioned iota and A/D/G, try running a merge -r2:3. |
| # The merge process should skip over these targets, since they're |
| # unversioned. |
| |
| svntest.main.file_append(iota_path, "foo") # unversioned |
| os.mkdir(G_path) # unversioned |
| |
| expected_output = wc.State(wc_dir, { |
| }) |
| expected_mergeinfo_output = wc.State(wc_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(wc_dir, { |
| }) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.remove('A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau') |
| expected_disk.add({ |
| '' : Item(props={SVN_PROP_MERGEINFO : '/:3'}), |
| 'A/B/F/Q' : Item(), |
| 'A/B/F/Q/bar' : Item("bar"), |
| 'A/B/F/foo' : Item("foo"), |
| 'A/C/Q' : Item("foo"), |
| }) |
| expected_disk.tweak('iota', contents="foo") |
| # No-op merge still sets mergeinfo |
| expected_status.tweak('', status=' M') |
| expected_skip = wc.State(wc_dir, { |
| 'iota' : Item(verb='Skipped'), |
| 'A/D/G' : Item(verb='Skipped'), |
| }) |
| svntest.actions.run_and_verify_merge(wc_dir, '2', '3', |
| sbox.repo_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status.copy(wc_dir), |
| expected_skip, |
| [], |
| True, False, '--allow-mixed-revisions', |
| wc_dir) |
| |
| # Revert the local mods, and commit a change to A/B/lambda (r4), and then |
| # commit the deletion of the same file. (r5) |
| svntest.main.safe_rmtree(G_path) |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| expected_status.tweak('', status=' ') |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| |
| lambda_path = sbox.ospath('A/B/lambda') |
| svntest.main.file_append(lambda_path, "more text") |
| expected_output = wc.State(wc_dir, { |
| 'A/B/lambda' : Item(verb='Sending'), |
| }) |
| expected_status.tweak('A/B/lambda', wc_rev=4) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'rm', lambda_path) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/B/lambda' : Item(verb='Deleting'), |
| }) |
| expected_status.remove('A/B/lambda') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # lambda is gone, so create an unversioned lambda in its place. |
| # Then attempt to merge -r3:4, which is a change to lambda. The merge |
| # should simply skip the unversioned file. |
| |
| svntest.main.file_append(lambda_path, "foo") # unversioned |
| |
| expected_output = wc.State(wc_dir, { }) |
| expected_mergeinfo_output = wc.State(wc_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(wc_dir, { |
| }) |
| expected_disk.add({ |
| 'A/B/lambda' : Item("foo"), |
| }) |
| expected_disk.remove('A/D/G') |
| expected_disk.tweak('', props={SVN_PROP_MERGEINFO : '/:4'}) |
| expected_skip = wc.State(wc_dir, { |
| 'A/B/lambda' : Item(verb='Skipped'), |
| }) |
| # No-op merge still sets mergeinfo. |
| expected_status_short = expected_status.copy(wc_dir) |
| expected_status_short.tweak('', status=' M') |
| |
| svntest.actions.run_and_verify_merge(wc_dir, '3', '4', |
| sbox.repo_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status_short, |
| expected_skip, |
| [], |
| True, False, '--allow-mixed-revisions', |
| wc_dir) |
| |
| # OK, so let's commit the new lambda (r6), and then delete the |
| # working file. Then re-run the -r3:4 merge, and see how svn deals |
| # with a file being under version control, but missing. |
| |
| svntest.actions.run_and_verify_svn(None, [], 'add', lambda_path) |
| |
| # Mergeinfo prop changed so update to avoid out of date error. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| expected_output = wc.State(wc_dir, { |
| '' : Item(verb='Sending'), |
| 'A/B/lambda' : Item(verb='Adding'), |
| }) |
| expected_mergeinfo_output = wc.State(wc_dir, {}) |
| expected_elision_output = wc.State(wc_dir, {}) |
| expected_status.tweak(wc_rev=5) |
| expected_status.add({ |
| 'A/B/lambda' : Item(wc_rev=6, status=' '), |
| }) |
| expected_status.tweak('', status=' ', wc_rev=6) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| os.unlink(lambda_path) |
| |
| expected_output = wc.State(wc_dir, { }) |
| expected_disk.remove('A/B/lambda') |
| expected_status.tweak('A/B/lambda', status='! ') |
| expected_status.tweak('', status=' ') |
| expected_skip = wc.State(wc_dir, { |
| 'A/B/lambda' : Item(verb='Skipped missing target'), |
| }) |
| # Why do we need to --ignore-ancestry? Because the previous merge of r4, |
| # despite being inoperative, set mergeinfo for r4 on the WC. With the |
| # advent of merge tracking this repeat merge attempt would not be attempted. |
| # By using --ignore-ancestry we disregard the mergeinfo and *really* try to |
| # merge into a missing path. This is another facet of issue #2898. |
| svntest.actions.run_and_verify_merge(wc_dir, '3', '4', |
| sbox.repo_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status.copy(wc_dir), |
| expected_skip, |
| [], |
| 1, 0, '--ignore-ancestry', |
| '--allow-mixed-revisions', wc_dir) |
| |
| #---------------------------------------------------------------------- |
| # At one time, a merge that added items with the same name as missing |
| # items would attempt to add the items and fail, leaving the working |
| # copy locked and broken. |
| |
| # This test involves tree conflicts. |
| @SkipUnless(server_has_mergeinfo) |
| def merge_into_missing(sbox): |
| "merge into missing must not break working copy" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| F_path = sbox.ospath('A/B/F') |
| F_url = sbox.repo_url + '/A/B/F' |
| Q_path = os.path.join(F_path, 'Q') |
| foo_path = os.path.join(F_path, 'foo') |
| |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', Q_path) |
| svntest.main.file_append(foo_path, "foo") |
| svntest.actions.run_and_verify_svn(None, [], 'add', foo_path) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/B/F/Q' : Item(verb='Adding'), |
| 'A/B/F/foo' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B/F/Q' : Item(status=' ', wc_rev=2), |
| 'A/B/F/foo' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| R_path = os.path.join(Q_path, 'R') |
| bar_path = os.path.join(R_path, 'bar') |
| baz_path = os.path.join(Q_path, 'baz') |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', R_path) |
| svntest.main.file_append(bar_path, "bar") |
| svntest.actions.run_and_verify_svn(None, [], 'add', bar_path) |
| svntest.main.file_append(baz_path, "baz") |
| svntest.actions.run_and_verify_svn(None, [], 'add', baz_path) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/B/F/Q/R' : Item(verb='Adding'), |
| 'A/B/F/Q/R/bar' : Item(verb='Adding'), |
| 'A/B/F/Q/baz' : Item(verb='Adding'), |
| }) |
| expected_status.add({ |
| 'A/B/F/Q/R' : Item(status=' ', wc_rev=3), |
| 'A/B/F/Q/R/bar' : Item(status=' ', wc_rev=3), |
| 'A/B/F/Q/baz' : Item(status=' ', wc_rev=3), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| os.unlink(foo_path) |
| svntest.main.safe_rmtree(Q_path) |
| |
| expected_output = wc.State(F_path, { |
| }) |
| expected_mergeinfo_output = wc.State(F_path, { |
| }) |
| expected_elision_output = wc.State(F_path, { |
| }) |
| expected_disk = wc.State('', { |
| }) |
| expected_status = wc.State(F_path, { |
| '' : Item(status=' ', wc_rev=1), |
| 'foo' : Item(status='! ', wc_rev=2), |
| 'Q' : Item(status='! ', wc_rev=2), |
| # Missing data still available |
| 'Q/R' : Item(status='! ', wc_rev=3), |
| 'Q/R/bar' : Item(status='! ', wc_rev=3), |
| 'Q/baz' : Item(status='! ', wc_rev=3), |
| }) |
| expected_skip = wc.State(F_path, { |
| 'Q' : Item(verb='Skipped missing target'), |
| 'foo' : Item(verb='Skipped missing target'), |
| }) |
| # Use --ignore-ancestry because merge tracking aware merges raise an |
| # error when the merge target is missing subtrees due to OS-level |
| # deletes. |
| |
| ### Need to real and dry-run separately since real merge notifies Q |
| ### twice! |
| svntest.actions.run_and_verify_merge(F_path, '1', '2', F_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], False, False, |
| '--dry-run', |
| '--ignore-ancestry', |
| '--allow-mixed-revisions', |
| F_path) |
| |
| expected_status = wc.State(F_path, { |
| '' : Item(status=' ', wc_rev=1), |
| 'foo' : Item(status='! ', wc_rev=2), |
| 'Q' : Item(status='! ', wc_rev='2'), |
| # Revision is known and we can record mergeinfo |
| 'Q/R' : Item(status='! ', wc_rev='3'), |
| 'Q/R/bar' : Item(status='! ', wc_rev='3'), |
| 'Q/baz' : Item(status='! ', wc_rev='3'), |
| }) |
| expected_mergeinfo_output = wc.State(F_path, { |
| }) |
| |
| svntest.actions.run_and_verify_merge(F_path, '1', '2', F_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], False, False, |
| '--ignore-ancestry', |
| '--allow-mixed-revisions', |
| F_path) |
| |
| # This merge fails when it attempts to descend into the missing |
| # directory. That's OK, there is no real need to support merge into |
| # an incomplete working copy, so long as when it fails it doesn't |
| # break the working copy. |
| svntest.main.run_svn('Working copy not locked', |
| 'merge', '-r1:3', '--dry-run', F_url, F_path) |
| |
| svntest.main.run_svn('Working copy not locked', |
| 'merge', '-r1:3', F_url, F_path) |
| |
| # Check working copy is not locked. |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B/F' : Item(status=' ', wc_rev=1), |
| 'A/B/F/foo' : Item(status='! ', wc_rev=2), |
| 'A/B/F/Q' : Item(status='! ', wc_rev=2), |
| 'A/B/F/Q/baz' : Item(status='! ', wc_rev='3'), |
| 'A/B/F/Q/R' : Item(status='! ', wc_rev='3'), |
| 'A/B/F/Q/R/bar' : Item(status='! ', wc_rev='3'), |
| }) |
| |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| |
| #---------------------------------------------------------------------- |
| # A test for issue 1738 |
| @Issue(1738) |
| @SkipUnless(server_has_mergeinfo) |
| def dry_run_adds_file_with_prop(sbox): |
| "merge --dry-run adding a new file with props" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Commit a new file which has a property. |
| zig_path = sbox.ospath('A/B/E/zig') |
| svntest.main.file_append(zig_path, "zig contents") |
| svntest.actions.run_and_verify_svn(None, [], 'add', zig_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val', |
| zig_path) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/B/E/zig' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B/E/zig' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Do a regular merge of that change into a different dir. |
| F_path = sbox.ospath('A/B/F') |
| E_url = sbox.repo_url + '/A/B/E' |
| |
| expected_output = wc.State(F_path, { |
| 'zig' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(F_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(F_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B/E:2'}), |
| 'zig' : Item("zig contents", {'foo':'foo_val'}), |
| }) |
| expected_skip = wc.State('', { }) |
| expected_status = None # status is optional |
| |
| svntest.actions.run_and_verify_merge(F_path, '1', '2', E_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| #---------------------------------------------------------------------- |
| # Regression test for issue #1673 |
| # Merge a binary file from two URL with a common ancestry |
| @Issue(1673) |
| def merge_binary_with_common_ancestry(sbox): |
| "merge binary files with common ancestry" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Create the common ancestry path |
| I_path = sbox.ospath('I') |
| svntest.main.run_svn(None, 'mkdir', I_path) |
| |
| # Add a binary file to the common ancestry path |
| theta_contents = open(os.path.join(sys.path[0], "theta.bin"), 'rb').read() |
| theta_I_path = os.path.join(I_path, 'theta') |
| svntest.main.file_write(theta_I_path, theta_contents, mode='wb') |
| svntest.main.run_svn(None, 'add', theta_I_path) |
| svntest.main.run_svn(None, 'propset', 'svn:mime-type', |
| 'application/octet-stream', theta_I_path) |
| |
| # Commit the ancestry |
| expected_output = wc.State(wc_dir, { |
| 'I' : Item(verb='Adding'), |
| 'I/theta' : Item(verb='Adding (bin)'), |
| }) |
| |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'I' : Item(status=' ', wc_rev=2), |
| 'I/theta' : Item(status=' ', wc_rev=2), |
| }) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| |
| # Create the first branch |
| J_path = sbox.ospath('J') |
| svntest.main.run_svn(None, 'copy', I_path, J_path) |
| |
| # Commit the first branch |
| expected_output = wc.State(wc_dir, { |
| 'J' : Item(verb='Adding'), |
| }) |
| |
| expected_status.add({ |
| 'J' : Item(status=' ', wc_rev=3), |
| 'J/theta' : Item(status=' ', wc_rev=3), |
| }) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| |
| # Create the path where the files will be merged |
| K_path = sbox.ospath('K') |
| svntest.main.run_svn(None, 'mkdir', K_path) |
| |
| # Commit the new path |
| expected_output = wc.State(wc_dir, { |
| 'K' : Item(verb='Adding'), |
| }) |
| |
| expected_status.add({ |
| 'K' : Item(status=' ', wc_rev=4), |
| }) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| |
| # Copy 'I/theta' to 'K/'. This file will be merged later. |
| theta_K_path = os.path.join(K_path, 'theta') |
| svntest.main.run_svn(None, 'copy', theta_I_path, theta_K_path) |
| |
| # Commit the new file |
| expected_output = wc.State(wc_dir, { |
| 'K/theta' : Item(verb='Adding (bin)'), |
| }) |
| |
| expected_status.add({ |
| 'K/theta' : Item(status=' ', wc_rev=5), |
| }) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| |
| # Modify the original ancestry 'I/theta' |
| svntest.main.file_append(theta_I_path, "some extra junk") |
| |
| # Commit the modification |
| expected_output = wc.State(wc_dir, { |
| 'I/theta' : Item(verb='Sending'), |
| }) |
| |
| expected_status.tweak('I/theta', wc_rev=6) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| |
| # Create the second branch from the modified ancestry |
| L_path = sbox.ospath('L') |
| svntest.main.run_svn(None, 'copy', I_path, L_path) |
| |
| # Commit the second branch |
| expected_output = wc.State(wc_dir, { |
| 'L' : Item(verb='Adding'), |
| 'L/theta' : Item(verb='Replacing'), |
| }) |
| |
| expected_status.add({ |
| 'L' : Item(status=' ', wc_rev=7), |
| 'L/theta' : Item(status=' ', wc_rev=7), |
| }) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| |
| # Now merge first ('J/') and second ('L/') branches into 'K/' |
| saved_cwd = os.getcwd() |
| |
| os.chdir(K_path) |
| theta_J_url = sbox.repo_url + '/J/theta' |
| theta_L_url = sbox.repo_url + '/L/theta' |
| svntest.actions.run_and_verify_svn(expected_merge_output(None, |
| ['U theta\n', |
| ' U theta\n', |
| ' G theta\n',], |
| two_url=True), |
| [], |
| 'merge', theta_J_url, theta_L_url) |
| os.chdir(saved_cwd) |
| |
| expected_status.tweak('K/theta', status='MM') |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| |
| #---------------------------------------------------------------------- |
| # A test for issue 1905 |
| @Issue(1905) |
| @SkipUnless(server_has_mergeinfo) |
| def merge_funny_chars_on_path(sbox): |
| "merge with funny characters" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # In following lists: 'd' stands for directory, 'f' for file |
| # targets to be added by recursive add |
| add_by_add = [ |
| ('d', 'dir_10', 'F%lename'), |
| ('d', 'dir%20', 'F lename'), |
| ('d', 'dir 30', 'Filename'), |
| ('d', 'dir 40', None), |
| ('f', 'F lename', None), |
| ] |
| |
| # targets to be added by 'svn mkdir' + add |
| add_by_mkdir = [ |
| ('d', 'dir_11', 'F%lename'), |
| ('d', 'dir%21', 'Filename'), |
| ('d', 'dir 31', 'F lename'), |
| ('d', 'dir 41', None), |
| ] |
| |
| for target in add_by_add: |
| if target[0] == 'd': |
| target_dir = os.path.join(wc_dir, 'A', 'B', 'E', target[1]) |
| os.mkdir(target_dir) |
| if target[2]: |
| target_path = os.path.join(wc_dir, 'A', 'B', 'E', '%s' % target[1], |
| target[2]) |
| svntest.main.file_append(target_path, "%s/%s" % (target[1], target[2])) |
| svntest.actions.run_and_verify_svn(None, [], 'add', target_dir) |
| elif target[0] == 'f': |
| target_path = os.path.join(wc_dir, 'A', 'B', 'E', '%s' % target[1]) |
| svntest.main.file_append(target_path, "%s" % target[1]) |
| svntest.actions.run_and_verify_svn(None, [], 'add', target_path) |
| else: |
| raise svntest.Failure |
| |
| |
| for target in add_by_mkdir: |
| if target[0] == 'd': |
| target_dir = os.path.join(wc_dir, 'A', 'B', 'E', target[1]) |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', target_dir) |
| if target[2]: |
| target_path = os.path.join(wc_dir, 'A', 'B', 'E', '%s' % target[1], |
| target[2]) |
| svntest.main.file_append(target_path, "%s/%s" % (target[1], target[2])) |
| svntest.actions.run_and_verify_svn(None, [], 'add', target_path) |
| |
| expected_output_dic = {} |
| expected_status_dic = {} |
| |
| for targets in add_by_add,add_by_mkdir: |
| for target in targets: |
| key = 'A/B/E/%s' % target[1] |
| expected_output_dic[key] = Item(verb='Adding') |
| expected_status_dic[key] = Item(status=' ', wc_rev=2) |
| |
| if target[2]: |
| key = 'A/B/E/%s/%s' % (target[1], target[2]) |
| expected_output_dic[key] = Item(verb='Adding') |
| expected_status_dic[key] = Item(status=' ', wc_rev=2) |
| |
| |
| expected_output = wc.State(wc_dir, expected_output_dic) |
| |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add(expected_status_dic) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Do a regular merge of that change into a different dir. |
| F_path = sbox.ospath('A/B/F') |
| E_url = sbox.repo_url + '/A/B/E' |
| |
| expected_output_dic = {} |
| expected_disk_dic = {} |
| |
| for targets in add_by_add,add_by_mkdir: |
| for target in targets: |
| key = '%s' % target[1] |
| expected_output_dic[key] = Item(status='A ') |
| if target[0] == 'd': |
| expected_disk_dic[key] = Item(None, {}) |
| elif target[0] == 'f': |
| expected_disk_dic[key] = Item("%s" % target[1], {}) |
| else: |
| raise svntest.Failure |
| if target[2]: |
| key = '%s/%s' % (target[1], target[2]) |
| expected_output_dic[key] = Item(status='A ') |
| expected_disk_dic[key] = Item('%s/%s' % (target[1], target[2]), {}) |
| |
| expected_output = wc.State(F_path, expected_output_dic) |
| expected_mergeinfo_output = wc.State(F_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(F_path, { |
| }) |
| expected_disk = wc.State('', expected_disk_dic) |
| expected_skip = wc.State('', { }) |
| expected_status = None # status is optional |
| |
| svntest.actions.run_and_verify_merge(F_path, '1', '2', E_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], |
| False, # don't check props |
| True) # but do a dry-run |
| |
| expected_output_dic = {} |
| |
| for targets in add_by_add,add_by_mkdir: |
| for target in targets: |
| key = '%s' % target[1] |
| expected_output_dic[key] = Item(verb='Adding') |
| |
| expected_output = wc.State(F_path, expected_output_dic) |
| expected_output.add({ |
| '' : Item(verb='Sending'), |
| }) |
| |
| svntest.actions.run_and_verify_commit(F_path, |
| expected_output, |
| None) |
| |
| #----------------------------------------------------------------------- |
| # Regression test for issue #2064 |
| @Issue(2064) |
| def merge_keyword_expansions(sbox): |
| "merge changes to keyword expansion property" |
| |
| sbox.build() |
| |
| wcpath = sbox.wc_dir |
| tpath = os.path.join(wcpath, "t") |
| bpath = os.path.join(wcpath, "b") |
| t_fpath = os.path.join(tpath, 'f') |
| b_fpath = os.path.join(bpath, 'f') |
| |
| os.mkdir(tpath) |
| svntest.main.run_svn(None, "add", tpath) |
| # Commit r2. |
| svntest.actions.run_and_verify_svn(None, [], |
| "ci", "-m", "r2", wcpath) |
| |
| # Copy t to b. |
| svntest.main.run_svn(None, "cp", tpath, bpath) |
| # Commit r3 |
| svntest.actions.run_and_verify_svn(None, [], |
| "ci", "-m", "r3", wcpath) |
| |
| # Add a file to t. |
| svntest.main.file_append(t_fpath, "$Revision$") |
| svntest.actions.run_and_verify_svn(None, [], |
| 'add', t_fpath) |
| # Ask for keyword expansion in the file. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'svn:keywords', 'Revision', |
| t_fpath) |
| # Commit r4 |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', 'r4', wcpath) |
| |
| # Update the wc before the merge. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'update', wcpath) |
| |
| expected_status = svntest.actions.get_virginal_state(wcpath, 4) |
| expected_status.add({ |
| 't' : Item(status=' ', wc_rev=4), |
| 't/f' : Item(status=' ', wc_rev=4), |
| 'b' : Item(status=' ', wc_rev=4), |
| }) |
| svntest.actions.run_and_verify_status(wcpath, expected_status) |
| |
| # Do the merge. |
| |
| expected_output = wc.State(bpath, { |
| 'f' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(bpath, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(bpath, { |
| }) |
| expected_disk = wc.State('', { |
| 'f' : Item("$Revision: 4 $"), |
| }) |
| expected_status = wc.State(bpath, { |
| '' : Item(status=' M', wc_rev=4), |
| 'f' : Item(status='A ', wc_rev='-', copied='+'), |
| }) |
| expected_skip = wc.State(bpath, { }) |
| |
| svntest.actions.run_and_verify_merge(bpath, '2', 'HEAD', |
| sbox.repo_url + '/t', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip) |
| |
| #---------------------------------------------------------------------- |
| @Issue(2132) |
| def merge_prop_change_to_deleted_target(sbox): |
| "merge prop change into deleted target" |
| # For issue #2132. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Add a property to alpha. |
| alpha_path = sbox.ospath('A/B/E/alpha') |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val', |
| alpha_path) |
| |
| # Commit the property add as r2. |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B/E/alpha' : Item(verb='Sending'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/B/E/alpha', wc_rev=2, status=' ') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'up', wc_dir) |
| |
| # Remove alpha entirely. |
| svntest.actions.run_and_verify_svn(None, [], 'rm', alpha_path) |
| expected_output = wc.State(wc_dir, { |
| 'A/B/E/alpha' : Item(verb='Deleting'), |
| }) |
| expected_status.tweak(wc_rev=2) |
| expected_status.remove('A/B/E/alpha') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status, |
| [], alpha_path) |
| |
| # Try merging the original propset, which applies to a target that |
| # no longer exists. The bug would only reproduce when run from |
| # inside the wc, so we cd in there. We have to use |
| # --ignore-ancestry here because our merge logic will otherwise |
| # prevent a merge of changes we already have. |
| os.chdir(wc_dir) |
| svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [], 'merge', |
| '-r1:2', '--ignore-ancestry', '.') |
| |
| #---------------------------------------------------------------------- |
| # A merge that replaces a directory |
| # Tests for Issue #2144 and Issue #2607 |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(2144,2607) |
| def merge_dir_replace(sbox): |
| "merge a replacement of a directory" |
| |
| set_up_dir_replace(sbox) |
| wc_dir = sbox.wc_dir |
| |
| C_path = sbox.ospath('A/C') |
| F_path = sbox.ospath('A/B/F') |
| F_url = sbox.repo_url + '/A/B/F' |
| foo_path = os.path.join(F_path, 'foo') |
| new_file2 = os.path.join(foo_path, "new file 2") |
| |
| # Recreate foo in F and add a new folder and two files |
| bar_path = os.path.join(foo_path, 'bar') |
| foo_file = os.path.join(foo_path, "file foo") |
| new_file3 = os.path.join(bar_path, "new file 3") |
| |
| # Make a couple of directories, and add some files within them. |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', foo_path) |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', bar_path) |
| svntest.main.file_append(new_file3, "Initial text in new file 3.\n") |
| svntest.main.run_svn(None, "add", new_file3) |
| svntest.main.file_append(foo_file, "Initial text in file foo.\n") |
| svntest.main.run_svn(None, "add", foo_file) |
| |
| # Commit the new content, creating r5. |
| expected_output = wc.State(wc_dir, { |
| 'A/B/F/foo' : Item(verb='Adding'), |
| 'A/B/F/foo/file foo' : Item(verb='Adding'), |
| 'A/B/F/foo/bar' : Item(verb='Adding'), |
| 'A/B/F/foo/bar/new file 3' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B/F/foo' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/file foo' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/bar' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/bar/new file 3' : Item(status=' ', wc_rev=5), |
| 'A/C' : Item(status=' ', wc_rev=3), |
| 'A/C/foo' : Item(status=' ', wc_rev=3), |
| 'A/C/foo/new file' : Item(status=' ', wc_rev=3), |
| 'A/C/foo/new file 2' : Item(status=' ', wc_rev=3), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| # Merge replacement of foo onto C |
| expected_output = wc.State(C_path, { |
| 'foo' : Item(status='R '), |
| 'foo/file foo' : Item(status='A '), |
| 'foo/bar' : Item(status='A '), |
| 'foo/bar/new file 3' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(C_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(C_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B/F:2-5'}), |
| 'foo' : Item(), |
| 'foo/file foo' : Item("Initial text in file foo.\n"), |
| 'foo/bar' : Item(), |
| 'foo/bar/new file 3' : Item("Initial text in new file 3.\n"), |
| }) |
| expected_status = wc.State(C_path, { |
| '' : Item(status=' M', wc_rev=3), |
| 'foo' : Item(status='R ', wc_rev='-', copied='+'), |
| 'foo/new file 2' : Item(status='D ', wc_rev='3'), |
| 'foo/file foo' : Item(status=' ', wc_rev='-', copied='+'), |
| 'foo/bar' : Item(status=' ', wc_rev='-', copied='+'), |
| 'foo/bar/new file 3' : Item(status=' ', wc_rev='-', copied='+'), |
| 'foo/new file' : Item(status='D ', wc_rev='3'), |
| }) |
| expected_skip = wc.State(C_path, { }) |
| svntest.actions.run_and_verify_merge(C_path, '2', '5', F_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, |
| False) # don't do a dry-run |
| # the output differs |
| |
| # Commit merge of foo onto C |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/C' : Item(verb='Sending'), |
| 'A/C/foo' : Item(verb='Replacing'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B/F/foo' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/file foo' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/bar' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/bar/new file 3' : Item(status=' ', wc_rev=5), |
| 'A/C' : Item(status=' ', wc_rev=6), |
| 'A/C/foo' : Item(status=' ', wc_rev=6), |
| 'A/C/foo/file foo' : Item(status=' ', wc_rev=6), |
| 'A/C/foo/bar' : Item(status=' ', wc_rev=6), |
| 'A/C/foo/bar/new file 3' : Item(status=' ', wc_rev=6), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| #---------------------------------------------------------------------- |
| # A merge that replaces a directory and one of its children |
| # Tests for Issue #2690 |
| @Issue(2690) |
| def merge_dir_and_file_replace(sbox): |
| "replace both dir and one of its children" |
| |
| set_up_dir_replace(sbox) |
| wc_dir = sbox.wc_dir |
| |
| C_path = sbox.ospath('A/C') |
| F_path = sbox.ospath('A/B/F') |
| F_url = sbox.repo_url + '/A/B/F' |
| foo_path = os.path.join(F_path, 'foo') |
| new_file2 = os.path.join(foo_path, "new file 2") |
| |
| # Recreate foo and 'new file 2' in F and add a new folder with a file |
| bar_path = os.path.join(foo_path, 'bar') |
| new_file3 = os.path.join(bar_path, "new file 3") |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', foo_path) |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', bar_path) |
| svntest.main.file_append(new_file3, "Initial text in new file 3.\n") |
| svntest.main.run_svn(None, "add", new_file3) |
| svntest.main.file_append(new_file2, "New text in new file 2.\n") |
| svntest.main.run_svn(None, "add", new_file2) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/B/F/foo' : Item(verb='Adding'), |
| 'A/B/F/foo/new file 2' : Item(verb='Adding'), |
| 'A/B/F/foo/bar' : Item(verb='Adding'), |
| 'A/B/F/foo/bar/new file 3' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B/F/foo' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/new file 2' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/bar' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/bar/new file 3' : Item(status=' ', wc_rev=5), |
| 'A/C/foo' : Item(status=' ', wc_rev=3), |
| 'A/C/foo/new file' : Item(status=' ', wc_rev=3), |
| 'A/C/foo/new file 2' : Item(status=' ', wc_rev=3), |
| }) |
| expected_status.tweak('A/C', wc_rev=3) # From mergeinfo |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| # Merge replacement of foo onto C |
| expected_output = wc.State(C_path, { |
| 'foo' : Item(status='R '), |
| 'foo/new file 2' : Item(status='A '), |
| 'foo/bar' : Item(status='A '), |
| 'foo/bar/new file 3' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(C_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(C_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B/F:2-5'}), |
| 'foo' : Item(), |
| 'foo/new file 2' : Item("New text in new file 2.\n"), |
| 'foo/bar' : Item(), |
| 'foo/bar/new file 3' : Item("Initial text in new file 3.\n"), |
| }) |
| expected_status = wc.State(C_path, { |
| '' : Item(status=' M', wc_rev=3), |
| 'foo' : Item(status='R ', wc_rev='-', copied='+'), |
| 'foo/new file 2' : Item(status=' ', wc_rev='-', copied='+'), |
| 'foo/bar' : Item(status=' ', wc_rev='-', copied='+'), |
| 'foo/bar/new file 3' : Item(status=' ', wc_rev='-', copied='+'), |
| 'foo/new file' : Item(status='D ', wc_rev=3), |
| }) |
| expected_skip = wc.State(C_path, { }) |
| svntest.actions.run_and_verify_merge(C_path, '2', '5', F_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], |
| True, |
| False) # don't do a dry-run |
| # the output differs |
| |
| # Commit merge of foo onto C |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/C' : Item(verb='Sending'), |
| 'A/C/foo' : Item(verb='Replacing'), |
| }) |
| |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B/F/foo' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/new file 2' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/bar' : Item(status=' ', wc_rev=5), |
| 'A/B/F/foo/bar/new file 3' : Item(status=' ', wc_rev=5), |
| 'A/C' : Item(status=' ', wc_rev=6), |
| 'A/C/foo' : Item(status=' ', wc_rev=6), |
| 'A/C/foo/new file 2' : Item(status=' ', wc_rev=6), |
| 'A/C/foo/bar' : Item(status=' ', wc_rev=6), |
| 'A/C/foo/bar/new file 3' : Item(status=' ', wc_rev=6), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Confirm the files are present in the repository. |
| new_file_2_url = sbox.repo_url + '/A/C/foo/new file 2' |
| svntest.actions.run_and_verify_svn(["New text in new file 2.\n"], |
| [], 'cat', |
| new_file_2_url) |
| new_file_3_url = sbox.repo_url + '/A/C/foo/bar/new file 3' |
| svntest.actions.run_and_verify_svn(["Initial text in new file 3.\n"], |
| [], 'cat', |
| new_file_3_url) |
| |
| #---------------------------------------------------------------------- |
| @Issue(2144) |
| def merge_file_with_space_in_its_name(sbox): |
| "merge a file whose name contains a space" |
| # For issue #2144 |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| new_file = sbox.ospath('new file') |
| |
| # Make r2. |
| svntest.main.file_append(new_file, "Initial text in the file.\n") |
| svntest.main.run_svn(None, "add", new_file) |
| svntest.actions.run_and_verify_svn(None, [], |
| "ci", "-m", "r2", wc_dir) |
| |
| # Make r3. |
| svntest.main.file_append(new_file, "Next line of text in the file.\n") |
| svntest.actions.run_and_verify_svn(None, [], |
| "ci", "-m", "r3", wc_dir) |
| |
| # Try to reverse merge. |
| # |
| # The reproduction recipe requires that no explicit merge target be |
| # passed, so we run merge from inside the wc dir where the target |
| # file (i.e., the URL basename) lives. |
| os.chdir(wc_dir) |
| target_url = sbox.repo_url + '/new%20file' |
| svntest.actions.run_and_verify_svn(None, [], |
| "merge", "-r3:2", target_url) |
| |
| #---------------------------------------------------------------------- |
| # A merge between two branches using no revision number with the dir being |
| # created already existing as an unversioned directory. |
| # Tests for Issue #2222 |
| @Issue(2222) |
| def merge_dir_branches(sbox): |
| "merge between branches" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_uuid = svntest.actions.get_wc_uuid(wc_dir) |
| |
| F_path = sbox.ospath('A/B/F') |
| F_url = sbox.repo_url + '/A/B/F' |
| C_url = sbox.repo_url + '/A/C' |
| |
| # Create foo in F |
| foo_path = os.path.join(F_path, 'foo') |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', foo_path) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/B/F/foo' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B/F/foo' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Create an unversioned foo |
| foo_path = sbox.ospath('foo') |
| os.mkdir(foo_path) |
| |
| # Merge from C to F onto the wc_dir |
| # We can't use run_and_verify_merge because it doesn't support this |
| # syntax of the merge command. |
| ### TODO: We can use run_and_verify_merge() here now. |
| expected_output = expected_merge_output(None, "A " + foo_path + "\n") |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '--allow-mixed-revisions', |
| C_url, F_url, wc_dir) |
| |
| # Run info to check the copied rev to make sure it's right |
| expected_info = {"Path" : re.escape(foo_path), # escape backslashes |
| "URL" : sbox.repo_url + "/foo", |
| "Repository Root" : sbox.repo_url, |
| "Repository UUID" : wc_uuid, |
| "Revision" : "2", |
| "Node Kind" : "directory", |
| "Schedule" : "add", |
| "Copied From URL" : F_url + "/foo", |
| "Copied From Rev" : "2", |
| } |
| svntest.actions.run_and_verify_info([expected_info], foo_path) |
| |
| |
| #---------------------------------------------------------------------- |
| def safe_property_merge(sbox): |
| "property merges don't overwrite existing prop-mods" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Add a property to two files and a directory, commit as r2. |
| alpha_path = sbox.ospath('A/B/E/alpha') |
| beta_path = sbox.ospath('A/B/E/beta') |
| E_path = sbox.ospath('A/B/E') |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val', |
| alpha_path, beta_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val', |
| E_path) |
| |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B/E' : Item(verb='Sending'), |
| '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', 'A/B/E/alpha', 'A/B/E/beta', |
| wc_rev=2, status=' ') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Copy B to B2 as rev 3 (making a branch) |
| B_url = sbox.repo_url + '/A/B' |
| B2_url = sbox.repo_url + '/A/B2' |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'copy', '-m', 'copy B to B2', |
| B_url, B2_url) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Change the properties underneath B again, and commit as r4 |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val2', |
| alpha_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propdel', 'foo', |
| beta_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val2', |
| E_path) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B/E' : Item(verb='Sending'), |
| 'A/B/E/alpha' : Item(verb='Sending'), |
| 'A/B/E/beta' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, None) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Make local propchanges to E, alpha and beta in the branch. |
| alpha_path2 = sbox.ospath('A/B2/E/alpha') |
| beta_path2 = sbox.ospath('A/B2/E/beta') |
| E_path2 = sbox.ospath('A/B2/E') |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'branchval', |
| alpha_path2, beta_path2) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'branchval', |
| E_path2) |
| |
| # Now merge the recent B change to the branch. Because we already |
| # have local propmods, we should get property conflicts. |
| B2_path = sbox.ospath('A/B2') |
| |
| expected_output = wc.State(B2_path, { |
| 'E' : Item(status=' C'), |
| 'E/alpha' : Item(status=' C'), |
| 'E/beta' : Item(status=' C'), |
| }) |
| expected_mergeinfo_output = wc.State(B2_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(B2_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : "/A/B:4"}), |
| 'E' : Item(), |
| 'E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'E/beta' : Item("This is the file 'beta'.\n"), |
| 'F' : Item(), |
| 'lambda' : Item("This is the file 'lambda'.\n"), |
| }) |
| expected_disk.tweak('E', 'E/alpha', 'E/beta', |
| props={'foo' : 'branchval'}) # local mods still present |
| |
| expected_status = wc.State(B2_path, { |
| '' : Item(status=' M'), |
| 'E' : Item(status=' C'), |
| 'E/alpha' : Item(status=' C'), |
| 'E/beta' : Item(status=' C'), |
| 'F' : Item(status=' '), |
| 'lambda' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=4) |
| |
| expected_skip = wc.State('', { }) |
| |
| # should have 3 'prej' files left behind, describing prop conflicts: |
| extra_files = ['alpha.*\.prej', 'beta.*\.prej', 'dir_conflicts.*\.prej'] |
| |
| svntest.actions.run_and_verify_merge(B2_path, '3', '4', B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| extra_files=extra_files) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue 2035, whereby 'svn merge' wouldn't always mark |
| # property conflicts when it should. |
| @Issue(2035) |
| @SkipUnless(server_has_mergeinfo) |
| def property_merge_from_branch(sbox): |
| "property merge conflict even without local mods" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Add a property to a file and a directory, commit as r2. |
| alpha_path = sbox.ospath('A/B/E/alpha') |
| E_path = sbox.ospath('A/B/E') |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val', |
| alpha_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val', |
| E_path) |
| |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B/E' : Item(verb='Sending'), |
| 'A/B/E/alpha' : Item(verb='Sending'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/B/E', 'A/B/E/alpha', wc_rev=2, status=' ') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Copy B to B2 as rev 3 (making a branch) |
| B_url = sbox.repo_url + '/A/B' |
| B2_url = sbox.repo_url + '/A/B2' |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'copy', '-m', 'copy B to B2', |
| B_url, B2_url) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Change the properties underneath B again, and commit as r4 |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val2', |
| alpha_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val2', |
| E_path) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B/E' : Item(verb='Sending'), |
| 'A/B/E/alpha' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, None) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Make different propchanges changes to the B2 branch and commit as r5. |
| alpha_path2 = sbox.ospath('A/B2/E/alpha') |
| E_path2 = sbox.ospath('A/B2/E') |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'branchval', |
| alpha_path2) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'branchval', |
| E_path2) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B2/E' : Item(verb='Sending'), |
| 'A/B2/E/alpha' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, None) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Now merge the recent B change to the branch. There are no local |
| # mods anywhere, but we should still get property conflicts anyway! |
| B2_path = sbox.ospath('A/B2') |
| |
| expected_output = wc.State(B2_path, { |
| 'E' : Item(status=' C'), |
| 'E/alpha' : Item(status=' C'), |
| }) |
| expected_mergeinfo_output = wc.State(B2_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(B2_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:4'}), |
| 'E' : Item(), |
| 'E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'E/beta' : Item("This is the file 'beta'.\n"), |
| 'F' : Item(), |
| 'lambda' : Item("This is the file 'lambda'.\n"), |
| }) |
| expected_disk.tweak('E', 'E/alpha', |
| props={'foo' : 'branchval'}) |
| |
| expected_status = wc.State(B2_path, { |
| '' : Item(status=' M'), |
| 'E' : Item(status=' C'), |
| 'E/alpha' : Item(status=' C'), |
| 'E/beta' : Item(status=' '), |
| 'F' : Item(status=' '), |
| 'lambda' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=5) |
| |
| expected_skip = wc.State('', { }) |
| |
| # should have 2 'prej' files left behind, describing prop conflicts: |
| extra_files = ['alpha.*\.prej', 'dir_conflicts.*\.prej'] |
| |
| svntest.actions.run_and_verify_merge(B2_path, '3', '4', B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| extra_files=extra_files) |
| |
| #---------------------------------------------------------------------- |
| # Another test for issue 2035, whereby sometimes 'svn merge' marked |
| # property conflicts when it shouldn't! |
| @Issue(2035) |
| def property_merge_undo_redo(sbox): |
| "undo, then redo a property merge" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Add a property to a file, commit as r2. |
| alpha_path = sbox.ospath('A/B/E/alpha') |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'foo', 'foo_val', |
| alpha_path) |
| |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B/E/alpha' : Item(verb='Sending'), |
| }) |
| |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/B/E/alpha', wc_rev=2, status=' ') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Use 'svn merge' to undo the commit. ('svn merge -r2:1') |
| # Result should be a single local-prop-mod. |
| expected_output = wc.State(wc_dir, {'A/B/E/alpha' : Item(status=' U'), }) |
| expected_mergeinfo_output = wc.State(wc_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(wc_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_disk = svntest.main.greek_state.copy() |
| |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 2) |
| expected_status.tweak('A/B/E/alpha', status=' M') |
| |
| expected_skip = wc.State('', { }) |
| |
| 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, |
| [], True, False) |
| |
| # Change mind, re-apply the change ('svn merge -r1:2'). |
| # This should merge cleanly into existing prop-mod, status shows nothing. |
| expected_output = wc.State(wc_dir, {'A/B/E/alpha' : Item(status=' C'), }) |
| expected_mergeinfo_output = wc.State(wc_dir, {}) |
| expected_elision_output = wc.State(wc_dir, {}) |
| expected_elision_output = wc.State(wc_dir, {}) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.add({'A/B/E/alpha.prej' |
| : Item("Trying to add new property 'foo'\n" |
| + "but the property has been locally deleted.\n" |
| + "Incoming property value:\nfoo_val\n")}) |
| |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 2) |
| expected_status.tweak('A/B/E/alpha', status=' C') |
| |
| expected_skip = wc.State('', { }) |
| |
| # Re-merge r1. We have to use --ignore-ancestry here. Otherwise |
| # the merge logic will claim we already have this change (because it |
| # was unable to record the previous undoing merge). |
| svntest.actions.run_and_verify_merge(wc_dir, '1', '2', |
| sbox.repo_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| '--ignore-ancestry', wc_dir) |
| |
| |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def cherry_pick_text_conflict(sbox): |
| "cherry-pick a dependent change, get conflict" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| A_path = sbox.ospath('A') |
| A_url = sbox.repo_url + '/A' |
| mu_path = os.path.join(A_path, 'mu') |
| branch_A_url = sbox.repo_url + '/copy-of-A' |
| branch_mu_path = sbox.ospath('copy-of-A/mu') |
| |
| # Create a branch of A. |
| svntest.actions.run_and_verify_svn(None, [], 'cp', |
| A_url, branch_A_url, |
| '-m', "Creating copy-of-A") |
| |
| # Update to get the branch. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'update', wc_dir) |
| |
| # Change mu's text on the branch, producing r3 through r6. |
| for rev in range(3, 7): |
| svntest.main.file_append(branch_mu_path, ("r%d\n" % rev) * 3) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', |
| 'Add lines to mu in r%d.' % rev, wc_dir) |
| |
| # Mark r5 as merged into trunk, to create disparate revision ranges |
| # which need to be merged. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[5]], |
| [' U ' + A_path + '\n']), |
| [], 'merge', '-c5', '--record-only', |
| branch_A_url, A_path) |
| |
| |
| # Try to merge r4:6 into trunk, without r3. It should fail. |
| expected_output = wc.State(A_path, { |
| 'mu' : Item(status='C '), |
| }) |
| expected_mergeinfo_output = wc.State(A_path, { |
| '' : Item(status=' G') |
| }) |
| expected_elision_output = wc.State(A_path, { |
| }) |
| expected_disk = wc.State('', { |
| 'mu' : Item("This is the file 'mu'.\n" |
| + make_conflict_marker_text('', "r3\n" * 3 + "r4\n" * 3, 3, 4, |
| old_text='r3\n' * 3)), |
| 'B' : Item(), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| }) |
| expected_status = wc.State(A_path, { |
| '' : Item(status=' M'), |
| 'mu' : Item(status='C '), |
| 'B' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=2) |
| expected_skip = wc.State('', { }) |
| expected_error = ".*conflicts were produced while merging r3:4.*" |
| svntest.actions.run_and_verify_merge(A_path, '3', '6', branch_A_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| expected_error, |
| extra_files= |
| ["mu\.working", |
| "mu\.merge-right\.r4", |
| "mu\.merge-left\.r3"]) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue 2135 |
| @Issue(2135) |
| def merge_file_replace(sbox): |
| "merge a replacement of a file" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # File scheduled for deletion |
| rho_path = sbox.ospath('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 and add a new file. |
| svntest.main.file_write(rho_path, "new rho\n") |
| 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 = 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 commit merged wc |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/D/G/rho': Item(verb='Replacing'), |
| }) |
| expected_status.tweak('A/D/G/rho', status=' ', copied=None, wc_rev='4') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue 2522 |
| # Same as merge_file_replace, but without update before merge. |
| @Issue(2522) |
| def merge_file_replace_to_mixed_rev_wc(sbox): |
| "merge a replacement of a file to mixed rev wc" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # File scheduled for deletion |
| rho_path = sbox.ospath('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) |
| |
| # Update working copy |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.remove('A/D/G/rho' ) |
| expected_output = svntest.wc.State(wc_dir, {}) |
| expected_status.tweak(wc_rev='2') |
| |
| svntest.actions.run_and_verify_update(wc_dir, |
| expected_output, |
| expected_disk, |
| expected_status) |
| |
| # Create and add a new file. |
| svntest.main.file_write(rho_path, "new rho\n") |
| 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'), |
| }) |
| |
| expected_disk.add({'A/D/G/rho' : Item(contents='new rho\n')} ) |
| expected_status.tweak(wc_rev='2') |
| expected_status.tweak('A/D/G/rho', status=' ', wc_rev='3') |
| |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # merge changes from r3:1... |
| # |
| # ...but first: |
| # |
| # Since "." is at revision 2, r3 is not part of "."'s implicit mergeinfo. |
| # Merge tracking permits only reverse merges from explicit or implicit |
| # mergeinfo, so only r2 would be reverse merged if we left the WC as is. |
| # Normally we'd simply update the whole working copy, but since that would |
| # defeat the purpose of this test (see the comment below), instead we'll |
| # update only "." using --depth empty. This preserves the intent of the |
| # original mixed-rev test for this issue, but allows the merge tracking |
| # logic to consider r3 as valid for reverse merging. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'up', '--depth', 'empty', wc_dir) |
| expected_status.tweak('', wc_rev=3) |
| 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 = 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, |
| [], |
| True, False, '--allow-mixed-revisions', |
| wc_dir) |
| |
| # When issue #2522 was filed, svn used to break the WC if we didn't |
| # update here. But nowadays, this no longer happens, so the separate |
| # update step which was done here originally has been removed. |
| |
| # Now commit merged wc |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/D/G/rho': Item(verb='Replacing'), |
| }) |
| expected_status.tweak('A/D/G/rho', status=' ', copied=None, wc_rev='4') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| #---------------------------------------------------------------------- |
| # use -x -w option for ignoring whitespace during merge |
| @SkipUnless(server_has_mergeinfo) |
| def merge_ignore_whitespace(sbox): |
| "ignore whitespace when merging" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # commit base version of iota |
| file_name = "iota" |
| file_path = os.path.join(wc_dir, file_name) |
| file_url = sbox.repo_url + '/iota' |
| |
| svntest.main.file_write(file_path, |
| "Aa\n" |
| "Bb\n" |
| "Cc\n") |
| expected_output = svntest.wc.State(wc_dir, { |
| 'iota' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, None) |
| |
| # change the file, mostly whitespace changes + an extra line |
| svntest.main.file_write(file_path, "A a\nBb \n Cc\nNew line in iota\n") |
| expected_output = wc.State(wc_dir, { file_name : Item(verb='Sending'), }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak(file_name, wc_rev=3) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Backdate iota to revision 2, so we can merge in the rev 3 changes. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'up', '-r', '2', file_path) |
| # Make some local whitespace changes, these should not conflict |
| # with the remote whitespace changes as both will be ignored. |
| svntest.main.file_write(file_path, " Aa\nB b\nC c\n") |
| |
| # Lines changed only by whitespace - both in local or remote - |
| # should be ignored |
| expected_output = wc.State(sbox.wc_dir, { file_name : Item(status='G ') }) |
| expected_mergeinfo_output = wc.State(sbox.wc_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(sbox.wc_dir, { |
| }) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.tweak(file_name, |
| contents=" Aa\n" |
| "B b\n" |
| "C c\n" |
| "New line in iota\n") |
| expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1) |
| expected_status.tweak('', status=' M', wc_rev=1) |
| expected_status.tweak(file_name, status='M ', wc_rev=2) |
| expected_skip = wc.State('', { }) |
| |
| svntest.actions.run_and_verify_merge(sbox.wc_dir, '2', '3', |
| sbox.repo_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], False, False, |
| '--allow-mixed-revisions', |
| '-x', '-w', wc_dir) |
| |
| #---------------------------------------------------------------------- |
| # use -x --ignore-eol-style option for ignoring eolstyle during merge |
| @SkipUnless(server_has_mergeinfo) |
| def merge_ignore_eolstyle(sbox): |
| "ignore eolstyle when merging" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # commit base version of iota |
| file_name = "iota" |
| file_path = os.path.join(wc_dir, file_name) |
| file_url = sbox.repo_url + '/iota' |
| |
| svntest.main.file_write(file_path, |
| "Aa\r\n" |
| "Bb\r\n" |
| "Cc\r\n", |
| "wb") |
| expected_output = svntest.wc.State(wc_dir, { |
| 'iota' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, None) |
| |
| # change the file, mostly eol changes + an extra line |
| svntest.main.file_write(file_path, |
| "Aa\r" |
| "Bb\n" |
| "Cc\r" |
| "New line in iota\n", |
| "wb") |
| expected_output = wc.State(wc_dir, { file_name : Item(verb='Sending'), }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak(file_name, wc_rev=3) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Backdate iota to revision 2, so we can merge in the rev 3 changes. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'up', '-r', '2', file_path) |
| # Make some local eol changes, these should not conflict |
| # with the remote eol changes as both will be ignored. |
| svntest.main.file_write(file_path, |
| "Aa\n" |
| "Bb\r" |
| "Cc\n", |
| "wb") |
| |
| # Lines changed only by eolstyle - both in local or remote - |
| # should be ignored |
| expected_output = wc.State(sbox.wc_dir, { file_name : Item(status='G ') }) |
| expected_mergeinfo_output = wc.State(sbox.wc_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(sbox.wc_dir, { |
| }) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.tweak(file_name, |
| contents="Aa\n" |
| "Bb\r" |
| "Cc\n" |
| "New line in iota\n") |
| expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1) |
| expected_status.tweak('', status=' M') |
| expected_status.tweak(file_name, status='M ', wc_rev=2) |
| expected_skip = wc.State('', { }) |
| |
| svntest.actions.run_and_verify_merge2(sbox.wc_dir, '2', '3', |
| sbox.repo_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], False, False, True, |
| '--allow-mixed-revisions', |
| '-x', '--ignore-eol-style', wc_dir) |
| |
| #---------------------------------------------------------------------- |
| # eol-style handling during merge with conflicts, scenario 1: |
| # when a merge creates a conflict on a file, make sure the file and files |
| # r<left>, r<right> and .mine are in the eol-style defined for that file. |
| # |
| # This test for 'svn update' can be found in update_tests.py as |
| # conflict_markers_matching_eol. |
| @SkipUnless(server_has_mergeinfo) |
| def merge_conflict_markers_matching_eol(sbox): |
| "conflict markers should match the file's eol style" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| filecount = 1 |
| |
| mu_path = sbox.ospath('A/mu') |
| |
| if os.name == 'nt': |
| native_nl = '\r\n' |
| else: |
| native_nl = '\n' |
| crlf = '\r\n' |
| |
| # Checkout a second working copy |
| wc_backup = sbox.add_wc_path('backup') |
| svntest.actions.run_and_verify_svn(None, [], 'checkout', |
| sbox.repo_url, wc_backup) |
| |
| # set starting revision |
| cur_rev = 1 |
| |
| expected_disk = svntest.main.greek_state.copy() |
| expected_status = svntest.actions.get_virginal_state(wc_dir, cur_rev) |
| expected_backup_status = svntest.actions.get_virginal_state(wc_backup, |
| cur_rev) |
| |
| path_backup = os.path.join(wc_backup, 'A', 'mu') |
| |
| # do the test for each eol-style |
| for eol, eolchar in zip(['CRLF', 'CR','native', 'LF'], |
| [crlf, '\015', native_nl, '\012']): |
| # rewrite file mu and set the eol-style property. |
| svntest.main.file_write(mu_path, "This is the file 'mu'."+ eolchar, 'wb') |
| svntest.main.run_svn(None, 'propset', 'svn:eol-style', eol, mu_path) |
| |
| expected_disk.add({ |
| 'A/mu' : Item("This is the file 'mu'." + eolchar) |
| }) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/mu' : Item(verb='Sending'), |
| }) |
| expected_status.tweak(wc_rev = cur_rev) |
| expected_status.add({ |
| 'A/mu' : Item(status=' ', wc_rev = cur_rev + 1), |
| }) |
| |
| # Commit the original change and note the 'base' revision number |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| cur_rev = cur_rev + 1 |
| base_rev = cur_rev |
| |
| svntest.main.run_svn(None, 'update', wc_backup) |
| |
| # Make a local mod to mu |
| svntest.main.file_append_binary(mu_path, |
| 'Original appended text for mu' + eolchar) |
| |
| # Commit the original change and note the 'theirs' revision number |
| svntest.main.run_svn(None, 'commit', '-m', 'test log', wc_dir) |
| cur_rev = cur_rev + 1 |
| theirs_rev = cur_rev |
| |
| # Make a local mod to mu, will conflict with the previous change |
| svntest.main.file_append_binary(path_backup, |
| 'Conflicting appended text for mu' |
| + eolchar) |
| |
| # Create expected output tree for an update of the wc_backup. |
| expected_backup_output = svntest.wc.State(wc_backup, { |
| 'A/mu' : Item(status='C '), |
| }) |
| |
| # Create expected disk tree for the update. |
| expected_backup_disk = expected_disk.copy() |
| |
| # verify content of resulting conflicted file |
| expected_backup_disk.add({ |
| 'A/mu' : Item(contents= "This is the file 'mu'." + eolchar + |
| "<<<<<<< .working" + eolchar + |
| "Conflicting appended text for mu" + eolchar + |
| "||||||| .merge-left.r" + str(cur_rev - 1) + eolchar + |
| "=======" + eolchar + |
| "Original appended text for mu" + eolchar + |
| ">>>>>>> .merge-right.r" + str(cur_rev) + eolchar), |
| }) |
| # verify content of base(left) file |
| expected_backup_disk.add({ |
| 'A/mu.merge-left.r' + str(base_rev) : |
| Item(contents= "This is the file 'mu'." + eolchar) |
| }) |
| # verify content of theirs(right) file |
| expected_backup_disk.add({ |
| 'A/mu.merge-right.r' + str(theirs_rev) : |
| Item(contents= "This is the file 'mu'." + eolchar + |
| "Original appended text for mu" + eolchar) |
| }) |
| # verify content of mine file |
| expected_backup_disk.add({ |
| 'A/mu.working' : Item(contents= "This is the file 'mu'." + |
| eolchar + |
| "Conflicting appended text for mu" + eolchar) |
| }) |
| |
| # Create expected status tree for the update. |
| expected_backup_status.add({ |
| 'A/mu' : Item(status=' ', wc_rev=cur_rev), |
| }) |
| expected_backup_status.tweak('A/mu', status='C ') |
| expected_backup_status.tweak(wc_rev = cur_rev - 1) |
| expected_backup_status.tweak('', status= ' M') |
| expected_mergeinfo_output = wc.State(wc_backup, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(wc_backup, { |
| }) |
| expected_backup_skip = wc.State('', { }) |
| |
| svntest.actions.run_and_verify_merge2(wc_backup, cur_rev - 1, cur_rev, |
| sbox.repo_url, None, |
| expected_backup_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_backup_disk, |
| expected_backup_status, |
| expected_backup_skip, |
| keep_eol_style=True) |
| |
| # cleanup for next run |
| svntest.main.run_svn(None, 'revert', '-R', wc_backup) |
| svntest.main.run_svn(None, 'update', wc_dir) |
| |
| #---------------------------------------------------------------------- |
| # eol-style handling during merge, scenario 2: |
| # if part of that merge is a propchange (add, change, delete) of |
| # svn:eol-style, make sure the correct eol-style is applied before |
| # calculating the merge (and conflicts if any) |
| # |
| # This test for 'svn update' can be found in update_tests.py as |
| # update_eolstyle_handling. |
| @SkipUnless(server_has_mergeinfo) |
| def merge_eolstyle_handling(sbox): |
| "handle eol-style propchange during merge" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| mu_path = sbox.ospath('A/mu') |
| |
| crlf = '\r\n' |
| |
| # Checkout a second working copy |
| wc_backup = sbox.add_wc_path('backup') |
| svntest.actions.run_and_verify_svn(None, [], 'checkout', |
| sbox.repo_url, wc_backup) |
| path_backup = os.path.join(wc_backup, 'A', 'mu') |
| |
| # Test 1: add the eol-style property and commit, change mu in the second |
| # working copy and merge the last revision; there should be no conflict! |
| svntest.main.run_svn(None, 'propset', 'svn:eol-style', "CRLF", mu_path) |
| svntest.main.run_svn(None, |
| 'commit', '-m', 'set eol-style property', wc_dir) |
| |
| svntest.main.file_append_binary(path_backup, 'Added new line of text.\012') |
| |
| expected_backup_disk = svntest.main.greek_state.copy() |
| expected_backup_disk.tweak( |
| 'A/mu', contents= "This is the file 'mu'." + crlf + |
| "Added new line of text." + crlf) |
| expected_backup_output = svntest.wc.State(wc_backup, { |
| 'A/mu' : Item(status='GU'), |
| }) |
| expected_mergeinfo_output = svntest.wc.State(wc_backup, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(wc_backup, { |
| }) |
| expected_backup_status = svntest.actions.get_virginal_state(wc_backup, 1) |
| expected_backup_status.tweak('', status=' M') |
| expected_backup_status.tweak('A/mu', status='MM') |
| |
| expected_backup_skip = wc.State('', { }) |
| |
| svntest.actions.run_and_verify_merge2(wc_backup, '1', '2', sbox.repo_url, |
| None, |
| expected_backup_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_backup_disk, |
| expected_backup_status, |
| expected_backup_skip, |
| keep_eol_style=True) |
| |
| # Test 2: now change the eol-style property to another value and commit, |
| # merge this revision in the still changed mu in the second working copy; |
| # there should be no conflict! |
| svntest.main.run_svn(None, 'propset', 'svn:eol-style', "CR", mu_path) |
| svntest.main.run_svn(None, |
| 'commit', '-m', 'set eol-style property', wc_dir) |
| |
| expected_backup_disk = svntest.main.greek_state.copy() |
| expected_backup_disk.add({ |
| 'A/mu' : Item(contents= "This is the file 'mu'.\015" + |
| "Added new line of text.\015") |
| }) |
| expected_backup_output = svntest.wc.State(wc_backup, { |
| 'A/mu' : Item(status='GU'), |
| }) |
| expected_mergeinfo_output = svntest.wc.State(wc_backup, { |
| '' : Item(status=' G'), |
| }) |
| expected_backup_status = svntest.actions.get_virginal_state(wc_backup, 1) |
| expected_backup_status.tweak('', status=' M') |
| expected_backup_status.tweak('A/mu', status='MM') |
| svntest.actions.run_and_verify_merge2(wc_backup, '2', '3', sbox.repo_url, |
| None, |
| expected_backup_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_backup_disk, |
| expected_backup_status, |
| expected_backup_skip, |
| keep_eol_style=True) |
| |
| # Test 3: now delete the eol-style property and commit, merge this revision |
| # in the still changed mu in the second working copy; there should be no |
| # conflict! |
| # EOL of mu should be unchanged (=CRLF). |
| svntest.main.run_svn(None, 'propdel', 'svn:eol-style', mu_path) |
| svntest.main.run_svn(None, |
| 'commit', '-m', 'del eol-style property', wc_dir) |
| |
| expected_backup_disk = svntest.main.greek_state.copy() |
| expected_backup_disk.add({ |
| 'A/mu' : Item(contents= "This is the file 'mu'.\015" + |
| "Added new line of text.\015") |
| }) |
| expected_backup_output = svntest.wc.State(wc_backup, { |
| 'A/mu' : Item(status=' G'), |
| }) |
| expected_backup_status = svntest.actions.get_virginal_state(wc_backup, 1) |
| expected_backup_status.tweak('', status=' M') |
| expected_backup_status.tweak('A/mu', status='M ') |
| svntest.actions.run_and_verify_merge2(wc_backup, '3', '4', sbox.repo_url, |
| None, |
| expected_backup_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_backup_disk, |
| expected_backup_status, |
| expected_backup_skip, |
| keep_eol_style=True) |
| |
| #---------------------------------------------------------------------- |
| def create_deep_trees(wc_dir): |
| """Create A/B/F/E by moving A/B/E to A/B/F/E. |
| Copy A/B/F/E to A/B/F/E1. |
| Copy A/B to A/copy-of-B, and return the expected status. |
| At the end of this function WC would be at r4""" |
| |
| A_path = os.path.join(wc_dir, 'A') |
| A_B_path = os.path.join(A_path, 'B') |
| A_B_E_path = os.path.join(A_B_path, 'E') |
| A_B_F_path = os.path.join(A_B_path, 'F') |
| A_B_F_E_path = os.path.join(A_B_F_path, 'E') |
| A_B_F_E1_path = os.path.join(A_B_F_path, 'E1') |
| |
| # Deepen the directory structure we're working with by moving E to |
| # underneath F and committing, creating revision 2. |
| svntest.main.run_svn(None, 'mv', A_B_E_path, A_B_F_path) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/B/E' : Item(verb='Deleting'), |
| 'A/B/F/E' : Item(verb='Adding') |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.remove('A/B/E', 'A/B/E/alpha', 'A/B/E/beta') |
| expected_status.add({ |
| 'A/B/F/E' : Item(status=' ', wc_rev=2), |
| 'A/B/F/E/alpha' : Item(status=' ', wc_rev=2), |
| 'A/B/F/E/beta' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| svntest.main.run_svn(None, 'cp', A_B_F_E_path, A_B_F_E1_path) |
| |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/B/F/E1' : Item(verb='Adding') |
| }) |
| expected_status.add({ |
| 'A/B/F/E1' : Item(status=' ', wc_rev=3), |
| 'A/B/F/E1/alpha' : Item(status=' ', wc_rev=3), |
| 'A/B/F/E1/beta' : Item(status=' ', wc_rev=3), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Bring the entire WC up to date with rev 3. |
| svntest.actions.run_and_verify_svn(None, [], 'update', wc_dir) |
| expected_status.tweak(wc_rev=3) |
| |
| # Copy B and commit, creating revision 4. |
| copy_of_B_path = os.path.join(A_path, 'copy-of-B') |
| svntest.main.run_svn(None, "cp", A_B_path, copy_of_B_path) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/copy-of-B' : Item(verb='Adding'), |
| }) |
| expected_status.add({ |
| 'A/copy-of-B' : Item(status=' ', wc_rev=4), |
| 'A/copy-of-B/F' : Item(status=' ', wc_rev=4), |
| 'A/copy-of-B/F/E' : Item(status=' ', wc_rev=4), |
| 'A/copy-of-B/F/E/alpha' : Item(status=' ', wc_rev=4), |
| 'A/copy-of-B/F/E/beta' : Item(status=' ', wc_rev=4), |
| 'A/copy-of-B/F/E1' : Item(status=' ', wc_rev=4), |
| 'A/copy-of-B/F/E1/alpha' : Item(status=' ', wc_rev=4), |
| 'A/copy-of-B/F/E1/beta' : Item(status=' ', wc_rev=4), |
| 'A/copy-of-B/lambda' : Item(status=' ', wc_rev=4), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.remove('A/B/E', 'A/B/E/alpha', 'A/B/E/beta') |
| expected_disk.add({ |
| 'A/B/F/E' : Item(), |
| 'A/B/F/E/alpha' : Item(contents="This is the file 'alpha'.\n"), |
| 'A/B/F/E/beta' : Item(contents="This is the file 'beta'.\n"), |
| 'A/B/F/E1' : Item(), |
| 'A/B/F/E1/alpha' : Item(contents="This is the file 'alpha'.\n"), |
| 'A/B/F/E1/beta' : Item(contents="This is the file 'beta'.\n"), |
| 'A/copy-of-B' : Item(), |
| 'A/copy-of-B/F' : Item(props={}), |
| 'A/copy-of-B/F/E' : Item(), |
| 'A/copy-of-B/F/E/alpha' : Item(contents="This is the file 'alpha'.\n"), |
| 'A/copy-of-B/F/E/beta' : Item(contents="This is the file 'beta'.\n"), |
| 'A/copy-of-B/F/E1' : Item(), |
| 'A/copy-of-B/F/E1/alpha' : Item(contents="This is the file 'alpha'.\n"), |
| 'A/copy-of-B/F/E1/beta' : Item(contents="This is the file 'beta'.\n"), |
| 'A/copy-of-B/lambda' : Item(contents="This is the file 'lambda'.\n"), |
| }) |
| svntest.actions.verify_disk(wc_dir, expected_disk, True) |
| |
| # Bring the entire WC up to date with rev 4. |
| svntest.actions.run_and_verify_svn(None, [], 'update', wc_dir) |
| |
| svntest.actions.verify_disk(wc_dir, expected_disk, True) |
| |
| expected_status.tweak(wc_rev=4) |
| expected_disk.tweak('A/copy-of-B/F/E', 'A/copy-of-B/F/E1', status=' M') |
| return expected_status |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def avoid_repeated_merge_using_inherited_merge_info(sbox): |
| "use inherited mergeinfo to avoid repeated merge" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| A_path = sbox.ospath('A') |
| A_B_path = os.path.join(A_path, 'B') |
| A_B_E_path = os.path.join(A_B_path, 'E') |
| A_B_F_path = os.path.join(A_B_path, 'F') |
| copy_of_B_path = os.path.join(A_path, 'copy-of-B') |
| |
| # Create a deeper directory structure. |
| expected_status = create_deep_trees(wc_dir) |
| |
| # Edit alpha and commit it, creating revision 5. |
| alpha_path = os.path.join(A_B_F_path, 'E', 'alpha') |
| new_content_for_alpha = 'new content to alpha\n' |
| svntest.main.file_write(alpha_path, new_content_for_alpha) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B/F/E/alpha' : Item(verb='Sending'), |
| }) |
| expected_status.tweak('A/B/F/E/alpha', wc_rev=5) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Bring the entire WC up to date with rev 5. |
| svntest.actions.run_and_verify_svn(None, [], 'update', wc_dir) |
| |
| # Merge changes from rev 5 of B (to alpha) into copy_of_B. |
| expected_output = wc.State(copy_of_B_path, { |
| 'F/E/alpha' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(copy_of_B_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(copy_of_B_path, { |
| }) |
| expected_status = wc.State(copy_of_B_path, { |
| '' : Item(status=' M', wc_rev=5), |
| 'F/E' : Item(status=' ', wc_rev=5), |
| 'F/E/alpha' : Item(status='M ', wc_rev=5), |
| 'F/E/beta' : Item(status=' ', wc_rev=5), |
| 'F/E1' : Item(status=' ', wc_rev=5), |
| 'F/E1/alpha' : Item(status=' ', wc_rev=5), |
| 'F/E1/beta' : Item(status=' ', wc_rev=5), |
| 'lambda' : Item(status=' ', wc_rev=5), |
| 'F' : Item(status=' ', wc_rev=5), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:5'}), |
| 'F/E' : Item(), |
| 'F/E/alpha' : Item(new_content_for_alpha), |
| 'F/E/beta' : Item("This is the file 'beta'.\n"), |
| 'F/E1' : Item(), |
| 'F/E1/alpha' : Item("This is the file 'alpha'.\n"), |
| 'F/E1/beta' : Item("This is the file 'beta'.\n"), |
| 'F' : Item(), |
| 'lambda' : Item("This is the file 'lambda'.\n") |
| }) |
| expected_skip = wc.State(copy_of_B_path, { }) |
| |
| svntest.actions.run_and_verify_merge(copy_of_B_path, '4', '5', |
| sbox.repo_url + '/A/B', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Commit the result of the merge, creating revision 6. |
| expected_output = svntest.wc.State(copy_of_B_path, { |
| '' : Item(verb='Sending'), |
| 'F/E/alpha' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(copy_of_B_path, expected_output, |
| None) |
| |
| # Update the WC to bring /A/copy_of_B/F from rev 4 to rev 6. |
| # Without this update, a subsequent merge will not find any merge |
| # info for /A/copy_of_B/F -- nor its parent dir in the repos -- at |
| # rev 4. Mergeinfo wasn't introduced until rev 6. |
| copy_of_B_F_E_path = os.path.join(copy_of_B_path, 'F', 'E') |
| svntest.actions.run_and_verify_svn(None, [], 'update', wc_dir) |
| |
| # Attempt to re-merge changes to alpha from rev 4. Use the merge |
| # info inherited from the grandparent (copy-of-B) of our merge |
| # target (/A/copy-of-B/F/E) to avoid a repeated merge. |
| expected_status = wc.State(copy_of_B_F_E_path, { |
| '' : Item(status=' ', wc_rev=6), |
| 'alpha' : Item(status=' ', wc_rev=6), |
| 'beta' : Item(status=' ', wc_rev=6), |
| }) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[5]], |
| [' U ' + copy_of_B_F_E_path + '\n', |
| ' G ' + copy_of_B_F_E_path + '\n'], |
| elides=True), |
| [], 'merge', '-r4:5', |
| sbox.repo_url + '/A/B/F/E', |
| copy_of_B_F_E_path) |
| svntest.actions.run_and_verify_status(copy_of_B_F_E_path, |
| expected_status) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(2821) |
| def avoid_repeated_merge_on_subtree_with_merge_info(sbox): |
| "use subtree's mergeinfo to avoid repeated merge" |
| # Create deep trees A/B/F/E and A/B/F/E1 and copy A/B to A/copy-of-B |
| # with the help of 'create_deep_trees' |
| # As /A/copy-of-B/F/E1 is not a child of /A/copy-of-B/F/E, |
| # set_path should not be called on /A/copy-of-B/F/E1 while |
| # doing a implicit subtree merge on /A/copy-of-B/F/E. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| A_path = sbox.ospath('A') |
| A_B_path = os.path.join(A_path, 'B') |
| A_B_E_path = os.path.join(A_B_path, 'E') |
| A_B_F_path = os.path.join(A_B_path, 'F') |
| A_B_F_E_path = os.path.join(A_B_F_path, 'E') |
| copy_of_B_path = os.path.join(A_path, 'copy-of-B') |
| copy_of_B_F_path = os.path.join(A_path, 'copy-of-B', 'F') |
| A_copy_of_B_F_E_alpha_path = os.path.join(A_path, 'copy-of-B', 'F', |
| 'E', 'alpha') |
| |
| # Create a deeper directory structure. |
| expected_status = create_deep_trees(wc_dir) |
| |
| # Edit alpha and commit it, creating revision 5. |
| alpha_path = os.path.join(A_B_F_E_path, 'alpha') |
| new_content_for_alpha1 = 'new content to alpha\n' |
| svntest.main.file_write(alpha_path, new_content_for_alpha1) |
| |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B/F/E/alpha' : Item(verb='Sending'), |
| }) |
| expected_status.tweak('A/B/F/E/alpha', wc_rev=5) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| for path_and_mergeinfo in (('E', '/A/B/F/E:5'), |
| ('E1', '/A/B/F/E:5')): |
| path_name = os.path.join(copy_of_B_path, 'F', path_and_mergeinfo[0]) |
| |
| # Merge r5 to path_name. |
| expected_output = wc.State(path_name, { |
| 'alpha' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(path_name, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(path_name, {}) |
| expected_status = wc.State(path_name, { |
| '' : Item(status=' M', wc_rev=4), |
| 'alpha' : Item(status='M ', wc_rev=4), |
| 'beta' : Item(status=' ', wc_rev=4), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : path_and_mergeinfo[1]}), |
| 'alpha' : Item(new_content_for_alpha1), |
| 'beta' : Item("This is the file 'beta'.\n"), |
| }) |
| expected_skip = wc.State(path_name, { }) |
| |
| svntest.actions.run_and_verify_merge(path_name, '4', '5', |
| sbox.repo_url + '/A/B/F/E', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Commit the result of the merge, creating new revision. |
| expected_output = svntest.wc.State(path_name, { |
| '' : Item(verb='Sending'), |
| 'alpha' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(path_name, |
| expected_output, None, [], wc_dir) |
| |
| # Edit A/B/F/E/alpha and commit it, creating revision 8. |
| new_content_for_alpha = 'new content to alpha\none more line\n' |
| svntest.main.file_write(alpha_path, new_content_for_alpha) |
| |
| expected_output = svntest.wc.State(A_B_F_E_path, { |
| 'alpha' : Item(verb='Sending'), |
| }) |
| expected_status = wc.State(A_B_F_E_path, { |
| '' : Item(status=' ', wc_rev=4), |
| 'alpha' : Item(status=' ', wc_rev=8), |
| 'beta' : Item(status=' ', wc_rev=4), |
| }) |
| svntest.actions.run_and_verify_commit(A_B_F_E_path, expected_output, |
| expected_status, [], wc_dir) |
| |
| # Update the WC to bring /A/copy_of_B to rev 8. |
| # Without this update expected_status tree would be cumbersome to |
| # understand. |
| svntest.actions.run_and_verify_svn(None, [], 'update', wc_dir) |
| |
| # Merge changes from rev 4:8 of A/B into A/copy_of_B. A/copy_of_B/F/E1 |
| # has explicit mergeinfo and exists at r4 in the merge source, so it |
| # should be treated as a subtree with intersecting mergeinfo and its |
| # mergeinfo updated. |
| expected_output = wc.State(copy_of_B_path, { |
| 'F/E/alpha' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(copy_of_B_path, { |
| '' : Item(status=' U'), |
| 'F/E' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(copy_of_B_path, { |
| 'F/E' : Item(status=' U') |
| }) |
| expected_status = wc.State(copy_of_B_path, { |
| # The subtree mergeinfo on F/E1 is not updated because |
| # this merge does not affect that subtree. |
| '' : Item(status=' M', wc_rev=8), |
| 'F/E' : Item(status=' M', wc_rev=8), |
| 'F/E/alpha' : Item(status='M ', wc_rev=8), |
| 'F/E/beta' : Item(status=' ', wc_rev=8), |
| 'F/E1' : Item(status=' ', wc_rev=8), |
| 'F/E1/alpha' : Item(status=' ', wc_rev=8), |
| 'F/E1/beta' : Item(status=' ', wc_rev=8), |
| 'lambda' : Item(status=' ', wc_rev=8), |
| 'F' : Item(status=' ', wc_rev=8) |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:5-8'}), |
| 'F/E' : Item(props={}), # elision! |
| 'F/E/alpha' : Item(new_content_for_alpha), |
| 'F/E/beta' : Item("This is the file 'beta'.\n"), |
| 'F' : Item(), |
| 'F/E1' : Item(props={SVN_PROP_MERGEINFO : |
| '/A/B/F/E:5'}), |
| 'F/E1/alpha' : Item(new_content_for_alpha1), |
| 'F/E1/beta' : Item("This is the file 'beta'.\n"), |
| 'lambda' : Item("This is the file 'lambda'.\n") |
| }) |
| expected_skip = wc.State(copy_of_B_path, { }) |
| svntest.actions.run_and_verify_merge(copy_of_B_path, '4', '8', |
| sbox.repo_url + '/A/B', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Test for part of Issue #2821, see |
| # https://issues.apache.org/jira/browse/SVN-2821#desc22 |
| # |
| # Revert all local changes. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| |
| # Make a text mod to A/copy-of-B/F/E/alpha |
| newer_content_for_alpha = "Conflicting content" |
| svntest.main.file_write(A_copy_of_B_F_E_alpha_path, |
| newer_content_for_alpha) |
| |
| # Re-merge r5 to A/copy-of-B/F, this *should* be a no-op as the mergeinfo |
| # on A/copy-of-B/F/E should prevent any attempt to merge r5 into that |
| # subtree. The merge will leave a few local changes as mergeinfo is set |
| # on A/copy-of-B/F, the mergeinfo on A/copy-of-B/F/E elides to it. The |
| # mergeinfo on A/copy-of-B/F/E1 remains unchanged as that subtree was |
| # untouched by the merge. |
| expected_output = wc.State(copy_of_B_F_path, {}) |
| expected_mergeinfo_output = wc.State(copy_of_B_F_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(copy_of_B_F_path, { |
| 'E' : Item(status=' U') |
| }) |
| expected_status = wc.State(copy_of_B_F_path, { |
| '' : Item(status=' M', wc_rev=8), |
| 'E' : Item(status=' M', wc_rev=8), |
| 'E/alpha' : Item(status='M ', wc_rev=8), |
| 'E/beta' : Item(status=' ', wc_rev=8), |
| 'E1' : Item(status=' ', wc_rev=8), |
| 'E1/alpha' : Item(status=' ', wc_rev=8), |
| 'E1/beta' : Item(status=' ', wc_rev=8), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B/F:5'}), |
| 'E' : Item(props={}), |
| 'E/alpha' : Item(newer_content_for_alpha), |
| 'E/beta' : Item("This is the file 'beta'.\n"), |
| 'E1' : Item(props={SVN_PROP_MERGEINFO : |
| '/A/B/F/E:5'}), |
| 'E1/alpha' : Item(new_content_for_alpha1), |
| 'E1/beta' : Item("This is the file 'beta'.\n") |
| }) |
| expected_skip = wc.State(copy_of_B_F_path, { }) |
| svntest.actions.run_and_verify_merge(copy_of_B_F_path, '4', '5', |
| sbox.repo_url + '/A/B/F', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| def tweak_src_then_merge_to_dest(sbox, src_path, dst_path, |
| canon_src_path, contents, cur_rev): |
| """Edit src and commit it. This results in new_rev. |
| Merge new_rev to dst_path. Return new_rev.""" |
| |
| wc_dir = sbox.wc_dir |
| new_rev = cur_rev + 1 |
| svntest.main.file_write(src_path, contents) |
| |
| expected_output = svntest.wc.State(src_path, { |
| '': Item(verb='Sending'), |
| }) |
| |
| expected_status = wc.State(src_path, |
| { '': Item(wc_rev=new_rev, status=' ')}) |
| |
| svntest.actions.run_and_verify_commit(src_path, expected_output, |
| expected_status) |
| |
| # Update the WC to new_rev so that it would be easier to expect everyone |
| # to be at new_rev. |
| svntest.actions.run_and_verify_svn(None, [], 'update', wc_dir) |
| |
| # Merge new_rev of src_path to dst_path. |
| |
| expected_status = wc.State(dst_path, |
| { '': Item(wc_rev=new_rev, status='MM')}) |
| |
| merge_url = sbox.repo_url + '/' + canon_src_path |
| if sys.platform == 'win32': |
| merge_url = merge_url.replace('\\', '/') |
| |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[new_rev]], |
| ['U ' + dst_path + '\n', |
| ' U ' + dst_path + '\n']), |
| [], 'merge', '-c', str(new_rev), merge_url, dst_path) |
| |
| svntest.actions.run_and_verify_status(dst_path, expected_status) |
| |
| return new_rev |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def obey_reporter_api_semantics_while_doing_subtree_merges(sbox): |
| "drive reporter api in depth first order" |
| |
| # Copy /A/D to /A/copy-of-D it results in rONE. |
| # Create children at different hierarchies having some merge-info |
| # to test the set_path calls on a reporter in a depth-first order. |
| # On all 'file' descendants of /A/copy-of-D/ we run merges. |
| # We create /A/D/umlaut directly over URL it results in rev rTWO. |
| # When we merge rONE+1:TWO of /A/D on /A/copy-of-D it should merge smoothly. |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| A_path = sbox.ospath('A') |
| A_D_path = sbox.ospath('A/D') |
| copy_of_A_D_path = sbox.ospath('A/copy-of-D') |
| |
| svntest.main.run_svn(None, "cp", A_D_path, copy_of_A_D_path) |
| |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/copy-of-D' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/copy-of-D' : Item(status=' ', wc_rev=2), |
| 'A/copy-of-D/G' : Item(status=' ', wc_rev=2), |
| 'A/copy-of-D/G/pi' : Item(status=' ', wc_rev=2), |
| 'A/copy-of-D/G/rho' : Item(status=' ', wc_rev=2), |
| 'A/copy-of-D/G/tau' : Item(status=' ', wc_rev=2), |
| 'A/copy-of-D/H' : Item(status=' ', wc_rev=2), |
| 'A/copy-of-D/H/chi' : Item(status=' ', wc_rev=2), |
| 'A/copy-of-D/H/omega' : Item(status=' ', wc_rev=2), |
| 'A/copy-of-D/H/psi' : Item(status=' ', wc_rev=2), |
| 'A/copy-of-D/gamma' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| |
| cur_rev = 2 |
| for path in (["A", "D", "G", "pi"], |
| ["A", "D", "G", "rho"], |
| ["A", "D", "G", "tau"], |
| ["A", "D", "H", "chi"], |
| ["A", "D", "H", "omega"], |
| ["A", "D", "H", "psi"], |
| ["A", "D", "gamma"]): |
| path_name = os.path.join(wc_dir, *path) |
| canon_path_name = os.path.join(*path) |
| path[1] = "copy-of-D" |
| copy_of_path_name = os.path.join(wc_dir, *path) |
| var_name = 'new_content_for_' + path[len(path) - 1] |
| file_contents = "new content to " + path[len(path) - 1] + "\n" |
| globals()[var_name] = file_contents |
| cur_rev = tweak_src_then_merge_to_dest(sbox, path_name, |
| copy_of_path_name, canon_path_name, |
| file_contents, cur_rev) |
| |
| copy_of_A_D_wc_rev = cur_rev |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision ' + str(cur_rev+1) + |
| '.\n'], |
| [], |
| 'mkdir', sbox.repo_url + '/A/D/umlaut', |
| '-m', "log msg") |
| rev_to_merge_to_copy_of_D = cur_rev + 1 |
| |
| # All the file descendants of /A/copy-of-D/ have already been merged |
| # so the only notification we expect is for the added 'umlaut'. |
| expected_output = wc.State(copy_of_A_D_path, { |
| 'umlaut' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(copy_of_A_D_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(copy_of_A_D_path, { |
| }) |
| # No subtree with explicit mergeinfo is affected by this merge, so they |
| # all remain unchanged from before the merge. The only mergeinfo updated |
| # is that on the target 'A/copy-of-D. |
| expected_status = wc.State(copy_of_A_D_path, { |
| '' : Item(status=' M', wc_rev=copy_of_A_D_wc_rev), |
| 'G' : Item(status=' ', wc_rev=copy_of_A_D_wc_rev), |
| 'G/pi' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), |
| 'G/rho' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), |
| 'G/tau' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), |
| 'H' : Item(status=' ', wc_rev=copy_of_A_D_wc_rev), |
| 'H/chi' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), |
| 'H/omega' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), |
| 'H/psi' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), |
| 'gamma' : Item(status='MM', wc_rev=copy_of_A_D_wc_rev), |
| 'umlaut' : Item(status='A ', copied='+', wc_rev='-'), |
| }) |
| |
| merged_rangelist = "3-%d" % rev_to_merge_to_copy_of_D |
| |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D:' + merged_rangelist}), |
| 'G' : Item(), |
| 'G/pi' : Item(new_content_for_pi, |
| props={SVN_PROP_MERGEINFO : '/A/D/G/pi:3'}), |
| 'G/rho' : Item(new_content_for_rho, |
| props={SVN_PROP_MERGEINFO : '/A/D/G/rho:4'}), |
| 'G/tau' : Item(new_content_for_tau, |
| props={SVN_PROP_MERGEINFO : '/A/D/G/tau:5'}), |
| 'H' : Item(), |
| 'H/chi' : Item(new_content_for_chi, |
| props={SVN_PROP_MERGEINFO : '/A/D/H/chi:6'}), |
| 'H/omega' : Item(new_content_for_omega, |
| props={SVN_PROP_MERGEINFO : '/A/D/H/omega:7'}), |
| 'H/psi' : Item(new_content_for_psi, |
| props={SVN_PROP_MERGEINFO : '/A/D/H/psi:8'}), |
| 'gamma' : Item(new_content_for_gamma, |
| props={SVN_PROP_MERGEINFO : '/A/D/gamma:9'}), |
| 'umlaut' : Item(), |
| }) |
| expected_skip = wc.State(copy_of_A_D_path, { }) |
| svntest.actions.run_and_verify_merge(copy_of_A_D_path, |
| 2, |
| str(rev_to_merge_to_copy_of_D), |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issues(2733,2734) |
| def mergeinfo_inheritance(sbox): |
| "target inherits mergeinfo from nearest ancestor" |
| |
| # Test for Issues #2733 and #2734. |
| # |
| # When the target of a merge has no explicit mergeinfo and the merge |
| # would result in mergeinfo being added to the target which... |
| # |
| # ...is a subset of the *local* mergeinfo on one of the target's |
| # ancestors (it's nearest ancestor takes precedence), then the merge is |
| # not repeated and no mergeinfo should be set on the target (Issue #2734). |
| # |
| # OR |
| # |
| # ...is not a subset it's nearest ancestor, the target should inherit the |
| # non-inersecting mergeinfo (local or committed, the former takes |
| # precedence) from it's nearest ancestor (Issue #2733). |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| B_COPY_path = sbox.ospath('A_COPY/B') |
| beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') |
| E_COPY_path = sbox.ospath('A_COPY/B/E') |
| omega_COPY_path = sbox.ospath('A_COPY/D/H/omega') |
| D_COPY_path = sbox.ospath('A_COPY/D') |
| G_COPY_path = sbox.ospath('A_COPY/D/G') |
| |
| # Now start merging... |
| |
| # Merge r4 into A_COPY/D/ |
| expected_output = wc.State(D_COPY_path, { |
| 'G/rho' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(D_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(D_COPY_path, { |
| }) |
| expected_status = wc.State(D_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'G' : Item(status=' ', wc_rev=2), |
| 'G/pi' : Item(status=' ', wc_rev=2), |
| 'G/rho' : Item(status='M ', wc_rev=2), |
| 'G/tau' : Item(status=' ', wc_rev=2), |
| 'H' : Item(status=' ', wc_rev=2), |
| 'H/chi' : Item(status=' ', wc_rev=2), |
| 'H/psi' : Item(status=' ', wc_rev=2), |
| 'H/omega' : Item(status=' ', wc_rev=2), |
| 'gamma' : Item(status=' ', wc_rev=2), |
| }) |
| # We test issue #2733 here (with a directory as the merge target). |
| # r1 should be inherited from 'A_COPY'. |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D:4'}), |
| 'G' : Item(), |
| 'G/pi' : Item("This is the file 'pi'.\n"), |
| 'G/rho' : Item("New content"), |
| 'G/tau' : Item("This is the file 'tau'.\n"), |
| 'H' : Item(), |
| 'H/chi' : Item("This is the file 'chi'.\n"), |
| 'H/psi' : Item("This is the file 'psi'.\n"), |
| 'H/omega' : Item("This is the file 'omega'.\n"), |
| 'gamma' : Item("This is the file 'gamma'.\n") |
| }) |
| expected_skip = wc.State(D_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(D_COPY_path, '3', '4', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Merge r4 again, this time into A_COPY/D/G. An ancestor directory |
| # (A_COPY/D) exists with identical local mergeinfo, so the merge |
| # should not be repeated. We test issue #2734 here with (with a |
| # directory as the merge target). |
| expected_output = wc.State(G_COPY_path, { }) |
| # A_COPY/D/G gets mergeinfo set, but it immediately elides to A_COPY/D. |
| expected_mergeinfo_output = wc.State(G_COPY_path, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(G_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_status = wc.State(G_COPY_path, { |
| '' : Item(status=' ', wc_rev=2), |
| 'pi' : Item(status=' ', wc_rev=2), |
| 'rho' : Item(status='M ', wc_rev=2), |
| 'tau' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| 'pi' : Item("This is the file 'pi'.\n"), |
| 'rho' : Item("New content"), |
| 'tau' : Item("This is the file 'tau'.\n"), |
| }) |
| expected_skip = wc.State(G_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(G_COPY_path, '3', '4', |
| sbox.repo_url + '/A/D/G', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Merge r5 into A_COPY/B. Again, r1 should be inherited from |
| # A_COPY (Issue #2733) |
| expected_output = wc.State(B_COPY_path, { |
| 'E/beta' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(B_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(B_COPY_path, { |
| }) |
| expected_status = wc.State(B_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'E' : Item(status=' ', wc_rev=2), |
| 'E/alpha' : Item(status=' ', wc_rev=2), |
| 'E/beta' : Item(status='M ', wc_rev=2), |
| 'lambda' : Item(status=' ', wc_rev=2), |
| 'F' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:5'}), |
| 'E' : Item(), |
| 'E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'E/beta' : Item("New content"), |
| 'F' : Item(), |
| 'lambda' : Item("This is the file 'lambda'.\n") |
| }) |
| expected_skip = wc.State(B_COPY_path, { }) |
| |
| svntest.actions.run_and_verify_merge(B_COPY_path, '4', '5', |
| sbox.repo_url + '/A/B', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Merge r5 again, this time into A_COPY/B/E/beta. An ancestor |
| # directory (A_COPY/B) exists with identical local mergeinfo, so |
| # the merge should not be repeated (Issue #2734 with a file as the |
| # merge target). |
| expected_skip = wc.State(beta_COPY_path, { }) |
| |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn([], [], 'merge', '-c5', |
| sbox.repo_url + '/A/B/E/beta', |
| beta_COPY_path) |
| |
| # The merge wasn't repeated so beta shouldn't have any mergeinfo. |
| # We are implicitly testing that without looking at the prop value |
| # itself, just beta's prop modification status. |
| expected_status = wc.State(beta_COPY_path, { |
| '' : Item(status='M ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_status(beta_COPY_path, expected_status) |
| |
| # Merge r3 into A_COPY. A_COPY's has two subtrees with mergeinfo, |
| # A_COPY/B/E/beta and A_COPY/D. Only the latter is effected by this |
| # merge so only its mergeinfo is updated to include r3. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/H/psi' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'B' : Item(status=' M', wc_rev=2), |
| 'mu' : Item(status=' ', wc_rev=2), |
| 'B/E' : Item(status=' ', wc_rev=2), |
| 'B/E/alpha' : Item(status=' ', wc_rev=2), |
| 'B/E/beta' : Item(status='M ', wc_rev=2), |
| 'B/lambda' : Item(status=' ', wc_rev=2), |
| 'B/F' : Item(status=' ', wc_rev=2), |
| 'C' : Item(status=' ', wc_rev=2), |
| 'D' : Item(status=' M', wc_rev=2), |
| 'D/G' : Item(status=' ', wc_rev=2), |
| 'D/G/pi' : Item(status=' ', wc_rev=2), |
| 'D/G/rho' : Item(status='M ', wc_rev=2), |
| 'D/G/tau' : Item(status=' ', wc_rev=2), |
| 'D/gamma' : Item(status=' ', wc_rev=2), |
| 'D/H' : Item(status=' ', wc_rev=2), |
| 'D/H/chi' : Item(status=' ', wc_rev=2), |
| 'D/H/psi' : Item(status='M ', wc_rev=2), |
| 'D/H/omega' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3'}), |
| 'B' : Item(props={SVN_PROP_MERGEINFO : '/A/B:5'}), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(props={SVN_PROP_MERGEINFO : '/A/D:3-4'}), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Merge r6 into A_COPY/D/H/omega, it should inherit it's nearest |
| # ancestor's (A_COPY/D) mergeinfo (Issue #2733 with a file as the |
| # merge target). |
| expected_skip = wc.State(omega_COPY_path, { }) |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[6]], |
| ['U ' + omega_COPY_path + '\n', |
| ' G ' + omega_COPY_path + '\n']), |
| [], 'merge', '-c6', |
| sbox.repo_url + '/A/D/H/omega', |
| omega_COPY_path) |
| |
| # Check that mergeinfo was properly set on A_COPY/D/H/omega |
| svntest.actions.run_and_verify_svn(["/A/D/H/omega:3-4,6\n"], |
| [], |
| 'propget', SVN_PROP_MERGEINFO, |
| omega_COPY_path) |
| |
| # Given a merge target *without* any of the following: |
| # |
| # 1) Explicit mergeinfo set on itself in the WC |
| # 2) Any WC ancestor to inherit mergeinfo from |
| # 3) Any mergeinfo for the target in the repository |
| # |
| # Check that the target still inherits mergeinfo from it's nearest |
| # repository ancestor. |
| # |
| # Commit all the merges thus far |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/B' : Item(verb='Sending'), |
| 'A_COPY/B/E/beta' : Item(verb='Sending'), |
| 'A_COPY/D' : Item(verb='Sending'), |
| 'A_COPY/D/G/rho' : Item(verb='Sending'), |
| 'A_COPY/D/H/omega' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending'), |
| }) |
| wc_status.tweak('A_COPY', 'A_COPY/B', 'A_COPY/B/E/beta', 'A_COPY/D', |
| 'A_COPY/D/G/rho', 'A_COPY/D/H/omega', 'A_COPY/D/H/psi', |
| wc_rev=7) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| wc_status) |
| |
| # In single-db mode you can't create a disconnected working copy by just |
| # copying a subdir |
| |
| ## Copy the subtree A_COPY/B/E from the working copy, making the |
| ## disconnected WC E_only. |
| #other_wc = sbox.add_wc_path('E_only') |
| #svntest.actions.duplicate_dir(E_COPY_path, other_wc) |
| # |
| ## Update the disconnected WC it so it will get the most recent mergeinfo |
| ## from the repos when merging. |
| #svntest.actions.run_and_verify_svn(exp_noop_up_out(7), [], 'up', |
| # other_wc) |
| # |
| ## Merge r5:4 into the root of the disconnected WC. |
| ## E_only has no explicit mergeinfo and since it's the root of the WC |
| ## cannot inherit any mergeinfo from a working copy ancestor path. Nor |
| ## does it have any mergeinfo explicitly set on it in the repository. |
| ## An ancestor path on the repository side, A_COPY/B does have the merge |
| ## info '/A/B:5' however and E_only should inherit this, resulting in |
| ## empty mergeinfo after the removal of r5 (A_COPY has mergeinfo of |
| ## '/A:3' so this empty mergeinfo is needed to override that. |
| #expected_output = wc.State(other_wc, |
| # {'beta' : Item(status='U ')}) |
| #expected_mergeinfo_output = wc.State(other_wc, { |
| # '' : Item(status=' G') |
| # }) |
| #expected_elision_output = wc.State(other_wc, { |
| # }) |
| #expected_status = wc.State(other_wc, { |
| # '' : Item(status=' M', wc_rev=7), |
| # 'alpha' : Item(status=' ', wc_rev=7), |
| # 'beta' : Item(status='M ', wc_rev=7), |
| # }) |
| #expected_disk = wc.State('', { |
| # '' : Item(props={SVN_PROP_MERGEINFO : ''}), |
| # 'alpha' : Item("This is the file 'alpha'.\n"), |
| # 'beta' : Item("This is the file 'beta'.\n"), |
| # }) |
| #expected_skip = wc.State(other_wc, { }) |
| # |
| #svntest.actions.run_and_verify_merge(other_wc, '5', '4', |
| # sbox.repo_url + '/A/B/E', None, |
| # expected_output, |
| # expected_mergeinfo_output, |
| # expected_elision_output, |
| # expected_disk, |
| # expected_status, |
| # expected_skip, |
| # check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def mergeinfo_elision(sbox): |
| "mergeinfo elides to ancestor with identical info" |
| |
| # When a merge would result in mergeinfo on a target which is identical |
| # to mergeinfo (local or committed) on one of the node's ancestors (the |
| # nearest ancestor takes precedence), then the mergeinfo elides from the |
| # target to the nearest ancestor (e.g. no mergeinfo is set on the target |
| # or committed mergeinfo is removed). |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') |
| G_COPY_path = sbox.ospath('A_COPY/D/G') |
| |
| # Now start merging... |
| |
| # Merge r5 into A_COPY/B/E/beta. |
| expected_skip = wc.State(beta_COPY_path, { }) |
| |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[5]], |
| ['U ' + beta_COPY_path + '\n', |
| ' U ' + beta_COPY_path + '\n']), |
| [], 'merge', '-c5', |
| sbox.repo_url + '/A/B/E/beta', |
| beta_COPY_path) |
| |
| # Check beta's status and props. |
| expected_status = wc.State(beta_COPY_path, { |
| '' : Item(status='MM', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_status(beta_COPY_path, expected_status) |
| |
| svntest.actions.run_and_verify_svn(["/A/B/E/beta:5\n"], [], |
| 'propget', SVN_PROP_MERGEINFO, |
| beta_COPY_path) |
| |
| # Commit the merge |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY/B/E/beta' : Item(verb='Sending'), |
| }) |
| wc_status.tweak('A_COPY/B/E/beta', wc_rev=7) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| wc_status) |
| |
| # Update A_COPY to get all paths to the same working revision. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(7), [], |
| 'up', wc_dir) |
| wc_status.tweak(wc_rev=7) |
| |
| # Merge r4 into A_COPY/D/G. |
| expected_output = wc.State(G_COPY_path, { |
| 'rho' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(G_COPY_path, { |
| '' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(G_COPY_path, { |
| }) |
| expected_status = wc.State(G_COPY_path, { |
| '' : Item(status=' M', wc_rev=7), |
| 'pi' : Item(status=' ', wc_rev=7), |
| 'rho' : Item(status='M ', wc_rev=7), |
| 'tau' : Item(status=' ', wc_rev=7), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/G:4'}), |
| 'pi' : Item("This is the file 'pi'.\n"), |
| 'rho' : Item("New content"), |
| 'tau' : Item("This is the file 'tau'.\n"), |
| }) |
| expected_skip = wc.State(G_COPY_path, { }) |
| |
| svntest.actions.run_and_verify_merge(G_COPY_path, '3', '4', |
| sbox.repo_url + '/A/D/G', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Merge r3:6 into A_COPY. The merge doesn't touch either of A_COPY's |
| # subtrees with explicit mergeinfo, so those are left alone. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/H/omega' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=7), |
| 'B' : Item(status=' ', wc_rev=7), |
| 'mu' : Item(status=' ', wc_rev=7), |
| 'B/E' : Item(status=' ', wc_rev=7), |
| 'B/E/alpha' : Item(status=' ', wc_rev=7), |
| 'B/E/beta' : Item(status=' ', wc_rev=7), |
| 'B/lambda' : Item(status=' ', wc_rev=7), |
| 'B/F' : Item(status=' ', wc_rev=7), |
| 'C' : Item(status=' ', wc_rev=7), |
| 'D' : Item(status=' ', wc_rev=7), |
| 'D/G' : Item(status=' M', wc_rev=7), |
| 'D/G/pi' : Item(status=' ', wc_rev=7), |
| 'D/G/rho' : Item(status='M ', wc_rev=7), |
| 'D/G/tau' : Item(status=' ', wc_rev=7), |
| 'D/gamma' : Item(status=' ', wc_rev=7), |
| 'D/H' : Item(status=' ', wc_rev=7), |
| 'D/H/chi' : Item(status=' ', wc_rev=7), |
| 'D/H/psi' : Item(status=' ', wc_rev=7), |
| 'D/H/omega' : Item(status='M ', wc_rev=7), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:4-6'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/B/E/beta:5'}), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(props={SVN_PROP_MERGEINFO : '/A/D/G:4'}), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '3', '6', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| # New repeat the above merge but with the --record-only option. |
| # This would result in identical mergeinfo |
| # (r4-6) on A_COPY and two of its descendants, A_COPY/D/G and |
| # A_COPY/B/E/beta, so the mergeinfo on the latter two should elide |
| # to A_COPY. In the case of A_COPY/D/G this means its wholly uncommitted |
| # mergeinfo is removed leaving no prop mods. In the case of |
| # A_COPY/B/E/beta its committed mergeinfo prop is removed leaving a prop |
| # change. |
| |
| # to A_COPY. |
| expected_output = wc.State(A_COPY_path, {}) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' G'), |
| 'D/G' : Item(status=' G'), |
| 'B/E/beta' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| 'B/E/beta' : Item(status=' U'), |
| 'D/G' : Item(status=' U'), |
| }) |
| expected_status.tweak('B/E/beta', status=' M') |
| expected_status.tweak('D/G', status=' ') |
| expected_disk.tweak('B/E/beta', 'D/G', props={}) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '3', '6', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| '--record-only', |
| A_COPY_path) |
| |
| # Reverse merge r5 out of A_COPY/B/E/beta. The mergeinfo on |
| # A_COPY/B/E/beta which previously elided will now return, |
| # minus r5 of course. |
| expected_skip = wc.State(beta_COPY_path, { }) |
| |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[-5]], |
| ['U ' + beta_COPY_path + '\n', |
| ' G ' + beta_COPY_path + '\n']), |
| [], 'merge', '-c-5', |
| sbox.repo_url + '/A/B/E/beta', |
| beta_COPY_path) |
| |
| # Check beta's status and props. |
| expected_status = wc.State(beta_COPY_path, { |
| '' : Item(status='MM', wc_rev=7), |
| }) |
| svntest.actions.run_and_verify_status(beta_COPY_path, expected_status) |
| |
| svntest.actions.run_and_verify_svn(["/A/B/E/beta:4,6\n"], [], |
| 'propget', SVN_PROP_MERGEINFO, |
| beta_COPY_path) |
| |
| # Merge r5 back into A_COPY/B/E/beta. Now the mergeinfo on the merge |
| # target (A_COPY/B/E/beta) is identical to it's nearest ancestor with |
| # mergeinfo (A_COPY) and so the former should elide. |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[5]], |
| ['G ' + beta_COPY_path + '\n', |
| ' G ' + beta_COPY_path + '\n', # Update mergeinfo |
| ' U ' + beta_COPY_path + '\n',], # Elide mereginfo, |
| elides=True), |
| [], 'merge', '-c5', |
| sbox.repo_url + '/A/B/E/beta', |
| beta_COPY_path) |
| |
| # Check beta's status and props. |
| expected_status = wc.State(beta_COPY_path, { |
| '' : Item(status=' M', wc_rev=7), |
| }) |
| svntest.actions.run_and_verify_status(beta_COPY_path, expected_status) |
| |
| # Once again A_COPY/B/E/beta has no mergeinfo. |
| svntest.actions.run_and_verify_svn([], '.*W200017: Property.*not found', |
| 'propget', SVN_PROP_MERGEINFO, |
| beta_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def mergeinfo_inheritance_and_discontinuous_ranges(sbox): |
| "discontinuous merges produce correct mergeinfo" |
| |
| # When a merge target has no explicit mergeinfo and is subject |
| # to multiple merges, the resulting mergeinfo on the target |
| # should reflect the combination of the inherited mergeinfo |
| # with each merge performed. |
| # |
| # Also tests implied merge source and target when only a revision |
| # range is specified. |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| A_url = sbox.repo_url + '/A' |
| A_COPY_path = sbox.ospath('A_COPY') |
| D_COPY_path = sbox.ospath('A_COPY/D') |
| A_COPY_rho_path = sbox.ospath('A_COPY/D/G/rho') |
| |
| expected_disk, expected_status = set_up_branch(sbox) |
| |
| # Merge r4 into A_COPY |
| saved_cwd = os.getcwd() |
| |
| os.chdir(A_COPY_path) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[4]], |
| ['U ' + os.path.join("D", "G", "rho") + '\n', |
| ' U .\n']), |
| [], 'merge', '-c4', A_url) |
| os.chdir(saved_cwd) |
| |
| # Check the results of the merge. |
| expected_status.tweak("A_COPY", status=' M') |
| expected_status.tweak("A_COPY/D/G/rho", status='M ') |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| svntest.actions.run_and_verify_svn(["/A:4\n"], [], |
| 'propget', SVN_PROP_MERGEINFO, |
| A_COPY_path) |
| |
| # Merge r2:6 into A_COPY/D |
| # |
| # A_COPY/D should inherit the mergeinfo '/A:4' from A_COPY |
| # combine it with the discontinuous merges performed directly on |
| # it (A/D/ 2:3 and A/D 4:6) resulting in '/A/D:3-6'. |
| expected_output = wc.State(D_COPY_path, { |
| 'H/psi' : Item(status='U '), |
| 'H/omega' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(D_COPY_path, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(D_COPY_path, { |
| }) |
| expected_status = wc.State(D_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'G' : Item(status=' ', wc_rev=2), |
| 'G/pi' : Item(status=' ', wc_rev=2), |
| 'G/rho' : Item(status='M ', wc_rev=2), |
| 'G/tau' : Item(status=' ', wc_rev=2), |
| 'H' : Item(status=' ', wc_rev=2), |
| 'H/chi' : Item(status=' ', wc_rev=2), |
| 'H/psi' : Item(status='M ', wc_rev=2), |
| 'H/omega' : Item(status='M ', wc_rev=2), |
| 'gamma' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D:3-6'}), |
| 'G' : Item(), |
| 'G/pi' : Item("This is the file 'pi'.\n"), |
| 'G/rho' : Item("New content"), |
| 'G/tau' : Item("This is the file 'tau'.\n"), |
| 'H' : Item(), |
| 'H/chi' : Item("This is the file 'chi'.\n"), |
| 'H/psi' : Item("New content"), |
| 'H/omega' : Item("New content"), |
| 'gamma' : Item("This is the file 'gamma'.\n") |
| }) |
| expected_skip = wc.State(D_COPY_path, { }) |
| |
| svntest.actions.run_and_verify_merge(D_COPY_path, '2', '6', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Wipe the memory of a portion of the previous merge... |
| ### It'd be nice to use 'merge --record-only' here, but we can't (yet) |
| ### wipe all ranges for a file due to the bug pointed out in r864719. |
| mu_copy_path = os.path.join(A_COPY_path, 'mu') |
| svntest.actions.run_and_verify_svn(["property '" + SVN_PROP_MERGEINFO |
| + "' set on '" + |
| mu_copy_path + "'\n"], [], 'propset', |
| SVN_PROP_MERGEINFO, '', mu_copy_path) |
| # ...and confirm that we can commit the wiped mergeinfo... |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY/mu' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| None, |
| [], |
| mu_copy_path) |
| # ...and that the presence of the property is retained, even when |
| # the value has been wiped. |
| svntest.actions.run_and_verify_svn(['\n'], [], 'propget', |
| SVN_PROP_MERGEINFO, mu_copy_path) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(2754) |
| def merge_to_target_with_copied_children(sbox): |
| "merge works when target has copied children" |
| |
| # Test for Issue #2754 Can't merge to target with copied/moved children |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| expected_disk, expected_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| D_COPY_path = sbox.ospath('A_COPY/D') |
| G_COPY_path = sbox.ospath('A_COPY/D/G') |
| rho_COPY_COPY_path = sbox.ospath('A_COPY/D/G/rho_copy') |
| |
| # URL to URL copy A_COPY/D/G/rho to A_COPY/D/G/rho_copy |
| svntest.actions.run_and_verify_svn(None, [], 'copy', |
| sbox.repo_url + '/A_COPY/D/G/rho', |
| sbox.repo_url + '/A_COPY/D/G/rho_copy', |
| '-m', 'copy') |
| |
| # Update WC. |
| expected_output = wc.State(wc_dir, |
| {'A_COPY/D/G/rho_copy' : Item(status='A ')}) |
| expected_disk.add({ |
| 'A_COPY/D/G/rho_copy' : Item("This is the file 'rho'.\n", props={}) |
| }) |
| expected_status.tweak(wc_rev=7) |
| expected_status.add({'A_COPY/D/G/rho_copy' : Item(status=' ', wc_rev=7)}) |
| svntest.actions.run_and_verify_update(wc_dir, |
| expected_output, |
| expected_disk, |
| expected_status, |
| check_props=True) |
| |
| # Merge r4 into A_COPY/D/G/rho_copy. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[4]], |
| ['U ' + rho_COPY_COPY_path + '\n', |
| ' U ' + rho_COPY_COPY_path + '\n']), |
| [], 'merge', '-c4', |
| sbox.repo_url + '/A/D/G/rho', |
| rho_COPY_COPY_path) |
| |
| # Merge r3:5 into A_COPY/D/G. |
| expected_output = wc.State(G_COPY_path, { |
| 'rho' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(G_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(G_COPY_path, { |
| }) |
| expected_status = wc.State(G_COPY_path, { |
| '' : Item(status=' M', wc_rev=7), |
| 'pi' : Item(status=' ', wc_rev=7), |
| 'rho' : Item(status='M ', wc_rev=7), |
| 'rho_copy' : Item(status='MM', wc_rev=7), |
| 'tau' : Item(status=' ', wc_rev=7), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/G:4-5'}), |
| 'pi' : Item("This is the file 'pi'.\n"), |
| 'rho' : Item("New content"), |
| 'rho_copy' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/D/G/rho:4'}), |
| 'tau' : Item("This is the file 'tau'.\n"), |
| }) |
| expected_skip = wc.State(G_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(G_COPY_path, '3', '5', |
| sbox.repo_url + '/A/D/G', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3188) |
| def merge_to_switched_path(sbox): |
| "merge to switched path does not inherit or elide" |
| |
| # When the target of a merge is a switched path we don't inherit WC |
| # mergeinfo from above the target or attempt to elide the mergeinfo |
| # set on the target as a result of the merge. |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| A_COPY_D_path = sbox.ospath('A_COPY/D') |
| G_COPY_path = sbox.ospath('A/D/G_COPY') |
| A_COPY_D_G_path = sbox.ospath('A_COPY/D/G') |
| A_COPY_D_G_rho_path = sbox.ospath('A_COPY/D/G/rho') |
| |
| expected = svntest.verify.UnorderedOutput( |
| ["A " + G_COPY_path + "\n", |
| "A " + os.path.join(G_COPY_path, "pi") + "\n", |
| "A " + os.path.join(G_COPY_path, "rho") + "\n", |
| "A " + os.path.join(G_COPY_path, "tau") + "\n", |
| ]) |
| |
| # r7 - Copy A/D/G to A/D/G_COPY and commit. |
| svntest.actions.run_and_verify_svn(expected, [], 'copy', |
| sbox.repo_url + "/A/D/G", |
| G_COPY_path) |
| |
| expected_output = wc.State(wc_dir, {'A/D/G_COPY' : Item(verb='Adding')}) |
| wc_status.add({ |
| "A/D/G_COPY" : Item(status=' ', wc_rev=7), |
| "A/D/G_COPY/pi" : Item(status=' ', wc_rev=7), |
| "A/D/G_COPY/rho" : Item(status=' ', wc_rev=7), |
| "A/D/G_COPY/tau" : Item(status=' ', wc_rev=7), |
| }) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # r8 - modify and commit A/D/G_COPY/rho |
| svntest.main.file_write(sbox.ospath('A/D/G_COPY/rho'), |
| "New *and* improved rho content") |
| expected_output = wc.State(wc_dir, {'A/D/G_COPY/rho' : Item(verb='Sending')}) |
| wc_status.tweak('A/D/G_COPY/rho', wc_rev=8) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Switch A_COPY/D/G to A/D/G. |
| wc_disk.add({ |
| "A" : Item(), |
| "A/D/G_COPY" : Item(), |
| "A/D/G_COPY/pi" : Item("This is the file 'pi'.\n"), |
| "A/D/G_COPY/rho" : Item("New *and* improved rho content"), |
| "A/D/G_COPY/tau" : Item("This is the file 'tau'.\n"), |
| }) |
| wc_disk.tweak('A_COPY/D/G/rho',contents="New content") |
| wc_status.tweak("A_COPY/D/G", wc_rev=8, switched='S') |
| wc_status.tweak("A_COPY/D/G/pi", wc_rev=8) |
| wc_status.tweak("A_COPY/D/G/rho", wc_rev=8) |
| wc_status.tweak("A_COPY/D/G/tau", wc_rev=8) |
| expected_output = svntest.wc.State(sbox.wc_dir, { |
| "A_COPY/D/G/rho" : Item(status='U '), |
| }) |
| svntest.actions.run_and_verify_switch(sbox.wc_dir, A_COPY_D_G_path, |
| sbox.repo_url + "/A/D/G", |
| expected_output, wc_disk, wc_status, |
| [], 1) |
| |
| # Update working copy to allow elision (if any). |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], |
| 'up', wc_dir) |
| |
| # Set some mergeinfo on a working copy parent of our switched subtree |
| # A_COPY/D/G. Because the subtree is switched it should *not* inherit |
| # this mergeinfo. |
| svntest.actions.run_and_verify_svn(["property '" + SVN_PROP_MERGEINFO + |
| "' set on '" + A_COPY_path + "'" + |
| "\n"], [], 'ps', SVN_PROP_MERGEINFO, |
| '/A:4', A_COPY_path) |
| |
| # Merge r8 from A/D/G_COPY into our switched target A_COPY/D/G. |
| # A_COPY/D/G should get mergeinfo for r8 as a result of the merge, |
| # but because it's switched should not inherit the mergeinfo from |
| # its nearest WC ancestor with mergeinfo (A_COPY: svn:mergeinfo : /A:4) |
| expected_output = wc.State(A_COPY_D_G_path, { |
| 'rho' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_D_G_path, { |
| '' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(A_COPY_D_G_path, { |
| }) |
| # Note: A_COPY/D/G won't show as switched. |
| expected_status = wc.State(A_COPY_D_G_path, { |
| '' : Item(status=' M', wc_rev=8), |
| 'pi' : Item(status=' ', wc_rev=8), |
| 'rho' : Item(status='M ', wc_rev=8), |
| 'tau' : Item(status=' ', wc_rev=8), |
| }) |
| expected_status.tweak('', switched='S') |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/G_COPY:8'}), |
| 'pi' : Item("This is the file 'pi'.\n"), |
| 'rho' : Item("New *and* improved rho content"), |
| 'tau' : Item("This is the file 'tau'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_D_G_path, { }) |
| |
| svntest.actions.run_and_verify_merge(A_COPY_D_G_path, '7', '8', |
| sbox.repo_url + '/A/D/G_COPY', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| |
| # Check that the mergeinfo set on a switched target can elide to the |
| # repository. |
| # |
| # Specifically this is testing the "switched target" portions of |
| # issue #3188 'Mergeinfo on switched targets/subtrees should |
| # elide to repos'. |
| # |
| # Revert the previous merge and manually set 'svn:mergeinfo : /A/D:4' |
| # on 'merge_tests-1\A_COPY\D'. Now merge -c-4 from /A/D/G into A_COPY/D/G. |
| # This should produce no mergeinfo on A_COPY/D/G'. If the A_COPY/D/G was |
| # unswitched this merge would normally set empty mergeinfo on A_COPY/D/G, |
| # but as it is switched this empty mergeinfo just elides to the |
| # repository (empty mergeinfo on a path can elide if that path doesn't |
| # inherit *any* mergeinfo). |
| svntest.actions.run_and_verify_svn(["Reverted '" + A_COPY_path+ "'\n", |
| "Reverted '" + A_COPY_D_G_path+ "'\n", |
| "Reverted '" + A_COPY_D_G_rho_path + |
| "'\n"], |
| [], 'revert', '-R', wc_dir) |
| svntest.actions.run_and_verify_svn(["property '" + SVN_PROP_MERGEINFO + |
| "' set on '" + A_COPY_D_path+ "'" + |
| "\n"], [], 'ps', SVN_PROP_MERGEINFO, |
| '/A/D:4', A_COPY_D_path) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[-4]], |
| ['U ' + A_COPY_D_G_rho_path + '\n', |
| ' U ' + A_COPY_D_G_path + '\n'], |
| elides=True), |
| [], 'merge', '-c-4', |
| sbox.repo_url + '/A/D/G_COPY', |
| A_COPY_D_G_path) |
| wc_status.tweak("A_COPY/D", status=' M') |
| wc_status.tweak("A_COPY/D/G/rho", status='M ') |
| wc_status.tweak(wc_rev=8) |
| svntest.actions.run_and_verify_status(wc_dir, wc_status) |
| check_mergeinfo_recursively(A_COPY_D_path, |
| { A_COPY_D_path : '/A/D:4' }) |
| |
| #---------------------------------------------------------------------- |
| # Test for issues |
| # |
| # 2823: Account for mergeinfo differences for switched |
| # directories when gathering mergeinfo |
| # |
| # 2839: Support non-inheritable mergeinfo revision ranges |
| # |
| # 3187: Reverse merges don't work properly with |
| # non-inheritable ranges. |
| # |
| # 3188: Mergeinfo on switched targets/subtrees should |
| # elide to repos |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(2823,2839,3187,3188,4056) |
| def merge_to_path_with_switched_children(sbox): |
| "merge to path with switched children" |
| |
| # Merging to a target with switched children requires special handling |
| # to keep mergeinfo correct: |
| # |
| # 1) If the target of a merge has switched children without explicit |
| # mergeinfo, the switched children should get mergeinfo set on |
| # them as a result of the merge. This mergeinfo includes the |
| # mergeinfo resulting from the merge *and* any mergeinfo inherited |
| # from the repos for the switched path. |
| # |
| # 2) Mergeinfo on switched children should never elide. |
| # |
| # 3) The path the switched child overrides cannot be modified by the |
| # merge (it isn't present in the WC) so should not inherit any |
| # mergeinfo added as a result of the merge. To prevent this, the |
| # immediate parent of any switched child should have non-inheritable |
| # mergeinfo added/modified for the merge performed. |
| # |
| # 4) Because of 3, siblings of switched children will not inherit the |
| # mergeinfo resulting from the merge, so must get their own, full set |
| # of mergeinfo. |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, False, 3) |
| |
| # Some paths we'll care about |
| D_path = sbox.ospath('A/D') |
| A_COPY_path = sbox.ospath('A_COPY') |
| A_COPY_beta_path = sbox.ospath('A_COPY/B/E/beta') |
| A_COPY_chi_path = sbox.ospath('A_COPY/D/H/chi') |
| A_COPY_omega_path = sbox.ospath('A_COPY/D/H/omega') |
| A_COPY_psi_path = sbox.ospath('A_COPY/D/H/psi') |
| A_COPY_G_path = sbox.ospath('A_COPY/D/G') |
| A_COPY_rho_path = sbox.ospath('A_COPY/D/G/rho') |
| A_COPY_H_path = sbox.ospath('A_COPY/D/H') |
| A_COPY_D_path = sbox.ospath('A_COPY/D') |
| A_COPY_gamma_path = sbox.ospath('A_COPY/D/gamma') |
| H_COPY_2_path = sbox.ospath('A_COPY_2/D/H') |
| |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], 'up', |
| wc_dir) |
| wc_status.tweak(wc_rev=8) |
| |
| # Switch a file and dir path in the branch: |
| |
| # Switch A_COPY/D/G to A_COPY_2/D/G. |
| wc_status.tweak("A_COPY/D/G", switched='S') |
| expected_output = svntest.wc.State(sbox.wc_dir, {}) |
| svntest.actions.run_and_verify_switch(sbox.wc_dir, A_COPY_G_path, |
| sbox.repo_url + "/A_COPY_2/D/G", |
| expected_output, wc_disk, wc_status, |
| [], 1) |
| |
| # Switch A_COPY/D/G/rho to A_COPY_3/D/G/rho. |
| wc_status.tweak("A_COPY/D/G/rho", switched='S') |
| expected_output = svntest.wc.State(sbox.wc_dir, {}) |
| svntest.actions.run_and_verify_switch(sbox.wc_dir, A_COPY_rho_path, |
| sbox.repo_url + "/A_COPY_3/D/G/rho", |
| expected_output, wc_disk, wc_status, |
| [], 1) |
| |
| # Switch A_COPY/D/H/psi to A_COPY_2/D/H/psi. |
| wc_status.tweak("A_COPY/D/H/psi", switched='S') |
| expected_output = svntest.wc.State(sbox.wc_dir, {}) |
| svntest.actions.run_and_verify_switch(sbox.wc_dir, A_COPY_psi_path, |
| sbox.repo_url + "/A_COPY_2/D/H/psi", |
| expected_output, wc_disk, wc_status, |
| [], 1) |
| |
| # Target with switched file child: |
| # |
| # Merge r8 from A/D/H into A_COPY/D/H. The switched child of |
| # A_COPY/D/H, file A_COPY/D/H/psi (which has no mergeinfo prior |
| # to the merge), is unaffected by the merge so does not get it's |
| # own explicit mergeinfo. |
| # |
| # A_COPY/D/H/psi's parent A_COPY/D/H has no pre-exiting explicit |
| # mergeinfo so should get its own mergeinfo, the non-inheritable |
| # r8 resulting from the merge. |
| # |
| # A_COPY/D/H/psi's unswitched sibling, A_COPY/D/H/omega is affected |
| # by the merge but won't inherit r8 from A_COPY/D/H, so it needs its |
| # own mergeinfo. |
| expected_output = wc.State(A_COPY_H_path, { |
| 'omega' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_H_path, { |
| '' : Item(status=' U'), |
| 'omega' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(A_COPY_H_path, { |
| 'omega' : Item(status=' U') |
| }) |
| expected_status = wc.State(A_COPY_H_path, { |
| '' : Item(status=' M', wc_rev=8), |
| 'psi' : Item(status=' ', wc_rev=8, switched='S'), |
| 'omega' : Item(status='M ', wc_rev=8), |
| 'chi' : Item(status=' ', wc_rev=8), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8'}), |
| 'psi' : Item("This is the file 'psi'.\n"), |
| 'omega' : Item("New content"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_H_path, { }) |
| |
| svntest.actions.run_and_verify_merge(A_COPY_H_path, '7', '8', |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| |
| # Target with switched dir child: |
| # |
| # Merge r6 from A/D into A_COPY/D. The only subtrees with explicit |
| # mergeinfo (or switched) that are affected by the merge are A_COPY/D/G |
| # and A_COPY/D/G/rho. Only these two subtrees, and the target itself, |
| # should receive mergeinfo updates. |
| expected_output = wc.State(A_COPY_D_path, { |
| 'G/rho' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_D_path, { |
| '' : Item(status=' U'), |
| 'G' : Item(status=' U'), |
| 'G/rho' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(A_COPY_D_path, { |
| }) |
| expected_status_D = wc.State(A_COPY_D_path, { |
| '' : Item(status=' M', wc_rev=8), |
| 'H' : Item(status=' M', wc_rev=8), |
| 'H/chi' : Item(status=' ', wc_rev=8), |
| 'H/omega' : Item(status='M ', wc_rev=8), |
| 'H/psi' : Item(status=' ', wc_rev=8, switched='S'), |
| 'G' : Item(status=' M', wc_rev=8, switched='S'), |
| 'G/pi' : Item(status=' ', wc_rev=8), |
| 'G/rho' : Item(status='MM', wc_rev=8, switched='S'), |
| 'G/tau' : Item(status=' ', wc_rev=8), |
| 'gamma' : Item(status=' ', wc_rev=8), |
| }) |
| expected_disk_D = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D:6*'}), |
| 'H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8'}), |
| 'H/chi' : Item("This is the file 'chi'.\n"), |
| 'H/omega' : Item("New content"), |
| 'H/psi' : Item("This is the file 'psi'.\n",), |
| 'G' : Item(props={SVN_PROP_MERGEINFO : '/A/D/G:6*'}), |
| 'G/pi' : Item("This is the file 'pi'.\n"), |
| 'G/rho' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/D/G/rho:6'}), |
| 'G/tau' : Item("This is the file 'tau'.\n"), |
| 'gamma' : Item("This is the file 'gamma'.\n"), |
| }) |
| expected_skip_D = wc.State(A_COPY_D_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_D_path, '5', '6', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk_D, |
| expected_status_D, expected_skip_D, |
| check_props=True) |
| |
| |
| # Merge r5 from A/D into A_COPY/D. This updates the mergeinfo on the |
| # target A_COPY\D because the target is always updated. It also updates |
| # the mergeinfo on A_COPY\D\H because that path has explicit mergeinfo |
| # and has a subtree affected by the merge. Lastly, mergeinfo on |
| # A_COPY/D/H/psi is added because that path is switched. |
| expected_output = wc.State(A_COPY_D_path, { |
| 'H/psi' : Item(status='U ')}) |
| expected_mergeinfo_output = wc.State(A_COPY_D_path, { |
| '' : Item(status=' G'), |
| 'H' : Item(status=' G'), |
| 'H/psi' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(A_COPY_D_path, { |
| }) |
| expected_disk_D.tweak('', props={SVN_PROP_MERGEINFO : '/A/D:5,6*'}) |
| expected_disk_D.tweak('H', props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8'}) |
| expected_disk_D.tweak('H/psi', contents="New content", |
| props={SVN_PROP_MERGEINFO :'/A/D/H/psi:5'}) |
| expected_status_D.tweak('H/psi', status='MM') |
| svntest.actions.run_and_verify_merge(A_COPY_D_path, '4', '5', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk_D, |
| expected_status_D, expected_skip_D, |
| check_props=True) |
| |
| # Finally, merge r4:8 into A_COPY. A_COPY gets mergeinfo for r5-8 added but |
| # since none of A_COPY's subtrees with mergeinfo are affected, none of them |
| # get any mergeinfo changes. |
| expected_output = wc.State(A_COPY_path, { |
| 'B/E/beta' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=8), |
| 'B' : Item(status=' ', wc_rev=8), |
| 'mu' : Item(status=' ', wc_rev=8), |
| 'B/E' : Item(status=' ', wc_rev=8), |
| 'B/E/alpha' : Item(status=' ', wc_rev=8), |
| 'B/E/beta' : Item(status='M ', wc_rev=8), |
| 'B/lambda' : Item(status=' ', wc_rev=8), |
| 'B/F' : Item(status=' ', wc_rev=8), |
| 'C' : Item(status=' ', wc_rev=8), |
| 'D' : Item(status=' M', wc_rev=8), |
| 'D/G' : Item(status=' M', wc_rev=8, switched='S'), |
| 'D/G/pi' : Item(status=' ', wc_rev=8), |
| 'D/G/rho' : Item(status='MM', wc_rev=8, switched='S'), |
| 'D/G/tau' : Item(status=' ', wc_rev=8), |
| 'D/gamma' : Item(status=' ', wc_rev=8), |
| 'D/H' : Item(status=' M', wc_rev=8), |
| 'D/H/chi' : Item(status=' ', wc_rev=8), |
| 'D/H/psi' : Item(status='MM', wc_rev=8, switched='S'), |
| 'D/H/omega' : Item(status='M ', wc_rev=8), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-8'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(props={SVN_PROP_MERGEINFO : '/A/D:5,6*'}), |
| 'D/G' : Item(props={SVN_PROP_MERGEINFO : '/A/D/G:6*'}), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/D/G/rho:6'}), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8'}), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/D/H/psi:5'}), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '4', '8', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| # Commit changes thus far. |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/B/E/beta' : Item(verb='Sending'), |
| 'A_COPY/D' : Item(verb='Sending'), |
| 'A_COPY/D/G' : Item(verb='Sending'), |
| 'A_COPY/D/G/rho' : Item(verb='Sending'), |
| 'A_COPY/D/H' : Item(verb='Sending'), |
| 'A_COPY/D/H/omega' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending'), |
| }) |
| wc_status.tweak('A_COPY', 'A_COPY/B/E/beta', 'A_COPY/D', 'A_COPY/D/G', |
| 'A_COPY/D/G/rho', 'A_COPY/D/H', 'A_COPY/D/H/omega', |
| 'A_COPY/D/H/psi', wc_rev=9) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Unswitch A_COPY/D/H/psi. |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A_COPY/D/H/psi' : Item(status='UU')}) |
| wc_status.tweak("A_COPY/D/H/psi", switched=None, wc_rev=9) |
| wc_disk.tweak("A_COPY", |
| props={SVN_PROP_MERGEINFO : '/A:5-8'}) |
| wc_disk.tweak("A_COPY/B/E/beta", |
| contents="New content") |
| wc_disk.tweak("A_COPY/D", |
| props={SVN_PROP_MERGEINFO : '/A/D:5,6*'}) |
| wc_disk.tweak("A_COPY/D/G", |
| props={SVN_PROP_MERGEINFO : '/A/D/G:6*'}) |
| wc_disk.tweak("A_COPY/D/G/rho", |
| contents="New content", |
| props={SVN_PROP_MERGEINFO : '/A/D/G/rho:6'}) |
| wc_disk.tweak("A_COPY/D/H", |
| props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8'}) |
| wc_disk.tweak("A_COPY/D/H/omega", |
| contents="New content") |
| wc_disk.tweak("A_COPY_2", props={}) |
| svntest.actions.run_and_verify_switch(sbox.wc_dir, A_COPY_psi_path, |
| sbox.repo_url + "/A_COPY/D/H/psi", |
| expected_output, wc_disk, wc_status, |
| [], 1) |
| |
| # Non-inheritable mergeinfo ranges on a target don't prevent repeat |
| # merges of that range on the target's children. |
| # |
| # Non-inheritable mergeinfo ranges on a target are removed if the target |
| # no longer has any switched children and a repeat merge is performed. |
| # |
| # Merge r4:8 from A/D/H into A_COPY/D/H. A_COPY/D/H already has mergeinfo |
| # for r5 and r8 but it is marked as uninheritable so the repeat merge is |
| # allowed on its children, notably the now unswitched A_COPY/D/H/psi. |
| # Since A_COPY/D/H no longer has any switched children and the merge of |
| # r4:8 has been repeated the previously uninheritable ranges 5* and 8* on |
| # A_COPY/D/H are made inheritable and combined with r6-7. A_COPY/D/H/omega |
| # has explicit mergeinfo, but is not touched by the merge, so is left as-is. |
| expected_output = wc.State(A_COPY_H_path, { |
| 'psi' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_H_path, { |
| '' : Item(status=' U'), |
| 'psi' : Item(status=' G') |
| }) |
| expected_elision_output = wc.State(A_COPY_H_path, { |
| 'psi' : Item(status=' U') |
| }) |
| expected_status = wc.State(A_COPY_H_path, { |
| '' : Item(status=' M', wc_rev=9), |
| 'psi' : Item(status='M ', wc_rev=9), |
| 'omega' : Item(status=' ', wc_rev=9), |
| 'chi' : Item(status=' ', wc_rev=8), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-8'}), |
| 'psi' : Item("New content"), |
| 'omega' : Item("New content"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_H_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_H_path, '4', '8', |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| [], |
| True, False, '--allow-mixed-revisions', |
| A_COPY_H_path) |
| |
| # Non-inheritable mergeinfo ranges on a target do prevent repeat |
| # merges on the target itself. |
| # |
| # Add a prop A/D and commit it as r10. Merge r10 into A_COPY/D. Since |
| # A_COPY/D has a switched child it gets r10 added as a non-inheritable |
| # range. Repeat the same merge checking that no repeat merge is |
| # attempted on A_COPY/D. |
| svntest.actions.run_and_verify_svn(["property 'prop:name' set on '" + |
| D_path + "'\n"], [], 'ps', |
| 'prop:name', 'propval', D_path) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/D' : Item(verb='Sending'), |
| 'A_COPY/D/H' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending'), |
| }) |
| wc_status.tweak('A_COPY/D', wc_rev=9) |
| wc_status.tweak('A/D', 'A_COPY/D/H', 'A_COPY/D/H/psi', wc_rev=10) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| expected_output = wc.State(A_COPY_D_path, { |
| '' : Item(status=' U') |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_D_path, { |
| '' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(A_COPY_D_path, { |
| }) |
| # Reuse expected status and disk from last merge to A_COPY/D |
| expected_status_D.tweak(status=' ') |
| expected_status_D.tweak('', status=' M', wc_rev=9) |
| expected_status_D.tweak('H', wc_rev=10) |
| expected_status_D.tweak('H/psi', wc_rev=10, switched=None) |
| expected_status_D.tweak('H/omega', wc_rev=9) |
| expected_status_D.tweak('G', 'G/rho', switched='S', wc_rev=9) |
| expected_disk_D.tweak('', props={SVN_PROP_MERGEINFO : '/A/D:5,6*,10', |
| "prop:name" : "propval"}) |
| expected_disk_D.tweak('G/rho', |
| props={SVN_PROP_MERGEINFO : '/A/D/G/rho:6'}) |
| expected_disk_D.tweak('H', props={SVN_PROP_MERGEINFO : '/A/D/H:5-8'}) |
| expected_disk_D.tweak('H/psi', contents="New content", props={}) |
| svntest.actions.run_and_verify_merge(A_COPY_D_path, '9', '10', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk_D, |
| expected_status_D, expected_skip_D, |
| [], |
| True, False, '--allow-mixed-revisions', |
| A_COPY_D_path) |
| # Repeated merge is a no-op, though we still see the notification reporting |
| # the mergeinfo describing the merge has been recorded, though this time it |
| # is a ' G' notification because there is a local mergeinfo change. |
| expected_output = wc.State(A_COPY_D_path, {}) |
| expected_mergeinfo_output = wc.State(A_COPY_D_path, { |
| '' : Item(status=' G') |
| }) |
| svntest.actions.run_and_verify_merge(A_COPY_D_path, '9', '10', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk_D, |
| expected_status_D, expected_skip_D, |
| [], |
| True, False, '--allow-mixed-revisions', |
| A_COPY_D_path) |
| |
| # Test issue #3187 'Reverse merges don't work properly with |
| # non-inheritable ranges'. |
| # |
| # Test the "switched subtrees" portion of issue #3188 'Mergeinfo on |
| # switched targets/subtrees should elide to repos'. |
| # |
| # Reverse merge r5-8, this should revert all the subtree merges done to |
| # A_COPY thus far and remove all mergeinfo. |
| |
| # Revert all local changes. This leaves just the mergeinfo for r5-8 |
| # on A_COPY and its various subtrees. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| |
| # Update merge target so working revisions are uniform and all |
| # possible elision occurs. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(10), [], |
| 'up', A_COPY_path) |
| |
| # Do the reverse merge. |
| expected_output = wc.State(A_COPY_path, { |
| 'B/E/beta' : Item(status='U '), |
| 'D/G/rho' : Item(status='U '), |
| 'D/H/omega' : Item(status='U '), |
| 'D/H/psi' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D' : Item(status=' U'), |
| 'D/G' : Item(status=' U'), |
| 'D/G/rho' : Item(status=' U'), |
| 'D/H' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D' : Item(status=' U'), |
| 'D/G' : Item(status=' U'), |
| 'D/G/rho' : Item(status=' U'), |
| 'D/H' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=10), |
| 'B' : Item(status=' ', wc_rev=10), |
| 'mu' : Item(status=' ', wc_rev=10), |
| 'B/E' : Item(status=' ', wc_rev=10), |
| 'B/E/alpha' : Item(status=' ', wc_rev=10), |
| 'B/E/beta' : Item(status='M ', wc_rev=10), |
| 'B/lambda' : Item(status=' ', wc_rev=10), |
| 'B/F' : Item(status=' ', wc_rev=10), |
| 'C' : Item(status=' ', wc_rev=10), |
| 'D' : Item(status=' M', wc_rev=10), |
| 'D/G' : Item(status=' M', wc_rev=10, switched='S'), |
| 'D/G/pi' : Item(status=' ', wc_rev=10), |
| 'D/G/rho' : Item(status='MM', wc_rev=10, switched='S'), |
| 'D/G/tau' : Item(status=' ', wc_rev=10), |
| 'D/gamma' : Item(status=' ', wc_rev=10), |
| 'D/H' : Item(status=' M', wc_rev=10), |
| 'D/H/chi' : Item(status=' ', wc_rev=10), |
| 'D/H/psi' : Item(status='M ', wc_rev=10), |
| 'D/H/omega' : Item(status='M ', wc_rev=10), |
| }) |
| expected_disk = wc.State('', { |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '8', '4', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue 2047: Merge from parent dir fails while it succeeds from |
| # the direct dir |
| @Issue(2047) |
| def merge_with_implicit_target_file(sbox): |
| "merge a change to a file, using relative path" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Make a change to A/mu, then revert it using 'svn merge -r 2:1 A/mu' |
| |
| # change A/mu and commit |
| A_path = sbox.ospath('A') |
| mu_path = os.path.join(A_path, 'mu') |
| |
| svntest.main.file_append(mu_path, "A whole new line.\n") |
| |
| 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', wc_rev=2) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Update to revision 2. |
| svntest.actions.run_and_verify_svn(None, [], 'update', wc_dir) |
| |
| # Revert the change committed in r2 |
| os.chdir(wc_dir) |
| |
| # run_and_verify_merge doesn't accept file paths. |
| svntest.actions.run_and_verify_svn(None, [], 'merge', '-r', '2:1', |
| 'A/mu') |
| |
| #---------------------------------------------------------------------- |
| # Test practical application of issue #2769 fix, empty rev range elision, |
| # and elision to the repos. |
| @Issue(2769) |
| @SkipUnless(server_has_mergeinfo) |
| def empty_mergeinfo(sbox): |
| "mergeinfo can explicitly be empty" |
| |
| # A bit o' history: The fix for issue #2769 originally permitted mergeinfo |
| # with empty range lists and as a result we permitted partial elision and |
| # had a whole slew of tests here for that. But the fix of issue #3029 now |
| # prevents svn ps or svn merge from creating mergeinfo with paths mapped to |
| # empty ranges, only empty mergeinfo is allowed. As a result this test now |
| # covers the following areas: |
| # |
| # A) Merging a set of revisions into a path, then reverse merging the |
| # same set out of a subtree of path results in empty mergeinfo |
| # (i.e. "") on the subtree. |
| # |
| # B) Empty mergeinfo elides to empty mergeinfo. |
| # |
| # C) If a merge sets empty mergeinfo on its target and that target has |
| # no ancestor in either the WC or the repository with explicit |
| # mergeinfo, then the target's mergeinfo is removed (a.k.a. elides |
| # to nothing). |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| H_COPY_path = sbox.ospath('A_COPY/D/H') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') |
| |
| # Test area A -- Merge r2:4 into A_COPY then reverse merge 4:2 to |
| # A_COPY/D/G. A_COPY/D/G should end up with empty mergeinfo to |
| # override that of A_COPY. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/H/psi' : Item(status='U '), |
| 'D/G/rho' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'B' : Item(status=' ', wc_rev=2), |
| 'mu' : Item(status=' ', wc_rev=2), |
| 'B/E' : Item(status=' ', wc_rev=2), |
| 'B/E/alpha' : Item(status=' ', wc_rev=2), |
| 'B/E/beta' : Item(status=' ', wc_rev=2), |
| 'B/lambda' : Item(status=' ', wc_rev=2), |
| 'B/F' : Item(status=' ', wc_rev=2), |
| 'C' : Item(status=' ', wc_rev=2), |
| 'D' : Item(status=' ', wc_rev=2), |
| 'D/G' : Item(status=' ', wc_rev=2), |
| 'D/G/pi' : Item(status=' ', wc_rev=2), |
| 'D/G/rho' : Item(status='M ', wc_rev=2), |
| 'D/G/tau' : Item(status=' ', wc_rev=2), |
| 'D/gamma' : Item(status=' ', wc_rev=2), |
| 'D/H' : Item(status=' ', wc_rev=2), |
| 'D/H/chi' : Item(status=' ', wc_rev=2), |
| 'D/H/psi' : Item(status='M ', wc_rev=2), |
| 'D/H/omega' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3-4'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '4', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| # Now do the reverse merge into the subtree. |
| expected_output = wc.State(H_COPY_path, { |
| 'psi' : Item(status='G '), |
| }) |
| expected_mergeinfo_output = wc.State(H_COPY_path, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(H_COPY_path, { |
| }) |
| expected_status = wc.State(H_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'chi' : Item(status=' ', wc_rev=2), |
| 'psi' : Item(status=' ', wc_rev=2), |
| 'omega' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : ''}), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| 'psi' : Item("This is the file 'psi'.\n"), |
| 'omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(H_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(H_COPY_path, '4', '2', |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Test areas B and C -- Reverse merge r3 into A_COPY, this would result in |
| # empty mergeinfo on A_COPY and A_COPY/D/H, but the empty mergeinfo on the |
| # latter elides to the former. And then the empty mergeinfo on A_COPY, |
| # which has no parent with explicit mergeinfo to override (in either the WC |
| # or the repos) itself elides. This leaves the WC in the same unmodified |
| # state as after the call to set_up_branch(). |
| expected_output = expected_merge_output( |
| [[4,3]], ['G ' + rho_COPY_path + '\n', |
| ' G ' + A_COPY_path + '\n', |
| ' U ' + H_COPY_path + '\n', |
| ' U ' + A_COPY_path + '\n',], |
| elides=True) |
| svntest.actions.run_and_verify_svn(expected_output, |
| [], 'merge', '-r4:2', |
| sbox.repo_url + '/A', |
| A_COPY_path) |
| svntest.actions.run_and_verify_status(wc_dir, wc_status) |
| # Check that A_COPY's mergeinfo is gone. |
| svntest.actions.run_and_verify_svn([], '.*W200017: Property.*not found', |
| 'pg', 'svn:mergeinfo', |
| A_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(2781) |
| def prop_add_to_child_with_mergeinfo(sbox): |
| "merge adding prop to child of merge target works" |
| |
| # Test for Issue #2781 Prop add to child of merge target corrupts WC if |
| # child has mergeinfo. |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| expected_disk, expected_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| beta_path = sbox.ospath('A/B/E/beta') |
| beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') |
| B_COPY_path = sbox.ospath('A_COPY/B') |
| |
| # Set a non-mergeinfo prop on a file. |
| svntest.actions.run_and_verify_svn(["property 'prop:name' set on '" + |
| beta_path + "'\n"], [], 'ps', |
| 'prop:name', 'propval', beta_path) |
| expected_disk.tweak('A/B/E/beta', props={'prop:name' : 'propval'}) |
| expected_status.tweak('A/B/E/beta', wc_rev=7) |
| expected_output = wc.State(wc_dir, |
| {'A/B/E/beta' : Item(verb='Sending')}) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Merge r4:5 from A/B/E/beta into A_COPY/B/E/beta. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[5]], |
| ['U ' + beta_COPY_path +'\n', |
| ' U ' + beta_COPY_path +'\n',]), |
| [], 'merge', '-c5', |
| sbox.repo_url + '/A/B/E/beta', |
| beta_COPY_path) |
| |
| # Merge r6:7 into A_COPY/B. In issue #2781 this adds a bogus |
| # and incomplete entry in A_COPY/B/.svn/entries for 'beta'. |
| expected_output = wc.State(B_COPY_path, { |
| 'E/beta' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(B_COPY_path, { |
| '' : Item(status=' U'), |
| 'E/beta' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(B_COPY_path, { |
| }) |
| expected_status = wc.State(B_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'E' : Item(status=' ', wc_rev=2), |
| 'E/alpha' : Item(status=' ', wc_rev=2), |
| 'E/beta' : Item(status='MM', wc_rev=2), |
| 'lambda' : Item(status=' ', wc_rev=2), |
| 'F' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:7'}), |
| 'E' : Item(), |
| 'E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'E/beta' : Item(contents="New content", |
| props={SVN_PROP_MERGEINFO : '/A/B/E/beta:5,7', |
| 'prop:name' : 'propval'}), |
| 'F' : Item(), |
| 'lambda' : Item("This is the file 'lambda'.\n") |
| }) |
| expected_skip = wc.State(B_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(B_COPY_path, '6', '7', |
| sbox.repo_url + '/A/B', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @Issue(2788,3383) |
| def foreign_repos_does_not_update_mergeinfo(sbox): |
| "set no mergeinfo when merging from foreign repos" |
| |
| # Test for issue #2788 and issue #3383. |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| expected_disk, expected_status = set_up_branch(sbox) |
| |
| # Set up for test of issue #2788. |
| |
| # Create a second repository with the same greek tree |
| repo_dir = sbox.repo_dir |
| other_repo_dir, other_repo_url = sbox.add_repo_path("other") |
| other_wc_dir = sbox.add_wc_path("other") |
| svntest.main.copy_repos(repo_dir, other_repo_dir, 6, 1) |
| |
| # Merge r3:4 (using implied peg revisions) from 'other' repos into |
| # A_COPY/D/G. Merge should succeed, but no mergeinfo should be set. |
| G_COPY_path = sbox.ospath('A_COPY/D/G') |
| svntest.actions.run_and_verify_svn(expected_merge_output([[4]], |
| 'U ' + |
| os.path.join(G_COPY_path, |
| "rho") + '\n', True), |
| [], 'merge', '-c4', |
| other_repo_url + '/A/D/G', |
| G_COPY_path) |
| |
| # Merge r4:5 (using explicit peg revisions) from 'other' repos into |
| # A_COPY/B/E. Merge should succeed, but no mergeinfo should be set. |
| E_COPY_path = sbox.ospath('A_COPY/B/E') |
| svntest.actions.run_and_verify_svn(expected_merge_output([[5]], |
| 'U ' + |
| os.path.join(E_COPY_path, |
| "beta") +'\n', True), |
| [], 'merge', |
| other_repo_url + '/A/B/E@4', |
| other_repo_url + '/A/B/E@5', |
| E_COPY_path) |
| |
| expected_status.tweak('A_COPY/D/G/rho', 'A_COPY/B/E/beta', status='M ') |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| |
| # Set up for test of issue #3383. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| |
| # Get a working copy for the foreign repos. |
| svntest.actions.run_and_verify_svn(None, [], 'co', other_repo_url, |
| other_wc_dir) |
| |
| # Create mergeinfo on the foreign repos on an existing directory and |
| # file and an added directory and file. Commit as r7. And no, we aren't |
| # checking these intermediate steps very thoroughly, but we test these |
| # simple merges to *death* elsewhere. |
| |
| # Create mergeinfo on an existing directory. |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| other_repo_url + '/A', |
| os.path.join(other_wc_dir, 'A_COPY'), |
| '-c5') |
| |
| # Create mergeinfo on an existing file. |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| other_repo_url + '/A/D/H/psi', |
| os.path.join(other_wc_dir, 'A_COPY', |
| 'D', 'H', 'psi'), |
| '-c3') |
| |
| # Add a new directory with mergeinfo in the foreign repos. |
| new_dir = os.path.join(other_wc_dir, 'A_COPY', 'N') |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', new_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'ps', |
| SVN_PROP_MERGEINFO, '', new_dir) |
| |
| # Add a new file with mergeinfo in the foreign repos. |
| new_file = os.path.join(other_wc_dir, 'A_COPY', 'nu') |
| svntest.main.file_write(new_file, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', new_file) |
| svntest.actions.run_and_verify_svn(None, [], 'ps', |
| SVN_PROP_MERGEINFO, '', new_file) |
| |
| expected_output = wc.State(other_wc_dir,{ |
| 'A_COPY' : Item(verb='Sending'), # Mergeinfo created |
| 'A_COPY/B/E/beta' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending'), # Mergeinfo created |
| 'A_COPY/N' : Item(verb='Adding'), # Has empty mergeinfo |
| 'A_COPY/nu' : Item(verb='Adding'), # Has empty mergeinfo |
| }) |
| svntest.actions.run_and_verify_commit(other_wc_dir, expected_output, |
| None, [], other_wc_dir, |
| '-m', |
| 'create mergeinfo on foreign repos') |
| # Now merge a diff from the foreign repos that contains the mergeinfo |
| # addition in r7 to A_COPY. The mergeinfo diff should *not* be applied |
| # to A_COPY since it refers to a foreign repository... |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| other_repo_url + '/A@1', |
| other_repo_url + '/A_COPY@7', |
| sbox.ospath('A_COPY')) |
| #...which means there should be no mergeinfo anywhere in WC_DIR, since |
| # this test never created any. |
| svntest.actions.run_and_verify_svn([], [], 'pg', |
| SVN_PROP_MERGEINFO, '-vR', |
| wc_dir) |
| |
| #---------------------------------------------------------------------- |
| # This test involves tree conflicts. |
| @XFail() |
| @Issue(2897) |
| def avoid_reflected_revs(sbox): |
| "avoid repeated merges for cyclic merging" |
| |
| # See <https://issues.apache.org/jira/browse/SVN-2897>. |
| # |
| # This test cherry-picks some changes (all of them, in fact) from the |
| # parent branch 'A' to the child branch 'A_COPY', and then tries to |
| # reintegrate 'A_COPY' to 'A' (explicitly specifying a revision range |
| # on the source branch). It expects the changes that are unique to the |
| # branch 'A_COPY' to be merged to 'A'. |
| # |
| # A --1----[3]---[5]----------? |
| # \ \_____\___ / |
| # \ \ \ / |
| # A_COPY 2-[---4-----6--7--8]- |
| |
| # Create a WC with a single branch |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, True, 1) |
| |
| # Some paths we'll care about |
| A_path = sbox.ospath('A') |
| A_COPY_path = sbox.ospath('A_COPY') |
| tfile1_path = sbox.ospath('A/tfile1') |
| tfile2_path = sbox.ospath('A/tfile2') |
| bfile1_path = os.path.join(A_COPY_path, 'bfile1') |
| bfile2_path = os.path.join(A_COPY_path, 'bfile2') |
| |
| # Contents to be added to files |
| tfile1_content = "This is tfile1\n" |
| tfile2_content = "This is tfile2\n" |
| bfile1_content = "This is bfile1\n" |
| bfile2_content = "This is bfile2\n" |
| |
| # We'll consider A as the trunk and A_COPY as the feature branch |
| # r3 - Create a tfile1 in A |
| svntest.main.file_write(tfile1_path, tfile1_content) |
| svntest.actions.run_and_verify_svn(None, [], 'add', tfile1_path) |
| expected_output = wc.State(wc_dir, {'A/tfile1' : Item(verb='Adding')}) |
| wc_status.add({'A/tfile1' : Item(status=' ', wc_rev=3)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # r4 - Create a bfile1 in A_COPY |
| svntest.main.file_write(bfile1_path, bfile1_content) |
| svntest.actions.run_and_verify_svn(None, [], 'add', bfile1_path) |
| expected_output = wc.State(wc_dir, {'A_COPY/bfile1' : Item(verb='Adding')}) |
| wc_status.add({'A_COPY/bfile1' : Item(status=' ', wc_rev=4)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # r5 - Create one more file in A |
| svntest.main.file_write(tfile2_path, tfile2_content) |
| svntest.actions.run_and_verify_svn(None, [], 'add', tfile2_path) |
| expected_output = wc.State(wc_dir, {'A/tfile2' : Item(verb='Adding')}) |
| wc_status.add({'A/tfile2' : Item(status=' ', wc_rev=5)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Merge r5 from /A to /A_COPY, creating r6 |
| expected_output = wc.State(A_COPY_path, { |
| 'tfile2' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'tfile2' : Item(status='A ', wc_rev='-', copied='+'), |
| 'bfile1' : Item(status=' ', wc_rev=4), |
| 'mu' : Item(status=' ', wc_rev=2), |
| 'C' : Item(status=' ', wc_rev=2), |
| 'D' : Item(status=' ', wc_rev=2), |
| 'B' : Item(status=' ', wc_rev=2), |
| 'B/lambda' : Item(status=' ', wc_rev=2), |
| 'B/E' : Item(status=' ', wc_rev=2), |
| 'B/E/alpha': Item(status=' ', wc_rev=2), |
| 'B/E/beta' : Item(status=' ', wc_rev=2), |
| 'B/F' : Item(status=' ', wc_rev=2), |
| 'D/gamma' : Item(status=' ', wc_rev=2), |
| 'D/G' : Item(status=' ', wc_rev=2), |
| 'D/G/pi' : Item(status=' ', wc_rev=2), |
| 'D/G/rho' : Item(status=' ', wc_rev=2), |
| 'D/G/tau' : Item(status=' ', wc_rev=2), |
| 'D/H' : Item(status=' ', wc_rev=2), |
| 'D/H/chi' : Item(status=' ', wc_rev=2), |
| 'D/H/omega': Item(status=' ', wc_rev=2), |
| 'D/H/psi' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:5'}), |
| 'tfile2' : Item(tfile2_content), |
| 'bfile1' : Item(bfile1_content), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'B' : Item(), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha': Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/F' : Item(), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/omega': Item("This is the file 'omega'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, {}) |
| |
| svntest.actions.run_and_verify_merge(A_COPY_path, '4', '5', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| A_COPY_path, |
| '--allow-mixed-revisions') |
| |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/tfile2' : Item(verb='Adding'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| None) |
| |
| # Merge r3 from /A to /A_COPY, creating r7 |
| expected_output = wc.State(A_COPY_path, { |
| 'tfile1' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status.tweak(wc_rev=5) |
| expected_status.tweak('', wc_rev=6) |
| expected_status.tweak('tfile2', status=' ', copied=None, wc_rev=6) |
| expected_status.add({ |
| 'tfile1' : Item(status='A ', wc_rev='-', copied='+'), |
| }) |
| expected_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A:3,5'}) |
| expected_disk.add({ |
| 'tfile1' : Item(tfile1_content), |
| }) |
| |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| A_COPY_path, |
| '--allow-mixed-revisions') |
| |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/tfile1' : Item(verb='Adding'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| None) |
| |
| # r8 - Add bfile2 to A_COPY |
| svntest.main.file_write(bfile2_path, bfile2_content) |
| svntest.actions.run_and_verify_svn(None, [], 'add', bfile2_path) |
| expected_output = wc.State(wc_dir, {'A_COPY/bfile2' : Item(verb='Adding')}) |
| wc_status.tweak(wc_rev=6) |
| wc_status.add({ |
| 'A_COPY/bfile2' : Item(status=' ', wc_rev=8), |
| 'A_COPY' : Item(status=' ', wc_rev=7), |
| 'A_COPY/tfile2' : Item(status=' ', wc_rev=6), |
| 'A_COPY/tfile1' : Item(status=' ', wc_rev=7), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Merge 2:8 from A_COPY(feature branch) to A(trunk). |
| expected_output = wc.State(A_path, { |
| 'bfile2' : Item(status='A '), |
| 'bfile1' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_path, { |
| }) |
| expected_status = wc.State(A_path, { |
| '' : Item(status=' M', wc_rev=6), |
| 'bfile2' : Item(status='A ', wc_rev='-', copied='+'), |
| 'bfile1' : Item(status='A ', wc_rev='-', copied='+'), |
| 'tfile2' : Item(status=' ', wc_rev=6), |
| 'tfile1' : Item(status=' ', wc_rev=6), |
| 'mu' : Item(status=' ', wc_rev=6), |
| 'C' : Item(status=' ', wc_rev=6), |
| 'D' : Item(status=' ', wc_rev=6), |
| 'B' : Item(status=' ', wc_rev=6), |
| 'B/lambda' : Item(status=' ', wc_rev=6), |
| 'B/E' : Item(status=' ', wc_rev=6), |
| 'B/E/alpha' : Item(status=' ', wc_rev=6), |
| 'B/E/beta' : Item(status=' ', wc_rev=6), |
| 'B/F' : Item(status=' ', wc_rev=6), |
| 'D/gamma' : Item(status=' ', wc_rev=6), |
| 'D/G' : Item(status=' ', wc_rev=6), |
| 'D/G/pi' : Item(status=' ', wc_rev=6), |
| 'D/G/rho' : Item(status=' ', wc_rev=6), |
| 'D/G/tau' : Item(status=' ', wc_rev=6), |
| 'D/H' : Item(status=' ', wc_rev=6), |
| 'D/H/chi' : Item(status=' ', wc_rev=6), |
| 'D/H/omega' : Item(status=' ', wc_rev=6), |
| 'D/H/psi' : Item(status=' ', wc_rev=6), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:3-8'}), |
| 'bfile2' : Item(bfile2_content), |
| 'bfile1' : Item(bfile1_content), |
| 'tfile2' : Item(tfile2_content), |
| 'tfile1' : Item(tfile1_content), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'B' : Item(), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/F' : Item(), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| }) |
| |
| expected_skip = wc.State(A_path, {}) |
| |
| svntest.actions.run_and_verify_merge(A_path, '2', '8', |
| sbox.repo_url + '/A_COPY', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def update_loses_mergeinfo(sbox): |
| "update does not merge mergeinfo" |
| |
| """ |
| When a working copy path receives a fresh svn:mergeinfo property due to |
| an update, and the path has local mergeinfo changes, then the local |
| mergeinfo should be merged with the incoming mergeinfo. |
| """ |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| A_C_wc_dir = sbox.ospath('A/C') |
| A_B_url = sbox.repo_url + '/A/B' |
| A_B_J_url = sbox.repo_url + '/A/B/J' |
| A_B_K_url = sbox.repo_url + '/A/B/K' |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 2.\n'], |
| [], |
| 'mkdir', '-m', 'rev 2', A_B_J_url) |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 3.\n'], |
| [], |
| 'mkdir', '-m', 'rev 3', A_B_K_url) |
| |
| other_wc = sbox.add_wc_path('other') |
| svntest.actions.duplicate_dir(wc_dir, other_wc) |
| |
| expected_output = wc.State(A_C_wc_dir, {'J' : Item(status='A ')}) |
| expected_mergeinfo_output = wc.State(A_C_wc_dir, { |
| '' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(A_C_wc_dir, { |
| }) |
| expected_disk = wc.State('', { |
| 'J' : Item(), |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:2'}), |
| }) |
| expected_status = wc.State(A_C_wc_dir, |
| { '' : Item(wc_rev=1, status=' M'), |
| 'J' : Item(status='A ', |
| wc_rev='-', copied='+') |
| } |
| ) |
| expected_skip = wc.State('', { }) |
| svntest.actions.run_and_verify_merge(A_C_wc_dir, '1', '2', |
| A_B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=1) |
| expected_output = wc.State(A_C_wc_dir, { |
| '' : Item(verb='Sending'), |
| 'J' : Item(verb='Adding') |
| }) |
| expected_status = wc.State(A_C_wc_dir, |
| { '' : Item(status=' ', wc_rev=4), |
| 'J' : Item(status=' ', wc_rev=4) |
| } |
| ) |
| svntest.actions.run_and_verify_commit(A_C_wc_dir, |
| expected_output, |
| expected_status) |
| |
| other_A_C_wc_dir = os.path.join(other_wc, 'A', 'C') |
| expected_output = wc.State(other_A_C_wc_dir, {'K' : Item(status='A ')}) |
| expected_mergeinfo_output = wc.State(other_A_C_wc_dir, { |
| '' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(other_A_C_wc_dir, { |
| }) |
| expected_disk = wc.State('', { |
| 'K' : Item(), |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:3'}), |
| }) |
| expected_status = wc.State(other_A_C_wc_dir, |
| { '' : Item(wc_rev=1, status=' M'), |
| 'K' : Item(status='A ', |
| wc_rev='-', copied='+') |
| } |
| ) |
| expected_skip = wc.State('', { }) |
| svntest.actions.run_and_verify_merge(other_A_C_wc_dir, '2', '3', |
| A_B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=1) |
| expected_output = wc.State(other_A_C_wc_dir, |
| {'J' : Item(status='A '), |
| '' : Item(status=' G') |
| } |
| ) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:2-3'}), |
| 'J' : Item(), |
| 'K' : Item(), |
| }) |
| expected_status = wc.State(other_A_C_wc_dir, |
| { '' : Item(wc_rev=4, status=' M'), |
| 'J' : Item(status=' ', wc_rev='4'), |
| 'K' : Item(status='A ', |
| wc_rev='-', copied='+') |
| } |
| ) |
| svntest.actions.run_and_verify_update(other_A_C_wc_dir, |
| expected_output, |
| expected_disk, |
| expected_status, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Tests part of issue# 2829. |
| @Issue(2829) |
| @SkipUnless(server_has_mergeinfo) |
| def merge_loses_mergeinfo(sbox): |
| "merge should merge mergeinfo" |
| |
| """ |
| When a working copy has no mergeinfo(due to local full revert of all merges), |
| and merge is attempted for someother revision rX, The new mergeinfo should be |
| /merge/src: rX not all the reverted ones reappearing along with rX. |
| """ |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| A_C_wc_dir = sbox.ospath('A/C') |
| A_B_url = sbox.repo_url + '/A/B' |
| A_B_J_url = sbox.repo_url + '/A/B/J' |
| A_B_K_url = sbox.repo_url + '/A/B/K' |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 2.\n'], |
| [], |
| 'mkdir', '-m', 'rev 2', A_B_J_url) |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 3.\n'], |
| [], |
| 'mkdir', '-m', 'rev 3', A_B_K_url) |
| |
| expected_output = wc.State(A_C_wc_dir, {'J' : Item(status='A ')}) |
| expected_mergeinfo_output = wc.State(A_C_wc_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_C_wc_dir, { |
| }) |
| expected_disk = wc.State('', { |
| 'J' : Item(), |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:2'}), |
| }) |
| expected_status = wc.State(A_C_wc_dir, |
| { '' : Item(wc_rev=1, status=' M'), |
| 'J' : Item(status='A ', |
| wc_rev='-', copied='+') |
| } |
| ) |
| expected_skip = wc.State('', { }) |
| svntest.actions.run_and_verify_merge(A_C_wc_dir, '1', '2', |
| A_B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=1) |
| expected_output = wc.State(A_C_wc_dir, { |
| '' : Item(verb='Sending'), |
| 'J' : Item(verb='Adding') |
| }) |
| expected_status = wc.State(A_C_wc_dir, |
| { '' : Item(status=' ', wc_rev=4), |
| 'J' : Item(status=' ', wc_rev=4) |
| } |
| ) |
| svntest.actions.run_and_verify_commit(A_C_wc_dir, |
| expected_output, |
| expected_status) |
| expected_output = wc.State(A_C_wc_dir, {'J' : Item(status='D ')}) |
| expected_elision_output = wc.State(A_C_wc_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_disk = wc.State('', {}) |
| expected_status = wc.State(A_C_wc_dir, |
| { '' : Item(wc_rev=4, status=' M'), |
| 'J' : Item(wc_rev=4, status='D ') |
| } |
| ) |
| svntest.actions.run_and_verify_merge(A_C_wc_dir, '2', '1', |
| A_B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=1) |
| |
| expected_output = wc.State(A_C_wc_dir, {'K' : Item(status='A ')}) |
| expected_disk = wc.State('', { |
| 'K' : Item(), |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:3'}), |
| }) |
| expected_status = wc.State(A_C_wc_dir, |
| { '' : Item(wc_rev=4, status=' M'), |
| 'K' : Item(status='A ', |
| wc_rev='-', copied='+'), |
| 'J' : Item(wc_rev=4, status='D ') |
| } |
| ) |
| expected_mergeinfo_output = wc.State(A_C_wc_dir, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_C_wc_dir, { |
| }) |
| svntest.actions.run_and_verify_merge(A_C_wc_dir, '2', '3', |
| A_B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=1) |
| |
| #---------------------------------------------------------------------- |
| @Issue(2853) |
| def single_file_replace_style_merge_capability(sbox): |
| "replace-style merge capability for a single file" |
| |
| # Test for issue #2853, do_single_file_merge() lacks "Replace-style |
| # merge" capability |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| iota_path = sbox.ospath('iota') |
| mu_path = sbox.ospath('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, '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) |
| |
| # Merge the file mu alone to rev1 |
| svntest.actions.run_and_verify_svn(expected_merge_output(None, |
| ['R ' + mu_path + '\n']), |
| [], |
| 'merge', |
| mu_path + '@2', |
| mu_path + '@1', |
| mu_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue 2786 fix. |
| @Issue(2786) |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_out_of_date_target(sbox): |
| "merge to ood path can lead to inaccurate mergeinfo" |
| |
| # Create a WC with a branch. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, False, 1) |
| |
| # Make second working copy |
| other_wc = sbox.add_wc_path('other') |
| svntest.actions.duplicate_dir(wc_dir, other_wc) |
| |
| # Some paths we'll care about |
| A_COPY_H_path = sbox.ospath('A_COPY/D/H') |
| other_A_COPY_H_path = os.path.join(other_wc, "A_COPY", "D", "H") |
| |
| # Merge -c3 into A_COPY/D/H of first WC. |
| expected_output = wc.State(A_COPY_H_path, { |
| 'psi' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_H_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_H_path, { |
| }) |
| expected_status = wc.State(A_COPY_H_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'psi' : Item(status='M ', wc_rev=2), |
| 'omega' : Item(status=' ', wc_rev=2), |
| 'chi' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:3'}), |
| 'psi' : Item("New content"), |
| 'omega' : Item("This is the file 'omega'.\n"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_H_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_H_path, '2', '3', |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| |
| # Commit merge to first WC. |
| wc_status.tweak('A_COPY/D/H/psi', 'A_COPY/D/H', wc_rev=7) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A_COPY/D/H' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi': Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| wc_status) |
| |
| # Merge -c6 into A_COPY/D/H of other WC. |
| expected_output = wc.State(other_A_COPY_H_path, { |
| 'omega' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(other_A_COPY_H_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(other_A_COPY_H_path, { |
| }) |
| expected_status = wc.State(other_A_COPY_H_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'psi' : Item(status=' ', wc_rev=2), |
| 'omega' : Item(status='M ', wc_rev=2), |
| 'chi' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:6'}), |
| 'psi' : Item("This is the file 'psi'.\n"), |
| 'omega' : Item("New content"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| }) |
| expected_skip = wc.State(other_A_COPY_H_path, { }) |
| svntest.actions.run_and_verify_merge(other_A_COPY_H_path, '5', '6', |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=1) |
| |
| # Update A_COPY/D/H in other WC. Local mergeinfo for r6 on A_COPY/D/H |
| # should be *merged* with r3 from first WC. |
| expected_output = svntest.wc.State(other_A_COPY_H_path, { |
| '' : Item(status=' G'), |
| 'psi' : Item(status='U ') |
| }) |
| other_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:3,6'}), |
| 'psi' : Item(contents="New content"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| 'omega' : Item(contents="New content"), |
| }) |
| other_status = wc.State(other_A_COPY_H_path,{ |
| '' : Item(wc_rev=7, status=' M'), |
| 'chi' : Item(wc_rev=7, status=' '), |
| 'psi' : Item(wc_rev=7, status=' '), |
| 'omega' : Item(wc_rev=7, status='M ') |
| }) |
| svntest.actions.run_and_verify_update(other_A_COPY_H_path, |
| expected_output, |
| other_disk, |
| other_status, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def merge_with_depth_files(sbox): |
| "merge test for --depth files" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| mu_path = sbox.ospath('A/mu') |
| gamma_path = sbox.ospath('A/D/gamma') |
| Acopy_path = sbox.ospath('A_copy') |
| Acopy_mu_path = sbox.ospath('A_copy/mu') |
| A_url = sbox.repo_url + '/A' |
| Acopy_url = sbox.repo_url + '/A_copy' |
| |
| # Copy A_url to A_copy_url |
| svntest.actions.run_and_verify_svn(None, [], 'cp', |
| A_url, Acopy_url, |
| '-m', 'create a new copy of A') |
| |
| svntest.main.file_write(mu_path, "this is file 'mu' modified.\n") |
| svntest.main.file_write(gamma_path, "this is file 'gamma' modified.\n") |
| |
| # Create expected output tree for commit |
| expected_output = wc.State(wc_dir, { |
| 'A/mu' : Item(verb='Sending'), |
| 'A/D/gamma' : Item(verb='Sending'), |
| }) |
| |
| # Create expected status tree for commit |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/mu' : Item(status=' ', wc_rev=3), |
| 'A/D/gamma' : Item(status=' ', wc_rev=3), |
| }) |
| |
| # Commit the modified contents |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # Update working copy |
| svntest.actions.run_and_verify_svn(None, [], |
| 'up', Acopy_path) |
| |
| # Merge r1:3 into A_copy with --depth files. The merge only affects |
| # 'A_copy' and its one file child 'mu', so 'A_copy' gets non-inheritable |
| # mergeinfo for -r1:3 and 'mu' gets its own complete set of mergeinfo: |
| # r1 from its parent, and r1:3 from the merge itself. |
| expected_output = wc.State(Acopy_path, { |
| 'mu' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(Acopy_path, { |
| '' : Item(status=' U'), |
| 'mu' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(Acopy_path, { |
| }) |
| expected_status = wc.State(Acopy_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status='MM'), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=3) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-3*'}), |
| 'B' : Item(), |
| 'mu' : Item("this is file 'mu' modified.\n", |
| props={SVN_PROP_MERGEINFO : '/A/mu:2-3'}), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(Acopy_path, { }) |
| svntest.actions.run_and_verify_merge(Acopy_path, '1', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| [], True, True, |
| '--depth', 'files', Acopy_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #2976 Subtrees can lose non-inheritable ranges. |
| # |
| # Also test for a bug with paths added as the immediate child of the |
| # merge target when the merge target has non-inheritable mergeinfo |
| # and is also the current working directory, see |
| # http://svn.haxx.se/dev/archive-2008-12/0133.shtml. |
| # |
| # Test for issue #3392 'Parsing error with reverse merges and |
| # non-inheritable mergeinfo. |
| # |
| # Test issue #3407 'Shallow merges incorrectly set mergeinfo on children'. |
| @SkipUnless(server_has_mergeinfo) |
| @Issues(2976,3392,3407,4057) |
| def merge_away_subtrees_noninheritable_ranges(sbox): |
| "subtrees can lose non-inheritable ranges" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, nbr_of_branches=2) |
| |
| # Some paths we'll care about |
| H_path = sbox.ospath('A/D/H') |
| D_COPY_path = sbox.ospath('A_COPY/D') |
| A_COPY_path = sbox.ospath('A_COPY') |
| nu_path = sbox.ospath('A/nu') |
| mu_path = sbox.ospath('A/mu') |
| mu_2_path = sbox.ospath('A_COPY_2/mu') |
| D_COPY_2_path = sbox.ospath('A_COPY_2/D') |
| H_COPY_2_path = sbox.ospath('A_COPY_2/D/H') |
| mu_COPY_path = sbox.ospath('A_COPY/mu') |
| nu_COPY_path = sbox.ospath('A_COPY/nu') |
| |
| # Make a change to directory A/D/H and commit as r8. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(7), [], |
| 'update', wc_dir) |
| |
| svntest.actions.run_and_verify_svn( |
| ["property 'prop:name' set on '" + H_path + "'\n"], [], |
| 'ps', 'prop:name', 'propval', H_path) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/D/H' : Item(verb='Sending'),}) |
| wc_status.tweak(wc_rev=7) |
| wc_status.tweak('A/D/H', wc_rev=8) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Merge r6:8 --depth immediates to A_COPY/D. This should merge the |
| # prop change from r8 to A_COPY/H but not the change to A_COPY/D/H/omega |
| # from r7 since that is below the depth we are merging to. Instead, |
| # non-inheritable mergeinfo should be set on the immediate directory |
| # child of A_COPY/D that is affected by the merge: A_COPY/D/H. |
| expected_output = wc.State(D_COPY_path, { |
| 'H' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(D_COPY_path, { |
| '' : Item(status=' U'), |
| 'H' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(D_COPY_path, { |
| }) |
| expected_status = wc.State(D_COPY_path, { |
| '' : Item(status=' M', wc_rev=7), |
| 'H' : Item(status=' M', wc_rev=7), |
| 'H/chi' : Item(status=' ', wc_rev=7), |
| 'H/omega' : Item(status=' ', wc_rev=7), |
| 'H/psi' : Item(status=' ', wc_rev=7), |
| 'G' : Item(status=' ', wc_rev=7), |
| 'G/pi' : Item(status=' ', wc_rev=7), |
| 'G/rho' : Item(status=' ', wc_rev=7), |
| 'G/tau' : Item(status=' ', wc_rev=7), |
| 'gamma' : Item(status=' ', wc_rev=7), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D:7-8'}), |
| 'H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:7-8*', |
| 'prop:name' : 'propval'}), |
| 'H/chi' : Item("This is the file 'chi'.\n"), |
| 'H/omega' : Item("This is the file 'omega'.\n"), |
| 'H/psi' : Item("This is the file 'psi'.\n"), |
| 'G' : Item(), |
| 'G/pi' : Item("This is the file 'pi'.\n"), |
| 'G/rho' : Item("This is the file 'rho'.\n"), |
| 'G/tau' : Item("This is the file 'tau'.\n"), |
| 'gamma' : Item("This is the file 'gamma'.\n"), |
| }) |
| expected_skip = wc.State(D_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(D_COPY_path, '6', '8', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| [], True, True, |
| '--depth', 'immediates', D_COPY_path) |
| |
| # Repeat the previous merge but at default depth of infinity. The change |
| # to A_COPY/D/H/omega should now happen and the non-inheritable ranges on |
| # A_COPY/D/G and A_COPY/D/H be changed to inheritable and then elide to |
| # A_COPY/D. |
| expected_output = wc.State(D_COPY_path, { |
| 'H/omega' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(D_COPY_path, { |
| '' : Item(status=' G'), |
| 'H' : Item(status=' G'), |
| 'H/omega' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(D_COPY_path, { |
| 'H' : Item(status=' U'), |
| 'H/omega' : Item(status=' U'), |
| }) |
| expected_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A/D:7-8'}) |
| expected_disk.tweak('H', props={'prop:name' : 'propval'}) |
| expected_disk.tweak('G', props={}) |
| expected_disk.tweak('H/omega', contents="New content") |
| expected_status.tweak('G', status=' ') |
| expected_status.tweak('H/omega', status='M ') |
| svntest.actions.run_and_verify_merge(D_COPY_path, '6', '8', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| [], True, True) |
| |
| # Now test the problem described in |
| # http://svn.haxx.se/dev/archive-2008-12/0133.shtml. |
| # |
| # First revert all local mods. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| |
| # r9: Merge all available revisions from A to A_COPY at a depth of empty |
| # this will create non-inheritable mergeinfo on A_COPY. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| wc_status.tweak(wc_rev=8) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '--depth', 'empty', |
| sbox.repo_url + '/A', A_COPY_path) |
| wc_status.tweak('A_COPY', wc_rev=9) |
| expected_output = wc.State(wc_dir, {'A_COPY' : Item(verb='Sending')}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # r10: Add the file A/nu. |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| expected_output = wc.State(wc_dir, {'A/nu' : Item(verb='Adding')}) |
| wc_status.add({'A/nu' : Item(status=' ', wc_rev=10)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Now merge -c10 from A to A_COPY. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_output = wc.State('', { |
| 'nu': Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State('', { |
| '' : Item(status=' U'), |
| 'nu' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State('', { |
| }) |
| expected_status = wc.State('', { |
| '' : Item(status=' M'), |
| 'nu' : Item(status='A ', copied='+'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=10) |
| expected_status.tweak('nu', wc_rev='-') |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-8*,10'}), |
| 'nu' : Item("This is the file 'nu'.\n", |
| props={SVN_PROP_MERGEINFO : '/A/nu:10'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State('.', { }) |
| saved_cwd = os.getcwd() |
| os.chdir(A_COPY_path) |
| svntest.actions.run_and_verify_merge('', '9', '10', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| os.chdir(saved_cwd) |
| |
| # If a merge target has inheritable and non-inheritable ranges and has a |
| # child with no explicit mergeinfo, test that a merge which brings |
| # mergeinfo changes to that child (i.e. as part of the diff) properly |
| # records mergeinfo on the child that includes both the incoming mergeinfo |
| # *and* the mergeinfo inherited from it's parent. |
| # |
| # First revert all local changes and remove A_COPY/C/nu from disk. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| |
| # Make a text change to A_COPY_2/mu in r11 and then merge that |
| # change to A/mu in r12. This will create mergeinfo of '/A_COPY_2/mu:11' |
| # on A/mu. |
| svntest.main.file_write(mu_2_path, 'new content') |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', 'log msg', |
| wc_dir) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[11]], |
| ['U ' + mu_path + '\n', |
| ' U ' + mu_path + '\n']), |
| [], 'merge', '-c11', sbox.repo_url + '/A_COPY_2/mu', mu_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', 'log msg', |
| wc_dir) |
| |
| # Now merge r12 from A to A_COPY. A_COPY/mu should get the mergeinfo from |
| # r12, '/A_COPY_2/mu:11' as well as mergeinfo describing the merge itself, |
| # '/A/mu:12'. |
| expected_output = wc.State('.', { |
| 'mu': Item(status='UG'), |
| }) |
| expected_mergeinfo_output = wc.State('.', { |
| '' : Item(status=' U'), |
| 'mu' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State('.', { |
| }) |
| expected_status = wc.State('', { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status='MM'), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=10) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-8*,12'}), |
| 'B' : Item(), |
| 'mu' : Item("new content", |
| props={SVN_PROP_MERGEINFO : '/A/mu:12\n/A_COPY_2/mu:11'}), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State('.', { }) |
| saved_cwd = os.getcwd() |
| os.chdir(A_COPY_path) |
| # Don't do a dry-run, because it will differ due to the way merge |
| # sets override mergeinfo on the children of paths with non-inheritable |
| # ranges. |
| svntest.actions.run_and_verify_merge('.', '11', '12', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False) |
| os.chdir(saved_cwd) |
| |
| # Test for issue #3392 |
| # |
| # Revert local changes and update. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Merge r8 from A/D/H to A_COPY_D/H at depth empty. Since r8 affects only |
| # A_COPY/D/H itself, the resulting mergeinfo is inheritable. Commit this |
| # merge as r13. |
| expected_output = wc.State(H_COPY_2_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(H_COPY_2_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(H_COPY_2_path, { |
| }) |
| expected_status = wc.State(H_COPY_2_path, { |
| '' : Item(status=' M', wc_rev=12), |
| 'psi' : Item(status=' ', wc_rev=12), |
| 'omega' : Item(status=' ', wc_rev=12), |
| 'chi' : Item(status=' ', wc_rev=12), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8', |
| "prop:name" : "propval"}), |
| 'psi' : Item("This is the file 'psi'.\n"), |
| 'omega' : Item("This is the file 'omega'.\n"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| }) |
| expected_skip = wc.State(H_COPY_2_path, {}) |
| svntest.actions.run_and_verify_merge(H_COPY_2_path, '7', '8', |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| [], True, True, |
| '--depth', 'empty', H_COPY_2_path) |
| svntest.actions.run_and_verify_svn(None, [], 'commit', '-m', |
| 'log msg', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| # Now reverse the prior merge. Issue #3392 manifests itself here with |
| # a mergeinfo parsing error: |
| # >svn merge %url%/A/D/H merge_tests-62\A_COPY_2\D\H -c-8 |
| # --- Reverse-merging r8 into 'merge_tests-62\A_COPY_2\D\H': |
| # U merge_tests-62\A_COPY_2\D\H |
| # ..\..\..\subversion\libsvn_subr\mergeinfo.c:590: (apr_err=200020) |
| # svn: Could not parse mergeinfo string '-8' |
| # ..\..\..\subversion\libsvn_subr\kitchensink.c:52: (apr_err=200022) |
| # svn: Negative revision number found parsing '-8' |
| # |
| # Status is identical but for the working revision. |
| expected_status.tweak(wc_rev=13) |
| # The mergeinfo and prop:name props should disappear. |
| expected_disk.remove('') |
| expected_elision_output = wc.State(H_COPY_2_path, { |
| '' : Item(status=' U'), |
| }) |
| svntest.actions.run_and_verify_merge(H_COPY_2_path, '8', '7', |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| |
| # Test issue #3407 'Shallow merges incorrectly set mergeinfo on children'. |
| # |
| # Revert all local mods. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| |
| # Merge all available changes from A to A_COPY at --depth empty. Only the |
| # mergeinfo on A_COPY should be affected. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[9,13]], |
| [' U ' + A_COPY_path + '\n']), |
| [], 'merge', '--depth', 'empty', |
| sbox.repo_url + '/A', A_COPY_path) |
| svntest.actions.run_and_verify_svn([A_COPY_path + ' - /A:2-13*\n'], |
| [], 'pg', SVN_PROP_MERGEINFO, |
| '-R', A_COPY_path) |
| |
| # Merge all available changes from A to A_COPY at --depth files. Only the |
| # mergeinfo on A_COPY and its file children should be affected. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| # Revisions 2-13 are already merged to A_COPY and now they will be merged |
| # to A_COPY's file children. Due to the way we drive the merge editor |
| # r2-3, which are inoperative on A_COPY's file children, do not show up |
| # in the merge notifications, although those revs are included in the |
| # recorded mergeinfo. |
| expected_output = expected_merge_output([[4,13], # Merge notification |
| [9,13], # Merge notification |
| [2,13]], # Mergeinfo notification |
| ['UU %s\n' % (mu_COPY_path), |
| 'A %s\n' % (nu_COPY_path), |
| ' U %s\n' % (A_COPY_path), |
| ' G %s\n' % (mu_COPY_path), |
| ' U %s\n' % (nu_COPY_path),]) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '--depth', 'files', |
| sbox.repo_url + '/A', A_COPY_path) |
| expected_output = svntest.verify.UnorderedOutput( |
| [A_COPY_path + ' - /A:2-13*\n', |
| mu_COPY_path + ' - /A/mu:2-13\n', |
| nu_COPY_path + ' - /A/nu:10-13\n',]) |
| svntest.actions.run_and_verify_svn(expected_output, |
| [], 'pg', SVN_PROP_MERGEINFO, |
| '-R', A_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #2827 |
| # Handle merge info for sparsely-populated directories |
| @Issue(2827) |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_sparse_directories(sbox): |
| "merge to sparse directories" |
| |
| # Merges into sparse working copies should set non-inheritable mergeinfo |
| # on the deepest directories present in the WC. |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, False, 1) |
| |
| # Some paths we'll care about |
| A_path = sbox.ospath('A') |
| D_path = sbox.ospath('A/D') |
| I_path = sbox.ospath('A/C/I') |
| G_path = sbox.ospath('A/D/G') |
| A_COPY_path = sbox.ospath('A_COPY') |
| |
| # Make a few more changes to the merge source... |
| |
| # r7 - modify and commit A/mu |
| svntest.main.file_write(sbox.ospath('A/mu'), |
| "New content") |
| expected_output = wc.State(wc_dir, {'A/mu' : Item(verb='Sending')}) |
| wc_status.tweak('A/mu', wc_rev=7) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| wc_disk.tweak('A/mu', contents="New content") |
| |
| # r8 - Add a prop to A/D and commit. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(7), [], |
| 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(["property 'prop:name' set on '" + |
| D_path + "'\n"], [], 'ps', |
| 'prop:name', 'propval', D_path) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/D' : Item(verb='Sending'), |
| }) |
| wc_status.tweak(wc_rev=7) |
| wc_status.tweak('A/D', wc_rev=8) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # r9 - Add a prop to A and commit. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], |
| 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(["property 'prop:name' set on '" + |
| A_path + "'\n"], [], 'ps', |
| 'prop:name', 'propval', A_path) |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A' : Item(verb='Sending'), |
| }) |
| wc_status.tweak(wc_rev=8) |
| wc_status.tweak('A', wc_rev=9) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Do an --immediates checkout of A_COPY |
| immediates_dir = sbox.add_wc_path('immediates') |
| expected_output = wc.State(immediates_dir, { |
| 'B' : Item(status='A '), |
| 'mu' : Item(status='A '), |
| 'C' : Item(status='A '), |
| 'D' : Item(status='A '), |
| }) |
| expected_disk = wc.State('', { |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'C' : Item(), |
| 'D' : Item(), |
| }) |
| svntest.actions.run_and_verify_checkout(sbox.repo_url + "/A_COPY", |
| immediates_dir, |
| expected_output, expected_disk, |
| [], |
| "--depth", "immediates") |
| |
| # Merge r4:9 into the immediates WC. |
| # The root of the immediates WC should get inheritable r4:9 as should |
| # the one file present 'mu'. The three directory children present, 'B', |
| # 'C', and 'D' are checked out at depth empty; the two of these affected |
| # by the merge, 'B' and 'D', get non-inheritable mergeinfo for r4:9. |
| # The root and 'D' do should also get the changes |
| # that affect them directly (the prop adds from r8 and r9). |
| # |
| # Currently this fails due to r1424469. For a full explanation see |
| # http://svn.haxx.se/dev/archive-2012-12/0472.shtml |
| # and http://svn.haxx.se/dev/archive-2012-12/0475.shtml |
| expected_output = wc.State(immediates_dir, { |
| 'D' : Item(status=' U'), |
| 'mu' : Item(status='U '), |
| '' : Item(status=' U'), |
| # Shadowed below skips |
| 'D/H/omega' : Item(status=' ', treeconflict='U'), |
| 'B/E/beta' : Item(status=' ', treeconflict='U'), |
| }) |
| expected_mergeinfo_output = wc.State(immediates_dir, { |
| '' : Item(status=' U'), |
| 'B' : Item(status=' U'), |
| 'D' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(immediates_dir, { |
| }) |
| expected_status = wc.State(immediates_dir, { |
| '' : Item(status=' M', wc_rev=9), |
| 'B' : Item(status=' M', wc_rev=9), |
| 'mu' : Item(status='M ', wc_rev=9), |
| 'C' : Item(status=' ', wc_rev=9), |
| 'D' : Item(status=' M', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-9', |
| "prop:name" : "propval"}), |
| 'B' : Item(props={SVN_PROP_MERGEINFO : '/A/B:5-9*'}), |
| 'mu' : Item("New content"), |
| 'C' : Item(), |
| 'D' : Item(props={SVN_PROP_MERGEINFO : '/A/D:5-9*', |
| "prop:name" : "propval"}), |
| }) |
| expected_skip = svntest.wc.State(immediates_dir, { |
| 'D/H' : Item(verb='Skipped missing target'), |
| 'B/E' : Item(verb='Skipped missing target'), |
| }) |
| svntest.actions.run_and_verify_merge(immediates_dir, '4', '9', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Do a --files checkout of A_COPY |
| files_dir = sbox.add_wc_path('files') |
| expected_output = wc.State(files_dir, { |
| 'mu' : Item(status='A '), |
| }) |
| expected_disk = wc.State('', { |
| 'mu' : Item("This is the file 'mu'.\n"), |
| }) |
| svntest.actions.run_and_verify_checkout(sbox.repo_url + "/A_COPY", |
| files_dir, |
| expected_output, expected_disk, |
| [], |
| "--depth", "files") |
| |
| # Merge r4:9 into the files WC. |
| # The root of the files WC should get non-inheritable r4:9 and its one |
| # present child 'mu' should get the same but inheritable. The root |
| # should also get the change that affects it directly (the prop add |
| # from r9). |
| expected_output = wc.State(files_dir, { |
| 'mu' : Item(status='U '), |
| '' : Item(status=' U'), |
| # Below the skips |
| 'D/H/omega' : Item(status=' ', treeconflict='U'), |
| 'B/E/beta' : Item(status=' ', treeconflict='U'), |
| }) |
| expected_mergeinfo_output = wc.State(files_dir, { |
| '' : Item(status=' U'), |
| 'mu' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(files_dir, { |
| }) |
| expected_status = wc.State(files_dir, { |
| '' : Item(status=' M', wc_rev=9), |
| 'mu' : Item(status='MM', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-9*', |
| "prop:name" : "propval"}), |
| 'mu' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/mu:5-9'}), |
| }) |
| expected_skip = svntest.wc.State(files_dir, { |
| 'D' : Item(verb='Skipped missing target'), |
| 'B' : Item(verb='Skipped missing target'), |
| }) |
| svntest.actions.run_and_verify_merge(files_dir, '4', '9', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Do an --empty checkout of A_COPY |
| empty_dir = sbox.add_wc_path('empty') |
| expected_output = wc.State(empty_dir, {}) |
| expected_disk = wc.State('', {}) |
| svntest.actions.run_and_verify_checkout(sbox.repo_url + "/A_COPY", |
| empty_dir, |
| expected_output, expected_disk, |
| [], |
| "--depth", "empty") |
| |
| # Merge r4:9 into the empty WC. |
| # The root of the files WC should get non-inheritable r4:9 and also get |
| # the one change that affects it directly (the prop add from r9). |
| expected_output = wc.State(empty_dir, { |
| '' : Item(status=' U'), |
| # Below the skips |
| 'B/E/beta' : Item(status=' ', treeconflict='U'), |
| 'D/H/omega' : Item(status=' ', treeconflict='U'), |
| }) |
| expected_mergeinfo_output = wc.State(empty_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(empty_dir, { |
| }) |
| expected_status = wc.State(empty_dir, { |
| '' : Item(status=' M', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-9*', |
| "prop:name" : "propval"}), |
| }) |
| expected_skip = svntest.wc.State(empty_dir, { |
| 'mu' : Item(verb='Skipped missing target'), |
| 'D' : Item(verb='Skipped missing target'), |
| 'B' : Item(verb='Skipped missing target'), |
| }) |
| svntest.actions.run_and_verify_merge(empty_dir, '4', '9', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Check that default depth for merge is infinity. |
| # |
| # Revert the previous changes to the immediates WC and update one |
| # child in that WC to depth infinity. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', |
| immediates_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'up', '--set-depth', |
| 'infinity', |
| os.path.join(immediates_dir, 'D')) |
| # Now merge r6 into the immediates WC, even though the root of the |
| # is at depth immediates, the subtree rooted at child 'D' is fully |
| # present, so a merge of r6 should affect 'D/H/omega'. |
| expected_output = wc.State(immediates_dir, { |
| 'D/H/omega' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(immediates_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(immediates_dir, { |
| }) |
| expected_status = wc.State(immediates_dir, { |
| '' : Item(status=' M', wc_rev=9), |
| 'B' : Item(status=' ', wc_rev=9), |
| 'mu' : Item(status=' ', wc_rev=9), |
| 'C' : Item(status=' ', wc_rev=9), |
| 'D' : Item(status=' ', wc_rev=9), |
| 'D/gamma' : Item(status=' ', wc_rev=9), |
| 'D/G' : Item(status=' ', wc_rev=9), |
| 'D/G/pi' : Item(status=' ', wc_rev=9), |
| 'D/G/rho' : Item(status=' ', wc_rev=9), |
| 'D/G/tau' : Item(status=' ', wc_rev=9), |
| 'D/H' : Item(status=' ', wc_rev=9), |
| 'D/H/chi' : Item(status=' ', wc_rev=9), |
| 'D/H/omega' : Item(status='M ', wc_rev=9), |
| 'D/H/psi' : Item(status=' ', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:6'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(immediates_dir, {}) |
| svntest.actions.run_and_verify_merge(immediates_dir, '5', '6', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def merge_old_and_new_revs_from_renamed_dir(sbox): |
| "merge -rold(before rename):head renamed dir" |
| |
| # See the email on dev@ from Paul Burba, 2007-09-27, "RE: svn commit: |
| # r26803 - [...]", <http://svn.haxx.se/dev/archive-2007-09/0706.shtml> or |
| # <http://subversion.tigris.org/ds/viewMessage.do?dsForumId=462&dsMessageId=927127>. |
| |
| # Create a WC with a single branch |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, True, 1) |
| |
| # Some paths we'll care about |
| A_url = sbox.repo_url + '/A' |
| A_MOVED_url = sbox.repo_url + '/A_MOVED' |
| A_COPY_path = sbox.ospath('A_COPY') |
| mu_path = sbox.ospath('A/mu') |
| A_MOVED_mu_path = sbox.ospath('A_MOVED/mu') |
| |
| # Make a modification to A/mu |
| svntest.main.file_write(mu_path, "This is the file 'mu' modified.\n") |
| expected_output = wc.State(wc_dir, {'A/mu' : Item(verb='Sending')}) |
| wc_status.add({'A/mu' : Item(status=' ', wc_rev=3)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Move A to A_MOVED |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 4.\n'], |
| [], 'mv', '-m', 'mv A to A_MOVED', |
| A_url, A_MOVED_url) |
| |
| # Update the working copy to get A_MOVED |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Make a modification to A_MOVED/mu |
| svntest.main.file_write(A_MOVED_mu_path, "This is 'mu' in A_MOVED.\n") |
| expected_output = wc.State(wc_dir, {'A_MOVED/mu' : Item(verb='Sending')}) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 4) |
| expected_status.remove('A', 'A/mu', 'A/C', 'A/D', 'A/B', 'A/B/lambda', |
| 'A/B/E', 'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', |
| 'A/D/gamma', 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', |
| 'A/D/G/tau', 'A/D/H', 'A/D/H/chi', 'A/D/H/omega', |
| 'A/D/H/psi') |
| expected_status.add({ |
| '' : Item(status=' ', wc_rev=4), |
| 'iota' : Item(status=' ', wc_rev=4), |
| 'A_MOVED' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/mu' : Item(status=' ', wc_rev=5), |
| 'A_MOVED/C' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/D' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/B' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/B/lambda' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/B/E' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/B/E/alpha': Item(status=' ', wc_rev=4), |
| 'A_MOVED/B/E/beta' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/B/F' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/D/gamma' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/D/G' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/D/G/pi' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/D/G/rho' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/D/G/tau' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/D/H' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/D/H/chi' : Item(status=' ', wc_rev=4), |
| 'A_MOVED/D/H/omega': Item(status=' ', wc_rev=4), |
| 'A_MOVED/D/H/psi' : Item(status=' ', wc_rev=4), |
| 'A_COPY' : Item(status=' ', wc_rev=4), |
| 'A_COPY/mu' : Item(status=' ', wc_rev=4), |
| 'A_COPY/C' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D' : Item(status=' ', wc_rev=4), |
| 'A_COPY/B' : Item(status=' ', wc_rev=4), |
| 'A_COPY/B/lambda' : Item(status=' ', wc_rev=4), |
| 'A_COPY/B/E' : Item(status=' ', wc_rev=4), |
| 'A_COPY/B/E/alpha' : Item(status=' ', wc_rev=4), |
| 'A_COPY/B/E/beta' : Item(status=' ', wc_rev=4), |
| 'A_COPY/B/F' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D/gamma' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D/G' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D/G/pi' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D/G/rho' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D/G/tau' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D/H' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D/H/chi' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D/H/omega' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D/H/psi' : Item(status=' ', wc_rev=4), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Merge /A_MOVED to /A_COPY - this happens in multiple passes |
| # because /A_MOVED has renames in its history between the boundaries |
| # of the requested merge range. |
| expected_output = wc.State(A_COPY_path, { |
| 'mu' : Item(status='G ', prev_status='U '), # mu gets touched twice |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' G', prev_status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=4), |
| 'mu' : Item(status='M ', wc_rev=4), |
| 'C' : Item(status=' ', wc_rev=4), |
| 'D' : Item(status=' ', wc_rev=4), |
| 'B' : Item(status=' ', wc_rev=4), |
| 'B/lambda' : Item(status=' ', wc_rev=4), |
| 'B/E' : Item(status=' ', wc_rev=4), |
| 'B/E/alpha': Item(status=' ', wc_rev=4), |
| 'B/E/beta' : Item(status=' ', wc_rev=4), |
| 'B/F' : Item(status=' ', wc_rev=4), |
| 'D/gamma' : Item(status=' ', wc_rev=4), |
| 'D/G' : Item(status=' ', wc_rev=4), |
| 'D/G/pi' : Item(status=' ', wc_rev=4), |
| 'D/G/rho' : Item(status=' ', wc_rev=4), |
| 'D/G/tau' : Item(status=' ', wc_rev=4), |
| 'D/H' : Item(status=' ', wc_rev=4), |
| 'D/H/chi' : Item(status=' ', wc_rev=4), |
| 'D/H/omega': Item(status=' ', wc_rev=4), |
| 'D/H/psi' : Item(status=' ', wc_rev=4), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3\n/A_MOVED:4-5'}), |
| 'mu' : Item("This is 'mu' in A_MOVED.\n"), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'B' : Item(), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha': Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/F' : Item(), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/omega': Item("This is the file 'omega'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, {}) |
| |
| ### Disabling dry_run mode because currently it can't handle the way |
| ### 'mu' gets textually modified in multiple passes. |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '5', |
| A_MOVED_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def merge_with_child_having_different_rev_ranges_to_merge(sbox): |
| "child having different rev ranges to merge" |
| #Modify A/mu to 30 lines with a content 'line1'...'line30' commit it at r2. |
| #Create a branch A_COPY from A, commit it at r3. |
| #Modify A/mu line number 7 to 'LINE7' modify and commit at r4. |
| #Modify A/mu line number 17 to 'LINE17' modify, set prop 'prop1' on 'A' |
| #with a value 'val1' and commit at r5. |
| #Modify A/mu line number 27 to 'LINE27' modify and commit at r6. |
| #Merge r5 to 'A/mu' as a single file merge explicitly to 'A_COPY/mu'. |
| #Merge r3:6 from 'A' to 'A_COPY |
| #This should merge r4 and then r5 through r6. |
| #Revert r5 and r6 via single file merge on A_COPY/mu. |
| #Revert r6 through r4 on A_COPY this should get back us the pristine copy. |
| #Merge r3:6 from 'A' to 'A_COPY |
| #Revert r5 on A_COPY/mu |
| #Modify line number 17 with 'some other line17' of A_COPY/mu |
| #Merge r6:3 from 'A' to 'A_COPY, This should leave line number 17 |
| #undisturbed in A_COPY/mu, rest should be reverted. |
| |
| # Create a WC |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| A_path = sbox.ospath('A') |
| mu_path = sbox.ospath('A/mu') |
| A_url = sbox.repo_url + '/A' |
| A_mu_url = sbox.repo_url + '/A/mu' |
| A_COPY_url = sbox.repo_url + '/A_COPY' |
| A_COPY_path = sbox.ospath('A_COPY') |
| A_COPY_mu_path = sbox.ospath('A_COPY/mu') |
| thirty_line_dummy_text = 'line1\n' |
| for i in range(2, 31): |
| thirty_line_dummy_text += 'line' + str(i) + '\n' |
| |
| svntest.main.file_write(mu_path, thirty_line_dummy_text) |
| expected_output = wc.State(wc_dir, {'A/mu' : Item(verb='Sending')}) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/mu', wc_rev=2) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'cp', A_url, A_COPY_url, '-m', 'rev 3') |
| # Update the working copy to get A_COPY |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_status.add({'A_COPY' : Item(status=' '), |
| 'A_COPY/mu' : Item(status=' '), |
| 'A_COPY/C' : Item(status=' '), |
| 'A_COPY/D' : Item(status=' '), |
| 'A_COPY/B' : Item(status=' '), |
| 'A_COPY/B/lambda' : Item(status=' '), |
| 'A_COPY/B/E' : Item(status=' '), |
| 'A_COPY/B/E/alpha' : Item(status=' '), |
| 'A_COPY/B/E/beta' : Item(status=' '), |
| 'A_COPY/B/F' : Item(status=' '), |
| 'A_COPY/D/gamma' : Item(status=' '), |
| 'A_COPY/D/G' : Item(status=' '), |
| 'A_COPY/D/G/pi' : Item(status=' '), |
| 'A_COPY/D/G/rho' : Item(status=' '), |
| 'A_COPY/D/G/tau' : Item(status=' '), |
| 'A_COPY/D/H' : Item(status=' '), |
| 'A_COPY/D/H/chi' : Item(status=' '), |
| 'A_COPY/D/H/omega' : Item(status=' '), |
| 'A_COPY/D/H/psi' : Item(status=' ')}) |
| expected_status.tweak(wc_rev=3) |
| tweaked_7th_line = thirty_line_dummy_text.replace('line7', 'LINE 7') |
| svntest.main.file_write(mu_path, tweaked_7th_line) |
| expected_status.tweak('A/mu', wc_rev=4) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_status.tweak(wc_rev=4) |
| tweaked_17th_line = tweaked_7th_line.replace('line17', 'LINE 17') |
| svntest.main.file_write(mu_path, tweaked_17th_line) |
| svntest.main.run_svn(None, 'propset', 'prop1', 'val1', A_path) |
| expected_output = wc.State(wc_dir, |
| { |
| 'A' : Item(verb='Sending'), |
| 'A/mu' : Item(verb='Sending') |
| } |
| ) |
| expected_status.tweak('A', wc_rev=5) |
| expected_status.tweak('A/mu', wc_rev=5) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| tweaked_27th_line = tweaked_17th_line.replace('line27', 'LINE 27') |
| svntest.main.file_write(mu_path, tweaked_27th_line) |
| expected_status.tweak('A/mu', wc_rev=6) |
| expected_output = wc.State(wc_dir, {'A/mu' : Item(verb='Sending')}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| # Merge r5 to A_COPY/mu |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[5]], |
| ['U ' + A_COPY_mu_path + '\n', |
| ' U ' + A_COPY_mu_path + '\n']), |
| [], 'merge', '-r4:5', A_mu_url, A_COPY_mu_path) |
| |
| expected_skip = wc.State(A_COPY_path, {}) |
| expected_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'mu' : Item(status='G ', prev_status='G '), # Updated twice |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'mu' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| 'mu' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=4), |
| 'mu' : Item(status='M ', wc_rev=4), |
| 'C' : Item(status=' ', wc_rev=4), |
| 'D' : Item(status=' ', wc_rev=4), |
| 'B' : Item(status=' ', wc_rev=4), |
| 'B/lambda' : Item(status=' ', wc_rev=4), |
| 'B/E' : Item(status=' ', wc_rev=4), |
| 'B/E/alpha': Item(status=' ', wc_rev=4), |
| 'B/E/beta' : Item(status=' ', wc_rev=4), |
| 'B/F' : Item(status=' ', wc_rev=4), |
| 'D/gamma' : Item(status=' ', wc_rev=4), |
| 'D/G' : Item(status=' ', wc_rev=4), |
| 'D/G/pi' : Item(status=' ', wc_rev=4), |
| 'D/G/rho' : Item(status=' ', wc_rev=4), |
| 'D/G/tau' : Item(status=' ', wc_rev=4), |
| 'D/H' : Item(status=' ', wc_rev=4), |
| 'D/H/chi' : Item(status=' ', wc_rev=4), |
| 'D/H/omega': Item(status=' ', wc_rev=4), |
| 'D/H/psi' : Item(status=' ', wc_rev=4), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:4-6', |
| 'prop1' : 'val1'}), |
| 'mu' : Item(tweaked_27th_line), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'B' : Item(), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha': Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/F' : Item(), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/omega': Item("This is the file 'omega'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '3', '6', |
| A_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| # Revert r5 and r6 on A_COPY/mu |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[6,5]], |
| ['G ' + A_COPY_mu_path + '\n', |
| ' G ' + A_COPY_mu_path + '\n']), |
| [], 'merge', '-r6:4', A_mu_url, A_COPY_mu_path) |
| |
| expected_output = wc.State(A_COPY_path, { |
| '' : Item(status=' G'), # merged removal of prop1 property |
| 'mu' : Item(status='G '), # merged reversion of text changes |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' G'), |
| 'mu' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'mu' : Item(status=' U'), |
| }) |
| expected_status.tweak('', status=' ') |
| expected_status.tweak('mu', status=' ') |
| expected_disk.tweak('', props={}) |
| expected_disk.remove('') |
| expected_disk.tweak('mu', contents=thirty_line_dummy_text) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '6', '3', |
| A_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| expected_disk.add({'' : Item(props={SVN_PROP_MERGEINFO : '/A:4-6', |
| 'prop1' : 'val1'})}) |
| expected_disk.tweak('mu', contents=tweaked_27th_line) |
| expected_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), # new mergeinfo and prop1 property |
| 'mu' : Item(status='U '), # text changes |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status.tweak('', status=' M') |
| expected_status.tweak('mu', status='M ') |
| svntest.actions.run_and_verify_merge(A_COPY_path, '3', '6', |
| A_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| #Revert r5 on A_COPY/mu |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[-5]], |
| ['G ' + A_COPY_mu_path + '\n', |
| ' G ' + A_COPY_mu_path + '\n']), |
| [], 'merge', '-r5:4', A_mu_url, A_COPY_mu_path) |
| tweaked_17th_line_1 = tweaked_27th_line.replace('LINE 17', |
| 'some other line17') |
| tweaked_17th_line_2 = thirty_line_dummy_text.replace('line17', |
| 'some other line17') |
| svntest.main.file_write(A_COPY_mu_path, tweaked_17th_line_1) |
| expected_output = wc.State(A_COPY_path, { |
| '' : Item(status=' G'), |
| 'mu' : Item(status='G ', prev_status='G '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' G'), |
| 'mu' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'mu' : Item(status=' U'), |
| }) |
| expected_status.tweak('', status=' ') |
| expected_status.tweak('mu', status='M ') |
| expected_disk.remove('') |
| expected_disk.tweak('mu', contents=tweaked_17th_line_2) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '6', '3', |
| A_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def merge_old_and_new_revs_from_renamed_file(sbox): |
| "merge -rold(before rename):head renamed file" |
| |
| ## See http://svn.haxx.se/dev/archive-2007-09/0706.shtml ## |
| |
| # Create a WC |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| mu_url = sbox.repo_url + '/A/mu' |
| mu_MOVED_url = sbox.repo_url + '/A/mu_MOVED' |
| mu_COPY_url = sbox.repo_url + '/A/mu_COPY' |
| mu_COPY_path = sbox.ospath('A/mu_COPY') |
| mu_path = sbox.ospath('A/mu') |
| mu_MOVED_path = sbox.ospath('A/mu_MOVED') |
| |
| # Copy mu to mu_COPY |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 2.\n'], |
| [], 'cp', '-m', 'cp mu to mu_COPY', |
| mu_url, mu_COPY_url) |
| |
| # Make a modification to A/mu |
| svntest.main.file_write(mu_path, "This is the file 'mu' modified.\n") |
| expected_output = wc.State(wc_dir, {'A/mu' : Item(verb='Sending')}) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/mu', wc_rev=3) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Move mu to mu_MOVED |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 4.\n'], |
| [], 'mv', '-m', 'mv mu to mu_MOVED', |
| mu_url, mu_MOVED_url) |
| |
| # Update the working copy to get mu_MOVED |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Make a modification to mu_MOVED |
| svntest.main.file_write(mu_MOVED_path, "This is 'mu' in mu_MOVED.\n") |
| expected_output = wc.State(wc_dir, {'A/mu_MOVED' : Item(verb='Sending')}) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 4) |
| expected_status.remove('A/mu') |
| expected_status.add({ |
| 'A/mu_MOVED' : Item(status=' ', wc_rev=5), |
| 'A/mu_COPY' : Item(status=' ', wc_rev=4), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Merge A/mu_MOVED to A/mu_COPY - this happens in multiple passes |
| # because A/mu_MOVED has renames in its history between the |
| # boundaries of the requested merge range. |
| expected_output = expected_merge_output([[2,3],[4,5]], |
| ['U %s\n' % (mu_COPY_path), |
| ' U %s\n' % (mu_COPY_path), |
| 'G %s\n' % (mu_COPY_path), |
| ' G %s\n' % (mu_COPY_path),]) |
| svntest.actions.run_and_verify_svn(expected_output, |
| [], 'merge', '-r', '1:5', |
| mu_MOVED_url, |
| mu_COPY_path) |
| svntest.actions.run_and_verify_svn(['/A/mu:2-3\n', |
| '/A/mu_MOVED:4-5\n'], |
| [], 'propget', SVN_PROP_MERGEINFO, |
| mu_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def merge_with_auto_rev_range_detection(sbox): |
| "merge with auto detection of revision ranges" |
| |
| ## See http://svn.haxx.se/dev/archive-2007-09/0735.shtml ## |
| |
| # Create a WC |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| A_url = sbox.repo_url + '/A' |
| A_COPY_url = sbox.repo_url + '/A_COPY' |
| B1_path = sbox.ospath('A/B1') |
| B1_mu_path = sbox.ospath('A/B1/mu') |
| A_COPY_path = sbox.ospath('A_COPY') |
| |
| # Create B1 inside A |
| svntest.actions.run_and_verify_svn(["A " + B1_path + "\n"], |
| [], 'mkdir', |
| B1_path) |
| |
| # Add a file mu inside B1 |
| svntest.main.file_write(B1_mu_path, "This is the file 'mu'.\n") |
| svntest.actions.run_and_verify_svn(["A " + B1_mu_path + "\n"], |
| [], 'add', B1_mu_path) |
| |
| # Commit B1 and B1/mu |
| expected_output = wc.State(wc_dir, { |
| 'A/B1' : Item(verb='Adding'), |
| 'A/B1/mu' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B1' : Item(status=' ', wc_rev=2), |
| 'A/B1/mu' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Copy A to A_COPY |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 3.\n'], |
| [], 'cp', '-m', 'cp A to A_COPY', |
| A_url, A_COPY_url) |
| |
| # Make a modification to A/B1/mu |
| svntest.main.file_write(B1_mu_path, "This is the file 'mu' modified.\n") |
| expected_output = wc.State(wc_dir, {'A/B1/mu' : Item(verb='Sending')}) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/B1' : Item(status=' ', wc_rev=2), |
| 'A/B1/mu' : Item(status=' ', wc_rev=4), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Update the working copy to get A_COPY |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Merge /A to /A_COPY |
| expected_output = wc.State(A_COPY_path, { |
| 'B1/mu' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=4), |
| 'mu' : Item(status=' ', wc_rev=4), |
| 'C' : Item(status=' ', wc_rev=4), |
| 'D' : Item(status=' ', wc_rev=4), |
| 'B' : Item(status=' ', wc_rev=4), |
| 'B/lambda' : Item(status=' ', wc_rev=4), |
| 'B/E' : Item(status=' ', wc_rev=4), |
| 'B/E/alpha': Item(status=' ', wc_rev=4), |
| 'B/E/beta' : Item(status=' ', wc_rev=4), |
| 'B/F' : Item(status=' ', wc_rev=4), |
| 'B1' : Item(status=' ', wc_rev=4), |
| 'B1/mu' : Item(status='M ', wc_rev=4), |
| 'D/gamma' : Item(status=' ', wc_rev=4), |
| 'D/G' : Item(status=' ', wc_rev=4), |
| 'D/G/pi' : Item(status=' ', wc_rev=4), |
| 'D/G/rho' : Item(status=' ', wc_rev=4), |
| 'D/G/tau' : Item(status=' ', wc_rev=4), |
| 'D/H' : Item(status=' ', wc_rev=4), |
| 'D/H/chi' : Item(status=' ', wc_rev=4), |
| 'D/H/omega': Item(status=' ', wc_rev=4), |
| 'D/H/psi' : Item(status=' ', wc_rev=4), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3-4'}), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'B' : Item(), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha': Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/F' : Item(), |
| 'B1' : Item(), |
| 'B1/mu' : Item("This is the file 'mu' modified.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/omega': Item("This is the file 'omega'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, {}) |
| svntest.actions.run_and_verify_merge(A_COPY_path, None, None, |
| A_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue 2818: Provide a 'merge' API which allows for merging of |
| # arbitrary revision ranges (e.g. '-c 3,5,7') |
| @Issue(2818) |
| @SkipUnless(server_has_mergeinfo) |
| def cherry_picking(sbox): |
| "command line supports cherry picked merge ranges" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| H_path = sbox.ospath('A/D/H') |
| G_path = sbox.ospath('A/D/G') |
| A_COPY_path = sbox.ospath('A_COPY') |
| D_COPY_path = sbox.ospath('A_COPY/D') |
| G_COPY_path = sbox.ospath('A_COPY/D/G') |
| H_COPY_path = sbox.ospath('A_COPY/D/H') |
| rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') |
| omega_COPY_path = sbox.ospath('A_COPY/D/H/omega') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| |
| # Update working copy |
| expected_output = svntest.wc.State(wc_dir, {}) |
| wc_status.tweak(wc_rev='6') |
| svntest.actions.run_and_verify_update(wc_dir, expected_output, |
| wc_disk, wc_status, |
| check_props=True) |
| |
| # Make some prop changes to some dirs. |
| svntest.actions.run_and_verify_svn(["property 'prop:name' set on '" + |
| G_path + "'\n"], [], 'ps', |
| 'prop:name', 'propval', G_path) |
| expected_output = svntest.wc.State(wc_dir, {'A/D/G': Item(verb='Sending'),}) |
| wc_status.tweak('A/D/G', wc_rev=7) |
| wc_disk.tweak('A/D/G', props={'prop:name' : 'propval'}) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| svntest.actions.run_and_verify_svn(["property 'prop:name' set on '" + |
| H_path + "'\n"], [], 'ps', |
| 'prop:name', 'propval', H_path) |
| expected_output = svntest.wc.State(wc_dir, {'A/D/H': Item(verb='Sending'),}) |
| wc_status.tweak('A/D/H', wc_rev=8) |
| wc_disk.tweak('A/D/H', props={'prop:name' : 'propval'}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Do multiple additive merges to a file" |
| # Merge -r2:4 -c6 into A_COPY/D/G/rho. |
| expected_skip = wc.State(rho_COPY_path, { }) |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[3,4],[6]], |
| ['U ' + rho_COPY_path + '\n', |
| ' U ' + rho_COPY_path + '\n', |
| ' G ' + rho_COPY_path + '\n',]), |
| [], 'merge', '-r2:4', '-c6', |
| sbox.repo_url + '/A/D/G/rho', rho_COPY_path) |
| |
| # Check rho's status and props. |
| expected_status = wc.State(rho_COPY_path, |
| {'' : Item(status='MM', wc_rev=6)}) |
| svntest.actions.run_and_verify_status(rho_COPY_path, expected_status) |
| svntest.actions.run_and_verify_svn(["/A/D/G/rho:3-4,6\n"], [], |
| 'propget', SVN_PROP_MERGEINFO, |
| rho_COPY_path) |
| |
| #Do multiple additive merges to a directory: |
| # Merge -c6 -c8 into A_COPY/D/H |
| expected_output = expected_merge_output( |
| [[6],[8]], |
| ['U ' + omega_COPY_path + '\n', |
| ' U ' + H_COPY_path + '\n', |
| ' G ' + H_COPY_path + '\n',]) |
| svntest.actions.run_and_verify_svn(expected_output, |
| [], 'merge', '-c6', '-c8', |
| sbox.repo_url + '/A/D/H', |
| H_COPY_path) |
| |
| # Check A_COPY/D/H's status and props. |
| expected_status = wc.State(H_COPY_path, |
| {'' : Item(status=' M', wc_rev=6), |
| 'psi' : Item(status=' ', wc_rev=6), |
| 'chi' : Item(status=' ', wc_rev=6), |
| 'omega': Item(status='M ', wc_rev=6),}) |
| svntest.actions.run_and_verify_status(H_COPY_path, expected_status) |
| svntest.actions.run_and_verify_svn([H_COPY_path + " - /A/D/H:6,8\n"], |
| [], 'propget', '-R', SVN_PROP_MERGEINFO, |
| H_COPY_path) |
| |
| # Do multiple reverse merges to a directory: |
| # Merge -c-6 -c-3 into A_COPY |
| expected_output = expected_merge_output( |
| [[-3],[-6]], |
| ['G ' + omega_COPY_path + '\n', |
| ' U ' + A_COPY_path + '\n', |
| ' U ' + H_COPY_path + '\n', |
| ' G ' + A_COPY_path + '\n', |
| ' G ' + H_COPY_path + '\n',], |
| elides=True) |
| svntest.actions.run_and_verify_svn(expected_output, |
| [], 'merge', '-c-3', '-c-6', |
| sbox.repo_url + '/A', |
| A_COPY_path) |
| expected_status = wc.State(A_COPY_path, |
| {'' : Item(status=' ', wc_rev=6), |
| 'B' : Item(status=' ', wc_rev=6), |
| 'B/lambda' : Item(status=' ', wc_rev=6), |
| 'B/E' : Item(status=' ', wc_rev=6), |
| 'B/E/alpha' : Item(status=' ', wc_rev=6), |
| 'B/E/beta' : Item(status=' ', wc_rev=6), |
| 'B/F' : Item(status=' ', wc_rev=6), |
| 'mu' : Item(status=' ', wc_rev=6), |
| 'C' : Item(status=' ', wc_rev=6), |
| 'D' : Item(status=' ', wc_rev=6), |
| 'D/gamma' : Item(status=' ', wc_rev=6), |
| 'D/G' : Item(status=' ', wc_rev=6), |
| 'D/G/pi' : Item(status=' ', wc_rev=6), |
| 'D/G/rho' : Item(status='MM', wc_rev=6), |
| 'D/G/tau' : Item(status=' ', wc_rev=6), |
| 'D/H' : Item(status=' M', wc_rev=6), |
| 'D/H/chi' : Item(status=' ', wc_rev=6), |
| 'D/H/psi' : Item(status=' ', wc_rev=6), |
| 'D/H/omega' : Item(status=' ', wc_rev=6),}) |
| svntest.actions.run_and_verify_status(A_COPY_path, expected_status) |
| # A_COPY/D/G/rho is untouched by the merge so its mergeinfo |
| # remains unchanged. |
| expected_out = H_COPY_path + " - /A/D/H:8\n|" + \ |
| rho_COPY_path + " - /A/D/G/rho:3-4,6\n" |
| # Construct proper regex for '\' infested Windows paths. |
| if sys.platform == 'win32': |
| expected_out = expected_out.replace("\\", "\\\\") |
| svntest.actions.run_and_verify_svn(expected_out, [], |
| 'propget', '-R', SVN_PROP_MERGEINFO, |
| A_COPY_path) |
| |
| # Do both additive and reverse merges to a directory: |
| # Merge -r2:3 -c-4 -r4:7 to A_COPY/D |
| expected_output = expected_merge_output( |
| [[3], [-4], [6,7], [5,7]], |
| [' U ' + G_COPY_path + '\n', |
| 'U ' + omega_COPY_path + '\n', |
| 'U ' + psi_COPY_path + '\n', |
| ' U ' + D_COPY_path + '\n', |
| ' G ' + D_COPY_path + '\n', |
| ' U ' + H_COPY_path + '\n', |
| ' G ' + H_COPY_path + '\n', |
| 'G ' + rho_COPY_path + '\n', |
| ' U ' + rho_COPY_path + '\n', |
| ' G ' + rho_COPY_path + '\n'], |
| elides=True) |
| svntest.actions.run_and_verify_svn(expected_output, [], 'merge', |
| '-r2:3', '-c-4', '-r4:7', |
| sbox.repo_url + '/A/D', |
| D_COPY_path) |
| expected_status = wc.State(D_COPY_path, |
| {'' : Item(status=' M', wc_rev=6), |
| 'gamma' : Item(status=' ', wc_rev=6), |
| 'G' : Item(status=' M', wc_rev=6), |
| 'G/pi' : Item(status=' ', wc_rev=6), |
| 'G/rho' : Item(status=' ', wc_rev=6), |
| 'G/tau' : Item(status=' ', wc_rev=6), |
| 'H' : Item(status=' M', wc_rev=6), |
| 'H/chi' : Item(status=' ', wc_rev=6), |
| 'H/psi' : Item(status='M ', wc_rev=6), |
| 'H/omega' : Item(status='M ', wc_rev=6),}) |
| svntest.actions.run_and_verify_status(D_COPY_path, expected_status) |
| expected_out = D_COPY_path + " - /A/D:3,5-7\n|" + \ |
| H_COPY_path + " - /A/D/H:3,5-8\n|" + \ |
| rho_COPY_path + " - /A/D/G/rho:3-4,6\n" |
| # Construct proper regex for '\' infested Windows paths. |
| if sys.platform == 'win32': |
| expected_out = expected_out.replace("\\", "\\\\") |
| svntest.actions.run_and_verify_svn(expected_out, [], |
| 'propget', '-R', SVN_PROP_MERGEINFO, |
| D_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(2969) |
| def propchange_of_subdir_raises_conflict(sbox): |
| "merge of propchange on subdir raises conflict" |
| |
| ## See https://issues.apache.org/jira/browse/SVN-2969. ## |
| |
| # Create a WC with a single branch |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, True, 1) |
| |
| # Some paths we'll care about |
| B_url = sbox.repo_url + '/A/B' |
| E_path = sbox.ospath('A/B/E') |
| lambda_path = sbox.ospath('A/B/lambda') |
| A_COPY_B_path = sbox.ospath('A_COPY/B') |
| A_COPY_B_E_path = sbox.ospath('A_COPY/B/E') |
| A_COPY_lambda_path = sbox.ospath('A_COPY/B/E/lambda') |
| |
| # Set a property on A/B/E and Make a modification to A/B/lambda |
| svntest.main.run_svn(None, 'propset', 'x', 'x', E_path) |
| |
| svntest.main.file_write(lambda_path, "This is the file 'lambda' modified.\n") |
| expected_output = wc.State(wc_dir, { |
| 'A/B/lambda' : Item(verb='Sending'), |
| 'A/B/E' : Item(verb='Sending'), |
| }) |
| wc_status.add({ |
| 'A/B/lambda' : Item(status=' ', wc_rev=3), |
| 'A/B/E' : Item(status=' ', wc_rev=3), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Merge /A/B to /A_COPY/B ie., r1 to r3 with depth files |
| expected_output = wc.State(A_COPY_B_path, { |
| 'lambda' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_B_path, { |
| '' : Item(status=' U'), |
| 'lambda' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_B_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:2-3*'}), |
| 'lambda' : Item(contents="This is the file 'lambda' modified.\n", |
| props={SVN_PROP_MERGEINFO : '/A/B/lambda:2-3'}), |
| 'F' : Item(), |
| 'E' : Item(), |
| 'E/alpha' : Item(contents="This is the file 'alpha'.\n"), |
| 'E/beta' : Item(contents="This is the file 'beta'.\n"), |
| }) |
| expected_status = wc.State(A_COPY_B_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'lambda' : Item(status='MM', wc_rev=2), |
| 'F' : Item(status=' ', wc_rev=2), |
| 'E' : Item(status=' ', wc_rev=2), |
| 'E/alpha' : Item(status=' ', wc_rev=2), |
| 'E/beta' : Item(status=' ', wc_rev=2), |
| }) |
| expected_skip = wc.State(A_COPY_B_path, {}) |
| |
| svntest.actions.run_and_verify_merge(A_COPY_B_path, None, None, |
| B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| '--depth', 'files', |
| A_COPY_B_path) |
| |
| # Merge /A/B to /A_COPY/B ie., r1 to r3 with infinite depth |
| expected_output = wc.State(A_COPY_B_path, { |
| 'E' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_B_path, { |
| '' : Item(status=' G'), |
| 'E' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_B_path, { |
| 'E' : Item(status=' U'), |
| 'lambda' : Item(status=' U'), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:2-3'}), |
| 'lambda' : Item(contents="This is the file 'lambda' modified.\n"), |
| 'F' : Item(), |
| 'E' : Item(props={'x': 'x'}), |
| 'E/alpha' : Item(contents="This is the file 'alpha'.\n"), |
| 'E/beta' : Item(contents="This is the file 'beta'.\n"), |
| }) |
| expected_status = wc.State(A_COPY_B_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'lambda' : Item(status='M ', wc_rev=2), |
| 'F' : Item(status=' ', wc_rev=2), |
| 'E' : Item(status=' M', wc_rev=2), |
| 'E/alpha' : Item(status=' ', wc_rev=2), |
| 'E/beta' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_merge(A_COPY_B_path, None, None, |
| B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], 1, 1) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #2971: Reverse merge of prop add segfaults if |
| # merging to parent of first merge |
| @Issue(2971) |
| @SkipUnless(server_has_mergeinfo) |
| def reverse_merge_prop_add_on_child(sbox): |
| "reverse merge of prop add on child" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, True, 1) |
| |
| # Some paths we'll care about |
| G_path = sbox.ospath('A/D/G') |
| D_COPY_path = sbox.ospath('A_COPY/D') |
| G_COPY_path = sbox.ospath('A_COPY/D/G') |
| |
| # Make some prop changes to some dirs. |
| svntest.actions.run_and_verify_svn(["property 'prop:name' set on '" + |
| G_path + "'\n"], [], 'ps', |
| 'prop:name', 'propval', G_path) |
| expected_output = svntest.wc.State(wc_dir, {'A/D/G': Item(verb='Sending'),}) |
| wc_status.tweak('A/D/G', wc_rev=3) |
| wc_disk.tweak('A/D/G', props={'prop:name' : 'propval'}) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Merge -c3's prop add to A_COPY/D/G |
| expected_output = wc.State(G_COPY_path, { |
| '' : Item(status=' U') |
| }) |
| expected_mergeinfo_output = wc.State(G_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(G_COPY_path, { |
| }) |
| expected_status = wc.State(G_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'pi' : Item(status=' ', wc_rev=2), |
| 'rho' : Item(status=' ', wc_rev=2), |
| 'tau' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/G:3', |
| 'prop:name' : 'propval'}), |
| 'pi' : Item("This is the file 'pi'.\n"), |
| 'rho' : Item("This is the file 'rho'.\n"), |
| 'tau' : Item("This is the file 'tau'.\n"), |
| }) |
| expected_skip = wc.State(G_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(G_COPY_path, '2', '3', |
| sbox.repo_url + '/A/D/G', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Now merge -c-3 but target the previous target's parent instead. |
| expected_output = wc.State(D_COPY_path, { |
| 'G' : Item(status=' G'), |
| }) |
| expected_mergeinfo_output = wc.State(D_COPY_path, { |
| '' : Item(status=' U'), |
| 'G' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(D_COPY_path, { |
| '' : Item(status=' U'), |
| 'G' : Item(status=' U'), |
| }) |
| expected_status = wc.State(D_COPY_path, { |
| '' : Item(status=' ', wc_rev=2), |
| 'G' : Item(status=' ', wc_rev=2), |
| 'G/pi' : Item(status=' ', wc_rev=2), |
| 'G/rho' : Item(status=' ', wc_rev=2), |
| 'G/tau' : Item(status=' ', wc_rev=2), |
| 'H' : Item(status=' ', wc_rev=2), |
| 'H/chi' : Item(status=' ', wc_rev=2), |
| 'H/psi' : Item(status=' ', wc_rev=2), |
| 'H/omega' : Item(status=' ', wc_rev=2), |
| 'gamma' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| 'G' : Item(), |
| 'G/pi' : Item("This is the file 'pi'.\n"), |
| 'G/rho' : Item("This is the file 'rho'.\n"), |
| 'G/tau' : Item("This is the file 'tau'.\n"), |
| 'H' : Item(), |
| 'H/chi' : Item("This is the file 'chi'.\n"), |
| 'H/psi' : Item("This is the file 'psi'.\n"), |
| 'H/omega' : Item("This is the file 'omega'.\n"), |
| 'gamma' : Item("This is the file 'gamma'.\n") |
| }) |
| expected_skip = wc.State(D_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(D_COPY_path, '3', '2', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @XFail() |
| @Issues(2970,3642) |
| def merge_target_with_non_inheritable_mergeinfo(sbox): |
| "merge target with non inheritable mergeinfo" |
| |
| ## See https://issues.apache.org/jira/browse/SVN-2970. ## |
| |
| # Create a WC with a single branch |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, True, 1) |
| |
| # Some paths we'll care about |
| B_url = sbox.repo_url + '/A/B' |
| lambda_path = sbox.ospath('A/B/lambda') |
| newfile_path = sbox.ospath('A/B/E/newfile') |
| A_COPY_B_path = sbox.ospath('A_COPY/B') |
| |
| # Make a modifications to A/B/lambda and add A/B/E/newfile |
| svntest.main.file_write(lambda_path, "This is the file 'lambda' modified.\n") |
| svntest.main.file_write(newfile_path, "This is the file 'newfile'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', newfile_path) |
| expected_output = wc.State(wc_dir, { |
| 'A/B/lambda' : Item(verb='Sending'), |
| 'A/B/E/newfile' : Item(verb='Adding'), |
| }) |
| wc_status.add({ |
| 'A/B/lambda' : Item(status=' ', wc_rev=3), |
| 'A/B/E/newfile' : Item(status=' ', wc_rev=3), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Merge /A/B to /A_COPY/B ie., r1 to r3 with depth immediates |
| expected_output = wc.State(A_COPY_B_path, { |
| 'lambda' : Item(status='U '), |
| }) |
| # Issue #3642 https://issues.apache.org/jira/browse/SVN-3642 |
| # |
| # We don't expect A_COPY/B/F to have mergeinfo recorded on it because |
| # not only is it unaffected by the merge at depth immediates, it could |
| # never be affected by the merge, regardless of depth. |
| expected_mergeinfo_output = wc.State(A_COPY_B_path, { |
| '' : Item(status=' U'), |
| 'E' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_B_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:2-3'}), |
| 'lambda' : Item(contents="This is the file 'lambda' modified.\n"), |
| 'F' : Item(), # No mergeinfo! |
| 'E' : Item(props={SVN_PROP_MERGEINFO : '/A/B/E:2-3*'}), |
| 'E/alpha' : Item(contents="This is the file 'alpha'.\n"), |
| 'E/beta' : Item(contents="This is the file 'beta'.\n"), |
| }) |
| expected_status = wc.State(A_COPY_B_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'lambda' : Item(status='M ', wc_rev=2), |
| 'F' : Item(status=' ', wc_rev=2), |
| 'E' : Item(status=' M', wc_rev=2), |
| 'E/alpha' : Item(status=' ', wc_rev=2), |
| 'E/beta' : Item(status=' ', wc_rev=2), |
| }) |
| expected_skip = wc.State(A_COPY_B_path, {}) |
| |
| svntest.actions.run_and_verify_merge(A_COPY_B_path, None, None, |
| B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| '--depth', 'immediates', |
| A_COPY_B_path) |
| |
| # Merge /A/B to /A_COPY/B ie., r1 to r3 with infinite depth |
| expected_output = wc.State(A_COPY_B_path, { |
| 'E/newfile' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_B_path, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_B_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:2-3'}), |
| 'lambda' : Item(contents="This is the file 'lambda' modified.\n"), |
| 'F' : Item(), |
| 'E' : Item(), |
| 'E/alpha' : Item(contents="This is the file 'alpha'.\n"), |
| 'E/beta' : Item(contents="This is the file 'beta'.\n"), |
| 'E/newfile' : Item(contents="This is the file 'newfile'.\n"), |
| }) |
| expected_status = wc.State(A_COPY_B_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'lambda' : Item(status='M ', wc_rev=2), |
| 'F' : Item(status=' ', wc_rev=2), |
| 'E' : Item(status=' ', wc_rev=2), |
| 'E/alpha' : Item(status=' ', wc_rev=2), |
| 'E/beta' : Item(status=' ', wc_rev=2), |
| 'E/newfile' : Item(status='A ', wc_rev=2), |
| }) |
| |
| svntest.actions.run_and_verify_merge(A_COPY_B_path, None, None, |
| B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def self_reverse_merge(sbox): |
| "revert a commit on a target" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Make changes to the working copy |
| mu_path = sbox.ospath('A/mu') |
| svntest.main.file_append(mu_path, 'appended mu text') |
| |
| # Created expected output tree for 'svn ci' |
| expected_output = wc.State(wc_dir, { |
| 'A/mu' : Item(verb='Sending'), |
| }) |
| |
| # Create expected status tree; all local revisions should be at 1, |
| # but mu should be at revision 2. |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.tweak('A/mu', wc_rev=2) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| |
| # update to HEAD so that the to-be-undone revision is found in the |
| # implicit mergeinfo (the natural history) of the target. |
| svntest.actions.run_and_verify_svn(None, [], 'update', wc_dir) |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/mu' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(wc_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(wc_dir, { |
| '' : Item(status=' U'), |
| }) |
| expected_skip = wc.State(wc_dir, { }) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 2) |
| expected_status.tweak('A/mu', status='M ') |
| 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, |
| [], True, True) |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| |
| # record dummy self mergeinfo to test the fact that self-reversal should work |
| # irrespective of mergeinfo. |
| svntest.actions.run_and_verify_svn(None, [], 'ps', SVN_PROP_MERGEINFO, |
| '/:1', wc_dir) |
| |
| # Bad svntest.main.greek_state does not have '', so adding it explicitly. |
| expected_disk.add({'' : Item(props={SVN_PROP_MERGEINFO : '/:1'})}) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 2) |
| expected_status.tweak('', status = ' M') |
| expected_status.tweak('A/mu', status = 'M ') |
| expected_mergeinfo_output = wc.State(wc_dir, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(wc_dir, { |
| }) |
| 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, |
| [], True, True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def ignore_ancestry_and_mergeinfo(sbox): |
| "--ignore-ancestry also ignores mergeinfo" |
| |
| # Create a WC with a single branch |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, True, 1) |
| |
| # Some paths we'll care about |
| A_B_url = sbox.repo_url + '/A/B' |
| A_COPY_B_path = sbox.ospath('A_COPY/B') |
| lambda_path = sbox.ospath('A/B/lambda') |
| A_COPY_lambda_path = sbox.ospath('A_COPY/B/lambda') |
| |
| # Make modifications to A/B/lambda |
| svntest.main.file_write(lambda_path, "This is the file 'lambda' modified.\n") |
| expected_output = wc.State(wc_dir, { |
| 'A/B/lambda' : Item(verb='Sending'), |
| }) |
| wc_status.add({ |
| 'A/B/lambda' : Item(status=' ', wc_rev=3), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Merge /A/B to /A_COPY/B ie., r1 to r3 with depth immediates |
| expected_output = wc.State(A_COPY_B_path, { |
| 'lambda' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_B_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_B_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:2-3'}), |
| 'lambda' : Item(contents="This is the file 'lambda' modified.\n"), |
| 'F' : Item(props={}), |
| 'E' : Item(props={}), |
| 'E/alpha' : Item(contents="This is the file 'alpha'.\n"), |
| 'E/beta' : Item(contents="This is the file 'beta'.\n"), |
| }) |
| expected_status = wc.State(A_COPY_B_path, { |
| '' : Item(status=' M', wc_rev=3), |
| 'lambda' : Item(status='M ', wc_rev=3), |
| 'F' : Item(status=' ', wc_rev=3), |
| 'E' : Item(status=' ', wc_rev=3), |
| 'E/alpha' : Item(status=' ', wc_rev=3), |
| 'E/beta' : Item(status=' ', wc_rev=3), |
| }) |
| expected_skip = wc.State(A_COPY_B_path, {}) |
| |
| svntest.actions.run_and_verify_merge(A_COPY_B_path, 1, 3, |
| A_B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| # Now, revert lambda and repeat the merge. Nothing should happen. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', |
| A_COPY_lambda_path) |
| expected_output.remove('lambda') |
| expected_disk.tweak('lambda', contents="This is the file 'lambda'.\n") |
| expected_status.tweak('lambda', status=' ') |
| expected_mergeinfo_output = wc.State(A_COPY_B_path, { |
| '' : Item(status=' G'), |
| }) |
| svntest.actions.run_and_verify_merge(A_COPY_B_path, 1, 3, |
| A_B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| # Now, try the merge again with --ignore-ancestry. We should get |
| # lambda re-modified. */ |
| expected_output = wc.State(A_COPY_B_path, { |
| 'lambda' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_B_path, {}) |
| expected_elision_output = wc.State(A_COPY_B_path, { |
| }) |
| expected_disk.tweak('lambda', |
| contents="This is the file 'lambda' modified.\n") |
| expected_status.tweak('lambda', status='M ') |
| svntest.actions.run_and_verify_merge(A_COPY_B_path, 1, 3, |
| A_B_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| '--ignore-ancestry', A_COPY_B_path) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3032) |
| def merge_from_renamed_branch_fails_while_avoiding_repeat_merge(sbox): |
| "merge from renamed branch" |
| #Copy A/C to A/COPY_C results in r2. |
| #Rename A/COPY_C to A/RENAMED_C results in r3. |
| #Add A/RENAMED_C/file1 and commit, results in r4. |
| #Change A/RENAMED_C/file1 and commit, results in r5. |
| #Merge r4 from A/RENAMED_C to A/C |
| #Merge r2:5 from A/RENAMED_C to A/C <-- This fails tracked via #3032. |
| |
| ## See https://issues.apache.org/jira/browse/SVN-3032. ## |
| |
| # Create a WC with a single branch |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| # Some paths we'll care about |
| A_C_url = sbox.repo_url + '/A/C' |
| A_COPY_C_url = sbox.repo_url + '/A/COPY_C' |
| A_RENAMED_C_url = sbox.repo_url + '/A/RENAMED_C' |
| A_C_path = sbox.ospath('A/C') |
| A_RENAMED_C_path = sbox.ospath('A/RENAMED_C') |
| A_RENAMED_C_file1_path = sbox.ospath('A/RENAMED_C/file1') |
| |
| svntest.main.run_svn(None, 'cp', A_C_url, A_COPY_C_url, '-m', 'copy...') |
| svntest.main.run_svn(None, 'mv', A_COPY_C_url, A_RENAMED_C_url, '-m', |
| 'rename...') |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| svntest.main.file_write(A_RENAMED_C_file1_path, "This is the file1.\n") |
| svntest.main.run_svn(None, 'add', A_RENAMED_C_file1_path) |
| expected_output = wc.State(A_RENAMED_C_path, { |
| 'file1' : Item(verb='Adding'), |
| }) |
| expected_status = wc.State(A_RENAMED_C_path, { |
| '' : Item(status=' ', wc_rev=3), |
| 'file1' : Item(status=' ', wc_rev=4), |
| }) |
| svntest.actions.run_and_verify_commit(A_RENAMED_C_path, expected_output, |
| expected_status) |
| svntest.main.file_write(A_RENAMED_C_file1_path, |
| "This is the file1 modified.\n") |
| expected_output = wc.State(A_RENAMED_C_path, { |
| 'file1' : Item(verb='Sending'), |
| }) |
| expected_status.tweak('file1', wc_rev=5) |
| svntest.actions.run_and_verify_commit(A_RENAMED_C_path, expected_output, |
| expected_status) |
| |
| expected_skip = wc.State(A_C_path, {}) |
| expected_output = wc.State(A_C_path, { |
| 'file1' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_C_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_C_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/RENAMED_C:4'}), |
| 'file1' : Item("This is the file1.\n"), |
| }) |
| expected_status = wc.State(A_C_path, { |
| '' : Item(status=' M', wc_rev=3), |
| 'file1' : Item(status='A ', wc_rev='-', copied='+'), |
| }) |
| svntest.actions.run_and_verify_merge(A_C_path, 3, 4, |
| A_RENAMED_C_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| expected_output = wc.State(A_C_path, { |
| 'file1' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_C_path, { |
| '' : Item(status=' G'), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/RENAMED_C:3-5'}), |
| 'file1' : Item("This is the file1 modified.\n"), |
| }) |
| expected_status = wc.State(A_C_path, { |
| '' : Item(status=' M', wc_rev=3), |
| 'file1' : Item(status='A ', wc_rev='-', copied='+'), |
| }) |
| svntest.actions.run_and_verify_merge(A_C_path, 2, 5, |
| A_RENAMED_C_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| #---------------------------------------------------------------------- |
| # Test for part of issue #2877: 'do subtree merge only if subtree has |
| # explicit mergeinfo set and exists in the merge source' |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(2877) |
| def merge_source_normalization_and_subtree_merges(sbox): |
| "normalized mergeinfo is recorded on subtrees" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| D_COPY_path = sbox.ospath('A_COPY/D') |
| G_COPY_path = sbox.ospath('A_COPY/D/G') |
| |
| # Use our helper to copy 'A' to 'A_COPY' then make some changes under 'A' |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # r7 - Move A to A_MOVED |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 7.\n'], |
| [], 'mv', '-m', 'mv A to A_MOVED', |
| sbox.repo_url + '/A', |
| sbox.repo_url + '/A_MOVED') |
| wc_status.add({ |
| 'A_MOVED/B' : Item(), |
| 'A_MOVED/B/lambda' : Item(), |
| 'A_MOVED/B/E' : Item(), |
| 'A_MOVED/B/E/alpha' : Item(), |
| 'A_MOVED/B/E/beta' : Item(), |
| 'A_MOVED/B/F' : Item(), |
| 'A_MOVED/mu' : Item(), |
| 'A_MOVED/C' : Item(), |
| 'A_MOVED/D' : Item(), |
| 'A_MOVED/D/gamma' : Item(), |
| 'A_MOVED/D/G' : Item(), |
| 'A_MOVED/D/G/pi' : Item(), |
| 'A_MOVED/D/G/rho' : Item(), |
| 'A_MOVED/D/G/tau' : Item(), |
| 'A_MOVED/D/H' : Item(), |
| 'A_MOVED/D/H/chi' : Item(), |
| 'A_MOVED/D/H/omega' : Item(), |
| 'A_MOVED/D/H/psi' : Item(), |
| 'A_MOVED' : Item()}) |
| wc_status.remove('A', 'A/B', 'A/B/lambda', 'A/B/E', 'A/B/E/alpha', |
| 'A/B/E/beta', 'A/B/F', 'A/mu', 'A/C', 'A/D', |
| 'A/D/gamma', 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', |
| 'A/D/G/tau' , 'A/D/H', 'A/D/H/chi', 'A/D/H/omega', |
| 'A/D/H/psi') |
| wc_status.tweak(status=' ', wc_rev=7) |
| |
| # Update the WC |
| svntest.actions.run_and_verify_svn(None, [], |
| 'update', wc_dir) |
| |
| # r8 - Make a text mod to 'A_MOVED/D/G/tau' |
| svntest.main.file_write(sbox.ospath('A_MOVED/D/G/tau'), |
| "New content") |
| expected_output = wc.State(wc_dir, |
| {'A_MOVED/D/G/tau' : Item(verb='Sending')}) |
| wc_status.tweak('A_MOVED/D/G/tau', status=' ', wc_rev=8) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Merge -c4 URL/A_MOVED/D/G A_COPY/D/G. |
| # |
| # A_MOVED/D/G doesn't exist at r3:4, it's still A/D/G, |
| # so the merge source normalization logic should set |
| # mergeinfo of '/A/D/G:4' on A_COPY/D/G, *not* 'A_MOVED/D/G:4', |
| # see issue #2953. |
| expected_output = wc.State(G_COPY_path, { |
| 'rho' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(G_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(G_COPY_path, { |
| }) |
| expected_status = wc.State(G_COPY_path, { |
| '' : Item(status=' M', wc_rev=7), |
| 'pi' : Item(status=' ', wc_rev=7), |
| 'rho' : Item(status='M ', wc_rev=7), |
| 'tau' : Item(status=' ', wc_rev=7), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/G:4'}), |
| 'pi' : Item("This is the file 'pi'.\n"), |
| 'rho' : Item("New content"), |
| 'tau' : Item("This is the file 'tau'.\n"), |
| }) |
| expected_skip = wc.State(G_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(G_COPY_path, '3', '4', |
| sbox.repo_url + '/A_MOVED/D/G', |
| None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Merge -c8 URL/A_MOVED/D A_COPY/D. |
| # |
| # The merge target A_COPY/D and the subtree at A_COPY/D/G |
| # should both have their mergeinfo updated with r8 |
| # from A_MOVED_D, see reopened issue #2877. |
| expected_output = wc.State(D_COPY_path, { |
| 'G/tau' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(D_COPY_path, { |
| '' : Item(status=' U'), |
| 'G' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(D_COPY_path, { |
| }) |
| expected_status = wc.State(D_COPY_path, { |
| '' : Item(status=' M', wc_rev=7), |
| 'G' : Item(status=' M', wc_rev=7), |
| 'G/pi' : Item(status=' ', wc_rev=7), |
| 'G/rho' : Item(status='M ', wc_rev=7), |
| 'G/tau' : Item(status='M ', wc_rev=7), |
| 'H' : Item(status=' ', wc_rev=7), |
| 'H/chi' : Item(status=' ', wc_rev=7), |
| 'H/psi' : Item(status=' ', wc_rev=7), |
| 'H/omega' : Item(status=' ', wc_rev=7), |
| 'gamma' : Item(status=' ', wc_rev=7), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A_MOVED/D:8'}), |
| 'G' : Item(props={SVN_PROP_MERGEINFO : |
| '/A/D/G:4\n/A_MOVED/D/G:8'}), |
| 'G/pi' : Item("This is the file 'pi'.\n"), |
| 'G/rho' : Item("New content"), |
| 'G/tau' : Item("New content"), |
| 'H' : Item(), |
| 'H/chi' : Item("This is the file 'chi'.\n"), |
| 'H/psi' : Item("This is the file 'psi'.\n"), |
| 'H/omega' : Item("This is the file 'omega'.\n"), |
| 'gamma' : Item("This is the file 'gamma'.\n") |
| }) |
| expected_skip = wc.State(D_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(D_COPY_path, '7', '8', |
| sbox.repo_url + '/A_MOVED/D', |
| None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Tests for issue #3067: 'subtrees with intersecting mergeinfo, that don't |
| # exist at the start of a merge range shouldn't break the merge' |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3067) |
| def new_subtrees_should_not_break_merge(sbox): |
| "subtrees added after start of merge range are ok" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| D_COPY_path = sbox.ospath('A_COPY/D') |
| nu_path = sbox.ospath('A/D/H/nu') |
| nu_COPY_path = sbox.ospath('A_COPY/D/H/nu') |
| rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') |
| H_COPY_path = sbox.ospath('A_COPY/D/H') |
| |
| # Create 'A/D/H/nu', commit it as r7, make a text mod to it in r8. |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| expected_output = wc.State(wc_dir, {'A/D/H/nu' : Item(verb='Adding')}) |
| wc_status.add({'A/D/H/nu' : Item(status=' ', wc_rev=7)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| svntest.main.file_write(nu_path, "New content") |
| expected_output = wc.State(wc_dir, {'A/D/H/nu' : Item(verb='Sending')}) |
| wc_status.tweak('A/D/H/nu', wc_rev=8) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Merge r7 to A_COPY/D/H, then, so it has it's own explicit mergeinfo, |
| # then merge r8 to A_COPY/D/H/nu so it too has explicit mergeinfo. |
| expected_output = wc.State(H_COPY_path, { |
| 'nu' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(H_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(H_COPY_path, { |
| }) |
| expected_status = wc.State(H_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'psi' : Item(status=' ', wc_rev=2), |
| 'omega' : Item(status=' ', wc_rev=2), |
| 'chi' : Item(status=' ', wc_rev=2), |
| 'nu' : Item(status='A ', copied='+', wc_rev='-'), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:7'}), |
| 'psi' : Item("This is the file 'psi'.\n"), |
| 'omega' : Item("This is the file 'omega'.\n"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| 'nu' : Item("This is the file 'nu'.\n"), |
| }) |
| expected_skip = wc.State(H_COPY_path, {}) |
| svntest.actions.run_and_verify_merge(H_COPY_path, '6', '7', |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[8]], |
| ['U ' + nu_COPY_path + '\n', |
| ' G ' + nu_COPY_path + '\n']), |
| [], 'merge', '-c8', '--allow-mixed-revisions', |
| sbox.repo_url + '/A/D/H/nu', nu_COPY_path) |
| |
| # Merge -r4:6 to A_COPY, then reverse merge r6 from A_COPY/D. |
| expected_output = wc.State(A_COPY_path, { |
| 'B/E/beta' : Item(status='U '), |
| 'D/H/omega': Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'B' : Item(status=' ', wc_rev=2), |
| 'mu' : Item(status=' ', wc_rev=2), |
| 'B/E' : Item(status=' ', wc_rev=2), |
| 'B/E/alpha' : Item(status=' ', wc_rev=2), |
| 'B/E/beta' : Item(status='M ', wc_rev=2), |
| 'B/lambda' : Item(status=' ', wc_rev=2), |
| 'B/F' : Item(status=' ', wc_rev=2), |
| 'C' : Item(status=' ', wc_rev=2), |
| 'D' : Item(status=' ', wc_rev=2), |
| 'D/G' : Item(status=' ', wc_rev=2), |
| 'D/G/pi' : Item(status=' ', wc_rev=2), |
| 'D/G/rho' : Item(status=' ', wc_rev=2), |
| 'D/G/tau' : Item(status=' ', wc_rev=2), |
| 'D/gamma' : Item(status=' ', wc_rev=2), |
| 'D/H' : Item(status=' M', wc_rev=2), |
| 'D/H/chi' : Item(status=' ', wc_rev=2), |
| 'D/H/psi' : Item(status=' ', wc_rev=2), |
| 'D/H/omega' : Item(status='M ', wc_rev=2), |
| 'D/H/nu' : Item(status='A ', copied='+', wc_rev='-'), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-6'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-7'}), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("New content"), |
| 'D/H/nu' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/D/H/nu:7-8'}), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '4', '6', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| expected_output = wc.State(D_COPY_path, { |
| 'H/omega': Item(status='G '), |
| }) |
| expected_mergeinfo_output = wc.State(D_COPY_path, { |
| '' : Item(status=' G'), |
| 'H' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(D_COPY_path, { |
| }) |
| expected_status = wc.State(D_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'G' : Item(status=' ', wc_rev=2), |
| 'G/pi' : Item(status=' ', wc_rev=2), |
| 'G/rho' : Item(status=' ', wc_rev=2), |
| 'G/tau' : Item(status=' ', wc_rev=2), |
| 'gamma' : Item(status=' ', wc_rev=2), |
| 'H' : Item(status=' M', wc_rev=2), |
| 'H/chi' : Item(status=' ', wc_rev=2), |
| 'H/psi' : Item(status=' ', wc_rev=2), |
| 'H/omega' : Item(status=' ', wc_rev=2), |
| 'H/nu' : Item(status='A ', copied='+', wc_rev='-'), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D:5'}), |
| 'G/pi' : Item("This is the file 'pi'.\n"), |
| 'G/rho' : Item("This is the file 'rho'.\n"), |
| 'G/tau' : Item("This is the file 'tau'.\n"), |
| 'gamma' : Item("This is the file 'gamma'.\n"), |
| 'H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5,7'}), |
| 'H/chi' : Item("This is the file 'chi'.\n"), |
| 'H/psi' : Item("This is the file 'psi'.\n"), |
| 'H/omega' : Item("This is the file 'omega'.\n"), |
| 'H/nu' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/D/H/nu:7-8'}), |
| }) |
| expected_skip = wc.State(D_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(D_COPY_path, '6', '5', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| # Now once again merge r6 to A_COPY. A_COPY already has r6 in its mergeinfo |
| # so we expect only subtree merges on A_COPY/D, A_COPY_D_H, and |
| # A_COPY/D/H/nu. The fact that A/D/H/nu doesn't exist at r6 should not cause |
| # the merge to fail -- see |
| # https://issues.apache.org/jira/browse/SVN-3067#desc7. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/H/omega': Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' G'), |
| 'D' : Item(status=' G'), |
| 'D/H' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| 'D' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'B' : Item(status=' ', wc_rev=2), |
| 'mu' : Item(status=' ', wc_rev=2), |
| 'B/E' : Item(status=' ', wc_rev=2), |
| 'B/E/alpha' : Item(status=' ', wc_rev=2), |
| 'B/E/beta' : Item(status='M ', wc_rev=2), |
| 'B/lambda' : Item(status=' ', wc_rev=2), |
| 'B/F' : Item(status=' ', wc_rev=2), |
| 'C' : Item(status=' ', wc_rev=2), |
| 'D' : Item(status=' ', wc_rev=2), |
| 'D/G' : Item(status=' ', wc_rev=2), |
| 'D/G/pi' : Item(status=' ', wc_rev=2), |
| 'D/G/rho' : Item(status=' ', wc_rev=2), |
| 'D/G/tau' : Item(status=' ', wc_rev=2), |
| 'D/gamma' : Item(status=' ', wc_rev=2), |
| 'D/H' : Item(status=' M', wc_rev=2), |
| 'D/H/chi' : Item(status=' ', wc_rev=2), |
| 'D/H/psi' : Item(status=' ', wc_rev=2), |
| 'D/H/omega' : Item(status='M ', wc_rev=2), |
| 'D/H/nu' : Item(status='A ', copied='+', wc_rev='-'), |
| }) |
| expected_disk_1 = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:5-6'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), # Mergeinfo elides to 'A_COPY' |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-7'}), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("New content"), |
| 'D/H/nu' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/D/H/nu:7-8'}), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '5', '6', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk_1, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Commit this merge as r9. |
| # |
| # Update the wc first to make setting the expected status a bit easier. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], |
| 'up', wc_dir) |
| wc_status.tweak(wc_rev=8) |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/B/E/beta' : Item(verb='Sending'), |
| 'A_COPY/D/H' : Item(verb='Sending'), |
| 'A_COPY/D/H/nu' : Item(verb='Adding'), |
| 'A_COPY/D/H/omega' : Item(verb='Sending'), |
| }) |
| wc_status.tweak('A_COPY', |
| 'A_COPY/B/E/beta', |
| 'A_COPY/D/H', |
| 'A_COPY/D/H/omega', |
| wc_rev=9) |
| wc_status.add({'A_COPY/D/H/nu' : Item(status=' ', wc_rev=9)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| # Update the WC. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], |
| 'up', wc_dir) |
| wc_status.tweak(wc_rev=9) |
| |
| # Yet another test for issue #3067. Merge -rX:Y, where X>Y (reverse merge) |
| # and the merge target has a subtree that came into existence at some rev |
| # N where X < N < Y. This merge should simply delete the subtree. |
| # |
| # For this test merge -r9:2 to A_COPY. This should revert all the merges |
| # done thus far, leaving the tree rooted at A_COPY with no explicit |
| # mergeinfo. |
| expected_output = wc.State(A_COPY_path, { |
| 'B/E/beta' : Item(status='U '), |
| 'D/H/omega': Item(status='U '), |
| 'D/H/nu' : Item(status='D '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H': Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H': Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=9), |
| 'B' : Item(status=' ', wc_rev=9), |
| 'mu' : Item(status=' ', wc_rev=9), |
| 'B/E' : Item(status=' ', wc_rev=9), |
| 'B/E/alpha' : Item(status=' ', wc_rev=9), |
| 'B/E/beta' : Item(status='M ', wc_rev=9), |
| 'B/lambda' : Item(status=' ', wc_rev=9), |
| 'B/F' : Item(status=' ', wc_rev=9), |
| 'C' : Item(status=' ', wc_rev=9), |
| 'D' : Item(status=' ', wc_rev=9), |
| 'D/G' : Item(status=' ', wc_rev=9), |
| 'D/G/pi' : Item(status=' ', wc_rev=9), |
| 'D/G/rho' : Item(status=' ', wc_rev=9), |
| 'D/G/tau' : Item(status=' ', wc_rev=9), |
| 'D/gamma' : Item(status=' ', wc_rev=9), |
| 'D/H' : Item(status=' M', wc_rev=9), |
| 'D/H/chi' : Item(status=' ', wc_rev=9), |
| 'D/H/psi' : Item(status=' ', wc_rev=9), |
| 'D/H/omega' : Item(status='M ', wc_rev=9), |
| 'D/H/nu' : Item(status='D ', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '9', '2', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Revert the previous merge, then merge r4 to A_COPY/D/G/rho. Commit |
| # this merge as r10. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[4]], |
| ['U ' + rho_COPY_path + '\n', |
| ' G ' + rho_COPY_path + '\n']), |
| [], 'merge', '-c4', sbox.repo_url + '/A/D/G/rho', rho_COPY_path) |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY/D/G/rho' : Item(verb='Sending'),}) |
| wc_status.tweak('A_COPY/D/G/rho', wc_rev=10) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(10), [], |
| 'up', wc_dir) |
| wc_status.tweak(wc_rev=10) |
| |
| # Yet another test for issue #3067. Merge -rX:Y, where X>Y (reverse merge) |
| # and the merge target has a subtree that doesn't exist in the merge source |
| # between X and Y. This merge should no effect on that subtree. |
| # |
| # Specifically, merge -c4 to A_COPY. This should revert the previous merge |
| # of r4 directly to A_COPY/D/G/rho. The subtree A_COPY/D/H/nu, whose merge |
| # source A/D/H/nu doesn't in r4:3, shouldn't be affected nor should it break |
| # the merge editor. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/G/rho': Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/G/rho' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| 'D/G/rho' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' ', wc_rev=10), |
| 'B' : Item(status=' ', wc_rev=10), |
| 'mu' : Item(status=' ', wc_rev=10), |
| 'B/E' : Item(status=' ', wc_rev=10), |
| 'B/E/alpha' : Item(status=' ', wc_rev=10), |
| 'B/E/beta' : Item(status=' ', wc_rev=10), |
| 'B/lambda' : Item(status=' ', wc_rev=10), |
| 'B/F' : Item(status=' ', wc_rev=10), |
| 'C' : Item(status=' ', wc_rev=10), |
| 'D' : Item(status=' ', wc_rev=10), |
| 'D/G' : Item(status=' ', wc_rev=10), |
| 'D/G/pi' : Item(status=' ', wc_rev=10), |
| 'D/G/rho' : Item(status='MM', wc_rev=10), |
| 'D/G/tau' : Item(status=' ', wc_rev=10), |
| 'D/gamma' : Item(status=' ', wc_rev=10), |
| 'D/H' : Item(status=' ', wc_rev=10), |
| 'D/H/chi' : Item(status=' ', wc_rev=10), |
| 'D/H/psi' : Item(status=' ', wc_rev=10), |
| 'D/H/omega' : Item(status=' ', wc_rev=10), |
| 'D/H/nu' : Item(status=' ', wc_rev=10), |
| }) |
| # Use expected_disk_1 from above since we should be |
| # returning to that state. |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '4', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk_1, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def dont_add_mergeinfo_from_own_history(sbox): |
| "cyclic merges don't add mergeinfo from own history" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| A_path = sbox.ospath('A') |
| A_MOVED_path = sbox.ospath('A_MOVED') |
| mu_path = sbox.ospath('A/mu') |
| mu_MOVED_path = sbox.ospath('A_MOVED/mu') |
| A_COPY_path = sbox.ospath('A_COPY') |
| mu_COPY_path = sbox.ospath('A_COPY/mu') |
| |
| # Merge r3 from 'A' to 'A_COPY', make a text mod to 'A_COPY/mu' and |
| # commit both as r7. This results in mergeinfo of '/A:3' on 'A_COPY'. |
| # Then merge r7 from 'A_COPY' to 'A'. This attempts to add the mergeinfo |
| # '/A:3' to 'A', but that is self-referrential and should be filtered out, |
| # leaving only the mergeinfo '/A_COPY:7' on 'A'. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/H/psi' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_A_COPY_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'B' : Item(status=' ', wc_rev=2), |
| 'mu' : Item(status=' ', wc_rev=2), |
| 'B/E' : Item(status=' ', wc_rev=2), |
| 'B/E/alpha' : Item(status=' ', wc_rev=2), |
| 'B/E/beta' : Item(status=' ', wc_rev=2), |
| 'B/lambda' : Item(status=' ', wc_rev=2), |
| 'B/F' : Item(status=' ', wc_rev=2), |
| 'C' : Item(status=' ', wc_rev=2), |
| 'D' : Item(status=' ', wc_rev=2), |
| 'D/G' : Item(status=' ', wc_rev=2), |
| 'D/G/pi' : Item(status=' ', wc_rev=2), |
| 'D/G/rho' : Item(status=' ', wc_rev=2), |
| 'D/G/tau' : Item(status=' ', wc_rev=2), |
| 'D/gamma' : Item(status=' ', wc_rev=2), |
| 'D/H' : Item(status=' ', wc_rev=2), |
| 'D/H/chi' : Item(status=' ', wc_rev=2), |
| 'D/H/psi' : Item(status='M ', wc_rev=2), |
| 'D/H/omega' : Item(status=' ', wc_rev=2), |
| }) |
| expected_A_COPY_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_A_COPY_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_A_COPY_disk, |
| expected_A_COPY_status, |
| expected_A_COPY_skip, |
| check_props=True) |
| |
| # Change 'A_COPY/mu' |
| svntest.main.file_write(mu_COPY_path, "New content") |
| |
| # Commit r7 |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending'), |
| 'A_COPY/mu' : Item(verb='Sending'), |
| }) |
| wc_status.tweak('A_COPY', 'A_COPY/D/H/psi', 'A_COPY/mu', wc_rev=7) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| wc_status) |
| |
| # Merge r7 back to the 'A' |
| expected_output = wc.State(A_path, { |
| 'mu' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_path, { |
| }) |
| expected_A_status = wc.State(A_path, { |
| '' : Item(status=' M', wc_rev=1), |
| 'B' : Item(status=' ', wc_rev=1), |
| 'mu' : Item(status='M ', wc_rev=1), |
| 'B/E' : Item(status=' ', wc_rev=1), |
| 'B/E/alpha' : Item(status=' ', wc_rev=1), |
| 'B/E/beta' : Item(status=' ', wc_rev=5), |
| 'B/lambda' : Item(status=' ', wc_rev=1), |
| 'B/F' : Item(status=' ', wc_rev=1), |
| 'C' : Item(status=' ', wc_rev=1), |
| 'D' : Item(status=' ', wc_rev=1), |
| 'D/G' : Item(status=' ', wc_rev=1), |
| 'D/G/pi' : Item(status=' ', wc_rev=1), |
| 'D/G/rho' : Item(status=' ', wc_rev=4), |
| 'D/G/tau' : Item(status=' ', wc_rev=1), |
| 'D/gamma' : Item(status=' ', wc_rev=1), |
| 'D/H' : Item(status=' ', wc_rev=1), |
| 'D/H/chi' : Item(status=' ', wc_rev=1), |
| 'D/H/psi' : Item(status=' ', wc_rev=3), |
| 'D/H/omega' : Item(status=' ', wc_rev=6), |
| }) |
| expected_A_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:7'}), |
| 'B' : Item(), |
| 'mu' : Item("New content"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_A_skip = wc.State(A_path, {}) |
| svntest.actions.run_and_verify_merge(A_path, '6', '7', |
| sbox.repo_url + '/A_COPY', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_A_disk, |
| expected_A_status, |
| expected_A_skip, |
| [], True, False, |
| '--allow-mixed-revisions', A_path) |
| |
| # Revert all local mods |
| svntest.actions.run_and_verify_svn(["Reverted '" + A_path + "'\n", |
| "Reverted '" + mu_path + "'\n"], |
| [], 'revert', '-R', wc_dir) |
| |
| # Move 'A' to 'A_MOVED' and once again merge r7 from 'A_COPY', this time |
| # to 'A_MOVED'. This attempts to add the mergeinfo '/A:3' to |
| # 'A_MOVED', but 'A_MOVED@3' is 'A', so again this mergeinfo is filtered |
| # out, leaving the only the mergeinfo created from the merge itself: |
| # '/A_COPY:7'. |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 8.\n'], |
| [], 'move', |
| sbox.repo_url + '/A', |
| sbox.repo_url + '/A_MOVED', |
| '-m', 'Copy A to A_MOVED') |
| wc_status.remove('A', 'A/B', 'A/B/lambda', 'A/B/E', 'A/B/E/alpha', |
| 'A/B/E/beta', 'A/B/F', 'A/mu', 'A/C', 'A/D', 'A/D/gamma', 'A/D/G', |
| 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau', 'A/D/H', 'A/D/H/chi', |
| 'A/D/H/omega', 'A/D/H/psi') |
| wc_status.add({ |
| 'A_MOVED' : Item(), |
| 'A_MOVED/B' : Item(), |
| 'A_MOVED/B/lambda' : Item(), |
| 'A_MOVED/B/E' : Item(), |
| 'A_MOVED/B/E/alpha' : Item(), |
| 'A_MOVED/B/E/beta' : Item(), |
| 'A_MOVED/B/F' : Item(), |
| 'A_MOVED/mu' : Item(), |
| 'A_MOVED/C' : Item(), |
| 'A_MOVED/D' : Item(), |
| 'A_MOVED/D/gamma' : Item(), |
| 'A_MOVED/D/G' : Item(), |
| 'A_MOVED/D/G/pi' : Item(), |
| 'A_MOVED/D/G/rho' : Item(), |
| 'A_MOVED/D/G/tau' : Item(), |
| 'A_MOVED/D/H' : Item(), |
| 'A_MOVED/D/H/chi' : Item(), |
| 'A_MOVED/D/H/omega' : Item(), |
| 'A_MOVED/D/H/psi' : Item(), |
| }) |
| wc_status.tweak(wc_rev=8, status=' ') |
| wc_disk.remove('A', 'A/B', 'A/B/lambda', 'A/B/E', 'A/B/E/alpha', |
| 'A/B/E/beta', 'A/B/F', 'A/mu', 'A/C', 'A/D', 'A/D/gamma', |
| 'A/D/G', 'A/D/G/pi', 'A/D/G/rho', 'A/D/G/tau', 'A/D/H', |
| 'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi' ) |
| wc_disk.add({ |
| 'A_MOVED' : Item(), |
| 'A_MOVED/B' : Item(), |
| 'A_MOVED/B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'A_MOVED/B/E' : Item(), |
| 'A_MOVED/B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'A_MOVED/B/E/beta' : Item("New content"), |
| 'A_MOVED/B/F' : Item(), |
| 'A_MOVED/mu' : Item("This is the file 'mu'.\n"), |
| 'A_MOVED/C' : Item(), |
| 'A_MOVED/D' : Item(), |
| 'A_MOVED/D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'A_MOVED/D/G' : Item(), |
| 'A_MOVED/D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'A_MOVED/D/G/rho' : Item("New content"), |
| 'A_MOVED/D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'A_MOVED/D/H' : Item(), |
| 'A_MOVED/D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'A_MOVED/D/H/omega' : Item("New content"), |
| 'A_MOVED/D/H/psi' : Item("New content"), |
| }) |
| wc_disk.tweak('A_COPY/D/H/psi', 'A_COPY/mu', contents='New content') |
| wc_disk.tweak('A_COPY', props={SVN_PROP_MERGEINFO : '/A:3'}) |
| expected_output = wc.State(wc_dir, { |
| 'A' : Item(status='D '), |
| 'A_MOVED' : Item(status='A '), |
| 'A_MOVED/B' : Item(status='A '), |
| 'A_MOVED/B/lambda' : Item(status='A '), |
| 'A_MOVED/B/E' : Item(status='A '), |
| 'A_MOVED/B/E/alpha' : Item(status='A '), |
| 'A_MOVED/B/E/beta' : Item(status='A '), |
| 'A_MOVED/B/F' : Item(status='A '), |
| 'A_MOVED/mu' : Item(status='A '), |
| 'A_MOVED/C' : Item(status='A '), |
| 'A_MOVED/D' : Item(status='A '), |
| 'A_MOVED/D/gamma' : Item(status='A '), |
| 'A_MOVED/D/G' : Item(status='A '), |
| 'A_MOVED/D/G/pi' : Item(status='A '), |
| 'A_MOVED/D/G/rho' : Item(status='A '), |
| 'A_MOVED/D/G/tau' : Item(status='A '), |
| 'A_MOVED/D/H' : Item(status='A '), |
| 'A_MOVED/D/H/chi' : Item(status='A '), |
| 'A_MOVED/D/H/omega' : Item(status='A '), |
| 'A_MOVED/D/H/psi' : Item(status='A ') |
| }) |
| svntest.actions.run_and_verify_update(wc_dir, |
| expected_output, |
| wc_disk, |
| wc_status, |
| check_props=True) |
| |
| expected_output = wc.State(A_MOVED_path, { |
| 'mu' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_MOVED_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_MOVED_path, { |
| }) |
| expected_A_status = wc.State(A_MOVED_path, { |
| '' : Item(status=' M', wc_rev=8), |
| 'B' : Item(status=' ', wc_rev=8), |
| 'mu' : Item(status='M ', wc_rev=8), |
| 'B/E' : Item(status=' ', wc_rev=8), |
| 'B/E/alpha' : Item(status=' ', wc_rev=8), |
| 'B/E/beta' : Item(status=' ', wc_rev=8), |
| 'B/lambda' : Item(status=' ', wc_rev=8), |
| 'B/F' : Item(status=' ', wc_rev=8), |
| 'C' : Item(status=' ', wc_rev=8), |
| 'D' : Item(status=' ', wc_rev=8), |
| 'D/G' : Item(status=' ', wc_rev=8), |
| 'D/G/pi' : Item(status=' ', wc_rev=8), |
| 'D/G/rho' : Item(status=' ', wc_rev=8), |
| 'D/G/tau' : Item(status=' ', wc_rev=8), |
| 'D/gamma' : Item(status=' ', wc_rev=8), |
| 'D/H' : Item(status=' ', wc_rev=8), |
| 'D/H/chi' : Item(status=' ', wc_rev=8), |
| 'D/H/psi' : Item(status=' ', wc_rev=8), |
| 'D/H/omega' : Item(status=' ', wc_rev=8), |
| }) |
| # We can reuse expected_A_disk from above without change. |
| svntest.actions.run_and_verify_merge(A_MOVED_path, '6', '7', |
| sbox.repo_url + '/A_COPY', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_A_disk, |
| expected_A_status, |
| expected_A_skip, |
| check_props=True) |
| |
| # Revert all local mods |
| svntest.actions.run_and_verify_svn(["Reverted '" + A_MOVED_path + "'\n", |
| "Reverted '" + mu_MOVED_path + "'\n"], |
| [], 'revert', '-R', wc_dir) |
| |
| # Create a new 'A' unrelated to the old 'A' which was moved. Then merge |
| # r7 from 'A_COPY' to this new 'A'. Since the new 'A' shares no history |
| # with the mergeinfo 'A@3', the mergeinfo '/A:3' is added and when combined |
| # with the mergeinfo created from the merge should result in |
| # '/A:3\n/A_COPY:7' |
| # |
| # Create the new 'A' by exporting the old 'A@1'. |
| expected_output = svntest.verify.UnorderedOutput( |
| ["A " + sbox.ospath('A') + "\n", |
| "A " + sbox.ospath('A/B') + "\n", |
| "A " + sbox.ospath('A/B/lambda') + "\n", |
| "A " + sbox.ospath('A/B/E') + "\n", |
| "A " + sbox.ospath('A/B/E/alpha') + "\n", |
| "A " + sbox.ospath('A/B/E/beta') + "\n", |
| "A " + sbox.ospath('A/B/F') + "\n", |
| "A " + sbox.ospath('A/mu') + "\n", |
| "A " + sbox.ospath('A/C') + "\n", |
| "A " + sbox.ospath('A/D') + "\n", |
| "A " + sbox.ospath('A/D/gamma') + "\n", |
| "A " + sbox.ospath('A/D/G') + "\n", |
| "A " + sbox.ospath('A/D/G/pi') + "\n", |
| "A " + sbox.ospath('A/D/G/rho') + "\n", |
| "A " + sbox.ospath('A/D/G/tau') + "\n", |
| "A " + sbox.ospath('A/D/H') + "\n", |
| "A " + sbox.ospath('A/D/H/chi') + "\n", |
| "A " + sbox.ospath('A/D/H/omega') + "\n", |
| "A " + sbox.ospath('A/D/H/psi') + "\n", |
| "Exported revision 1.\n",] |
| ) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'export', sbox.repo_url + '/A@1', |
| A_path) |
| expected_output = svntest.verify.UnorderedOutput( |
| ["A " + sbox.ospath('A') + "\n", |
| "A " + sbox.ospath('A/B') + "\n", |
| "A " + sbox.ospath('A/B/lambda') + "\n", |
| "A " + sbox.ospath('A/B/E') + "\n", |
| "A " + sbox.ospath('A/B/E/alpha') + "\n", |
| "A " + sbox.ospath('A/B/E/beta') + "\n", |
| "A " + sbox.ospath('A/B/F') + "\n", |
| "A " + sbox.ospath('A/mu') + "\n", |
| "A " + sbox.ospath('A/C') + "\n", |
| "A " + sbox.ospath('A/D') + "\n", |
| "A " + sbox.ospath('A/D/gamma') + "\n", |
| "A " + sbox.ospath('A/D/G') + "\n", |
| "A " + sbox.ospath('A/D/G/pi') + "\n", |
| "A " + sbox.ospath('A/D/G/rho') + "\n", |
| "A " + sbox.ospath('A/D/G/tau') + "\n", |
| "A " + sbox.ospath('A/D/H') + "\n", |
| "A " + sbox.ospath('A/D/H/chi') + "\n", |
| "A " + sbox.ospath('A/D/H/omega') + "\n", |
| "A " + sbox.ospath('A/D/H/psi') + "\n",] |
| ) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'add', A_path) |
| # Commit the new 'A' as r9 |
| expected_output = wc.State(wc_dir, { |
| 'A' : Item(verb='Adding'), |
| 'A/B' : Item(verb='Adding'), |
| 'A/mu' : Item(verb='Adding'), |
| 'A/B/E' : Item(verb='Adding'), |
| 'A/B/E/alpha' : Item(verb='Adding'), |
| 'A/B/E/beta' : Item(verb='Adding'), |
| 'A/B/lambda' : Item(verb='Adding'), |
| 'A/B/F' : Item(verb='Adding'), |
| 'A/C' : Item(verb='Adding'), |
| 'A/D' : Item(verb='Adding'), |
| 'A/D/G' : Item(verb='Adding'), |
| 'A/D/G/pi' : Item(verb='Adding'), |
| 'A/D/G/rho' : Item(verb='Adding'), |
| 'A/D/G/tau' : Item(verb='Adding'), |
| 'A/D/gamma' : Item(verb='Adding'), |
| 'A/D/H' : Item(verb='Adding'), |
| 'A/D/H/chi' : Item(verb='Adding'), |
| 'A/D/H/psi' : Item(verb='Adding'), |
| 'A/D/H/omega' : Item(verb='Adding'), |
| }) |
| wc_status.tweak(wc_rev=8) |
| wc_status.add({ |
| 'A' : Item(wc_rev=9), |
| 'A/B' : Item(wc_rev=9), |
| 'A/B/lambda' : Item(wc_rev=9), |
| 'A/B/E' : Item(wc_rev=9), |
| 'A/B/E/alpha' : Item(wc_rev=9), |
| 'A/B/E/beta' : Item(wc_rev=9), |
| 'A/B/F' : Item(wc_rev=9), |
| 'A/mu' : Item(wc_rev=9), |
| 'A/C' : Item(wc_rev=9), |
| 'A/D' : Item(wc_rev=9), |
| 'A/D/gamma' : Item(wc_rev=9), |
| 'A/D/G' : Item(wc_rev=9), |
| 'A/D/G/pi' : Item(wc_rev=9), |
| 'A/D/G/rho' : Item(wc_rev=9), |
| 'A/D/G/tau' : Item(wc_rev=9), |
| 'A/D/H' : Item(wc_rev=9), |
| 'A/D/H/chi' : Item(wc_rev=9), |
| 'A/D/H/omega' : Item(wc_rev=9), |
| 'A/D/H/psi' : Item(wc_rev=9), |
| }) |
| wc_status.tweak(status=' ') |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| wc_status) |
| |
| expected_output = wc.State(A_path, { |
| 'mu' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| '' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(A_path, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_path, { |
| }) |
| expected_A_status = wc.State(A_path, { |
| '' : Item(status=' M', wc_rev=9), |
| 'B' : Item(status=' ', wc_rev=9), |
| 'mu' : Item(status='M ', wc_rev=9), |
| 'B/E' : Item(status=' ', wc_rev=9), |
| 'B/E/alpha' : Item(status=' ', wc_rev=9), |
| 'B/E/beta' : Item(status=' ', wc_rev=9), |
| 'B/lambda' : Item(status=' ', wc_rev=9), |
| 'B/F' : Item(status=' ', wc_rev=9), |
| 'C' : Item(status=' ', wc_rev=9), |
| 'D' : Item(status=' ', wc_rev=9), |
| 'D/G' : Item(status=' ', wc_rev=9), |
| 'D/G/pi' : Item(status=' ', wc_rev=9), |
| 'D/G/rho' : Item(status=' ', wc_rev=9), |
| 'D/G/tau' : Item(status=' ', wc_rev=9), |
| 'D/gamma' : Item(status=' ', wc_rev=9), |
| 'D/H' : Item(status=' ', wc_rev=9), |
| 'D/H/chi' : Item(status=' ', wc_rev=9), |
| 'D/H/psi' : Item(status='M ', wc_rev=9), |
| 'D/H/omega' : Item(status=' ', wc_rev=9), |
| }) |
| expected_A_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3\n/A_COPY:7'}), |
| 'B' : Item(), |
| 'mu' : Item("New content"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_A_skip = wc.State(A_path, {}) |
| svntest.actions.run_and_verify_merge(A_path, '6', '7', |
| sbox.repo_url + '/A_COPY', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_A_disk, |
| expected_A_status, |
| expected_A_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3094) |
| def merge_range_predates_history(sbox): |
| "merge range predates history" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| iota_path = sbox.ospath('iota') |
| trunk_file_path = sbox.ospath('trunk/file') |
| trunk_url = sbox.repo_url + "/trunk" |
| branches_url = sbox.repo_url + "/branches" |
| branch_path = sbox.ospath('branches/branch') |
| branch_file_path = sbox.ospath('branches/branch/file') |
| branch_url = sbox.repo_url + "/branches/branch" |
| |
| # Tweak a file and commit. (r2) |
| svntest.main.file_append(iota_path, "More data.\n") |
| sbox.simple_commit(message='tweak iota') |
| |
| # Create our trunk and branches directory, and update working copy. (r3) |
| svntest.main.run_svn(None, 'mkdir', trunk_url, branches_url, |
| '-m', 'add trunk and branches dirs') |
| svntest.main.run_svn(None, 'up', wc_dir) |
| |
| # Add a file to the trunk and commit. (r4) |
| svntest.main.file_append(trunk_file_path, "This is the file 'file'.\n") |
| svntest.main.run_svn(None, 'add', trunk_file_path) |
| sbox.simple_commit(message='add trunk file') |
| |
| # Branch trunk from r3, and update working copy. (r5) |
| svntest.main.run_svn(None, 'cp', trunk_url, branch_url, '-r3', |
| '-m', 'branch trunk@2') |
| svntest.main.run_svn(None, 'up', wc_dir) |
| |
| # Now, try to merge trunk into the branch. There should be one |
| # outstanding change -- the addition of the file. |
| expected_output = expected_merge_output([[4,5]], |
| ['A ' + branch_file_path + '\n', |
| ' U ' + branch_path + '\n']) |
| svntest.actions.run_and_verify_svn(expected_output, [], 'merge', |
| trunk_url, branch_path) |
| |
| #---------------------------------------------------------------------- |
| @Issue(3623) |
| def foreign_repos(sbox): |
| "merge from a foreign repository" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Make a copy of this repository and associated working copy. Both |
| # should have nothing but a Greek tree in them, and the two |
| # repository UUIDs should differ. |
| sbox2 = sbox.clone_dependent(True) |
| sbox2.build() |
| wc_dir2 = sbox2.wc_dir |
| |
| # Convenience variables for working copy paths. |
| Z_path = sbox.ospath('A/D/G/Z') |
| B_path = sbox.ospath('A/B') |
| Q_path = sbox.ospath('Q') |
| H_path = sbox.ospath('A/D/H') |
| iota_path = sbox.ospath('iota') |
| beta_path = sbox.ospath('A/B/E/beta') |
| alpha_path = sbox.ospath('A/B/E/alpha') |
| zeta_path = sbox.ospath('A/D/G/Z/zeta') |
| fred_path = sbox.ospath('A/C/fred') |
| |
| # Add new directories, with and without properties. |
| svntest.main.run_svn(None, 'mkdir', Q_path, Z_path) |
| svntest.main.run_svn(None, 'pset', 'foo', 'bar', Z_path) |
| |
| # Add new files, with contents, with and without properties. |
| zeta_contents = "This is the file 'zeta'.\n" |
| fred_contents = "This is the file 'fred'.\n" |
| svntest.main.file_append(zeta_path, zeta_contents) |
| svntest.main.file_append(fred_path, fred_contents) |
| svntest.main.run_svn(None, 'add', zeta_path, fred_path) |
| svntest.main.run_svn(None, 'pset', 'foo', 'bar', fred_path) |
| |
| # Modify existing files and directories. |
| added_contents = "This is another line of text.\n" |
| svntest.main.file_append(iota_path, added_contents) |
| svntest.main.file_append(beta_path, added_contents) |
| svntest.main.run_svn(None, 'pset', 'foo', 'bar', iota_path, B_path) |
| |
| # Delete some stuff |
| svntest.main.run_svn(None, 'delete', alpha_path, H_path) |
| |
| # Commit up these changes. |
| expected_output = wc.State(wc_dir, { |
| 'Q' : Item(verb='Adding'), |
| 'A/D/G/Z' : Item(verb='Adding'), |
| 'A/D/G/Z/zeta' : Item(verb='Adding'), |
| 'A/C/fred' : Item(verb='Adding'), |
| 'iota' : Item(verb='Sending'), |
| 'A/B' : Item(verb='Sending'), |
| 'A/B/E/beta' : Item(verb='Sending'), |
| 'A/B/E/alpha' : Item(verb='Deleting'), |
| 'A/D/H' : Item(verb='Deleting'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'Q' : Item(status=' ', wc_rev=2), |
| 'A/D/G/Z' : Item(status=' ', wc_rev=2), |
| 'A/D/G/Z/zeta' : Item(status=' ', wc_rev=2), |
| 'A/C/fred' : Item(status=' ', wc_rev=2), |
| }) |
| expected_status.tweak('iota', 'A/B/E/beta', 'A/B', wc_rev=2) |
| expected_status.remove('A/B/E/alpha', 'A/D/H', 'A/D/H/chi', |
| 'A/D/H/psi', 'A/D/H/omega') |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.add({ |
| 'Q' : Item(), |
| 'A/D/G/Z' : Item(props={'foo':'bar'}), |
| 'A/D/G/Z/zeta' : Item(contents=zeta_contents), |
| 'A/C/fred' : Item(contents=fred_contents,props={'foo':'bar'}), |
| }) |
| expected_disk.remove('A/B/E/alpha', 'A/D/H', 'A/D/H/chi', |
| 'A/D/H/psi', 'A/D/H/omega') |
| expected_disk.tweak('iota', |
| contents=expected_disk.desc['iota'].contents |
| + added_contents, |
| props={'foo':'bar'}) |
| expected_disk.tweak('A/B', props={'foo':'bar'}) |
| expected_disk.tweak('A/B/E/beta', |
| contents=expected_disk.desc['A/B/E/beta'].contents |
| + added_contents) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| svntest.actions.verify_disk(wc_dir, expected_disk, True) |
| |
| # Now, merge our committed revision into a working copy of another |
| # repository. Not only should the merge succeed, but the results on |
| # disk should match those in our first working copy. |
| |
| ### TODO: Use run_and_verify_merge() ### |
| svntest.main.run_svn(None, 'merge', '-c2', sbox.repo_url, wc_dir2) |
| sbox2.simple_commit(message='Merge from foreign repo') |
| svntest.actions.verify_disk(wc_dir2, expected_disk, True) |
| |
| # Now, let's make a third checkout -- our second from the original |
| # repository -- and make sure that all the data there is correct. |
| # It should look just like the original EXPECTED_DISK. |
| # This is a regression test for issue #3623 in which wc_dir2 had the |
| # correct state but the committed state was wrong. |
| wc_dir3 = sbox.add_wc_path('wc3') |
| svntest.actions.run_and_verify_svn(None, [], 'checkout', |
| sbox2.repo_url, wc_dir3) |
| svntest.actions.verify_disk(wc_dir3, expected_disk, True) |
| |
| #---------------------------------------------------------------------- |
| def foreign_repos_uuid(sbox): |
| "verify uuid of items added via foreign repo merge" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_uuid = svntest.actions.get_wc_uuid(wc_dir) |
| |
| # Make a copy of this repository and associated working copy. Both |
| # should have nothing but a Greek tree in them, and the two |
| # repository UUIDs should differ. |
| sbox2 = sbox.clone_dependent(True) |
| sbox2.build() |
| wc_dir2 = sbox2.wc_dir |
| wc2_uuid = svntest.actions.get_wc_uuid(wc_dir2) |
| |
| # Convenience variables for working copy paths. |
| zeta_path = sbox.ospath('A/D/G/zeta') |
| Z_path = sbox.ospath('A/Z') |
| |
| # Add new file and directory. |
| zeta_contents = "This is the file 'zeta'.\n" |
| svntest.main.file_append(zeta_path, zeta_contents) |
| os.mkdir(Z_path) |
| svntest.main.run_svn(None, 'add', zeta_path, Z_path) |
| |
| # Commit up these changes. |
| expected_output = wc.State(wc_dir, { |
| 'A/D/G/zeta' : Item(verb='Adding'), |
| 'A/Z' : Item(verb='Adding'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/D/G/zeta' : Item(status=' ', wc_rev=2), |
| 'A/Z' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.add({ |
| 'A/D/G/zeta' : Item(contents=zeta_contents), |
| 'A/Z' : Item(), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| svntest.actions.verify_disk(wc_dir, expected_disk, True) |
| |
| svntest.main.run_svn(None, 'merge', '-c2', sbox.repo_url, wc_dir2) |
| sbox2.simple_commit(message='Merge from foreign repos') |
| |
| # Run info to check the copied rev to make sure it's right |
| zeta2_path = os.path.join(wc_dir2, 'A', 'D', 'G', 'zeta') |
| expected_info = {"Path" : re.escape(zeta2_path), # escape backslashes |
| "URL" : sbox2.repo_url + "/A/D/G/zeta", |
| "Repository Root" : sbox2.repo_url, |
| "Repository UUID" : wc2_uuid, |
| "Revision" : "2", |
| "Node Kind" : "file", |
| "Schedule" : "normal", |
| } |
| svntest.actions.run_and_verify_info([expected_info], zeta2_path) |
| |
| # Run info to check the copied rev to make sure it's right |
| Z2_path = os.path.join(wc_dir2, 'A', 'Z') |
| expected_info = {"Path" : re.escape(Z2_path), # escape backslashes |
| "URL" : sbox2.repo_url + "/A/Z", |
| "Repository Root" : sbox2.repo_url, |
| "Repository UUID" : wc2_uuid, |
| "Revision" : "2", |
| "Node Kind" : "directory", |
| "Schedule" : "normal", |
| } |
| svntest.actions.run_and_verify_info([expected_info], Z2_path) |
| |
| #---------------------------------------------------------------------- |
| def foreign_repos_2_url(sbox): |
| "2-url merge from a foreign repository" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Make a copy of this repository and associated working copy. Both |
| # should have nothing but a Greek tree in them, and the two |
| # repository UUIDs should differ. |
| sbox2 = sbox.clone_dependent(True) |
| sbox2.build() |
| wc_dir2 = sbox2.wc_dir |
| |
| # Convenience variables for working copy paths. |
| Z_path = sbox.ospath('A/D/G/Z') |
| Q_path = sbox.ospath('A/Q') |
| H_path = sbox.ospath('A/D/H') |
| beta_path = sbox.ospath('A/B/E/beta') |
| alpha_path = sbox.ospath('A/B/E/alpha') |
| zeta_path = sbox.ospath('A/D/G/Z/zeta') |
| fred_path = sbox.ospath('A/C/fred') |
| |
| # First, "tag" the current state of the repository. |
| svntest.main.run_svn(None, 'copy', sbox.repo_url + '/A', |
| sbox.repo_url + '/A-tag1', '-m', 'tag1') |
| |
| # Add new directories |
| svntest.main.run_svn(None, 'mkdir', Q_path, Z_path) |
| |
| # Add new files |
| zeta_contents = "This is the file 'zeta'.\n" |
| fred_contents = "This is the file 'fred'.\n" |
| svntest.main.file_append(zeta_path, zeta_contents) |
| svntest.main.file_append(fred_path, fred_contents) |
| svntest.main.run_svn(None, 'add', zeta_path, fred_path) |
| |
| # Modify existing files |
| added_contents = "This is another line of text.\n" |
| svntest.main.file_append(beta_path, added_contents) |
| |
| # Delete some stuff |
| svntest.main.run_svn(None, 'delete', alpha_path, H_path) |
| |
| # Commit up these changes. |
| expected_output = wc.State(wc_dir, { |
| 'A/Q' : Item(verb='Adding'), |
| 'A/D/G/Z' : Item(verb='Adding'), |
| 'A/D/G/Z/zeta' : Item(verb='Adding'), |
| 'A/C/fred' : Item(verb='Adding'), |
| 'A/B/E/beta' : Item(verb='Sending'), |
| 'A/B/E/alpha' : Item(verb='Deleting'), |
| 'A/D/H' : Item(verb='Deleting'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/Q' : Item(status=' ', wc_rev=3), |
| 'A/D/G/Z' : Item(status=' ', wc_rev=3), |
| 'A/D/G/Z/zeta' : Item(status=' ', wc_rev=3), |
| 'A/C/fred' : Item(status=' ', wc_rev=3), |
| }) |
| expected_status.tweak('A/B/E/beta', wc_rev=3) |
| expected_status.remove('A/B/E/alpha', 'A/D/H', 'A/D/H/chi', |
| 'A/D/H/psi', 'A/D/H/omega') |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.add({ |
| 'A/Q' : Item(), |
| 'A/D/G/Z' : Item(), |
| 'A/D/G/Z/zeta' : Item(contents=zeta_contents), |
| 'A/C/fred' : Item(contents=fred_contents), |
| }) |
| expected_disk.remove('A/B/E/alpha', 'A/D/H', 'A/D/H/chi', |
| 'A/D/H/psi', 'A/D/H/omega') |
| expected_disk.tweak('A/B/E/beta', |
| contents=expected_disk.desc['A/B/E/beta'].contents |
| + added_contents) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| expected_status) |
| svntest.actions.verify_disk(wc_dir, expected_disk, True) |
| |
| # Now, "tag" the new state of the repository. |
| svntest.main.run_svn(None, 'copy', sbox.repo_url + '/A', |
| sbox.repo_url + '/A-tag2', '-m', 'tag2') |
| |
| # Now, merge across our "tags" (copies of /A) into the /A of a |
| # working copy of another repository. Not only should the merge |
| # succeed, but the results on disk should match those in our first |
| # working copy. |
| |
| ### TODO: Use run_and_verify_merge() ### |
| svntest.main.run_svn(None, 'merge', sbox.repo_url + '/A-tag1', |
| sbox.repo_url + '/A-tag2', |
| os.path.join(wc_dir2, 'A')) |
| sbox2.simple_commit(message='Merge from foreign repos') |
| svntest.actions.verify_disk(wc_dir2, expected_disk, True) |
| |
| #---------------------------------------------------------------------- |
| @Issue(1962) |
| def merge_added_subtree(sbox): |
| "merge added subtree" |
| |
| # The result of a subtree added by copying |
| # or merging an added subtree, should be the same on disk |
| ### with the exception of mergeinfo?! |
| |
| # test for issue 1962 |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| url = sbox.repo_url |
| |
| # make a branch of A |
| # svn cp A A_COPY |
| A_url = url + "/A" |
| A_COPY_url = url + "/A_COPY" |
| A_path = sbox.ospath('A') |
| |
| svntest.actions.run_and_verify_svn(["Committing transaction...\n", |
| "Committed revision 2.\n"], [], |
| "cp", "-m", "", A_url, A_COPY_url) |
| svntest.actions.run_and_verify_svn(["Committing transaction...\n", |
| "Committed revision 3.\n"], [], |
| "cp", "-m", "", |
| A_COPY_url + '/D', |
| A_COPY_url + '/D2') |
| expected_output = wc.State(A_path, { |
| 'D2' : Item(status='A '), |
| 'D2/gamma' : Item(status='A '), |
| 'D2/H' : Item(status='A '), |
| 'D2/H/chi' : Item(status='A '), |
| 'D2/H/psi' : Item(status='A '), |
| 'D2/H/omega': Item(status='A '), |
| 'D2/G' : Item(status='A '), |
| 'D2/G/pi' : Item(status='A '), |
| 'D2/G/rho' : Item(status='A '), |
| 'D2/G/tau' : Item(status='A ') |
| }) |
| |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/D2' : Item(status='A ', copied='+', wc_rev='-'), |
| 'A/D2/gamma' : Item(status=' ', copied='+', wc_rev='-'), |
| 'A/D2/H' : Item(status=' ', copied='+', wc_rev='-'), |
| 'A/D2/H/chi' : Item(status=' ', copied='+', wc_rev='-'), |
| 'A/D2/H/psi' : Item(status=' ', copied='+', wc_rev='-'), |
| 'A/D2/H/omega': Item(status=' ', copied='+', wc_rev='-'), |
| 'A/D2/G' : Item(status=' ', copied='+', wc_rev='-'), |
| 'A/D2/G/pi' : Item(status=' ', copied='+', wc_rev='-'), |
| 'A/D2/G/rho' : Item(status=' ', copied='+', wc_rev='-'), |
| 'A/D2/G/tau' : Item(status=' ', copied='+', wc_rev='-') |
| }) |
| expected_status.remove('', 'iota') |
| |
| expected_skip = wc.State('', {}) |
| expected_disk = svntest.main.greek_state.subtree("A") |
| dest_name = '' |
| expected_disk.add({ |
| dest_name + 'D2' : Item(), |
| dest_name + 'D2/gamma' : Item("This is the file 'gamma'.\n"), |
| dest_name + 'D2/G' : Item(), |
| dest_name + 'D2/G/pi' : Item("This is the file 'pi'.\n"), |
| dest_name + 'D2/G/rho' : Item("This is the file 'rho'.\n"), |
| dest_name + 'D2/G/tau' : Item("This is the file 'tau'.\n"), |
| dest_name + 'D2/H' : Item(), |
| dest_name + 'D2/H/chi' : Item("This is the file 'chi'.\n"), |
| dest_name + 'D2/H/omega' : Item("This is the file 'omega'.\n"), |
| dest_name + 'D2/H/psi' : Item("This is the file 'psi'.\n") |
| }) |
| |
| # Using the above information, verify a REPO->WC copy |
| svntest.actions.run_and_verify_svn(None, [], |
| "cp", A_COPY_url + '/D2', |
| os.path.join(A_path, "D2")) |
| svntest.actions.verify_disk(A_path, expected_disk) |
| svntest.actions.run_and_verify_status(A_path, expected_status) |
| |
| # Remove the copy artifacts |
| svntest.actions.run_and_verify_svn(None, [], |
| "revert", "-R", A_path) |
| svntest.main.safe_rmtree(os.path.join(A_path, "D2")) |
| |
| # Add merge-tracking differences between copying and merging |
| # Verify a merge using the otherwise unchanged disk and status trees |
| expected_status.tweak('A',status=' M') |
| expected_mergeinfo_output = wc.State(A_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_path, { |
| }) |
| svntest.actions.run_and_verify_merge(A_path, 2, 3, A_COPY_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip) |
| |
| #---------------------------------------------------------------------- |
| # Issue #3138 |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3138) |
| def merge_unknown_url(sbox): |
| "merging an unknown url should return error" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # remove a path from the repo and commit. |
| iota_path = sbox.ospath('iota') |
| svntest.actions.run_and_verify_svn(None, [], 'rm', iota_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| "ci", wc_dir, "-m", "log message") |
| |
| |
| url = sbox.repo_url + "/iota" |
| expected_err = ".*File not found.*iota.*|.*iota.*path not found.*" |
| svntest.actions.run_and_verify_svn(None, expected_err, |
| "merge", url, wc_dir) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def reverse_merge_away_all_mergeinfo(sbox): |
| "merges that remove all mergeinfo work" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| A_COPY_H_path = sbox.ospath('A_COPY/D/H') |
| |
| # Merge r4:8 from A/D/H into A_COPY/D/H. |
| expected_output = wc.State(A_COPY_H_path, { |
| 'omega' : Item(status='U '), |
| 'psi' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_H_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_H_path, { |
| }) |
| expected_status = wc.State(A_COPY_H_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'psi' : Item(status='M ', wc_rev=2), |
| 'omega' : Item(status='M ', wc_rev=2), |
| 'chi' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:3-6'}), |
| 'psi' : Item("New content"), |
| 'omega' : Item("New content"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_H_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_H_path, '2', '6', |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| |
| # Commit the merge as r7 |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY/D/H' : Item(verb='Sending'), |
| 'A_COPY/D/H/omega' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending'), |
| }) |
| wc_status.tweak('A_COPY/D/H', 'A_COPY/D/H/omega', 'A_COPY/D/H/psi', |
| wc_rev=7) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| wc_status) |
| |
| # Now reverse merge r7 from itself, all mergeinfo should be removed. |
| expected_output = wc.State(A_COPY_H_path, { |
| '' : Item(status=' U'), |
| 'omega' : Item(status='U '), |
| 'psi' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_H_path, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_H_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_H_path, { |
| '' : Item(status=' M', wc_rev=7), |
| 'psi' : Item(status='M ', wc_rev=7), |
| 'omega' : Item(status='M ', wc_rev=7), |
| 'chi' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| 'psi' : Item("This is the file 'psi'.\n"), |
| 'omega' : Item("This is the file 'omega'.\n"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_H_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_H_path, '7', '6', |
| sbox.repo_url + '/A_COPY/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| [], |
| True, False, '--allow-mixed-revisions', |
| A_COPY_H_path) |
| |
| #---------------------------------------------------------------------- |
| # Issue #3138 |
| # Another test for issue #3067: 'subtrees with intersecting mergeinfo, |
| # that don't exist at the start of a merge range shouldn't break the |
| # merge'. Specifically see |
| # https://issues.apache.org/jira/browse/SVN-3067#desc5 |
| @SkipUnless(server_has_mergeinfo) |
| @Issues(3138,3067,4217) |
| def dont_merge_revs_into_subtree_that_predate_it(sbox): |
| "dont merge revs into a subtree that predate it" |
| |
| # +-> merge -c7 A/D/H/nu@7 H_COPY/nu |
| # | +-> merge -c2 A/D/H H_COPY |
| # | | +-> merge A/D/H H_COPY |
| # | | | |
| # A/D/H A---------------------- |
| # +-psi +-M-------------M------ |
| # +-nu A-D C---M-D |
| # H_COPY C---------G-G |
| # +-psi +---------+-. |
| # +-nu +-------G---. |
| # 1 2 3 4 5 6 7 8 9 w w w |
| |
| # Create our good 'ole greek tree. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| psi_path = sbox.ospath('A/D/H/psi') |
| nu_path = sbox.ospath('A/D/H/nu') |
| H_COPY_path = sbox.ospath('H_COPY') |
| nu_COPY_path = sbox.ospath('H_COPY/nu') |
| |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_disk = svntest.main.greek_state.copy() |
| |
| # Make a text mod to 'A/D/H/psi' and commit it as r2 |
| svntest.main.file_write(psi_path, "New content") |
| expected_output = wc.State(wc_dir, {'A/D/H/psi' : Item(verb='Sending')}) |
| expected_status.tweak('A/D/H/psi', wc_rev=2) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| expected_disk.tweak('A/D/H/psi', contents="New content") |
| |
| # Create 'A/D/H/nu' and commit it as r3. |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| expected_output = wc.State(wc_dir, {'A/D/H/nu' : Item(verb='Adding')}) |
| expected_status.add({'A/D/H/nu' : Item(status=' ', wc_rev=3)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Delete 'A/D/H/nu' and commit it as r4. |
| svntest.actions.run_and_verify_svn(None, [], 'rm', nu_path) |
| expected_output = wc.State(wc_dir, {'A/D/H/nu' : Item(verb='Deleting')}) |
| expected_status.remove('A/D/H/nu') |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Copy 'A/D/H/nu' from r3 and commit it as r5. |
| svntest.actions.run_and_verify_svn(None, [], 'cp', |
| sbox.repo_url + '/A/D/H/nu@3', nu_path) |
| expected_output = wc.State(wc_dir, {'A/D/H/nu' : Item(verb='Adding')}) |
| expected_status.add({'A/D/H/nu' : Item(status=' ', wc_rev=5)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Copy 'A/D/H' to 'H_COPY' in r6. |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 6.\n'], |
| [], 'copy', |
| sbox.repo_url + "/A/D/H", |
| sbox.repo_url + "/H_COPY", |
| "-m", "Copy A/D/H to H_COPY") |
| expected_status.add({ |
| "H_COPY" : Item(), |
| "H_COPY/chi" : Item(), |
| "H_COPY/omega" : Item(), |
| "H_COPY/psi" : Item(), |
| "H_COPY/nu" : Item()}) |
| |
| # Update to pull the previous copy into the WC |
| svntest.main.run_svn(None, 'up', wc_dir) |
| expected_status.tweak(status=' ', wc_rev=6) |
| |
| # Make a text mod to 'A/D/H/nu' and commit it as r7. |
| svntest.main.file_write(nu_path, "New content") |
| expected_output = wc.State(wc_dir, {'A/D/H/nu' : Item(verb='Sending')}) |
| expected_status.tweak('A/D/H/nu', wc_rev=7) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Remove A/D/H/nu and commit it as r8. |
| # We do this deletion so that following cherry harvest has a *tough* |
| # time to identify the line of history of /A/D/H/nu@HEAD. |
| svntest.main.run_svn(None, 'rm', nu_path) |
| expected_output = wc.State(wc_dir, {'A/D/H/nu' : Item(verb='Deleting')}) |
| expected_status.remove('A/D/H/nu') |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Make another text mod to 'A/D/H/psi' that can be merged to 'H_COPY' |
| # during a cherry harvest and commit it as r9. |
| svntest.main.file_write(psi_path, "Even *newer* content") |
| expected_output = wc.State(wc_dir, {'A/D/H/psi' : Item(verb='Sending')}) |
| expected_status.tweak('A/D/H/psi', wc_rev=9) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| expected_disk.tweak('A/D/H/psi', contents="Even *newer* content") |
| |
| # Update WC so elision occurs smoothly. |
| svntest.main.run_svn(None, 'up', wc_dir) |
| expected_status.tweak(status=' ', wc_rev=9) |
| |
| # Merge r7 from 'A/D/H/nu' to 'H_COPY/nu'. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[7]], |
| ['U ' + nu_COPY_path + '\n', |
| ' U ' + nu_COPY_path + '\n']), |
| [], 'merge', '-c7', sbox.repo_url + '/A/D/H/nu@7', nu_COPY_path) |
| |
| # Cherry harvest all eligible revisions from 'A/D/H' to 'H_COPY'. |
| # |
| # This is where we see the problem described in |
| # https://issues.apache.org/jira/browse/SVN-3067#desc5. |
| # |
| # Use run_and_verify_svn() because run_and_verify_merge*() require |
| # explicit revision ranges. |
| |
| expected_skip = wc.State(H_COPY_path, { }) |
| #Cherry pick r2 prior to cherry harvest. |
| svntest.actions.run_and_verify_svn([], [], 'merge', '-c2', |
| sbox.repo_url + '/A/D/H', |
| H_COPY_path) |
| |
| # H_COPY needs r6-9 applied while H_COPY/nu needs only 6,8-9. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output( |
| [[7,9], # Merge notification |
| [6,9]], # Mergeinfo notification |
| ['U ' + os.path.join(H_COPY_path, "psi") + '\n', |
| 'D ' + os.path.join(H_COPY_path, "nu") + '\n', |
| ' U ' + H_COPY_path + '\n',]), |
| [], 'merge', sbox.repo_url + '/A/D/H', H_COPY_path, '--force') |
| |
| # Check the status after the merge. |
| expected_status.tweak('H_COPY', status=' M') |
| expected_status.tweak('H_COPY/psi', status='M ') |
| expected_status.tweak('H_COPY/nu', status='D ') |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| check_mergeinfo_recursively(wc_dir, |
| { H_COPY_path: '/A/D/H:6-9' }) |
| |
| #---------------------------------------------------------------------- |
| # Helper for merge_chokes_on_renamed_subtrees and |
| # subtrees_with_empty_mergeinfo. |
| def set_up_renamed_subtree(sbox): |
| '''Starting with standard greek tree, make a text mod to A/D/H/psi |
| as r2. Tweak A/D/H/omega and commit it at r3(We do this to create |
| broken segment of history of A/D/H. |
| *DO NOT SVN UPDATE*. |
| Move A/D/H/psi to A/D/H/psi_moved as r4. Copy A/D/H to H_COPY |
| as r5. Make a text mod to A/D/H/psi_moved and commit it at r6. |
| Update the working copy and return the expected disk and status |
| representing it''' |
| |
| # Create our good 'ole greek tree. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| psi_path = sbox.ospath('A/D/H/psi') |
| omega_path = sbox.ospath('A/D/H/omega') |
| psi_moved_path = sbox.ospath('A/D/H/psi_moved') |
| psi_COPY_moved_path = sbox.ospath('H_COPY/psi_moved') |
| H_COPY_path = sbox.ospath('H_COPY') |
| |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_disk = svntest.main.greek_state.copy() |
| |
| # Make a text mod to 'A/D/H/psi' and commit it as r2 |
| svntest.main.file_write(psi_path, "New content") |
| expected_output = wc.State(wc_dir, {'A/D/H/psi' : Item(verb='Sending')}) |
| expected_status.tweak('A/D/H/psi', wc_rev=2) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| expected_disk.tweak('A/D/H/psi', contents="New content") |
| |
| # Make a text mod to 'A/D/H/omega' and commit it as r3 |
| svntest.main.file_write(omega_path, "New omega") |
| expected_output = wc.State(wc_dir, {'A/D/H/omega' : Item(verb='Sending')}) |
| expected_status.tweak('A/D/H/omega', wc_rev=3) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| expected_disk.tweak('A/D/H/omega', contents="New omega") |
| |
| # Move 'A/D/H/psi' to 'A/D/H/psi_moved' and commit it as r4. |
| svntest.actions.run_and_verify_svn(None, [], 'move', |
| psi_path, psi_moved_path) |
| expected_output = wc.State(wc_dir, { |
| 'A/D/H/psi' : Item(verb='Deleting'), |
| 'A/D/H/psi_moved' : Item(verb='Adding') |
| }) |
| expected_status.add({'A/D/H/psi_moved' : Item(status=' ', wc_rev=4)}) |
| expected_status.remove('A/D/H/psi') |
| |
| # Replicate old WC-to-WC move behavior where empty mergeinfo was set on |
| # the move destination. Pre 1.6 repositories might have mergeinfo like |
| # this so we still want to test that the issue #3067 fixes tested by |
| # merge_chokes_on_renamed_subtrees and subtrees_with_empty_mergeinfo |
| # still work. |
| svntest.actions.run_and_verify_svn(None, [], 'ps', SVN_PROP_MERGEINFO, |
| "", psi_moved_path) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Copy 'A/D/H' to 'H_COPY' in r5. |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 5.\n'], |
| [], 'copy', |
| sbox.repo_url + "/A/D/H", |
| sbox.repo_url + "/H_COPY", |
| "-m", "Copy A/D/H to H_COPY") |
| expected_status.add({ |
| "H_COPY" : Item(), |
| "H_COPY/chi" : Item(), |
| "H_COPY/omega" : Item(), |
| "H_COPY/psi_moved" : Item()}) |
| |
| # Update to pull the previous copy into the WC |
| svntest.main.run_svn(None, 'up', wc_dir) |
| expected_status.tweak(status=' ', wc_rev=5) |
| |
| # Make a text mod to 'A/D/H/psi_moved' and commit it as r6 |
| svntest.main.file_write(psi_moved_path, "Even *Newer* content") |
| expected_output = wc.State(wc_dir, |
| {'A/D/H/psi_moved' : Item(verb='Sending')}) |
| expected_status.tweak('A/D/H/psi_moved', wc_rev=6) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| expected_disk.remove('A/D/H/psi') |
| expected_disk.add({ |
| 'A/D/H/psi_moved' : Item("Even *Newer* content"), |
| }) |
| |
| # Update for a uniform working copy before merging. |
| svntest.main.run_svn(None, 'up', wc_dir) |
| expected_status.tweak(status=' ', wc_rev=6) |
| |
| return wc_dir, expected_disk, expected_status |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3174: 'Merge algorithm chokes on subtrees needing |
| # special attention that have been renamed' |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3174) |
| def merge_chokes_on_renamed_subtrees(sbox): |
| "merge fails with renamed subtrees with mergeinfo" |
| |
| # Use helper to setup a renamed subtree. |
| wc_dir, expected_disk, expected_status = set_up_renamed_subtree(sbox) |
| |
| # Some paths we'll care about |
| psi_COPY_moved_path = sbox.ospath('H_COPY/psi_moved') |
| |
| |
| # Cherry harvest all available revsions from 'A/D/H/psi_moved' to |
| # 'H_COPY/psi_moved'. |
| # |
| # Here is where issue #3174 appears, the merge fails with: |
| # svn: svn: File not found: revision 3, path '/A/D/H/psi' |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[5,6],[3,6]], |
| ['U ' + psi_COPY_moved_path + '\n', |
| ' U ' + psi_COPY_moved_path + '\n', |
| ' G ' + psi_COPY_moved_path + '\n',], |
| elides=True), |
| [], 'merge', sbox.repo_url + '/A/D/H/psi_moved', |
| psi_COPY_moved_path) |
| |
| expected_status.tweak('H_COPY/psi_moved', status='MM') |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| |
| |
| #---------------------------------------------------------------------- |
| # Issue #3157 |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3157) |
| def dont_explicitly_record_implicit_mergeinfo(sbox): |
| "don't explicitly record implicit mergeinfo" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| A_path = sbox.ospath('A') |
| A_copy_path = sbox.ospath('A_copy') |
| A_copy2_path = sbox.ospath('A_copy2') |
| A_copy_mu_path = sbox.ospath('A_copy/mu') |
| A_copy2_mu_path = sbox.ospath('A_copy2/mu') |
| nu_path = sbox.ospath('A/D/H/nu') |
| nu_copy_path = sbox.ospath('A_copy/D/H/nu') |
| |
| def _commit_and_update(rev, action): |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', 'r%d - %s' % (rev, action), |
| sbox.wc_dir) |
| svntest.main.run_svn(None, 'up', wc_dir) |
| |
| # r2 - copy A to A_copy |
| svntest.main.run_svn(None, 'cp', A_path, A_copy_path) |
| _commit_and_update(2, "Copy A to A_copy.") |
| |
| # r3 - tweak A_copy/mu |
| svntest.main.file_append(A_copy_mu_path, "r3\n") |
| _commit_and_update(3, "Edit A_copy/mu.") |
| |
| # r4 - copy A_copy to A_copy2 |
| svntest.main.run_svn(None, 'cp', A_copy_path, A_copy2_path) |
| _commit_and_update(4, "Copy A_copy to A_copy2.") |
| |
| # r5 - tweak A_copy2/mu |
| svntest.main.file_append(A_copy2_mu_path, "r5\n") |
| _commit_and_update(5, "Edit A_copy2/mu.") |
| |
| # Merge r5 from A_copy2/mu to A_copy/mu. |
| # |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. Check the resulting mergeinfo with |
| # a propget. |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[5]], ['U ' + A_copy_mu_path + '\n', |
| ' U ' + A_copy_mu_path + '\n']), |
| [], 'merge', '-c5', sbox.repo_url + '/A_copy2/mu', A_copy_mu_path) |
| check_mergeinfo_recursively(A_copy_mu_path, |
| { A_copy_mu_path: '/A_copy2/mu:5' }) |
| |
| # Now, merge A_copy2 (in full) back to A_copy. This should result in |
| # mergeinfo of '/A_copy2:4-5' on A_copy and '/A_copy2/mu:4-5' on A_copy/mu |
| # and the latter should elide to the former. Any revisions < 4 are part of |
| # A_copy's natural history and should not be explicitly recorded. |
| expected_output = wc.State(A_copy_path, {}) |
| expected_mergeinfo_output = wc.State(A_copy_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_copy_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A_copy2:4-5'}), |
| 'mu' : Item("This is the file 'mu'.\nr3\nr5\n", |
| props={SVN_PROP_MERGEINFO : '/A_copy2/mu:5'}), |
| 'B' : Item(), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| }) |
| expected_status = wc.State(A_copy_path, { |
| '' : Item(status=' M'), |
| 'mu' : Item(status='MM'), |
| 'B' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=5) |
| expected_skip = wc.State(A_copy_path, { }) |
| svntest.actions.run_and_verify_merge(A_copy_path, None, None, |
| sbox.repo_url + '/A_copy2', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| |
| # Revert the previous merges and try a cherry harvest merge where |
| # the subtree's natural history is a proper subset of the merge. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| wc_status = svntest.actions.get_virginal_state(wc_dir, 5) |
| wc_status.add({ |
| 'A_copy' : Item(), |
| 'A_copy/B' : Item(), |
| 'A_copy/B/lambda' : Item(), |
| 'A_copy/B/E' : Item(), |
| 'A_copy/B/E/alpha' : Item(), |
| 'A_copy/B/E/beta' : Item(), |
| 'A_copy/B/F' : Item(), |
| 'A_copy/mu' : Item(), |
| 'A_copy/C' : Item(), |
| 'A_copy/D' : Item(), |
| 'A_copy/D/gamma' : Item(), |
| 'A_copy/D/G' : Item(), |
| 'A_copy/D/G/pi' : Item(), |
| 'A_copy/D/G/rho' : Item(), |
| 'A_copy/D/G/tau' : Item(), |
| 'A_copy/D/H' : Item(), |
| 'A_copy/D/H/chi' : Item(), |
| 'A_copy/D/H/omega' : Item(), |
| 'A_copy/D/H/psi' : Item(), |
| 'A_copy2' : Item(), |
| 'A_copy2/B' : Item(), |
| 'A_copy2/B/lambda' : Item(), |
| 'A_copy2/B/E' : Item(), |
| 'A_copy2/B/E/alpha' : Item(), |
| 'A_copy2/B/E/beta' : Item(), |
| 'A_copy2/B/F' : Item(), |
| 'A_copy2/mu' : Item(), |
| 'A_copy2/C' : Item(), |
| 'A_copy2/D' : Item(), |
| 'A_copy2/D/gamma' : Item(), |
| 'A_copy2/D/G' : Item(), |
| 'A_copy2/D/G/pi' : Item(), |
| 'A_copy2/D/G/rho' : Item(), |
| 'A_copy2/D/G/tau' : Item(), |
| 'A_copy2/D/H' : Item(), |
| 'A_copy2/D/H/chi' : Item(), |
| 'A_copy2/D/H/omega' : Item(), |
| 'A_copy2/D/H/psi' : Item(), |
| }) |
| wc_status.tweak(status=' ', wc_rev=5) |
| |
| # r6 - Add the file 'A/D/H/nu'. |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| expected_output = wc.State(wc_dir, {'A/D/H/nu' : Item(verb='Adding')}) |
| wc_status.add({'A/D/H/nu' : Item(status=' ', wc_rev=6)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # r7 - Make a change to 'A/D/H/nu'. |
| svntest.main.file_write(nu_path, "Nu content") |
| expected_output = wc.State(wc_dir, {'A/D/H/nu' : Item(verb='Sending')}) |
| wc_status.tweak('A/D/H/nu', wc_rev=7) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # r8 - Merge r6 to 'A_copy'. |
| expected_output = wc.State(A_copy_path, { |
| 'D/H/nu' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_copy_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_copy_path, { |
| }) |
| expected_A_copy_status = wc.State(A_copy_path, { |
| '' : Item(status=' M', wc_rev=5), |
| 'B' : Item(status=' ', wc_rev=5), |
| 'mu' : Item(status=' ', wc_rev=5), |
| 'B/E' : Item(status=' ', wc_rev=5), |
| 'B/E/alpha' : Item(status=' ', wc_rev=5), |
| 'B/E/beta' : Item(status=' ', wc_rev=5), |
| 'B/lambda' : Item(status=' ', wc_rev=5), |
| 'B/F' : Item(status=' ', wc_rev=5), |
| 'C' : Item(status=' ', wc_rev=5), |
| 'D' : Item(status=' ', wc_rev=5), |
| 'D/G' : Item(status=' ', wc_rev=5), |
| 'D/G/pi' : Item(status=' ', wc_rev=5), |
| 'D/G/rho' : Item(status=' ', wc_rev=5), |
| 'D/G/tau' : Item(status=' ', wc_rev=5), |
| 'D/gamma' : Item(status=' ', wc_rev=5), |
| 'D/H' : Item(status=' ', wc_rev=5), |
| 'D/H/chi' : Item(status=' ', wc_rev=5), |
| 'D/H/psi' : Item(status=' ', wc_rev=5), |
| 'D/H/omega' : Item(status=' ', wc_rev=5), |
| 'D/H/nu' : Item(status='A ', wc_rev='-', copied='+'), |
| }) |
| expected_A_copy_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:6'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\nr3\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| 'D/H/nu' : Item("This is the file 'nu'.\n"), |
| }) |
| expected_A_copy_skip = wc.State(A_copy_path, {}) |
| svntest.actions.run_and_verify_merge(A_copy_path, '5', '6', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_A_copy_disk, |
| expected_A_copy_status, |
| expected_A_copy_skip, |
| check_props=True) |
| wc_status.add({'A_copy/D/H/nu' : Item(status=' ', wc_rev=8)}) |
| wc_status.tweak('A_copy', wc_rev=8) |
| expected_output = wc.State(wc_dir, { |
| 'A_copy/D/H/nu' : Item(verb='Adding'), |
| 'A_copy' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # r9 - Merge r7 to 'A_copy/D/H/nu'. |
| expected_skip = wc.State(nu_copy_path, { }) |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[7]], |
| ['U ' + nu_copy_path + '\n', |
| ' G ' + nu_copy_path + '\n',]), |
| [], 'merge', '-c7', sbox.repo_url + '/A/D/H/nu', nu_copy_path) |
| expected_output = wc.State(wc_dir, {'A_copy/D/H/nu' : Item(verb='Sending')}) |
| wc_status.tweak('A_copy/D/H/nu', wc_rev=9) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Update WC |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| wc_status.tweak(wc_rev=9) |
| |
| # r10 - Make another change to 'A/D/H/nu'. |
| svntest.main.file_write(nu_path, "Even nuer content") |
| expected_output = wc.State(wc_dir, {'A/D/H/nu' : Item(verb='Sending')}) |
| wc_status.tweak('A/D/H/nu', wc_rev=10) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Update WC |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| wc_status.tweak(wc_rev=10) |
| |
| # Now do a cherry harvest merge to 'A_copy'. |
| expected_output = wc.State(A_copy_path, { |
| 'D/H/nu' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_copy_path, { |
| '' : Item(status=' U'), |
| 'D/H/nu' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_copy_path, { |
| }) |
| expected_A_copy_status = wc.State(A_copy_path, { |
| '' : Item(status=' M', wc_rev=10), |
| 'B' : Item(status=' ', wc_rev=10), |
| 'mu' : Item(status=' ', wc_rev=10), |
| 'B/E' : Item(status=' ', wc_rev=10), |
| 'B/E/alpha' : Item(status=' ', wc_rev=10), |
| 'B/E/beta' : Item(status=' ', wc_rev=10), |
| 'B/lambda' : Item(status=' ', wc_rev=10), |
| 'B/F' : Item(status=' ', wc_rev=10), |
| 'C' : Item(status=' ', wc_rev=10), |
| 'D' : Item(status=' ', wc_rev=10), |
| 'D/G' : Item(status=' ', wc_rev=10), |
| 'D/G/pi' : Item(status=' ', wc_rev=10), |
| 'D/G/rho' : Item(status=' ', wc_rev=10), |
| 'D/G/tau' : Item(status=' ', wc_rev=10), |
| 'D/gamma' : Item(status=' ', wc_rev=10), |
| 'D/H' : Item(status=' ', wc_rev=10), |
| 'D/H/chi' : Item(status=' ', wc_rev=10), |
| 'D/H/psi' : Item(status=' ', wc_rev=10), |
| 'D/H/omega' : Item(status=' ', wc_rev=10), |
| 'D/H/nu' : Item(status='MM', wc_rev=10), |
| }) |
| expected_A_copy_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-10'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\nr3\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| 'D/H/nu' : Item("Even nuer content", |
| props={SVN_PROP_MERGEINFO : '/A/D/H/nu:6-10'}), |
| }) |
| expected_A_copy_skip = wc.State(A_copy_path, {}) |
| svntest.actions.run_and_verify_merge(A_copy_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_A_copy_disk, |
| expected_A_copy_status, |
| expected_A_copy_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue where merging a change to a broken link fails |
| @SkipUnless(svntest.main.is_posix_os) |
| def merge_broken_link(sbox): |
| "merge with broken symlinks in target" |
| |
| # Create our good 'ole greek tree. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| src_path = sbox.ospath('A/B/E') |
| copy_path = sbox.ospath('A/B/E_COPY') |
| link_path = os.path.join(src_path, 'beta_link') |
| |
| os.symlink('beta_broken', link_path) |
| svntest.main.run_svn(None, 'add', link_path) |
| svntest.main.run_svn(None, 'commit', '-m', 'Create a broken link', link_path) |
| svntest.main.run_svn(None, 'copy', src_path, copy_path) |
| svntest.main.run_svn(None, 'commit', '-m', 'Copy the tree with the broken link', |
| copy_path) |
| os.unlink(link_path) |
| os.symlink('beta', link_path) |
| svntest.main.run_svn(None, 'commit', '-m', 'Fix a broken link', link_path) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[4]], |
| ['U ' + copy_path + '/beta_link\n', |
| ' U ' + copy_path + '\n']), |
| [], 'merge', '-c4', src_path, copy_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3199 'Subtree merges broken when required ranges |
| # don't intersect with merge target' |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3199) |
| def subtree_merges_dont_intersect_with_targets(sbox): |
| "subtree ranges might not intersect with target" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Make two branches to merge to. |
| wc_disk, wc_status = set_up_branch(sbox, False, 2) |
| |
| # Some paths we'll care about. |
| A_COPY_path = sbox.ospath('A_COPY') |
| A_COPY_2_path = sbox.ospath('A_COPY_2') |
| H_COPY_2_path = sbox.ospath('A_COPY_2/D/H') |
| gamma_path = sbox.ospath('A/D/gamma') |
| psi_path = sbox.ospath('A/D/H/psi') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| gamma_COPY_path = sbox.ospath('A_COPY/D/gamma') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| psi_COPY_2_path = sbox.ospath('A_COPY_2/D/H/psi') |
| rho_COPY_2_path = sbox.ospath('A_COPY_2/D/G/rho') |
| |
| # Make a tweak to A/D/gamma and A/D/H/psi in r8. |
| svntest.main.file_write(gamma_path, "New content") |
| svntest.main.file_write(psi_path, "Even newer content") |
| expected_output = wc.State(wc_dir, { |
| 'A/D/gamma' : Item(verb='Sending'), |
| 'A/D/H/psi' : Item(verb='Sending'), |
| }) |
| wc_status.tweak('A/D/gamma', 'A/D/H/psi', wc_rev=8) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| wc_disk.tweak('A/D/gamma', contents="New content") |
| wc_disk.tweak('A/D/H/psi', contents="Even newer content") |
| |
| # Update the WC. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], |
| 'update', wc_dir) |
| wc_status.tweak(wc_rev=8) |
| |
| # Run a bunch of merges to setup the 2 branches with explicit |
| # mergeinfo on each branch root and explicit mergeinfo on one subtree |
| # of each root. The mergeinfo should be such that: |
| # |
| # 1) On one branch: The mergeinfo on the root and the subtree do |
| # not intersect. |
| # |
| # 2) On the other branch: The mergeinfo on the root and subtree |
| # are each 'missing' and eligible ranges and these missing |
| # ranges do not intersect. |
| # |
| # Note: We just use run_and_verify_svn(...'merge'...) here rather than |
| # run_and_verify_merge() because these types of simple merges are |
| # tested to death elsewhere and this is just setup for the "real" |
| # test. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '-c4', |
| sbox.repo_url + '/A/D/H/psi', |
| psi_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '-c8', |
| sbox.repo_url + '/A', |
| A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '-c-8', |
| sbox.repo_url + '/A/D/H/psi', |
| psi_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', |
| sbox.repo_url + '/A', |
| A_COPY_2_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '-c-5', |
| sbox.repo_url + '/A', |
| A_COPY_2_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '-c5', '-c-8', |
| sbox.repo_url + '/A/D/H', |
| H_COPY_2_path) |
| |
| # Commit all the previous merges as r9. |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending'), |
| 'A_COPY/D/gamma' : Item(verb='Sending'), |
| 'A_COPY_2' : Item(verb='Sending'), |
| 'A_COPY_2/B/E/beta' : Item(verb='Sending'), |
| 'A_COPY_2/D/H' : Item(verb='Sending'), |
| 'A_COPY_2/D/H/omega' : Item(verb='Sending'), |
| 'A_COPY_2/D/H/psi' : Item(verb='Sending'), |
| 'A_COPY_2/D/gamma' : Item(verb='Sending'), |
| }) |
| wc_status.tweak('A_COPY', |
| 'A_COPY/D/H/psi', |
| 'A_COPY/D/gamma', |
| 'A_COPY_2', |
| 'A_COPY_2/B/E/beta', |
| 'A_COPY_2/D/H', |
| 'A_COPY_2/D/H/omega', |
| 'A_COPY_2/D/H/psi', |
| 'A_COPY_2/D/gamma', |
| wc_rev=9) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| wc_status) |
| |
| # Update the WC. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], |
| 'update', wc_dir) |
| |
| # Make sure we have mergeinfo that meets the two criteria set out above. |
| check_mergeinfo_recursively(wc_dir, |
| { # Criterion 1 |
| A_COPY_path: '/A:8', |
| psi_COPY_path: '/A/D/H/psi:4', |
| # Criterion 2 |
| A_COPY_2_path : '/A:3-4,6-8', |
| H_COPY_2_path : '/A/D/H:3-7' }) |
| |
| # Merging to the criterion 2 branch. |
| # |
| # Forward merge a range to a target with a subtree where the target |
| # and subtree need different, non-intersecting revision ranges applied: |
| # Merge r3:9 from A into A_COPY_2. |
| # |
| # The subtree A_COPY_2/D/H needs r8-9 applied (affecting A_COPY_2/D/H/psi) |
| # while the target needs r5 (affecting A_COPY_2/D/G/rho) applied. The |
| # resulting mergeinfo on A_COPY_2 and A_COPY_2/D/H should be equivalent |
| # and therefore elide to A_COPY_2. |
| expected_output = wc.State(A_COPY_2_path, { |
| 'D/G/rho' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_2_path, { |
| '' : Item(status=' U'), |
| 'D/H' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_2_path, { |
| 'D/H' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_2_path, { |
| '' : Item(status=' M', wc_rev=9), |
| 'B' : Item(status=' ', wc_rev=9), |
| 'mu' : Item(status=' ', wc_rev=9), |
| 'B/E' : Item(status=' ', wc_rev=9), |
| 'B/E/alpha' : Item(status=' ', wc_rev=9), |
| 'B/E/beta' : Item(status=' ', wc_rev=9), |
| 'B/lambda' : Item(status=' ', wc_rev=9), |
| 'B/F' : Item(status=' ', wc_rev=9), |
| 'C' : Item(status=' ', wc_rev=9), |
| 'D' : Item(status=' ', wc_rev=9), |
| 'D/G' : Item(status=' ', wc_rev=9), |
| 'D/G/pi' : Item(status=' ', wc_rev=9), |
| 'D/G/rho' : Item(status='M ', wc_rev=9), |
| 'D/G/tau' : Item(status=' ', wc_rev=9), |
| 'D/gamma' : Item(status=' ', wc_rev=9), |
| 'D/H' : Item(status=' M', wc_rev=9), |
| 'D/H/chi' : Item(status=' ', wc_rev=9), |
| 'D/H/psi' : Item(status='M ', wc_rev=9), |
| 'D/H/omega' : Item(status=' ', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3-9'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("New content"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("Even newer content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_2_path, {}) |
| svntest.actions.run_and_verify_merge(A_COPY_2_path, '3', '9', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Merging to the criterion 1 branch. |
| # |
| # Reverse merge a range to a target with a subtree where the target |
| # and subtree need different, non-intersecting revision ranges |
| # reversed: Merge r9:3 from A into A_COPY. |
| # |
| # The subtree A_COPY_2/D/H/psi needs r4 reversed, while the target needs |
| # r8 (affecting A_COPY/D/gamma) reversed. Since this reverses all merges |
| # thus far to A_COPY, there should be *no* mergeinfo post merge. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/gamma' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H/psi' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H/psi' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=9), |
| 'B' : Item(status=' ', wc_rev=9), |
| 'mu' : Item(status=' ', wc_rev=9), |
| 'B/E' : Item(status=' ', wc_rev=9), |
| 'B/E/alpha' : Item(status=' ', wc_rev=9), |
| 'B/E/beta' : Item(status=' ', wc_rev=9), |
| 'B/lambda' : Item(status=' ', wc_rev=9), |
| 'B/F' : Item(status=' ', wc_rev=9), |
| 'C' : Item(status=' ', wc_rev=9), |
| 'D' : Item(status=' ', wc_rev=9), |
| 'D/G' : Item(status=' ', wc_rev=9), |
| 'D/G/pi' : Item(status=' ', wc_rev=9), |
| 'D/G/rho' : Item(status=' ', wc_rev=9), |
| 'D/G/tau' : Item(status=' ', wc_rev=9), |
| 'D/gamma' : Item(status='M ', wc_rev=9), |
| 'D/H' : Item(status=' ', wc_rev=9), |
| 'D/H/chi' : Item(status=' ', wc_rev=9), |
| 'D/H/psi' : Item(status='MM', wc_rev=9), |
| 'D/H/omega' : Item(status=' ', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, {}) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '9', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Test the notification portion of issue #3199. |
| # |
| # run_and_verify_merge() doesn't check the notification headers |
| # so we need to repeat the previous two merges using |
| # run_and_verify_svn(...'merge'...) and expected_merge_output(). |
| # |
| ### TODO: Things are fairly ugly when it comes to testing the |
| ### merge notification headers. run_and_verify_merge*() |
| ### just ignores the notifications and in the few places |
| ### we use expected_merge_output() the order of notifications |
| ### and paths are not considered. In a perfect world we'd |
| ### have run_and_verify_merge() that addressed these |
| ### shortcomings (and allowed merges to file targets). |
| # |
| # Revert the previous merges. |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', wc_dir) |
| |
| # Repeat the forward merge |
| expected_output = expected_merge_output( |
| [[5],[8],[5,9]], |
| ['U %s\n' % (rho_COPY_2_path), |
| 'U %s\n' % (psi_COPY_2_path), |
| ' U %s\n' % (H_COPY_2_path), |
| ' U %s\n' % (A_COPY_2_path),], |
| elides=True) |
| svntest.actions.run_and_verify_svn(expected_output, |
| [], 'merge', '-r', '3:9', |
| sbox.repo_url + '/A', |
| A_COPY_2_path) |
| # Repeat the reverse merge |
| expected_output = expected_merge_output( |
| [[-4],[-8],[8,4]], |
| ['U %s\n' % (gamma_COPY_path), |
| 'U %s\n' % (psi_COPY_path), |
| ' U %s\n' % (A_COPY_path), |
| ' U %s\n' % (psi_COPY_path)], |
| elides=True) |
| svntest.actions.run_and_verify_svn(expected_output, |
| [], 'merge', '-r', '9:3', |
| sbox.repo_url + '/A', |
| A_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| # Some more tests for issue #3067 'subtrees that don't exist at the start |
| # or end of a merge range shouldn't break the merge' |
| @Issue(3067) |
| @SkipUnless(server_has_mergeinfo) |
| def subtree_source_missing_in_requested_range(sbox): |
| "subtree merge source might not exist" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Make a branch to merge to. |
| wc_disk, wc_status = set_up_branch(sbox, False, 1) |
| |
| # Some paths we'll care about. |
| psi_path = sbox.ospath('A/D/H/psi') |
| omega_path = sbox.ospath('A/D/H/omega') |
| A_COPY_path = sbox.ospath('A_COPY') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| omega_COPY_path = sbox.ospath('A_COPY/D/H/omega') |
| |
| # r7 Delete A/D/H/psi. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'delete', psi_path) |
| sbox.simple_commit(message='delete psi') |
| |
| # r8 - modify A/D/H/omega. |
| svntest.main.file_write(os.path.join(omega_path), "Even newer content") |
| sbox.simple_commit(message='modify omega') |
| |
| # r9 - Merge r3 to A_COPY/D/H/psi |
| expected_output = expected_merge_output( |
| [[3]], ['U %s\n' % (psi_COPY_path), |
| ' U %s\n' % (psi_COPY_path),]) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '-c', '3', |
| sbox.repo_url + '/A/D/H/psi@3', |
| psi_COPY_path) |
| sbox.simple_commit(message='merge r3 to A_COPY/D/H/psi') |
| |
| # r10 - Merge r6 to A_COPY/D/H/omega. |
| expected_output = expected_merge_output( |
| [[6]], ['U %s\n' % (omega_COPY_path), |
| ' U %s\n' % (omega_COPY_path),]) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '-c', '6', |
| sbox.repo_url + '/A/D/H/omega', |
| omega_COPY_path) |
| sbox.simple_commit(message='merge r6 to A_COPY') |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(10), [], 'up', |
| wc_dir) |
| |
| # r11 - Merge r8 to A_COPY. |
| expected_output = expected_merge_output( |
| [[8]], ['U %s\n' % (omega_COPY_path), |
| ' U %s\n' % (omega_COPY_path), |
| ' U %s\n' % (A_COPY_path)]) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '-c', '8', |
| sbox.repo_url + '/A', |
| A_COPY_path) |
| # Repeat the merge using the --record-only option so A_COPY/D/H/psi gets |
| # mergeinfo including 'A/D/H/psi:8', which doesn't exist. Why? Because |
| # we are trying to create mergeinfo that will provoke an invalid editor |
| # drive. In 1.5-1.6 merge updated all subtrees, regardless of whether the |
| # merge touched these subtrees. This --record-only merge duplicates that |
| # behavior, allowing us to test the relevant issue #3067 fixes. |
| expected_output = expected_merge_output( |
| [[8]], [' G %s\n' % (omega_COPY_path), |
| ' U %s\n' % (psi_COPY_path), |
| ' G %s\n' % (A_COPY_path)]) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '-c', '8', |
| sbox.repo_url + '/A', |
| A_COPY_path, '--record-only') |
| sbox.simple_commit(message='merge r8 to A_COPY/D/H/omega') |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(11), [], 'up', |
| wc_dir) |
| |
| # r12 - modify A/D/H/omega yet again. |
| svntest.main.file_write(os.path.join(omega_path), |
| "Now with fabulous new content!") |
| sbox.simple_commit(message='modify omega') |
| |
| # r13 - Merge all available revs to A_COPY/D/H/omega. |
| expected_output = expected_merge_output( |
| [[9,12],[2,12]], ['U %s\n' % (omega_COPY_path), |
| ' U %s\n' % (omega_COPY_path)]) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', |
| sbox.repo_url + '/A/D/H/omega', |
| omega_COPY_path) |
| sbox.simple_commit(message='cherry harvest to A_COPY/D/H/omega') |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(13), [], 'up', |
| wc_dir) |
| |
| # Check that svn:mergeinfo is as expected. |
| check_mergeinfo_recursively(wc_dir, |
| { A_COPY_path: '/A:8', |
| omega_COPY_path: '/A/D/H/omega:2-12', |
| psi_COPY_path : '/A/D/H/psi:3,8' }) |
| |
| # Now test a reverse merge where part of the requested range postdates |
| # a subtree's existence. Merge -r12:1 to A_COPY. This should revert |
| # all of the merges done thus far. The fact that A/D/H/psi no longer |
| # exists after r7 shouldn't break the subtree merge into A_COPY/D/H/psi. |
| # A_COPY/D/H/psi should simply have r3 reverse merged. No paths under |
| # in the tree rooted at A_COPY should have any explicit mergeinfo. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/H/omega' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| 'D/H/omega' : Item(status='G ', prev_status='G '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H/psi' : Item(status=' U'), |
| 'D/H/omega' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H/psi' : Item(status=' U'), |
| 'D/H/omega' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=13), |
| 'B' : Item(status=' ', wc_rev=13), |
| 'mu' : Item(status=' ', wc_rev=13), |
| 'B/E' : Item(status=' ', wc_rev=13), |
| 'B/E/alpha' : Item(status=' ', wc_rev=13), |
| 'B/E/beta' : Item(status=' ', wc_rev=13), |
| 'B/lambda' : Item(status=' ', wc_rev=13), |
| 'B/F' : Item(status=' ', wc_rev=13), |
| 'C' : Item(status=' ', wc_rev=13), |
| 'D' : Item(status=' ', wc_rev=13), |
| 'D/G' : Item(status=' ', wc_rev=13), |
| 'D/G/pi' : Item(status=' ', wc_rev=13), |
| 'D/G/rho' : Item(status=' ', wc_rev=13), |
| 'D/G/tau' : Item(status=' ', wc_rev=13), |
| 'D/gamma' : Item(status=' ', wc_rev=13), |
| 'D/H' : Item(status=' ', wc_rev=13), |
| 'D/H/chi' : Item(status=' ', wc_rev=13), |
| 'D/H/psi' : Item(status='MM', wc_rev=13), |
| 'D/H/omega' : Item(status='MM', wc_rev=13), |
| }) |
| expected_disk = wc.State('', { |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '12', '1', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False) |
| |
| # Revert the previous merge. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '-R', wc_dir) |
| # Merge r12 to A_COPY and commit as r14. |
| expected_output = wc.State(A_COPY_path, {}) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=13), |
| 'B' : Item(status=' ', wc_rev=13), |
| 'mu' : Item(status=' ', wc_rev=13), |
| 'B/E' : Item(status=' ', wc_rev=13), |
| 'B/E/alpha' : Item(status=' ', wc_rev=13), |
| 'B/E/beta' : Item(status=' ', wc_rev=13), |
| 'B/lambda' : Item(status=' ', wc_rev=13), |
| 'B/F' : Item(status=' ', wc_rev=13), |
| 'C' : Item(status=' ', wc_rev=13), |
| 'D' : Item(status=' ', wc_rev=13), |
| 'D/G' : Item(status=' ', wc_rev=13), |
| 'D/G/pi' : Item(status=' ', wc_rev=13), |
| 'D/G/rho' : Item(status=' ', wc_rev=13), |
| 'D/G/tau' : Item(status=' ', wc_rev=13), |
| 'D/gamma' : Item(status=' ', wc_rev=13), |
| 'D/H' : Item(status=' ', wc_rev=13), |
| 'D/H/chi' : Item(status=' ', wc_rev=13), |
| 'D/H/psi' : Item(status=' ', wc_rev=13), |
| 'D/H/omega' : Item(status=' ', wc_rev=13), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:8,12'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/D/H/psi:3,8'}), |
| 'D/H/omega' : Item("Now with fabulous new content!", |
| props={SVN_PROP_MERGEINFO : '/A/D/H/omega:2-12'}), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '11', '12', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False) |
| # As we did earlier, repeat the merge with the --record-only option to |
| # preserve the old behavior of recording mergeinfo on every subtree, thus |
| # allowing this test to actually test the issue #3067 fixes. |
| expected_output = expected_merge_output( |
| [[12]], ['U %s\n' % (A_COPY_path), |
| ' G %s\n' % (A_COPY_path), |
| ' U %s\n' % (psi_COPY_path), |
| ' U %s\n' % (omega_COPY_path),]) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '-c', '12', |
| sbox.repo_url + '/A', |
| A_COPY_path, '--record-only') |
| sbox.simple_commit(message='Merge r12 to A_COPY') |
| |
| # Update A_COPY/D/H/rho back to r13 so it's mergeinfo doesn't include |
| # r12. Then merge a range, -r6:12 which should delete a subtree |
| # (A_COPY/D/H/psi). |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(14), [], 'up', |
| wc_dir) |
| expected_output = wc.State(A_COPY_path, { |
| 'D/H/psi' : Item(status='D '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=14), |
| 'B' : Item(status=' ', wc_rev=14), |
| 'mu' : Item(status=' ', wc_rev=14), |
| 'B/E' : Item(status=' ', wc_rev=14), |
| 'B/E/alpha' : Item(status=' ', wc_rev=14), |
| 'B/E/beta' : Item(status=' ', wc_rev=14), |
| 'B/lambda' : Item(status=' ', wc_rev=14), |
| 'B/F' : Item(status=' ', wc_rev=14), |
| 'C' : Item(status=' ', wc_rev=14), |
| 'D' : Item(status=' ', wc_rev=14), |
| 'D/G' : Item(status=' ', wc_rev=14), |
| 'D/G/pi' : Item(status=' ', wc_rev=14), |
| 'D/G/rho' : Item(status=' ', wc_rev=14), |
| 'D/G/tau' : Item(status=' ', wc_rev=14), |
| 'D/gamma' : Item(status=' ', wc_rev=14), |
| 'D/H' : Item(status=' ', wc_rev=14), |
| 'D/H/chi' : Item(status=' ', wc_rev=14), |
| 'D/H/psi' : Item(status='D ', wc_rev=14), |
| 'D/H/omega' : Item(status=' ', wc_rev=14), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:7-12'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/omega' : Item("Now with fabulous new content!", |
| props={SVN_PROP_MERGEINFO : '/A/D/H/omega:2-12'}), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '6', '12', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False) |
| |
| #---------------------------------------------------------------------- |
| # Another test for issue #3067: 'subtrees that don't exist at the start |
| # or end of a merge range shouldn't break the merge' |
| # |
| # See https://issues.apache.org/jira/browse/SVN-3067#desc34 |
| @Issue(3067) |
| @SkipUnless(server_has_mergeinfo) |
| def subtrees_with_empty_mergeinfo(sbox): |
| "mergeinfo not set on subtree with empty mergeinfo" |
| |
| # Use helper to setup a renamed subtree. |
| wc_dir, expected_disk, expected_status = set_up_renamed_subtree(sbox) |
| |
| # Some paths we'll care about |
| H_COPY_path = sbox.ospath('H_COPY') |
| |
| # Cherry harvest all available revsions from 'A/D/H' to 'H_COPY'. |
| # |
| # This should merge r4:6 from 'A/D/H' setting mergeinfo for r5-6 |
| # on both 'H_COPY' and 'H_COPY/psi_moved'. But since the working copy |
| # is at a uniform working revision, the latter's mergeinfo should |
| # elide, leaving explicit mergeinfo only on the merge target. |
| expected_output = wc.State(H_COPY_path, { |
| 'psi_moved' : Item(status='U ') |
| }) |
| expected_mergeinfo_output = wc.State(H_COPY_path, { |
| '' : Item(status=' U'), |
| 'psi_moved' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(H_COPY_path, { |
| 'psi_moved' : Item(status=' U'), |
| }) |
| expected_status = wc.State(H_COPY_path, { |
| '' : Item(status=' M', wc_rev=6), # mergeinfo set on target |
| 'psi_moved' : Item(status='MM', wc_rev=6), # mergeinfo elides |
| 'omega' : Item(status=' ', wc_rev=6), |
| 'chi' : Item(status=' ', wc_rev=6), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-6'}), |
| 'psi_moved' : Item("Even *Newer* content"), # mergeinfo elides |
| 'omega' : Item("New omega"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| }) |
| expected_skip = wc.State(H_COPY_path, { }) |
| |
| svntest.actions.run_and_verify_merge(H_COPY_path, None, None, |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3240 'commits to subtrees added by merge |
| # corrupt working copy and repos'. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3240) |
| def commit_to_subtree_added_by_merge(sbox): |
| "commits to subtrees added by merge wreak havoc" |
| |
| # Setup a standard greek tree in r1. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| N_path = sbox.ospath('A/D/H/N') |
| nu_path = sbox.ospath('A/D/H/N/nu') |
| nu_COPY_path = sbox.ospath('A_COPY/D/H/N/nu') |
| H_COPY_path = sbox.ospath('A_COPY/D/H') |
| |
| # Copy 'A' to 'A_COPY' in r2. |
| wc_disk, wc_status = set_up_branch(sbox, True) |
| |
| # Create a 'A/D/H/N' and 'A/D/H/N/nu', and commit this new |
| # subtree as r3. |
| os.mkdir(N_path) |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', N_path) |
| expected_output = wc.State(wc_dir, |
| {'A/D/H/N' : Item(verb='Adding'), |
| 'A/D/H/N/nu' : Item(verb='Adding')}) |
| wc_status.add({'A/D/H/N' : Item(status=' ', wc_rev=3), |
| 'A/D/H/N/nu' : Item(status=' ', wc_rev=3)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Merge r3 to 'A_COPY/D/H', creating A_COPY/D/H/N' and 'A_COPY/D/H/N/nu'. |
| # Commit the merge as r4. |
| expected_output = wc.State(H_COPY_path, { |
| 'N' : Item(status='A '), |
| 'N/nu' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(H_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(H_COPY_path, { |
| }) |
| expected_status = wc.State(H_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'psi' : Item(status=' ', wc_rev=2), |
| 'omega' : Item(status=' ', wc_rev=2), |
| 'chi' : Item(status=' ', wc_rev=2), |
| 'N' : Item(status='A ', copied='+', wc_rev='-'), |
| 'N/nu' : Item(status=' ', copied='+', wc_rev='-'), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:2-3'}), |
| 'psi' : Item("This is the file 'psi'.\n"), |
| 'omega' : Item("This is the file 'omega'.\n"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| 'N' : Item(), |
| 'N/nu' : Item("This is the file 'nu'.\n"), |
| }) |
| expected_skip = wc.State(H_COPY_path, {}) |
| svntest.actions.run_and_verify_merge(H_COPY_path, |
| None, None, |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY/D/H' : Item(verb='Sending'), |
| 'A_COPY/D/H/N' : Item(verb='Adding'), |
| }) |
| wc_status.add({'A_COPY/D/H/N' : Item(status=' ', wc_rev=4), |
| 'A_COPY/D/H/N/nu' : Item(status=' ', wc_rev=4)}) |
| wc_status.tweak('A_COPY/D/H', wc_rev=4) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Make a text change to 'A_COPY/D/H/N/nu' and commit it as r5. This |
| # is the first place issue #3240 appears over DAV layers, and the |
| # commit fails with an error like this: |
| # trunk>svn ci -m "" merge_tests-100 |
| # Sending merge_tests-100\A_COPY\D\H\N\nu |
| # Transmitting file data ...\..\..\subversion\libsvn_client\commit.c:919: |
| # (apr_err=20014) |
| # svn: Commit failed (details follow): |
| # ..\..\..\subversion\libsvn_ra_neon\merge.c:260: (apr_err=20014) |
| # svn: A MERGE response for '/svn-test-work/repositories/merge_tests-100/ |
| # A/D/H/N/nu' is not a child of the destination |
| # ('/svn-test-work/repositories/merge_tests-100/A_COPY/D/H/N') |
| svntest.main.file_write(nu_COPY_path, "New content") |
| expected_output = wc.State(wc_dir, |
| {'A_COPY/D/H/N/nu' : Item(verb='Sending')}) |
| wc_status.tweak('A_COPY/D/H/N/nu', wc_rev=5) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| # The second place issue #3240 shows up is in the fact that the commit |
| # *did* succeed, but the wrong path ('A/D/H/nu' rather than 'A_COPY/D/H/nu') |
| # is affected. We can see this by running an update; since we just |
| # committed there shouldn't be any incoming changes. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(5), [], 'up', |
| wc_dir) |
| |
| |
| #---------------------------------------------------------------------- |
| # Tests for merging the deletion of a node, where the node to be deleted |
| # is the same as or different from the node that was deleted. |
| |
| #---------------------------------------------------------------------- |
| def del_identical_file(sbox): |
| "merge tries to delete a file of identical content" |
| |
| # Set up a standard greek tree in r1. |
| sbox.build() |
| |
| saved_cwd = os.getcwd() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| # Set up a modification and deletion in the source branch. |
| source = 'A/D/G' |
| s_rev_orig = 1 |
| svn_modfile(source+"/tau") |
| sbox.simple_commit(source) |
| s_rev_mod = 2 |
| sbox.simple_rm(source+"/tau") |
| sbox.simple_commit(source) |
| s_rev_del = 3 |
| |
| # Make an identical copy, and merge a deletion to it. |
| target = 'A/D/G2' |
| svn_copy(s_rev_mod, source, target) |
| sbox.simple_commit(target) |
| # Should be deleted quietly. |
| svn_merge(s_rev_del, source, target, |
| ['D %s\n' % local_path('A/D/G2/tau')]) |
| |
| # Make a differing copy, locally modify it so it's the same, |
| # and merge a deletion to it. |
| target = 'A/D/G3' |
| svn_copy(s_rev_orig, source, target) |
| sbox.simple_commit(target) |
| svn_modfile(target+"/tau") |
| # Should be deleted quietly. |
| svn_merge(s_rev_del, source, target, |
| ['D %s\n' % local_path('A/D/G3/tau')]) |
| |
| os.chdir(saved_cwd) |
| |
| #---------------------------------------------------------------------- |
| def del_sched_add_hist_file(sbox): |
| "merge tries to delete identical sched-add file" |
| |
| # Setup a standard greek tree in r1. |
| sbox.build() |
| |
| saved_cwd = os.getcwd() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| # Set up a creation in the source branch. |
| source = 'A/D/G' |
| s_rev_orig = 1 |
| svn_mkfile(source+"/file") |
| sbox.simple_commit(source) |
| s_rev_add = 2 |
| |
| # Merge a creation, and delete by reverse-merging into uncommitted WC. |
| target = 'A/D/G2' |
| svn_copy(s_rev_orig, source, target) |
| sbox.simple_commit(target) |
| s_rev = 3 |
| svn_merge(s_rev_add, source, target, |
| ['A %s\n' % local_path('A/D/G2/file')]) |
| # Should be deleted quietly. |
| svn_merge(-s_rev_add, source, target, |
| ['D %s\n' % local_path('A/D/G2/file')], elides=['A/D/G2']) |
| |
| os.chdir(saved_cwd) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def subtree_merges_dont_cause_spurious_conflicts(sbox): |
| "subtree merges dont cause spurious conflicts" |
| |
| # Fix a merge bug where previous merges are incorrectly reversed leading |
| # to repeat merges and spurious conflicts. These can occur when a subtree |
| # needs a range M:N merged that is older than the ranges X:Y needed by the |
| # merge target *and* there are changes in the merge source between N:X that |
| # affect parts of the merge target other than the subtree. An actual case |
| # where our own epository encountered this problem is described here: |
| # http://subversion.tigris.org/servlets/ReadMsg?listName=dev&msgNo=141832 |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| rho_path = sbox.ospath('A/D/G/rho') |
| A_COPY_path = sbox.ospath('A_COPY') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| |
| # Make a branch to merge to. |
| wc_disk, wc_status = set_up_branch(sbox, False, 1) |
| |
| # r7 Make a text change to A/D/G/rho. |
| svntest.main.file_write(rho_path, "Newer content") |
| expected_output = wc.State(wc_dir, {'A/D/G/rho' : Item(verb='Sending')}) |
| wc_status.tweak('A/D/G/rho', wc_rev=7) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| wc_disk.tweak('A/D/G/rho', contents="Newer content") |
| |
| # r8 Make another text change to A/D/G/rho. |
| svntest.main.file_write(rho_path, "Even *newer* content") |
| expected_output = wc.State(wc_dir, {'A/D/G/rho' : Item(verb='Sending')}) |
| wc_status.tweak('A/D/G/rho', wc_rev=8) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| wc_disk.tweak('A/D/G/rho', contents="Even *newer* content") |
| |
| # Update the WC to allow full mergeinfo inheritance and elision. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], 'up', |
| wc_dir) |
| wc_status.tweak(wc_rev=8) |
| |
| # r9 Merge r0:7 from A to A_COPY, then create a subtree with differing |
| # mergeinfo under A_COPY by reverse merging r3 from A_COPY/D/H/psi. |
| expected_output = wc.State(A_COPY_path, { |
| 'B/E/beta' : Item(status='U '), |
| 'D/G/rho' : Item(status='U '), |
| 'D/H/omega' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=8), |
| 'B' : Item(status=' ', wc_rev=8), |
| 'mu' : Item(status=' ', wc_rev=8), |
| 'B/E' : Item(status=' ', wc_rev=8), |
| 'B/E/alpha' : Item(status=' ', wc_rev=8), |
| 'B/E/beta' : Item(status='M ', wc_rev=8), |
| 'B/lambda' : Item(status=' ', wc_rev=8), |
| 'B/F' : Item(status=' ', wc_rev=8), |
| 'C' : Item(status=' ', wc_rev=8), |
| 'D' : Item(status=' ', wc_rev=8), |
| 'D/G' : Item(status=' ', wc_rev=8), |
| 'D/G/pi' : Item(status=' ', wc_rev=8), |
| 'D/G/rho' : Item(status='M ', wc_rev=8), |
| 'D/G/tau' : Item(status=' ', wc_rev=8), |
| 'D/gamma' : Item(status=' ', wc_rev=8), |
| 'D/H' : Item(status=' ', wc_rev=8), |
| 'D/H/chi' : Item(status=' ', wc_rev=8), |
| 'D/H/psi' : Item(status='M ', wc_rev=8), |
| 'D/H/omega' : Item(status='M ', wc_rev=8), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-7'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("Newer content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content",), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '0', '7', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| # run_and_verify_merge doesn't support merging to a file WCPATH |
| # so use run_and_verify_svn. |
| ### TODO: We can use run_and_verify_merge() here now. |
| svntest.actions.run_and_verify_svn(expected_merge_output([[-3]], |
| ['G ' + psi_COPY_path + '\n', |
| ' G ' + psi_COPY_path + '\n']), |
| [], 'merge', '-c-3', |
| sbox.repo_url + '/A/D/H/psi', |
| psi_COPY_path) |
| # Commit the two merges. |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/B/E/beta' : Item(verb='Sending'), |
| 'A_COPY/D/G/rho' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending'), |
| 'A_COPY/D/H/omega' : Item(verb='Sending'), |
| }) |
| wc_status.tweak('A_COPY', |
| 'A_COPY/B/E/beta', |
| 'A_COPY/D/G/rho', |
| 'A_COPY/D/H/psi', |
| 'A_COPY/D/H/omega', |
| wc_rev=9) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Update the WC to allow full mergeinfo inheritance and elision. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], 'up', |
| wc_dir) |
| wc_status.tweak(wc_rev=9) |
| |
| # r9 Merge all available revisions from A to A_COPY. |
| # |
| # This is where the bug revealed itself, instead of cleanly merging |
| # just r3 and then r8-9, the first merge editor drive of r3 set A_COPY |
| # to the state it was in r7, effectively reverting the merge committed |
| # in r9. So we saw unexpected merges to omega, rho, and beta, as they |
| # are returned to their r7 state and then a conflict on rho as the editor |
| # attempted to merge r8: |
| # |
| # trunk>svn merge %url%/A merge_tests-104\A_COPY |
| # --- Merging r3 into 'merge_tests-104\A_COPY\D\H\psi': |
| # U merge_tests-104\A_COPY\D\H\psi |
| # --- Merging r8 through r9 into 'merge_tests-104\A_COPY': |
| # U merge_tests-104\A_COPY\D\H\omega |
| # U merge_tests-104\A_COPY\D\G\rho |
| # U merge_tests-104\A_COPY\B\E\beta |
| # Conflict discovered in 'merge_tests-104/A_COPY/D/G/rho'. |
| # Select: (p) postpone, (df) diff-full, (e) edit, |
| # (mc) mine-conflict, (tc) theirs-conflict, |
| # (s) show all options: p |
| # --- Merging r8 through r9 into 'merge_tests-104\A_COPY': |
| # C merge_tests-104\A_COPY\D\G\rho |
| expected_output = wc.State(A_COPY_path, { |
| 'D/G/rho' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H/psi' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| 'D/H/psi' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=9), |
| 'B' : Item(status=' ', wc_rev=9), |
| 'mu' : Item(status=' ', wc_rev=9), |
| 'B/E' : Item(status=' ', wc_rev=9), |
| 'B/E/alpha' : Item(status=' ', wc_rev=9), |
| 'B/E/beta' : Item(status=' ', wc_rev=9), |
| 'B/lambda' : Item(status=' ', wc_rev=9), |
| 'B/F' : Item(status=' ', wc_rev=9), |
| 'C' : Item(status=' ', wc_rev=9), |
| 'D' : Item(status=' ', wc_rev=9), |
| 'D/G' : Item(status=' ', wc_rev=9), |
| 'D/G/pi' : Item(status=' ', wc_rev=9), |
| 'D/G/rho' : Item(status='M ', wc_rev=9), |
| 'D/G/tau' : Item(status=' ', wc_rev=9), |
| 'D/gamma' : Item(status=' ', wc_rev=9), |
| 'D/H' : Item(status=' ', wc_rev=9), |
| 'D/H/chi' : Item(status=' ', wc_rev=9), |
| 'D/H/psi' : Item(status='MM', wc_rev=9), |
| 'D/H/omega' : Item(status=' ', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-9'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("Even *newer* content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), # Mergeinfo elides to A_COPY |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Test for yet another variant of issue #3067. |
| @Issue(3067) |
| @SkipUnless(server_has_mergeinfo) |
| def merge_target_and_subtrees_need_nonintersecting_ranges(sbox): |
| "target and subtrees need nonintersecting revs" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| nu_path = sbox.ospath('A/D/G/nu') |
| A_COPY_path = sbox.ospath('A_COPY') |
| nu_COPY_path = sbox.ospath('A_COPY/D/G/nu') |
| omega_COPY_path = sbox.ospath('A_COPY/D/H/omega') |
| beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') |
| rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| |
| # Make a branch to merge to. |
| wc_disk, wc_status = set_up_branch(sbox, False, 1) |
| |
| # Add file A/D/G/nu in r7. |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| expected_output = wc.State(wc_dir, {'A/D/G/nu' : Item(verb='Adding')}) |
| wc_status.add({'A/D/G/nu' : Item(status=' ', wc_rev=7)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Make a text mod to A/D/G/nu in r8. |
| svntest.main.file_write(nu_path, "New content") |
| expected_output = wc.State(wc_dir, {'A/D/G/nu' : Item(verb='Sending')}) |
| wc_status.tweak('A/D/G/nu', wc_rev=8) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # Do several merges to setup a situation where the merge |
| # target and two of its subtrees need non-intersecting ranges |
| # merged when doing a synch (a.k.a. cherry harvest) merge. |
| # |
| # 1) Merge -r0:7 from A to A_COPY. |
| # |
| # 2) Merge -c8 from A/D/G/nu to A_COPY/D/G/nu. |
| # |
| # 3) Merge -c-6 from A/D/H/omega to A_COPY/D/H/omega. |
| # |
| # Commit this group of merges as r9. Since we already test these type |
| # of merges to death we don't use run_and_verify_merge() on these |
| # intermediate merges. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[2,7]], |
| ['U ' + beta_COPY_path + '\n', |
| 'A ' + nu_COPY_path + '\n', |
| 'U ' + rho_COPY_path + '\n', |
| 'U ' + omega_COPY_path + '\n', |
| 'U ' + psi_COPY_path + '\n', |
| ' U ' + A_COPY_path + '\n',] |
| ), |
| [], 'merge', '-r0:7', sbox.repo_url + '/A', A_COPY_path) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[8]], ['U ' + nu_COPY_path + '\n', |
| ' G ' + nu_COPY_path + '\n']), |
| [], 'merge', '-c8', sbox.repo_url + '/A/D/G/nu', nu_COPY_path) |
| |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[-6]], ['G ' + omega_COPY_path + '\n', |
| ' G ' + omega_COPY_path + '\n']), |
| [], 'merge', '-c-6', sbox.repo_url + '/A/D/H/omega', omega_COPY_path) |
| wc_status.add({'A_COPY/D/G/nu' : Item(status=' ', wc_rev=9)}) |
| wc_status.tweak('A_COPY', |
| 'A_COPY/B/E/beta', |
| 'A_COPY/D/G/rho', |
| 'A_COPY/D/H/omega', |
| 'A_COPY/D/H/psi', |
| wc_rev=9) |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/B/E/beta' : Item(verb='Sending'), |
| 'A_COPY/D/G/rho' : Item(verb='Sending'), |
| 'A_COPY/D/G/nu' : Item(verb='Adding'), |
| 'A_COPY/D/H/omega' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Update the WC to allow full mergeinfo inheritance and elision. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], 'up', |
| wc_dir) |
| |
| # Merge all available revisions from A to A_COPY, the merge logic |
| # should handle this situation (no "svn: Working copy path 'D/G/nu' |
| # does not exist in repository" errors!). The mergeinfo on |
| # A_COPY/D/H/omega elides to the root, but the mergeinfo on |
| # A_COPY/D/G/nu, untouched by the merge, does not get updated so |
| # does not elide. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/H/omega': Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H/omega': Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| 'D/H/omega': Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=9), |
| 'B' : Item(status=' ', wc_rev=9), |
| 'mu' : Item(status=' ', wc_rev=9), |
| 'B/E' : Item(status=' ', wc_rev=9), |
| 'B/E/alpha' : Item(status=' ', wc_rev=9), |
| 'B/E/beta' : Item(status=' ', wc_rev=9), |
| 'B/lambda' : Item(status=' ', wc_rev=9), |
| 'B/F' : Item(status=' ', wc_rev=9), |
| 'C' : Item(status=' ', wc_rev=9), |
| 'D' : Item(status=' ', wc_rev=9), |
| 'D/G' : Item(status=' ', wc_rev=9), |
| 'D/G/pi' : Item(status=' ', wc_rev=9), |
| 'D/G/rho' : Item(status=' ', wc_rev=9), |
| 'D/G/tau' : Item(status=' ', wc_rev=9), |
| 'D/G/nu' : Item(status=' ', wc_rev=9), |
| 'D/gamma' : Item(status=' ', wc_rev=9), |
| 'D/H' : Item(status=' ', wc_rev=9), |
| 'D/H/chi' : Item(status=' ', wc_rev=9), |
| 'D/H/psi' : Item(status=' ', wc_rev=9), |
| 'D/H/omega' : Item(status='MM', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-9'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/G/nu' : Item("New content", |
| props={SVN_PROP_MERGEINFO : '/A/D/G/nu:2-8'}), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Part of this test is a regression test for issue #3250 "Repeated merging |
| # of conflicting properties fails". |
| @Issue(3250) |
| def merge_two_edits_to_same_prop(sbox): |
| "merge two successive edits to the same property" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Make a branch to merge to. (This is r6.) |
| wc_disk, wc_status = set_up_branch(sbox, False, 1) |
| initial_rev = 6 |
| |
| # Change into the WC dir for convenience |
| was_cwd = os.getcwd() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_disk.wc_dir = '' |
| wc_status.wc_dir = '' |
| |
| # Some paths we'll care about |
| A_path = "A" |
| A_COPY_path = "A_COPY" |
| mu_path = os.path.join(A_path, "mu") |
| mu_COPY_path = os.path.join(A_COPY_path, "mu") |
| |
| # In the source, make two successive changes to the same property |
| sbox.simple_propset('p', 'new-val-1', 'A/mu') |
| sbox.simple_commit('A/mu') |
| rev1 = initial_rev + 1 |
| sbox.simple_propset('p', 'new-val-2', 'A/mu') |
| sbox.simple_commit('A/mu') |
| rev2 = initial_rev + 2 |
| |
| # Merge the first change, then the second, to a target branch. |
| svn_merge(rev1, A_path, A_COPY_path) |
| svn_merge(rev2, A_path, A_COPY_path) |
| |
| # Both changes should merge automatically: the second one should not |
| # complain about the local mod which the first one caused. The starting |
| # value in the target ("mine") for the second merge is exactly equal to |
| # the merge-left source value. |
| |
| # A merge-tracking version of this problem is when the merge-tracking |
| # algorithm breaks a single requested merge into two phases because of |
| # some other target within the same merge requiring only a part of the |
| # revision range. |
| |
| # ==================================================================== |
| |
| # We test issue #3250 here: that is, test that we can make two successive |
| # conflicting changes to the same property on the same node (here a file; |
| # in #3250 it was on a dir). |
| # |
| # ### But we no longer support merging into a node that's already in |
| # conflict, and the 'rev3' merge here has been tweaked to resolve |
| # the conflict, so it no longer tests the original #3250 scenario. |
| # |
| # Revert changes to branch wc |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '--recursive', A_COPY_path) |
| |
| # In the branch, make two successive changes to the same property |
| sbox.simple_propset('p', 'new-val-3', 'A_COPY/mu') |
| sbox.simple_commit('A_COPY/mu') |
| rev3 = initial_rev + 3 |
| sbox.simple_propset('p', 'new-val-4', 'A_COPY/mu') |
| sbox.simple_commit('A_COPY/mu') |
| rev4 = initial_rev + 4 |
| |
| # Merge the two changes together to trunk. |
| svn_merge([rev3, rev4], A_COPY_path, A_path, [ |
| " C %s\n" % mu_path, |
| ], prop_conflicts=1, args=['--allow-mixed-revisions']) |
| |
| # Revert changes to trunk wc, to test next scenario of #3250 |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '--recursive', A_path) |
| |
| # Merge the first change, then the second, to trunk. |
| svn_merge(rev3, A_COPY_path, A_path, [ |
| " C %s\n" % mu_path, |
| "Resolved .* '%s'\n" % mu_path, |
| ], prop_resolved=1, |
| args=['--allow-mixed-revisions', |
| '--accept=working']) |
| svn_merge(rev4, A_COPY_path, A_path, [ |
| " C %s\n" % mu_path, |
| ], prop_conflicts=1, args=['--allow-mixed-revisions']) |
| |
| os.chdir(was_cwd) |
| |
| #---------------------------------------------------------------------- |
| def merge_an_eol_unification_and_set_svn_eol_style(sbox): |
| "merge an EOL unification and set svn:eol-style" |
| # In svn 1.5.2, merging the two changes between these three states: |
| # r1. inconsistent EOLs and no svn:eol-style |
| # r2. consistent EOLs and no svn:eol-style |
| # r3. consistent EOLs and svn:eol-style=native |
| # fails if attempted as a single merge (e.g. "svn merge r1:3") though it |
| # succeeds if attempted in two phases (e.g. "svn merge -c2,3"). |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Make a branch to merge to. (This will be r6.) |
| wc_disk, wc_status = set_up_branch(sbox, False, 1) |
| initial_rev = 6 |
| |
| # Change into the WC dir for convenience |
| was_cwd = os.getcwd() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_disk.wc_dir = '' |
| wc_status.wc_dir = '' |
| |
| content1 = 'Line1\nLine2\r\n' # write as 'binary' to get these exact EOLs |
| content2 = 'Line1\nLine2\n' # write as 'text' to get native EOLs in file |
| |
| # In the source branch, create initial state and two successive changes. |
| # Use binary mode to write the first file so no newline conversion occurs. |
| svntest.main.file_write('A/mu', content1, 'wb') |
| sbox.simple_commit('A/mu') |
| rev1 = initial_rev + 1 |
| # Use text mode to write the second copy of the file to get native EOLs. |
| svntest.main.file_write('A/mu', content2, 'w') |
| sbox.simple_commit('A/mu') |
| rev2 = initial_rev + 2 |
| sbox.simple_propset('svn:eol-style', 'native', 'A/mu') |
| sbox.simple_commit('A/mu') |
| rev3 = initial_rev + 3 |
| |
| # Merge the initial state (inconsistent EOLs) to the target branch. |
| svn_merge(rev1, 'A', 'A_COPY') |
| sbox.simple_commit('A_COPY') |
| |
| # Merge the two changes together to the target branch. |
| svn_merge([rev2, rev3], 'A', 'A_COPY', |
| args=['--allow-mixed-revisions']) |
| |
| # That merge should succeed. |
| # Surprise: setting svn:eol-style='LF' instead of 'native' doesn't fail. |
| # Surprise: if we don't merge the file's 'rev1' state first, it doesn't fail |
| # nor even raise a conflict. |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def merge_adds_mergeinfo_correctly(sbox): |
| "merge adds mergeinfo to subtrees correctly" |
| |
| # A merge may add explicit mergeinfo to the subtree of a merge target |
| # as a result of changes in the merge source. These paths may have |
| # inherited mergeinfo prior to the merge, if so the subtree should end up |
| # with mergeinfo that reflects all of the following: |
| # |
| # A) The mergeinfo added from the merge source |
| # |
| # B) The mergeinfo the subtree inherited prior to the merge. |
| # |
| # C) Mergeinfo describing the merge performed. |
| # |
| # See http://subversion.tigris.org/servlets/ReadMsg?listName=dev&msgNo=142460 |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Setup a 'trunk' and two branches. |
| wc_disk, wc_status = set_up_branch(sbox, False, 2) |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| D_COPY_path = sbox.ospath('A_COPY/D') |
| A_COPY_2_path = sbox.ospath('A_COPY_2') |
| D_COPY_2_path = sbox.ospath('A_COPY_2/D') |
| |
| # Update working copy to allow full inheritance and elision. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(7), [], |
| 'up', wc_dir) |
| wc_status.tweak(wc_rev=7) |
| |
| # Merge r5 from A to A_COPY and commit as r8. |
| # This creates explicit mergeinfo on A_COPY of '/A:5'. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/G/rho': Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=7), |
| 'B' : Item(status=' ', wc_rev=7), |
| 'mu' : Item(status=' ', wc_rev=7), |
| 'B/E' : Item(status=' ', wc_rev=7), |
| 'B/E/alpha' : Item(status=' ', wc_rev=7), |
| 'B/E/beta' : Item(status=' ', wc_rev=7), |
| 'B/lambda' : Item(status=' ', wc_rev=7), |
| 'B/F' : Item(status=' ', wc_rev=7), |
| 'C' : Item(status=' ', wc_rev=7), |
| 'D' : Item(status=' ', wc_rev=7), |
| 'D/G' : Item(status=' ', wc_rev=7), |
| 'D/G/pi' : Item(status=' ', wc_rev=7), |
| 'D/G/rho' : Item(status='M ', wc_rev=7), |
| 'D/G/tau' : Item(status=' ', wc_rev=7), |
| 'D/gamma' : Item(status=' ', wc_rev=7), |
| 'D/H' : Item(status=' ', wc_rev=7), |
| 'D/H/chi' : Item(status=' ', wc_rev=7), |
| 'D/H/psi' : Item(status=' ', wc_rev=7), |
| 'D/H/omega' : Item(status=' ', wc_rev=7), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:5'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '4', '5', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| wc_status.tweak('A_COPY', |
| 'A_COPY/D/G/rho', |
| wc_rev=8) |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/D/G/rho' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Merge r7 from A/D to A_COPY_2/D and commit as r9. |
| # This creates explicit mergeinfo on A_COPY_2/D of '/A/D:7'. |
| expected_output = wc.State(D_COPY_2_path, { |
| 'H/omega': Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(D_COPY_2_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(D_COPY_2_path, { |
| }) |
| expected_status = wc.State(D_COPY_2_path, { |
| '' : Item(status=' M', wc_rev=7), |
| 'G' : Item(status=' ', wc_rev=7), |
| 'G/pi' : Item(status=' ', wc_rev=7), |
| 'G/rho' : Item(status=' ', wc_rev=7), |
| 'G/tau' : Item(status=' ', wc_rev=7), |
| 'gamma' : Item(status=' ', wc_rev=7), |
| 'H' : Item(status=' ', wc_rev=7), |
| 'H/chi' : Item(status=' ', wc_rev=7), |
| 'H/psi' : Item(status=' ', wc_rev=7), |
| 'H/omega' : Item(status='M ', wc_rev=7), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D:7'}), |
| 'G' : Item(), |
| 'G/pi' : Item("This is the file 'pi'.\n"), |
| 'G/rho' : Item("This is the file 'rho'.\n"), |
| 'G/tau' : Item("This is the file 'tau'.\n"), |
| 'gamma' : Item("This is the file 'gamma'.\n"), |
| 'H' : Item(), |
| 'H/chi' : Item("This is the file 'chi'.\n"), |
| 'H/psi' : Item("This is the file 'psi'.\n"), |
| 'H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(D_COPY_2_path, '6', '7', |
| sbox.repo_url + '/A/D', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| wc_status.tweak('A_COPY_2/D', |
| 'A_COPY_2/D/H/omega', |
| wc_rev=9) |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY_2/D' : Item(verb='Sending'), |
| 'A_COPY_2/D/H/omega' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Merge r9 from A_COPY_2 to A_COPY. A_COPY/D gets the explicit mergeinfo |
| # '/A/D/:7' added from r9. But it prior to the merge it inherited '/A/D:5' |
| # from A_COPY, so this should be present in its explicit mergeinfo. Lastly, |
| # the mergeinfo describing this merge '/A_COPY_2:9' should also be present |
| # in A_COPY's explicit mergeinfo. |
| # Update working copy to allow full inheritance and elision. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], |
| 'up', wc_dir) |
| expected_output = wc.State(A_COPY_path, { |
| 'D' : Item(status=' U'), |
| 'D/H/omega': Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=9), |
| 'B' : Item(status=' ', wc_rev=9), |
| 'mu' : Item(status=' ', wc_rev=9), |
| 'B/E' : Item(status=' ', wc_rev=9), |
| 'B/E/alpha' : Item(status=' ', wc_rev=9), |
| 'B/E/beta' : Item(status=' ', wc_rev=9), |
| 'B/lambda' : Item(status=' ', wc_rev=9), |
| 'B/F' : Item(status=' ', wc_rev=9), |
| 'C' : Item(status=' ', wc_rev=9), |
| 'D' : Item(status=' M', wc_rev=9), |
| 'D/G' : Item(status=' ', wc_rev=9), |
| 'D/G/pi' : Item(status=' ', wc_rev=9), |
| 'D/G/rho' : Item(status=' ', wc_rev=9), |
| 'D/G/tau' : Item(status=' ', wc_rev=9), |
| 'D/gamma' : Item(status=' ', wc_rev=9), |
| 'D/H' : Item(status=' ', wc_rev=9), |
| 'D/H/chi' : Item(status=' ', wc_rev=9), |
| 'D/H/psi' : Item(status=' ', wc_rev=9), |
| 'D/H/omega' : Item(status='M ', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:5\n/A_COPY_2:9'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(props={SVN_PROP_MERGEINFO : '/A/D:5,7\n/A_COPY_2/D:9'}), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '8', '9', |
| sbox.repo_url + '/A_COPY_2', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Revert and repeat the above merge, but this time create some |
| # uncommitted mergeinfo on A_COPY/D, this should not cause a write |
| # lock error as was seen in http://subversion.tigris.org/ |
| # ds/viewMessage.do?dsForumId=462&dsMessageId=103945 |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '-R', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ps', SVN_PROP_MERGEINFO, '', |
| D_COPY_path) |
| expected_output = wc.State(A_COPY_path, { |
| 'D' : Item(status=' G'), # Merged with local svn:mergeinfo |
| 'D/H/omega': Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '8', '9', |
| sbox.repo_url + '/A_COPY_2', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def natural_history_filtering(sbox): |
| "natural history filtering permits valid mergeinfo" |
| |
| # While filtering self-referential mergeinfo (e.g. natural history) that |
| # a merge tries to add to a target, we may encounter contiguous revision |
| # ranges that describe *both* natural history and valid mergeinfo. The |
| # former should be filtered, but the latter allowed and recorded on the |
| # target. See |
| # http://subversion.tigris.org/servlets/ReadMsg?listName=dev&msgNo=142777. |
| # |
| # To set up a situation where this can occur we'll do the following: |
| # |
| # trunk -1-----3-4-5-6-------8----------- A |
| # \ \ \ |
| # branch1 2-----------\-------9-------- A_COPY |
| # \ \ |
| # branch2 7--------10---- A_COPY_2 |
| # |
| # 1) Create a 'trunk'. |
| # |
| # 2) Copy 'trunk' to 'branch1'. |
| # |
| # 3) Make some changes under 'trunk'. |
| # |
| # 4) Copy 'trunk' to 'branch2'. |
| # |
| # 5) Make some more changes under 'trunk'. |
| # |
| # 6) Merge all available revisions from 'trunk' to 'branch1' and commit. |
| # |
| # 7) Merge all available revisions from 'branch1' to 'branch2'. |
| # 'branch2' should have explicit merginfo for both 'branch1' *and* for |
| # the revisions on 'trunk' which occurred after 'branch2' was copied as |
| # these are not part of 'branch2's natural history. |
| |
| def path_join(head, tail): |
| if not head: return tail |
| if not tail: return head |
| return head + '/' + tail |
| |
| def greek_file_item(path): |
| if path[-1:].islower(): |
| basename = re.sub('.*/', '', path) |
| return Item("This is the file '" + basename + "'.\n") |
| return Item() |
| |
| A_paths = [ |
| "", |
| "B", |
| "B/lambda", |
| "B/E", |
| "B/E/alpha", |
| "B/E/beta", |
| "B/F", |
| "mu", |
| "C", |
| "D", |
| "D/gamma", |
| "D/G", |
| "D/G/pi", |
| "D/G/rho", |
| "D/G/tau", |
| "D/H", |
| "D/H/chi", |
| "D/H/omega", |
| "D/H/psi", |
| ] |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| A_COPY_2_path = sbox.ospath('A_COPY_2') |
| chi_path = sbox.ospath('A/D/H/chi') |
| |
| # r1-r6: Setup a 'trunk' (A) and a 'branch' (A_COPY). |
| wc_disk, wc_status = set_up_branch(sbox, False, 1) |
| |
| # r7: Make a second 'branch': Copy A to A_COPY_2 |
| expected = svntest.verify.UnorderedOutput( |
| [ "A " + sbox.ospath(path_join("A_COPY_2", p)) + "\n" |
| for p in A_paths ]) |
| wc_status.add( |
| { path_join("A_COPY_2", p) : Item(status=' ', wc_rev=7) |
| for p in A_paths }) |
| wc_disk.add( |
| { path_join("A_COPY_2", p) : |
| Item("New content") if p in ['B/E/beta', 'D/G/rho', 'D/H/chi', 'D/H/psi'] |
| else greek_file_item(p) |
| for p in A_paths }) |
| svntest.actions.run_and_verify_svn(expected, [], 'copy', |
| sbox.repo_url + "/A", |
| A_COPY_2_path) |
| expected_output = wc.State(wc_dir, {"A_COPY_2" : Item(verb='Adding')}) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, |
| wc_status) |
| |
| # r8: Make a text change under A, to A/D/H/chi. |
| svntest.main.file_write(chi_path, "New content") |
| expected_output = wc.State(wc_dir, {'A/D/H/chi' : Item(verb='Sending')}) |
| wc_status.tweak('A/D/H/chi', wc_rev=8) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| wc_disk.tweak('A/D/H/psi', contents="New content") |
| |
| # r9: Merge all available revisions from A to A_COPY. But first |
| # update working copy to allow full inheritance and elision. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], |
| 'up', wc_dir) |
| wc_status.tweak(wc_rev=8) |
| expected_output = wc.State(A_COPY_path, { |
| 'B/E/beta' : Item(status='U '), |
| 'D/G/rho' : Item(status='U '), |
| 'D/H/chi' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| 'D/H/omega': Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=8), |
| 'B' : Item(status=' ', wc_rev=8), |
| 'mu' : Item(status=' ', wc_rev=8), |
| 'B/E' : Item(status=' ', wc_rev=8), |
| 'B/E/alpha' : Item(status=' ', wc_rev=8), |
| 'B/E/beta' : Item(status='M ', wc_rev=8), |
| 'B/lambda' : Item(status=' ', wc_rev=8), |
| 'B/F' : Item(status=' ', wc_rev=8), |
| 'C' : Item(status=' ', wc_rev=8), |
| 'D' : Item(status=' ', wc_rev=8), |
| 'D/G' : Item(status=' ', wc_rev=8), |
| 'D/G/pi' : Item(status=' ', wc_rev=8), |
| 'D/G/rho' : Item(status='M ', wc_rev=8), |
| 'D/G/tau' : Item(status=' ', wc_rev=8), |
| 'D/gamma' : Item(status=' ', wc_rev=8), |
| 'D/H' : Item(status=' ', wc_rev=8), |
| 'D/H/chi' : Item(status='M ', wc_rev=8), |
| 'D/H/psi' : Item(status='M ', wc_rev=8), |
| 'D/H/omega' : Item(status='M ', wc_rev=8), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-8'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("New content"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| wc_status.tweak('A_COPY', |
| 'A_COPY/B/E/beta', |
| 'A_COPY/D/G/rho', |
| 'A_COPY/D/H/chi', |
| 'A_COPY/D/H/psi', |
| 'A_COPY/D/H/omega', |
| wc_rev=9) |
| expected_output = wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/B/E/beta' : Item(verb='Sending'), |
| 'A_COPY/D/G/rho' : Item(verb='Sending'), |
| 'A_COPY/D/H/chi' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending'), |
| 'A_COPY/D/H/omega' : Item(verb='Sending'), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # Again update the working copy to allow full inheritance and elision. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], |
| 'up', wc_dir) |
| wc_status.tweak(wc_rev=9) |
| |
| # Merge all available revisions from A_COPY to A_COPY_2. The mergeinfo on |
| # A_COPY_2 should reflect both the merge of revisions 2-9 from A_COPY *and* |
| # revisions 7-8 from A. Reivisions 2-6 from A should not be part of the |
| # explicit mergeinfo on A_COPY_2 as they are already part of its natural |
| # history. |
| expected_output = wc.State(A_COPY_2_path, { |
| '' : Item(status=' U'), |
| 'D/H/chi' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_2_path, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_2_path, { |
| }) |
| expected_status = wc.State(A_COPY_2_path, { |
| '' : Item(status=' M', wc_rev=9), |
| 'B' : Item(status=' ', wc_rev=9), |
| 'mu' : Item(status=' ', wc_rev=9), |
| 'B/E' : Item(status=' ', wc_rev=9), |
| 'B/E/alpha' : Item(status=' ', wc_rev=9), |
| 'B/E/beta' : Item(status=' ', wc_rev=9), |
| 'B/lambda' : Item(status=' ', wc_rev=9), |
| 'B/F' : Item(status=' ', wc_rev=9), |
| 'C' : Item(status=' ', wc_rev=9), |
| 'D' : Item(status=' ', wc_rev=9), |
| 'D/G' : Item(status=' ', wc_rev=9), |
| 'D/G/pi' : Item(status=' ', wc_rev=9), |
| 'D/G/rho' : Item(status=' ', wc_rev=9), |
| 'D/G/tau' : Item(status=' ', wc_rev=9), |
| 'D/gamma' : Item(status=' ', wc_rev=9), |
| 'D/H' : Item(status=' ', wc_rev=9), |
| 'D/H/chi' : Item(status='M ', wc_rev=9), |
| 'D/H/psi' : Item(status=' ', wc_rev=9), |
| 'D/H/omega' : Item(status=' ', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:7-8\n/A_COPY:2-9'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("New content"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_2_path, None, None, |
| sbox.repo_url + '/A_COPY', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3067) |
| def subtree_gets_changes_even_if_ultimately_deleted(sbox): |
| "subtree gets changes even if ultimately deleted" |
| |
| # merge_tests.py 101 'merge tries to delete a file of identical content' |
| # demonstrates how a file can be deleted by a merge if the file is identical |
| # to the file deleted in the merge source. If the file differs then it |
| # should be 'skipped' as a tree-conflict. But suppose the file has |
| # mergeinfo such that the requested merge should bring the file into a state |
| # identical to the deleted source *before* attempting to delete it. Then the |
| # file should get those changes first and then be deleted rather than skipped. |
| # |
| # This problem, as discussed here, |
| # http://subversion.tigris.org/servlets/ReadMsg?listName=dev&msgNo=141533, |
| # is only nominally a tree conflict issue. More accurately this is yet |
| # another issue #3067 problem, in that the merge target has a subtree which |
| # doesn't exist in part of the requested merge range. |
| |
| # r1: Create a greek tree. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| H_COPY_path = sbox.ospath('A_COPY/D/H') |
| psi_path = sbox.ospath('A/D/H/psi') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| |
| # r2 - r6: Copy A to A_COPY and then make some text changes under A. |
| set_up_branch(sbox) |
| |
| # r7: Make an additional text mod to A/D/H/psi. |
| svntest.main.file_write(psi_path, "Even newer content") |
| sbox.simple_commit(message='mod psi') |
| |
| # r8: Delete A/D/H/psi. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'delete', psi_path) |
| sbox.simple_commit(message='delete psi') |
| |
| # Update WC before merging so mergeinfo elision and inheritance |
| # occur smoothly. |
| svntest.main.run_svn(None, 'up', wc_dir) |
| |
| # r9: Merge r3,7 from A/D/H to A_COPY/D/H, then reverse merge r7 from |
| # A/D/H/psi to A_COPY/D/H/psi. |
| expected_output = wc.State(H_COPY_path, { |
| 'psi' : Item(status='G ', prev_status='U '), # Touched twice |
| }) |
| expected_mergeinfo_output = wc.State(H_COPY_path, { |
| '' : Item(status=' G', prev_status=' U'), |
| }) |
| expected_elision_output = wc.State(H_COPY_path, { |
| }) |
| expected_status = wc.State(H_COPY_path, { |
| '' : Item(status=' M', wc_rev=8), |
| 'psi' : Item(status='M ', wc_rev=8), |
| 'omega' : Item(status=' ', wc_rev=8), |
| 'chi' : Item(status=' ', wc_rev=8), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:3,7'}), |
| 'psi' : Item("Even newer content"), |
| 'omega' : Item("This is the file 'omega'.\n"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| }) |
| expected_skip = wc.State(H_COPY_path, { }) |
| |
| svntest.actions.run_and_verify_merge(H_COPY_path, None, None, |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| [], True, False, |
| '-c3,7', H_COPY_path) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[-7]], |
| ['G ' + psi_COPY_path + '\n', |
| ' G ' + psi_COPY_path + '\n',]), |
| [], 'merge', '-c-7', sbox.repo_url + '/A/D/H/psi@7', psi_COPY_path) |
| sbox.simple_commit(message='merge -c3,7 from A/D/H,' \ |
| 'reverse merge -c-7 from A/D/H/psi') |
| |
| # Merge all available revisions from A/D/H to A_COPY/D/H. This merge |
| # ultimately tries to delete A_COPY/D/H/psi, but first it should merge |
| # r7 to A_COPY/D/H/psi, since that is one of the available revisions. |
| # Then when merging the deletion of A_COPY/D/H/psi in r8 the file will |
| # be identical to the deleted source A/D/H/psi and the deletion will |
| # succeed. |
| # |
| # Update WC before merging so mergeinfo elision and inheritance |
| # occur smoothly. |
| svntest.main.run_svn(None, 'up', wc_dir) |
| expected_output = wc.State(H_COPY_path, { |
| 'omega' : Item(status='U '), |
| 'psi' : Item(status='D ', prev_status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(H_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(H_COPY_path, { |
| }) |
| expected_status = wc.State(H_COPY_path, { |
| '' : Item(status=' M', wc_rev=9), |
| 'psi' : Item(status='D ', wc_rev=9), |
| 'omega' : Item(status='M ', wc_rev=9), |
| 'chi' : Item(status=' ', wc_rev=9), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:2-9'}), |
| 'omega' : Item("New content"), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| }) |
| expected_skip = wc.State(H_COPY_path, { }) |
| |
| svntest.actions.run_and_verify_merge(H_COPY_path, None, None, |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, expected_skip, |
| [], True, False) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def no_self_referential_filtering_on_added_path(sbox): |
| "no self referential filtering on added path" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| C_COPY_path = sbox.ospath('A_COPY/C') |
| A_path = sbox.ospath('A') |
| C_path = sbox.ospath('A/C') |
| A_COPY_2_path = sbox.ospath('A_COPY_2') |
| |
| # r1-r7: Setup a 'trunk' and two 'branches'. |
| wc_disk, wc_status = set_up_branch(sbox, False, 2) |
| |
| # r8: Make a prop change on A_COPY/C. |
| svntest.actions.run_and_verify_svn(["property 'propname' set on '" + |
| C_COPY_path + "'\n"], [], |
| 'ps', 'propname', 'propval', |
| C_COPY_path) |
| expected_output = svntest.wc.State(wc_dir, |
| {'A_COPY/C' : Item(verb='Sending')}) |
| wc_status.tweak('A_COPY/C', wc_rev=8) |
| wc_disk.tweak("A_COPY/C", |
| props={'propname' : 'propval'}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # r9: Merge r8 from A_COPY to A. |
| # |
| # Update first to avoid an out of date error. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], 'up', |
| wc_dir) |
| wc_status.tweak(wc_rev=8) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[8]], |
| [' U ' + C_path + '\n', |
| ' U ' + A_path + '\n',]), |
| [], 'merge', '-c8', sbox.repo_url + '/A_COPY', A_path) |
| expected_output = svntest.wc.State(wc_dir, |
| {'A' : Item(verb='Sending'), |
| 'A/C' : Item(verb='Sending')}) |
| wc_status.tweak('A', 'A/C', wc_rev=9) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| wc_disk.tweak("A/C", |
| props={'propname' : 'propval'}) |
| wc_disk.tweak("A", |
| props={SVN_PROP_MERGEINFO : '/A_COPY:8'}) |
| |
| # r10: Move A/C to A/C_MOVED. |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 10.\n'], |
| [], 'move', |
| sbox.repo_url + '/A/C', |
| sbox.repo_url + '/A/C_MOVED', |
| '-m', 'Copy A/C to A/C_MOVED') |
| svntest.actions.run_and_verify_svn(None, [], 'up', |
| wc_dir) |
| |
| # Now try to merge all available revisions from A to A_COPY_2. |
| # This should try to add the directory A_COPY_2/C_MOVED which has |
| # explicit mergeinfo. This should not break self-referential mergeinfo |
| # filtering logic...in fact there is no reason to even attempt such |
| # filtering since the file is *new*. |
| |
| expected_output = wc.State(A_COPY_2_path, { |
| '' : Item(status=' U'), |
| 'B/E/beta' : Item(status='U '), |
| 'D/G/rho' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| 'D/H/omega' : Item(status='U '), |
| 'C' : Item(status='D '), |
| 'C_MOVED' : Item(status='A '), |
| }) |
| # Why is C_MOVED notified as ' G' rather than ' U'? C_MOVED was |
| # added by the merge and there is only a single editor drive, so |
| # how can any prop changes be merged to it? The answer is that |
| # the merge code does some quiet housekeeping, merging C_MOVED's |
| # inherited mergeinfo into its incoming mergeinfo, see |
| # https://issues.apache.org/jira/browse/SVN-4309 |
| # This test is not covering issue #4309 so we let the current |
| # behavior pass. |
| expected_mergeinfo_output = wc.State(A_COPY_2_path, { |
| '' : Item(status=' G'), |
| 'C_MOVED' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_2_path, { |
| }) |
| expected_A_COPY_2_status = wc.State(A_COPY_2_path, { |
| '' : Item(status=' M', wc_rev=10), |
| 'B' : Item(status=' ', wc_rev=10), |
| 'mu' : Item(status=' ', wc_rev=10), |
| 'B/E' : Item(status=' ', wc_rev=10), |
| 'B/E/alpha' : Item(status=' ', wc_rev=10), |
| 'B/E/beta' : Item(status='M ', wc_rev=10), |
| 'B/lambda' : Item(status=' ', wc_rev=10), |
| 'B/F' : Item(status=' ', wc_rev=10), |
| 'C' : Item(status='D ', wc_rev=10), |
| 'C_MOVED' : Item(status='A ', wc_rev='-', copied='+'), |
| 'D' : Item(status=' ', wc_rev=10), |
| 'D/G' : Item(status=' ', wc_rev=10), |
| 'D/G/pi' : Item(status=' ', wc_rev=10), |
| 'D/G/rho' : Item(status='M ', wc_rev=10), |
| 'D/G/tau' : Item(status=' ', wc_rev=10), |
| 'D/gamma' : Item(status=' ', wc_rev=10), |
| 'D/H' : Item(status=' ', wc_rev=10), |
| 'D/H/chi' : Item(status=' ', wc_rev=10), |
| 'D/H/psi' : Item(status='M ', wc_rev=10), |
| 'D/H/omega' : Item(status='M ', wc_rev=10), |
| }) |
| expected_A_COPY_2_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3-10\n/A_COPY:8'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| # What's up with the mergeinfo |
| 'C_MOVED' : Item(props={SVN_PROP_MERGEINFO : '/A/C_MOVED:10\n' + |
| '/A_COPY/C:8\n' + |
| '/A_COPY/C_MOVED:8', |
| 'propname' : 'propval'}), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_A_COPY_2_skip = wc.State(A_COPY_2_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_2_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_A_COPY_2_disk, |
| expected_A_COPY_2_status, |
| expected_A_COPY_2_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3324 |
| # https://issues.apache.org/jira/browse/SVN-3324 |
| @Issue(3324) |
| @SkipUnless(server_has_mergeinfo) |
| def merge_range_prior_to_rename_source_existence(sbox): |
| "merge prior to rename src existence still dels src" |
| |
| # Replicate a merge bug found while syncing up a feature branch on the |
| # Subversion repository with trunk. See r874121 of |
| # http://svn.apache.org/repos/asf/subversion/branches/ignore-mergeinfo, in which |
| # a move was merged to the target, but the delete half of the move |
| # didn't occur. |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| nu_path = sbox.ospath('A/D/H/nu') |
| nu_moved_path = sbox.ospath('A/D/H/nu_moved') |
| A_path = sbox.ospath('A') |
| alpha_path = sbox.ospath('A/B/E/alpha') |
| A_COPY_path = sbox.ospath('A_COPY') |
| A_COPY_2_path = sbox.ospath('A_COPY_2') |
| B_COPY_path = sbox.ospath('A_COPY/B') |
| B_COPY_2_path = sbox.ospath('A_COPY_2/B') |
| alpha_COPY_path = sbox.ospath('A_COPY/B/E/alpha') |
| beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') |
| gamma_COPY_path = sbox.ospath('A_COPY/D/gamma') |
| rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') |
| omega_COPY_path = sbox.ospath('A_COPY/D/H/omega') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| nu_COPY_path = sbox.ospath('A_COPY/D/H/nu') |
| |
| # Setup our basic 'trunk' and 'branch': |
| # r2 - Copy A to A_COPY |
| # r3 - Copy A to A_COPY_2 |
| # r4 - Text change to A/D/H/psi |
| # r5 - Text change to A/D/G/rho |
| # r6 - Text change to A/B/E/beta |
| # r7 - Text change to A/D/H/omega |
| wc_disk, wc_status = set_up_branch(sbox, False, 2) |
| |
| # r8 - Text change to A/B/E/alpha |
| svntest.main.file_write(alpha_path, "New content") |
| wc_status.tweak('A/B/E/alpha', wc_rev=8) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Text change', wc_dir) |
| |
| # r9 - Add the file A/D/H/nu and make another change to A/B/E/alpha. |
| svntest.main.file_write(alpha_path, "Even newer content") |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| expected_output = wc.State(wc_dir, |
| {'A/D/H/nu' : Item(verb='Adding'), |
| 'A/B/E/alpha' : Item(verb='Sending')}) |
| wc_status.add({'A/D/H/nu' : Item(status=' ', wc_rev=9)}) |
| wc_status.tweak('A/B/E/alpha', wc_rev=9) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # r10 - Merge all available revisions (i.e. -r1:9) from A to A_COPY. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], 'up', |
| wc_dir) |
| wc_status.tweak(wc_rev=9) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[2,9]], |
| ['A ' + nu_COPY_path + '\n', |
| 'U ' + alpha_COPY_path + '\n', |
| 'U ' + beta_COPY_path + '\n', |
| 'U ' + rho_COPY_path + '\n', |
| 'U ' + omega_COPY_path + '\n', |
| 'U ' + psi_COPY_path + '\n', |
| ' U ' + A_COPY_path + '\n',]), |
| [], 'merge', sbox.repo_url + '/A', A_COPY_path) |
| expected_output = wc.State(wc_dir, |
| {'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/D/H/nu' : Item(verb='Adding'), |
| 'A_COPY/B/E/alpha' : Item(verb='Sending'), |
| 'A_COPY/B/E/beta' : Item(verb='Sending'), |
| 'A_COPY/D/G/rho' : Item(verb='Sending'), |
| 'A_COPY/D/H/omega' : Item(verb='Sending'), |
| 'A_COPY/D/H/psi' : Item(verb='Sending')}) |
| wc_status.tweak('A_COPY', |
| 'A_COPY/B/E/alpha', |
| 'A_COPY/B/E/beta', |
| 'A_COPY/D/G/rho', |
| 'A_COPY/D/H/omega', |
| 'A_COPY/D/H/psi', |
| wc_rev=10) |
| wc_status.add({'A_COPY/D/H/nu' : Item(status=' ', wc_rev=10)}) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # r11 - Reverse merge -r9:1 from A/B to A_COPY/B |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(10), [], 'up', |
| wc_dir) |
| wc_status.tweak(wc_rev=10) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[9,2]], ['U ' + alpha_COPY_path + '\n', |
| 'U ' + beta_COPY_path + '\n', |
| ' G ' + B_COPY_path + '\n',]), |
| [], 'merge', sbox.repo_url + '/A/B', B_COPY_path, '-r9:1') |
| expected_output = wc.State(wc_dir, |
| {'A_COPY/B' : Item(verb='Sending'), |
| 'A_COPY/B/E/alpha' : Item(verb='Sending'), |
| 'A_COPY/B/E/beta' : Item(verb='Sending')}) |
| wc_status.tweak('A_COPY/B', |
| 'A_COPY/B/E/alpha', |
| 'A_COPY/B/E/beta', |
| wc_rev=11) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| |
| # r12 - Move A/D/H/nu to A/D/H/nu_moved |
| svntest.actions.run_and_verify_svn(["Committing transaction...\n", |
| "Committed revision 12.\n"], [], |
| 'move', sbox.repo_url + '/A/D/H/nu', |
| sbox.repo_url + '/A/D/H/nu_moved', |
| '-m', 'Move nu to nu_moved') |
| expected_output = svntest.verify.UnorderedOutput( |
| ["Updating '%s':\n" % (wc_dir), |
| "D " + nu_path + "\n", |
| "A " + nu_moved_path + "\n", |
| "Updated to revision 12.\n"], |
| ) |
| svntest.actions.run_and_verify_svn(expected_output, |
| [], 'up', wc_dir) |
| |
| # Now merge -r7:12 from A to A_COPY. |
| # A_COPY needs only -r10:12, which amounts to the rename of nu. |
| # The subtree A_COPY/B needs the entire range -r7:12 because of |
| # the reverse merge we performed in r11; the only operative change |
| # here is the text mod to alpha made in r9. |
| # |
| # This merge previously failed because the delete half of the A_COPY/D/H/nu |
| # to A_COPY/D/H/nu_moved move was reported in the notifications, but didn't |
| # actually happen. |
| expected_output = wc.State(A_COPY_path, { |
| 'B/E/alpha' : Item(status='U '), |
| 'D/H/nu' : Item(status='D '), |
| 'D/H/nu_moved' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'B' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=12), |
| 'B' : Item(status=' M', wc_rev=12), |
| 'mu' : Item(status=' ', wc_rev=12), |
| 'B/E' : Item(status=' ', wc_rev=12), |
| 'B/E/alpha' : Item(status='M ', wc_rev=12), |
| 'B/E/beta' : Item(status=' ', wc_rev=12), |
| 'B/lambda' : Item(status=' ', wc_rev=12), |
| 'B/F' : Item(status=' ', wc_rev=12), |
| 'C' : Item(status=' ', wc_rev=12), |
| 'D' : Item(status=' ', wc_rev=12), |
| 'D/G' : Item(status=' ', wc_rev=12), |
| 'D/G/pi' : Item(status=' ', wc_rev=12), |
| 'D/G/rho' : Item(status=' ', wc_rev=12), |
| 'D/G/tau' : Item(status=' ', wc_rev=12), |
| 'D/gamma' : Item(status=' ', wc_rev=12), |
| 'D/H' : Item(status=' ', wc_rev=12), |
| 'D/H/nu' : Item(status='D ', wc_rev=12), |
| 'D/H/nu_moved' : Item(status='A ', wc_rev='-', copied='+'), |
| 'D/H/chi' : Item(status=' ', wc_rev=12), |
| 'D/H/psi' : Item(status=' ', wc_rev=12), |
| 'D/H/omega' : Item(status=' ', wc_rev=12), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-12'}), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B' : Item(props={SVN_PROP_MERGEINFO : '/A/B:8-12'}), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("Even newer content"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/nu_moved' : Item("This is the file 'nu'.\n"), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, {}) |
| svntest.actions.run_and_verify_merge(A_COPY_path, 7, 12, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Merge -r7:12 from A to A_COPY', wc_dir) |
| |
| # Now run a similar scenario as above on the second branch, but with |
| # a reverse merge this time. |
| # |
| # r14 - Merge all available revisions from A/B to A_COPY_B and then merge |
| # -r2:9 from A to A_COPY_2. Among other things, this adds A_COPY_2/D/H/nu |
| # and leaves us with mergeinfo on the A_COPY_2 branch of: |
| # |
| # Properties on 'A_COPY_2': |
| # svn:mergeinfo |
| # /A:3-9 |
| # Properties on 'A_COPY_2\B': |
| # svn:mergeinfo |
| # /A/B:3-13 |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(13), [], 'up', |
| wc_dir) |
| svntest.actions.run_and_verify_svn(None, # Don't check stdout, we test this |
| # type of merge to death elsewhere. |
| [], 'merge', sbox.repo_url + '/A/B', |
| B_COPY_2_path) |
| svntest.actions.run_and_verify_svn(None,[], 'merge', '-r', '2:9', |
| sbox.repo_url + '/A', A_COPY_2_path) |
| svntest.actions.run_and_verify_svn( |
| None, [], 'ci', '-m', |
| 'Merge all from A/B to A_COPY_2/B\nMerge -r2:9 from A to A_COPY_2', |
| wc_dir) |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(14), [], 'up', |
| wc_dir) |
| |
| # Now reverse merge -r13:7 from A to A_COPY_2. |
| # |
| # Recall: |
| # |
| # >svn log -r8:13 ^/A -v |
| # ------------------------------------------------------------------------ |
| # r8 | jrandom | 2010-10-14 11:25:59 -0400 (Thu, 14 Oct 2010) | 1 line |
| # Changed paths: |
| # M /A/B/E/alpha |
| # |
| # Text change |
| # ------------------------------------------------------------------------ |
| # r9 | jrandom | 2010-10-14 11:25:59 -0400 (Thu, 14 Oct 2010) | 1 line |
| # Changed paths: |
| # M /A/B/E/alpha |
| # A /A/D/H/nu |
| # |
| # log msg |
| # ------------------------------------------------------------------------ |
| # r12 | jrandom | 2010-10-14 11:26:01 -0400 (Thu, 14 Oct 2010) | 1 line |
| # Changed paths: |
| # D /A/D/H/nu |
| # A /A/D/H/nu_moved (from /A/D/H/nu:11) |
| # |
| # Move nu to nu_moved |
| # ------------------------------------------------------------------------ |
| # |
| # We can only reverse merge changes from the explicit mergeinfo or |
| # natural history of a target, but since all of these changes intersect with |
| # the target's explicit mergeinfo (including subtrees), all should be |
| # reverse merged, including the deletion of A_COPY/D/H/nu. Like the forward |
| # merge performed earlier, this test previously failed when A_COPY/D/H/nu |
| # was reported as deleted, but still remained as a versioned item in the WC. |
| expected_output = wc.State(A_COPY_2_path, { |
| 'B/E/alpha' : Item(status='U '), |
| 'D/H/nu' : Item(status='D '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_2_path, { |
| '' : Item(status=' U'), |
| 'B' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_2_path, { |
| 'B' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_2_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' M'), |
| 'mu' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status='M '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/nu' : Item(status='D '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=14) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3-7'}), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B' : Item(), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, {}) |
| svntest.actions.run_and_verify_merge(A_COPY_2_path, 13, 7, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| #---------------------------------------------------------------------- |
| def set_up_natural_history_gap(sbox): |
| '''Starting with standard greek tree, do the following: |
| r2 - A/D/H/psi |
| r3 - A/D/G/rho |
| r4 - A/B/E/beta |
| r5 - A/D/H/omega |
| r6 - Delete A |
| r7 - "Resurrect" A, by copying A@2 to A |
| r8 - Copy A to A_COPY |
| r9 - Text mod to A/D/gamma |
| Lastly it updates the WC to r9. |
| All text mods set file contents to "New content". |
| Return (expected_disk, expected_status).''' |
| |
| # r1: Create a standard greek tree. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # r2-5: Make some changes under 'A' (no branches yet). |
| wc_disk, wc_status = set_up_branch(sbox, False, 0) |
| |
| # Some paths we'll care about. |
| A_COPY_path = sbox.ospath('A_COPY') |
| gamma_path = sbox.ospath('A/D/gamma') |
| |
| # r6: Delete 'A' |
| exit_code, out, err = svntest.actions.run_and_verify_svn( |
| ["Committing transaction...\n", |
| "Committed revision 6.\n"], [], |
| 'delete', sbox.repo_url + '/A', '-m', 'Delete A') |
| |
| # r7: Resurrect 'A' by copying 'A@2' to 'A'. |
| exit_code, out, err = svntest.actions.run_and_verify_svn( |
| ["Committing transaction...\n", |
| "Committed revision 7.\n"], [], |
| 'copy', sbox.repo_url + '/A@2', sbox.repo_url + '/A', |
| '-m', 'Resurrect A from A@2') |
| |
| # r8: Branch the resurrected 'A' to 'A_COPY'. |
| exit_code, out, err = svntest.actions.run_and_verify_svn( |
| ["Committing transaction...\n", |
| "Committed revision 8.\n"], [], |
| 'copy', sbox.repo_url + '/A', sbox.repo_url + '/A_COPY', |
| '-m', 'Copy A to A_COPY') |
| |
| # Update to bring all the repos side changes down. |
| exit_code, out, err = svntest.actions.run_and_verify_svn(None, [], |
| 'up', wc_dir) |
| wc_status.add({ |
| "A_COPY/B" : Item(status=' '), |
| "A_COPY/B/lambda" : Item(status=' '), |
| "A_COPY/B/E" : Item(status=' '), |
| "A_COPY/B/E/alpha" : Item(status=' '), |
| "A_COPY/B/E/beta" : Item(status=' '), |
| "A_COPY/B/F" : Item(status=' '), |
| "A_COPY/mu" : Item(status=' '), |
| "A_COPY/C" : Item(status=' '), |
| "A_COPY/D" : Item(status=' '), |
| "A_COPY/D/gamma" : Item(status=' '), |
| "A_COPY/D/G" : Item(status=' '), |
| "A_COPY/D/G/pi" : Item(status=' '), |
| "A_COPY/D/G/rho" : Item(status=' '), |
| "A_COPY/D/G/tau" : Item(status=' '), |
| "A_COPY/D/H" : Item(status=' '), |
| "A_COPY/D/H/chi" : Item(status=' '), |
| "A_COPY/D/H/omega" : Item(status=' '), |
| "A_COPY/D/H/psi" : Item(status=' '), |
| "A_COPY" : Item(status=' ')}) |
| wc_status.tweak(wc_rev=8) |
| |
| # r9: Make a text change to 'A/D/gamma'. |
| svntest.main.file_write(gamma_path, "New content") |
| expected_output = wc.State(wc_dir, {'A/D/gamma' : Item(verb='Sending')}) |
| wc_status.tweak('A/D/gamma', wc_rev=9) |
| |
| # Update the WC to a uniform revision. |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| wc_status) |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], |
| 'up', wc_dir) |
| return wc_disk, wc_status |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def dont_merge_gaps_in_history(sbox): |
| "mergeinfo aware merges ignore natural history gaps" |
| |
| ## See http://svn.haxx.se/dev/archive-2008-11/0618.shtml ## |
| |
| wc_dir = sbox.wc_dir |
| |
| # Create a branch with gaps in its natural history. |
| set_up_natural_history_gap(sbox) |
| |
| # Some paths we'll care about. |
| A_COPY_path = sbox.ospath('A_COPY') |
| |
| # Now merge all available changes from 'A' to 'A_COPY'. The only |
| # available revisions are r8 and r9. Only r9 effects the source/target |
| # so this merge should change 'A/D/gamma' from r9. The fact that 'A_COPY' |
| # has 'broken' natural history, i.e. |
| # |
| # /A:2,7 <-- Recall 'A@7' was copied from 'A@2'. |
| # /A_COPY:8-9 |
| # |
| # should have no impact, but currently this fact is causing a failure: |
| # |
| # >svn merge %url127%/A merge_tests-127\A_COPY |
| # ..\..\..\subversion\libsvn_repos\reporter.c:1162: (apr_err=160005) |
| # svn: Target path '/A' does not exist. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/gamma' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status='M '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=9) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:8-9'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("New content"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3432 'Merge can record mergeinfo from natural history |
| # gaps'. See https://issues.apache.org/jira/browse/SVN-3432 |
| @Issue(3432) |
| @SkipUnless(server_has_mergeinfo) |
| def handle_gaps_in_implicit_mergeinfo(sbox): |
| "correctly consider natural history gaps" |
| |
| wc_dir = sbox.wc_dir |
| |
| # Create a branch with gaps in its natural history. |
| # |
| # r1--------r2--------r3--------r4--------r5--------r6 |
| # Add 'A' edit edit edit edit Delete A |
| # psi rho beta omega |
| # | |
| # V |
| # r7--------r9-----------------> |
| # Rez 'A' edit |
| # | gamma |
| # | |
| # V |
| # r8---------------------------> |
| # Copy 'A@7' to |
| # 'A_COPY' |
| # |
| expected_disk, expected_status = set_up_natural_history_gap(sbox) |
| |
| # Some paths we'll care about. |
| A_COPY_path = sbox.ospath('A_COPY') |
| |
| # Merge r4 to 'A_COPY' from A@4, which is *not* part of A_COPY's history. |
| expected_output = wc.State(A_COPY_path, { |
| 'B/E/beta' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status='M '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=9) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:4'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), # From the merge of A@4 |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), # From A@2 |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, 3, 4, |
| sbox.repo_url + '/A@4', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Now reverse merge -r9:2 from 'A@HEAD' to 'A_COPY'. This should be |
| # a no-op since the only operative change made on 'A@HEAD' between r2:9 |
| # is the text mod to 'A/D/gamma' made in r9, but since that was after |
| # 'A_COPY' was copied from 'A 'and that change was never merged, we don't |
| # try to reverse merge it. |
| # |
| # Also, the mergeinfo recorded by the previous merge, i.e. '/A:4', should |
| # *not* be removed! A@4 is not on the same line of history as 'A@9'. |
| expected_output = wc.State(A_COPY_path, {}) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' G'), |
| }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, 9, 2, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Now merge all available revisions from 'A' to 'A_COPY'. |
| # The mergeinfo '/A:4' on 'A_COPY' should have no impact on this merge |
| # since it refers to another line of history. Since 'A_COPY' was copied |
| # from 'A@7' the only available revisions are r8 and r9. |
| expected_output = wc.State(A_COPY_path, { |
| 'D/gamma' : Item(status='U '), |
| }) |
| expected_status.tweak('D/gamma', status='M ') |
| expected_disk.tweak('D/gamma', contents='New content') |
| expected_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A:4,8-9'}) |
| svntest.actions.run_and_verify_merge(A_COPY_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3323 'Mergeinfo deleted by a merge should disappear' |
| @Issue(3323) |
| @SkipUnless(server_has_mergeinfo) |
| def mergeinfo_deleted_by_a_merge_should_disappear(sbox): |
| "mergeinfo deleted by a merge should disappear" |
| |
| |
| # r1: Create a greek tree. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| D_COPY_path = sbox.ospath('A_COPY/D') |
| A_COPY_path = sbox.ospath('A_COPY') |
| A_COPY_2_path = sbox.ospath('A_COPY_2') |
| |
| # r2 - r6: Copy A to A_COPY and then make some text changes under A. |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # r7: Merge all available revisions from A/D to A_COPY/D, this creates |
| # mergeinfo on A_COPY/D. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, # Don't check stdout, we test this |
| # type of merge to death elsewhere. |
| [], 'merge', sbox.repo_url + '/A/D', |
| D_COPY_path) |
| svntest.actions.run_and_verify_svn( |
| None, [], 'ci', '-m', |
| 'Merge all available revisions from A/D to A_COPY/D', wc_dir) |
| |
| # r8: Copy A_COPY to A_COPY_2, this carries the mergeinf on A_COPY/D |
| # to A_COPY_2/D. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None,[], |
| 'copy', A_COPY_path, A_COPY_2_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Copy A_COPY to A_COPY_2', wc_dir) |
| |
| # r9: Propdel the mergeinfo on A_COPY/D. |
| svntest.actions.run_and_verify_svn(None,[], |
| 'pd', SVN_PROP_MERGEINFO, D_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Propdel the mergeinfo on A_COPY/D', |
| wc_dir) |
| |
| # r10: Merge r5 from A to A_COPY_2 so the latter gets some explicit |
| # mergeinfo. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'merge', '-c5', |
| sbox.repo_url + '/A', A_COPY_2_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Merge r5 from A to A_COPY_2', wc_dir) |
| |
| # Now merge r9 from A_COPY to A_COPY_2. Since the merge itself cleanly |
| # removes all explicit mergeinfo from A_COPY_2/D, we should not set any |
| # mergeinfo on that subtree describing the merge. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_output = wc.State(A_COPY_2_path, { |
| 'D' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_2_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_2_path, { |
| }) |
| expected_status = wc.State(A_COPY_2_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' M'), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=10) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:5\n/A_COPY:9'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_2_path, '8', '9', |
| sbox.repo_url + '/A_COPY', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # File merge optimization caused segfault during noop file merge |
| # when multiple ranges are eligible for merge, see |
| # http://svn.haxx.se/dev/archive-2009-05/0363.shtml |
| @SkipUnless(server_has_mergeinfo) |
| def noop_file_merge(sbox): |
| "noop file merge does not segfault" |
| |
| # r1: Create a greek tree. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') |
| chi_COPY_path = sbox.ospath('A_COPY/D/H/chi') |
| |
| # r2 - r6: Copy A to A_COPY and then make some text changes under A. |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Merge r5 from A to A_COPY and commit as r7. This will split the |
| # eligible ranges to be merged to A_COPY/D/H/chi into two discrete |
| # sets: r1-4 and r5-HEAD |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[5]], |
| ['U ' + beta_COPY_path + '\n', |
| ' U ' + A_COPY_path + '\n',]), |
| [], 'merge', '-c5', sbox.repo_url + '/A', A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'commit', '-m', |
| 'Merge r5 from A to A_COPY', |
| wc_dir) |
| |
| # Update working copy to allow full inheritance and elision. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(7), [], |
| 'up', wc_dir) |
| |
| # Merge all available revisions from A/D/H/chi to A_COPY/D/H/chi. |
| # There are no operative changes in the source, so this should |
| # not produce any output other than mergeinfo updates on |
| # A_COPY/D/H/chi. This is where the segfault occurred. |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url + '/A/D/H/chi', |
| chi_COPY_path) |
| svntest.actions.run_and_verify_svn([' M ' + chi_COPY_path + '\n'], |
| [], 'st', chi_COPY_path) |
| svntest.actions.run_and_verify_svn(['/A/D/H/chi:2-7\n'], |
| [], 'pg', SVN_PROP_MERGEINFO, |
| chi_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(2690) |
| def copy_then_replace_via_merge(sbox): |
| "copy then replace via merge" |
| # Testing issue #2690 with deleted/added/replaced files and subdirs. |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| j = os.path.join |
| |
| A = j(wc_dir, 'A') |
| AJ = j(wc_dir, 'A', 'J') |
| AJK = j(AJ, 'K') |
| AJL = j(AJ, 'L') |
| AJM = j(AJ, 'M') |
| AJ_sigma = j(AJ, 'sigma') |
| AJ_theta = j(AJ, 'theta') |
| AJ_omega = j(AJ, 'omega') |
| AJK_zeta = j(AJK, 'zeta') |
| AJL_zeta = j(AJL, 'zeta') |
| AJM_zeta = j(AJM, 'zeta') |
| branch = j(wc_dir, 'branch') |
| branch_J = j(wc_dir, 'branch', 'J') |
| url_A = sbox.repo_url + '/A' |
| url_branch = sbox.repo_url + '/branch' |
| |
| # Create a branch. |
| main.run_svn(None, 'cp', url_A, url_branch, '-m', 'create branch') # r2 |
| |
| # Create a tree J in A. |
| os.makedirs(AJK) |
| os.makedirs(AJL) |
| main.file_append(AJ_sigma, 'new text') |
| main.file_append(AJ_theta, 'new text') |
| main.file_append(AJK_zeta, 'new text') |
| main.file_append(AJL_zeta, 'new text') |
| main.run_svn(None, 'add', AJ) |
| sbox.simple_commit(message='create tree J') # r3 |
| main.run_svn(None, 'up', wc_dir) |
| |
| # Copy J to the branch via merge |
| main.run_svn(None, 'merge', url_A, branch) |
| sbox.simple_commit(message='merge to branch') # r4 |
| main.run_svn(None, 'up', wc_dir) |
| |
| # In A, replace J with a slightly different tree |
| main.run_svn(None, 'rm', AJ) |
| sbox.simple_commit(message='rm AJ') # r5 |
| main.run_svn(None, 'up', wc_dir) |
| |
| os.makedirs(AJL) |
| os.makedirs(AJM) |
| main.file_append(AJ_theta, 'really new text') |
| main.file_append(AJ_omega, 'really new text') |
| main.file_append(AJL_zeta, 'really new text') |
| main.file_append(AJM_zeta, 'really new text') |
| main.run_svn(None, 'add', AJ) |
| sbox.simple_commit(message='create tree J again') # r6 |
| main.run_svn(None, 'up', wc_dir) |
| |
| # Run merge to replace /branch/J in one swell foop. |
| main.run_svn(None, 'merge', url_A, branch) |
| |
| # Check status: |
| # sigma and K are deleted (not copied!) |
| # theta and L are replaced (deleted then copied-here) |
| # omega and M are copied-here |
| expected_status = wc.State(branch_J, { |
| '' : Item(status='R ', copied='+', wc_rev='-'), |
| 'sigma' : Item(status='D ', wc_rev=6), |
| 'K' : Item(status='D ', wc_rev=6), |
| 'K/zeta' : Item(status='D ', wc_rev=6), |
| 'theta' : Item(status=' ', copied='+', wc_rev='-'), |
| 'L' : Item(status=' ', copied='+', wc_rev='-'), |
| 'L/zeta' : Item(status=' ', copied='+', wc_rev='-'), |
| 'omega' : Item(status=' ', copied='+', wc_rev='-'), |
| 'M' : Item(status=' ', copied='+', wc_rev='-'), |
| 'M/zeta' : Item(status=' ', copied='+', wc_rev='-'), |
| }) |
| actions.run_and_verify_status(branch_J, expected_status) |
| |
| # Update and commit, just to make sure the WC isn't busted. |
| main.run_svn(None, 'up', branch_J) |
| expected_output = wc.State(branch_J, { |
| '' : Item(verb='Replacing'), |
| }) |
| expected_status = wc.State(branch_J, { |
| '' : Item(status=' ', wc_rev=7), |
| 'theta' : Item(status=' ', wc_rev=7), |
| 'L' : Item(status=' ', wc_rev=7), |
| 'L/zeta' : Item(status=' ', wc_rev=7), |
| 'omega' : Item(status=' ', wc_rev=7), |
| 'M' : Item(status=' ', wc_rev=7), |
| 'M/zeta' : Item(status=' ', wc_rev=7), |
| }) |
| actions.run_and_verify_commit(branch_J, |
| expected_output, |
| expected_status) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def record_only_merge(sbox): |
| "record only merge applies mergeinfo diffs" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| nu_path = sbox.ospath('A/C/nu') |
| A_COPY_path = sbox.ospath('A_COPY') |
| A2_path = sbox.ospath('A2') |
| Z_path = sbox.ospath('A/B/Z') |
| Z_COPY_path = sbox.ospath('A_COPY/B/Z') |
| rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') |
| omega_COPY_path = sbox.ospath('A_COPY/D/H/omega') |
| H_COPY_path = sbox.ospath('A_COPY/D/H') |
| nu_COPY_path = sbox.ospath('A_COPY/C/nu') |
| |
| # r7 - Copy the branch A_COPY@2 to A2 and update the WC. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'copy', A_COPY_path, A2_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'commit', '-m', 'Branch the branch', |
| wc_dir) |
| # r8 - Add A/C/nu and A/B/Z. |
| # Add a new file with mergeinfo in the foreign repos. |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', Z_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'commit', '-m', 'Add subtrees', |
| wc_dir) |
| |
| # r9 - Edit A/C/nu and add a random property on A/B/Z. |
| svntest.main.file_write(nu_path, "New content.\n") |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ps', 'propname', 'propval', Z_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'commit', '-m', 'Subtree changes', |
| wc_dir) |
| |
| # r10 - Merge r8 from A to A_COPY. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], 'up', |
| wc_dir) |
| svntest.actions.run_and_verify_svn(expected_merge_output( |
| [[8]], |
| ['A ' + Z_COPY_path + '\n', |
| 'A ' + nu_COPY_path + '\n', |
| ' U ' + A_COPY_path + '\n',]), |
| [], 'merge', '-c8', |
| sbox.repo_url + '/A', |
| A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'commit', '-m', 'Root merge of r8', |
| wc_dir) |
| |
| # r11 - Do several subtree merges: |
| # |
| # r4 from A/D/G/rho to A_COPY/D/G/rho |
| # r6 from A/D/H to A_COPY/D/H |
| # r9 from A/C/nu to A_COPY/C/nu |
| # r9 from A/B/Z to A_COPY/B/Z |
| svntest.actions.run_and_verify_svn(expected_merge_output( |
| [[4]], |
| ['U ' + rho_COPY_path + '\n', |
| ' U ' + rho_COPY_path + '\n',]), |
| [], 'merge', '-c4', |
| sbox.repo_url + '/A/D/G/rho', |
| rho_COPY_path) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[6]], |
| ['U ' + omega_COPY_path + '\n', |
| ' U ' + H_COPY_path + '\n',]), |
| [], 'merge', '-c6', sbox.repo_url + '/A/D/H', H_COPY_path) |
| svntest.actions.run_and_verify_svn(expected_merge_output( |
| [[9]], |
| ['U ' + nu_COPY_path + '\n', |
| ' G ' + nu_COPY_path + '\n',]), |
| [], 'merge', '-c9', |
| sbox.repo_url + '/A/C/nu', |
| nu_COPY_path) |
| svntest.actions.run_and_verify_svn(expected_merge_output( |
| [[9]], |
| [' U ' + Z_COPY_path + '\n', |
| ' G ' + Z_COPY_path + '\n']), |
| [], 'merge', '-c9', |
| sbox.repo_url + '/A/B/Z', |
| Z_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'commit', '-m', 'Several subtree merges', |
| wc_dir) |
| |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(11), [], 'up', |
| wc_dir) |
| |
| # Now do a --record-only merge of r10 and r11 from A_COPY to A2. |
| # |
| # We only expect svn:mergeinfo changes to be applied to existing paths: |
| # |
| # From r10 the mergeinfo '/A:r8' is recorded on A_COPY. |
| # |
| # From r11 the mergeinfo of '/A/D/G/rho:r4' is recorded on A_COPY/D/G/rho |
| # and the mergeinfo of '/A/D/H:r6' is recorded on A_COPY/D/H. Rev 8 should |
| # also be recorded on both subtrees because explicit mergeinfo must be |
| # complete. |
| # |
| # The mergeinfo describing the merge source itself, '/A_COPY:10-11' should |
| # also be recorded on the root and the two subtrees. |
| # |
| # The mergeinfo changes from r10 to A_COPY/C/nu and A_COPY/B/Z cannot be |
| # applied because the corresponding paths don't exist under A2; this should |
| # not cause any problems. |
| expected_output = wc.State(A2_path, { |
| '' : Item(status=' U'), |
| 'D/G/rho' : Item(status=' U'), |
| 'D/H' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(A2_path, { |
| '' : Item(status=' G'), |
| 'D/H' : Item(status=' G'), |
| 'D/G/rho' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A2_path, { |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:8\n/A_COPY:10-11'}), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B' : Item(), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(props={SVN_PROP_MERGEINFO : |
| '/A/D/H:6,8\n/A_COPY/D/H:10-11'}), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n", |
| props={SVN_PROP_MERGEINFO : |
| '/A/D/G/rho:4,8\n/A_COPY/D/G/rho:10-11'}), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| }) |
| expected_status = wc.State(A2_path, { |
| '' : Item(status=' M'), |
| 'mu' : Item(status=' '), |
| 'B' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' M'), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' M'), |
| 'D/G/tau' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=11) |
| expected_skip = wc.State('', { }) |
| svntest.actions.run_and_verify_merge(A2_path, '9', '11', |
| sbox.repo_url + '/A_COPY', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| '--record-only', A2_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3514 'svn merge --accept [ base | theirs-full ] |
| # doesn't work' |
| @Issue(3514) |
| def merge_automatic_conflict_resolution(sbox): |
| "automatic conflict resolutions work with merge" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| |
| # r7 - Make a change on A_COPY that will conflict with r3 on A |
| svntest.main.file_write(psi_COPY_path, "BASE.\n") |
| svntest.actions.run_and_verify_svn(None, [], |
| 'commit', '-m', 'log msg', wc_dir) |
| |
| # Set up our base expectations, we'll tweak accordingly for each option. |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'B' : Item(status=' ', wc_rev=2), |
| 'mu' : Item(status=' ', wc_rev=2), |
| 'B/E' : Item(status=' ', wc_rev=2), |
| 'B/E/alpha' : Item(status=' ', wc_rev=2), |
| 'B/E/beta' : Item(status=' ', wc_rev=2), |
| 'B/lambda' : Item(status=' ', wc_rev=2), |
| 'B/F' : Item(status=' ', wc_rev=2), |
| 'C' : Item(status=' ', wc_rev=2), |
| 'D' : Item(status=' ', wc_rev=2), |
| 'D/G' : Item(status=' ', wc_rev=2), |
| 'D/G/pi' : Item(status=' ', wc_rev=2), |
| 'D/G/rho' : Item(status=' ', wc_rev=2), |
| 'D/G/tau' : Item(status=' ', wc_rev=2), |
| 'D/gamma' : Item(status=' ', wc_rev=2), |
| 'D/H' : Item(status=' ', wc_rev=2), |
| 'D/H/chi' : Item(status=' ', wc_rev=2), |
| 'D/H/psi' : Item(status=' ', wc_rev=7), |
| 'D/H/omega' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| |
| # Test --accept postpone |
| expected_output = wc.State(A_COPY_path, {'D/H/psi' : Item(status='C ')}) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_disk.tweak('D/H/psi', contents="<<<<<<< .working\n" |
| "BASE.\n" |
| "||||||| .merge-left.r2\n" |
| "This is the file 'psi'.\n" |
| "=======\n" |
| "New content>>>>>>> .merge-right.r3\n") |
| expected_status.tweak('D/H/psi', status='C ') |
| psi_conflict_support_files = ["psi\.working", |
| "psi\.merge-right\.r3", |
| "psi\.merge-left\.r2"] |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| '--accept', 'postpone', |
| '--allow-mixed-revisions', |
| A_COPY_path, |
| extra_files= |
| list(psi_conflict_support_files)) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '--recursive', wc_dir) |
| |
| # Test --accept mine-conflict and mine-full |
| ### TODO: Also test that the output has a 'Resolved' line for this path. |
| expected_output = wc.State(A_COPY_path, {'D/H/psi' : Item(status='C ')}) |
| expected_disk.tweak('D/H/psi', contents="BASE.\n") |
| expected_status.tweak('D/H/psi', status=' ') |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| '--accept', 'mine-conflict', |
| '--allow-mixed-revisions', |
| A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '--recursive', wc_dir) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| '--accept', 'mine-full', |
| '--allow-mixed-revisions', |
| A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '--recursive', wc_dir) |
| |
| # Test --accept theirs-conflict and theirs-full |
| ### TODO: Also test that the output has a 'Resolved' line for this path. |
| expected_output = wc.State(A_COPY_path, {'D/H/psi' : Item(status='C ')}) |
| expected_disk.tweak('D/H/psi', contents="New content") |
| expected_status.tweak('D/H/psi', status='M ') |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| '--accept', 'theirs-conflict', |
| '--allow-mixed-revisions', |
| A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '--recursive', wc_dir) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| '--accept', 'theirs-full', |
| '--allow-mixed-revisions', |
| A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '--recursive', wc_dir) |
| # Test --accept base |
| ### TODO: Also test that the output has a 'Resolved' line for this path. |
| expected_output = wc.State(A_COPY_path, {'D/H/psi' : Item(status='C ')}) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_disk.tweak('D/H/psi', contents="This is the file 'psi'.\n") |
| expected_status.tweak('D/H/psi', status='M ') |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| '--accept', 'base', |
| '--allow-mixed-revisions', |
| A_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3440 'Skipped paths get incorrect override mergeinfo |
| # during merge'. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3440) |
| def skipped_files_get_correct_mergeinfo(sbox): |
| "skipped files get correct mergeinfo set" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| H_COPY_path = sbox.ospath('A_COPY/D/H') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| psi_path = sbox.ospath('A/D/H/psi') |
| |
| # Setup our basic 'trunk' and 'branch': |
| # r2 - Copy A to A_COPY |
| # r3 - Text change to A/D/H/psi |
| # r4 - Text change to A/D/G/rho |
| # r5 - Text change to A/B/E/beta |
| # r6 - Text change to A/D/H/omega |
| wc_disk, wc_status = set_up_branch(sbox, False, 1) |
| |
| # r7 Make another text change to A/D/H/psi |
| svntest.main.file_write(psi_path, "Even newer content") |
| expected_output = wc.State(wc_dir, {'A/D/H/psi' : Item(verb='Sending')}) |
| svntest.main.run_svn(None, 'commit', '-m', 'another change to A/D/H/psi', |
| wc_dir) |
| |
| # Merge r3 from A to A_COPY, this will create explicit mergeinfo of |
| # '/A:3' on A_COPY. Commit this merge as r8. |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[3]], |
| ['U ' + psi_COPY_path + '\n', |
| ' U ' + A_COPY_path + '\n',]), |
| [], 'merge', '-c3', sbox.repo_url + '/A', A_COPY_path) |
| svntest.main.run_svn(None, 'commit', '-m', 'initial merge', wc_dir) |
| |
| # Update WC to uniform revision and then set the depth on A_COPY/D/H to |
| # empty. Then merge all available revisions from A to A_COPY. |
| # A_COPY/D/H/psi and A_COPY/D/H/omega are not present due to their |
| # parent's depth and should be reported as skipped. A_COPY/D/H should |
| # get explicit mergeinfo set on it reflecting what it previously inherited |
| # from A_COPY after the first merge, i.e. '/A/D/H:3', plus non-inheritable |
| # mergeinfo describing what was done during this merge, |
| # i.e. '/A/D/H:2*,4-8*'. |
| # |
| # Issue #3440 occurred when empty mergeinfo was set on A_COPY/D/H, making |
| # it appear that r3 was never merged. |
| svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], |
| 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'up', '--set-depth=empty', H_COPY_path) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status='M '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status='M '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' M'), |
| }) |
| expected_status.tweak(wc_rev=8) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-8'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:2*,3,4-8*'}), |
| }) |
| expected_skip = wc.State( |
| A_COPY_path, |
| {'D/H/psi' : Item(verb='Skipped missing target'), |
| 'D/H/omega' : Item(verb='Skipped missing target')}) |
| expected_output = wc.State(A_COPY_path, |
| {'B/E/beta' : Item(status='U '), |
| 'D/G/rho' : Item(status='U ')}) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H' : Item(status=' G'), # ' G' because override mergeinfo gets set |
| # on this, the root of a 'missing' subtree. |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3115 'Case only renames resulting from merges don't |
| # work or break the WC on case-insensitive file systems'. |
| @Issue(3115) |
| def committed_case_only_move_and_revert(sbox): |
| "committed case only move causes revert to fail" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, True) |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| |
| # r3: A case-only file rename on the server |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 3.\n'], |
| [], 'move', |
| sbox.repo_url + '/A/mu', |
| sbox.repo_url + '/A/MU', |
| '-m', 'Move A/mu to A/MU') |
| |
| # Now merge that rename into the WC |
| expected_output = wc.State(A_COPY_path, { |
| 'mu' : Item(status='D '), |
| 'MU' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M', wc_rev=2), |
| 'B' : Item(status=' ', wc_rev=2), |
| 'mu' : Item(status='D ', wc_rev=2), |
| 'MU' : Item(status='A ', wc_rev='-', copied='+'), |
| 'B/E' : Item(status=' ', wc_rev=2), |
| 'B/E/alpha' : Item(status=' ', wc_rev=2), |
| 'B/E/beta' : Item(status=' ', wc_rev=2), |
| 'B/lambda' : Item(status=' ', wc_rev=2), |
| 'B/F' : Item(status=' ', wc_rev=2), |
| 'C' : Item(status=' ', wc_rev=2), |
| 'D' : Item(status=' ', wc_rev=2), |
| 'D/G' : Item(status=' ', wc_rev=2), |
| 'D/G/pi' : Item(status=' ', wc_rev=2), |
| 'D/G/rho' : Item(status=' ', wc_rev=2), |
| 'D/G/tau' : Item(status=' ', wc_rev=2), |
| 'D/gamma' : Item(status=' ', wc_rev=2), |
| 'D/H' : Item(status=' ', wc_rev=2), |
| 'D/H/chi' : Item(status=' ', wc_rev=2), |
| 'D/H/psi' : Item(status=' ', wc_rev=2), |
| 'D/H/omega' : Item(status=' ', wc_rev=2), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3'}), |
| 'B' : Item(), |
| 'MU' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, '2', '3', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False) |
| |
| # Commit the merge |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A_COPY' : Item(verb='Sending'), |
| 'A_COPY/mu' : Item(verb='Deleting'), |
| 'A_COPY/MU' : Item(verb='Adding'), |
| }) |
| wc_status.tweak('A_COPY', wc_rev=4) |
| wc_status.remove('A_COPY/mu') |
| wc_status.add({'A_COPY/MU': Item(status=' ', wc_rev=4)}) |
| |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, wc_status) |
| |
| # In issue #3115 the WC gets corrupted and any subsequent revert |
| # attempts fail with this error: |
| # svn.exe revert -R "svn-test-work\working_copies\merge_tests-139" |
| # ..\..\..\subversion\svn\revert-cmd.c:81: (apr_err=2) |
| # ..\..\..\subversion\libsvn_client\revert.c:167: (apr_err=2) |
| # ..\..\..\subversion\libsvn_client\revert.c:103: (apr_err=2) |
| # ..\..\..\subversion\libsvn_wc\adm_ops.c:2232: (apr_err=2) |
| # ..\..\..\subversion\libsvn_wc\adm_ops.c:2232: (apr_err=2) |
| # ..\..\..\subversion\libsvn_wc\adm_ops.c:2232: (apr_err=2) |
| # ..\..\..\subversion\libsvn_wc\adm_ops.c:2176: (apr_err=2) |
| # ..\..\..\subversion\libsvn_wc\adm_ops.c:2053: (apr_err=2) |
| # ..\..\..\subversion\libsvn_wc\adm_ops.c:1869: (apr_err=2) |
| # ..\..\..\subversion\libsvn_wc\workqueue.c:520: (apr_err=2) |
| # ..\..\..\subversion\libsvn_wc\workqueue.c:490: (apr_err=2) |
| # svn: Error restoring text for 'C:\SVN\src-trunk\Debug\subversion\tests |
| # \cmdline\svn-test-work\working_copies\merge_tests-139\A_COPY\MU' |
| svntest.actions.run_and_verify_svn([], [], 'revert', '-R', wc_dir) |
| |
| # r5: A case-only directory rename on the server |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 5.\n'], |
| [], 'move', |
| sbox.repo_url + '/A/C', |
| sbox.repo_url + '/A/c', |
| '-m', 'Move A/C to A/c') |
| expected_output = wc.State(A_COPY_path, { |
| 'C' : Item(status='D '), |
| 'c' : Item(status='A '), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_disk.tweak('', props={SVN_PROP_MERGEINFO : '/A:3,5'}) |
| expected_disk.add({'c' : Item()}) |
| expected_disk.remove('C') |
| expected_status.tweak('MU', status=' ', wc_rev=4, copied=None) |
| expected_status.remove('mu') |
| expected_status.tweak('C', status='D ') |
| expected_status.tweak('', wc_rev=4) |
| expected_status.add({'c' : Item(status='A ', copied='+', wc_rev='-')}) |
| # This merge succeeds. It used to leave a strange state, added with |
| # history but missing: |
| # |
| # M merge_tests-139\A_COPY |
| # ! + merge_tests-139\A_COPY\c |
| # R + merge_tests-139\A_COPY\C |
| svntest.actions.run_and_verify_merge(A_COPY_path, '4', '5', |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False, |
| '--allow-mixed-revisions', A_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| # This is a test for issue #3221 'Unable to merge into working copy of |
| # deleted branch'. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3221) |
| def merge_into_wc_for_deleted_branch(sbox): |
| "merge into WC of deleted branch should work" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Copy 'A' to 'A_COPY' then make some changes under 'A' |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| gamma_path = sbox.ospath('A/D/gamma') |
| |
| # r7 - Delete the branch on the repository, obviously it still |
| # exists in our WC. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'delete', sbox.repo_url + '/A_COPY', |
| '-m', 'Delete A_COPY directly in repos') |
| |
| # r8 - Make another change under 'A'. |
| svntest.main.file_write(gamma_path, "Content added after A_COPY deleted") |
| expected_output = wc.State(wc_dir, {'A/D/gamma' : Item(verb='Sending')}) |
| svntest.main.run_svn(None, 'commit', |
| '-m', 'Change made on A after A_COPY was deleted', |
| wc_dir) |
| |
| # Now merge all available revisions from A to A_COPY: |
| expected_output = wc.State(A_COPY_path, { |
| 'B/E/beta' : Item(status='U '), |
| 'D/G/rho' : Item(status='U '), |
| 'D/H/omega' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| 'D/gamma' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status='M '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status='M '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status='M '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status='M '), |
| 'D/H/omega' : Item(status='M '), |
| }) |
| expected_status.tweak(wc_rev=2) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-8'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("Content added after A_COPY deleted"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| # Issue #3221: Previously this merge failed with: |
| # ..\..\..\subversion\svn\util.c:900: (apr_err=160013) |
| # ..\..\..\subversion\libsvn_client\merge.c:9383: (apr_err=160013) |
| # ..\..\..\subversion\libsvn_client\merge.c:8029: (apr_err=160013) |
| # ..\..\..\subversion\libsvn_client\merge.c:7577: (apr_err=160013) |
| # ..\..\..\subversion\libsvn_client\merge.c:4132: (apr_err=160013) |
| # ..\..\..\subversion\libsvn_client\merge.c:3312: (apr_err=160013) |
| # ..\..\..\subversion\libsvn_client\ra.c:659: (apr_err=160013) |
| # ..\..\..\subversion\libsvn_repos\rev_hunt.c:696: (apr_err=160013) |
| # ..\..\..\subversion\libsvn_repos\rev_hunt.c:539: (apr_err=160013) |
| # ..\..\..\subversion\libsvn_fs_fs\tree.c:2818: (apr_err=160013) |
| # svn: File not found: revision 8, path '/A_COPY' |
| svntest.actions.run_and_verify_merge(A_COPY_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| def foreign_repos_del_and_props(sbox): |
| "merge del and ps variants from a foreign repos" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc2_dir = sbox.add_wc_path('wc2') |
| |
| (r2_path, r2_url) = sbox.add_repo_path('fgn') |
| svntest.main.create_repos(r2_path) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'checkout', |
| r2_url, wc2_dir) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'propset', |
| 'svn:eol-style', 'native', |
| sbox.ospath('iota')) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'cp', |
| sbox.ospath('A/D'), |
| sbox.ospath('D')) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'rm', |
| sbox.ospath('A/D'), |
| sbox.ospath('D/G')) |
| |
| new_file = sbox.ospath('new-file') |
| svntest.main.file_write(new_file, 'new-file') |
| svntest.actions.run_and_verify_svn(None, [], 'add', new_file) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'propset', |
| 'svn:eol-style', 'native', new_file) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'commit', wc_dir, |
| '-m', 'changed') |
| |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url, wc2_dir, |
| '-r', '0:1') |
| |
| expected_status = svntest.actions.get_virginal_state(wc2_dir, 0) |
| expected_status.tweak(status='A ') |
| expected_status.add( |
| { |
| '' : Item(status=' ', wc_rev='0'), |
| }) |
| svntest.actions.run_and_verify_status(wc2_dir, expected_status) |
| |
| expected_status = svntest.actions.get_virginal_state(wc2_dir, 1) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'commit', wc2_dir, |
| '-m', 'Merged r1') |
| |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url, wc2_dir, |
| '-r', '1:2', '--allow-mixed-revisions') |
| |
| expected_status.tweak('A/D', 'A/D/G', 'A/D/G/rho', 'A/D/G/tau', 'A/D/G/pi', |
| 'A/D/gamma', 'A/D/H', 'A/D/H/psi', 'A/D/H/omega', |
| 'A/D/H/chi', status='D ') |
| expected_status.tweak(wc_rev='1') |
| expected_status.tweak('', wc_rev='0') |
| expected_status.tweak('iota', status=' M') |
| |
| expected_status.add( |
| { |
| 'new-file' : Item(status='A ', wc_rev='0'), |
| 'D' : Item(status='A ', wc_rev='0'), |
| 'D/H' : Item(status='A ', wc_rev='0'), |
| 'D/H/omega' : Item(status='A ', wc_rev='0'), |
| 'D/H/psi' : Item(status='A ', wc_rev='0'), |
| 'D/H/chi' : Item(status='A ', wc_rev='0'), |
| 'D/gamma' : Item(status='A ', wc_rev='0'), |
| }) |
| |
| svntest.actions.run_and_verify_status(wc2_dir, expected_status) |
| |
| expected_output = ["Properties on '%s':\n" % (os.path.join(wc2_dir, 'iota')), |
| " svn:eol-style\n", |
| "Properties on '%s':\n" % (os.path.join(wc2_dir, 'new-file')), |
| " svn:eol-style\n" ] |
| svntest.actions.run_and_verify_svn(expected_output, [], 'proplist', |
| os.path.join(wc2_dir, 'iota'), |
| os.path.join(wc2_dir, 'new-file')) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3642 'immediate depth merges don't create proper subtree |
| # mergeinfo'. See https://issues.apache.org/jira/browse/SVN-3642 |
| @Issue(3642) |
| def immediate_depth_merge_creates_minimal_subtree_mergeinfo(sbox): |
| "no spurious mergeinfo from immediate depth merges" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| B_path = sbox.ospath('A/B') |
| B_COPY_path = sbox.ospath('A_COPY/B') |
| |
| |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Merge -c5 from A/B to A_COPY/B at --depth immediates. |
| # This should create only the minimum subtree mergeinfo |
| # required to describe the merge. This means that A_COPY/B/E gets |
| # non-inheritable mergeinfo for r5, because a full depth merge would |
| # affect that subtree. The other child of the merge target, A_COPY/B/F |
| # would never be affected by r5, so it doesn't need any explicit |
| # mergeinfo. |
| expected_output = wc.State(B_COPY_path, {}) |
| expected_mergeinfo_output = wc.State(B_COPY_path, { |
| '' : Item(status=' U'), |
| 'E' : Item(status=' U'), # A_COPY/B/E would be affected by r5 if the |
| # merge was at infinite depth, so it needs |
| # non-inheritable override mergeinfo. |
| #'F' : Item(status=' U'), No override mergeinfo, r5 is |
| # inoperative on this child. |
| }) |
| expected_elision_output = wc.State(B_COPY_path, { |
| }) |
| expected_status = wc.State(B_COPY_path, { |
| '' : Item(status=' M'), |
| 'F' : Item(status=' '), |
| 'E' : Item(status=' M'), |
| 'E/alpha' : Item(status=' '), |
| 'E/beta' : Item(status=' '), |
| 'lambda' : Item(status=' '), |
| |
| }) |
| expected_status.tweak(wc_rev=6) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:5'}), |
| 'E' : Item(props={SVN_PROP_MERGEINFO : '/A/B/E:5*'}), |
| 'E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'E/beta' : Item("This is the file 'beta'.\n"), |
| 'F' : Item(), |
| 'lambda' : Item("This is the file 'lambda'.\n") |
| }) |
| expected_skip = wc.State(B_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(B_COPY_path, '4', '5', |
| sbox.repo_url + '/A/B', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| '--depth', 'immediates', |
| B_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3646 'cyclic --record-only merges create self-referential |
| # mergeinfo' |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3646) |
| def record_only_merge_creates_self_referential_mergeinfo(sbox): |
| "merge creates self referential mergeinfo" |
| |
| # Given a copy of trunk@M to branch, committed in r(M+1), if we |
| # --record-only merge the branch back to trunk with no revisions |
| # specified, then trunk gets self-referential mergeinfo recorded |
| # reflecting its entire natural history. |
| |
| # Setup a standard greek tree in r1. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| mu_path = sbox.ospath('A/mu') |
| A_path = sbox.ospath('A') |
| A_branch_path = sbox.ospath('A-branch') |
| |
| # Make a change to A/mu in r2. |
| svntest.main.file_write(mu_path, "Trunk edit\n") |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', 'trunk edit', |
| wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| # Copy A to A-branch in r3 |
| svntest.actions.run_and_verify_svn(None, [], |
| 'copy', A_path, A_branch_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', |
| '-m', 'Branch A to A-branch', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Merge A-branch back to A. This should record the mergeinfo '/A-branch:3' |
| # on A. |
| expected_output = wc.State(A_path, {}) |
| expected_mergeinfo_output = wc.State(A_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_path, {}) |
| expected_A_status = wc.State(A_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_A_status.tweak(wc_rev=3) |
| expected_A_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A-branch:3'}), |
| 'B' : Item(), |
| 'mu' : Item("Trunk edit\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_A_skip = wc.State(A_path, {}) |
| svntest.actions.run_and_verify_merge(A_path, None, None, |
| sbox.repo_url + '/A-branch', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_A_disk, |
| expected_A_status, |
| expected_A_skip, |
| [], True, True, |
| '--record-only', A_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3657 'dav update report handler in skelta mode can cause |
| # spurious conflicts'. |
| @Issue(3657) |
| def dav_skelta_mode_causes_spurious_conflicts(sbox): |
| "dav skelta mode can cause spurious conflicts" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| mu_path = sbox.ospath('A/mu') |
| A_path = sbox.ospath('A') |
| C_path = sbox.ospath('A/C') |
| A_branch_path = sbox.ospath('A-branch') |
| C_branch_path = sbox.ospath('A-branch/C') |
| |
| # r2 - Set some initial properties: |
| # |
| # 'dir-prop'='value1' on A/C. |
| # 'svn:eol-style'='native' on A/mu. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ps', 'dir-prop', 'initial-val', |
| C_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ps', 'svn:eol-style', 'native', |
| mu_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', 'Set some properties', |
| wc_dir) |
| |
| # r3 - Branch 'A' to 'A-branch': |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'copy', A_path, A_branch_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', 'Create a branch of A', |
| wc_dir) |
| |
| # r4 - Make a text mod to 'A/mu' and add new props to 'A/mu' and 'A/C': |
| svntest.main.file_write(mu_path, "The new mu!\n") |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ps', 'prop-name', 'prop-val', mu_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ps', 'another-dir-prop', 'initial-val', |
| C_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Edit a file and make some prop changes', |
| wc_dir) |
| |
| # r5 - Modify the sole property on 'A-branch/C': |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ps', 'dir-prop', 'branch-val', |
| C_branch_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', 'prop mod on branch', wc_dir) |
| |
| # Now merge r4 from 'A' to 'A-branch'. |
| # |
| # Previously this failed over ra_neon and ra_serf on Windows: |
| # |
| # >svn merge ^^/A A-branch -c4 |
| # Conflict discovered in 'C:/SVN/src-trunk/Debug/subversion/tests/cmdline |
| # /svn-test-work/working_copies/merge_tests-110/A-branch/mu'. |
| # Select: (p) postpone, (df) diff-full, (e) edit, |
| # (mc) mine-conflict, (tc) theirs-conflict, |
| # (s) show all options: p |
| # --- Merging r4 into 'A-branch': |
| # CU A-branch\mu |
| # Conflict for property 'another-dir-prop' discovered on 'C:/SVN/src-trunk |
| # /Debug/subversion/tests/cmdline/svn-test-work/working_copies/ |
| # merge_tests-110/A-branch/C'. |
| # Select: (p) postpone, |
| # (mf) mine-full, (tf) theirs-full, |
| # (s) show all options: p |
| # C A-branch\C |
| # --- Recording mergeinfo for merge of r4 into 'A-branch': |
| # U A-branch |
| # Summary of conflicts: |
| # Text conflicts: 1 |
| # Property conflicts: 1 |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_output = wc.State(A_branch_path, { |
| 'mu' : Item(status='UU'), |
| 'C' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(A_branch_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_branch_path, {}) |
| expected_status = wc.State(A_branch_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status='MM'), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' M'), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=5) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : |
| '/A:4'}), |
| 'B' : Item(), |
| 'mu' : Item("The new mu!\n", |
| props={'prop-name' : 'prop-val', |
| 'svn:eol-style' : 'native'}), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(props={'dir-prop' : 'branch-val', |
| 'another-dir-prop' : 'initial-val'}), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_branch_path, {}) |
| svntest.actions.run_and_verify_merge(A_branch_path, 3, 4, |
| sbox.repo_url + '/A', |
| None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| |
| #---------------------------------------------------------------------- |
| def merge_into_locally_added_file(sbox): |
| "merge into locally added file" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| pi_path = sbox.ospath("A/D/G/pi") |
| new_path = sbox.ospath("A/D/G/new") |
| |
| shutil.copy(pi_path, new_path) |
| svntest.main.file_append(pi_path, "foo\n") |
| sbox.simple_commit() # r2 |
| |
| sbox.simple_add('A/D/G/new') |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/D/G/new' : Item(status='G '), |
| }) |
| expected_mergeinfo_output = wc.State(wc_dir, { |
| 'A/D/G/new' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(wc_dir, {}) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ 'A/D/G/new' : Item(status='A ', wc_rev=0)}) |
| expected_status.tweak('A/D/G/pi', wc_rev=2) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.tweak('A/D/G/pi', contents="This is the file 'pi'.\nfoo\n") |
| expected_disk.add({'A/D/G/new' : Item("This is the file 'pi'.\nfoo\n", |
| props={SVN_PROP_MERGEINFO : '/A/D/G/pi:2'})}) |
| expected_skip = wc.State(wc_dir, {}) |
| |
| svntest.actions.run_and_verify_merge(wc_dir, '1', '2', |
| sbox.repo_url + '/A/D/G/pi', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| new_path) |
| sbox.simple_commit() |
| |
| #---------------------------------------------------------------------- |
| def merge_into_locally_added_directory(sbox): |
| "merge into locally added directory" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about |
| G_path = sbox.ospath("A/D/G") |
| pi_path = sbox.ospath("A/D/G/pi") |
| new_dir_path = sbox.ospath("A/D/new_dir") |
| |
| svntest.main.file_append_binary(pi_path, "foo\n") |
| sbox.simple_commit() # r2 |
| |
| os.mkdir(new_dir_path) |
| svntest.main.file_append_binary(os.path.join(new_dir_path, 'pi'), |
| "This is the file 'pi'.\n") |
| svntest.main.file_append_binary(os.path.join(new_dir_path, 'rho'), |
| "This is the file 'rho'.\n") |
| svntest.main.file_append_binary(os.path.join(new_dir_path, 'tau'), |
| "This is the file 'tau'.\n") |
| sbox.simple_add('A/D/new_dir') |
| |
| expected_output = wc.State(wc_dir, { |
| 'A/D/new_dir/pi' : Item(status='G '), |
| }) |
| expected_mergeinfo_output = wc.State(wc_dir, { |
| 'A/D/new_dir' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(wc_dir, {}) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ 'A/D/new_dir' : Item(status='A ', wc_rev=0)}) |
| expected_status.add({ 'A/D/new_dir/pi' : Item(status='A ', wc_rev=0)}) |
| expected_status.add({ 'A/D/new_dir/rho' : Item(status='A ', wc_rev=0)}) |
| expected_status.add({ 'A/D/new_dir/tau' : Item(status='A ', wc_rev=0)}) |
| expected_status.tweak('A/D/G/pi', wc_rev=2) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.tweak('A/D/G/pi', contents="This is the file 'pi'.\nfoo\n") |
| expected_disk.add({'A/D/new_dir' : |
| Item(props={SVN_PROP_MERGEINFO : '/A/D/G:2'})}) |
| expected_disk.add({'A/D/new_dir/pi' : |
| Item(contents="This is the file 'pi'.\nfoo\n")}) |
| expected_disk.add({'A/D/new_dir/rho' : |
| Item(contents="This is the file 'rho'.\n")}) |
| expected_disk.add({'A/D/new_dir/tau' : |
| Item(contents="This is the file 'tau'.\n")}) |
| expected_skip = wc.State(wc_dir, {}) |
| |
| svntest.actions.run_and_verify_merge(wc_dir, '1', '2', |
| sbox.repo_url + '/A/D/G', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| new_dir_path) |
| sbox.simple_commit() |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #2915 'Handle mergeinfo for subtrees missing due to removal |
| # by non-svn command' |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(2915) |
| def merge_with_os_deleted_subtrees(sbox): |
| "merge tracking fails if target missing subtrees" |
| |
| # r1: Create a greek tree. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # r2 - r6: Copy A to A_COPY and then make some text changes under A. |
| set_up_branch(sbox) |
| |
| # Some paths we'll care about |
| A_COPY_path = sbox.ospath('A_COPY') |
| C_COPY_path = sbox.ospath('A_COPY/C') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| mu_COPY_path = sbox.ospath('A_COPY/mu') |
| G_COPY_path = sbox.ospath('A_COPY/D/G') |
| |
| # Remove several subtrees from disk. |
| svntest.main.safe_rmtree(C_COPY_path) |
| svntest.main.safe_rmtree(G_COPY_path) |
| os.remove(psi_COPY_path) |
| os.remove(mu_COPY_path) |
| |
| # Be sure the regex paths are properly escaped on Windows, see the |
| # note about "The Backslash Plague" in expected_merge_output(). |
| if sys.platform == 'win32': |
| re_sep = '\\\\' |
| else: |
| re_sep = os.sep |
| |
| # Common part of the expected error message for all cases we will test. |
| err_re = "svn: E195016: Merge tracking not allowed with missing subtrees; " + \ |
| "try restoring these items first:" + \ |
| "|(\n)" + \ |
| "|" + svntest.main.stack_trace_regexp |
| |
| # Case 1: Infinite depth merge into infinite depth WC target. |
| # Every missing subtree under the target should be reported as missing. |
| missing = "|(.*A_COPY" + re_sep + "mu\n)" + \ |
| "|(.*A_COPY" + re_sep + "D" + re_sep + "G\n)" + \ |
| "|(.*A_COPY" + re_sep + "C\n)" + \ |
| "|(.*A_COPY" + re_sep + "D" + re_sep + "H" + re_sep + "psi\n)" |
| exit_code, out, err = svntest.actions.run_and_verify_svn( |
| [], svntest.verify.AnyOutput, |
| 'merge', sbox.repo_url + '/A', A_COPY_path) |
| svntest.verify.verify_outputs("Merge failed but not in the way expected", |
| err, None, err_re + missing, None, |
| True) # Match *all* lines of stderr |
| |
| # Case 2: Immediates depth merge into infinite depth WC target. |
| # Only the two immediate children of the merge target should be reported |
| # as missing. |
| missing = "|(.*A_COPY" + re_sep + "mu\n)" + \ |
| "|(.*A_COPY" + re_sep + "C\n)" |
| exit_code, out, err = svntest.actions.run_and_verify_svn( |
| [], svntest.verify.AnyOutput, |
| 'merge', sbox.repo_url + '/A', A_COPY_path, '--depth=immediates') |
| svntest.verify.verify_outputs("Merge failed but not in the way expected", |
| err, None, err_re + missing, None, True) |
| |
| # Case 3: Files depth merge into infinite depth WC target. |
| # Only the single file child of the merge target should be reported |
| # as missing. |
| missing = "|(.*A_COPY" + re_sep + "mu\n)" |
| exit_code, out, err = svntest.actions.run_and_verify_svn( |
| [], svntest.verify.AnyOutput, |
| 'merge', sbox.repo_url + '/A', A_COPY_path, '--depth=files') |
| svntest.verify.verify_outputs("Merge failed but not in the way expected", |
| err, None, err_re + missing, None, True) |
| |
| # Case 4: Empty depth merge into infinite depth WC target. |
| # Only the...oh, wait, the target is present and that is as deep |
| # as the merge goes, so this merge should succeed! |
| svntest.actions.run_and_verify_svn( |
| svntest.verify.AnyOutput, [], 'merge', sbox.repo_url + '/A', |
| A_COPY_path, '--depth=empty') |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3668 'inheritance can result in self-referential |
| # mergeinfo' and issue #3669 'inheritance can result in mergeinfo |
| # describing nonexistent sources' |
| @Issue(3668,3669) |
| @XFail() |
| def no_self_referential_or_nonexistent_inherited_mergeinfo(sbox): |
| "don't inherit bogus mergeinfo" |
| |
| # r1: Create a greek tree. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # r2 - r6: Copy A to A_COPY and then make some text changes under A. |
| set_up_branch(sbox, nbr_of_branches=1) |
| |
| # Some paths we'll care about |
| nu_path = sbox.ospath('A/C/nu') |
| nu_COPY_path = sbox.ospath('A_COPY/C/nu') |
| J_path = sbox.ospath('A/D/J') |
| J_COPY_path = sbox.ospath('A_COPY/D/J') |
| zeta_path = sbox.ospath('A/D/J/zeta') |
| A_COPY_path = sbox.ospath('A_COPY') |
| |
| # r7 - Add the file A/C/nu |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| svntest.actions.run_and_verify_svn(None, [], 'commit', |
| '-m', 'Add file', wc_dir) |
| |
| # r8 - Sync merge A to A_COPY |
| svntest.actions.run_and_verify_svn( |
| svntest.verify.AnyOutput, [], 'merge', sbox.repo_url + '/A', |
| A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'commit', |
| '-m', 'Sync A_COPY with A', wc_dir) |
| |
| # r9 - Add the subtree A/D/J |
| # A/D/J/zeta |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', J_path) |
| svntest.main.file_write(zeta_path, "This is the file 'zeta'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', zeta_path) |
| svntest.actions.run_and_verify_svn(None, [], 'commit', |
| '-m', 'Add subtree', wc_dir) |
| |
| # Update the WC in preparation for merges. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # r10 - Sync merge A to A_COPY |
| svntest.actions.run_and_verify_svn( |
| svntest.verify.AnyOutput, [], 'merge', sbox.repo_url + '/A', |
| A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'commit', |
| '-m', 'Sync A_COPY with A', wc_dir) |
| |
| # r11 - Text changes to A/C/nu and A/D/J/zeta. |
| svntest.main.file_write(nu_path, "This is the EDITED file 'nu'.\n") |
| svntest.main.file_write(zeta_path, "This is the EDITED file 'zeta'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'commit', |
| '-m', 'Edit added files', wc_dir) |
| |
| # Update the WC in preparation for merges. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # This test is marked as XFail because the following two merges |
| # create mergeinfo with both non-existent path-revs and self-referential |
| # mergeinfo. |
| # |
| # Merge all available revisions from A/C/nu to A_COPY/C/nu. |
| # The target has no explicit mergeinfo of its own but inherits mergeinfo |
| # from A_COPY. A_COPY has the mergeinfo '/A:2-9' so the naive mergeinfo |
| # A_COPY/C/nu inherits is '/A/C/nu:2-9'. However, '/A/C/nu:2-6' don't |
| # actually exist (issue #3669) and '/A/C/nu:7-8' is self-referential |
| # (issue #3668). Neither of these should be present in the resulting |
| # mergeinfo for A_COPY/C/nu, only '/A/C/nu:8-11' |
| expected_output = wc.State(nu_COPY_path, { |
| '' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(nu_COPY_path, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(nu_COPY_path, { |
| }) |
| expected_status = wc.State(nu_COPY_path, { |
| '' : Item(status='MM', wc_rev=11), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/C/nu:8-11'}), |
| }) |
| expected_skip = wc.State(nu_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(nu_COPY_path, None, None, |
| sbox.repo_url + '/A/C/nu', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| # Merge all available revisions from A/D/J to A_COPY/D/J. Like the |
| # previous merge, the target should not have any non-existent ('/A/D/J:2-8') |
| # or self-referential mergeinfo ('/A/D/J:9') recorded on it post-merge. |
| expected_output = wc.State(J_COPY_path, { |
| 'zeta' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(J_COPY_path, { |
| '' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(J_COPY_path, { |
| }) |
| expected_status = wc.State(J_COPY_path, { |
| '' : Item(status=' M', wc_rev=11), |
| 'zeta' : Item(status='M ', wc_rev=11), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/J:10-11'}), |
| 'zeta' : Item("This is the EDITED file 'zeta'.\n") |
| }) |
| expected_skip = wc.State(J_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(J_COPY_path, None, None, |
| sbox.repo_url + '/A/D/J', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3756 'subtree merge can inherit invalid working mergeinfo', |
| # issue #3668 'inheritance can result in self-referential mergeinfo', and |
| # issue #3669 'inheritance can result in mergeinfo describing nonexistent |
| # sources'. |
| @XFail() |
| @Issue(3756,3668,3669) |
| def subtree_merges_inherit_invalid_working_mergeinfo(sbox): |
| "don't inherit bogus working mergeinfo" |
| |
| # r1: Create a greek tree. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # r2 - r6: Copy A to A_COPY and then make some text changes under A. |
| set_up_branch(sbox, nbr_of_branches=1) |
| |
| # Some paths we'll care about |
| nu_path = sbox.ospath('A/C/nu') |
| nu_COPY_path = sbox.ospath('A_COPY/C/nu') |
| A_COPY_path = sbox.ospath('A_COPY') |
| |
| # r7 - Add the file A/C/nu |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| svntest.actions.run_and_verify_svn(None, [], 'commit', |
| '-m', 'Add file', wc_dir) |
| |
| # r8 Merge c7 from A to A_COPY. |
| svntest.actions.run_and_verify_svn( |
| svntest.verify.AnyOutput, [], 'merge', sbox.repo_url + '/A', |
| A_COPY_path, '-c7') |
| svntest.actions.run_and_verify_svn(None, [], 'commit', |
| '-m', 'Merge subtree file addition', |
| wc_dir) |
| |
| # r9 - A text change to A/C/nu. |
| svntest.main.file_write(nu_path, "This is the EDITED file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'commit', |
| '-m', 'Edit added file', wc_dir) |
| |
| # Update the WC in preparation for merges. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Now do two merges. The first, r3 to the root of the branch A_COPY. |
| # This creates working mergeinfo '/A:3,7' on A_COPY. Then do a subtree |
| # file merge of r9 from A/C/nu to A_COPY/C/nu. Since the target has no |
| # explicit mergeinfo, the mergeinfo set to record the merge of r9 should |
| # include the mergeinfo inherited from A_COPY. *But* that raw inherited |
| # mergeinfo, '/A/C/nu:3,7' is wholly invalid: '/A/C/nu:3' simply doesn't |
| # exist in the repository and '/A/C/nu:7' is self-referential. So the |
| # resulting mergeinfo on 'A_COPY/C/nu' should be only '/A/C/nu:9'. |
| # |
| # Currently this test is marked as XFail because the resulting mergeinfo is |
| # '/A/C/nu:3,7,9' and thus includes a non-existent path-rev. |
| svntest.actions.run_and_verify_svn( |
| svntest.verify.AnyOutput, [], 'merge', sbox.repo_url + '/A', |
| A_COPY_path, '-c3') |
| svntest.actions.run_and_verify_svn( |
| svntest.verify.AnyOutput, [], 'merge', sbox.repo_url + '/A/C/nu', |
| nu_COPY_path, '-c9') |
| svntest.actions.run_and_verify_svn( |
| '/A/C/nu:9', [], 'pg', SVN_PROP_MERGEINFO, nu_COPY_path) |
| |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3686 'executable flag not correctly set on merge' |
| # See https://issues.apache.org/jira/browse/SVN-3686 |
| @Issue(3686) |
| @SkipUnless(server_has_mergeinfo) |
| @SkipUnless(svntest.main.is_posix_os) |
| def merge_change_to_file_with_executable(sbox): |
| "executable flag is maintained during binary merge" |
| |
| # Scenario: When merging a change to a binary file with the 'svn:executable' |
| # property set, the file is not marked as 'executable'. After commit, the |
| # executable bit is set correctly. |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| trunk_url = sbox.repo_url + '/A/B/E' |
| |
| alpha_path = sbox.ospath('A/B/E/alpha') |
| beta_path = sbox.ospath('A/B/E/beta') |
| |
| # Force one of the files to be a binary type |
| svntest.actions.run_and_verify_svn2(None, |
| binary_mime_type_on_text_file_warning, 0, |
| 'propset', 'svn:mime-type', |
| 'application/octet-stream', |
| alpha_path) |
| |
| # Set the 'svn:executable' property on both files |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'svn:executable', 'ON', |
| beta_path) |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'propset', 'svn:executable', 'ON', |
| alpha_path) |
| |
| # Verify the executable bit has been set before committing |
| if not os.access(alpha_path, os.X_OK): |
| raise svntest.Failure("alpha not marked as executable before commit") |
| if not os.access(beta_path, os.X_OK): |
| raise svntest.Failure("beta is not marked as executable before commit") |
| |
| # Commit change (r2) |
| sbox.simple_commit() |
| |
| # Verify the executable bit has remained after committing |
| if not os.access(alpha_path, os.X_OK): |
| raise svntest.Failure("alpha not marked as executable before commit") |
| if not os.access(beta_path, os.X_OK): |
| raise svntest.Failure("beta is not marked as executable before commit") |
| |
| # Create the branch |
| svntest.actions.run_and_verify_svn(None, [], 'cp', |
| trunk_url, |
| sbox.repo_url + '/branch', |
| '-m', "Creating the Branch") |
| |
| # Modify the files + commit (r3) |
| svntest.main.file_append(alpha_path, 'appended alpha text') |
| svntest.main.file_append(beta_path, 'appended beta text') |
| sbox.simple_commit() |
| |
| # Re-root the WC at the branch |
| svntest.main.safe_rmtree(wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'checkout', |
| sbox.repo_url + '/branch', wc_dir) |
| |
| # Recalculate the paths |
| alpha_path = sbox.ospath('alpha') |
| beta_path = sbox.ospath('beta') |
| |
| expected_output = wc.State(wc_dir, { |
| 'beta' : Item(status='U '), |
| 'alpha' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(wc_dir, { |
| '' : Item(status=' U') |
| }) |
| expected_elision_output = wc.State(wc_dir, { |
| }) |
| expected_disk = wc.State('', { |
| '.' : Item(props={'svn:mergeinfo':'/A/B/E:3-4'}), |
| 'alpha' : Item(contents="This is the file 'alpha'.\nappended alpha text", |
| props={'svn:executable':'*', |
| 'svn:mime-type':'application/octet-stream'}), |
| 'beta' : Item(contents="This is the file 'beta'.\nappended beta text", |
| props={"svn:executable" : '*'}), |
| }) |
| expected_status = wc.State(wc_dir, { |
| '' : Item(status=' M', wc_rev='4'), |
| 'alpha' : Item(status='M ', wc_rev='4'), |
| 'beta' : Item(status='M ', wc_rev='4'), |
| }) |
| expected_skip = wc.State(wc_dir, { }) |
| |
| # Merge the changes across |
| svntest.actions.run_and_verify_merge(wc_dir, None, None, |
| trunk_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True) |
| |
| |
| # Verify the executable bit has been set |
| if not os.access(alpha_path, os.X_OK): |
| raise svntest.Failure("alpha is not marked as executable after merge") |
| if not os.access(beta_path, os.X_OK): |
| raise svntest.Failure("beta is not marked as executable after merge") |
| |
| # Commit (r4) |
| sbox.simple_commit() |
| |
| # Verify the executable bit has been set |
| if not os.access(alpha_path, os.X_OK): |
| raise svntest.Failure("alpha is not marked as executable after commit") |
| if not os.access(beta_path, os.X_OK): |
| raise svntest.Failure("beta is not marked as executable after commit") |
| |
| def dry_run_merge_conflicting_binary(sbox): |
| "dry run shouldn't resolve conflicts" |
| |
| # This test-case is to showcase the regression caused by |
| # r1075802. Here is the link to the relevant discussion: |
| # http://svn.haxx.se/dev/archive-2011-03/0145.shtml |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| # Add a binary file to the project |
| theta_contents = open(os.path.join(sys.path[0], "theta.bin"), 'rb').read() |
| # Write PNG file data into 'A/theta'. |
| theta_path = sbox.ospath('A/theta') |
| svntest.main.file_write(theta_path, theta_contents, 'wb') |
| |
| svntest.main.run_svn(None, 'add', theta_path) |
| |
| # Commit the new binary file, creating revision 2. |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/theta' : Item(verb='Adding (bin)'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/theta' : Item(status=' ', wc_rev=2), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # Make the "other" working copy |
| other_wc = sbox.add_wc_path('other') |
| svntest.actions.duplicate_dir(wc_dir, other_wc) |
| |
| # Change the binary file in first working copy, commit revision 3. |
| svntest.main.file_append(theta_path, "some extra junk") |
| expected_output = wc.State(wc_dir, { |
| 'A/theta' : Item(verb='Sending'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.add({ |
| 'A/theta' : Item(status=' ', wc_rev=3), |
| }) |
| svntest.actions.run_and_verify_commit(wc_dir, expected_output, |
| expected_status) |
| |
| # In second working copy, append different content to the binary |
| # and attempt to 'svn merge -r 2:3'. |
| # We should see a conflict during the merge. |
| other_theta_path = os.path.join(other_wc, 'A', 'theta') |
| svntest.main.file_append(other_theta_path, "some other junk") |
| expected_output = wc.State(other_wc, { |
| 'A/theta' : Item(status='C '), |
| }) |
| expected_mergeinfo_output = wc.State(other_wc, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(other_wc, { |
| }) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_disk.add({ |
| '' : Item(props={SVN_PROP_MERGEINFO : '/:3'}), |
| 'A/theta' : Item(theta_contents + b"some other junk", |
| props={'svn:mime-type' : 'application/octet-stream'}), |
| }) |
| |
| # verify content of base(left) file |
| expected_disk.add({ |
| 'A/theta.merge-left.r2' : |
| Item(contents = theta_contents ) |
| }) |
| # verify content of theirs(right) file |
| expected_disk.add({ |
| 'A/theta.merge-right.r3' : |
| Item(contents= theta_contents + b"some extra junk") |
| }) |
| |
| expected_status = svntest.actions.get_virginal_state(other_wc, 1) |
| expected_status.add({ |
| '' : Item(status=' M', wc_rev=1), |
| 'A/theta' : Item(status='C ', wc_rev=2), |
| }) |
| expected_skip = wc.State('', { }) |
| |
| svntest.actions.run_and_verify_merge(other_wc, '2', '3', |
| sbox.repo_url, None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| '--allow-mixed-revisions', |
| other_wc) |
| |
| #---------------------------------------------------------------------- |
| @Issue(3857) |
| def foreign_repos_prop_conflict(sbox): |
| "prop conflict from foreign repos merge" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Create a second repository and working copy with the original |
| # greek tree. |
| repo_dir = sbox.repo_dir |
| other_repo_dir, other_repo_url = sbox.add_repo_path("other") |
| other_wc_dir = sbox.add_wc_path("other") |
| svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 1) |
| svntest.actions.run_and_verify_svn(None, [], 'co', other_repo_url, |
| other_wc_dir) |
| |
| # Add properties in the first repos and commit. |
| sbox.simple_propset('red', 'rojo', 'A/D/G') |
| sbox.simple_propset('yellow', 'amarillo', 'A/D/G') |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', 'spenglish', wc_dir) |
| |
| # Tweak properties in the first repos and commit. |
| sbox.simple_propset('red', 'rosso', 'A/D/G') |
| sbox.simple_propset('yellow', 'giallo', 'A/D/G') |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', 'engtalian', wc_dir) |
| |
| # Now, merge the propchange to the *second* working copy. |
| expected_output = [' C %s\n' % (os.path.join(other_wc_dir, |
| "A", "D", "G"))] |
| expected_output = expected_merge_output([[3]], expected_output, True, |
| prop_conflicts=1) |
| svntest.actions.run_and_verify_svn(expected_output, |
| [], 'merge', '-c3', |
| sbox.repo_url, |
| other_wc_dir) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #3975 'adds with explicit mergeinfo don't get mergeinfo |
| # describing merge which added them' |
| @Issue(3975) |
| @SkipUnless(server_has_mergeinfo) |
| def merge_adds_subtree_with_mergeinfo(sbox): |
| "merge adds subtree with mergeinfo" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, False, 2) |
| |
| A_path = sbox.ospath('A') |
| nu_path = sbox.ospath('A/C/nu') |
| nu_COPY_path = sbox.ospath('A_COPY/C/nu') |
| A_COPY2_path = sbox.ospath('A_COPY_2') |
| |
| # r8 - Add the file A_COPY/C/nu. |
| svntest.main.file_write(nu_COPY_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Add a file on the A_COPY branch', |
| wc_dir) |
| |
| # r9 - Cherry pick r8 from A_COPY to A. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url + '/A_COPY', |
| A_path, '-c8') |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Merge r8 from A_COPY to A', wc_dir) |
| |
| # r10 - Make a modification to A_COPY/C/nu |
| svntest.main.file_append(nu_COPY_path, |
| "More work on the A_COPY branch.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Some work on the A_COPY branch', wc_dir) |
| |
| # r9 - Cherry pick r10 from A_COPY/C/nu to A/C/nu. Make some |
| # changes to A/C/nu before committing the merge. |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url + '/A_COPY/C/nu', |
| nu_path, '-c10') |
| svntest.main.file_append(nu_path, "A faux conflict resolution.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Merge r8 from A_COPY to A', wc_dir) |
| |
| # Sync merge A to A_COPY_2 |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_output = wc.State(A_COPY2_path, { |
| 'B/E/beta' : Item(status='U '), |
| 'C/nu' : Item(status='A '), |
| 'D/G/rho' : Item(status='U '), |
| 'D/H/omega' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| '' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY2_path, { |
| '' : Item(status=' G'), |
| 'C/nu' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY2_path, { |
| }) |
| expected_status = wc.State(A_COPY2_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status='M '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'C/nu' : Item(status='A ', copied='+'), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status='M '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status='M '), |
| 'D/H/omega' : Item(status='M '), |
| }) |
| expected_status.tweak(wc_rev=11) |
| expected_status.tweak('C/nu', wc_rev='-') |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3-11\n/A_COPY:8'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| # C/nu will pick up the mergeinfo A_COPY/C/nu:8 which is self-referential. |
| # This is issue #3668 'inheritance can result in self-referential |
| # mergeinfo', but we'll allow it in this test since issue #3668 is |
| # tested elsewhere and is not the point of *this* test. |
| 'C/nu' : Item("This is the file 'nu'.\n" \ |
| "More work on the A_COPY branch.\n" \ |
| "A faux conflict resolution.\n", |
| props={SVN_PROP_MERGEINFO : |
| '/A/C/nu:9-11\n/A_COPY/C/nu:8,10'}), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State('.', { }) |
| svntest.actions.run_and_verify_merge(A_COPY2_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # A test for issue #3978 'reverse merge which adds subtree fails'. |
| @Issue(3978,4057) |
| @SkipUnless(server_has_mergeinfo) |
| def reverse_merge_adds_subtree(sbox): |
| "reverse merge adds subtree" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| A_path = sbox.ospath('A') |
| chi_path = sbox.ospath('A/D/H/chi') |
| A_COPY_path = sbox.ospath('A_COPY') |
| H_COPY_path = sbox.ospath('A_COPY/D/H') |
| |
| # r7 - Delete A\D\H\chi |
| svntest.actions.run_and_verify_svn(None, [], 'delete', chi_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Delete a file', wc_dir) |
| |
| # r8 - Merge r7 from A to A_COPY |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url + '/A', |
| A_COPY_path, '-c7') |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Cherry-pick r7 from A to A_COPY', wc_dir) |
| |
| # r9 - File depth sync merge from A/D/H to A_COPY/D/H/ |
| # This shallow merge does not create non-inheritable mergeinfo because of |
| # the issue #4057 fix; all subtrees affected by the diff are present, so |
| # non-inheritable mergeinfo is not required. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url + '/A/D/H', |
| H_COPY_path, '--depth', 'files') |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Cherry-pick r7 from A to A_COPY', wc_dir) |
| |
| # Reverse merge r7 from A to A_COPY |
| # |
| # Prior to the issue #3978 fix this merge failed with an assertion: |
| # |
| # >svn merge ^/A A_COPY -c-7 |
| # --- Reverse-merging r7 into 'A_COPY\D\H': |
| # A A_COPY\D\H\chi |
| # --- Recording mergeinfo for reverse merge of r7 into 'A_COPY': |
| # U A_COPY |
| # --- Recording mergeinfo for reverse merge of r7 into 'A_COPY\D\H': |
| # U A_COPY\D\H |
| # ..\..\..\subversion\svn\util.c:913: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_client\merge.c:10990: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_client\merge.c:10944: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_client\merge.c:10944: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_client\merge.c:10914: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_client\merge.c:8928: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_client\merge.c:7850: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_client\mergeinfo.c:120: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_wc\props.c:2472: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_wc\props.c:2247: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_wc\props.c:2576: (apr_err=200020) |
| # ..\..\..\subversion\libsvn_subr\mergeinfo.c:705: (apr_err=200020) |
| # svn: E200020: Could not parse mergeinfo string '-7' |
| # ..\..\..\subversion\libsvn_subr\mergeinfo.c:688: (apr_err=200022) |
| # ..\..\..\subversion\libsvn_subr\mergeinfo.c:607: (apr_err=200022) |
| # ..\..\..\subversion\libsvn_subr\mergeinfo.c:504: (apr_err=200022) |
| # ..\..\..\subversion\libsvn_subr\kitchensink.c:57: (apr_err=200022) |
| # svn: E200022: Negative revision number found parsing '-7' |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_output = wc.State(A_COPY_path, { |
| 'D/H/chi' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'D/H' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status=' '), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' M'), |
| 'D/H/chi' : Item(status='A ', copied='+'), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=9) |
| expected_status.tweak('D/H/chi', wc_rev='-') |
| expected_disk = wc.State('', { |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:2-6,8'}), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State('.', { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, 7, 6, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # A test for issue #3989 'merge which deletes file with native eol-style |
| # raises spurious tree conflict'. |
| @Issue(3989) |
| @SkipUnless(server_has_mergeinfo) |
| def merged_deletion_causes_tree_conflict(sbox): |
| "merged deletion causes spurious tree conflict" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| |
| A_path = sbox.ospath('A') |
| psi_path = sbox.ospath('A/D/H/psi') |
| H_branch_path = sbox.ospath('branch/D/H') |
| |
| # r2 - Set svn:eol-style native on A/D/H/psi |
| svntest.actions.run_and_verify_svn(None, [], 'ps', 'svn:eol-style', |
| 'native', psi_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Set eol-style native on a path', |
| wc_dir) |
| |
| # r3 - Branch ^/A to ^/branch |
| svntest.actions.run_and_verify_svn(None, [], 'copy', |
| sbox.repo_url + '/A', |
| sbox.repo_url + '/branch', |
| '-m', 'Copy ^/A to ^/branch') |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # r4 - Delete A/D/H/psi |
| svntest.actions.run_and_verify_svn(None, [], 'delete', psi_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'Delete a a path with native eol-style', |
| wc_dir) |
| |
| # Sync merge ^/A/D/H to branch/D/H. |
| # |
| # branch/D/H/psi is, ignoring differences caused by svn:eol-style, identical |
| # to ^/A/D/H/psi when the latter was deleted, so the deletion should merge |
| # cleanly. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_output = wc.State(H_branch_path, { |
| 'psi' : Item(status='D '), |
| }) |
| expected_mergeinfo_output = wc.State(H_branch_path, { |
| '' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(H_branch_path, {}) |
| expected_status = wc.State(H_branch_path, { |
| '' : Item(status=' M'), |
| 'chi' : Item(status=' '), |
| 'psi' : Item(status='D '), |
| 'omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=4) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:3-4'}), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| 'omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State('.', { }) |
| svntest.actions.run_and_verify_merge(H_branch_path, None, None, |
| sbox.repo_url + '/A/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| # A test for issue #3976 'record-only merges which add new subtree mergeinfo |
| # don't record mergeinfo describing merge'. |
| @Issue(3976) |
| @SkipUnless(server_has_mergeinfo) |
| def record_only_merge_adds_new_subtree_mergeinfo(sbox): |
| "record only merge adds new subtree mergeinfo" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| psi_path = sbox.ospath('A/D/H/psi') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| H_COPY2_path = sbox.ospath('A_COPY_2/D/H') |
| |
| # r7 - Copy ^/A_COPY to ^/A_COPY_2 |
| svntest.actions.run_and_verify_svn(None, [], |
| 'copy', '-m', 'copy A_COPY to A_COPY_2', |
| sbox.repo_url + '/A_COPY', |
| sbox.repo_url + '/A_COPY_2') |
| |
| # r8 - Set a property on A/D/H/psi. It doesn't matter what property |
| # we use, just as long as we have a change that can be merged independently |
| # of the text change to A/D/H/psi in r3. |
| svntest.main.run_svn(None, 'propset', 'svn:eol-style', 'native', psi_path) |
| svntest.main.run_svn(None, 'commit', '-m', 'set svn:eol-style', wc_dir) |
| |
| # r9 - Merge r3 from ^/A/D/H/psi to A_COPY/D/H/psi. |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url + '/A/D/H/psi', |
| psi_COPY_path, '-c3') |
| svntest.main.run_svn(None, 'commit', '-m', 'Subtree merge', wc_dir) |
| |
| # r10 - Merge r8 from ^/A/D/H/psi to A_COPY/D/H/psi. |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url + '/A/D/H/psi', |
| psi_COPY_path, '-c8') |
| svntest.main.run_svn(None, 'commit', '-m', 'Subtree merge', wc_dir) |
| |
| # Merge r10 from ^/A_COPY/D/H to A_COPY_2/D/H. This should leave |
| # A_COPY_2/D/H/psi with three new property additions: |
| # |
| # 1) The 'svn:eol-style=native' from r10 via r8. |
| # |
| # 2) The mergeinfo '/A/D/H/psi:8' from r10. |
| # |
| # 3) The mergeinfo '/A_COPY/D/H/psi:10' describing the merge itself. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_output = wc.State(H_COPY2_path, { |
| 'psi' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(H_COPY2_path, { |
| '' : Item(status=' U'), |
| 'psi' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(H_COPY2_path, {}) |
| expected_status = wc.State(H_COPY2_path, { |
| '' : Item(status=' M'), |
| 'chi' : Item(status=' '), |
| 'psi' : Item(status=' M'), |
| 'omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=10) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A_COPY/D/H:10'}), |
| 'psi' : Item("This is the file 'psi'.\n", |
| props={SVN_PROP_MERGEINFO : |
| '/A/D/H/psi:8\n/A_COPY/D/H/psi:10', |
| 'svn:eol-style' : 'native'}), |
| 'chi' : Item("This is the file 'chi'.\n"), |
| 'omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State('.', { }) |
| svntest.actions.run_and_verify_merge(H_COPY2_path, 9, 10, |
| sbox.repo_url + '/A_COPY/D/H', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #4056 "don't record non-inheritable mergeinfo if missing |
| # subtrees are not touched by the full-depth diff". |
| @Issue(4056) |
| @SkipUnless(server_has_mergeinfo) |
| def unnecessary_noninheritable_mergeinfo_missing_subtrees(sbox): |
| "missing subtrees untouched by infinite depth merge" |
| |
| B_branch_path = sbox.ospath('branch/B') |
| |
| # Setup a simple branch to which |
| expected_output, expected_mergeinfo_output, expected_elision_output, \ |
| expected_status, expected_disk, expected_skip = \ |
| noninheritable_mergeinfo_test_set_up(sbox) |
| |
| # Create a shallow merge target; set depth of branch/B to files. |
| svntest.main.run_svn(None, 'up', '--set-depth=files', B_branch_path) |
| expected_status.remove('E', 'E/alpha', 'E/beta', 'F') |
| expected_disk.remove('E', 'E/alpha', 'E/beta', 'F') |
| |
| # Merge r3 from ^/A/B to branch/B |
| # |
| # Merge is smart enough to realize that despite the shallow merge target, |
| # the diff can only affect branch/B/lambda, which is still present, so there |
| # is no need to record non-inheritable mergeinfo on the target |
| # or any subtree mergeinfo whatsoever: |
| # |
| # >svn pg svn:mergeinfo -vR |
| # Properties on 'branch\B': |
| # svn:mergeinfo |
| # /A/B:3 <-- Nothing was skipped, so doesn't need |
| # to be non-inheritable. |
| svntest.actions.run_and_verify_merge(B_branch_path, |
| '2', '3', |
| sbox.repo_url + '/A/B', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| B_branch_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #4057 "don't record non-inheritable mergeinfo in shallow |
| # merge if entire diff is within requested depth". |
| @Issue(4057) |
| @SkipUnless(server_has_mergeinfo) |
| def unnecessary_noninheritable_mergeinfo_shallow_merge(sbox): |
| "shallow merge reaches all necessary subtrees" |
| |
| B_branch_path = sbox.ospath('branch/B') |
| E_path = sbox.ospath('A/B/E') |
| |
| # Setup a simple branch to which |
| expected_output, expected_mergeinfo_output, expected_elision_output, \ |
| expected_status, expected_disk, expected_skip = \ |
| noninheritable_mergeinfo_test_set_up(sbox) |
| |
| # Merge r3 from ^/A/B to branch/B at operational depth=files |
| # |
| # Previously this failed because merge wasn't smart enough to |
| # realize that despite being a shallow merge, the diff can |
| # only affect branch/B/lambda, which is within the specified |
| # depth, so there is no need to record non-inheritable mergeinfo |
| # or subtree mergeinfo: |
| # |
| # >svn pg svn:mergeinfo -vR |
| # Properties on 'branch\B': |
| # svn:mergeinfo |
| # /A/B:3* <-- Should be inheritable |
| # Properties on 'branch\B\lambda': |
| # svn:mergeinfo |
| # /A/B/lambda:3 <-- Not necessary |
| expected_skip = wc.State(B_branch_path, {}) |
| svntest.actions.run_and_verify_merge(B_branch_path, '2', '3', |
| sbox.repo_url + '/A/B', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| '--depth', 'files', B_branch_path) |
| |
| # Revert the merge and then make a prop change to A/B/E in r4. |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '--recursive', sbox.wc_dir) |
| svntest.actions.run_and_verify_svn(["property 'prop:name' set on '" + |
| E_path + "'\n"], [], 'ps', |
| 'prop:name', 'propval', E_path) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ci', '-m', 'A new property on a dir', |
| sbox.wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'up', sbox.wc_dir) |
| |
| # Merge r4 from ^/A/B to branch/B at operational depth=immediates |
| # |
| # Previously this failed because the mergetracking logic didn't realize |
| # that despite being a shallow merge, the diff only affected branch/B/E, |
| # which was within the specified depth, so there was no need to record |
| # non-inheritable mergeinfo or subtree mergeinfo: |
| # |
| # >svn pg svn:mergeinfo -vR |
| # Properties on 'branch\B': |
| # svn:mergeinfo |
| # /A/B:4* <-- Should be inheritable |
| # Properties on 'branch\B\E': |
| # svn:mergeinfo |
| # /A/B/E:4 <-- Not necessary |
| expected_output = wc.State(B_branch_path, { |
| 'E' : Item(status=' U'), |
| }) |
| expected_mergeinfo_output = wc.State(B_branch_path, { |
| '' : Item(status=' U'), |
| 'E' : Item(status=' U'), |
| }) |
| expected_elision_output = wc.State(B_branch_path, { |
| 'E' : Item(status=' U'), |
| }) |
| expected_status = wc.State(B_branch_path, { |
| '' : Item(status=' M'), |
| 'lambda' : Item(status=' '), |
| 'E' : Item(status=' M'), |
| 'E/alpha' : Item(status=' '), |
| 'E/beta' : Item(status=' '), |
| 'F' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev='4') |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A/B:4'}), |
| 'lambda' : Item("This is the file 'lambda'.\n"), |
| 'E' : Item(props={'prop:name' : 'propval'}), |
| 'E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'E/beta' : Item("This is the file 'beta'.\n"), |
| 'F' : Item(), |
| }) |
| svntest.actions.run_and_verify_merge(B_branch_path, '3', '4', |
| sbox.repo_url + '/A/B', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, True, |
| '--depth', 'immediates', B_branch_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #4132, "merge of replaced source asserts". |
| # The original use-case is the following merges, which both asserted: |
| # svn merge -cr1295005 ^/subversion/trunk@1295000 ../src |
| # svn merge -cr1295004 ^/subversion/trunk/@r1295004 ../src |
| @Issue(4132) |
| def svnmucc_abuse_1(sbox): |
| "svnmucc: merge a replacement" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| |
| ## Using A/ as our trunk, since one cannot replace the root. |
| |
| ## r2: open a branch |
| sbox.simple_repo_copy('A', 'A_COPY') |
| |
| ## r3: padding (to make the revnums-mod-10 match) |
| sbox.simple_repo_copy('iota', 'padding') |
| |
| ## r4: trunk: accidental change |
| sbox.simple_append('A/mu', 'accidental change') |
| sbox.simple_commit() |
| |
| ## r5: fail to revert it |
| svntest.actions.run_and_verify_svnmucc(None, [], |
| '-m', 'r5', |
| '-U', sbox.repo_url, |
| 'rm', 'A', |
| 'cp', 'HEAD', 'A', 'A') |
| |
| ## r6: really revert it |
| svntest.actions.run_and_verify_svnmucc(None, [], |
| '-m', 'r6', |
| '-U', sbox.repo_url, |
| 'rm', 'A', |
| 'cp', '3', 'A', 'A') |
| |
| ## Attempt to merge that. |
| # This used to assert: |
| # --- Recording mergeinfo for merge of r5 into \ |
| # 'svn-test-work/working_copies/merge_tests-125/A_COPY': |
| # subversion/libsvn_subr/mergeinfo.c:1172: (apr_err=235000) |
| # svn: E235000: In file 'subversion/libsvn_subr/mergeinfo.c' \ |
| # line 1172: assertion failed (IS_VALID_FORWARD_RANGE(first)) |
| # |
| # Then, prior to the fix asserted this way: |
| # |
| # >svn merge -c5 ^/A@r5 A_COPY |
| # subversion\libsvn_client\merge.c:4871: (apr_err=235000) |
| # svn: E235000: In file 'subversion\libsvn_client\merge.c' |
| # line 4871: assertion failed (*gap_start < *gap_end) |
| sbox.simple_update() |
| svntest.main.run_svn(None, 'merge', '-c', 'r5', '^/A@r5', |
| sbox.ospath('A_COPY')) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #4138 'replacement in merge source not notified correctly'. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4138) |
| def merge_source_with_replacement(sbox): |
| "replacement in merge source not notified correctly" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about. |
| A_path = sbox.ospath('A') |
| omega_path = sbox.ospath('A/D/H/omega') |
| A_COPY_path = sbox.ospath('A_COPY') |
| beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') |
| omega_COPY_path = sbox.ospath('A_COPY/D/H/omega') |
| |
| # branch A@1 to A_COPY in r2, then make a few edits under A in r3-6: |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # r7 Delete A, replace it with A@5, effectively reverting the change |
| # made to A/D/H/omega in r6: |
| svntest.main.run_svn(None, 'up', wc_dir) |
| svntest.main.run_svn(None, 'del', A_path) |
| svntest.main.run_svn(None, 'copy', sbox.repo_url + '/A@5', A_path) |
| sbox.simple_commit(message='Replace A with older version of itself') |
| |
| # r8: Make an edit to A/D/H/omega: |
| svntest.main.file_write(omega_path, "New content for 'omega'.\n") |
| sbox.simple_commit(message='file edit') |
| |
| # Update and sync merge ^/A to A_COPY. |
| # |
| # text text text text text |
| # edit edit edit edit edit |
| # psi rho beta omega omega |
| # A@r1---r3----r4----r5----r6---X r7---r8---------> |
| # | | ^ | |
| # | v | | |
| # | +---replacement---+ | |
| # copy | |
| # | sync-merge |
| # | | |
| # v v |
| # r2---A_COPY-----------------------------------------> |
| svntest.main.run_svn(None, 'up', wc_dir) |
| # This test previously failed because the merge notifications make it look |
| # like r6 from ^/A was merged and recorded: |
| # |
| # >svn merge ^^/A A_COPY |
| # --- Merging r2 through r5 into 'A_COPY': |
| # U A_COPY\B\E\beta |
| # U A_COPY\D\G\rho |
| # U A_COPY\D\H\psi |
| # --- Recording mergeinfo for merge of r2 through r5 into 'A_COPY': |
| # U A_COPY |
| # --- Merging r6 through r8 into 'A_COPY': |
| # U A_COPY\D\H\omega |
| # --- Recording mergeinfo for merge of r6 through r8 into 'A_COPY': |
| # G A_COPY |
| expected_output = expected_merge_output( |
| [[2,5],[7,8]], |
| ['U ' + beta_COPY_path + '\n', |
| 'U ' + rho_COPY_path + '\n', |
| 'U ' + omega_COPY_path + '\n', |
| 'U ' + psi_COPY_path + '\n', |
| ' U ' + A_COPY_path + '\n', |
| ' G ' + A_COPY_path + '\n',]) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', sbox.repo_url + '/A', |
| A_COPY_path) |
| |
| # Misleading notifications are one thing, incorrect mergeinfo is quite |
| # another. |
| svntest.actions.run_and_verify_svn([A_COPY_path + ' - /A:2-5,7-8\n'], |
| [], 'pg', SVN_PROP_MERGEINFO, |
| '-R', A_COPY_path) |
| |
| # Commit the above merge and then reverse merge it. Again r6 is not |
| # being merged and should not be part of the notifications. |
| sbox.simple_commit() |
| sbox.simple_update() |
| expected_output = expected_merge_output( |
| [[5,2],[8,7]], |
| ['U ' + beta_COPY_path + '\n', |
| 'U ' + rho_COPY_path + '\n', |
| 'U ' + omega_COPY_path + '\n', |
| 'U ' + psi_COPY_path + '\n', |
| ' U ' + A_COPY_path + '\n', |
| ' G ' + A_COPY_path + '\n',], |
| elides=True) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', sbox.repo_url + '/A', |
| A_COPY_path, '-r8:1') |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #4144 'Reverse merge with replace in source applies |
| # diffs in forward order'. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4144) |
| def reverse_merge_with_rename(sbox): |
| "reverse merge applies revs in reverse order" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about. |
| A_path = sbox.ospath('A') |
| omega_path = sbox.ospath('trunk/D/H/omega') |
| A_COPY_path = sbox.ospath('A_COPY') |
| beta_COPY_path = sbox.ospath('A_COPY/B/E/beta') |
| psi_COPY_path = sbox.ospath('A_COPY/D/H/psi') |
| rho_COPY_path = sbox.ospath('A_COPY/D/G/rho') |
| omega_COPY_path = sbox.ospath('A_COPY/D/H/omega') |
| |
| # branch A@1 to A_COPY in r2, then make a few edits under A in r3-6: |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # r7 - Rename ^/A to ^/trunk. |
| svntest.actions.run_and_verify_svn(['Committing transaction...\n', |
| 'Committed revision 7.\n'], |
| [], 'move', |
| sbox.repo_url + '/A', |
| sbox.repo_url + '/trunk', |
| '-m', "Rename 'A' to 'trunk'") |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # r8 - Make and edit to trunk/D/H/omega (which was also edited in r6). |
| svntest.main.file_write(omega_path, "Edit 'omega' on trunk.\n") |
| sbox.simple_commit(message='Another omega edit') |
| |
| # r9 - Sync merge ^/trunk to A_COPY. |
| svntest.actions.run_and_verify_svn(None, # Don't check stdout, we test this |
| # type of merge to death elsewhere. |
| [], 'merge', sbox.repo_url + '/trunk', |
| A_COPY_path) |
| sbox.simple_commit(message='Sync A_COPY with ^/trunk') |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Reverse merge -r9:1 from ^/trunk to A_COPY. This should return |
| # A_COPY to the same state it had prior to the sync merge in r2. |
| # |
| # This currently fails because the Subversion tries to reverse merge |
| # -r6:1 first, then -r8:6, causing a spurious conflict on omega: |
| # |
| # >svn merge ^/trunk A_COPY -r9:1 --accept=postpone |
| # --- Reverse-merging r6 through r2 into 'A_COPY': |
| # U A_COPY\B\E\beta |
| # U A_COPY\D\G\rho |
| # C A_COPY\D\H\omega |
| # U A_COPY\D\H\psi |
| # --- Recording mergeinfo for reverse merge of r6 through r2 into 'A_COPY': |
| # U A_COPY |
| # Summary of conflicts: |
| # Text conflicts: 1 |
| # ..\..\..\subversion\svn\util.c:913: (apr_err=155015) |
| # ..\..\..\subversion\libsvn_client\merge.c:10848: (apr_err=155015) |
| # ..\..\..\subversion\libsvn_client\merge.c:10812: (apr_err=155015) |
| # ..\..\..\subversion\libsvn_client\merge.c:8984: (apr_err=155015) |
| # ..\..\..\subversion\libsvn_client\merge.c:4728: (apr_err=155015) |
| # svn: E155015: One or more conflicts were produced while merging r6:1 |
| # into 'C:\SVN\src-trunk-4\Debug\subversion\tests\cmdline\svn-test-work |
| # \working_copies\merge_tests-127\A_COPY' -- resolve all conflicts and |
| # rerun the merge to apply the remaining unmerged revisions |
| expected_output = expected_merge_output( |
| [[8,7],[6,2]], |
| ['U ' + beta_COPY_path + '\n', |
| 'U ' + rho_COPY_path + '\n', |
| 'U ' + omega_COPY_path + '\n', |
| 'G ' + omega_COPY_path + '\n', |
| 'U ' + psi_COPY_path + '\n', |
| ' U ' + A_COPY_path + '\n', |
| ' G ' + A_COPY_path + '\n',], elides=True) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', sbox.repo_url + '/trunk', |
| A_COPY_path, '-r9:1') |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #4166 'multiple merge editor drives which add then |
| # delete a subtree fail'. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4166) |
| def merge_adds_then_deletes_subtree(sbox): |
| "merge adds then deletes subtree" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about. |
| A_path = sbox.ospath('A') |
| nu_path = sbox.ospath('A/C/nu') |
| C_branch_path = sbox.ospath('branch/C') |
| nu_branch_path = sbox.ospath('branch/C/nu') |
| |
| # Make a branch. |
| svntest.actions.run_and_verify_svn(None, [], 'copy', |
| sbox.repo_url + '/A', |
| sbox.repo_url + '/branch', |
| '-m', 'Make a branch.') |
| |
| # On the branch parent: Add a file in r3 and then delete it in r4. |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', wc_dir, |
| '-m', 'Add a file') |
| svntest.actions.run_and_verify_svn(None, [], 'delete', nu_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', wc_dir, |
| '-m', 'Delete a file') |
| |
| # Merge r3 and r4 from ^/A/C to branch/C as part of one merge |
| # command, but as separate editor drives, i.e. 'c3,4 vs. -r2:4. |
| # These should be equivalent but the former was failing with: |
| # |
| # >svn merge ^/A/C branch\C -c3,4 |
| # --- Merging r3 into 'branch\C': |
| # A branch\C\nu |
| # --- Recording mergeinfo for merge of r3 into 'branch\C': |
| # U branch\C |
| # --- Merging r4 into 'branch\C': |
| # D branch\C\nu |
| # --- Recording mergeinfo for merge of r4 into 'branch\C': |
| # G branch\C |
| # ..\..\..\subversion\svn\util.c:913: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_client\merge.c:10873: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_client\merge.c:10837: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_client\merge.c:8994: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_client\merge.c:7923: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_client\mergeinfo.c:257: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_client\mergeinfo.c:97: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_wc\props.c:2003: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_wc\props.c:2024: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_wc\wc_db.c:11473: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_wc\wc_db.c:7247: (apr_err=155010) |
| # ..\..\..\subversion\libsvn_wc\wc_db.c:7232: (apr_err=155010) |
| # svn: E155010: The node 'C:\SVN\src-trunk\Debug\subversion\tests |
| # \cmdline\svn-test-work\working_copies\merge_tests-128\branch\C\nu' |
| # was not found. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[3],[4]], |
| ['A ' + nu_branch_path + '\n', |
| 'D ' + nu_branch_path + '\n', |
| ' U ' + C_branch_path + '\n', |
| ' G ' + C_branch_path + '\n',]), |
| [], 'merge', '-c3,4', sbox.repo_url + '/A/C', C_branch_path) |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #4169 'added subtrees with non-inheritable mergeinfo |
| # cause spurious subtree mergeinfo'. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4169) |
| def merge_with_added_subtrees_with_mergeinfo(sbox): |
| "merge with added subtrees with mergeinfo" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about. |
| A_path = sbox.ospath('A') |
| Y_path = sbox.ospath('A/C/X/Y') |
| Z_path = sbox.ospath('A/C/X/Y/Z') |
| nu_path = sbox.ospath('A/C/X/Y/Z/nu') |
| A_COPY_path = sbox.ospath('A_COPY') |
| Y_COPY_path = sbox.ospath('A_COPY/C/X/Y') |
| W_COPY_path = sbox.ospath('A_COPY/C/X/Y/Z/W') |
| A_COPY2_path = sbox.ospath('A_COPY_2') |
| |
| # Make two branches of ^/A and then make a few edits under A in r4-7: |
| wc_disk, wc_status = set_up_branch(sbox, nbr_of_branches=2) |
| |
| # r8 - Add a subtree under A. |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', '--parents', |
| Z_path) |
| svntest.main.file_write(nu_path, "This is the file 'nu'.\n") |
| svntest.actions.run_and_verify_svn(None, [], 'add', nu_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', wc_dir, |
| '-m', 'Add a subtree on our "trunk"') |
| |
| # r9 - Sync ^/A to the first branch A_COPY. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url + '/A', A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', wc_dir, |
| '-m', 'Sync ^/A to ^/A_COPY') |
| |
| # r10 - Make some edits on the first branch. |
| svntest.actions.run_and_verify_svn(None, [], 'ps', 'branch-prop-foo', |
| 'bar', Y_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'mkdir', W_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', wc_dir, |
| '-m', 'Make some edits on "branch 1"') |
| |
| # r11 - Cherry-pick r10 on the first branch back to A, but |
| # do so at depth=empty so non-inheritable mergeinfo is created. |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '-c10', '--depth=empty', |
| sbox.repo_url + '/A_COPY/C/X/Y', Y_path) |
| svntest.actions.run_and_verify_svn( |
| None, [], 'ci', wc_dir, |
| '-m', 'Depth empty subtree cherry pick from "branch 1" to "trunk"') |
| |
| # Sync ^/A to the second branch A_COPY_2. |
| # |
| # Previously this failed because spurious mergeinfo was created on |
| # A_COPY_2/C/X/Y/Z: |
| # |
| # >svn merge ^^/A A_COPY_2 |
| # --- Merging r3 through r11 into 'A_COPY_2': |
| # U A_COPY_2\B\E\beta |
| # A A_COPY_2\C\X |
| # A A_COPY_2\C\X\Y |
| # A A_COPY_2\C\X\Y\Z |
| # A A_COPY_2\C\X\Y\Z\nu |
| # U A_COPY_2\D\G\rho |
| # U A_COPY_2\D\H\omega |
| # U A_COPY_2\D\H\psi |
| # --- Recording mergeinfo for merge of r3 through r11 into 'A_COPY_2': |
| # U A_COPY_2 |
| # --- Recording mergeinfo for merge of r3 through r11 into 'A_COPY_2\C\X\Y': |
| # G A_COPY_2\C\X\Y |
| # vvvvvvvvvvvvvvvvvvvv |
| # U A_COPY_2\C\X\Y\Z |
| # ^^^^^^^^^^^^^^^^^^^^ |
| # |
| # >svn pl -vR A_COPY_2 |
| # Properties on 'A_COPY_2': |
| # svn:mergeinfo |
| # /A:3-11 |
| # Properties on 'A_COPY_2\C\X\Y': |
| # branch-prop-foo |
| # bar |
| # svn:mergeinfo |
| # /A/C/X/Y:8-11 |
| # /A_COPY/C/X/Y:10* |
| # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv |
| # Properties on 'A_COPY_2\C\X\Y\Z': |
| # svn:mergeinfo |
| # /A/C/X/Y/Z:8-11 |
| # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| expected_output = wc.State(A_COPY2_path, { |
| 'B/E/beta' : Item(status='U '), |
| 'D/G/rho' : Item(status='U '), |
| 'D/H/omega' : Item(status='U '), |
| 'D/H/psi' : Item(status='U '), |
| 'C/X' : Item(status='A '), |
| 'C/X/Y' : Item(status='A '), |
| 'C/X/Y/Z' : Item(status='A '), |
| 'C/X/Y/Z/nu' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY2_path, { |
| '' : Item(status=' U'), |
| 'C/X/Y' : Item(status=' U'), # Added with explicit mergeinfo |
| }) |
| expected_elision_output = wc.State(A_COPY2_path, { |
| }) |
| expected_status = wc.State(A_COPY2_path, { |
| '' : Item(status=' M', wc_rev=11), |
| 'B' : Item(status=' ', wc_rev=11), |
| 'mu' : Item(status=' ', wc_rev=11), |
| 'B/E' : Item(status=' ', wc_rev=11), |
| 'B/E/alpha' : Item(status=' ', wc_rev=11), |
| 'B/E/beta' : Item(status='M ', wc_rev=11), |
| 'B/lambda' : Item(status=' ', wc_rev=11), |
| 'B/F' : Item(status=' ', wc_rev=11), |
| 'C' : Item(status=' ', wc_rev=11), |
| 'C/X' : Item(status='A ', wc_rev='-', copied='+'), |
| 'C/X/Y' : Item(status=' M', wc_rev='-', copied='+'), |
| 'C/X/Y/Z' : Item(status=' ', wc_rev='-', copied='+'), |
| 'C/X/Y/Z/nu' : Item(status=' ', wc_rev='-', copied='+'), |
| 'D' : Item(status=' ', wc_rev=11), |
| 'D/G' : Item(status=' ', wc_rev=11), |
| 'D/G/pi' : Item(status=' ', wc_rev=11), |
| 'D/G/rho' : Item(status='M ', wc_rev=11), |
| 'D/G/tau' : Item(status=' ', wc_rev=11), |
| 'D/gamma' : Item(status=' ', wc_rev=11), |
| 'D/H' : Item(status=' ', wc_rev=11), |
| 'D/H/chi' : Item(status=' ', wc_rev=11), |
| 'D/H/psi' : Item(status='M ', wc_rev=11), |
| 'D/H/omega' : Item(status='M ', wc_rev=11), |
| }) |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:3-11'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("New content"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(), |
| 'C/X' : Item(), |
| 'C/X/Y' : Item(props={ |
| SVN_PROP_MERGEINFO : '/A/C/X/Y:8-11\n/A_COPY/C/X/Y:10*', |
| 'branch-prop-foo' : 'bar'}), |
| 'C/X/Y/Z' : Item(), |
| 'C/X/Y/Z/nu' : Item("This is the file 'nu'.\n"), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("New content"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("New content"), |
| 'D/H/omega' : Item("New content"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY2_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| check_props=True) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| def merge_with_externals_with_mergeinfo(sbox): |
| "merge with externals with mergeinfo" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| |
| # Some paths we'll care about. |
| A_path = sbox.ospath('A') |
| A_COPY_path = sbox.ospath('A_COPY') |
| file_external_path = sbox.ospath('A/file-external') |
| mu_COPY_path = sbox.ospath('A_COPY/mu') |
| mu_path = sbox.ospath('A/mu') |
| |
| # Make a branch of ^/A and then make a few edits under A in r3-6: |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| svntest.main.file_write(mu_COPY_path, "branch edit") |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'file edit on the branch', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| |
| # Create a file external under 'A' and set some bogus mergeinfo |
| # on it (the fact that this mergeinfo is bogus has no bearing on |
| # this test). |
| svntest.actions.run_and_verify_svn(None, [], 'propset', |
| 'svn:externals', |
| '^/iota file-external', A_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'set file external', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, [], 'ps', SVN_PROP_MERGEINFO, |
| "/bogus-mergeinfo:5", file_external_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'set mergeinfo on file external', |
| file_external_path) |
| |
| # Sync merge ^/A to A_COPY and then reintegrate A_COPY back to A. |
| svntest.actions.run_and_verify_svn(None, [], 'merge', |
| sbox.repo_url + '/A', A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'ci', '-m', |
| 'sync merge', wc_dir) |
| # This was segfaulting, see |
| # http://svn.haxx.se/dev/archive-2012-10/0364.shtml |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output(None, |
| ['U ' + mu_path + '\n', |
| ' U ' + A_path + '\n'], |
| two_url=True), |
| [], 'merge', '--reintegrate', sbox.repo_url + '/A_COPY', |
| A_path) |
| |
| #---------------------------------------------------------------------- |
| # Test merging 'binary' files with keyword expansion enabled. |
| # Tests issue #4221 'Trivial merge of a binary file with svn:keywords |
| # raises a conflict', among other cases. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4221) |
| def merge_binary_file_with_keywords(sbox): |
| "merge binary file with keywords" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| # Some binary files, and some binary files that will become text files. |
| # 'mod_src' means a content change on the branch (the merge source); |
| # 'mod_tgt' means a content change on the original (the merge target); |
| # 'to_txt' means svn:mime-type removed on the branch (the merge source). |
| file_mod_both = 'A/B/E/alpha' |
| file_mod_src = 'A/D/G/pi' |
| file_mod_tgt = 'A/D/G/rho' |
| file_mod_none = 'A/D/G/tau' |
| file_mod_both_to_txt = 'A/B/E/beta' |
| file_mod_src_to_txt = 'A/D/H/chi' |
| file_mod_tgt_to_txt = 'A/D/H/psi' |
| file_mod_none_to_txt = 'A/D/H/omega' |
| files_bin = [ file_mod_both, file_mod_src, file_mod_tgt, file_mod_none ] |
| files_txt = [ file_mod_both_to_txt, file_mod_src_to_txt, |
| file_mod_tgt_to_txt, file_mod_none_to_txt ] |
| files = files_bin + files_txt |
| |
| # make some 'binary' files with keyword expansion enabled |
| for f in files: |
| svntest.main.file_append(sbox.ospath(f), "With $Revision: $ keyword.\n") |
| svntest.main.run_svn(binary_mime_type_on_text_file_warning, |
| 'propset', 'svn:mime-type', |
| 'application/octet-stream', sbox.ospath(f)) |
| sbox.simple_propset('svn:keywords', 'Revision', f) |
| sbox.simple_commit() |
| |
| # branch the files |
| sbox.simple_repo_copy('A', 'A2') |
| sbox.simple_update() |
| |
| # Modify the branched (source) and/or original (target) versions. Remove |
| # the svn:mime-type from the 'to_txt' files on the branch. |
| # The original bug in issue #4221 gave a conflict if we modified either |
| # version or neither (using a single-file merge test case). |
| for f in [ file_mod_both, file_mod_both_to_txt, |
| file_mod_src, file_mod_src_to_txt ]: |
| f_branch = 'A2' + f[1:] |
| svntest.main.file_append(sbox.ospath(f_branch), "Incoming mod.\n") |
| for f in [ file_mod_both, file_mod_both_to_txt, |
| file_mod_tgt, file_mod_tgt_to_txt ]: |
| svntest.main.file_append(sbox.ospath(f), "Mod on merge target.\n") |
| for f in files_txt: |
| f_branch = 'A2' + f[1:] |
| sbox.simple_propdel('svn:mime-type', f_branch) |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # merge back |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[3,4]], |
| ['C ' + sbox.ospath(file_mod_both) + '\n', |
| 'U ' + sbox.ospath(file_mod_src) + '\n', |
| #' ' + sbox.ospath(file_mod_tgt) + '\n', |
| #' ' + sbox.ospath(file_mod_none) + '\n', |
| 'CU ' + sbox.ospath(file_mod_both_to_txt) + '\n', |
| 'UU ' + sbox.ospath(file_mod_src_to_txt) + '\n', |
| ' U ' + sbox.ospath(file_mod_tgt_to_txt) + '\n', |
| ' U ' + sbox.ospath(file_mod_none_to_txt) + '\n', |
| ' U A\n'], |
| text_conflicts=2), |
| [], 'merge', '^/A2', 'A') |
| |
| #---------------------------------------------------------------------- |
| # Test for issue #4155 'Merge conflict text of expanded keyword incorrect |
| # when svn:keyword property value removed'. Failed in 1.7.0 through 1.7.8. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4155) |
| def merge_conflict_when_keywords_removed(sbox): |
| "merge conflict when keywords removed" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| # make a file with keyword expansion enabled |
| svntest.main.file_write('A/keyfile', "$Date$ $Revision$\n") |
| sbox.simple_add('A/keyfile') |
| sbox.simple_propset('svn:keywords', 'Date Revision', 'A/keyfile') |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # branch the file |
| sbox.simple_repo_copy('A', 'A2') |
| sbox.simple_update() |
| |
| # |
| svntest.main.file_append('A/keyfile', " some changes\n") |
| sbox.simple_commit() |
| |
| # sync merge |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[3,4]], |
| ['U '+ sbox.ospath('A2/keyfile') + '\n', |
| ' U A2\n']), |
| [], 'merge', '^/A', 'A2') |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # modify the original version: disable those KW & enable 'Id' |
| sbox.simple_propset('svn:keywords', 'Id', 'A/keyfile') |
| svntest.main.file_append('A/keyfile', "$Id$\n") |
| sbox.simple_commit() |
| |
| # sync merge again |
| svntest.actions.run_and_verify_svn( |
| expected_merge_output([[5,6]], |
| ['UU ' + sbox.ospath('A2/keyfile') + '\n', |
| ' U A2\n']), |
| [], 'merge', '--accept=postpone', '^/A', 'A2') |
| |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4139, 3274, 3503) |
| def merge_target_selection(sbox): |
| "merge target selection handling" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| # r2 |
| sbox.simple_mkdir('dir') |
| sbox.simple_add_text('\1\2\3\4\5', 'dir/binary-file') |
| sbox.simple_add_text('abcde', 'dir/text-file') |
| sbox.simple_commit() |
| |
| # r3 |
| sbox.simple_copy('dir', 'branch') |
| sbox.simple_commit() |
| |
| # r4 |
| svntest.main.file_write(sbox.ospath('dir/binary-file'), |
| '\9\8\7\6\5\4\3\2\1') |
| sbox.simple_commit() |
| |
| sbox.simple_update() |
| |
| os.chdir(sbox.ospath('branch')) |
| |
| # Merge the directory (no target) |
| expected_output = [ |
| '--- Merging r4 into \'.\':\n', |
| 'U binary-file\n', |
| '--- Recording mergeinfo for merge of r4 into \'.\':\n', |
| ' U .\n', |
| ] |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '^/dir', '-c', '4') |
| |
| svntest.main.run_svn(None, 'revert', '-R', '.') |
| |
| # Merge the file (no target) |
| expected_output = [ |
| '--- Merging r4 into \'binary-file\':\n', |
| 'U binary-file\n', |
| '--- Recording mergeinfo for merge of r4 into \'binary-file\':\n', |
| ' U binary-file\n', |
| ] |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '^/dir/binary-file', '-c', '4') |
| |
| svntest.main.run_svn(None, 'revert', '-R', '.') |
| |
| # Merge the directory (explicit target) |
| expected_output = [ |
| '--- Merging r4 into \'.\':\n', |
| 'U binary-file\n', |
| '--- Recording mergeinfo for merge of r4 into \'.\':\n', |
| ' U .\n', |
| ] |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '^/dir', '-c', '4', '.') |
| |
| svntest.main.run_svn(None, 'revert', '-R', '.') |
| |
| # Merge the file (explicit target) |
| expected_output = [ |
| '--- Merging r4 into \'binary-file\':\n', |
| 'U binary-file\n', |
| '--- Recording mergeinfo for merge of r4 into \'binary-file\':\n', |
| ' U binary-file\n', |
| ] |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '^/dir/binary-file', '-c', '4', 'binary-file') |
| |
| svntest.main.run_svn(None, 'revert', '-R', '.') |
| |
| # Merge the file (wrong target) |
| expected_output = [ |
| '--- Merging r4 into \'.\':\n', |
| ' C .\n', |
| '--- Recording mergeinfo for merge of r4 into \'.\':\n', |
| ' U .\n', |
| ] + svntest.main.summary_of_conflicts(tree_conflicts=1) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '^/dir/binary-file', |
| '-c', '4', '.', '--accept', 'postpone') |
| |
| svntest.main.run_svn(None, 'revert', '-R', '.') |
| |
| # Merge the dir (wrong target) |
| expected_output = [ |
| '--- Merging r4 into \'binary-file\':\n', |
| ' C %s\n' % os.path.join('binary-file'), |
| '--- Recording mergeinfo for merge of r4 into \'binary-file\':\n', |
| ' U binary-file\n', |
| ] + svntest.main.summary_of_conflicts(tree_conflicts=1) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'merge', '^/dir', '-c', '4', 'binary-file', |
| '--accept', 'postpone') |
| |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(3405) # seems to be the wrong issue number |
| def merge_properties_on_adds(sbox): |
| "merged directory properties are added" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| sbox.simple_copy('A/D/G', 'G') |
| |
| sbox.simple_mkdir('A/D/G/M') |
| sbox.simple_mkdir('A/D/G/M/N') |
| sbox.simple_add_text('QQ', 'A/D/G/file', 'A/D/G/M/file') |
| sbox.simple_propset('key', 'value', |
| 'A/D/G/M', 'A/D/G/file', 'A/D/G/M/N', 'A/D/G/M/file') |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '^/A/D/G', sbox.ospath('G')) |
| |
| expected_output = svntest.verify.UnorderedOutput([ |
| 'Properties on \'%s\':\n' % sbox.ospath('G'), |
| ' svn:mergeinfo\n', |
| 'Properties on \'%s\':\n' % sbox.ospath('G/M'), |
| ' key\n', |
| 'Properties on \'%s\':\n' % sbox.ospath('G/file'), |
| ' key\n', |
| 'Properties on \'%s\':\n' % sbox.ospath('G/M/N'), |
| ' key\n', |
| 'Properties on \'%s\':\n' % sbox.ospath('G/M/file'), |
| ' key\n', |
| ]) |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'proplist', '-R', sbox.ospath('G')) |
| |
| expected_output = svntest.verify.UnorderedOutput([ |
| 'Properties on \'%s\':\n' % sbox.ospath('G/M'), |
| ' key\n', |
| 'Properties on \'%s\':\n' % sbox.ospath('G/file'), |
| ' key\n', |
| 'Properties on \'%s\':\n' % sbox.ospath('G/M/N'), |
| ' key\n', |
| 'Properties on \'%s\':\n' % sbox.ospath('G/M/file'), |
| ' key\n', |
| ]) |
| |
| # I merged the tree, which should include history but only the files have |
| # the properties stored in PRISTINE. All directories have the properties |
| # as local changes in ACTUAL. |
| svntest.actions.run_and_verify_svn(expected_output, [], |
| 'proplist', '-R', sbox.ospath('G'), |
| '-r', 'BASE') |
| |
| # Note that this is not a regression. This has been the case since 1.0. |
| # ### We just made status, update and merge handle this without users |
| # ### knowing about this limitation. |
| |
| # ### My guess is that the base merge support on svn_wc_merge_props() |
| # ### was originally designed to resolve this problem, but I can't |
| # ### find a released version where this was actually implemented. |
| |
| # For fun, also check the status: 'svn status' suppresses the M from AM. |
| |
| # G = sbox.ospath('G') |
| # |
| # expected_status = wc.State('G', { |
| # '' : Item(status=' M', wc_rev='2'), |
| # 'pi' : Item(status=' ', wc_rev='2'), |
| # 'tau' : Item(status=' ', wc_rev='2'), |
| # 'file' : Item(status='A ', copied='+', wc_rev='-'), # Copied, no changes |
| # 'M' : Item(status='A ', copied='+', wc_rev='-'), # Copied, changes |
| # 'M/file' : Item(status=' ', copied='+', wc_rev='-'), # Copied, no changes |
| # 'M/N' : Item(status=' M', copied='+', wc_rev='-'), # Local changes |
| # 'rho' : Item(status=' ', wc_rev='2'), |
| # }) |
| # svntest.actions.run_and_verify_status(G, expected_status) |
| |
| |
| # ====================================================================== |
| # Functions for parsing mergeinfo |
| |
| def parse_changes_list(changes_string): |
| """Parse a string containing a list of revision numbers in the form |
| of the '--change' command-line argument (e.g. '1,3,-5,7-10'). |
| Return a list of elements of the form [[1], [3], [-5], [7,10]]. |
| """ |
| rev_ranges = [] |
| for rr in changes_string.split(','): |
| if '-' in rr[1:]: |
| revs = rr.split('-') |
| rev_ranges.append([int(revs[0]), int(revs[1])]) |
| else: |
| rev_ranges.append([int(rr)]) |
| return rev_ranges |
| |
| def parse_rev_args(arg_list): |
| """Return a list of [rX:rY] or [rZ] elements representing ARG_LIST |
| whose elements are strings in the form '-rX:Y' or '-cZ,X-Y,...'. |
| """ |
| rev_ranges = [] |
| for arg in arg_list: |
| kind = arg[:2] |
| val = arg[2:] |
| if kind == '-r': |
| if ':' in val: |
| revs = map(int, val.split(':')) |
| if revs[0] < revs[1]: |
| rev_ranges.append([revs[0] + 1, revs[1]]) |
| else: |
| rev_ranges.append([revs[0], revs[1] + 1]) |
| else: |
| rev_ranges.append([int(val)]) |
| elif kind == '-c': |
| rev_ranges.extend(parse_changes_list(val)) |
| else: |
| raise ValueError("revision arg '" + arg + "' in '" + arg_list + |
| "' does not start with -r or -c") |
| return rev_ranges |
| |
| class RangeList(list): |
| """Represents of a list of revision ranges, as a list of one- or |
| two-element lists, each of the form [X] meaning "--revision (X-1):X" |
| or [X,Y] meaning "--revision (X-1):Y". |
| """ |
| def __init__(self, arg): |
| """ |
| """ |
| self.as_given = arg |
| if isinstance(arg, str): |
| list.__init__(self, parse_changes_list(arg)) |
| elif isinstance(arg, list): |
| list.__init__(self, parse_rev_args(arg)) |
| else: |
| raise ValueError("RangeList needs a string or a list, not '" + str(arg) + "'") |
| |
| def expected_merge_output2(tgt_ospath, |
| recorded_ranges, |
| merged_ranges=None, |
| prop_conflicts=0, |
| prop_resolved=0): |
| """Return an ExpectedOutput instance corresponding to the expected |
| output of a merge into TGT_OSPATH, with one 'recording |
| mergeinfo...' notification per specified revision range in |
| RECORDED_RANGES and one 'merging...' notification per revision |
| range in MERGED_RANGES. |
| |
| RECORDED_RANGES is a mergeinfo-string or a RangeList. |
| |
| MERGED_RANGES is a list of mergeinfo-strings or a list of |
| RangeLists. If None, it means [[r] for r in RECORDED_RANGES]. |
| """ |
| # Convert RECORDED_RANGES to a RangeList. |
| if isinstance(recorded_ranges, str): |
| recorded_ranges = RangeList(recorded_ranges) |
| # Convert MERGED_RANGES to a list of RangeLists. |
| if merged_ranges is None: |
| merged_ranges = [[r] for r in recorded_ranges] |
| elif len(merged_ranges) > 0 and isinstance(merged_ranges[0], str): |
| # List of mergeinfo-strings => list of rangelists |
| merged_ranges = [RangeList(r) for r in merged_ranges] |
| |
| status_letters_re = (prop_conflicts or prop_resolved) and ' [UC]' or ' U' |
| status_letters_mi = ' [UG]' |
| lines = [] |
| for i, rr in enumerate(recorded_ranges): |
| # Merging ... |
| for sr in merged_ranges[i]: |
| revstart = sr[0] |
| revend = len(sr) > 1 and sr[1] or None |
| lines += [svntest.main.merge_notify_line(revstart, revend, |
| target=tgt_ospath)] |
| lines += [status_letters_re + ' ' + re.escape(tgt_ospath) + '\n'] |
| # Recording mergeinfo ... |
| revstart = rr[0] |
| revend = len(rr) > 1 and rr[1] or None |
| lines += [svntest.main.mergeinfo_notify_line(revstart, revend, |
| target=tgt_ospath)] |
| lines += [status_letters_mi + ' ' + re.escape(tgt_ospath) + '\n'] |
| |
| # Summary of conflicts |
| lines += svntest.main.summary_of_conflicts(prop_conflicts=prop_conflicts, |
| prop_resolved=prop_resolved, |
| as_regex=True) |
| |
| # The 'match_all=False' is because we also expect some |
| # 'Resolved conflicted state of ...' lines. |
| return RegexListOutput(lines, match_all=False) |
| |
| def expected_out_and_err(tgt_ospath, |
| recorded_ranges, |
| merged_ranges=None, |
| prop_conflicts=0, |
| prop_resolved=0, |
| expect_error=True): |
| """Return a tuple (expected_out, expected_err) giving the expected |
| output and expected error output for a merge into TGT_OSPATH. See |
| expected_merge_output2() for details of RECORDED_RANGES and |
| MERGED_RANGES and PROP_CONFLICTS. EXPECT_ERROR should be true iff |
| we expect the merge to abort with an error about conflicts being |
| raised. |
| """ |
| expected_out = expected_merge_output2(tgt_ospath, recorded_ranges, |
| merged_ranges, |
| prop_conflicts, prop_resolved) |
| if expect_error: |
| expected_err = RegexListOutput([ |
| '^svn: E155015: .* conflicts were produced .* into$', |
| "^'.*" + re.escape(tgt_ospath) + "' --$", |
| '^resolve all conflicts .* remaining$', |
| '^unmerged revisions$'], |
| match_all=False) |
| else: |
| expected_err = [] |
| |
| return expected_out, expected_err |
| |
| def check_mergeinfo(expected_mergeinfo, tgt_ospath): |
| """Read the mergeinfo on TGT_OSPATH; verify that it matches |
| EXPECTED_MERGEINFO (list of lines). |
| """ |
| svntest.actions.run_and_verify_svn( |
| expected_mergeinfo, [], 'pg', SVN_PROP_MERGEINFO, tgt_ospath) |
| |
| def simple_merge(src_path, tgt_ospath, rev_args): |
| """Merge from ^/SRC_PATH to TGT_OSPATH using revision arguments REV_ARGS |
| (list of '-r...' or '-c...' strings); expect a single-target merge |
| with no conflicts or errors. |
| """ |
| rev_ranges = RangeList(rev_args) |
| |
| expected_out = expected_merge_output(rev_ranges, |
| [' U ' + tgt_ospath + '\n', |
| ' [UG] ' + tgt_ospath + '\n'], |
| target=tgt_ospath) |
| src_url = '^/' + src_path |
| svntest.actions.run_and_verify_svn( |
| expected_out, [], |
| 'merge', src_url, tgt_ospath, '--accept', 'postpone', *rev_args) |
| |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4306) |
| # Test for issue #4306 'multiple editor drive file merges record wrong |
| # mergeinfo during conflicts' |
| def conflict_aborted_mergeinfo_described_partial_merge(sbox): |
| "conflicted split merge can be repeated" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| trunk = 'A' |
| branch = 'A2' |
| file = 'mu' |
| dir = 'B' |
| trunk_file = 'A/mu' |
| trunk_dir = 'A/B' |
| |
| # r2: initial state |
| for rev in range(4, 11): |
| sbox.simple_propset('prop-' + str(rev), 'Old pval ' + str(rev), |
| trunk_file, trunk_dir) |
| sbox.simple_commit() |
| |
| # r3: branch |
| sbox.simple_copy(trunk, branch) |
| sbox.simple_commit() |
| |
| zero_rev = 3 |
| |
| def edit_file_or_dir(path, rev, val): |
| """Make a local edit to the file at PATH.""" |
| sbox.simple_propset('prop-' + str(rev), val + ' pval ' + str(rev), path) |
| |
| # r4 through r10: simple edits |
| for rev in range(4, 11): |
| edit_file_or_dir(trunk_file, rev, 'Edited') |
| edit_file_or_dir(trunk_dir, rev, 'Edited') |
| sbox.simple_commit() |
| |
| # r14: merge some changes to the branch so that later merges will be split |
| svntest.actions.run_and_verify_svn(None, [], 'merge', '-c5,9', |
| '^/' + trunk, sbox.ospath(branch), |
| '--accept', 'theirs-conflict') |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| def revert_branch(): |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', |
| sbox.ospath(branch)) |
| |
| def try_merge(relpath, conflict_rev, rev_args, |
| expected_out_err, expected_mi): |
| """Revert RELPATH in the branch; make a change that will conflict |
| with CONFLICT_REV if not None; merge RELPATH in the trunk |
| to RELPATH in the branch using revision arguments REV_ARGS (list of |
| '-r...' or '-c...' strings). |
| |
| EXPECTED_OUT_ERR_MI is a tuple: (expected_out, expected_err, |
| expected_mi). EXPECTED_OUT and EXPECTED_ERR are instances of |
| ExpectedOutput. |
| |
| Expect to find mergeinfo EXPECTED_MI if not None. EXPECTED_MI is |
| a single mergeinfo-string. |
| """ |
| src_path = trunk + '/' + relpath |
| tgt_path = branch + '/' + relpath |
| tgt_ospath = sbox.ospath(tgt_path) |
| |
| expected_out, expected_err = expected_out_err |
| |
| revert_branch() |
| |
| # Arrange for the merge to conflict at CONFLICT_REV. |
| if conflict_rev: |
| edit_file_or_dir(tgt_path, conflict_rev, 'Conflict') |
| |
| src_url = '^/' + src_path |
| svntest.actions.run_and_verify_svn( |
| expected_out, expected_err, |
| 'merge', src_url, tgt_ospath, '--accept', 'postpone', |
| *rev_args) |
| |
| if expected_mi is not None: |
| expected_mergeinfo = ['/' + src_path + ':' + expected_mi + '\n'] |
| check_mergeinfo(expected_mergeinfo, tgt_ospath) |
| |
| # In a mergeinfo-aware merge, each specified revision range is split |
| # internally into sub-ranges, to avoid any already-merged revisions. |
| # |
| # From white-box inspection, we see there are code paths that treat |
| # the last specified range and the last sub-range specially. The |
| # first specified range or sub-range is not treated specially in terms |
| # of the code paths, although it might be in terms of data flow. |
| # |
| # We test merges that raise a conflict in the first and last sub-range |
| # of the first and last specified range. |
| |
| for target in [file, dir]: |
| |
| tgt_ospath = sbox.ospath(branch + '/' + target) |
| |
| # First test: Merge "everything" to the branch. |
| # |
| # This merge is split into three sub-ranges: r3-4, r6-8, r10-head. |
| # We have arranged that the merge will raise a conflict in the first |
| # sub-range. Since we are postponing conflict resolution, the merge |
| # should stop after the first sub-range, allowing us to resolve and |
| # repeat the merge at which point the next sub-range(s) can be merged. |
| # The mergeinfo on the target then should only reflect that the first |
| # sub-range (r3-4) has been merged. |
| # |
| # Previously the merge failed after merging only r3-4 (as it should) |
| # but mergeinfo for the whole range was recorded, preventing subsequent |
| # repeat merges from applying the rest of the source changes. |
| expect = expected_out_and_err(tgt_ospath, |
| '3-4', ['3-4'], |
| prop_conflicts=1) |
| try_merge(target, 4, [], expect, '3-5,9') |
| |
| # Try a multiple-range merge that raises a conflict in the |
| # first sub-range in the first specified range; |
| expect = expected_out_and_err(tgt_ospath, |
| '4', ['4'], |
| prop_conflicts=1) |
| try_merge(target, 4, ['-c4-6,8-10'], expect, '4-5,9') |
| # last sub-range in the first specified range; |
| expect = expected_out_and_err(tgt_ospath, |
| '4-6', ['4,6'], |
| prop_conflicts=1) |
| try_merge(target, 6, ['-c4-6,8-10'], expect, '4-6,9') |
| # first sub-range in the last specified range; |
| expect = expected_out_and_err(tgt_ospath, |
| '4-6,8', ['4,6', '8'], |
| prop_conflicts=1) |
| try_merge(target, 8, ['-c4-6,8-10'], expect, '4-6,8-9') |
| # last sub-range in the last specified range. |
| # (Expect no error, because 'svn merge' does not throw an error if |
| # there is no more merging to do when a conflict occurs.) |
| expect = expected_out_and_err(tgt_ospath, |
| '4-6,8-10', ['4,6', '8,10'], |
| prop_conflicts=1, expect_error=False) |
| try_merge(target, 10, ['-c4-6,8-10'], expect, '4-6,8-10') |
| |
| # Try similar merges but involving ranges in reverse order. |
| expect = expected_out_and_err(tgt_ospath, |
| '8', ['8'], |
| prop_conflicts=1) |
| try_merge(target, 8, ['-c8-10,4-6'], expect, '5,8-9') |
| expect = expected_out_and_err(tgt_ospath, |
| '8-10', ['8,10'], |
| prop_conflicts=1) |
| try_merge(target, 10, ['-c8-10,4-6'], expect, '5,8-10') |
| expect = expected_out_and_err(tgt_ospath, |
| '8-10,4', ['8,10', '4'], |
| prop_conflicts=1) |
| try_merge(target, 4, ['-c8-10,4-6'], expect, '4-5,8-10') |
| expect = expected_out_and_err(tgt_ospath, |
| '8-10,4-6', ['8,10', '4,6'], |
| prop_conflicts=1, expect_error=False) |
| try_merge(target, 6, ['-c8-10,4-6'], expect, '4-6,8-10') |
| |
| # Try some reverse merges, with ranges in forward and reverse order. |
| # |
| # Reverse merges start with all source changes merged except 5 and 9. |
| revert_branch() |
| simple_merge(trunk + '/' + target, sbox.ospath(branch + '/' + target), |
| ['-c-5,-9,4,6-8,10']) |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| expect = expected_out_and_err(tgt_ospath, |
| '6-4,10-8', ['-6,-4', '-10,-8'], |
| expect_error=False) |
| try_merge(target, None, ['-r6:3', '-r10:7'], expect, '7') |
| expect = expected_out_and_err(tgt_ospath, |
| '-6', ['-6'], |
| prop_conflicts=1) |
| try_merge(target, 6, ['-r6:3', '-r10:7'], expect, '4,7-8,10') |
| expect = expected_out_and_err(tgt_ospath, |
| '6-4', ['-6,-4'], |
| prop_conflicts=1) |
| try_merge(target, 4, ['-r6:3', '-r10:7'], expect, '7-8,10') |
| expect = expected_out_and_err(tgt_ospath, |
| '6-4,-10', ['-6,-4', '-10'], |
| prop_conflicts=1) |
| try_merge(target, 10, ['-r6:3', '-r10:7'], expect, '7-8') |
| expect = expected_out_and_err(tgt_ospath, |
| '6-4,10-8', ['-6,-4', '-10,-8'], |
| prop_conflicts=1, expect_error=False) |
| try_merge(target, 8, ['-r6:3', '-r10:7'], expect, '7') |
| |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4310) |
| # Test for issue #4310 "each editor drive gets its own notification |
| # during 'svn merge'" |
| def multiple_editor_drive_merge_notifications(sbox): |
| "each editor drive gets its own notification" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| iota_branch_path = sbox.ospath('iota-copy') |
| C_branch_path = sbox.ospath('branch') |
| |
| # Branch a file and a directory: |
| |
| # r2 |
| sbox.simple_copy('iota', 'iota-copy') |
| sbox.simple_commit() |
| |
| # r3 |
| sbox.simple_copy('A/C', 'branch') |
| sbox.simple_commit() |
| |
| # r4-8 - Set five non-conflicting properties on the branch parents. |
| for i in range(0,5): |
| sbox.simple_propset('foo' + str(i) , 'bar', 'iota') |
| sbox.simple_propset('foo' + str(i) , 'bar', 'A/C') |
| sbox.simple_commit() |
| |
| # Cherry pick merge r5 and r6 to each branch and commit. |
| svntest.actions.run_and_verify_svn(None, [], 'merge', '^/iota', |
| '-c', '5,7', iota_branch_path) |
| svntest.actions.run_and_verify_svn(None, [], 'merge', '^/A/C', |
| '-c', '5,7', C_branch_path) |
| sbox.simple_commit() |
| |
| # Now auto merge all eligible revisions to each branch. |
| # First the directory target: |
| # |
| # TODO: We don't use run_and_verify_merge here because it has limitations |
| # re checking the merge notification headers -- which need to be improved |
| # at some point. |
| svntest.actions.run_and_verify_svn( |
| ["--- Merging r2 through r4 into '" + C_branch_path + "':\n", |
| " U " + C_branch_path + "\n", |
| "--- Merging r6 into '" + C_branch_path + "':\n", |
| " U " + C_branch_path + "\n", |
| "--- Merging r8 through r9 into '" + C_branch_path + "':\n", |
| " U " + C_branch_path + "\n", |
| "--- Recording mergeinfo for merge of r2 through r9 into '" + |
| C_branch_path + "':\n", |
| " U " + C_branch_path + "\n"], |
| [], 'merge', sbox.repo_url + '/A/C', C_branch_path) |
| |
| # Then the file target: |
| # Previously this failed because only the first range notification was |
| # printed: |
| # |
| # >svn merge ^/iota iota-copy |
| # --- Merging r2 through r4 into 'iota-copy': |
| # U iota-copy |
| # U iota-copy |
| # U iota-copy |
| # --- Recording mergeinfo for merge of r2 through r9 into 'iota-copy': |
| # U iota-copy |
| # |
| # This is what we expect: |
| # |
| # --- Merging r2 through r4 into 'iota-copy': |
| # U iota-copy |
| # --- Merging r6 into 'iota-copy': <-- 2nd editor drive |
| # U iota-copy |
| # --- Merging r8 through r9 into 'iota-copy': <-- 3rd editor drive |
| # U iota-copy |
| # --- Recording mergeinfo for merge of r2 through r9 into 'iota-copy': |
| # U iota-copy |
| svntest.actions.run_and_verify_svn( |
| ["--- Merging r2 through r4 into '" + iota_branch_path + "':\n", |
| " U " + iota_branch_path + "\n", |
| "--- Merging r6 into '" + iota_branch_path + "':\n", |
| " U " + iota_branch_path + "\n", |
| "--- Merging r8 through r9 into '" + iota_branch_path + "':\n", |
| " U " + iota_branch_path + "\n", |
| "--- Recording mergeinfo for merge of r2 through r9 into '" + |
| iota_branch_path + "':\n", |
| " U " + iota_branch_path + "\n"], |
| [], 'merge', sbox.repo_url + '/iota', iota_branch_path) |
| |
| #---------------------------------------------------------------------- |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4317) |
| # Test for issue #4317 "redundant notifications in single editor drive merge". |
| def single_editor_drive_merge_notifications(sbox): |
| "single editor drive merge notifications" |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| wc_dir = sbox.wc_dir |
| |
| A_copy_path = sbox.ospath('A_COPY') |
| D_copy_path = sbox.ospath('A_COPY/D') |
| psi_copy_path = sbox.ospath('A_COPY/D/H/psi') |
| omega_copy_path = sbox.ospath('A_COPY/D/H/omega') |
| beta_copy_path = sbox.ospath('A_COPY/B/E/beta') |
| |
| # r2 - r6: Copy A to A_COPY and then make some text changes under A. |
| set_up_branch(sbox) |
| |
| # r7 - Subtree merge |
| svntest.actions.run_and_verify_svn(None, [], 'merge', '^/A/D', |
| '-c4', D_copy_path) |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # Previously this failed because of redundant merge notifications |
| # for r4-7: |
| # |
| # >svn merge ^/A A_COPY |
| # --- Merging r2 through r3 into 'A_COPY\D': |
| # U A_COPY\D\H\psi |
| # --- Merging r5 through r7 into 'A_COPY\D': |
| # U A_COPY\D\H\omega |
| # --- Merging r4 through r7 into 'A_COPY': |
| # U A_COPY\B\E\beta |
| # --- Recording mergeinfo for merge of r2 through r7 into 'A_COPY': |
| # U A_COPY |
| # --- Recording mergeinfo for merge of r2 through r7 into 'A_COPY\D': |
| # U A_COPY\D |
| # --- Eliding mergeinfo from 'A_COPY\D': |
| # U A_COPY\D |
| # |
| # The order of 'beta' and 'omega' can vary, so use UnorderedOutput. This |
| # raises the possibility that the test could spuriously pass if the 'U'pdate |
| # notifications aren't grouped with the correct headers, but that's not what |
| # is being tested here. |
| expected_output = svntest.verify.UnorderedOutput( |
| ["--- Merging r2 through r3 into '" + A_copy_path + "':\n", |
| "U " + psi_copy_path + "\n", |
| "--- Merging r4 through r7 into '" + A_copy_path + "':\n", |
| "U " + omega_copy_path + "\n", |
| "U " + beta_copy_path + "\n", |
| "--- Recording mergeinfo for merge of r2 through r7 into '" + |
| A_copy_path + "':\n", |
| " U " + A_copy_path + "\n", |
| "--- Recording mergeinfo for merge of r2 through r7 into '" + |
| D_copy_path + "':\n", |
| " U " + D_copy_path + "\n", |
| "--- Eliding mergeinfo from '" + D_copy_path + "':\n", |
| " U " + D_copy_path + "\n"]) |
| svntest.actions.run_and_verify_svn(expected_output, [], 'merge', |
| sbox.repo_url + '/A', A_copy_path) |
| |
| # r8 and r9 - Commit and do reverse subtree merge. |
| sbox.simple_commit() |
| sbox.simple_update() |
| svntest.actions.run_and_verify_svn(None, [], 'merge', '^/A/D', |
| '-c-4', D_copy_path) |
| sbox.simple_commit() |
| |
| # Now try a reverse merge. There should only be one notification for |
| # r7-5: |
| sbox.simple_update() |
| expected_output = svntest.verify.UnorderedOutput( |
| ["--- Reverse-merging r7 through r5 into '" + A_copy_path + "':\n", |
| "U " + beta_copy_path + "\n", |
| "U " + omega_copy_path + "\n", |
| "--- Reverse-merging r4 through r3 into '" + A_copy_path + "':\n", |
| "U " + psi_copy_path + "\n", |
| "--- Recording mergeinfo for reverse merge of r7 through r3 into '" + |
| A_copy_path + "':\n", |
| " U " + A_copy_path + "\n", |
| "--- Recording mergeinfo for reverse merge of r7 through r3 into '" + |
| D_copy_path + "':\n", |
| " U " + D_copy_path + "\n", |
| "--- Eliding mergeinfo from '" + D_copy_path + "':\n", |
| " U " + D_copy_path + "\n"]) |
| svntest.actions.run_and_verify_svn(expected_output, [], 'merge', |
| '-r9:2', sbox.repo_url + '/A', |
| A_copy_path) |
| |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4316) # 'Merge errors out after resolving conflicts' |
| # Very similar to conflict_aborted_mergeinfo_described_partial_merge() |
| # (test number 135), except here we tell the merge to resolve the |
| # conflicts that are generated part way through a multi-revision-range |
| # merge, and we expect it to continue with the rest of the merge. |
| def conflicted_split_merge_with_resolve(sbox): |
| "conflicted split merge with resolve" |
| |
| sbox.build() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| trunk = 'A' |
| branch = 'A2' |
| file = 'mu' |
| dir = 'B' |
| trunk_file = 'A/mu' |
| trunk_dir = 'A/B' |
| |
| # r2: initial state |
| for rev in range(4, 11): |
| sbox.simple_propset('prop-' + str(rev), 'Old pval ' + str(rev), |
| trunk_file, trunk_dir) |
| sbox.simple_commit() |
| |
| # r3: branch |
| sbox.simple_update() |
| sbox.simple_copy(trunk, branch) |
| sbox.simple_commit() |
| |
| zero_rev = 3 |
| |
| def edit_file_or_dir(path, rev, val): |
| """Make a local edit to the file at PATH.""" |
| sbox.simple_propset('prop-' + str(rev), val + ' pval ' + str(rev), path) |
| |
| # r4 through r10: simple edits |
| for rev in range(4, 11): |
| edit_file_or_dir(trunk_file, rev, 'Edited') |
| edit_file_or_dir(trunk_dir, rev, 'Edited') |
| sbox.simple_commit() |
| |
| # r14: merge some changes to the branch so that later merges will be split |
| svntest.actions.run_and_verify_svn(None, [], 'merge', '-c5,9', |
| '^/' + trunk, sbox.ospath(branch), |
| '--accept', 'theirs-conflict') |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| def revert_branch(): |
| svntest.actions.run_and_verify_svn(None, [], 'revert', '-R', |
| sbox.ospath(branch)) |
| |
| def try_merge(relpath, conflict_rev, rev_args, |
| expected_out_err, expected_mi): |
| """Revert RELPATH in the branch; make a change that will conflict |
| with CONFLICT_REV if not None; merge RELPATH in the trunk |
| to RELPATH in the branch using revision arguments REV_ARGS (list of |
| '-r...' or '-c...' strings). |
| |
| EXPECTED_OUT_ERR_MI is a tuple: (expected_out, expected_err, |
| expected_mi). EXPECTED_OUT and EXPECTED_ERR are instances of |
| ExpectedOutput. |
| |
| Expect to find mergeinfo EXPECTED_MI if not None. EXPECTED_MI is |
| a single mergeinfo-string. |
| """ |
| src_path = trunk + '/' + relpath |
| tgt_path = branch + '/' + relpath |
| tgt_ospath = sbox.ospath(tgt_path) |
| |
| expected_out, expected_err = expected_out_err |
| |
| revert_branch() |
| |
| # Arrange for the merge to conflict at CONFLICT_REV. |
| if conflict_rev: |
| edit_file_or_dir(tgt_path, conflict_rev, 'Conflict') |
| |
| src_url = '^/' + src_path + '@11' |
| svntest.actions.run_and_verify_svn( |
| expected_out, expected_err, |
| 'merge', src_url, tgt_ospath, '--accept', 'mine-full', |
| *rev_args) |
| |
| if expected_mi is not None: |
| expected_mergeinfo = ['/' + src_path + ':' + expected_mi + '\n'] |
| check_mergeinfo(expected_mergeinfo, tgt_ospath) |
| |
| # In a mergeinfo-aware merge, each specified revision range is split |
| # internally into sub-ranges, to avoid any already-merged revisions. |
| # |
| # From white-box inspection, we see there are code paths that treat |
| # the last specified range and the last sub-range specially. The |
| # first specified range or sub-range is not treated specially in terms |
| # of the code paths, although it might be in terms of data flow. |
| # |
| # We test merges that raise a conflict in the first and last sub-range |
| # of the first and last specified range. |
| |
| for target in [file, dir]: |
| |
| tgt_ospath = sbox.ospath(branch + '/' + target) |
| |
| # First test: Merge "everything" to the branch. |
| # |
| # This merge is split into three sub-ranges: r3-4, r6-8, r10-head. |
| # We have arranged that the merge will raise a conflict in the first |
| # sub-range. Since we are postponing conflict resolution, the merge |
| # should stop after the first sub-range, allowing us to resolve and |
| # repeat the merge at which point the next sub-range(s) can be merged. |
| # The mergeinfo on the target then should only reflect that the first |
| # sub-range (r3-4) has been merged. |
| expect = expected_out_and_err(tgt_ospath, |
| '3-4,6-11', |
| ['3-4', '6-8,10-11'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 4, [], expect, '3-11') |
| |
| # Try a multiple-range merge that raises a conflict in the |
| # first sub-range in the first specified range; |
| expect = expected_out_and_err(tgt_ospath, |
| '4,6,8-10', |
| ['4', '6', '8,10'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 4, ['-c4-6,8-10'], expect, '4-6,8-10') |
| # last sub-range in the first specified range; |
| expect = expected_out_and_err(tgt_ospath, |
| '4-6,8-10', ['4,6', '8,10'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 6, ['-c4-6,8-10'], expect, '4-6,8-10') |
| # first sub-range in the last specified range; |
| expect = expected_out_and_err(tgt_ospath, |
| '4-6,8,10', |
| ['4,6', '8', '10'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 8, ['-c4-6,8-10'], expect, '4-6,8-10') |
| # last sub-range in the last specified range. |
| # (Expect no error, because 'svn merge' does not throw an error if |
| # there is no more merging to do when a conflict occurs.) |
| expect = expected_out_and_err(tgt_ospath, |
| '4-6,8-10', ['4,6', '8,10'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 10, ['-c4-6,8-10'], expect, '4-6,8-10') |
| |
| # Try similar merges but involving ranges in reverse order. |
| expect = expected_out_and_err(tgt_ospath, |
| '8', ['8'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 8, ['-c8-10,4-6'], expect, '4-6,8-10') |
| expect = expected_out_and_err(tgt_ospath, |
| '8-10', ['8,10'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 10, ['-c8-10,4-6'], expect, '4-6,8-10') |
| expect = expected_out_and_err(tgt_ospath, |
| '8-10,4', ['8,10', '4'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 4, ['-c8-10,4-6'], expect, '4-6,8-10') |
| expect = expected_out_and_err(tgt_ospath, |
| '8-10,4-6', ['8,10', '4,6'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 6, ['-c8-10,4-6'], expect, '4-6,8-10') |
| |
| # Try some reverse merges, with ranges in forward and reverse order. |
| # |
| # Reverse merges start with all source changes merged except 5 and 9. |
| revert_branch() |
| simple_merge(trunk + '/' + target, sbox.ospath(branch + '/' + target), |
| ['-c-5,-9,4,6-8,10']) |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| expect = expected_out_and_err(tgt_ospath, |
| '6-4,10-8', ['-6,-4', '-10,-8'], |
| expect_error=False) |
| try_merge(target, None, ['-r6:3', '-r10:7'], expect, '7') |
| expect = expected_out_and_err(tgt_ospath, |
| '-6,-4,10-8', |
| ['-6', '-4', '-10,-8'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 6, ['-r6:3', '-r10:7'], expect, '7') |
| expect = expected_out_and_err(tgt_ospath, |
| '6-4,10-8', ['-6,-4', '-10,-8'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 4, ['-r6:3', '-r10:7'], expect, '7') |
| expect = expected_out_and_err(tgt_ospath, |
| '6-4,-10,-8', |
| ['-6,-4', '-10', '-8'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 10, ['-r6:3', '-r10:7'], expect, '7') |
| expect = expected_out_and_err(tgt_ospath, |
| '6-4,10-8', ['-6,-4', '-10,-8'], |
| prop_resolved=1, expect_error=False) |
| try_merge(target, 8, ['-r6:3', '-r10:7'], expect, '7') |
| |
| #---------------------------------------------------------------------- |
| # Test for issue 4367 'merge to shallow WC, repeat merge to infinite |
| # depth WC is broken'. |
| @SkipUnless(server_has_mergeinfo) |
| @Issues(4367) |
| def merge_to_empty_target_merge_to_infinite_target(sbox): |
| "repeat merge to infinite depth WC conflicts" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| wc_disk, wc_status = set_up_branch(sbox, branch_only=True) |
| A_COPY_path = sbox.ospath('A_COPY') |
| C_COPY_path = sbox.ospath('A_COPY/C') |
| E_path = sbox.ospath('A/B/E') |
| J_path = sbox.ospath('A/C/J') |
| K_path = sbox.ospath('A/C/J/K') |
| nu1_path = sbox.ospath('A/C/J/nu1') |
| nu2_path = sbox.ospath('A/C/J/K/nu2') |
| L_path = sbox.ospath('A/B/L') |
| nu3_path = sbox.ospath('A/B/L/nu3') |
| |
| B1_path = sbox.ospath('A/B/B1') |
| B1a_path = sbox.ospath('A/B/B1/B1a') |
| test1_path = sbox.ospath('A/B/B1/test.txt') |
| test2_path = sbox.ospath('A/B/B1/B1a/test.txt') |
| |
| C1_path = sbox.ospath('A/C/C1') |
| test3_path = sbox.ospath('A/C/C1/test.txt') |
| |
| # r3 - Add some subtrees: |
| # A /A/B/B1 |
| # A /A/B/B1/B1a |
| # A /A/B/B1/B1a/test.txt |
| # A /A/B/B1/test.txt |
| svntest.main.run_svn(None, 'mkdir', B1_path) |
| svntest.main.run_svn(None, 'mkdir', B1a_path) |
| svntest.main.file_append(test1_path, "New file.\n") |
| svntest.main.file_append(test2_path, "New file.\n") |
| svntest.main.run_svn(None, 'add', test1_path, test2_path) |
| sbox.simple_commit() |
| |
| # r4 - Add some another subtree. |
| # A /A/C/C1 |
| # A /A/C/C1/test.txt |
| svntest.main.run_svn(None, 'mkdir', C1_path) |
| svntest.main.file_append(test3_path, "New file.\n") |
| svntest.main.run_svn(None, 'add', test3_path) |
| sbox.simple_commit() |
| |
| # r5 - Delete part of the subtree added in r3. |
| # D /A/B/B1/B1a |
| svntest.main.run_svn(None, 'del', B1a_path) |
| sbox.simple_commit() |
| |
| # r6 - Set depth of A_COPY to empty, merge all available revs from ^/A. |
| svntest.actions.run_and_verify_svn(None, [], 'up', |
| '--set-depth=empty', A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'up', |
| '--set-depth=infinity', C_COPY_path) |
| svntest.actions.run_and_verify_svn(None, [], 'merge', '^/A', |
| A_COPY_path) |
| sbox.simple_commit() |
| |
| # Update A_COPY back to depth infinity and retry the prior merge. |
| svntest.actions.run_and_verify_svn(None, [], 'up', |
| '--set-depth=infinity', A_COPY_path) |
| |
| expected_output = wc.State(A_COPY_path, { |
| 'B/B1' : Item(status='A '), |
| 'B/B1/test.txt' : Item(status='A '), |
| 'B/B1/B1a' : Item(status='D ', prev_status='A '), |
| 'B/B1/B1a/test.txt' : Item(status='A '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'B' : Item(status=' G'), |
| }) |
| expected_elision_output = wc.State(A_COPY_path, { |
| 'B' : Item(status=' U'), |
| }) |
| expected_status = wc.State(A_COPY_path, { |
| '' : Item(status=' M'), |
| 'B' : Item(status=' '), |
| 'mu' : Item(status=' '), |
| 'B/B1' : Item(status='A ', copied='+'), |
| 'B/B1/test.txt' : Item(status=' ', copied='+'), |
| 'B/B1/B1a' : Item(status='D ', copied='+'), |
| 'B/B1/B1a/test.txt' : Item(status='D ', copied='+'), |
| 'B/E' : Item(status=' '), |
| 'B/E/alpha' : Item(status=' '), |
| 'B/E/beta' : Item(status=' '), |
| 'B/lambda' : Item(status=' '), |
| 'B/F' : Item(status=' '), |
| 'C' : Item(status=' '), |
| 'C/C1' : Item(status=' '), |
| 'C/C1/test.txt' : Item(status=' '), |
| 'D' : Item(status=' '), |
| 'D/G' : Item(status=' '), |
| 'D/G/pi' : Item(status=' '), |
| 'D/G/rho' : Item(status=' '), |
| 'D/G/tau' : Item(status=' '), |
| 'D/gamma' : Item(status=' '), |
| 'D/H' : Item(status=' '), |
| 'D/H/chi' : Item(status=' '), |
| 'D/H/psi' : Item(status=' '), |
| 'D/H/omega' : Item(status=' '), |
| }) |
| expected_status.tweak(wc_rev=6) |
| expected_status.tweak('B/B1', 'B/B1/test.txt', 'B/B1/B1a', |
| 'B/B1/B1a/test.txt', wc_rev='-') |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A:2-6'}), |
| 'B' : Item(), |
| 'mu' : Item("This is the file 'mu'.\n"), |
| 'B/B1' : Item(), |
| 'B/B1/test.txt' : Item("New file.\n"), |
| 'B/E' : Item(), |
| 'B/E/alpha' : Item("This is the file 'alpha'.\n"), |
| 'B/E/beta' : Item("This is the file 'beta'.\n"), |
| 'B/lambda' : Item("This is the file 'lambda'.\n"), |
| 'B/F' : Item(), |
| 'C' : Item(props={SVN_PROP_MERGEINFO : '/A/C:2-5'}), |
| 'C/C1' : Item(), |
| 'C/C1/test.txt' : Item("New file.\n"), |
| 'D' : Item(), |
| 'D/G' : Item(), |
| 'D/G/pi' : Item("This is the file 'pi'.\n"), |
| 'D/G/rho' : Item("This is the file 'rho'.\n"), |
| 'D/G/tau' : Item("This is the file 'tau'.\n"), |
| 'D/gamma' : Item("This is the file 'gamma'.\n"), |
| 'D/H' : Item(), |
| 'D/H/chi' : Item("This is the file 'chi'.\n"), |
| 'D/H/psi' : Item("This is the file 'psi'.\n"), |
| 'D/H/omega' : Item("This is the file 'omega'.\n"), |
| }) |
| expected_skip = wc.State(A_COPY_path, { }) |
| svntest.actions.run_and_verify_merge(A_COPY_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| [], True, False) |
| |
| # Commit the merge. |
| #sbox.simple_commit() |
| |
| def conflict_naming(sbox): |
| "verify conflict file naming" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| sbox.simple_append('file.txt', 'This is the initial content\n') |
| sbox.simple_add('file.txt') |
| sbox.simple_commit() |
| |
| sbox.simple_append('file.txt', 'This is the new content\n', truncate=True) |
| sbox.simple_commit() |
| |
| sbox.simple_append('file.txt', 'This is conflicting content\n', truncate=True) |
| |
| # Update - no preserve ext |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 2) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_output = svntest.wc.State(wc_dir, { |
| 'file.txt' : Item(status='C ') |
| }) |
| expected_status.add({ |
| 'file.txt' : Item(status='C ', wc_rev='2') |
| }) |
| |
| expected_disk.add({ |
| 'file.txt.r3' : Item(contents="This is the new content\n"), |
| 'file.txt.r2' : Item(contents="This is the initial content\n"), |
| 'file.txt' : Item(contents="<<<<<<< .mine\n" \ |
| "This is conflicting content\n" \ |
| "||||||| .r3\n" \ |
| "This is the new content\n" \ |
| "=======\n" \ |
| "This is the initial content\n" \ |
| ">>>>>>> .r2\n"), |
| 'file.txt.mine' : Item(contents="This is conflicting content\n"), |
| }) |
| svntest.actions.run_and_verify_update(wc_dir, |
| expected_output, expected_disk, |
| expected_status, |
| [], False, |
| wc_dir, '-r', '2') |
| |
| sbox.simple_revert('file.txt') |
| sbox.simple_update('', revision=3) |
| sbox.simple_append('file.txt', 'This is conflicting content\n', truncate=True) |
| |
| # Update - preserve ext |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 2) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_output = svntest.wc.State(wc_dir, { |
| 'file.txt' : Item(status='C ') |
| }) |
| expected_status.add({ |
| 'file.txt' : Item(status='C ', wc_rev='2') |
| }) |
| |
| expected_disk.add({ |
| 'file.txt.r3.txt' : Item(contents="This is the new content\n"), |
| 'file.txt.r2.txt' : Item(contents="This is the initial content\n"), |
| 'file.txt' : Item(contents="<<<<<<< .mine.txt\n" \ |
| "This is conflicting content\n" \ |
| "||||||| .r3.txt\n" \ |
| "This is the new content\n" \ |
| "=======\n" \ |
| "This is the initial content\n" \ |
| ">>>>>>> .r2.txt\n"), |
| 'file.txt.mine.txt' : Item(contents="This is conflicting content\n"), |
| }) |
| svntest.actions.run_and_verify_update( |
| wc_dir, |
| expected_output, expected_disk, expected_status, |
| [], False, |
| wc_dir, '-r', '2', |
| '--config-option', |
| 'config:miscellany:preserved-conflict-file-exts=' + |
| 'c txt h') |
| |
| sbox.simple_revert('file.txt') |
| sbox.simple_update('', revision=3) |
| sbox.simple_append('file.txt', 'This is conflicting content\n', truncate=True) |
| |
| # Merge - no preserve ext |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 3) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_status.add({ |
| 'file.txt' : Item(status='C ', wc_rev='3') |
| }) |
| expected_disk.add({ |
| 'file.txt.merge-left.r3' : Item(contents="This is the new content\n"), |
| 'file.txt.merge-right.r2': Item(contents="This is the initial content\n"), |
| 'file.txt' : Item(contents="<<<<<<< .working\n" \ |
| "This is conflicting content\n" \ |
| "||||||| .merge-left.r3\n" \ |
| "This is the new content\n" \ |
| "=======\n" \ |
| "This is the initial content\n" \ |
| ">>>>>>> .merge-right.r2\n"), |
| 'file.txt.working' : Item(contents="This is conflicting content\n"), |
| }) |
| |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '-c-3', '^/', sbox.ospath('')) |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| svntest.actions.verify_disk(wc_dir, expected_disk) |
| |
| sbox.simple_revert('file.txt') |
| sbox.simple_append('file.txt', 'This is conflicting content\n', truncate=True) |
| |
| # Merge - preserve ext |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 3) |
| expected_disk = svntest.main.greek_state.copy() |
| expected_status.add({ |
| 'file.txt' : Item(status='C ', wc_rev='3') |
| }) |
| expected_disk.add({ |
| 'file.txt.merge-left.r3.txt' : Item(contents="This is the new content\n"), |
| 'file.txt.merge-right.r2.txt': Item(contents="This is the initial content\n"), |
| 'file.txt' : Item(contents="<<<<<<< .working.txt\n" \ |
| "This is conflicting content\n" \ |
| "||||||| .merge-left.r3.txt\n" \ |
| "This is the new content\n" \ |
| "=======\n" \ |
| "This is the initial content\n" \ |
| ">>>>>>> .merge-right.r2.txt\n"), |
| 'file.txt.working.txt' : Item(contents="This is conflicting content\n"), |
| }) |
| |
| svntest.actions.run_and_verify_svn( |
| None, [], |
| 'merge', '-c-3', '^/', sbox.ospath(''), |
| '--config-option', |
| 'config:miscellany:preserved-conflict-file-exts=' + |
| 'c txt h') |
| svntest.actions.run_and_verify_status(wc_dir, expected_status) |
| svntest.actions.verify_disk(wc_dir, expected_disk) |
| |
| def merge_dir_delete_force(sbox): |
| "merge a directory delete with --force" |
| |
| sbox.build() |
| |
| sbox.simple_rm('A/D/G') |
| sbox.simple_commit() # r2 |
| |
| sbox.simple_update(revision=1) |
| |
| # Just merging r2 on r1 succeeds |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '-c2', '^/', sbox.wc_dir, |
| '--ignore-ancestry') |
| |
| # Bring working copy to r1 again |
| svntest.actions.run_and_verify_svn(None, [], |
| 'revert', '-R', sbox.wc_dir) |
| |
| # But when using --force this same merge caused a segfault in 1.8.0-1.8.8 |
| svntest.actions.run_and_verify_svn(None, [], |
| 'merge', '-c2', '^/', sbox.wc_dir, |
| '--ignore-ancestry', '--force') |
| |
| # Issue #4859: Merge removing a folder with non-inheritable mergeinfo -> |
| # E155023: can't set properties: invalid status for updating properties |
| @Issue(4859) |
| def merge_deleted_folder_with_mergeinfo(sbox): |
| "merge deleted folder with mergeinfo" |
| |
| sbox.build() |
| |
| was_cwd = os.getcwd() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| # Some non-inheritable mergeinfo |
| sbox.simple_propset('svn:mergeinfo', '/A/C:1*', 'A/D') |
| sbox.simple_commit() # r2 |
| |
| # Branching |
| sbox.simple_repo_copy('A', 'branch_A') # r3 |
| sbox.simple_update() |
| |
| # On branch, remove a folder that has non-inheritable mergeinfo |
| sbox.simple_rm('branch_A/D') |
| sbox.simple_commit() # r4 |
| |
| sbox.simple_update() |
| |
| # A merge that removes that folder |
| # (merge revision 4 only from 'branch_A' to 'A') |
| expected_output = wc.State(sbox.ospath(''), { |
| 'A/D' : Item(status='D '), |
| }) |
| expected_mergeinfo_output = wc.State(sbox.ospath(''), { |
| 'A' : Item(status=' U'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(sbox.ospath('A'), 4).subtree('A') |
| expected_status.add({ '': Item(status=' M', wc_rev=4) }) |
| expected_status.tweak_some( |
| lambda path, item: [True] if path.split('/')[0] == 'D' else [], |
| status='D ') |
| svntest.actions.run_and_verify_merge(sbox.ospath('A'), 3, 4, |
| '^/branch_A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| None, |
| None, |
| expected_status, |
| wc.State('', {}), |
| [], |
| check_props=False, |
| dry_run=False # as dry run is broken |
| ) |
| |
| os.chdir(was_cwd) |
| |
| # Issue #4859: Merge removing a folder with non-inheritable mergeinfo -> |
| # E155023: can't set properties: invalid status for updating properties |
| # |
| # In this test we split the merge into two separate operable parts, a |
| # delete followed later by an add, to check it will set the mergeinfo on the |
| # subtree paths if the deleted folder is later replaced within the same |
| # overall merge. |
| @Issue(4859) |
| def merge_deleted_folder_with_mergeinfo_2(sbox): |
| "merge deleted folder with mergeinfo 2" |
| |
| sbox.build() |
| |
| was_cwd = os.getcwd() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| # Some non-inheritable mergeinfo |
| sbox.simple_propset('svn:mergeinfo', '/A/C:1*', 'A/D') |
| sbox.simple_commit() # r2 |
| |
| # Branching |
| sbox.simple_repo_copy('A', 'branch_A') # r3 |
| sbox.simple_update() |
| |
| # On branch, remove a folder that has non-inheritable mergeinfo |
| sbox.simple_rm('branch_A/D') |
| sbox.simple_commit() # r4 |
| |
| # A commit that we don't want to merge from the branch, to split the merge |
| # into two separate operable parts. |
| sbox.simple_mkdir('branch_A/IgnoreThis') |
| sbox.simple_commit() # r5 |
| |
| # On branch, replace the deleted folder with a new one, with mergeinfo, |
| # to check we don't omit setting mergeinfo on this. |
| sbox.simple_mkdir('branch_A/D') |
| sbox.simple_propset('svn:mergeinfo', '/branch_B/C:1*', 'branch_A/D') |
| sbox.simple_mkdir('branch_A/D/G', 'branch_A/D/G2') |
| sbox.simple_propset('svn:mergeinfo', '/branch_B/C/G:1*', 'branch_A/D/G') |
| sbox.simple_propset('svn:mergeinfo', '/branch_B/C/G2:1*', 'branch_A/D/G2') |
| sbox.simple_commit() # r6 |
| |
| sbox.simple_propset('svn:mergeinfo', '/branch_A:5', 'A') |
| sbox.simple_commit() # r7 |
| |
| sbox.simple_update() |
| |
| # A merge that removes that folder |
| expected_output = wc.State(sbox.ospath(''), { |
| 'A/D' : Item(status='A ', prev_status='D '), |
| 'A/D/G' : Item(status='A '), |
| 'A/D/G2' : Item(status='A '), |
| }) |
| # verify that mergeinfo is set/changed on A/D, A/D/G, A/D/G2. |
| expected_mergeinfo_output = wc.State(sbox.ospath(''), { |
| 'A' : Item(status=' U'), |
| 'A/D' : Item(status=' G'), |
| 'A/D/G' : Item(status=' G'), |
| 'A/D/G2' : Item(status=' G'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(sbox.ospath('A'), 7).subtree('A') |
| expected_status.tweak_some( |
| lambda path, item: [True] if path.split('/')[0] == 'D' else [], |
| status='D ') |
| expected_status.add({ |
| '' : Item(status=' M', wc_rev=7), |
| 'D' : Item(status='RM', copied='+', wc_rev='-'), |
| 'D/G' : Item(status=' M', copied='+', wc_rev='-'), |
| 'D/G2' : Item(status=' M', copied='+', wc_rev='-'), |
| }) |
| svntest.actions.run_and_verify_merge(sbox.ospath('A'), None, None, |
| '^/branch_A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| None, |
| None, |
| expected_status, |
| wc.State('', {}), |
| [], |
| check_props=False, |
| dry_run=False # as dry run is broken |
| ) |
| |
| # verify that mergeinfo is set/changed on A/D, A/D/G, A/D/G2. |
| expected_mergeinfo = [ |
| ('A', ['/branch_A:3-7']), |
| ('A/D', ['/branch_A/D:5-7\n', '/branch_B/C:1*']), |
| ('A/D/G', ['/branch_A/D/G:5-7\n', '/branch_B/C/G:1*']), |
| ('A/D/G2', ['/branch_A/D/G2:5-7\n', '/branch_B/C/G2:1*']), |
| ] |
| for path, mergeinfo in expected_mergeinfo: |
| svntest.actions.check_prop('svn:mergeinfo', sbox.ospath(path), |
| [m.encode() for m in mergeinfo]) |
| |
| os.chdir(was_cwd) |
| |
| ######################################################################## |
| # Run the tests |
| |
| |
| # list all tests here, starting with None: |
| test_list = [ None, |
| textual_merges_galore, |
| add_with_history, |
| simple_property_merges, |
| merge_with_implicit_target_using_r, |
| merge_with_implicit_target_using_c, |
| merge_with_implicit_target_and_revs, |
| merge_similar_unrelated_trees, |
| merge_with_prev, |
| merge_binary_file, |
| merge_one_file_using_r, |
| merge_one_file_using_c, |
| merge_one_file_using_implicit_revs, |
| merge_record_only, |
| merge_in_new_file_and_diff, |
| merge_skips_obstructions, |
| merge_into_missing, |
| dry_run_adds_file_with_prop, |
| merge_binary_with_common_ancestry, |
| merge_funny_chars_on_path, |
| merge_keyword_expansions, |
| merge_prop_change_to_deleted_target, |
| merge_file_with_space_in_its_name, |
| merge_dir_branches, |
| safe_property_merge, |
| property_merge_from_branch, |
| property_merge_undo_redo, |
| cherry_pick_text_conflict, |
| merge_file_replace, |
| merge_dir_replace, |
| merge_dir_and_file_replace, |
| merge_file_replace_to_mixed_rev_wc, |
| merge_ignore_whitespace, |
| merge_ignore_eolstyle, |
| merge_conflict_markers_matching_eol, |
| merge_eolstyle_handling, |
| avoid_repeated_merge_using_inherited_merge_info, |
| avoid_repeated_merge_on_subtree_with_merge_info, |
| obey_reporter_api_semantics_while_doing_subtree_merges, |
| mergeinfo_inheritance, |
| mergeinfo_elision, |
| mergeinfo_inheritance_and_discontinuous_ranges, |
| merge_to_target_with_copied_children, |
| merge_to_switched_path, |
| merge_to_path_with_switched_children, |
| merge_with_implicit_target_file, |
| empty_mergeinfo, |
| prop_add_to_child_with_mergeinfo, |
| foreign_repos_does_not_update_mergeinfo, |
| avoid_reflected_revs, |
| update_loses_mergeinfo, |
| merge_loses_mergeinfo, |
| single_file_replace_style_merge_capability, |
| merge_to_out_of_date_target, |
| merge_with_depth_files, |
| merge_away_subtrees_noninheritable_ranges, |
| merge_to_sparse_directories, |
| merge_old_and_new_revs_from_renamed_dir, |
| merge_with_child_having_different_rev_ranges_to_merge, |
| merge_old_and_new_revs_from_renamed_file, |
| merge_with_auto_rev_range_detection, |
| cherry_picking, |
| propchange_of_subdir_raises_conflict, |
| reverse_merge_prop_add_on_child, |
| merge_target_with_non_inheritable_mergeinfo, |
| self_reverse_merge, |
| ignore_ancestry_and_mergeinfo, |
| merge_from_renamed_branch_fails_while_avoiding_repeat_merge, |
| merge_source_normalization_and_subtree_merges, |
| new_subtrees_should_not_break_merge, |
| dont_add_mergeinfo_from_own_history, |
| merge_range_predates_history, |
| foreign_repos, |
| foreign_repos_uuid, |
| foreign_repos_2_url, |
| merge_added_subtree, |
| merge_unknown_url, |
| reverse_merge_away_all_mergeinfo, |
| dont_merge_revs_into_subtree_that_predate_it, |
| merge_chokes_on_renamed_subtrees, |
| dont_explicitly_record_implicit_mergeinfo, |
| merge_broken_link, |
| subtree_merges_dont_intersect_with_targets, |
| subtree_source_missing_in_requested_range, |
| subtrees_with_empty_mergeinfo, |
| commit_to_subtree_added_by_merge, |
| del_identical_file, |
| del_sched_add_hist_file, |
| subtree_merges_dont_cause_spurious_conflicts, |
| merge_target_and_subtrees_need_nonintersecting_ranges, |
| merge_two_edits_to_same_prop, |
| merge_an_eol_unification_and_set_svn_eol_style, |
| merge_adds_mergeinfo_correctly, |
| natural_history_filtering, |
| subtree_gets_changes_even_if_ultimately_deleted, |
| no_self_referential_filtering_on_added_path, |
| merge_range_prior_to_rename_source_existence, |
| dont_merge_gaps_in_history, |
| mergeinfo_deleted_by_a_merge_should_disappear, |
| noop_file_merge, |
| handle_gaps_in_implicit_mergeinfo, |
| copy_then_replace_via_merge, |
| record_only_merge, |
| merge_automatic_conflict_resolution, |
| skipped_files_get_correct_mergeinfo, |
| committed_case_only_move_and_revert, |
| merge_into_wc_for_deleted_branch, |
| foreign_repos_del_and_props, |
| immediate_depth_merge_creates_minimal_subtree_mergeinfo, |
| record_only_merge_creates_self_referential_mergeinfo, |
| dav_skelta_mode_causes_spurious_conflicts, |
| merge_into_locally_added_file, |
| merge_into_locally_added_directory, |
| merge_with_os_deleted_subtrees, |
| no_self_referential_or_nonexistent_inherited_mergeinfo, |
| subtree_merges_inherit_invalid_working_mergeinfo, |
| merge_change_to_file_with_executable, |
| dry_run_merge_conflicting_binary, |
| foreign_repos_prop_conflict, |
| merge_adds_subtree_with_mergeinfo, |
| reverse_merge_adds_subtree, |
| merged_deletion_causes_tree_conflict, |
| record_only_merge_adds_new_subtree_mergeinfo, |
| unnecessary_noninheritable_mergeinfo_missing_subtrees, |
| unnecessary_noninheritable_mergeinfo_shallow_merge, |
| svnmucc_abuse_1, |
| merge_source_with_replacement, |
| reverse_merge_with_rename, |
| merge_adds_then_deletes_subtree, |
| merge_with_added_subtrees_with_mergeinfo, |
| merge_with_externals_with_mergeinfo, |
| merge_binary_file_with_keywords, |
| merge_conflict_when_keywords_removed, |
| merge_target_selection, |
| merge_properties_on_adds, |
| conflict_aborted_mergeinfo_described_partial_merge, |
| multiple_editor_drive_merge_notifications, |
| single_editor_drive_merge_notifications, |
| conflicted_split_merge_with_resolve, |
| merge_to_empty_target_merge_to_infinite_target, |
| conflict_naming, |
| merge_dir_delete_force, |
| merge_deleted_folder_with_mergeinfo, |
| merge_deleted_folder_with_mergeinfo_2, |
| ] |
| |
| if __name__ == '__main__': |
| svntest.main.run_tests(test_list) |
| # NOTREACHED |
| |
| |
| ### End of file. |