1#!/usr/bin/env python
2#
3#  mergeinfo_tests.py:  testing Merge Tracking reporting
4#
5#  Subversion is a tool for revision control.
6#  See http://subversion.apache.org for more information.
7#
8# ====================================================================
9#    Licensed to the Apache Software Foundation (ASF) under one
10#    or more contributor license agreements.  See the NOTICE file
11#    distributed with this work for additional information
12#    regarding copyright ownership.  The ASF licenses this file
13#    to you under the Apache License, Version 2.0 (the
14#    "License"); you may not use this file except in compliance
15#    with the License.  You may obtain a copy of the License at
16#
17#      http://www.apache.org/licenses/LICENSE-2.0
18#
19#    Unless required by applicable law or agreed to in writing,
20#    software distributed under the License is distributed on an
21#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22#    KIND, either express or implied.  See the License for the
23#    specific language governing permissions and limitations
24#    under the License.
25######################################################################
26
27# General modules
28import shutil, sys, re, os
29
30# Our testing module
31import svntest
32from svntest import wc
33
34# (abbreviation)
35Item = wc.StateItem
36Skip = svntest.testcase.Skip_deco
37SkipUnless = svntest.testcase.SkipUnless_deco
38XFail = svntest.testcase.XFail_deco
39Issues = svntest.testcase.Issues_deco
40Issue = svntest.testcase.Issue_deco
41Wimp = svntest.testcase.Wimp_deco
42exp_noop_up_out = svntest.actions.expected_noop_update_output
43
44from svntest.main import SVN_PROP_MERGEINFO
45from svntest.main import server_has_mergeinfo
46
47# Get a couple merge helpers
48from svntest.mergetrees import set_up_branch
49from svntest.mergetrees import expected_merge_output
50
51def adjust_error_for_server_version(expected_err):
52  "Return the expected error regexp appropriate for the server version."
53  if server_has_mergeinfo():
54    return expected_err
55  else:
56    return ".*Retrieval of mergeinfo unsupported by '.+'"
57
58######################################################################
59# Tests
60#
61#   Each test must return on success or raise on failure.
62
63
64#----------------------------------------------------------------------
65
66def no_mergeinfo(sbox):
67  "'mergeinfo' on a URL that lacks mergeinfo"
68
69  sbox.build(create_wc=False)
70  sbox.simple_repo_copy('A', 'A2')
71  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
72                                           [],
73                                           sbox.repo_url + '/A',
74                                           sbox.repo_url + '/A2',
75                                           "--show-revs=merged")
76
77@SkipUnless(server_has_mergeinfo)
78def mergeinfo(sbox):
79  "'mergeinfo' on a path with mergeinfo"
80
81  sbox.build()
82  wc_dir = sbox.wc_dir
83
84  # make a branch 'A2'
85  sbox.simple_repo_copy('A', 'A2')  # r2
86  # make a change in branch 'A'
87  sbox.simple_mkdir('A/newdir')
88  sbox.simple_commit()  # r3
89  sbox.simple_update()
90
91  # Dummy up some mergeinfo.
92  svntest.actions.run_and_verify_svn(None, [],
93                                     'ps', SVN_PROP_MERGEINFO, '/A:3',
94                                     sbox.ospath('A2'))
95  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
96                                           ['3'],
97                                           sbox.repo_url + '/A',
98                                           sbox.ospath('A2'),
99                                           "--show-revs=merged")
100
101@SkipUnless(server_has_mergeinfo)
102def explicit_mergeinfo_source(sbox):
103  "'mergeinfo' with source selection"
104
105  # The idea is the target has mergeinfo pertaining to two or more different
106  # source branches and we're asking about just one of them.
107
108  sbox.build()
109
110  def url(relpath):
111    return sbox.repo_url + '/' + relpath
112  def path(relpath):
113    return sbox.ospath(relpath)
114
115  B = 'A/B'
116
117  # make some branches
118  B2 = 'A/B2'
119  B3 = 'A/B3'
120  sbox.simple_repo_copy(B, B2)  # r2
121  sbox.simple_repo_copy(B, B3)  # r3
122  sbox.simple_update()
123
124  # make changes in the branches
125  sbox.simple_mkdir('A/B2/newdir')
126  sbox.simple_commit()  # r4
127  sbox.simple_mkdir('A/B3/newdir')
128  sbox.simple_commit()  # r5
129
130  # Put dummy mergeinfo on branch root
131  mergeinfo = '/A/B2:2-5\n/A/B3:2-5\n'
132  sbox.simple_propset(SVN_PROP_MERGEINFO, mergeinfo, B)
133  sbox.simple_commit()
134
135  # Check using each of our recorded merge sources (as paths and URLs).
136  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
137                                           ['2', '4'], url(B2), path(B),
138                                           "--show-revs=merged")
139  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
140                                           ['2', '4'], path(B2), path(B),
141                                           "--show-revs=merged")
142  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
143                                           ['3', '5'], url(B3), path(B),
144                                           "--show-revs=merged")
145  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
146                                           ['3', '5'], path(B3), path(B),
147                                           "--show-revs=merged")
148
149@SkipUnless(server_has_mergeinfo)
150def mergeinfo_non_source(sbox):
151  "'mergeinfo' with uninteresting source selection"
152
153  sbox.build()
154  wc_dir = sbox.wc_dir
155  H_path = os.path.join(wc_dir, 'A', 'D', 'H')
156  H2_path = os.path.join(wc_dir, 'A', 'D', 'H2')
157  B_url = sbox.repo_url + '/A/B'
158  B_path = os.path.join(wc_dir, 'A', 'B')
159  G_url = sbox.repo_url + '/A/D/G'
160  G_path = os.path.join(wc_dir, 'A', 'D', 'G')
161  H2_url = sbox.repo_url + '/A/D/H2'
162
163  # Make a copy, and dummy up some mergeinfo.
164  mergeinfo = '/A/B:1\n/A/D/G:1\n'
165  svntest.actions.set_prop(SVN_PROP_MERGEINFO, mergeinfo, H_path)
166  svntest.main.run_svn(None, "cp", H_path, H2_path)
167  svntest.main.run_svn(None, "ci", "-m", "r2", wc_dir)
168
169  # Check on a source we haven't "merged" from.
170  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
171                                           [], H2_url, H_path,
172                                           "--show-revs=merged")
173
174#----------------------------------------------------------------------
175# Issue #3138
176@SkipUnless(server_has_mergeinfo)
177@Issue(3138)
178def mergeinfo_on_unknown_url(sbox):
179  "mergeinfo of an unknown url should return error"
180
181  sbox.build()
182  wc_dir = sbox.wc_dir
183
184  # remove a path from the repo and commit.
185  iota_path = os.path.join(wc_dir, 'iota')
186  svntest.actions.run_and_verify_svn(None, [], 'rm', iota_path)
187  svntest.actions.run_and_verify_svn(None, [],
188                                     "ci", wc_dir, "-m", "log message")
189
190  url = sbox.repo_url + "/iota"
191  expected_err = adjust_error_for_server_version(".*File not found.*iota.*|"
192                                                 ".*iota.*path not found.*")
193  svntest.actions.run_and_verify_svn(None, expected_err,
194                                     "mergeinfo", "--show-revs", "eligible",
195                                     url, wc_dir)
196
197# Test for issue #3126 'svn mergeinfo shows too few or too many
198# eligible revisions'.  Specifically
199# https://issues.apache.org/jira/browse/SVN-3126#desc5.
200@SkipUnless(server_has_mergeinfo)
201@Issue(3126)
202def non_inheritable_mergeinfo(sbox):
203  "non-inheritable mergeinfo shows as merged"
204
205  sbox.build()
206  wc_dir = sbox.wc_dir
207  expected_disk, expected_status = set_up_branch(sbox)
208
209  # Some paths we'll care about
210  A_COPY_path   = os.path.join(wc_dir, "A_COPY")
211  D_COPY_path   = os.path.join(wc_dir, "A_COPY", "D")
212  rho_COPY_path = os.path.join(wc_dir, "A_COPY", "D", "G", "rho")
213
214  # Update the WC, then merge r4 from A to A_COPY and r6 from A to A_COPY
215  # at --depth empty and commit the merges as r7.
216  svntest.actions.run_and_verify_svn(exp_noop_up_out(6), [], 'up',
217                                     wc_dir)
218  expected_status.tweak(wc_rev=6)
219  svntest.actions.run_and_verify_svn(
220    expected_merge_output([[4]],
221                          ['U    ' + rho_COPY_path + '\n',
222                           ' U   ' + A_COPY_path + '\n',]),
223    [], 'merge', '-c4',
224    sbox.repo_url + '/A',
225    A_COPY_path)
226  svntest.actions.run_and_verify_svn(
227    expected_merge_output([[6]], ' G   ' + A_COPY_path + '\n'),
228    [], 'merge', '-c6',
229    sbox.repo_url + '/A',
230    A_COPY_path, '--depth', 'empty')
231  expected_output = wc.State(wc_dir, {
232    'A_COPY'         : Item(verb='Sending'),
233    'A_COPY/D/G/rho' : Item(verb='Sending'),
234    })
235  expected_status.tweak('A_COPY', 'A_COPY/D/G/rho', wc_rev=7)
236  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
237                                        expected_status)
238
239  # Update the WC a last time to ensure full inheritance.
240  svntest.actions.run_and_verify_svn(exp_noop_up_out(7), [], 'up',
241                                     wc_dir)
242
243  # Despite being non-inheritable, r6 should still show as merged to A_COPY
244  # and not eligible for merging.
245  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
246                                           ['4','6*'],
247                                           sbox.repo_url + '/A',
248                                           A_COPY_path,
249                                           '--show-revs', 'merged')
250  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
251                                           ['3','5','6*'],
252                                           sbox.repo_url + '/A',
253                                           A_COPY_path,
254                                           '--show-revs', 'eligible')
255  # But if we drop down to A_COPY/D, r6 should show as eligible because it
256  # was only merged into A_COPY, no deeper.
257  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
258                                           ['4'],
259                                           sbox.repo_url + '/A/D',
260                                           D_COPY_path,
261                                           '--show-revs', 'merged')
262  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
263                                           ['3','6'],
264                                           sbox.repo_url + '/A/D',
265                                           D_COPY_path,
266                                           '--show-revs', 'eligible')
267
268# Test for -R option with svn mergeinfo subcommand.
269#
270# Test for issue #3242 'Subversion demands unnecessary access to parent
271# directories of operations'
272@Issue(3242)
273@SkipUnless(server_has_mergeinfo)
274def recursive_mergeinfo(sbox):
275  "test svn mergeinfo -R"
276
277  sbox.build()
278  wc_dir = sbox.wc_dir
279  expected_disk, expected_status = set_up_branch(sbox)
280
281  # Some paths we'll care about
282  A_path          = os.path.join(wc_dir, "A")
283  A_COPY_path     = os.path.join(wc_dir, "A_COPY")
284  B_COPY_path     = os.path.join(wc_dir, "A_COPY", "B")
285  C_COPY_path     = os.path.join(wc_dir, "A_COPY", "C")
286  rho_COPY_path   = os.path.join(wc_dir, "A_COPY", "D", "G", "rho")
287  H_COPY_path     = os.path.join(wc_dir, "A_COPY", "D", "H")
288  F_COPY_path     = os.path.join(wc_dir, "A_COPY", "B", "F")
289  omega_COPY_path = os.path.join(wc_dir, "A_COPY", "D", "H", "omega")
290  beta_COPY_path  = os.path.join(wc_dir, "A_COPY", "B", "E", "beta")
291  A2_path         = os.path.join(wc_dir, "A2")
292  nu_path         = os.path.join(wc_dir, "A2", "B", "F", "nu")
293  nu_COPY_path    = os.path.join(wc_dir, "A_COPY", "B", "F", "nu")
294  nu2_path        = os.path.join(wc_dir, "A2", "C", "nu2")
295
296  # Rename A to A2 in r7.
297  svntest.actions.run_and_verify_svn(exp_noop_up_out(6), [], 'up', wc_dir)
298  svntest.actions.run_and_verify_svn(None, [],
299                                     'ren', A_path, A2_path)
300  svntest.actions.run_and_verify_svn(None, [],
301                                     'ci', wc_dir, '-m', 'rename A to A2')
302
303  # Add the files A/B/F/nu and A/C/nu2 and commit them as r8.
304  svntest.main.file_write(nu_path, "A new file.\n")
305  svntest.main.file_write(nu2_path, "Another new file.\n")
306  svntest.main.run_svn(None, "add", nu_path, nu2_path)
307  svntest.actions.run_and_verify_svn(None, [],
308                                     'ci', wc_dir, '-m', 'Add 2 new files')
309
310  # Do several merges to create varied subtree mergeinfo
311
312  # Merge r4 from A2 to A_COPY at depth empty
313  svntest.actions.run_and_verify_svn(exp_noop_up_out(8), [], 'up',
314                                     wc_dir)
315  svntest.actions.run_and_verify_svn(
316    expected_merge_output([[4]], ' U   ' + A_COPY_path + '\n'),
317    [], 'merge', '-c4', '--depth', 'empty',
318    sbox.repo_url + '/A2',
319    A_COPY_path)
320
321  # Merge r6 from A2/D/H to A_COPY/D/H
322  svntest.actions.run_and_verify_svn(
323    expected_merge_output([[6]],
324                          ['U    ' + omega_COPY_path + '\n',
325                           ' G   ' + H_COPY_path + '\n']),
326    [], 'merge', '-c6',
327    sbox.repo_url + '/A2/D/H',
328    H_COPY_path)
329
330  # Merge r5 from A2 to A_COPY
331  svntest.actions.run_and_verify_svn(
332    expected_merge_output([[5]],
333                          ['U    ' + beta_COPY_path + '\n',
334                           ' G   ' + A_COPY_path + '\n',
335                           ' G   ' + B_COPY_path + '\n',
336                           ' U   ' + B_COPY_path + '\n',], # Elision
337                          elides=True),
338    [], 'merge', '-c5',
339    sbox.repo_url + '/A2',
340    A_COPY_path)
341
342  # Reverse merge -r5 from A2/C to A_COPY/C leaving empty mergeinfo on
343  # A_COPY/C.
344  svntest.actions.run_and_verify_svn(
345    expected_merge_output([[-5]],
346                          ' G   ' + C_COPY_path + '\n'),
347    [], 'merge', '-c-5',
348    sbox.repo_url + '/A2/C', C_COPY_path)
349
350  # Merge r8 from A2/B/F to A_COPY/B/F
351  svntest.actions.run_and_verify_svn(
352    expected_merge_output([[8]],
353                          ['A    ' + nu_COPY_path + '\n',
354                           ' G   ' + F_COPY_path + '\n']),
355    [], 'merge', '-c8',
356    sbox.repo_url + '/A2/B/F',
357    F_COPY_path)
358
359  # Commit everything this far as r9
360  svntest.actions.run_and_verify_svn(None, [],
361                                     'ci', wc_dir, '-m', 'Many merges')
362  svntest.actions.run_and_verify_svn(exp_noop_up_out(9), [], 'up',
363                                     wc_dir)
364
365  # Test svn mergeinfo -R / --depth infinity.
366
367  # Asking for eligible revisions from A2 to A_COPY should show:
368  #
369  #  r3  - Was never merged.
370  #
371  #  r4 - Was merged at depth empty, so while there is mergeinfo for the
372  #       revision, the actual text change to A_COPY/D/G/rho hasn't yet
373  #       happened.
374  #
375  #  r8* - Was only partially merged to the subtree at A_COPY/B/F.  The
376  #        addition of A_COPY/C/nu2 is still outstanding.
377  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
378                                           ['3', '4*', '8*'],
379                                           sbox.repo_url + '/A2',
380                                           sbox.repo_url + '/A_COPY',
381                                           '--show-revs', 'eligible', '-R')
382  # Do the same as above, but test that we can request the revisions
383  # in reverse order.
384  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
385                                           ['8*', '4*', '3'],
386                                           sbox.repo_url + '/A2',
387                                           sbox.repo_url + '/A_COPY',
388                                           '--show-revs', 'eligible', '-R',
389                                           '-r', '9:0')
390
391  # Asking for merged revisions from A2 to A_COPY should show:
392  #
393  #  r4* - Was merged at depth empty, so while there is mergeinfo for the
394  #        revision, the actual text change to A_COPY/D/G/rho hasn't yet
395  #        happened.
396  #
397  #  r5  - Was merged at depth infinity to the root of the 'branch', so it
398  #        should show as fully merged.
399  #
400  #  r6  - This was a subtree merge, but since the subtree A_COPY/D/H was
401  #        the ancestor of the only change made in r6 it is considered
402  #        fully merged.
403  #
404  #  r8* - Was only partially merged to the subtree at A_COPY/B/F.  The
405  #        addition of A_COPY/C/nu2 is still outstanding.
406  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
407                                           ['4*', '5', '6', '8*'],
408                                           A2_path,
409                                           A_COPY_path,
410                                           '--show-revs', 'merged',
411                                           '--depth', 'infinity')
412  # Do the same as above, but test that we can request the revisions
413  # in reverse order.
414  svntest.actions.run_and_verify_mergeinfo(adjust_error_for_server_version(""),
415                                           ['8*', '6', '5', '4*'],
416                                           A2_path,
417                                           A_COPY_path,
418                                           '--show-revs', 'merged',
419                                           '--depth', 'infinity',
420                                           '-r', '9:0')
421
422  # A couple tests of problems found with initial issue #3242 fixes.
423  # We should be able to check for the merged revs from a URL to a URL
424  # when the latter has explicit mergeinfo...
425  svntest.actions.run_and_verify_mergeinfo(
426    adjust_error_for_server_version(''), ['6'],
427    sbox.repo_url + '/A2/D/H',
428    sbox.repo_url + '/A_COPY/D/H',
429    '--show-revs', 'merged')
430  # ...and when the latter has inherited mergeinfo.
431  svntest.actions.run_and_verify_mergeinfo(
432    adjust_error_for_server_version(''), ['6'],
433    sbox.repo_url + '/A2/D/H/omega',
434    sbox.repo_url + '/A_COPY/D/H/omega',
435    '--show-revs', 'merged')
436
437# Test for issue #3180 'svn mergeinfo ignores peg rev for WC target'.
438@SkipUnless(server_has_mergeinfo)
439def mergeinfo_on_pegged_wc_path(sbox):
440  "svn mergeinfo on pegged working copy target"
441
442  sbox.build()
443  wc_dir = sbox.wc_dir
444  expected_disk, expected_status = set_up_branch(sbox)
445
446  # Some paths we'll care about
447  A_path          = os.path.join(wc_dir, "A")
448  A_COPY_path     = os.path.join(wc_dir, "A_COPY")
449  psi_COPY_path   = os.path.join(wc_dir, "A_COPY", "D", "H", "psi")
450  omega_COPY_path = os.path.join(wc_dir, "A_COPY", "D", "H", "omega")
451  beta_COPY_path  = os.path.join(wc_dir, "A_COPY", "B", "E", "beta")
452
453  # Do a couple merges
454  #
455  # r7 - Merge -c3,6 from A to A_COPY.
456  svntest.actions.run_and_verify_svn(
457    expected_merge_output([[3],[6]],
458                          ['U    ' + psi_COPY_path + '\n',
459                           'U    ' + omega_COPY_path + '\n',
460                           ' U   ' + A_COPY_path + '\n',
461                           ' G   ' + A_COPY_path + '\n',]),
462    [], 'merge', '-c3,6', sbox.repo_url + '/A', A_COPY_path)
463  svntest.actions.run_and_verify_svn(None, [],
464                                     'ci', wc_dir,
465                                     '-m', 'Merge r3 and r6')
466
467  # r8 - Merge -c5 from A to A_COPY.
468  svntest.actions.run_and_verify_svn(
469    expected_merge_output([[5]],
470                          ['U    ' + beta_COPY_path + '\n',
471                           ' U   ' + A_COPY_path + '\n']),
472    [], 'merge', '-c5', '--allow-mixed-revisions',
473    sbox.repo_url + '/A', A_COPY_path)
474  svntest.actions.run_and_verify_svn(None, [],
475                                     'ci', wc_dir,
476                                     '-m', 'Merge r5')
477
478  # Ask for merged and eligible revisions to A_COPY pegged at various values.
479  # Prior to issue #3180 fix the peg revision was ignored.
480  #
481  # A_COPY pegged to non-existent revision
482  svntest.actions.run_and_verify_mergeinfo(
483    adjust_error_for_server_version('.*No such revision 99'),
484    [], A_path, A_COPY_path + '@99', '--show-revs', 'merged')
485
486  # A_COPY@BASE
487  svntest.actions.run_and_verify_mergeinfo(
488    adjust_error_for_server_version(''),
489    ['3','5','6'], A_path, A_COPY_path + '@BASE', '--show-revs', 'merged')
490
491  # A_COPY@HEAD
492  svntest.actions.run_and_verify_mergeinfo(
493    adjust_error_for_server_version(''),
494    ['3','5','6'], A_path, A_COPY_path + '@HEAD', '--show-revs', 'merged')
495
496  # A_COPY@4 (Prior to any merges)
497  svntest.actions.run_and_verify_mergeinfo(
498    adjust_error_for_server_version(''),
499    [], A_path, A_COPY_path + '@4', '--show-revs', 'merged')
500
501  # A_COPY@COMMITTED (r8)
502  svntest.actions.run_and_verify_mergeinfo(
503    adjust_error_for_server_version(''),
504    ['3','5','6'], A_path, A_COPY_path + '@COMMITTED', '--show-revs',
505    'merged')
506
507  # A_COPY@PREV (r7)
508  svntest.actions.run_and_verify_mergeinfo(
509    adjust_error_for_server_version(''),
510    ['3', '6'], A_path, A_COPY_path + '@PREV', '--show-revs', 'merged')
511
512  # A_COPY@BASE
513  svntest.actions.run_and_verify_mergeinfo(
514    adjust_error_for_server_version(''),
515    ['4'], A_path, A_COPY_path + '@BASE', '--show-revs', 'eligible')
516
517  # A_COPY@HEAD
518  svntest.actions.run_and_verify_mergeinfo(
519    adjust_error_for_server_version(''),
520    ['4'], A_path, A_COPY_path + '@HEAD', '--show-revs', 'eligible')
521
522  # A_COPY@4 (Prior to any merges)
523  svntest.actions.run_and_verify_mergeinfo(
524    adjust_error_for_server_version(''),
525    ['3', '4', '5', '6'], A_path, A_COPY_path + '@4', '--show-revs', 'eligible')
526
527  # A_COPY@COMMITTED (r8)
528  svntest.actions.run_and_verify_mergeinfo(
529    adjust_error_for_server_version(''),
530    ['4'], A_path, A_COPY_path + '@COMMITTED', '--show-revs',
531    'eligible')
532
533  # A_COPY@PREV (r7)
534  svntest.actions.run_and_verify_mergeinfo(
535    adjust_error_for_server_version(''),
536    ['4', '5'], A_path, A_COPY_path + '@PREV', '--show-revs', 'eligible')
537
538#----------------------------------------------------------------------
539# A test for issue 3986 'svn_client_mergeinfo_log API is broken'.
540@Issue(3986)
541@SkipUnless(server_has_mergeinfo)
542def wc_target_inherits_mergeinfo_from_repos(sbox):
543  "wc target inherits mergeinfo from repos"
544
545  sbox.build()
546  wc_dir = sbox.wc_dir
547  wc_disk, wc_status = set_up_branch(sbox, nbr_of_branches=2)
548
549  A_COPY_path   = os.path.join(wc_dir, 'A_COPY')
550  rho_COPY_path = os.path.join(wc_dir, 'A_COPY', 'D', 'G', 'rho')
551  gamma_2_path  = os.path.join(wc_dir, 'A_COPY_2', 'D', 'gamma')
552  tau_path      = os.path.join(wc_dir, 'A', 'D', 'G', 'tau')
553  D_COPY_path   = os.path.join(wc_dir, 'A_COPY', 'D')
554
555  # Merge -c5 ^/A/D/G/rho A_COPY\D\G\rho
556  # Merge -c7 ^/A A_COPY
557  # Commit as r8
558  #
559  # This gives us some explicit mergeinfo on the "branch" root and
560  # one of its subtrees:
561  #
562  #   Properties on 'A_COPY\D\G\rho':
563  #     svn:mergeinfo
564  #       /A/D/G/rho:5
565  #   Properties on 'A_COPY':
566  #     svn:mergeinfo
567  #       /A:7
568  svntest.actions.run_and_verify_svn(None, [], 'merge',
569                                     sbox.repo_url + '/A/D/G/rho',
570                                     rho_COPY_path, '-c5')
571  svntest.actions.run_and_verify_svn(None, [], 'merge',
572                                     sbox.repo_url + '/A',
573                                     A_COPY_path, '-c7')
574  svntest.actions.run_and_verify_svn(None, [], 'ci', '-m',
575                                     'Cherrypicks to branch subtree and root',
576                                     wc_dir)
577
578  # Checkout a new wc rooted at ^/A_COPY/D.
579  subtree_wc = sbox.add_wc_path('D_COPY')
580  svntest.actions.run_and_verify_svn(None, [], 'co',
581                                     sbox.repo_url + '/A_COPY/D',
582                                     subtree_wc)
583
584  # Check the merged and eligible revisions both recursively and
585  # non-recursively.
586
587  # Eligible : Non-recursive
588  svntest.actions.run_and_verify_mergeinfo(
589    adjust_error_for_server_version(''),
590    ['4','5'], sbox.repo_url + '/A/D', subtree_wc,
591    '--show-revs', 'eligible')
592
593  # Eligible : Recursive
594  svntest.actions.run_and_verify_mergeinfo(
595    adjust_error_for_server_version(''),
596    ['4'], sbox.repo_url + '/A/D', subtree_wc,
597    '--show-revs', 'eligible', '-R')
598
599  # Merged : Non-recursive
600  svntest.actions.run_and_verify_mergeinfo(
601    adjust_error_for_server_version(''),
602    ['7'], sbox.repo_url + '/A/D', subtree_wc,
603    '--show-revs', 'merged')
604
605  # Merged : Recursive
606  svntest.actions.run_and_verify_mergeinfo(
607    adjust_error_for_server_version(''),
608    ['5','7'], sbox.repo_url + '/A/D', subtree_wc,
609    '--show-revs', 'merged', '-R')
610
611  # Test that intersecting revisions in the 'svn mergeinfo' target
612  # from one source don't show up as merged when asking about a different
613  # source.
614  #
615  # In r9 make a change that effects two branches:
616  svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir)
617  svntest.main.file_write(gamma_2_path, "New content.\n")
618  svntest.main.file_write(tau_path, "New content.\n")
619  svntest.actions.run_and_verify_svn(None, [], 'ci', '-m',
620                                     'Make changes under both A and A_COPY_2',
621                                     wc_dir)
622
623  # In r10 merge r9 from A_COPY_2 to A_COPY.
624  #
625  # This gives us this mergeinfo:
626  #
627  #   Properties on 'A_COPY':
628  #     svn:mergeinfo
629  #       /A:7
630  #       /A_COPY_2:9
631  #   Properties on 'A_COPY\D\G\rho':
632  #     svn:mergeinfo
633  #       /A/D/G/rho:5
634  svntest.actions.run_and_verify_svn(None, [], 'merge',
635                                     sbox.repo_url + '/A_COPY_2',
636                                     A_COPY_path, '-c9')
637  svntest.actions.run_and_verify_svn(None, [], 'ci', '-m',
638                                     'Merge r8 from A_COPY_2 to A_COPY',
639                                     wc_dir)
640
641  def test_svn_mergeinfo_4_way(wc_target):
642    # Eligible : Non-recursive
643    svntest.actions.run_and_verify_mergeinfo(
644      adjust_error_for_server_version(''),
645      ['4','5','9'], sbox.repo_url + '/A/D', wc_target,
646      '--show-revs', 'eligible')
647
648    # Eligible : Recursive
649    svntest.actions.run_and_verify_mergeinfo(
650      adjust_error_for_server_version(''),
651      ['4','9'], sbox.repo_url + '/A/D', wc_target,
652      '--show-revs', 'eligible', '-R')
653
654    # Merged : Non-recursive
655    svntest.actions.run_and_verify_mergeinfo(
656      adjust_error_for_server_version(''),
657      ['7'], sbox.repo_url + '/A/D', wc_target,
658      '--show-revs', 'merged')
659
660    # Merged : Recursive
661    svntest.actions.run_and_verify_mergeinfo(
662      adjust_error_for_server_version(''),
663      ['5','7'], sbox.repo_url + '/A/D', wc_target,
664      '--show-revs', 'merged', '-R')
665
666  # Test while the target is the full WC and then with the subtree WC:
667  svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir)
668  svntest.actions.run_and_verify_svn(None, [], 'up', subtree_wc)
669
670  test_svn_mergeinfo_4_way(D_COPY_path)
671  test_svn_mergeinfo_4_way(subtree_wc)
672
673#----------------------------------------------------------------------
674# A test for issue 3791 'svn mergeinfo shows natural history of added
675# subtrees as eligible'.
676@Issue(3791)
677@SkipUnless(server_has_mergeinfo)
678def natural_history_is_not_eligible_nor_merged(sbox):
679  "natural history is not eligible nor merged"
680
681  sbox.build()
682  wc_dir = sbox.wc_dir
683  wc_disk, wc_status = set_up_branch(sbox)
684
685  nu_path      = os.path.join(wc_dir, 'A', 'C', 'nu')
686  A_COPY_path  = os.path.join(wc_dir, 'A_COPY')
687  nu_COPY_path = os.path.join(wc_dir, 'A_COPY', 'C', 'nu')
688
689  # r7 - Add a new file A/C/nu
690  svntest.main.file_write(nu_path, "This is the file 'nu'.\n")
691  svntest.actions.run_and_verify_svn(None, [], 'add', nu_path)
692  svntest.actions.run_and_verify_svn(None, [], 'ci',
693                                     '-m', 'Add a file', wc_dir)
694
695  # r8 - Sync merge ^/A to A_COPY
696  svntest.actions.run_and_verify_svn(None, [], 'merge',
697                                     sbox.repo_url + '/A', A_COPY_path)
698  svntest.actions.run_and_verify_svn(None, [], 'ci',
699                                     '-m', 'Add a file', wc_dir)
700
701  # r9 - Modify the file added in r7
702  svntest.main.file_write(nu_path, "Modification to file 'nu'.\n")
703  svntest.actions.run_and_verify_svn(None, [], 'ci',
704                                     '-m', 'Modify added file', wc_dir)
705
706  # r10 - Merge ^/A/C/nu to A_COPY/C/nu, creating subtree mergeinfo.
707  svntest.actions.run_and_verify_svn(None, [], 'merge',
708                                     sbox.repo_url + '/A/C/nu', nu_COPY_path)
709  svntest.actions.run_and_verify_svn(None, [], 'ci',
710                                     '-m', 'Add a file', wc_dir)
711  svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir)
712
713  # We've effectively merged everything from ^/A to A_COPY, check
714  # that svn mergeinfo -R agrees.
715  #
716  # First check if there are eligible revisions, there should be none.
717  svntest.actions.run_and_verify_mergeinfo(
718    adjust_error_for_server_version(''),
719    [], sbox.repo_url + '/A',
720    A_COPY_path, '--show-revs', 'eligible', '-R')
721
722  # Now check that all operative revisions show as merged.
723  svntest.actions.run_and_verify_mergeinfo(
724    adjust_error_for_server_version(''),
725    ['3','4','5','6','7','9'], sbox.repo_url + '/A',
726    A_COPY_path, '--show-revs', 'merged', '-R')
727
728#----------------------------------------------------------------------
729# A test for issue 4050 "'svn mergeinfo' always considers non-inheritable
730# ranges as partially merged".
731@Issue(4050)
732@SkipUnless(server_has_mergeinfo)
733def noninheritable_mergeinfo_not_always_eligible(sbox):
734  "noninheritable mergeinfo not always eligible"
735
736  sbox.build()
737  wc_dir = sbox.wc_dir
738
739  A_path      = os.path.join(wc_dir, 'A')
740  branch_path = os.path.join(wc_dir, 'branch')
741
742  # r2 - Branch ^/A to ^/branch.
743  svntest.main.run_svn(None, 'copy', sbox.repo_url + '/A',
744                       sbox.repo_url + '/branch', '-m', 'make a branch')
745
746  # r3 - Make prop edit to A.
747  svntest.main.run_svn(None, 'ps', 'prop', 'val', A_path)
748  svntest.main.run_svn(None, 'commit', '-m', 'file edit', wc_dir)
749  svntest.main.run_svn(None, 'up', wc_dir)
750
751  # r4 - Merge r3 from ^/A to branch at depth=empty.
752  svntest.actions.run_and_verify_svn(None, [], 'merge',
753                                     sbox.repo_url + '/A', branch_path,
754                                     '-c3', '--depth=empty')
755  # Forcibly set non-inheritable mergeinfo to replicate the pre-1.8 behavior,
756  # where prior to the fix for issue #4057, non-inheritable mergeinfo was
757  # unconditionally set for merges with shallow operational depths.
758  svntest.actions.run_and_verify_svn(None, [],
759                                     'propset', SVN_PROP_MERGEINFO,
760                                     '/A:3*\n', branch_path)
761  svntest.main.run_svn(None, 'commit', '-m', 'shallow merge', wc_dir)
762
763  # Now check that r3 is reported as fully merged from ^/A to ^/branch
764  # and does not show up all when asking for eligible revs.
765  svntest.actions.run_and_verify_mergeinfo(
766    adjust_error_for_server_version(''),
767    ['3'], sbox.repo_url + '/A', sbox.repo_url + '/branch',
768    '--show-revs', 'merged', '-R')
769  # Likewise r3 shows up as partially eligible when asking about
770  # for --show-revs=eligible.
771  svntest.actions.run_and_verify_mergeinfo(
772    adjust_error_for_server_version(''),
773    [], sbox.repo_url + '/A', sbox.repo_url + '/branch',
774    '--show-revs', 'eligible', '-R')
775
776@SkipUnless(server_has_mergeinfo)
777@Issue(4301)
778def mergeinfo_local_move(sbox):
779  "'mergeinfo' on a locally moved path"
780
781  sbox.build()
782  wc_dir = sbox.wc_dir
783
784  sbox.simple_move('A', 'A2')
785  svntest.actions.run_and_verify_svn(None, [],
786                                     'mergeinfo', sbox.repo_url + '/A',
787                                     sbox.ospath('A2'))
788
789@SkipUnless(server_has_mergeinfo)
790@Issue(4582)
791def no_mergeinfo_on_tree_conflict_victim(sbox):
792  "do not record mergeinfo on tree conflict victims"
793  sbox.build()
794
795  # Create a branch of A called A_copy
796  sbox.simple_copy('A', 'A_copy')
797  sbox.simple_commit()
798
799  # Add a new directory and file on both branches
800  sbox.simple_mkdir('A/dir')
801  sbox.simple_add_text('new file', 'A/dir/f')
802  sbox.simple_commit()
803
804  sbox.simple_mkdir('A_copy/dir')
805  sbox.simple_add_text('new file', 'A_copy/dir/f')
806  sbox.simple_commit()
807
808  # Run a merge from A to A_copy
809  expected_output = wc.State(sbox.ospath('A_copy'), {
810    'dir'               : Item(status='  ', treeconflict='C'),
811    'dir/f'             : Item(status='  ', treeconflict='A'),
812    })
813  expected_mergeinfo_output = wc.State(sbox.ospath('A_copy'), {
814    '' : Item(status=' U'),
815    })
816  expected_elision_output = wc.State(sbox.ospath('A_copy'), {
817    })
818
819  expected_disk = svntest.wc.State('', {
820    'C'                 : Item(),
821    'B/E/beta'          : Item(contents="This is the file 'beta'.\n"),
822    'B/E/alpha'         : Item(contents="This is the file 'alpha'.\n"),
823    'B/lambda'          : Item(contents="This is the file 'lambda'.\n"),
824    'B/F'               : Item(),
825    'D/H/omega'         : Item(contents="This is the file 'omega'.\n"),
826    'D/H/psi'           : Item(contents="This is the file 'psi'.\n"),
827    'D/H/chi'           : Item(contents="This is the file 'chi'.\n"),
828    'D/G/tau'           : Item(contents="This is the file 'tau'.\n"),
829    'D/G/pi'            : Item(contents="This is the file 'pi'.\n"),
830    'D/G/rho'           : Item(contents="This is the file 'rho'.\n"),
831    'D/gamma'           : Item(contents="This is the file 'gamma'.\n"),
832    'dir/f'             : Item(contents="new file"),
833    'mu'                : Item(contents="This is the file 'mu'.\n"),
834    })
835
836  # The merge will create an add vs add tree conflict on A_copy/dir
837  expected_status = svntest.wc.State(sbox.ospath('A_copy'), {
838    ''                  : Item(status=' M', wc_rev='4'),
839    'D'                 : Item(status='  ', wc_rev='4'),
840    'D/G'               : Item(status='  ', wc_rev='4'),
841    'D/G/pi'            : Item(status='  ', wc_rev='4'),
842    'D/G/rho'           : Item(status='  ', wc_rev='4'),
843    'D/G/tau'           : Item(status='  ', wc_rev='4'),
844    'D/H'               : Item(status='  ', wc_rev='4'),
845    'D/H/psi'           : Item(status='  ', wc_rev='4'),
846    'D/H/omega'         : Item(status='  ', wc_rev='4'),
847    'D/H/chi'           : Item(status='  ', wc_rev='4'),
848    'D/gamma'           : Item(status='  ', wc_rev='4'),
849    'B'                 : Item(status='  ', wc_rev='4'),
850    'B/F'               : Item(status='  ', wc_rev='4'),
851    'B/E'               : Item(status='  ', wc_rev='4'),
852    'B/E/alpha'         : Item(status='  ', wc_rev='4'),
853    'B/E/beta'          : Item(status='  ', wc_rev='4'),
854    'B/lambda'          : Item(status='  ', wc_rev='4'),
855    'C'                 : Item(status='  ', wc_rev='4'),
856    'dir'               : Item(status='  ', treeconflict='C', wc_rev='4'),
857    'dir/f'             : Item(status='  ', wc_rev='4'),
858    'mu'                : Item(status='  ', wc_rev='4'),
859    })
860
861  expected_skip = wc.State('', { })
862
863  sbox.simple_update('A_copy')
864  svntest.actions.run_and_verify_merge(sbox.ospath('A_copy'),
865                                       None, None, # rev1, rev2
866                                       '^/A',
867                                       None, # URL2
868                                       expected_output,
869                                       expected_mergeinfo_output,
870                                       expected_elision_output,
871                                       expected_disk,
872                                       expected_status,
873                                       expected_skip)
874
875  # Resolve the tree conflict by accepting the working copy state left
876  # behind by the merge. This preserves the line of history of A_copy/dir,
877  # which originated on the branch 'A_copy', rather than replacing it with
878  # Jthe line f history of A/dir which originated on branch 'A'
879  svntest.actions.run_and_verify_resolve([sbox.ospath('A_copy/dir')],
880                                         '--accept', 'working',
881                                         sbox.ospath('A_copy/dir'))
882  sbox.simple_commit('A_copy')
883
884  # Now try to merge the 'A_copy' branch back to 'A"
885  expected_output = wc.State(sbox.ospath('A'), {
886    'dir'               : Item(status='R '), # changes line of history of A/dir
887    'dir/f'             : Item(status='A '),
888    })
889  expected_mergeinfo_output = wc.State(sbox.ospath('A'), {
890    ''                  : Item(status=' U'),
891    })
892  expected_elision_output = wc.State(sbox.ospath('A'), {
893    })
894
895  expected_disk = svntest.wc.State('', {
896    'C'                 : Item(),
897    'B/E/beta'          : Item(contents="This is the file 'beta'.\n"),
898    'B/E/alpha'         : Item(contents="This is the file 'alpha'.\n"),
899    'B/F'               : Item(),
900    'B/lambda'          : Item(contents="This is the file 'lambda'.\n"),
901    'D/H/omega'         : Item(contents="This is the file 'omega'.\n"),
902    'D/H/psi'           : Item(contents="This is the file 'psi'.\n"),
903    'D/H/chi'           : Item(contents="This is the file 'chi'.\n"),
904    'D/G/tau'           : Item(contents="This is the file 'tau'.\n"),
905    'D/G/pi'            : Item(contents="This is the file 'pi'.\n"),
906    'D/G/rho'           : Item(contents="This is the file 'rho'.\n"),
907    'D/gamma'           : Item(contents="This is the file 'gamma'.\n"),
908    'dir/f'             : Item(contents="new file"),
909    'mu'                : Item(contents="This is the file 'mu'.\n"),
910    })
911
912  expected_status = svntest.wc.State(sbox.ospath('A'), {
913    ''                  : Item(status=' M', wc_rev='5'),
914    'dir'               : Item(status='R ', copied='+', wc_rev='-'),
915    'dir/f'             : Item(status='  ', copied='+', wc_rev='-'),
916    'D'                 : Item(status='  ', wc_rev='5'),
917    'D/H'               : Item(status='  ', wc_rev='5'),
918    'D/H/chi'           : Item(status='  ', wc_rev='5'),
919    'D/H/omega'         : Item(status='  ', wc_rev='5'),
920    'D/H/psi'           : Item(status='  ', wc_rev='5'),
921    'D/G'               : Item(status='  ', wc_rev='5'),
922    'D/G/pi'            : Item(status='  ', wc_rev='5'),
923    'D/G/rho'           : Item(status='  ', wc_rev='5'),
924    'D/G/tau'           : Item(status='  ', wc_rev='5'),
925    'D/gamma'           : Item(status='  ', wc_rev='5'),
926    'B'                 : Item(status='  ', wc_rev='5'),
927    'B/E'               : Item(status='  ', wc_rev='5'),
928    'B/E/beta'          : Item(status='  ', wc_rev='5'),
929    'B/E/alpha'         : Item(status='  ', wc_rev='5'),
930    'B/lambda'          : Item(status='  ', wc_rev='5'),
931    'B/F'               : Item(status='  ', wc_rev='5'),
932    'mu'                : Item(status='  ', wc_rev='5'),
933    'C'                 : Item(status='  ', wc_rev='5'),
934    })
935
936  expected_skip = wc.State('', { })
937  sbox.simple_update('A')
938  svntest.actions.run_and_verify_merge(sbox.ospath('A'),
939                                       None, None, # rev1, rev2
940                                       '^/A_copy',
941                                       None, # URL2
942                                       expected_output,
943                                       expected_mergeinfo_output,
944                                       expected_elision_output,
945                                       expected_disk,
946                                       expected_status,
947                                       expected_skip)
948  sbox.simple_commit('A')
949
950########################################################################
951# Run the tests
952
953# Note that mergeinfo --log is tested in log_tests.py
954
955# list all tests here, starting with None:
956test_list = [ None,
957              no_mergeinfo,
958              mergeinfo,
959              explicit_mergeinfo_source,
960              mergeinfo_non_source,
961              mergeinfo_on_unknown_url,
962              non_inheritable_mergeinfo,
963              recursive_mergeinfo,
964              mergeinfo_on_pegged_wc_path,
965              wc_target_inherits_mergeinfo_from_repos,
966              natural_history_is_not_eligible_nor_merged,
967              noninheritable_mergeinfo_not_always_eligible,
968              mergeinfo_local_move,
969              no_mergeinfo_on_tree_conflict_victim,
970             ]
971
972if __name__ == '__main__':
973  svntest.main.run_tests(test_list)
974  # NOTREACHED
975