3
# merge_automatic_tests.py: testing "automatic merge" scenarios
5
# Subversion is a tool for revision control.
6
# See http://subversion.apache.org for more information.
8
# ====================================================================
9
# Licensed to the Apache Software Foundation (ASF) under one
10
# or more contributor license agreements. See the NOTICE file
11
# distributed with this work for additional information
12
# regarding copyright ownership. The ASF licenses this file
13
# to you under the Apache License, Version 2.0 (the
14
# "License"); you may not use this file except in compliance
15
# with the License. You may obtain a copy of the License at
17
# http://www.apache.org/licenses/LICENSE-2.0
19
# Unless required by applicable law or agreed to in writing,
20
# software distributed under the License is distributed on an
21
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22
# KIND, either express or implied. See the License for the
23
# specific language governing permissions and limitations
25
######################################################################
28
import shutil, sys, re, os
33
from svntest import main, wc, verify, actions
37
Skip = svntest.testcase.Skip_deco
38
SkipUnless = svntest.testcase.SkipUnless_deco
39
XFail = svntest.testcase.XFail_deco
40
Issues = svntest.testcase.Issues_deco
41
Issue = svntest.testcase.Issue_deco
42
Wimp = svntest.testcase.Wimp_deco
44
from svntest.main import SVN_PROP_MERGEINFO
45
from svntest.main import server_has_mergeinfo
46
from merge_tests import local_path
47
from merge_tests import expected_merge_output
48
from merge_tests import svn_merge
49
from merge_tests import set_up_branch
51
#----------------------------------------------------------------------
53
# Merging scenarios to test
61
# Merge twice in same direction
73
# A (--o-----o-----?--x
75
# B (--o--x--o--x--?---
77
# A (--o-----o--x--?--x
79
# B (--o--x--o-----?---
81
# A (--o-----o--x--?---
83
# B (--o--x--o-----?--x
85
# Merge with cherry-picks
86
# (This set of six cases represents all of the topologically distinct
87
# scenarios involving one cherry-pick between two automatic merges.)
90
# A (--o-----o-[o]----o---
92
# B (--o--x--?-----c-----x
95
# A (--o-----?-----c--o---
97
# B (--o--x--o-[o]-------x
100
# A (--o-----?-------c--o----
103
# B (--o--o-[o]-x-/---------x
107
# A (--o-----o-[o]-------x
109
# B (--o--x--?-----c--o---
112
# A (--o-----?-----c-----x
114
# B (--o--x--o-[o]----o---
117
# A (--o-----?-------c------x
120
# B (--o--o-[o]-x-/-----o----
125
# A (--o--?--x--?----
129
# B (--o--?--x--?---x
134
# A (--o-o-o-o---------x
139
# merge to, reverse cherry subtree to, merge to
140
# A (--o-o-o-o------------------
143
# B ( o--o------x-------rcs----x
156
# o - an original change
157
# ? - an original change or no-op (test both)
158
# x - a branch root merge
159
# c - a cherry-pick merge
160
# [o] - source range of a cherry-pick merge
161
# s - a subtree merge
165
########################################################################
167
def assert_equal(a, b):
168
"""Assert that two generic Python objects are equal. If not, raise an
169
exception giving their values. Rationale: During debugging, it's
170
easier to see what's wrong if we see the values rather than just
171
an indication that the assertion failed."""
173
raise Exception("assert_equal failed: a = (%s), b = (%s)" % (a, b))
175
def logical_changes_in_branch(sbox, branch):
176
"""Return the set of logical changes that are actually in branch BRANCH
177
(at its current working version), by examining the state of the
178
branch files and directories rather than its mergeinfo.
180
Each logical change is described by its branch and revision number
181
as a string such as 'A1'."""
183
for propname in sbox.simple_proplist(branch + '/D').keys():
184
if propname.startswith('prop-'):
185
changes.add(propname[5:])
188
def get_mergeinfo_change(sbox, target):
189
"""Return a list of revision numbers representing the mergeinfo change
190
on TARGET (working version against base). Non-recursive."""
191
exit, out, err = actions.run_and_verify_svn(None, None, [],
192
'diff', '--depth=empty',
196
match = re.match(r' Merged /(\w+):r(.*)', line)
198
for r_range in match.group(2).split(','):
200
r_start, r_end = r_range.split('-')
202
r_start = r_end = r_range
203
merged_revs += range(int(r_start), int(r_end) + 1)
206
def get_3ways_from_output(output):
207
"""Scan the list of lines OUTPUT for indications of 3-way merges.
208
Return a list of (base, source-right) tuples."""
209
### Problem: test suite strips debugging output within run_and_verify_...()
210
### so we don't see it here. And relying on debug output is a temporary
211
### measure only. Better to access svn_client_find_automatic_merge()
212
### directly, via bindings?
217
# Extract "A1" from a line like "DBG: merge.c:11336: base svn://.../A@1"
218
match = re.search(r'merge\.c:.* base .* /(\w+)@([0-9-]+)', line)
220
base = match.group(1) + match.group(2)
221
match = re.search(r'merge\.c:.* right.* /(\w+)@([0-9-]+)', line)
223
right = match.group(1) + match.group(2)
224
assert base is not None
225
merges.append((base, right))
229
def make_branches(sbox):
230
"""Make branches A and B."""
232
sbox.simple_copy('A', 'B')
234
os.chdir(sbox.wc_dir)
237
def modify_branch(sbox, branch, number, conflicting=False):
238
"""Commit a modification to branch BRANCH. The actual modification depends
239
on NUMBER. If CONFLICTING=True, the change will be of a kind that
240
conflicts with any other change that has CONFLICTING=True. We don't
241
modify (properties on) the branch root node itself, to make it easier
242
for the tests to distinguish mergeinfo changes from these mods."""
243
uniq = branch + str(number) # something like 'A1' or 'B2'
245
sbox.simple_propset('conflict', uniq, branch + '/C')
247
# Make some changes. We add a property, which we will read later in
248
# logical_changes_in_branch() to check that the correct logical
249
# changes were merged. We add a file, so that we will notice if
250
# Subversion tries to merge this same logical change into a branch
251
# that already has it (it will raise a tree conflict).
252
sbox.simple_propset('prop-' + uniq, uniq, branch + '/D')
253
sbox.simple_copy(branch + '/mu', branch + '/mu-' + uniq)
256
def expected_automatic_merge_output(target, expect_3ways):
257
"""Calculate the expected output."""
259
# (This is rather specific to the current implementation.)
261
# Match a notification for each rev-range.
264
for base, right in expect_3ways:
265
if base[0] == right[0]:
266
base_rev = int(base[1:])
267
right_rev = int(right[1:])
268
rev_ranges += [(base_rev + 1, right_rev)];
272
# Match any content modifications; but not of the root of the branch
273
# because we don't intentionally modify the branch root node in most
274
# tests and we don't want to accidentally overlook a mergeinfo change.
275
lines = ["(A |D |[UG] | [UG]|[UG][UG]) " + target + os.path.sep + ".*\n"]
277
# Match mergeinfo changes. (### Subtrees are not yet supported here.)
278
lines += [" [UG] " + target + "\n"]
280
# At the moment, the automatic merge code sometimes says 'Merging
281
# differences between repository URLs' and sometimes 'Merging r3 through
282
# r5', but it's not trivial to predict which, so expect either form.
283
lines += ["--- Merging .* into '%s':\n" % (target,),
284
"--- Recording mergeinfo for merge .* into '%s':\n" % (target,)]
286
return expected_merge_output(rev_ranges, lines, target=target)
288
def automatic_merge(sbox, source, target, args=[],
289
expect_changes=None, expect_mi=None, expect_3ways=None):
290
"""Do a complete, automatic merge from path SOURCE to path TARGET, and
291
commit. Verify the output and that there is no error.
292
### TODO: Verify the changes made.
294
ARGS are additional arguments passed to svn merge."""
296
source = local_path(source)
297
target = local_path(target)
299
# First, update the WC target because mixed-rev is not fully supported.
300
sbox.simple_update(target)
302
before_changes = logical_changes_in_branch(sbox, target)
304
exp_out = expected_automatic_merge_output(target, expect_3ways)
305
exit, out, err = svntest.actions.run_and_verify_svn(None, exp_out, [],
307
'^/' + source, target,
310
if expect_changes is not None:
311
after_changes = logical_changes_in_branch(sbox, target)
312
merged_changes = after_changes - before_changes
313
assert_equal(merged_changes, set(expect_changes))
314
reversed_changes = before_changes - after_changes
315
assert_equal(reversed_changes, set())
317
if expect_mi is not None:
318
actual_mi_change = get_mergeinfo_change(sbox, target)
319
assert_equal(actual_mi_change, expect_mi)
321
if expect_3ways is not None:
322
### actual_3ways = get_3ways_from_output(out)
323
### assert_equal(actual_3ways, expect_3ways)
328
def three_way_merge(base_node, source_right_node):
329
return (base_node, source_right_node)
331
def three_way_merge_no_op(base_node, source_right_node):
332
return (base_node, source_right_node)
334
def cherry_pick(sbox, rev, source, target):
335
"""Cherry-pick merge revision REV from branch SOURCE to branch TARGET
336
(both WC-relative paths), and commit."""
337
sbox.simple_update(target)
338
svn_merge(rev, source, target)
342
def no_op_commit(sbox):
343
"""Commit a new revision that does not affect the branches under test."""
345
global no_op_commit__n
346
sbox.simple_propset('foo', str(no_op_commit__n), 'iota')
348
sbox.simple_commit('iota')
351
#----------------------------------------------------------------------
353
def init_mod_merge_mod(sbox, mod_6, mod_7):
354
"""Modify both branches, merge A -> B, optionally modify again.
355
MOD_6 is True to modify A in r6, MOD_7 is True to modify B in r7,
356
otherwise make no-op commits for r6 and/or r7."""
364
modify_branch(sbox, 'A', 3)
365
modify_branch(sbox, 'B', 4)
367
automatic_merge(sbox, 'A', 'B',
368
expect_changes=['A3'],
370
expect_3ways=[three_way_merge('A1', 'A4')])
373
modify_branch(sbox, 'A', 6)
375
no_op_commit(sbox) # r6
378
modify_branch(sbox, 'B', 7)
380
no_op_commit(sbox) # r7
382
########################################################################
386
@SkipUnless(server_has_mergeinfo)
387
def merge_once_1(sbox):
396
no_op_commit(sbox) # r3
397
no_op_commit(sbox) # r4
399
automatic_merge(sbox, 'A', 'B',
402
expect_3ways=[three_way_merge_no_op('A1', 'A4')])
404
@SkipUnless(server_has_mergeinfo)
405
def merge_once_2(sbox):
414
modify_branch(sbox, 'A', 3)
415
no_op_commit(sbox) # r4
417
automatic_merge(sbox, 'A', 'B',
418
expect_changes=['A3'],
420
expect_3ways=[three_way_merge('A1', 'A4')])
422
@SkipUnless(server_has_mergeinfo)
423
def merge_once_3(sbox):
432
no_op_commit(sbox) # r3
433
modify_branch(sbox, 'B', 4)
435
automatic_merge(sbox, 'A', 'B',
438
expect_3ways=[three_way_merge_no_op('A1', 'A4')])
440
@SkipUnless(server_has_mergeinfo)
441
def merge_once_4(sbox):
450
modify_branch(sbox, 'A', 3)
451
modify_branch(sbox, 'B', 4)
453
automatic_merge(sbox, 'A', 'B',
454
expect_changes=['A3'],
456
expect_3ways=[three_way_merge('A1', 'A4')])
458
#----------------------------------------------------------------------
460
# Merge twice in same direction
462
@SkipUnless(server_has_mergeinfo)
463
def merge_twice_same_direction_1(sbox):
464
"""merge_twice_same_direction_1"""
471
init_mod_merge_mod(sbox, mod_6=False, mod_7=False)
473
automatic_merge(sbox, 'A', 'B',
476
expect_3ways=[three_way_merge_no_op('A4', 'A7')])
478
@SkipUnless(server_has_mergeinfo)
479
def merge_twice_same_direction_2(sbox):
480
"""merge_twice_same_direction_2"""
487
init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
489
automatic_merge(sbox, 'A', 'B',
490
expect_changes=['A6'],
492
expect_3ways=[three_way_merge('A4', 'A7')])
494
#----------------------------------------------------------------------
498
@SkipUnless(server_has_mergeinfo)
499
def merge_to_and_fro_1_1(sbox):
500
"""merge_to_and_fro_1_1"""
507
init_mod_merge_mod(sbox, mod_6=False, mod_7=False)
509
automatic_merge(sbox, 'B', 'A',
510
expect_changes=['B4'],
511
expect_mi=[2, 3, 4, 5, 6, 7],
512
expect_3ways=[three_way_merge('A4', 'B7')])
514
@SkipUnless(server_has_mergeinfo)
515
def merge_to_and_fro_1_2(sbox):
516
"""merge_to_and_fro_1_2"""
523
init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
525
automatic_merge(sbox, 'B', 'A',
526
expect_changes=['B4', 'B7'],
527
expect_mi=[2, 3, 4, 5, 6, 7],
528
expect_3ways=[three_way_merge('A4', 'B7')])
530
def init_merge_to_and_fro_2(sbox, mod_9, mod_10):
531
"""Set up branches A and B for the merge_to_and_fro_2 scenarios.
532
MOD_9 is True to modify A in r9, MOD_10 is True to modify B in r10,
533
otherwise make no-op commits for r9 and/or r10."""
535
# A (--o------o------?-
537
# B (---o--x---o--x---?
540
init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
542
automatic_merge(sbox, 'A', 'B',
543
expect_changes=['A6'],
545
expect_3ways=[three_way_merge('A4', 'A7')])
548
modify_branch(sbox, 'A', 9)
550
no_op_commit(sbox) # r9
553
modify_branch(sbox, 'B', 10)
555
no_op_commit(sbox) # r10
557
@SkipUnless(server_has_mergeinfo)
558
def merge_to_and_fro_2_1(sbox):
559
"""merge_to_and_fro_2_1"""
561
# A (--o------o----------x
563
# B (---o--x---o--x-------
566
init_merge_to_and_fro_2(sbox, mod_9=False, mod_10=False)
568
automatic_merge(sbox, 'B', 'A',
569
expect_changes=['B4', 'B7'],
570
expect_mi=[2, 3, 4, 5, 6, 7, 8, 9, 10],
571
expect_3ways=[three_way_merge('A7', 'B10')])
573
@SkipUnless(server_has_mergeinfo)
574
def merge_to_and_fro_2_2(sbox):
575
"""merge_to_and_fro_2_2"""
577
# A (--o------o------o---x
579
# B (---o--x---o--x---o---
582
init_merge_to_and_fro_2(sbox, mod_9=True, mod_10=True)
584
automatic_merge(sbox, 'B', 'A',
585
expect_changes=['B4', 'B7', 'B10'],
586
expect_mi=[2, 3, 4, 5, 6, 7, 8, 9, 10],
587
expect_3ways=[three_way_merge('A7', 'B10')])
589
def init_merge_to_and_fro_3(sbox, mod_9, mod_10):
590
"""Set up branches A and B for the merge_to_and_fro_3/4 scenarios.
591
MOD_9 is True to modify A in r9, MOD_10 is True to modify B in r10,
592
otherwise make no-op commits for r9 and/or r10."""
594
# A (--o------o---x--?-
596
# B (---o--x---o------?
599
init_mod_merge_mod(sbox, mod_6=True, mod_7=True)
601
automatic_merge(sbox, 'B', 'A',
602
expect_changes=['B4', 'B7'],
603
expect_mi=[2, 3, 4, 5, 6, 7],
604
expect_3ways=[three_way_merge('A4', 'B7')])
607
modify_branch(sbox, 'A', 9)
609
no_op_commit(sbox) # r9
612
modify_branch(sbox, 'B', 10)
614
no_op_commit(sbox) # r10
616
@SkipUnless(server_has_mergeinfo)
617
def merge_to_and_fro_3_1(sbox):
618
"""merge_to_and_fro_3_1"""
620
# A (--o------o---x------x
622
# B (---o--x---o----------
625
init_merge_to_and_fro_3(sbox, mod_9=False, mod_10=False)
627
automatic_merge(sbox, 'B', 'A',
629
expect_mi=[8, 9, 10],
630
expect_3ways=[three_way_merge_no_op('B7', 'B10')])
632
@SkipUnless(server_has_mergeinfo)
633
def merge_to_and_fro_3_2(sbox):
634
"""merge_to_and_fro_3_2"""
636
# A (--o------o---x--o---x
638
# B (---o--x---o------o---
641
init_merge_to_and_fro_3(sbox, mod_9=True, mod_10=True)
643
automatic_merge(sbox, 'B', 'A',
644
expect_changes=['B10'],
645
expect_mi=[8, 9, 10],
646
expect_3ways=[three_way_merge('B7', 'B10')])
648
@SkipUnless(server_has_mergeinfo)
649
def merge_to_and_fro_4_1(sbox):
650
"""merge_to_and_fro_4_1"""
652
# A (--o------o---x-------
654
# B (---o--x---o---------x
657
init_merge_to_and_fro_3(sbox, mod_9=False, mod_10=False)
659
automatic_merge(sbox, 'A', 'B',
660
expect_changes=['A6'],
661
expect_mi=[5, 6, 7, 8, 9, 10],
662
expect_3ways=[three_way_merge_no_op('B7', 'A10')])
664
@SkipUnless(server_has_mergeinfo)
665
def merge_to_and_fro_4_2(sbox):
666
"""merge_to_and_fro_4_2"""
668
# A (--o------o---x--o----
670
# B (---o--x---o------o--x
673
init_merge_to_and_fro_3(sbox, mod_9=True, mod_10=True)
675
automatic_merge(sbox, 'A', 'B',
676
expect_changes=['A6', 'A9'],
677
expect_mi=[5, 6, 7, 8, 9, 10],
678
expect_3ways=[three_way_merge('B7', 'A10')])
680
#----------------------------------------------------------------------
682
# Cherry-pick scenarios
684
@SkipUnless(server_has_mergeinfo)
685
def cherry1_fwd(sbox):
688
# A (--o------o--[o]----o---
690
# B (---o--x---------c-----x
693
init_mod_merge_mod(sbox, mod_6=True, mod_7=False)
694
modify_branch(sbox, 'A', 8)
695
cherry_pick(sbox, 8, 'A', 'B')
696
modify_branch(sbox, 'A', 10)
698
automatic_merge(sbox, 'A', 'B',
699
expect_changes=['A6', 'A10'], # and NOT A8
700
expect_mi=[5, 6, 7, 9, 10],
701
expect_3ways=[three_way_merge('A4', 'A7'),
702
three_way_merge('A8', 'A10')])
704
@SkipUnless(server_has_mergeinfo)
707
def cherry2_fwd(sbox):
710
# A (--o-------------c--o---
712
# B (---o--x---o-[o]-------x
715
init_mod_merge_mod(sbox, mod_6=False, mod_7=True)
716
modify_branch(sbox, 'B', 8)
717
cherry_pick(sbox, 8, 'B', 'A')
718
modify_branch(sbox, 'A', 10)
720
automatic_merge(sbox, 'A', 'B',
721
expect_changes=['A10'], # and NOT A9
722
expect_mi=[5, 6, 7, 8, 9, 10],
723
expect_3ways=[three_way_merge('A9', 'A10')])
725
@SkipUnless(server_has_mergeinfo)
728
def cherry3_fwd(sbox):
731
# A (--o--------------c--o----
734
# B (---o--o-[o]-x-/---------x
739
modify_branch(sbox, 'A', 3)
740
modify_branch(sbox, 'B', 4)
741
modify_branch(sbox, 'B', 5)
742
modify_branch(sbox, 'B', 6)
744
automatic_merge(sbox, 'A', 'B',
745
expect_changes=['A3'],
746
expect_mi=[2, 3, 4, 5, 6],
747
expect_3ways=[three_way_merge('A1', 'A6')])
749
cherry_pick(sbox, 6, 'B', 'A')
750
modify_branch(sbox, 'A', 9)
752
automatic_merge(sbox, 'A', 'B',
753
expect_changes=['A9'], # and NOT A8
755
expect_3ways=[three_way_merge('A8', 'A9')])
757
#----------------------------------------------------------------------
758
# Automatic merges ignore subtree mergeinfo during reintegrate.
759
@SkipUnless(server_has_mergeinfo)
761
def subtree_to_and_fro(sbox):
762
"reintegrate considers source subtree mergeinfo"
764
# A (-----o-o-o-o------------x
767
# A_COPY ( o---------o--s--o--
770
# Some paths we'll care about.
771
A_COPY_gamma_path = sbox.ospath('A_COPY/D/gamma')
772
psi_path = sbox.ospath('A/D/H/psi')
773
A_COPY_D_path = sbox.ospath('A_COPY/D')
774
A_path = sbox.ospath('A')
779
# Setup a simple 'trunk & branch': Copy ^/A to ^/A_COPY in r2 and then
780
# make a few edits under A in r3-6 (edits r3, r4, r6 are under subtree 'D'):
781
wc_disk, wc_status = set_up_branch(sbox)
783
# r7 - Edit a file on the branch.
784
svntest.main.file_write(A_COPY_gamma_path, "Branch edit to 'gamma'.\n")
785
svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir,
786
'-m', 'Edit a file on our branch')
788
# r8 - Do a subtree sync merge from ^/A/D to A_COPY/D.
789
# Note that among other things this changes A_COPY/D/H/psi.
790
svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
791
svntest.actions.run_and_verify_svn(None, None, [], 'merge',
792
sbox.repo_url + '/A/D', A_COPY_D_path)
793
svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir,
794
'-m', 'Automatic subtree merge')
796
# r9 - Make an edit to A/D/H/psi.
797
svntest.main.file_write(psi_path, "Trunk Edit to 'psi'.\n")
798
svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir,
799
'-m', 'Edit a file on our trunk')
801
# Now reintegrate ^/A_COPY back to A. Prior to issue #4258's fix, the
802
# the subtree merge to A_COPY/D just looks like any other branch edit and
803
# was not considered a merge. So the changes which exist on A/D and were
804
# merged to A_COPY/D, were merged *back* to A, resulting in a conflict:
806
# C:\...\working_copies\merge_automatic_tests-18>svn merge ^^/A_COPY A
807
# DBG: merge.c:11461: base on source: ^/A@1
808
# DBG: merge.c:11462: base on target: ^/A@1
809
# DBG: merge.c:11567: yca ^/A@1
810
# DBG: merge.c:11568: base ^/A@1
811
# DBG: merge.c:11571: right ^/A_COPY@8
812
# Conflict discovered in file 'A\D\H\psi'.
813
# Select: (p) postpone, (df) diff-full, (e) edit,
814
# (mc) mine-conflict, (tc) theirs-conflict,
815
# (s) show all options: p
816
# --- Merging r2 through r9 into 'A':
819
# --- Recording mergeinfo for merge of r2 through r9 into 'A':
821
# Summary of conflicts:
823
svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
824
exit_code, out, err = svntest.actions.run_and_verify_svn(
825
None, [], svntest.verify.AnyOutput,
826
'merge', sbox.repo_url + '/A_COPY', A_path)
828
# Better to produce the same warning that explicitly using the
829
# --reintegrate option would produce:
830
svntest.verify.verify_outputs("Automatic Reintegrate failed, but not "
831
"in the way expected",
833
"(svn: E195016: Reintegrate can only be used if "
834
"revisions 2 through 9 were previously "
835
"merged from .*/A to the reintegrate source, "
836
"but this is not the case:\n)"
838
"|( Missing ranges: /A:5\n)"
840
"|" + svntest.main.stack_trace_regexp,
842
True) # Match *all* lines of stdout
844
#----------------------------------------------------------------------
845
# Automatic merges ignore subtree mergeinfo gaps older than the last rev
846
# synced to the target root.
847
@SkipUnless(server_has_mergeinfo)
848
def merge_to_reverse_cherry_subtree_to_merge_to(sbox):
849
"sync merge considers target subtree mergeinfo"
851
# A (--o-o-o-o------------------
854
# B ( o--o------x-------rc-----x
856
# Some paths we'll care about.
857
A_COPY_path = sbox.ospath('A_COPY')
858
A_COPY_B_path = sbox.ospath('A_COPY/B')
859
A_COPY_beta_path = sbox.ospath('A_COPY/B/E/beta')
864
# Setup a simple 'trunk & branch': Copy ^/A to ^/A_COPY in r2 and then
865
# make a few edits under A in r3-6:
866
wc_disk, wc_status = set_up_branch(sbox)
868
# Sync merge ^/A to A_COPY, then reverse merge r5 from ^/A/B to A_COPY/B.
869
# This results in mergeinfo on the target which makes it appear that the
870
# branch is synced up to r6, but the subtree mergeinfo on A_COPY/B reveals
871
# that r5 has not been merged to that subtree:
873
# Properties on 'A_COPY':
876
# Properties on 'A_COPY\B':
879
svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
880
svntest.actions.run_and_verify_svn(None, None, [], 'merge',
881
sbox.repo_url + '/A', A_COPY_path)
882
svntest.actions.run_and_verify_svn(None, None, [], 'merge', '-c-5',
883
sbox.repo_url + '/A/B',
885
svntest.actions.run_and_verify_svn(None, None, [], 'ci', wc_dir, '-m',
886
'sync merge and reverse subtree merge')
888
# Try an automatic sync merge from ^/A to A_COPY. Revision 5 should be
889
# merged to A_COPY/B as its subtree mergeinfo reveals that rev is missing,
892
# >svn merge ^/A A_COPY
893
# --- Merging r5 into 'A_COPY\B':
895
# --- Recording mergeinfo for merge of r5 through r7 into 'A_COPY':
897
# --- Recording mergeinfo for merge of r5 through r7 into 'A_COPY\B':
899
# --- Eliding mergeinfo from 'A_COPY\B':
902
# But the merge ignores the subtree mergeinfo and considers
903
# only the mergeinfo on the target itself (and thus is a no-op but for
904
# the mergeinfo change on the root of the merge target):
906
# >svn merge ^/A A_COPY
907
# --- Recording mergeinfo for merge of r7 into 'A_COPY':
912
# ===================================================================
913
# --- A_COPY (revision 7)
914
# +++ A_COPY (working copy)
916
# Property changes on: A_COPY
917
# ___________________________________________________________________
918
# Modified: svn:mergeinfo
920
svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir)
921
expected_output = wc.State(A_COPY_path, {
922
'B/E/beta' : Item(status='U '),
924
expected_mergeinfo_output = wc.State(A_COPY_path, {
925
'' : Item(status=' U'),
926
'B' : Item(status=' U'),
928
expected_elision_output = wc.State(A_COPY_path, {
929
'B' : Item(status=' U'),
931
expected_status = wc.State(A_COPY_path, {
932
'' : Item(status=' M'),
933
'B' : Item(status=' M'),
934
'mu' : Item(status=' '),
935
'B/E' : Item(status=' '),
936
'B/E/alpha' : Item(status=' '),
937
'B/E/beta' : Item(status='M '),
938
'B/lambda' : Item(status=' '),
939
'B/F' : Item(status=' '),
940
'C' : Item(status=' '),
941
'D' : Item(status=' '),
942
'D/G' : Item(status=' '),
943
'D/G/pi' : Item(status=' '),
944
'D/G/rho' : Item(status=' '),
945
'D/G/tau' : Item(status=' '),
946
'D/gamma' : Item(status=' '),
947
'D/H' : Item(status=' '),
948
'D/H/chi' : Item(status=' '),
949
'D/H/psi' : Item(status=' '),
950
'D/H/omega' : Item(status=' '),
952
expected_status.tweak(wc_rev='7')
953
expected_disk = wc.State('', {
954
'' : Item(props={SVN_PROP_MERGEINFO : '/A:2-7'}),
956
'mu' : Item("This is the file 'mu'.\n"),
958
'B/E/alpha' : Item("This is the file 'alpha'.\n"),
959
'B/E/beta' : Item("New content"),
960
'B/lambda' : Item("This is the file 'lambda'.\n"),
965
'D/G/pi' : Item("This is the file 'pi'.\n"),
966
'D/G/rho' : Item("New content"),
967
'D/G/tau' : Item("This is the file 'tau'.\n"),
968
'D/gamma' : Item("This is the file 'gamma'.\n"),
970
'D/H/chi' : Item("This is the file 'chi'.\n"),
971
'D/H/psi' : Item("New content"),
972
'D/H/omega' : Item("New content"),
974
expected_skip = wc.State(A_COPY_path, { })
975
svntest.actions.run_and_verify_merge(A_COPY_path, None, None,
976
sbox.repo_url + '/A', None,
978
expected_mergeinfo_output,
979
expected_elision_output,
983
None, None, None, None,
984
None, 1, 0, A_COPY_path)
986
#----------------------------------------------------------------------
987
# Automatic merges should notice ancestory for replaced files
988
@SkipUnless(server_has_mergeinfo)
989
def merge_replacement(sbox):
990
"notice ancestory for replaced files"
992
A_path = sbox.ospath('A')
993
A_COPY_path = sbox.ospath('A_copy')
994
A_COPY_mu_path = sbox.ospath('A_copy/mu')
998
sbox.simple_copy('A', 'A_copy')
1000
sbox.simple_commit()
1002
sbox.simple_rm('A_copy/B/lambda')
1003
sbox.simple_copy('A_copy/D/gamma', 'A_copy/B/lambda')
1005
sbox.simple_rm('A_copy/mu')
1006
svntest.main.file_write(A_COPY_mu_path, "Branch edit to 'mu'.\n")
1007
sbox.simple_add('A_copy/mu')
1010
sbox.simple_commit()
1012
expected_output = wc.State(A_path, {
1013
'B/lambda' : Item(status='R '),
1014
'mu' : Item(status='R '),
1016
expected_mergeinfo_output = wc.State(A_path, {
1017
'' : Item(status=' U'),
1019
expected_elision_output = wc.State(A_path, {
1022
expected_status = wc.State(A_path, {
1023
'' : Item(status=' M', wc_rev='1'),
1024
'B' : Item(status=' ', wc_rev='1'),
1025
'mu' : Item(status='R ', copied='+', wc_rev='-'),
1026
'B/E' : Item(status=' ', wc_rev='1'),
1027
'B/E/alpha' : Item(status=' ', wc_rev='1'),
1028
'B/E/beta' : Item(status=' ', wc_rev='1'),
1029
'B/lambda' : Item(status='R ', copied='+', wc_rev='-'),
1030
'B/F' : Item(status=' ', wc_rev='1'),
1031
'C' : Item(status=' ', wc_rev='1'),
1032
'D' : Item(status=' ', wc_rev='1'),
1033
'D/G' : Item(status=' ', wc_rev='1'),
1034
'D/G/pi' : Item(status=' ', wc_rev='1'),
1035
'D/G/rho' : Item(status=' ', wc_rev='1'),
1036
'D/G/tau' : Item(status=' ', wc_rev='1'),
1037
'D/gamma' : Item(status=' ', wc_rev='1'),
1038
'D/H' : Item(status=' ', wc_rev='1'),
1039
'D/H/chi' : Item(status=' ', wc_rev='1'),
1040
'D/H/psi' : Item(status=' ', wc_rev='1'),
1041
'D/H/omega' : Item(status=' ', wc_rev='1'),
1044
expected_disk = wc.State('', {
1045
'' : Item(props={SVN_PROP_MERGEINFO : '/A_copy:2-3'}),
1047
'mu' : Item("Branch edit to 'mu'.\n"),
1049
'B/E/alpha' : Item("This is the file 'alpha'.\n"),
1050
'B/E/beta' : Item("This is the file 'beta'.\n"),
1051
'B/lambda' : Item("This is the file 'gamma'.\n"),
1056
'D/G/pi' : Item("This is the file 'pi'.\n"),
1057
'D/G/rho' : Item("This is the file 'rho'.\n"),
1058
'D/G/tau' : Item("This is the file 'tau'.\n"),
1059
'D/gamma' : Item("This is the file 'gamma'.\n"),
1061
'D/H/chi' : Item("This is the file 'chi'.\n"),
1062
'D/H/psi' : Item("This is the file 'psi'.\n"),
1063
'D/H/omega' : Item("This is the file 'omega'.\n"),
1066
expected_skip = wc.State(A_COPY_path, { })
1068
svntest.actions.run_and_verify_merge(A_path, None, None,
1069
sbox.repo_url + '/A_copy', None,
1071
expected_mergeinfo_output,
1072
expected_elision_output,
1076
None, None, None, None,
1079
@SkipUnless(server_has_mergeinfo)
1082
# Test for issue #4313 'replaced merges source causes assertion during
1084
def auto_merge_handles_replacements_in_merge_source(sbox):
1085
"automerge handles replacements in merge source"
1089
A_path = sbox.ospath('A')
1090
branch1_path = sbox.ospath('branch-1')
1091
branch2_path = sbox.ospath('branch-2')
1093
# r2 - Make two branches.
1094
sbox.simple_copy('A', 'branch-1')
1095
sbox.simple_copy('A', 'branch-2')
1096
sbox.simple_commit()
1097
sbox.simple_update()
1099
# r3 - Replace 'A' with 'branch-1'.
1100
svntest.main.run_svn(None, 'del', A_path)
1101
svntest.main.run_svn(None, 'copy', branch1_path, A_path)
1102
sbox.simple_commit()
1103
sbox.simple_update()
1105
# Merge^/A to branch-2, it should be a no-op but for mergeinfo changes,
1106
# but it *should* work. Previously this failed because automatic merges
1107
# weren't adhering to the merge source normalization rules, resulting in
1110
# >svn merge ^/A branch-2
1111
# ..\..\..\subversion\libsvn_client\merge.c:4568: (apr_err=235000)
1112
# svn: E235000: In file '..\..\..\subversion\libsvn_client\merge.c'
1113
# line 4568: assertion failed (apr_hash_count(implicit_src_mergeinfo)
1116
# This application has requested the Runtime to terminate it in an
1118
# Please contact the application's support team for more information.
1119
svntest.actions.run_and_verify_svn(
1121
["--- Recording mergeinfo for merge of r2 into '" + branch2_path + "':\n",
1122
" U " + branch2_path + "\n",
1123
"--- Recording mergeinfo for merge of r3 into '" + branch2_path + "':\n",
1124
" G " + branch2_path + "\n"],
1125
[], 'merge', sbox.repo_url + '/A', branch2_path)
1127
# Test for issue #4329 'automatic merge uses reintegrate type merge if
1128
# source is fully synced'
1129
@SkipUnless(server_has_mergeinfo)
1131
def effective_sync_results_in_reintegrate(sbox):
1132
"an effectively synced branch gets reintegrated"
1136
iota_path = sbox.ospath('iota')
1137
A_path = sbox.ospath('A')
1138
psi_path = sbox.ospath('A/D/H/psi')
1139
mu_path = sbox.ospath('A/mu')
1140
branch_path = sbox.ospath('branch')
1141
psi_branch_path = sbox.ospath('branch/D/H/psi')
1143
# r2 - Make a branch.
1144
sbox.simple_copy('A', 'branch')
1145
sbox.simple_commit()
1147
# r3 - An edit to a file on the trunk.
1148
sbox.simple_append('A/mu', "Trunk edit to 'mu'\n", True)
1149
sbox.simple_commit()
1151
# r4 - An edit to a file on the branch
1152
sbox.simple_append('branch/D/H/psi', "Branch edit to 'psi'\n", True)
1153
sbox.simple_commit()
1155
# r5 - Effectively sync all changes on trunk to the branch. We do this
1156
# not via an automatic sync merge, but with a cherry pick that effectively
1157
# merges the same changes (i.e. r3).
1158
sbox.simple_update()
1159
cherry_pick(sbox, 3, A_path, branch_path)
1161
# r6 - Make another edit to the file on the trunk.
1162
sbox.simple_append('A/mu', "2nd trunk edit to 'mu'\n", True)
1163
sbox.simple_commit()
1165
# Now try an explicit --reintegrate merge from ^/branch to A.
1166
# This should work because since the resolution of
1167
# http://subversion.tigris.org/issues/show_bug.cgi?id=3577
1168
# if B is *effectively* synced with A, then B can be reintegrated
1170
sbox.simple_update()
1172
"--- Merging differences between repository URLs into '" +
1174
"U " + psi_path + "\n",
1175
"--- Recording mergeinfo for merge between repository URLs into '" +
1177
" U " + A_path + "\n"]
1178
svntest.actions.run_and_verify_svn(None, expected_output, [], 'merge',
1179
sbox.repo_url + '/branch', A_path,
1182
# Revert the merge and try it again, this time without the --reintegrate
1183
# option. The merge should still work with the same results.
1185
# Previously this failed because the reintegrate code path is not followed,
1186
# rather the automatic merge attempts a sync style merge of the yca (^/A@1)
1187
# through the HEAD of the branch (^/branch@7). This results in a spurious
1188
# conflict on A/mu as the edit made in r3 is reapplied.
1190
# >svn merge ^/branch A
1191
# --- Merging r2 through r6 into 'A':
1194
# --- Recording mergeinfo for merge of r2 through r6 into 'A':
1196
# Summary of conflicts:
1198
# Conflict discovered in file 'A\mu'.
1199
# Select: (p) postpone, (df) diff-full, (e) edit, (m) merge,
1200
# (mc) mine-conflict, (tc) theirs-conflict, (s) show all options: p
1201
svntest.actions.run_and_verify_svn(None, None, [], 'revert', A_path, '-R')
1202
svntest.actions.run_and_verify_svn(None, expected_output, [], 'merge',
1203
sbox.repo_url + '/branch', A_path)
1205
########################################################################
1209
# list all tests here, starting with None:
1215
merge_twice_same_direction_1,
1216
merge_twice_same_direction_2,
1217
merge_to_and_fro_1_1,
1218
merge_to_and_fro_1_2,
1219
merge_to_and_fro_2_1,
1220
merge_to_and_fro_2_2,
1221
merge_to_and_fro_3_1,
1222
merge_to_and_fro_3_2,
1223
merge_to_and_fro_4_1,
1224
merge_to_and_fro_4_2,
1229
merge_to_reverse_cherry_subtree_to_merge_to,
1231
auto_merge_handles_replacements_in_merge_source,
1232
effective_sync_results_in_reintegrate,
1235
if __name__ == '__main__':
1236
svntest.main.run_tests(test_list)