1#!/usr/bin/env python
2#
3#  merge_authz_tests.py:  merge tests that need to write an authz file
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
29import time
30
31# Our testing module
32import svntest
33from svntest import wc
34
35# (abbreviation)
36Item = wc.StateItem
37Skip = svntest.testcase.Skip_deco
38SkipUnless = svntest.testcase.SkipUnless_deco
39XFail = svntest.testcase.XFail_deco
40Issues = svntest.testcase.Issues_deco
41Issue = svntest.testcase.Issue_deco
42Wimp = svntest.testcase.Wimp_deco
43
44from svntest.mergetrees import set_up_branch
45from svntest.mergetrees import expected_merge_output
46from svntest.main import SVN_PROP_MERGEINFO
47from svntest.main import write_restrictive_svnserve_conf
48from svntest.main import write_authz_file
49from svntest.main import is_ra_type_dav
50from svntest.main import is_ra_type_svn
51from svntest.main import server_has_mergeinfo
52from svntest.actions import fill_file_with_lines
53from svntest.actions import make_conflict_marker_text
54from svntest.actions import inject_conflict_into_expected_state
55
56######################################################################
57# Tests
58#
59#   Each test must return on success or raise on failure.
60
61
62#----------------------------------------------------------------------
63
64# Test for issues
65#
66# #2893 - Handle merge info for portions of a tree not checked out due
67#        to insufficient authz.
68#
69# #2997 - If skipped paths come first in operative merge mergeinfo
70#         is incomplete
71#
72# #2829 - Improve handling for skipped paths encountered during a merge.
73#         This is *not* a full test of issue #2829, see also merge_tests.py,
74#         search for "2829".  This tests the problem where a merge adds a path
75#         with a missing sibling and so needs its own explicit mergeinfo.
76#
77# #4056 - Don't record non-inheritable mergeinfo if missing subtrees are not
78#         touched by the full-depth diff
79@Issues(2893,2997,2829,4056)
80@SkipUnless(svntest.main.server_has_mergeinfo)
81@Skip(svntest.main.is_ra_type_file)
82def mergeinfo_and_skipped_paths(sbox):
83  "skipped paths get overriding mergeinfo"
84
85  # Test that we override the mergeinfo for child paths which weren't
86  # actually merged because they were skipped.
87  #
88  # This test covers paths skipped because:
89  #
90  #   1) The source of a merge is inaccessible due to authz restrictions.
91  #   2) Destination of merge is inaccessible due to authz restrictions.
92  #   3) Source *and* destination of merge is inaccessible due to authz
93  #      restrictions.
94
95  sbox.build()
96  wc_dir = sbox.wc_dir
97  wc_disk, wc_status = set_up_branch(sbox, False, 3)
98
99  # Create a restrictive authz where part of the merge source and part
100  # of the target are inaccesible.
101  write_restrictive_svnserve_conf(sbox.repo_dir)
102  write_authz_file(sbox, {"/"               : svntest.main.wc_author +"=rw",
103                          # Make a directory in the merge source inaccessible.
104                          "/A/B/E"            : svntest.main.wc_author + "=",
105                          # Make a file and dir in the merge destination
106                          # inaccessible.
107                          "/A_COPY_2/D/H/psi" : svntest.main.wc_author + "=",
108                          "/A_COPY_2/D/G" : svntest.main.wc_author + "=",
109                          # Make the source and destination inaccessible.
110                          "/A_COPY_3/B/E"     : svntest.main.wc_author + "=",
111                          })
112
113  # Checkout just the branch under the newly restricted authz.
114  wc_restricted = sbox.add_wc_path('restricted')
115  svntest.actions.run_and_verify_svn(None, [], 'checkout',
116                                     sbox.repo_url,
117                                     wc_restricted)
118
119  # Some paths we'll use in the second WC.
120  A_COPY_path = os.path.join(wc_restricted, "A_COPY")
121  A_COPY_2_path = os.path.join(wc_restricted, "A_COPY_2")
122  A_COPY_2_H_path = os.path.join(wc_restricted, "A_COPY_2", "D", "H")
123  A_COPY_3_path = os.path.join(wc_restricted, "A_COPY_3")
124  omega_path = os.path.join(wc_restricted, "A_COPY", "D", "H", "omega")
125  zeta_path = sbox.ospath("A/D/H/zeta")
126
127  # Merge r4:8 into the restricted WC's A_COPY.
128  #
129  # We expect A_COPY/B/E to be skipped because we can't access the source
130  # and A_COPY/D/H/omega because it is missing.  Since we have A_COPY/B/E
131  # we should override it's inherited mergeinfo, giving it just what it
132  # inherited from A_COPY before the merge.
133  expected_output = wc.State(A_COPY_path, {
134    'D/G/rho'   : Item(status='U '),
135    'D/H/psi'   : Item(status='U '),
136    'D/H/omega' : Item(status='U '),
137    })
138  expected_mergeinfo_output = wc.State(A_COPY_path, {
139    ''          : Item(status=' U'),
140    'B/E'       : Item(status=' U'),
141    })
142  expected_elision_output = wc.State(A_COPY_path, {
143    })
144  expected_status = wc.State(A_COPY_path, {
145    ''          : Item(status=' M', wc_rev=8),
146    'D/H/chi'   : Item(status='  ', wc_rev=8),
147    'D/H/psi'   : Item(status='M ', wc_rev=8),
148    'D/H/omega' : Item(status='M ', wc_rev=8),
149    'D/H'       : Item(status='  ', wc_rev=8),
150    'D/G/pi'    : Item(status='  ', wc_rev=8),
151    'D/G/rho'   : Item(status='M ', wc_rev=8),
152    'D/G/tau'   : Item(status='  ', wc_rev=8),
153    'D/G'       : Item(status='  ', wc_rev=8),
154    'D/gamma'   : Item(status='  ', wc_rev=8),
155    'D'         : Item(status='  ', wc_rev=8),
156    'B/lambda'  : Item(status='  ', wc_rev=8),
157    'B/E'       : Item(status=' M', wc_rev=8),
158    'B/E/alpha' : Item(status='  ', wc_rev=8),
159    'B/E/beta'  : Item(status='  ', wc_rev=8),
160    'B/F'       : Item(status='  ', wc_rev=8),
161    'B'         : Item(status='  ', wc_rev=8),
162    'mu'        : Item(status='  ', wc_rev=8),
163    'C'         : Item(status='  ', wc_rev=8),
164    })
165  expected_disk = wc.State('', {
166    ''          : Item(props={SVN_PROP_MERGEINFO : '/A:5-8'}),
167    'D/H/psi'   : Item("New content"),
168    'D/H/chi'   : Item("This is the file 'chi'.\n"),
169    'D/H/omega' : Item("New content"),
170    'D/H'       : Item(),
171    'D/G/pi'    : Item("This is the file 'pi'.\n"),
172    'D/G/rho'   : Item("New content"),
173    'D/G/tau'   : Item("This is the file 'tau'.\n"),
174    'D/G'       : Item(),
175    'D/gamma'   : Item("This is the file 'gamma'.\n"),
176    'D'         : Item(),
177    'B/lambda'  : Item("This is the file 'lambda'.\n"),
178    'B/E'       : Item(props={SVN_PROP_MERGEINFO : ''}),
179    'B/E/alpha' : Item("This is the file 'alpha'.\n"),
180    'B/E/beta'  : Item("This is the file 'beta'.\n"),
181    'B/F'       : Item(),
182    'B'         : Item(),
183    'mu'        : Item("This is the file 'mu'.\n"),
184    'C'         : Item(),
185    })
186  expected_skip = wc.State(A_COPY_path, {
187    'B/E'       : Item(verb='Skipped missing target'),
188    })
189  svntest.actions.run_and_verify_merge(A_COPY_path, '4', '8',
190                                       sbox.repo_url + '/A', None,
191                                       expected_output,
192                                       expected_mergeinfo_output,
193                                       expected_elision_output,
194                                       expected_disk,
195                                       expected_status,
196                                       expected_skip,
197                                       check_props=True)
198
199  # Merge r4:8 into the restricted WC's A_COPY_2.
200  #
201  # As before we expect A_COPY_2/B/E to be skipped because we can't access the
202  # source but now the destination paths A_COPY_2/D/G, A_COPY_2/D/G/rho, and
203  # A_COPY_2/D/H/psi should also be skipped because our test user doesn't have
204  # access.
205  #
206  # After the merge the parents of the missing dest paths, A_COPY_2/D and
207  # A_COPY_2/D/H get non-inheritable mergeinfo.  Those parents' children that
208  # *are* present and are affected by the merge, only A_COPY_2/D/H/omega in
209  # this case, get their own mergeinfo.  Note that A_COPY_2/D/H is both the
210  # parent of a missing child and the sibling of missing child, but the former
211  # always takes precedence in terms of getting *non*-inheritable mergeinfo.
212  expected_output = wc.State(A_COPY_2_path, {
213    'D/H/omega' : Item(status='U '),
214    # Below the skip
215    'D/G/rho'   : Item(status='  ', treeconflict='U'),
216    })
217  expected_mergeinfo_output = wc.State(A_COPY_2_path, {
218    ''          : Item(status=' U'),
219    'D'         : Item(status=' U'),
220    'D/H'       : Item(status=' U'),
221    'D/H/omega' : Item(status=' U'),
222    'B/E'       : Item(status=' U'),
223    })
224  expected_elision_output = wc.State(A_COPY_2_path, {
225    })
226  expected_status = wc.State(A_COPY_2_path, {
227    ''          : Item(status=' M', wc_rev=8),
228    'D/H/chi'   : Item(status='  ', wc_rev=8),
229    'D/H/omega' : Item(status='MM', wc_rev=8),
230    'D/H'       : Item(status=' M', wc_rev=8),
231    'D/gamma'   : Item(status='  ', wc_rev=8),
232    'D'         : Item(status=' M', wc_rev=8),
233    'B/lambda'  : Item(status='  ', wc_rev=8),
234    'B/E'       : Item(status=' M', wc_rev=8),
235    'B/E/alpha' : Item(status='  ', wc_rev=8),
236    'B/E/beta'  : Item(status='  ', wc_rev=8),
237    'B/F'       : Item(status='  ', wc_rev=8),
238    'B'         : Item(status='  ', wc_rev=8),
239    'mu'        : Item(status='  ', wc_rev=8),
240    'C'         : Item(status='  ', wc_rev=8),
241    })
242  expected_disk = wc.State('', {
243    ''          : Item(props={SVN_PROP_MERGEINFO : '/A:5-8'}),
244    'D/H/omega' : Item("New content",
245                       props={SVN_PROP_MERGEINFO : '/A/D/H/omega:5-8'}),
246    'D/H/chi'   : Item("This is the file 'chi'.\n"),
247    'D/H'       : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-8*'}),
248    'D/gamma'   : Item("This is the file 'gamma'.\n"),
249    'D'         : Item(props={SVN_PROP_MERGEINFO : '/A/D:5-8*'}),
250    'B/lambda'  : Item("This is the file 'lambda'.\n"),
251    'B/E'       : Item(props={SVN_PROP_MERGEINFO : ''}),
252    'B/E/alpha' : Item("This is the file 'alpha'.\n"),
253    'B/E/beta'  : Item("This is the file 'beta'.\n"),
254    'B/F'       : Item(),
255    'B'         : Item(),
256    'mu'        : Item("This is the file 'mu'.\n"),
257    'C'         : Item(),
258    })
259  expected_skip = wc.State(A_COPY_2_path, {
260    'B/E'     : Item(verb='Skipped missing target'),
261    'D/G'     : Item(verb='Skipped missing target'),
262    'D/H/psi' : Item(verb='Skipped missing target'),
263    })
264  svntest.actions.run_and_verify_merge(A_COPY_2_path, '4', '8',
265                                       sbox.repo_url + '/A', None,
266                                       expected_output,
267                                       expected_mergeinfo_output,
268                                       expected_elision_output,
269                                       expected_disk,
270                                       expected_status,
271                                       expected_skip,
272                                       check_props=True)
273
274  # Merge r5:7 into the restricted WC's A_COPY_3.
275  #
276  # Again A_COPY_3/B/E should be skipped, but because we can't access the
277  # source *or* the destination we expect its parent A_COPY_3/B to get
278  # non-inheritable mergeinfo.  A_COPY_3B's two existing siblings,
279  # A_COPY_3/B/F and A_COPY_3/B/lambda are untouched by the merge so
280  # neither gets any mergeinfo recorded.
281  expected_output = wc.State(A_COPY_3_path, {
282    'D/G/rho' : Item(status='U '),
283    })
284  expected_mergeinfo_output = wc.State(A_COPY_3_path, {
285    ''  : Item(status=' U'),
286    'B' : Item(status=' U'),
287    })
288  expected_elision_output = wc.State(A_COPY_3_path, {
289    })
290  expected_status = wc.State(A_COPY_3_path, {
291    ''          : Item(status=' M', wc_rev=8),
292    'D/H/chi'   : Item(status='  ', wc_rev=8),
293    'D/H/omega' : Item(status='  ', wc_rev=8),
294    'D/H/psi'   : Item(status='  ', wc_rev=8),
295    'D/H'       : Item(status='  ', wc_rev=8),
296    'D/gamma'   : Item(status='  ', wc_rev=8),
297    'D'         : Item(status='  ', wc_rev=8),
298    'D/G'       : Item(status='  ', wc_rev=8),
299    'D/G/pi'    : Item(status='  ', wc_rev=8),
300    'D/G/rho'   : Item(status='M ', wc_rev=8),
301    'D/G/tau'   : Item(status='  ', wc_rev=8),
302    'B/lambda'  : Item(status='  ', wc_rev=8),
303    'B/F'       : Item(status='  ', wc_rev=8),
304    'B'         : Item(status=' M', wc_rev=8),
305    'mu'        : Item(status='  ', wc_rev=8),
306    'C'         : Item(status='  ', wc_rev=8),
307    })
308  expected_disk = wc.State('', {
309    ''          : Item(props={SVN_PROP_MERGEINFO : '/A:6-7'}),
310    'D/H/omega' : Item("This is the file 'omega'.\n"),
311    'D/H/chi'   : Item("This is the file 'chi'.\n"),
312    'D/H/psi'   : Item("This is the file 'psi'.\n"),
313    'D/H'       : Item(),
314    'D/gamma'   : Item("This is the file 'gamma'.\n"),
315    'D'         : Item(),
316    'D/G'       : Item(),
317    'D/G/pi'    : Item("This is the file 'pi'.\n"),
318    'D/G/rho'   : Item("New content"),
319    'D/G/tau'   : Item("This is the file 'tau'.\n"),
320    'B/lambda'  : Item("This is the file 'lambda'.\n"),
321    'B/F'       : Item(),
322    'B'         : Item(props={SVN_PROP_MERGEINFO : '/A/B:6-7*'}),
323    'mu'        : Item("This is the file 'mu'.\n"),
324    'C'         : Item(),
325    })
326  expected_skip = wc.State(A_COPY_3_path,
327                           {'B/E' : Item(verb='Skipped missing target')})
328  svntest.actions.run_and_verify_merge(A_COPY_3_path, '5', '7',
329                                       sbox.repo_url + '/A', None,
330                                       expected_output,
331                                       expected_mergeinfo_output,
332                                       expected_elision_output,
333                                       expected_disk,
334                                       expected_status,
335                                       expected_skip,
336                                       check_props=True)
337  svntest.actions.run_and_verify_svn(None, [], 'revert', '--recursive',
338                                     wc_restricted)
339
340  # Test issue #2997.  If a merge requires two separate editor drives and the
341  # first is non-operative we should still update the mergeinfo to reflect
342  # this.
343  #
344  # Merge -c5 -c8 to the restricted WC's A_COPY_2/D/H.  r5 gets merged first
345  # but is a no-op, r8 get's merged next and is operative so the mergeinfo
346  # should be updated on the merge target to reflect both merges.
347  expected_output = wc.State(A_COPY_2_H_path, {
348    'omega' : Item(status='U '),
349    })
350  expected_elision_output = wc.State(A_COPY_2_H_path, {
351    })
352  expected_status = wc.State(A_COPY_2_H_path, {
353    ''      : Item(status=' M', wc_rev=8),
354    'chi'   : Item(status='  ', wc_rev=8),
355    'omega' : Item(status='MM', wc_rev=8),
356    })
357  expected_disk = wc.State('', {
358    ''      : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5*,8*'}),
359    'omega' : Item("New content",
360                   props={SVN_PROP_MERGEINFO : '/A/D/H/omega:8'}),
361    'chi'   : Item("This is the file 'chi'.\n"),
362    })
363  expected_skip = wc.State(A_COPY_2_H_path, {
364    'psi'   : Item(verb='Skipped missing target'),
365    })
366  # Note we don't bother checking expected mergeinfo output because the
367  # multiple merges being performed here, -c5 and -c8, will result in
368  # first ' U' and then ' G' mergeinfo notifications.  Our expected
369  # tree structures can't handle checking for multiple values for the
370  # same key.
371  svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '4', '5',
372                                       sbox.repo_url + '/A/D/H', None,
373                                       expected_output,
374                                       None, # expected_mergeinfo_output,
375                                       expected_elision_output,
376                                       expected_disk,
377                                       expected_status,
378                                       expected_skip,
379                                       [], True, False,
380                                       '-c5', '-c8',
381                                       A_COPY_2_H_path)
382
383  # Test issue #2829 'Improve handling for skipped paths encountered
384  # during a merge'
385
386  # Revert previous changes to restricted WC
387  svntest.actions.run_and_verify_svn(None, [], 'revert', '--recursive',
388                                     wc_restricted)
389  # Add new path 'A/D/H/zeta'
390  svntest.main.file_write(zeta_path, "This is the file 'zeta'.\n")
391  svntest.actions.run_and_verify_svn(None, [], 'add', zeta_path)
392  expected_output = wc.State(wc_dir, {'A/D/H/zeta' : Item(verb='Adding')})
393  wc_status.add({'A/D/H/zeta' : Item(status='  ', wc_rev=9)})
394  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
395                                        wc_status)
396
397  # Merge -r7:9 to the restricted WC's A_COPY_2/D/H.
398  #
399  # r9 adds a path, 'A_COPY_2/D/H/zeta', which has a missing sibling 'psi',
400  # but since 'psi' is untouched by the merge it isn't skipped, and since it
401  # isn't skipped, its parent 'A_COPY_2/D/H' won't get non-inheritable
402  # mergeinfo set on it to describe the merge, so none of the parent's
403  # children will get explicit mergeinfo -- see issue #4056.
404  expected_output = wc.State(A_COPY_2_H_path, {
405    'omega' : Item(status='U '),
406    'zeta'  : Item(status='A '),
407    })
408  expected_mergeinfo_output = wc.State(A_COPY_2_H_path, {
409    ''      : Item(status=' U'),
410    'omega' : Item(status=' U'),
411    })
412  expected_elision_output = wc.State(A_COPY_2_H_path, {
413    'omega' : Item(status=' U'),
414    })
415  expected_status = wc.State(A_COPY_2_H_path, {
416    ''      : Item(status=' M', wc_rev=8),
417    'chi'   : Item(status='  ', wc_rev=8),
418    'omega' : Item(status='M ', wc_rev=8),
419    'zeta'  : Item(status='A ', copied='+', wc_rev='-'),
420    })
421  expected_disk = wc.State('', {
422    ''      : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:8-9'}),
423    'omega' : Item("New content"),
424    'chi'   : Item("This is the file 'chi'.\n"),
425    'zeta'  : Item("This is the file 'zeta'.\n"),
426    })
427  expected_skip = wc.State(A_COPY_2_H_path, {})
428  svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '7', '9',
429                                       sbox.repo_url + '/A/D/H', None,
430                                       expected_output,
431                                       expected_mergeinfo_output,
432                                       expected_elision_output,
433                                       expected_disk,
434                                       expected_status,
435                                       expected_skip,
436                                       check_props=True)
437
438  # Merge -r4:9 to the restricted WC's A_COPY_2/D/H.
439  #
440  # r9 adds a path, 'A_COPY_2/D/H/zeta', which has a parent with
441  # non-inheritable mergeinfo (due to the fact 'A_COPY_2/D/H/psi' is missing
442  # and skipped). 'A_COPY_2/D/H/zeta' must therefore get its own explicit
443  # mergeinfo from this merge.
444  svntest.actions.run_and_verify_svn(None, [], 'revert', '--recursive',
445                                     wc_restricted)
446  expected_output = wc.State(A_COPY_2_H_path, {
447    'omega' : Item(status='U '),
448    'zeta'  : Item(status='A '),
449    })
450  expected_mergeinfo_output = wc.State(A_COPY_2_H_path, {
451    ''      : Item(status=' U'),
452    'omega' : Item(status=' U'),
453    'zeta'  : Item(status=' U'),
454    })
455  expected_elision_output = wc.State(A_COPY_2_H_path, {
456    })
457  expected_status = wc.State(A_COPY_2_H_path, {
458    ''      : Item(status=' M', wc_rev=8),
459    'chi'   : Item(status='  ', wc_rev=8),
460    'omega' : Item(status='MM', wc_rev=8),
461    'zeta'  : Item(status='A ', copied='+', wc_rev='-'),
462    })
463  expected_disk = wc.State('', {
464    ''      : Item(props={SVN_PROP_MERGEINFO : '/A/D/H:5-9*'}),
465    'omega' : Item("New content",
466                   props={SVN_PROP_MERGEINFO : '/A/D/H/omega:5-9'}),
467    'chi'   : Item("This is the file 'chi'.\n"),
468    'zeta'  : Item("This is the file 'zeta'.\n",
469                   props={SVN_PROP_MERGEINFO : '/A/D/H/zeta:9'}),
470    })
471  expected_skip = wc.State(A_COPY_2_H_path, {
472    'psi' : Item(verb='Skipped missing target'),
473    })
474  svntest.actions.run_and_verify_merge(A_COPY_2_H_path, '4', '9',
475                                       sbox.repo_url + '/A/D/H', None,
476                                       expected_output,
477                                       expected_mergeinfo_output,
478                                       expected_elision_output,
479                                       expected_disk,
480                                       expected_status,
481                                       expected_skip,
482                                       check_props=True)
483
484@SkipUnless(server_has_mergeinfo)
485@Issue(2876)
486def merge_fails_if_subtree_is_deleted_on_src(sbox):
487  "merge fails if subtree is deleted on src"
488
489  ## See https://issues.apache.org/jira/browse/SVN-2876. ##
490
491  # Create a WC
492  sbox.build()
493  wc_dir = sbox.wc_dir
494
495  if is_ra_type_svn() or is_ra_type_dav():
496    write_authz_file(sbox, {"/" : "* = rw",
497                            "/unrelated" : ("* =\n" +
498                             svntest.main.wc_author2 + " = rw")})
499
500  # Some paths we'll care about
501  Acopy_path = sbox.ospath('A_copy')
502  gamma_path = sbox.ospath('A/D/gamma')
503  Acopy_gamma_path = sbox.ospath('A_copy/D/gamma')
504  Acopy_D_path = sbox.ospath('A_copy/D')
505  A_url = sbox.repo_url + '/A'
506  Acopy_url = sbox.repo_url + '/A_copy'
507
508  # Contents to be added to 'gamma'
509  new_content = "line1\nline2\nline3\nline4\nline5\n"
510
511  svntest.main.file_write(gamma_path, new_content)
512
513  # Create expected output tree for commit
514  expected_output = wc.State(wc_dir, {
515    'A/D/gamma' : Item(verb='Sending'),
516    })
517
518  # Create expected status tree for commit
519  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
520  expected_status.tweak('A/D/gamma', wc_rev=2)
521
522  # Commit the new content
523  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
524                                        expected_status)
525
526  svntest.actions.run_and_verify_svn(None, [], 'cp', A_url, Acopy_url,
527                                     '-m', 'create a new copy of A')
528
529  # Update working copy
530  svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir)
531
532  svntest.main.file_substitute(gamma_path, "line1", "this is line1")
533  # Create expected output tree for commit
534  expected_output = wc.State(wc_dir, {
535    'A/D/gamma' : Item(verb='Sending'),
536    })
537
538  # Create expected status tree for commit
539  expected_status.tweak(wc_rev=3)
540  expected_status.tweak('A/D/gamma', wc_rev=4)
541  expected_status.add({
542    'A_copy'          : Item(status='  ', wc_rev=3),
543    'A_copy/B'        : Item(status='  ', wc_rev=3),
544    'A_copy/B/lambda' : Item(status='  ', wc_rev=3),
545    'A_copy/B/E'      : Item(status='  ', wc_rev=3),
546    'A_copy/B/E/alpha': Item(status='  ', wc_rev=3),
547    'A_copy/B/E/beta' : Item(status='  ', wc_rev=3),
548    'A_copy/B/F'      : Item(status='  ', wc_rev=3),
549    'A_copy/mu'       : Item(status='  ', wc_rev=3),
550    'A_copy/C'        : Item(status='  ', wc_rev=3),
551    'A_copy/D'        : Item(status='  ', wc_rev=3),
552    'A_copy/D/gamma'  : Item(status='  ', wc_rev=3),
553    'A_copy/D/G'      : Item(status='  ', wc_rev=3),
554    'A_copy/D/G/pi'   : Item(status='  ', wc_rev=3),
555    'A_copy/D/G/rho'  : Item(status='  ', wc_rev=3),
556    'A_copy/D/G/tau'  : Item(status='  ', wc_rev=3),
557    'A_copy/D/H'      : Item(status='  ', wc_rev=3),
558    'A_copy/D/H/chi'  : Item(status='  ', wc_rev=3),
559    'A_copy/D/H/omega': Item(status='  ', wc_rev=3),
560    'A_copy/D/H/psi'  : Item(status='  ', wc_rev=3),
561    })
562
563  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
564                                        expected_status)
565
566  # Delete A/D/gamma from working copy
567  svntest.actions.run_and_verify_svn(None, [], 'delete', gamma_path)
568  # Create expected output tree for commit
569  expected_output = wc.State(wc_dir, {
570    'A/D/gamma' : Item(verb='Deleting'),
571    })
572
573  expected_status.remove('A/D/gamma')
574
575  svntest.actions.run_and_verify_commit(wc_dir,
576                                        expected_output,
577                                        expected_status,
578                                        [],
579                                        wc_dir, wc_dir)
580  svntest.actions.run_and_verify_svn(
581    expected_merge_output([[3,4]],
582                          ['U    ' + Acopy_gamma_path + '\n',
583                           ' U   ' + Acopy_gamma_path + '\n']),
584    [], 'merge', '-r1:4',
585    A_url + '/D/gamma' + '@4',
586    Acopy_gamma_path)
587
588  # r6: create an empty (unreadable) commit.
589  # Empty or unreadable revisions used to crash a svn 1.6+ client when
590  # used with a 1.5 server:
591  # http://svn.haxx.se/dev/archive-2009-04/0476.shtml
592  svntest.main.run_svn(None, 'mkdir', sbox.repo_url + '/unrelated',
593                       '--username', svntest.main.wc_author2,
594                       '-m', 'creating a rev with no paths.')
595
596  # A delete merged ontop of a modified file is normally a tree conflict,
597  # see notes/tree-conflicts/detection.txt, but --force currently avoids
598  # this.
599  svntest.actions.run_and_verify_svn(
600    expected_merge_output([[3,6]],
601                          ['D    ' + Acopy_gamma_path + '\n',
602                           ' U   ' + Acopy_path + '\n']),
603    [], 'merge', '-r1:6', '--force',
604    A_url, Acopy_path)
605
606@SkipUnless(svntest.main.server_has_mergeinfo)
607@Skip(svntest.main.is_ra_type_file)
608@Issue(3242)
609def reintegrate_fails_if_no_root_access(sbox):
610  "reintegrate fails if no root access"
611
612  # If a user is authorized to a reintegrate source and target, they
613  # should be able to reintegrate, regardless of what authorization
614  # they have to parents of the source and target.
615  #
616  # See https://issues.apache.org/jira/browse/SVN-3242#desc78
617
618  # Some paths we'll care about
619  wc_dir = sbox.wc_dir
620  A_path          = sbox.ospath('A')
621  A_COPY_path     = sbox.ospath('A_COPY')
622  beta_COPY_path  = sbox.ospath('A_COPY/B/E/beta')
623  rho_COPY_path   = sbox.ospath('A_COPY/D/G/rho')
624  omega_COPY_path = sbox.ospath('A_COPY/D/H/omega')
625  psi_COPY_path   = sbox.ospath('A_COPY/D/H/psi')
626
627  # Copy A@1 to A_COPY in r2, and then make some changes to A in r3-6.
628  sbox.build()
629  wc_dir = sbox.wc_dir
630  expected_disk, expected_status = set_up_branch(sbox)
631
632  # Make a change on the branch, to A_COPY/mu, commit in r7.
633  svntest.main.file_write(sbox.ospath("A_COPY/mu"),
634                          "Changed on the branch.")
635  expected_output = wc.State(wc_dir, {'A_COPY/mu' : Item(verb='Sending')})
636  expected_status.tweak('A_COPY/mu', wc_rev=7)
637  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
638                                        expected_status)
639  expected_disk.tweak('A_COPY/mu', contents='Changed on the branch.')
640
641  # Update the WC.
642  svntest.main.run_svn(None, 'up', wc_dir)
643
644
645  # Sync A_COPY with A.
646  expected_output = expected_merge_output([[2,7]],
647                                          ['U    ' + beta_COPY_path  + '\n',
648                                           'U    ' + rho_COPY_path   + '\n',
649                                           'U    ' + omega_COPY_path + '\n',
650                                           'U    ' + psi_COPY_path   + '\n',
651                                           # Mergeinfo notification
652                                           ' U   ' + A_COPY_path     + '\n'])
653  svntest.actions.run_and_verify_svn(expected_output, [], 'merge',
654                                     sbox.repo_url + '/A', A_COPY_path)
655  sbox.simple_commit(message='synch A_COPY with A')
656
657  # Update so we are ready for reintegrate.
658  svntest.main.run_svn(None, 'up', wc_dir)
659
660  # Change authz file so everybody has access to everything but the root.
661  if is_ra_type_svn() or is_ra_type_dav():
662    write_restrictive_svnserve_conf(sbox.repo_dir)
663    write_authz_file(sbox, {"/"       : "* =",
664                            "/A"      : "* = rw",
665                            "/A_COPY" : "* = rw",
666                            "/iota"   : "* = rw"})
667
668  # Now reintegrate A_COPY back to A.  The lack of access to the root of the
669  # repository shouldn't be a problem.
670  expected_output = wc.State(A_path, {
671    'mu'           : Item(status='U '),
672    })
673  expected_mergeinfo_output = wc.State(A_path, {
674    '' : Item(status=' U'),
675    })
676  expected_elision_output = wc.State(A_path, {
677    })
678  expected_disk = wc.State('', {
679    ''          : Item(props={SVN_PROP_MERGEINFO : '/A_COPY:2-8'}),
680    'B'         : Item(),
681    'B/lambda'  : Item("This is the file 'lambda'.\n"),
682    'B/E'       : Item(),
683    'B/E/alpha' : Item("This is the file 'alpha'.\n"),
684    'B/E/beta'  : Item("New content"),
685    'B/F'       : Item(),
686    'mu'        : Item("Changed on the branch."),
687    'C'         : Item(),
688    'D'         : Item(),
689    'D/gamma'   : Item("This is the file 'gamma'.\n"),
690    'D/G'       : Item(),
691    'D/G/pi'    : Item("This is the file 'pi'.\n"),
692    'D/G/rho'   : Item("New content"),
693    'D/G/tau'   : Item("This is the file 'tau'.\n"),
694    'D/H'       : Item(),
695    'D/H/chi'   : Item("This is the file 'chi'.\n"),
696    'D/H/omega' : Item("New content"),
697    'D/H/psi'   : Item("New content"),
698  })
699  expected_status = wc.State(A_path, {
700    "B"            : Item(status='  ', wc_rev=8),
701    "B/lambda"     : Item(status='  ', wc_rev=8),
702    "B/E"          : Item(status='  ', wc_rev=8),
703    "B/E/alpha"    : Item(status='  ', wc_rev=8),
704    "B/E/beta"     : Item(status='  ', wc_rev=8),
705    "B/F"          : Item(status='  ', wc_rev=8),
706    "mu"           : Item(status='M ', wc_rev=8),
707    "C"            : Item(status='  ', wc_rev=8),
708    "D"            : Item(status='  ', wc_rev=8),
709    "D/gamma"      : Item(status='  ', wc_rev=8),
710    "D/G"          : Item(status='  ', wc_rev=8),
711    "D/G/pi"       : Item(status='  ', wc_rev=8),
712    "D/G/rho"      : Item(status='  ', wc_rev=8),
713    "D/G/tau"      : Item(status='  ', wc_rev=8),
714    "D/H"          : Item(status='  ', wc_rev=8),
715    "D/H/chi"      : Item(status='  ', wc_rev=8),
716    "D/H/omega"    : Item(status='  ', wc_rev=8),
717    "D/H/psi"      : Item(status='  ', wc_rev=8),
718    ""             : Item(status=' M', wc_rev=8),
719  })
720  expected_skip = wc.State(A_path, {})
721  svntest.actions.run_and_verify_merge(A_path, None, None,
722                                       sbox.repo_url + '/A_COPY', None,
723                                       expected_output,
724                                       expected_mergeinfo_output,
725                                       expected_elision_output,
726                                       expected_disk,
727                                       expected_status,
728                                       expected_skip,
729                                       [], True, True,
730                                       '--reintegrate', A_path)
731
732def diff_unauth_parent(sbox):
733  "diff directory without reading parent"
734
735  sbox.build(create_wc=False)
736
737  # Create r2: Change A a bit
738  svntest.actions.run_and_verify_svnmucc(None, [],
739                                         'propset', 'k', 'v',
740                                         sbox.repo_url + '/A',
741                                         '-m', 'set prop')
742
743  # Create r3 Mark E and G
744  svntest.actions.run_and_verify_svnmucc(None, [],
745                                         'propset', 'this-is', 'E',
746                                         sbox.repo_url + '/A/B/E',
747                                         'propset', 'this-is', 'G',
748                                         sbox.repo_url + '/A/D/G',
749                                         '-m', 'set prop')
750
751  # Create r4: Replace A/B/E with A/D/G
752  svntest.actions.run_and_verify_svnmucc(None, [],
753                                         'rm', sbox.repo_url + '/A/B/E',
754                                         'cp', '3', sbox.repo_url + '/A/D/G',
755                                         sbox.repo_url + '/A/B/E',
756                                         '-m', 'replace A/B/E')
757
758
759  if is_ra_type_svn() or is_ra_type_dav():
760    write_restrictive_svnserve_conf(sbox.repo_dir)
761    write_authz_file(sbox, {"/"       : "* =",
762                            "/A"    : "* = rw"})
763
764  # Diff the property change
765  expected_output = [
766    'Index: .\n',
767    '===================================================================\n',
768    '--- .\t(revision 1)\n',
769    '+++ .\t(revision 2)\n',
770    '\n',
771    'Property changes on: .\n',
772    '___________________________________________________________________\n',
773    'Added: k\n',
774    '## -0,0 +1 ##\n',
775    '+v\n',
776    '\ No newline at end of property\n'
777  ]
778  svntest.actions.run_and_verify_svn(expected_output, [],
779                                     'diff', sbox.repo_url + '/A', '-c', '2')
780
781  if is_ra_type_svn() or is_ra_type_dav():
782    write_authz_file(sbox, {"/"       : "* =",
783                            "/A/B/E"    : "* = rw"})
784
785  # Diff the replacement
786  expected_output = [
787    'Index: alpha\n',
788    '===================================================================\n',
789    '--- alpha\t(revision 3)\n',
790    '+++ alpha\t(nonexistent)\n',
791    '@@ -1 +0,0 @@\n',
792    '-This is the file \'alpha\'.\n',
793    'Index: beta\n',
794    '===================================================================\n',
795    '--- beta\t(revision 3)\n',
796    '+++ beta\t(nonexistent)\n',
797    '@@ -1 +0,0 @@\n',
798    '-This is the file \'beta\'.\n',
799    'Index: tau\n',
800    '===================================================================\n',
801    '--- tau\t(nonexistent)\n',
802    '+++ tau\t(revision 4)\n',
803    '@@ -0,0 +1 @@\n',
804    '+This is the file \'tau\'.\n',
805    'Index: rho\n',
806    '===================================================================\n',
807    '--- rho\t(nonexistent)\n',
808    '+++ rho\t(revision 4)\n',
809    '@@ -0,0 +1 @@\n',
810    '+This is the file \'rho\'.\n',
811    'Index: pi\n',
812    '===================================================================\n',
813    '--- pi\t(nonexistent)\n',
814    '+++ pi\t(revision 4)\n',
815    '@@ -0,0 +1 @@\n',
816    '+This is the file \'pi\'.\n',
817  ]
818
819  if is_ra_type_svn() or is_ra_type_dav():
820    # Because we can't anchor above C we see just a changed C, not a
821    # replacement
822    expected_output += [
823    'Index: .\n',
824    '===================================================================\n',
825    '--- .\t(revision 3)\n',
826    '+++ .\t(revision 4)\n',
827    '\n',
828    'Property changes on: .\n',
829    '___________________________________________________________________\n',
830      'Modified: this-is\n',
831      '## -1 +1 ##\n',
832      '-E\n',
833      '\ No newline at end of property\n',
834      '+G\n',
835      '\ No newline at end of property\n',
836    ]
837  else:
838    # ### We should also see a property deletion here!
839    expected_output += [
840    'Index: .\n',
841    '===================================================================\n',
842    '--- .\t(revision 3)\n',
843    '+++ .\t(nonexistent)\n',
844    '\n',
845    'Property changes on: .\n',
846    '___________________________________________________________________\n',
847    'Deleted: this-is\n',
848    '## -1 +0,0 ##\n',
849    '-E\n',
850    '\ No newline at end of property\n',
851    'Index: .\n',
852    '===================================================================\n',
853    '--- .\t(nonexistent)\n',
854    '+++ .\t(revision 4)\n',
855    '\n',
856    'Property changes on: .\n',
857    '___________________________________________________________________\n',
858      'Added: this-is\n',
859      '## -0,0 +1 ##\n',
860      '+G\n',
861      '\ No newline at end of property\n',
862    ]
863
864  # Use two url diff, because 'svn diff url -c' uses copyfrom to diff against
865  expected_output = svntest.verify.UnorderedOutput(expected_output)
866  svntest.actions.run_and_verify_svn(expected_output, [],
867                                     'diff', sbox.repo_url + '/A/B/E@3',
868                                      sbox.repo_url + '/A/B/E@4',
869                                      '--notice-ancestry')
870
871  # Do the same thing with summarize to really see directory deletes and adds
872  if is_ra_type_svn() or is_ra_type_dav():
873    # With no rights on the parent directory we just see a property change on E
874    expected_output = [
875      'D       %s/A/B/E/alpha\n' % sbox.repo_url,
876      'D       %s/A/B/E/beta\n' % sbox.repo_url,
877      'A       %s/A/B/E/tau\n' % sbox.repo_url,
878      'A       %s/A/B/E/rho\n' % sbox.repo_url,
879      'A       %s/A/B/E/pi\n' % sbox.repo_url,
880      ' M      %s/A/B/E\n' % sbox.repo_url,
881    ]
882  else:
883    # But with rights on the parent we see a replacement of E
884    expected_output = [
885      'D       %s/A/B/E/alpha\n' % sbox.repo_url,
886      'D       %s/A/B/E/beta\n' % sbox.repo_url,
887      'D       %s/A/B/E\n' % sbox.repo_url,
888      'A       %s/A/B/E/tau\n' % sbox.repo_url,
889      'A       %s/A/B/E/rho\n' % sbox.repo_url,
890      'A       %s/A/B/E/pi\n' % sbox.repo_url,
891      'A       %s/A/B/E\n' % sbox.repo_url,
892    ]
893
894  expected_output = svntest.verify.UnorderedOutput(expected_output)
895  svntest.actions.run_and_verify_svn(expected_output, [],
896                                     'diff', sbox.repo_url + '/A/B/E@3',
897                                      sbox.repo_url + '/A/B/E@4',
898                                      '--notice-ancestry', '--summarize')
899
900########################################################################
901# Run the tests
902
903
904# list all tests here, starting with None:
905test_list = [ None,
906              mergeinfo_and_skipped_paths,
907              merge_fails_if_subtree_is_deleted_on_src,
908              reintegrate_fails_if_no_root_access,
909              diff_unauth_parent,
910             ]
911serial_only = True
912
913if __name__ == '__main__':
914  svntest.main.run_tests(test_list, serial_only = serial_only)
915  # NOTREACHED
916
917
918### End of file.
919