3 # merge_authz_tests.py: merge tests that need to write an authz file
5 # Subversion is a tool for revision control.
6 # See http://subversion.tigris.org for more information.
8 # ====================================================================
9 # Copyright (c) 2000-2007 CollabNet. All rights reserved.
11 # This software is licensed as described in the file COPYING, which
12 # you should have received as part of this distribution. The terms
13 # are also available at http://subversion.tigris.org/license-1.html.
14 # If newer versions of this license are posted there, you may use a
15 # newer version instead, at your option.
17 ######################################################################
20 import shutil
, sys
, re
, os
25 from svntest
import wc
29 XFail
= svntest
.testcase
.XFail
30 Skip
= svntest
.testcase
.Skip
31 SkipUnless
= svntest
.testcase
.SkipUnless
33 from merge_tests
import set_up_branch
34 from merge_tests
import shorten_path_kludge
36 from svntest
.main
import SVN_PROP_MERGEINFO
37 from svntest
.main
import write_restrictive_svnserve_conf
38 from svntest
.main
import write_authz_file
39 from svntest
.main
import server_has_mergeinfo
40 from svntest
.actions
import fill_file_with_lines
41 from svntest
.actions
import make_conflict_marker_text
42 from svntest
.actions
import inject_conflict_into_expected_state
44 ######################################################################
47 # Each test must return on success or raise on failure.
50 #----------------------------------------------------------------------
54 # #2893 - Handle merge info for portions of a tree not checked out due
55 # to insufficient authz.
57 # #2997 - If skipped paths come first in operative merge mergeinfo
60 # #2829 - Improve handling for skipped paths encountered during a merge.
61 # This is *not* a full test of issue #2829, see also merge_tests.py,
62 # search for "2829". This tests the problem where a merge adds a path
63 # with a missing sibling and so needs its own explicit mergeinfo.
64 def mergeinfo_and_skipped_paths(sbox
):
65 "skipped paths get overriding mergeinfo"
67 # Test that we override the mergeinfo for child paths which weren't
68 # actually merged because they were skipped.
70 # This test covers paths skipped because:
72 # 1) The source of a merge is inaccessible due to authz restrictions.
73 # 2) Destination of merge is inaccessible due to authz restrictions.
74 # 3) Source *and* destination of merge is inaccessible due to authz
76 # 4) File path is versioned but is missing from disk due to OS deletion.
77 # This isn't technically part of issue #2893 but we handle this case
78 # and it didn't warrant its own test).
80 # Eventually we should also test(?):
82 # 5) Dir path is versioned but is missing from disk due to an OS deletion.
86 wc_disk
, wc_status
= set_up_branch(sbox
, False, 3)
88 # Create a restrictive authz where part of the merge source and part
89 # of the target are inaccesible.
90 write_restrictive_svnserve_conf(sbox
.repo_dir
)
91 write_authz_file(sbox
, {"/" : svntest
.main
.wc_author
+"=rw",
92 # Make a directory in the merge source inaccessible.
93 "/A/B/E" : svntest
.main
.wc_author
+ "=",
94 # Make a file and dir in the merge destination
96 "/A_COPY_2/D/H/psi" : svntest
.main
.wc_author
+ "=",
97 "/A_COPY_2/D/G" : svntest
.main
.wc_author
+ "=",
98 # Make the source and destination inaccessible.
99 "/A_COPY_3/B/E" : svntest
.main
.wc_author
+ "=",
102 # Checkout just the branch under the newly restricted authz.
103 wc_restricted
= sbox
.add_wc_path('restricted')
104 svntest
.actions
.run_and_verify_svn(None, None, [], 'checkout',
108 # Some paths we'll use in the second WC.
109 A_COPY_path
= os
.path
.join(wc_restricted
, "A_COPY")
110 A_COPY_2_path
= os
.path
.join(wc_restricted
, "A_COPY_2")
111 A_COPY_2_H_path
= os
.path
.join(wc_restricted
, "A_COPY_2", "D", "H")
112 A_COPY_3_path
= os
.path
.join(wc_restricted
, "A_COPY_3")
113 omega_path
= os
.path
.join(wc_restricted
, "A_COPY", "D", "H", "omega")
114 zeta_path
= os
.path
.join(wc_dir
, "A", "D", "H", "zeta")
116 # Restrict access to some more of the merge destination the
117 # old fashioned way, delete it via the OS.
118 ### TODO: Delete a versioned directory?
119 os
.remove(omega_path
)
121 # Merge r4:8 into the restricted WC's A_COPY.
123 # Search for the comment entitled "The Merge Kluge" elsewhere in
124 # this file, to understand why we shorten and chdir() below.
126 # We expect A_COPY/B/E to be skipped because we can't access the source
127 # and A_COPY/D/H/omega because it is missing. Since we have A_COPY/B/E
128 # we should override it's inherited mergeinfo, giving it just what it
129 # inherited from A_COPY before the merge. omega is missing, but since
130 # it is a file we can record the fact that it is missing in its parent
131 # directory A_COPY/D/H.
132 short_path
= shorten_path_kludge(A_COPY_path
)
133 expected_output
= wc
.State(short_path
, {
134 'D/G/rho' : Item(status
='U '),
135 'D/H/psi' : Item(status
='U '),
137 expected_status
= wc
.State(short_path
, {
138 '' : Item(status
=' M', wc_rev
=8),
139 'D/H/chi' : Item(status
=' ', wc_rev
=8),
140 'D/H/psi' : Item(status
='M ', wc_rev
=8),
141 'D/H/omega' : Item(status
='!M', wc_rev
=8),
142 'D/H' : Item(status
=' ', wc_rev
=8),
143 'D/G/pi' : Item(status
=' ', wc_rev
=8),
144 'D/G/rho' : Item(status
='M ', wc_rev
=8),
145 'D/G/tau' : Item(status
=' ', wc_rev
=8),
146 'D/G' : Item(status
=' ', wc_rev
=8),
147 'D/gamma' : Item(status
=' ', wc_rev
=8),
148 'D' : Item(status
=' ', wc_rev
=8),
149 'B/lambda' : Item(status
=' ', wc_rev
=8),
150 'B/E' : Item(status
=' M', wc_rev
=8),
151 'B/E/alpha' : Item(status
=' ', wc_rev
=8),
152 'B/E/beta' : Item(status
=' ', wc_rev
=8),
153 'B/F' : Item(status
=' ', wc_rev
=8),
154 'B' : Item(status
=' ', wc_rev
=8),
155 'mu' : Item(status
=' ', wc_rev
=8),
156 'C' : Item(status
=' ', wc_rev
=8),
158 expected_disk
= wc
.State('', {
159 '' : Item(props
={SVN_PROP_MERGEINFO
: '/A:5-8'}),
160 'D/H/psi' : Item("New content"),
161 'D/H/chi' : Item("This is the file 'chi'.\n"),
162 # 'D/H/omega' : run_and_verify_merge() doesn't support checking
163 # the props on a missing path, so we do that
164 # manually (see below).
166 'D/G/pi' : Item("This is the file 'pi'.\n"),
167 'D/G/rho' : Item("New content"),
168 'D/G/tau' : Item("This is the file 'tau'.\n"),
170 'D/gamma' : Item("This is the file 'gamma'.\n"),
172 'B/lambda' : Item("This is the file 'lambda'.\n"),
173 'B/E' : Item(props
={SVN_PROP_MERGEINFO
: ''}),
174 'B/E/alpha' : Item("This is the file 'alpha'.\n"),
175 'B/E/beta' : Item("This is the file 'beta'.\n"),
178 'mu' : Item("This is the file 'mu'.\n"),
181 expected_skip
= wc
.State(short_path
, {
182 'D/H/omega' : Item(),
185 saved_cwd
= os
.getcwd()
186 os
.chdir(svntest
.main
.work_dir
)
187 svntest
.actions
.run_and_verify_merge(short_path
, '4', '8',
194 None, None, None, None,
198 # Manually check the props on A_COPY/D/H/omega.
199 svntest
.actions
.run_and_verify_svn(None,
200 ["Properties on '" + omega_path
+ "':\n",
201 ' ' + SVN_PROP_MERGEINFO
+ ' : ' +
203 [], 'pl', '-vR', omega_path
)
205 # Merge r4:8 into the restricted WC's A_COPY_2.
207 # As before we expect A_COPY_2/B/E to be skipped because we can't access the
208 # source but now the destination paths A_COPY_2/D/G, A_COPY_2/D/G/rho, and
209 # A_COPY_2/D/H/psi should also be skipped because our test user doesn't have
212 # After the merge the parents of the missing dest paths, A_COPY_2/D and
213 # A_COPY_2/D/H get non-inheritable mergeinfo. Those parents children that
214 # *are* present, A_COPY_2/D/gamma, A_COPY_2/D/H/chi, and A_COPY_2/D/H/omega
215 # get their own mergeinfo. Note that A_COPY_2/D/H is both the parent of
216 # a missing child and the sibling of missing child, but the former always
217 # takes precedence in terms of getting *non*-inheritable mergeinfo.
218 short_path
= shorten_path_kludge(A_COPY_2_path
)
219 expected_output
= wc
.State(short_path
, {
220 'D/H/omega' : Item(status
='U '),
222 expected_status
= wc
.State(short_path
, {
223 '' : Item(status
=' M', wc_rev
=8),
224 'D/H/chi' : Item(status
=' M', wc_rev
=8),
225 'D/H/omega' : Item(status
='MM', wc_rev
=8),
226 'D/H' : Item(status
=' M', wc_rev
=8),
227 'D/gamma' : Item(status
=' M', wc_rev
=8),
228 'D' : Item(status
=' M', wc_rev
=8),
229 'B/lambda' : Item(status
=' ', wc_rev
=8),
230 'B/E' : Item(status
=' M', wc_rev
=8),
231 'B/E/alpha' : Item(status
=' ', wc_rev
=8),
232 'B/E/beta' : Item(status
=' ', wc_rev
=8),
233 'B/F' : Item(status
=' ', wc_rev
=8),
234 'B' : Item(status
=' ', wc_rev
=8),
235 'mu' : Item(status
=' ', wc_rev
=8),
236 'C' : Item(status
=' ', wc_rev
=8),
238 expected_disk
= wc
.State('', {
239 '' : Item(props
={SVN_PROP_MERGEINFO
: '/A:5-8'}),
240 'D/H/omega' : Item("New content",
241 props
={SVN_PROP_MERGEINFO
: '/A/D/H/omega:5-8'}),
242 'D/H/chi' : Item("This is the file 'chi'.\n",
243 props
={SVN_PROP_MERGEINFO
: '/A/D/H/chi:5-8'}),
244 'D/H' : Item(props
={SVN_PROP_MERGEINFO
: '/A/D/H:5-8*'}),
245 'D/gamma' : Item("This is the file 'gamma'.\n",
246 props
={SVN_PROP_MERGEINFO
: '/A/D/gamma:5-8'}),
247 'D' : Item(props
={SVN_PROP_MERGEINFO
: '/A/D:5-8*'}),
248 'B/lambda' : Item("This is the file 'lambda'.\n"),
249 'B/E' : Item(props
={SVN_PROP_MERGEINFO
: ''}),
250 'B/E/alpha' : Item("This is the file 'alpha'.\n"),
251 'B/E/beta' : Item("This is the file 'beta'.\n"),
254 'mu' : Item("This is the file 'mu'.\n"),
257 expected_skip
= wc
.State(short_path
, {
263 saved_cwd
= os
.getcwd()
264 os
.chdir(svntest
.main
.work_dir
)
265 svntest
.actions
.run_and_verify_merge(short_path
, '4', '8',
272 None, None, None, None,
276 # Merge r5:7 into the restricted WC's A_COPY_3.
278 # Again A_COPY_3/B/E should be skipped, but because we can't access the
279 # source *or* the destination we expect its parent A_COPY_3/B to get
280 # non-inheritable mergeinfo and its two existing siblings, A_COPY_3/B/F
281 # and A_COPY_3/B/lambda to get their own mergeinfo.
282 short_path
= shorten_path_kludge(A_COPY_3_path
)
283 expected_output
= wc
.State(short_path
, {
284 'D/G/rho' : Item(status
='U '),
286 expected_status
= wc
.State(short_path
, {
287 '' : Item(status
=' M', wc_rev
=8),
288 'D/H/chi' : Item(status
=' ', wc_rev
=8),
289 'D/H/omega' : Item(status
=' ', wc_rev
=8),
290 'D/H/psi' : Item(status
=' ', wc_rev
=8),
291 'D/H' : Item(status
=' ', wc_rev
=8),
292 'D/gamma' : Item(status
=' ', wc_rev
=8),
293 'D' : Item(status
=' ', wc_rev
=8),
294 'D/G' : Item(status
=' ', wc_rev
=8),
295 'D/G/pi' : Item(status
=' ', wc_rev
=8),
296 'D/G/rho' : Item(status
='M ', wc_rev
=8),
297 'D/G/tau' : Item(status
=' ', wc_rev
=8),
298 'B/lambda' : Item(status
=' M', wc_rev
=8),
299 'B/F' : Item(status
=' M', wc_rev
=8),
300 'B' : Item(status
=' M', wc_rev
=8),
301 'mu' : Item(status
=' ', wc_rev
=8),
302 'C' : Item(status
=' ', wc_rev
=8),
304 expected_disk
= wc
.State('', {
305 '' : Item(props
={SVN_PROP_MERGEINFO
: '/A:6-7'}),
306 'D/H/omega' : Item("This is the file 'omega'.\n"),
307 'D/H/chi' : Item("This is the file 'chi'.\n"),
308 'D/H/psi' : Item("This is the file 'psi'.\n"),
310 'D/gamma' : Item("This is the file 'gamma'.\n"),
313 'D/G/pi' : Item("This is the file 'pi'.\n"),
314 'D/G/rho' : Item("New content"),
315 'D/G/tau' : Item("This is the file 'tau'.\n"),
316 'B/lambda' : Item("This is the file 'lambda'.\n",
317 props
={SVN_PROP_MERGEINFO
: '/A/B/lambda:6-7'}),
318 'B/F' : Item(props
={SVN_PROP_MERGEINFO
: '/A/B/F:6-7'}),
319 'B' : Item(props
={SVN_PROP_MERGEINFO
: '/A/B:6-7*'}),
320 'mu' : Item("This is the file 'mu'.\n"),
323 expected_skip
= wc
.State(short_path
, {'B/E' : Item()})
324 saved_cwd
= os
.getcwd()
325 os
.chdir(svntest
.main
.work_dir
)
326 svntest
.actions
.run_and_verify_merge(short_path
, '5', '7',
333 None, None, None, None,
336 svntest
.actions
.run_and_verify_svn(None, None, [], 'revert', '--recursive',
339 # Test issue #2997. If a merge requires two separate editor drives and the
340 # first is non-operative we should still update the mergeinfo to reflect
343 # Merge -c5 -c8 to the restricted WC's A_COPY_2/D/H. r5 gets merged first
344 # but is a no-op, r8 get's merged next and is operative so the mergeinfo
345 # should be updated to reflect both merges.
346 short_path
= shorten_path_kludge(A_COPY_2_H_path
)
347 expected_output
= wc
.State(short_path
, {
348 'omega' : Item(status
='U '),
350 expected_status
= wc
.State(short_path
, {
351 '' : Item(status
=' M', wc_rev
=8),
352 'chi' : Item(status
=' M', wc_rev
=8),
353 'omega' : Item(status
='MM', wc_rev
=8),
355 expected_disk
= wc
.State('', {
356 '' : Item(props
={SVN_PROP_MERGEINFO
: '/A/D/H:5*,8*'}),
357 'omega' : Item("New content",
358 props
={SVN_PROP_MERGEINFO
: '/A/D/H/omega:5,8'}),
359 'chi' : Item("This is the file 'chi'.\n",
360 props
={SVN_PROP_MERGEINFO
: '/A/D/H/chi:5,8'}),
362 expected_skip
= wc
.State(short_path
, {'psi' : Item()})
363 saved_cwd
= os
.getcwd()
364 os
.chdir(svntest
.main
.work_dir
)
365 svntest
.actions
.run_and_verify_merge(short_path
, '4', '5',
372 None, None, None, None,
373 None, 1, 0, '-c5', '-c8')
377 # Test issue #2829 'Improve handling for skipped paths encountered
380 # Revert previous changes to restricted WC
381 svntest
.actions
.run_and_verify_svn(None, None, [], 'revert', '--recursive',
383 # Add new path 'A/D/H/zeta'
384 svntest
.main
.file_write(zeta_path
, "This is the file 'zeta'.\n")
385 svntest
.actions
.run_and_verify_svn(None, None, [], 'add', zeta_path
)
386 expected_output
= wc
.State(wc_dir
, {'A/D/H/zeta' : Item(verb
='Adding')})
387 wc_status
.add({'A/D/H/zeta' : Item(status
=' ', wc_rev
=9)})
388 svntest
.actions
.run_and_verify_commit(wc_dir
, expected_output
,
389 wc_status
, None, wc_dir
)
391 # Merge -r7:9 to the restricted WC's A_COPY_2/D/H.
393 # r9 adds a path, 'A_COPY_2/D/H/zeta', which has a parent with
394 # non-inheritable mergeinfo (due to the fact 'A_COPY_2/D/H/psi' is missing).
395 # 'A_COPY_2/D/H/zeta' must therefore get its own explicit mergeinfo from
397 short_path
= shorten_path_kludge(A_COPY_2_H_path
)
398 expected_output
= wc
.State(short_path
, {
399 'omega' : Item(status
='U '),
400 'zeta' : Item(status
='A '),
402 expected_status
= wc
.State(short_path
, {
403 '' : Item(status
=' M', wc_rev
=8),
404 'chi' : Item(status
=' M', wc_rev
=8),
405 'omega' : Item(status
='MM', wc_rev
=8),
406 'zeta' : Item(status
='A ', copied
='+', wc_rev
='-'),
408 expected_disk
= wc
.State('', {
409 '' : Item(props
={SVN_PROP_MERGEINFO
: '/A/D/H:8-9*'}),
410 'omega' : Item("New content",
411 props
={SVN_PROP_MERGEINFO
: '/A/D/H/omega:8-9'}),
412 'chi' : Item("This is the file 'chi'.\n",
413 props
={SVN_PROP_MERGEINFO
: '/A/D/H/chi:8-9'}),
414 'zeta' : Item("This is the file 'zeta'.\n",
415 props
={SVN_PROP_MERGEINFO
: '/A/D/H/zeta:8-9'}),
417 expected_skip
= wc
.State(short_path
, {})
418 saved_cwd
= os
.getcwd()
419 os
.chdir(svntest
.main
.work_dir
)
420 svntest
.actions
.run_and_verify_merge(short_path
, '7', '9',
427 None, None, None, None,
431 ########################################################################
435 # list all tests here, starting with None:
437 SkipUnless(Skip(mergeinfo_and_skipped_paths
,
438 svntest
.main
.is_ra_type_file
),
439 svntest
.main
.server_has_mergeinfo
),
442 if __name__
== '__main__':
443 svntest
.main
.run_tests(test_list
, serial_only
= True)