| #!/usr/bin/env python |
| # |
| # merge_automatic_tests.py: testing "automatic merge" scenarios |
| # |
| # 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 |
| |
| # (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 |
| |
| from svntest.main import SVN_PROP_MERGEINFO |
| from svntest.main import server_has_mergeinfo |
| from merge_tests import local_path |
| from merge_tests import expected_merge_output |
| from merge_tests import svn_merge |
| from merge_tests import set_up_branch |
| |
| #---------------------------------------------------------------------- |
| |
| # Merging scenarios to test |
| # |
| # Merge once |
| # |
| # A (--?--- |
| # ( \ |
| # B (--?--x |
| # |
| # Merge twice in same direction |
| # |
| # A (--o-----?--- |
| # ( \ \ |
| # B (--o--x--?--x |
| # |
| # Merge to and fro |
| # |
| # A (--o-----?--x |
| # ( \ / |
| # B (--o--x--?--- |
| # |
| # A (--o-----o-----?--x |
| # ( \ \ / |
| # B (--o--x--o--x--?--- |
| # |
| # A (--o-----o--x--?--x |
| # ( \ / / |
| # B (--o--x--o-----?--- |
| # |
| # A (--o-----o--x--?--- |
| # ( \ / \ |
| # B (--o--x--o-----?--x |
| # |
| # Merge with cherry-picks |
| # (This set of six cases represents all of the topologically distinct |
| # scenarios involving one cherry-pick between two automatic merges.) |
| # |
| # Cherry1, fwd |
| # A (--o-----o-[o]----o--- |
| # ( \ \ \ |
| # B (--o--x--?-----c-----x |
| # |
| # Cherry2, fwd |
| # A (--o-----?-----c--o--- |
| # ( \ / \ |
| # B (--o--x--o-[o]-------x |
| # |
| # Cherry3, fwd |
| # A (--o-----?-------c--o---- |
| # ( \_____ / \ |
| # ( \ / \ |
| # B (--o--o-[o]-x-/---------x |
| # \__/ |
| # |
| # Cherry1, back |
| # A (--o-----o-[o]-------x |
| # ( \ \ / |
| # B (--o--x--?-----c--o--- |
| # |
| # Cherry2, back |
| # A (--o-----?-----c-----x |
| # ( \ / / |
| # B (--o--x--o-[o]----o--- |
| # |
| # Cherry3, back |
| # A (--o-----?-------c------x |
| # ( \_____ / / |
| # ( \ / / |
| # B (--o--o-[o]-x-/-----o---- |
| # \__/ |
| # |
| # Criss-cross merge |
| # |
| # A (--o--?--x--?---- |
| # ( \ / \ |
| # ( X \ |
| # ( / \ \ |
| # B (--o--?--x--?---x |
| # |
| # Subtree mergeinfo |
| # |
| # subtree to, fro |
| # A (--o-o-o-o---------x |
| # ( \ \ / |
| # ( \ \ / |
| # B ( o--o------s-- |
| # |
| # merge to, reverse cherry subtree to, merge to |
| # A (--o-o-o-o------------------ |
| # ( \ \ \ \ |
| # ( \ \ \ \ |
| # B ( o--o------x-------rcs----x |
| # |
| # Sparse WC |
| # |
| # ... |
| # |
| # Mixed-rev WC |
| # |
| # ... |
| # |
| # |
| # Key to diagrams: |
| # |
| # o - an original change |
| # ? - an original change or no-op (test both) |
| # x - a branch root merge |
| # c - a cherry-pick merge |
| # [o] - source range of a cherry-pick merge |
| # s - a subtree merge |
| # r - reverse merge |
| |
| |
| ######################################################################## |
| |
| def assert_equal(a, b): |
| """Assert that two generic Python objects are equal. If not, raise an |
| exception giving their values. Rationale: During debugging, it's |
| easier to see what's wrong if we see the values rather than just |
| an indication that the assertion failed.""" |
| if a != b: |
| raise Exception("assert_equal failed: a = (%s), b = (%s)" % (a, b)) |
| |
| def logical_changes_in_branch(sbox, branch): |
| """Return the set of logical changes that are actually in branch BRANCH |
| (at its current working version), by examining the state of the |
| branch files and directories rather than its mergeinfo. |
| |
| Each logical change is described by its branch and revision number |
| as a string such as 'A1'.""" |
| changes = set() |
| for propname in sbox.simple_proplist(branch + '/D').keys(): |
| if propname.startswith('prop-'): |
| changes.add(propname[5:]) |
| return changes |
| |
| def get_mergeinfo_change(sbox, target): |
| """Return a list of revision numbers representing the mergeinfo change |
| on TARGET (working version against base). Non-recursive.""" |
| exit, out, err = actions.run_and_verify_svn(None, None, [], |
| 'diff', '--depth=empty', |
| sbox.ospath(target)) |
| merged_revs = [] |
| for line in out: |
| match = re.match(r' Merged /(\w+):r(.*)', line) |
| if match: |
| for r_range in match.group(2).split(','): |
| if '-' in r_range: |
| r_start, r_end = r_range.split('-') |
| else: |
| r_start = r_end = r_range |
| merged_revs += range(int(r_start), int(r_end) + 1) |
| return merged_revs |
| |
| def get_3ways_from_output(output): |
| """Scan the list of lines OUTPUT for indications of 3-way merges. |
| Return a list of (base, source-right) tuples.""" |
| ### Problem: test suite strips debugging output within run_and_verify_...() |
| ### so we don't see it here. And relying on debug output is a temporary |
| ### measure only. Better to access svn_client_find_automatic_merge() |
| ### directly, via bindings? |
| |
| merges = [] |
| for line in output: |
| print "## " + line, |
| # Extract "A1" from a line like "DBG: merge.c:11336: base svn://.../A@1" |
| match = re.search(r'merge\.c:.* base .* /(\w+)@([0-9-]+)', line) |
| if match: |
| base = match.group(1) + match.group(2) |
| match = re.search(r'merge\.c:.* right.* /(\w+)@([0-9-]+)', line) |
| if match: |
| right = match.group(1) + match.group(2) |
| assert base is not None |
| merges.append((base, right)) |
| base = None |
| return merges |
| |
| def make_branches(sbox): |
| """Make branches A and B.""" |
| sbox.build() |
| sbox.simple_copy('A', 'B') |
| sbox.simple_commit() |
| os.chdir(sbox.wc_dir) |
| sbox.wc_dir = '' |
| |
| def modify_branch(sbox, branch, number, conflicting=False): |
| """Commit a modification to branch BRANCH. The actual modification depends |
| on NUMBER. If CONFLICTING=True, the change will be of a kind that |
| conflicts with any other change that has CONFLICTING=True. We don't |
| modify (properties on) the branch root node itself, to make it easier |
| for the tests to distinguish mergeinfo changes from these mods.""" |
| uniq = branch + str(number) # something like 'A1' or 'B2' |
| if conflicting: |
| sbox.simple_propset('conflict', uniq, branch + '/C') |
| else: |
| # Make some changes. We add a property, which we will read later in |
| # logical_changes_in_branch() to check that the correct logical |
| # changes were merged. We add a file, so that we will notice if |
| # Subversion tries to merge this same logical change into a branch |
| # that already has it (it will raise a tree conflict). |
| sbox.simple_propset('prop-' + uniq, uniq, branch + '/D') |
| sbox.simple_copy(branch + '/mu', branch + '/mu-' + uniq) |
| sbox.simple_commit() |
| |
| def expected_automatic_merge_output(target, expect_3ways): |
| """Calculate the expected output.""" |
| |
| # (This is rather specific to the current implementation.) |
| |
| # Match a notification for each rev-range. |
| if expect_3ways: |
| rev_ranges = [] |
| for base, right in expect_3ways: |
| if base[0] == right[0]: |
| base_rev = int(base[1:]) |
| right_rev = int(right[1:]) |
| rev_ranges += [(base_rev + 1, right_rev)]; |
| else: |
| rev_ranges = None |
| |
| # Match any content modifications; but not of the root of the branch |
| # because we don't intentionally modify the branch root node in most |
| # tests and we don't want to accidentally overlook a mergeinfo change. |
| lines = ["(A |D |[UG] | [UG]|[UG][UG]) " + target + os.path.sep + ".*\n"] |
| |
| # Match mergeinfo changes. (### Subtrees are not yet supported here.) |
| lines += [" [UG] " + target + "\n"] |
| |
| # At the moment, the automatic merge code sometimes says 'Merging |
| # differences between repository URLs' and sometimes 'Merging r3 through |
| # r5', but it's not trivial to predict which, so expect either form. |
| lines += ["--- Merging .* into '%s':\n" % (target,), |
| "--- Recording mergeinfo for merge .* into '%s':\n" % (target,)] |
| |
| return expected_merge_output(rev_ranges, lines, target=target) |
| |
| def automatic_merge(sbox, source, target, args=[], |
| expect_changes=None, expect_mi=None, expect_3ways=None): |
| """Do a complete, automatic merge from path SOURCE to path TARGET, and |
| commit. Verify the output and that there is no error. |
| ### TODO: Verify the changes made. |
| |
| ARGS are additional arguments passed to svn merge.""" |
| |
| source = local_path(source) |
| target = local_path(target) |
| |
| # First, update the WC target because mixed-rev is not fully supported. |
| sbox.simple_update(target) |
| |
| before_changes = logical_changes_in_branch(sbox, target) |
| |
| exp_out = expected_automatic_merge_output(target, expect_3ways) |
| exit, out, err = svntest.actions.run_and_verify_svn(None, exp_out, [], |
| 'merge', |
| '^/' + source, target, |
| *args) |
| |
| if expect_changes is not None: |
| after_changes = logical_changes_in_branch(sbox, target) |
| merged_changes = after_changes - before_changes |
| assert_equal(merged_changes, set(expect_changes)) |
| reversed_changes = before_changes - after_changes |
| assert_equal(reversed_changes, set()) |
| |
| if expect_mi is not None: |
| actual_mi_change = get_mergeinfo_change(sbox, target) |
| assert_equal(actual_mi_change, expect_mi) |
| |
| if expect_3ways is not None: |
| ### actual_3ways = get_3ways_from_output(out) |
| ### assert_equal(actual_3ways, expect_3ways) |
| pass |
| |
| sbox.simple_commit() |
| |
| def three_way_merge(base_node, source_right_node): |
| return (base_node, source_right_node) |
| |
| def three_way_merge_no_op(base_node, source_right_node): |
| return (base_node, source_right_node) |
| |
| def cherry_pick(sbox, rev, source, target): |
| """Cherry-pick merge revision REV from branch SOURCE to branch TARGET |
| (both WC-relative paths), and commit.""" |
| sbox.simple_update(target) |
| svn_merge(rev, source, target) |
| sbox.simple_commit() |
| |
| no_op_commit__n = 0 |
| def no_op_commit(sbox): |
| """Commit a new revision that does not affect the branches under test.""" |
| |
| global no_op_commit__n |
| sbox.simple_propset('foo', str(no_op_commit__n), 'iota') |
| no_op_commit__n += 1 |
| sbox.simple_commit('iota') |
| |
| |
| #---------------------------------------------------------------------- |
| |
| def init_mod_merge_mod(sbox, mod_6, mod_7): |
| """Modify both branches, merge A -> B, optionally modify again. |
| MOD_6 is True to modify A in r6, MOD_7 is True to modify B in r7, |
| otherwise make no-op commits for r6 and/or r7.""" |
| |
| # A (--o------?- |
| # ( \ |
| # B (---o--x---? |
| # 2 34 5 67 |
| |
| make_branches(sbox) |
| modify_branch(sbox, 'A', 3) |
| modify_branch(sbox, 'B', 4) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A3'], |
| expect_mi=[2, 3, 4], |
| expect_3ways=[three_way_merge('A1', 'A4')]) |
| |
| if mod_6: |
| modify_branch(sbox, 'A', 6) |
| else: |
| no_op_commit(sbox) # r6 |
| |
| if mod_7: |
| modify_branch(sbox, 'B', 7) |
| else: |
| no_op_commit(sbox) # r7 |
| |
| ######################################################################## |
| |
| # Merge once |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_once_1(sbox): |
| """merge_once_1""" |
| |
| # A (------ |
| # ( \ |
| # B (-----x |
| # 2 34 5 |
| |
| make_branches(sbox) |
| no_op_commit(sbox) # r3 |
| no_op_commit(sbox) # r4 |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=[], |
| expect_mi=[2, 3, 4], |
| expect_3ways=[three_way_merge_no_op('A1', 'A4')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_once_2(sbox): |
| """merge_once_2""" |
| |
| # A (-o---- |
| # ( \ |
| # B (-----x |
| # 2 34 5 |
| |
| make_branches(sbox) |
| modify_branch(sbox, 'A', 3) |
| no_op_commit(sbox) # r4 |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A3'], |
| expect_mi=[2, 3, 4], |
| expect_3ways=[three_way_merge('A1', 'A4')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_once_3(sbox): |
| """merge_once_3""" |
| |
| # A (------ |
| # ( \ |
| # B (--o--x |
| # 2 34 5 |
| |
| make_branches(sbox) |
| no_op_commit(sbox) # r3 |
| modify_branch(sbox, 'B', 4) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=[], |
| expect_mi=[2, 3, 4], |
| expect_3ways=[three_way_merge_no_op('A1', 'A4')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_once_4(sbox): |
| """merge_once_4""" |
| |
| # A (-o---- |
| # ( \ |
| # B (--o--x |
| # 2 34 5 |
| |
| make_branches(sbox) |
| modify_branch(sbox, 'A', 3) |
| modify_branch(sbox, 'B', 4) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A3'], |
| expect_mi=[2, 3, 4], |
| expect_3ways=[three_way_merge('A1', 'A4')]) |
| |
| #---------------------------------------------------------------------- |
| |
| # Merge twice in same direction |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_twice_same_direction_1(sbox): |
| """merge_twice_same_direction_1""" |
| |
| # A (--o----------- |
| # ( \ \ |
| # B (---o--x------x |
| # 2 34 5 67 8 |
| |
| init_mod_merge_mod(sbox, mod_6=False, mod_7=False) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=[], |
| expect_mi=[5, 6, 7], |
| expect_3ways=[three_way_merge_no_op('A4', 'A7')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_twice_same_direction_2(sbox): |
| """merge_twice_same_direction_2""" |
| |
| # A (--o------o---- |
| # ( \ \ |
| # B (---o--x---o--x |
| # 2 34 5 67 8 |
| |
| init_mod_merge_mod(sbox, mod_6=True, mod_7=True) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A6'], |
| expect_mi=[5, 6, 7], |
| expect_3ways=[three_way_merge('A4', 'A7')]) |
| |
| #---------------------------------------------------------------------- |
| |
| # Merge to and fro |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_and_fro_1_1(sbox): |
| """merge_to_and_fro_1_1""" |
| |
| # A (--o----------x |
| # ( \ / |
| # B (---o--x------- |
| # 2 34 5 67 8 |
| |
| init_mod_merge_mod(sbox, mod_6=False, mod_7=False) |
| |
| automatic_merge(sbox, 'B', 'A', |
| expect_changes=['B4'], |
| expect_mi=[2, 3, 4, 5, 6, 7], |
| expect_3ways=[three_way_merge('A4', 'B7')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_and_fro_1_2(sbox): |
| """merge_to_and_fro_1_2""" |
| |
| # A (--o------o---x |
| # ( \ / |
| # B (---o--x---o--- |
| # 2 34 5 67 8 |
| |
| init_mod_merge_mod(sbox, mod_6=True, mod_7=True) |
| |
| automatic_merge(sbox, 'B', 'A', |
| expect_changes=['B4', 'B7'], |
| expect_mi=[2, 3, 4, 5, 6, 7], |
| expect_3ways=[three_way_merge('A4', 'B7')]) |
| |
| def init_merge_to_and_fro_2(sbox, mod_9, mod_10): |
| """Set up branches A and B for the merge_to_and_fro_2 scenarios. |
| MOD_9 is True to modify A in r9, MOD_10 is True to modify B in r10, |
| otherwise make no-op commits for r9 and/or r10.""" |
| |
| # A (--o------o------?- |
| # ( \ \ |
| # B (---o--x---o--x---? |
| # 2 34 5 67 8--90 |
| |
| init_mod_merge_mod(sbox, mod_6=True, mod_7=True) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A6'], |
| expect_mi=[5, 6, 7], |
| expect_3ways=[three_way_merge('A4', 'A7')]) |
| |
| if mod_9: |
| modify_branch(sbox, 'A', 9) |
| else: |
| no_op_commit(sbox) # r9 |
| |
| if mod_10: |
| modify_branch(sbox, 'B', 10) |
| else: |
| no_op_commit(sbox) # r10 |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_and_fro_2_1(sbox): |
| """merge_to_and_fro_2_1""" |
| |
| # A (--o------o----------x |
| # ( \ \ / |
| # B (---o--x---o--x------- |
| # 2 34 5 67 8 90 1 |
| |
| init_merge_to_and_fro_2(sbox, mod_9=False, mod_10=False) |
| |
| automatic_merge(sbox, 'B', 'A', |
| expect_changes=['B4', 'B7'], |
| expect_mi=[2, 3, 4, 5, 6, 7, 8, 9, 10], |
| expect_3ways=[three_way_merge('A7', 'B10')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_and_fro_2_2(sbox): |
| """merge_to_and_fro_2_2""" |
| |
| # A (--o------o------o---x |
| # ( \ \ / |
| # B (---o--x---o--x---o--- |
| # 2 34 5 67 8 90 1 |
| |
| init_merge_to_and_fro_2(sbox, mod_9=True, mod_10=True) |
| |
| automatic_merge(sbox, 'B', 'A', |
| expect_changes=['B4', 'B7', 'B10'], |
| expect_mi=[2, 3, 4, 5, 6, 7, 8, 9, 10], |
| expect_3ways=[three_way_merge('A7', 'B10')]) |
| |
| def init_merge_to_and_fro_3(sbox, mod_9, mod_10): |
| """Set up branches A and B for the merge_to_and_fro_3/4 scenarios. |
| MOD_9 is True to modify A in r9, MOD_10 is True to modify B in r10, |
| otherwise make no-op commits for r9 and/or r10.""" |
| |
| # A (--o------o---x--?- |
| # ( \ / |
| # B (---o--x---o------? |
| # 2 34 5 67 8 90 |
| |
| init_mod_merge_mod(sbox, mod_6=True, mod_7=True) |
| |
| automatic_merge(sbox, 'B', 'A', |
| expect_changes=['B4', 'B7'], |
| expect_mi=[2, 3, 4, 5, 6, 7], |
| expect_3ways=[three_way_merge('A4', 'B7')]) |
| |
| if mod_9: |
| modify_branch(sbox, 'A', 9) |
| else: |
| no_op_commit(sbox) # r9 |
| |
| if mod_10: |
| modify_branch(sbox, 'B', 10) |
| else: |
| no_op_commit(sbox) # r10 |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_and_fro_3_1(sbox): |
| """merge_to_and_fro_3_1""" |
| |
| # A (--o------o---x------x |
| # ( \ / / |
| # B (---o--x---o---------- |
| # 2 34 5 67 8 90 1 |
| |
| init_merge_to_and_fro_3(sbox, mod_9=False, mod_10=False) |
| |
| automatic_merge(sbox, 'B', 'A', |
| expect_changes=[], |
| expect_mi=[8, 9, 10], |
| expect_3ways=[three_way_merge_no_op('B7', 'B10')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_and_fro_3_2(sbox): |
| """merge_to_and_fro_3_2""" |
| |
| # A (--o------o---x--o---x |
| # ( \ / / |
| # B (---o--x---o------o--- |
| # 2 34 5 67 8 90 1 |
| |
| init_merge_to_and_fro_3(sbox, mod_9=True, mod_10=True) |
| |
| automatic_merge(sbox, 'B', 'A', |
| expect_changes=['B10'], |
| expect_mi=[8, 9, 10], |
| expect_3ways=[three_way_merge('B7', 'B10')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_and_fro_4_1(sbox): |
| """merge_to_and_fro_4_1""" |
| |
| # A (--o------o---x------- |
| # ( \ / \ |
| # B (---o--x---o---------x |
| # 2 34 5 67 8 90 1 |
| |
| init_merge_to_and_fro_3(sbox, mod_9=False, mod_10=False) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A6'], |
| expect_mi=[5, 6, 7, 8, 9, 10], |
| expect_3ways=[three_way_merge_no_op('B7', 'A10')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_and_fro_4_2(sbox): |
| """merge_to_and_fro_4_2""" |
| |
| # A (--o------o---x--o---- |
| # ( \ / \ |
| # B (---o--x---o------o--x |
| # 2 34 5 67 8 90 1 |
| |
| init_merge_to_and_fro_3(sbox, mod_9=True, mod_10=True) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A6', 'A9'], |
| expect_mi=[5, 6, 7, 8, 9, 10], |
| expect_3ways=[three_way_merge('B7', 'A10')]) |
| |
| #---------------------------------------------------------------------- |
| |
| # Cherry-pick scenarios |
| |
| @SkipUnless(server_has_mergeinfo) |
| def cherry1_fwd(sbox): |
| """cherry1_fwd""" |
| |
| # A (--o------o--[o]----o--- |
| # ( \ \ \ |
| # B (---o--x---------c-----x |
| # 2 34 5 67 8 9 0 1 |
| |
| init_mod_merge_mod(sbox, mod_6=True, mod_7=False) |
| modify_branch(sbox, 'A', 8) |
| cherry_pick(sbox, 8, 'A', 'B') |
| modify_branch(sbox, 'A', 10) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A6', 'A10'], # and NOT A8 |
| expect_mi=[5, 6, 7, 9, 10], |
| expect_3ways=[three_way_merge('A4', 'A7'), |
| three_way_merge('A8', 'A10')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| @XFail() |
| @Issue(4255) |
| def cherry2_fwd(sbox): |
| """cherry2_fwd""" |
| |
| # A (--o-------------c--o--- |
| # ( \ / \ |
| # B (---o--x---o-[o]-------x |
| # 2 34 5 67 8 9 0 1 |
| |
| init_mod_merge_mod(sbox, mod_6=False, mod_7=True) |
| modify_branch(sbox, 'B', 8) |
| cherry_pick(sbox, 8, 'B', 'A') |
| modify_branch(sbox, 'A', 10) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A10'], # and NOT A9 |
| expect_mi=[5, 6, 7, 8, 9, 10], |
| expect_3ways=[three_way_merge('A9', 'A10')]) |
| |
| @SkipUnless(server_has_mergeinfo) |
| @XFail() |
| @Issue(4255) |
| def cherry3_fwd(sbox): |
| """cherry3_fwd""" |
| |
| # A (--o--------------c--o---- |
| # ( \ / \ |
| # ( \ / \ |
| # B (---o--o-[o]-x-/---------x |
| # \__/ |
| # 2 34 5 6 7 8 9 0 |
| |
| make_branches(sbox) |
| modify_branch(sbox, 'A', 3) |
| modify_branch(sbox, 'B', 4) |
| modify_branch(sbox, 'B', 5) |
| modify_branch(sbox, 'B', 6) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A3'], |
| expect_mi=[2, 3, 4, 5, 6], |
| expect_3ways=[three_way_merge('A1', 'A6')]) |
| |
| cherry_pick(sbox, 6, 'B', 'A') |
| modify_branch(sbox, 'A', 9) |
| |
| automatic_merge(sbox, 'A', 'B', |
| expect_changes=['A9'], # and NOT A8 |
| expect_mi=[7, 8, 9], |
| expect_3ways=[three_way_merge('A8', 'A9')]) |
| |
| #---------------------------------------------------------------------- |
| # Automatic merges ignore subtree mergeinfo during reintegrate. |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4258) |
| def subtree_to_and_fro(sbox): |
| "reintegrate considers source subtree mergeinfo" |
| |
| # A (-----o-o-o-o------------x |
| # ( \ \ / |
| # ( \ \ / |
| # A_COPY ( o---------o--s--o-- |
| # 2 3 4 5 6 7 8 9 |
| |
| # Some paths we'll care about. |
| A_COPY_gamma_path = sbox.ospath('A_COPY/D/gamma') |
| psi_path = sbox.ospath('A/D/H/psi') |
| A_COPY_D_path = sbox.ospath('A_COPY/D') |
| A_path = sbox.ospath('A') |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Setup a simple 'trunk & branch': Copy ^/A to ^/A_COPY in r2 and then |
| # make a few edits under A in r3-6 (edits r3, r4, r6 are under subtree 'D'): |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # r7 - Edit a file on the branch. |
| svntest.main.file_write(A_COPY_gamma_path, "Branch edit to 'gamma'.\n") |
| svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir, |
| '-m', 'Edit a file on our branch') |
| |
| # r8 - Do a subtree sync merge from ^/A/D to A_COPY/D. |
| # Note that among other things this changes A_COPY/D/H/psi. |
| svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, None, [], 'merge', |
| sbox.repo_url + '/A/D', A_COPY_D_path) |
| svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir, |
| '-m', 'Automatic subtree merge') |
| |
| # r9 - Make an edit to A/D/H/psi. |
| svntest.main.file_write(psi_path, "Trunk Edit to 'psi'.\n") |
| svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir, |
| '-m', 'Edit a file on our trunk') |
| |
| # Now reintegrate ^/A_COPY back to A. Prior to issue #4258's fix, the |
| # the subtree merge to A_COPY/D just looks like any other branch edit and |
| # was not considered a merge. So the changes which exist on A/D and were |
| # merged to A_COPY/D, were merged *back* to A, resulting in a conflict: |
| # |
| # C:\...\working_copies\merge_automatic_tests-18>svn merge ^^/A_COPY A |
| # DBG: merge.c:11461: base on source: ^/A@1 |
| # DBG: merge.c:11462: base on target: ^/A@1 |
| # DBG: merge.c:11567: yca ^/A@1 |
| # DBG: merge.c:11568: base ^/A@1 |
| # DBG: merge.c:11571: right ^/A_COPY@8 |
| # Conflict discovered in file 'A\D\H\psi'. |
| # Select: (p) postpone, (df) diff-full, (e) edit, |
| # (mc) mine-conflict, (tc) theirs-conflict, |
| # (s) show all options: p |
| # --- Merging r2 through r9 into 'A': |
| # C A\D\H\psi |
| # U A\D\gamma |
| # --- Recording mergeinfo for merge of r2 through r9 into 'A': |
| # U A |
| # Summary of conflicts: |
| # Text conflicts: 1 |
| svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) |
| exit_code, out, err = svntest.actions.run_and_verify_svn( |
| None, [], svntest.verify.AnyOutput, |
| 'merge', sbox.repo_url + '/A_COPY', A_path) |
| |
| # Better to produce the same warning that explicitly using the |
| # --reintegrate option would produce: |
| svntest.verify.verify_outputs("Automatic Reintegrate failed, but not " |
| "in the way expected", |
| err, None, |
| "(svn: E195016: Reintegrate can only be used if " |
| "revisions 2 through 9 were previously " |
| "merged from .*/A to the reintegrate source, " |
| "but this is not the case:\n)" |
| "|( A_COPY\n)" |
| "|( Missing ranges: /A:5\n)" |
| "|(\n)" |
| "|" + svntest.main.stack_trace_regexp, |
| None, |
| True) # Match *all* lines of stdout |
| |
| #---------------------------------------------------------------------- |
| # Automatic merges ignore subtree mergeinfo gaps older than the last rev |
| # synced to the target root. |
| @SkipUnless(server_has_mergeinfo) |
| def merge_to_reverse_cherry_subtree_to_merge_to(sbox): |
| "sync merge considers target subtree mergeinfo" |
| |
| # A (--o-o-o-o------------------ |
| # ( \ \ \ \ |
| # ( \ \ \ \ |
| # B ( o--o------x-------rc-----x |
| |
| # Some paths we'll care about. |
| A_COPY_path = sbox.ospath('A_COPY') |
| A_COPY_B_path = sbox.ospath('A_COPY/B') |
| A_COPY_beta_path = sbox.ospath('A_COPY/B/E/beta') |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| # Setup a simple 'trunk & branch': Copy ^/A to ^/A_COPY in r2 and then |
| # make a few edits under A in r3-6: |
| wc_disk, wc_status = set_up_branch(sbox) |
| |
| # Sync merge ^/A to A_COPY, then reverse merge r5 from ^/A/B to A_COPY/B. |
| # This results in mergeinfo on the target which makes it appear that the |
| # branch is synced up to r6, but the subtree mergeinfo on A_COPY/B reveals |
| # that r5 has not been merged to that subtree: |
| # |
| # Properties on 'A_COPY': |
| # svn:mergeinfo |
| # /A:2-6 |
| # Properties on 'A_COPY\B': |
| # svn:mergeinfo |
| # /A/B:2-4,6 |
| svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) |
| svntest.actions.run_and_verify_svn(None, None, [], 'merge', |
| sbox.repo_url + '/A', A_COPY_path) |
| svntest.actions.run_and_verify_svn(None, None, [], 'merge', '-c-5', |
| sbox.repo_url + '/A/B', |
| A_COPY_B_path) |
| svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir, '-m', |
| 'sync merge and reverse subtree merge') |
| |
| # Try an automatic sync merge from ^/A to A_COPY. Revision 5 should be |
| # merged to A_COPY/B as its subtree mergeinfo reveals that rev is missing, |
| # like so: |
| # |
| # >svn merge ^/A A_COPY |
| # --- Merging r5 into 'A_COPY\B': |
| # U A_COPY\B\E\beta |
| # --- Recording mergeinfo for merge of r5 through r7 into 'A_COPY': |
| # U A_COPY |
| # --- Recording mergeinfo for merge of r5 through r7 into 'A_COPY\B': |
| # U A_COPY\B |
| # --- Eliding mergeinfo from 'A_COPY\B': |
| # U A_COPY\B |
| # |
| # But the merge ignores the subtree mergeinfo and considers |
| # only the mergeinfo on the target itself (and thus is a no-op but for |
| # the mergeinfo change on the root of the merge target): |
| # |
| # >svn merge ^/A A_COPY |
| # --- Recording mergeinfo for merge of r7 into 'A_COPY': |
| # U A_COPY |
| # |
| # >svn diff |
| # Index: A_COPY |
| # =================================================================== |
| # --- A_COPY (revision 7) |
| # +++ A_COPY (working copy) |
| # |
| # Property changes on: A_COPY |
| # ___________________________________________________________________ |
| # Modified: svn:mergeinfo |
| # Merged /A:r7 |
| svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) |
| expected_output = wc.State(A_COPY_path, { |
| 'B/E/beta' : Item(status='U '), |
| }) |
| expected_mergeinfo_output = wc.State(A_COPY_path, { |
| '' : Item(status=' U'), |
| 'B' : Item(status=' U'), |
| }) |
| 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=' M'), |
| '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='7') |
| 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("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_path, None, None, |
| sbox.repo_url + '/A', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| None, None, None, None, |
| None, 1, 0, A_COPY_path) |
| |
| #---------------------------------------------------------------------- |
| # Automatic merges should notice ancestory for replaced files |
| @SkipUnless(server_has_mergeinfo) |
| def merge_replacement(sbox): |
| "notice ancestory for replaced files" |
| |
| A_path = sbox.ospath('A') |
| A_COPY_path = sbox.ospath('A_copy') |
| A_COPY_mu_path = sbox.ospath('A_copy/mu') |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| sbox.simple_copy('A', 'A_copy') |
| # Commit as r2 |
| sbox.simple_commit() |
| |
| sbox.simple_rm('A_copy/B/lambda') |
| sbox.simple_copy('A_copy/D/gamma', 'A_copy/B/lambda') |
| |
| sbox.simple_rm('A_copy/mu') |
| svntest.main.file_write(A_COPY_mu_path, "Branch edit to 'mu'.\n") |
| sbox.simple_add('A_copy/mu') |
| |
| # Commit as r3 |
| sbox.simple_commit() |
| |
| expected_output = wc.State(A_path, { |
| 'B/lambda' : Item(status='R '), |
| 'mu' : Item(status='R '), |
| }) |
| 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='1'), |
| 'B' : Item(status=' ', wc_rev='1'), |
| 'mu' : Item(status='R ', copied='+', wc_rev='-'), |
| 'B/E' : Item(status=' ', wc_rev='1'), |
| 'B/E/alpha' : Item(status=' ', wc_rev='1'), |
| 'B/E/beta' : Item(status=' ', wc_rev='1'), |
| 'B/lambda' : Item(status='R ', copied='+', wc_rev='-'), |
| '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='1'), |
| '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='1'), |
| 'D/H/omega' : Item(status=' ', wc_rev='1'), |
| }) |
| |
| expected_disk = wc.State('', { |
| '' : Item(props={SVN_PROP_MERGEINFO : '/A_copy:2-3'}), |
| 'B' : Item(), |
| 'mu' : Item("Branch edit to '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 'gamma'.\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_path, None, None, |
| sbox.repo_url + '/A_copy', None, |
| expected_output, |
| expected_mergeinfo_output, |
| expected_elision_output, |
| expected_disk, |
| expected_status, |
| expected_skip, |
| None, None, None, None, |
| None, 1, 0, A_path) |
| |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4313) |
| |
| # Test for issue #4313 'replaced merges source causes assertion during |
| # automatic merge' |
| def auto_merge_handles_replacements_in_merge_source(sbox): |
| "automerge handles replacements in merge source" |
| |
| sbox.build() |
| |
| A_path = sbox.ospath('A') |
| branch1_path = sbox.ospath('branch-1') |
| branch2_path = sbox.ospath('branch-2') |
| |
| # r2 - Make two branches. |
| sbox.simple_copy('A', 'branch-1') |
| sbox.simple_copy('A', 'branch-2') |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # r3 - Replace 'A' with 'branch-1'. |
| svntest.main.run_svn(None, 'del', A_path) |
| svntest.main.run_svn(None, 'copy', branch1_path, A_path) |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # Merge^/A to branch-2, it should be a no-op but for mergeinfo changes, |
| # but it *should* work. Previously this failed because automatic merges |
| # weren't adhering to the merge source normalization rules, resulting in |
| # this assertion: |
| # |
| # >svn merge ^/A branch-2 |
| # ..\..\..\subversion\libsvn_client\merge.c:4568: (apr_err=235000) |
| # svn: E235000: In file '..\..\..\subversion\libsvn_client\merge.c' |
| # line 4568: assertion failed (apr_hash_count(implicit_src_mergeinfo) |
| # == 1) |
| # |
| # This application has requested the Runtime to terminate it in an |
| # unusual way. |
| # Please contact the application's support team for more information. |
| svntest.actions.run_and_verify_svn( |
| None, |
| ["--- Recording mergeinfo for merge of r2 into '" + branch2_path + "':\n", |
| " U " + branch2_path + "\n", |
| "--- Recording mergeinfo for merge of r3 into '" + branch2_path + "':\n", |
| " G " + branch2_path + "\n"], |
| [], 'merge', sbox.repo_url + '/A', branch2_path) |
| |
| # Test for issue #4329 'automatic merge uses reintegrate type merge if |
| # source is fully synced' |
| @SkipUnless(server_has_mergeinfo) |
| @Issue(4329) |
| def effective_sync_results_in_reintegrate(sbox): |
| "an effectively synced branch gets reintegrated" |
| |
| sbox.build() |
| |
| iota_path = sbox.ospath('iota') |
| A_path = sbox.ospath('A') |
| psi_path = sbox.ospath('A/D/H/psi') |
| mu_path = sbox.ospath('A/mu') |
| branch_path = sbox.ospath('branch') |
| psi_branch_path = sbox.ospath('branch/D/H/psi') |
| |
| # r2 - Make a branch. |
| sbox.simple_copy('A', 'branch') |
| sbox.simple_commit() |
| |
| # r3 - An edit to a file on the trunk. |
| sbox.simple_append('A/mu', "Trunk edit to 'mu'\n", True) |
| sbox.simple_commit() |
| |
| # r4 - An edit to a file on the branch |
| sbox.simple_append('branch/D/H/psi', "Branch edit to 'psi'\n", True) |
| sbox.simple_commit() |
| |
| # r5 - Effectively sync all changes on trunk to the branch. We do this |
| # not via an automatic sync merge, but with a cherry pick that effectively |
| # merges the same changes (i.e. r3). |
| sbox.simple_update() |
| cherry_pick(sbox, 3, A_path, branch_path) |
| |
| # r6 - Make another edit to the file on the trunk. |
| sbox.simple_append('A/mu', "2nd trunk edit to 'mu'\n", True) |
| sbox.simple_commit() |
| |
| # Now try an explicit --reintegrate merge from ^/branch to A. |
| # This should work because since the resolution of |
| # http://subversion.tigris.org/issues/show_bug.cgi?id=3577 |
| # if B is *effectively* synced with A, then B can be reintegrated |
| # to A. |
| sbox.simple_update() |
| expected_output = [ |
| "--- Merging differences between repository URLs into '" + |
| A_path + "':\n", |
| "U " + psi_path + "\n", |
| "--- Recording mergeinfo for merge between repository URLs into '" + |
| A_path + "':\n", |
| " U " + A_path + "\n"] |
| svntest.actions.run_and_verify_svn(None, expected_output, [], 'merge', |
| sbox.repo_url + '/branch', A_path, |
| '--reintegrate') |
| |
| # Revert the merge and try it again, this time without the --reintegrate |
| # option. The merge should still work with the same results. |
| # |
| # Previously this failed because the reintegrate code path is not followed, |
| # rather the automatic merge attempts a sync style merge of the yca (^/A@1) |
| # through the HEAD of the branch (^/branch@7). This results in a spurious |
| # conflict on A/mu as the edit made in r3 is reapplied. |
| # |
| # >svn merge ^/branch A |
| # --- Merging r2 through r6 into 'A': |
| # C A\mu |
| # U A\D\H\psi |
| # --- Recording mergeinfo for merge of r2 through r6 into 'A': |
| # U A |
| # Summary of conflicts: |
| # Text conflicts: 1 |
| # Conflict discovered in file 'A\mu'. |
| # Select: (p) postpone, (df) diff-full, (e) edit, (m) merge, |
| # (mc) mine-conflict, (tc) theirs-conflict, (s) show all options: p |
| svntest.actions.run_and_verify_svn(None, None, [], 'revert', A_path, '-R') |
| svntest.actions.run_and_verify_svn(None, expected_output, [], 'merge', |
| sbox.repo_url + '/branch', A_path) |
| |
| @Issue(4481) |
| def reintegrate_subtree_not_updated(sbox): |
| "reintegrate subtree not updated" |
| |
| sbox.build() |
| |
| # Create change on branch 'D_1' |
| sbox.simple_copy('A/D', 'D_1') |
| sbox.simple_commit() |
| sbox.simple_append('D_1/G/pi', "D_1/G pi edit\n") |
| sbox.simple_append('D_1/H/chi', "D_1/H chi edit\n") |
| sbox.simple_commit() |
| |
| # Merge back to 'D' with two subtree merges |
| expected_output = [ |
| "--- Merging r2 through r3 into '" |
| + sbox.ospath('A/D/G') + "':\n", |
| "U " |
| + sbox.ospath('A/D/G/pi') + "\n", |
| "--- Recording mergeinfo for merge of r2 through r3 into '" |
| + sbox.ospath('A/D/G') + "':\n", |
| " U " |
| + sbox.ospath('A/D/G') + "\n"] |
| svntest.actions.run_and_verify_svn(None, expected_output, [], |
| 'merge', |
| sbox.repo_url + '/D_1/G', |
| sbox.ospath('A/D/G')) |
| expected_output = [ |
| "--- Merging r2 through r3 into '" |
| + sbox.ospath('A/D/H') + "':\n", |
| "U " |
| + sbox.ospath('A/D/H/chi') + "\n", |
| "--- Recording mergeinfo for merge of r2 through r3 into '" |
| + sbox.ospath('A/D/H') + "':\n", |
| " U " |
| + sbox.ospath('A/D/H') + "\n"] |
| svntest.actions.run_and_verify_svn(None, expected_output, [], |
| 'merge', |
| sbox.repo_url + '/D_1/H', |
| sbox.ospath('A/D/H')) |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # Create branch 'D_2' |
| sbox.simple_copy('A/D', 'D_2') |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # Create change on 'D_2' |
| sbox.simple_append('D_2/G/pi', "D_2/G pi edit\n") |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # Create change on 'D' |
| sbox.simple_append('A/D/G/rho', "D/G rho edit\n") |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # Sync merge to 'D_2' (doesn't record mergeinfo on 'D_2/H' subtree) |
| expected_output = [ |
| "--- Merging r5 through r7 into '" |
| + sbox.ospath('D_2') + "':\n", |
| "U " |
| + sbox.ospath('D_2/G/rho') + "\n", |
| "--- Recording mergeinfo for merge of r5 through r7 into '" |
| + sbox.ospath('D_2') + "':\n", |
| " U " |
| + sbox.ospath('D_2') + "\n", |
| "--- Recording mergeinfo for merge of r5 through r7 into '" |
| + sbox.ospath('D_2/G') + "':\n", |
| " U " |
| + sbox.ospath('D_2/G') + "\n"] |
| svntest.actions.run_and_verify_svn(None, expected_output, [], |
| 'merge', |
| sbox.repo_url + '/A/D', |
| sbox.ospath('D_2')) |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # Reintegrate 'D_2' to 'D' |
| expected_output = [ |
| "--- Merging differences between repository URLs into '" |
| + sbox.ospath('A/D') + "':\n", |
| "U " |
| + sbox.ospath('A/D/G/pi') + "\n", |
| " U " |
| + sbox.ospath('A/D/G') + "\n", |
| "--- Recording mergeinfo for merge between repository URLs into '" |
| + sbox.ospath('A/D') + "':\n", |
| " U " |
| + sbox.ospath('A/D') + "\n", |
| " U " |
| + sbox.ospath('A/D/G') + "\n"] |
| svntest.actions.run_and_verify_svn(None, expected_output, [], |
| 'merge', |
| sbox.repo_url + '/D_2', |
| sbox.ospath('A/D')) |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| # merge to 'D_2'. This merge previously failed with this error: |
| # |
| # svn: E195016: Reintegrate can only be used if revisions 5 through 9 were |
| # previously merged from [URL]/D_2 to the reintegrate source, but this is |
| # not the case: |
| # A/D/G |
| # Missing ranges: /A/D/G:7 |
| # |
| expected_output = [ |
| "--- Merging differences between repository URLs into '" |
| + sbox.ospath('D_2') + "':\n", |
| " U " |
| + sbox.ospath('D_2/G') + "\n", |
| "--- Recording mergeinfo for merge between repository URLs into '" |
| + sbox.ospath('D_2') + "':\n", |
| " U " |
| + sbox.ospath('D_2') + "\n", |
| " G " |
| + sbox.ospath('D_2/G') + "\n"] |
| svntest.actions.run_and_verify_svn(None, expected_output, [], |
| 'merge', |
| sbox.repo_url + '/A/D', |
| sbox.ospath('D_2')) |
| sbox.simple_commit() |
| sbox.simple_update() |
| |
| def merge_to_copy_and_add(sbox): |
| "merge peg to a copy and add" |
| |
| sbox.build() |
| |
| sbox.simple_copy('A', 'AA') |
| sbox.simple_append('A/mu', 'A/mu') |
| sbox.simple_commit('A') |
| |
| # This is the scenario the code is supposed to support; a copy |
| svntest.actions.run_and_verify_svn(None, None, [], |
| 'merge', '^/A', sbox.ospath('AA')) |
| |
| sbox.simple_mkdir('A3') |
| # And this case used to segfault, because merge didn't check |
| # if the path has a repository location |
| expected_err = ".*svn: E195012: Can't perform .*A3'.*added.*" |
| svntest.actions.run_and_verify_svn(None, None, expected_err, |
| 'merge', '^/A', sbox.ospath('A3')) |
| # Try the same merge with --reintegrate, for completeness' sake. |
| expected_err = ".*svn: E195012: Can't reintegrate into .*A3'.*added.*" |
| svntest.actions.run_and_verify_svn(None, None, expected_err, |
| 'merge', '--reintegrate', '^/A', |
| sbox.ospath('A3')) |
| |
| ######################################################################## |
| # Run the tests |
| |
| |
| # list all tests here, starting with None: |
| test_list = [ None, |
| merge_once_1, |
| merge_once_2, |
| merge_once_3, |
| merge_once_4, |
| merge_twice_same_direction_1, |
| merge_twice_same_direction_2, |
| merge_to_and_fro_1_1, |
| merge_to_and_fro_1_2, |
| merge_to_and_fro_2_1, |
| merge_to_and_fro_2_2, |
| merge_to_and_fro_3_1, |
| merge_to_and_fro_3_2, |
| merge_to_and_fro_4_1, |
| merge_to_and_fro_4_2, |
| cherry1_fwd, |
| cherry2_fwd, |
| cherry3_fwd, |
| subtree_to_and_fro, |
| merge_to_reverse_cherry_subtree_to_merge_to, |
| merge_replacement, |
| auto_merge_handles_replacements_in_merge_source, |
| effective_sync_results_in_reintegrate, |
| reintegrate_subtree_not_updated, |
| merge_to_copy_and_add, |
| ] |
| |
| if __name__ == '__main__': |
| svntest.main.run_tests(test_list) |
| # NOTREACHED |
| |
| |
| ### End of file. |