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 setup_branch
34 from merge_tests
import shorten_path_kludge
36 from svntest
.main
import SVN_PROP_MERGE_INFO
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 #----------------------------------------------------------------------
52 # Test for issue #2893
53 # Handle merge info for portions of a tree not checked out due to
55 def mergeinfo_and_skipped_paths(sbox
):
56 "skipped paths get overriding mergeinfo"
58 # Test that we override the mergeinfo for child paths which weren't
59 # actually merged because they were skipped.
61 # This test covers paths skipped because:
63 # 1) The source of a merge is inaccessible due to authz restrictions.
64 # 2) Destination of merge is inaccessible due to authz restrictions.
65 # 3) Source *and* destination of merge is inaccessible due to authz
67 # 4) File path is versioned but is missing from disk due to OS deletion.
68 # This isn't technically part of issue #2893 but we handle this case
69 # and it didn't warrant its own test).
71 # Eventually we should also test(?):
73 # 5) Dir path is versioned but is missing from disk due to an OS deletion.
77 wc_disk
, wc_status
= setup_branch(sbox
, False, 3)
79 # Create a restrictive authz where part of the merge source and part
80 # of the target are inaccesible.
81 write_restrictive_svnserve_conf(sbox
.repo_dir
)
82 write_authz_file(sbox
, {"/" : svntest
.main
.wc_author
+"=rw",
83 # Make a directory in the merge source inaccessible.
84 "/A/B/E" : svntest
.main
.wc_author
+ "=",
85 # Make a file and dir in the merge destination
87 "/A_COPY_2/D/H/psi" : svntest
.main
.wc_author
+ "=",
88 "/A_COPY_2/D/G" : svntest
.main
.wc_author
+ "=",
89 # Make the source and destination inaccessible.
90 "/A_COPY_3/B/E" : svntest
.main
.wc_author
+ "=",
93 # Checkout just the branch under the newly restricted authz.
94 wc_restricted
= sbox
.add_wc_path('restricted')
95 svntest
.actions
.run_and_verify_svn(None, None, [], 'checkout',
99 # Some paths we'll use in the second WC.
100 A_COPY_path
= os
.path
.join(wc_restricted
, "A_COPY")
101 A_COPY_2_path
= os
.path
.join(wc_restricted
, "A_COPY_2")
102 A_COPY_3_path
= os
.path
.join(wc_restricted
, "A_COPY_3")
103 omega_path
= os
.path
.join(wc_restricted
, "A_COPY", "D", "H", "omega")
105 # Restrict access to some more of the merge destination the
106 # old fashioned way, delete it via the OS.
107 ### TODO: Delete a versioned directory?
108 os
.remove(omega_path
)
110 # Merge r4:8 into the restricted WC's A_COPY.
112 # Search for the comment entitled "The Merge Kluge" elsewhere in
113 # this file, to understand why we shorten and chdir() below.
115 # We expect A_COPY/B/E to be skipped because we can't access the source
116 # and A_COPY/D/H/omega because it is missing. Since we have A_COPY/B/E
117 # we should override it's inherited mergeinfo, giving it just what it
118 # inherited from A_COPY before the merge. omega is missing, but since
119 # it is a file we can record the fact that it is missing in its parent
120 # directory A_COPY/D/H.
121 short_path
= shorten_path_kludge(A_COPY_path
)
122 expected_output
= wc
.State(short_path
, {
123 'D/G/rho' : Item(status
='U '),
124 'D/H/psi' : Item(status
='U '),
126 expected_status
= wc
.State(short_path
, {
127 '' : Item(status
=' M', wc_rev
=8),
128 'D/H/chi' : Item(status
=' ', wc_rev
=8),
129 'D/H/psi' : Item(status
='M ', wc_rev
=8),
130 'D/H/omega' : Item(status
='!M', wc_rev
=8),
131 'D/H' : Item(status
=' ', wc_rev
=8),
132 'D/G/pi' : Item(status
=' ', wc_rev
=8),
133 'D/G/rho' : Item(status
='M ', wc_rev
=8),
134 'D/G/tau' : Item(status
=' ', wc_rev
=8),
135 'D/G' : Item(status
=' ', wc_rev
=8),
136 'D/gamma' : Item(status
=' ', wc_rev
=8),
137 'D' : Item(status
=' ', wc_rev
=8),
138 'B/lambda' : Item(status
=' ', wc_rev
=8),
139 'B/E' : Item(status
=' M', wc_rev
=8),
140 'B/E/alpha' : Item(status
=' ', wc_rev
=8),
141 'B/E/beta' : Item(status
=' ', wc_rev
=8),
142 'B/F' : Item(status
=' ', wc_rev
=8),
143 'B' : Item(status
=' ', wc_rev
=8),
144 'mu' : Item(status
=' ', wc_rev
=8),
145 'C' : Item(status
=' ', wc_rev
=8),
147 expected_disk
= wc
.State('', {
148 '' : Item(props
={SVN_PROP_MERGE_INFO
: '/A:5-8'}),
149 'D/H/psi' : Item("New content"),
150 'D/H/chi' : Item("This is the file 'chi'.\n"),
151 # 'D/H/omega' : run_and_verify_merge() doesn't support checking
152 # the props on a missing path, so we do that
153 # manually (see below).
155 'D/G/pi' : Item("This is the file 'pi'.\n"),
156 'D/G/rho' : Item("New content"),
157 'D/G/tau' : Item("This is the file 'tau'.\n"),
159 'D/gamma' : Item("This is the file 'gamma'.\n"),
161 'B/lambda' : Item("This is the file 'lambda'.\n"),
162 'B/E' : Item(props
={SVN_PROP_MERGE_INFO
: ''}),
163 'B/E/alpha' : Item("This is the file 'alpha'.\n"),
164 'B/E/beta' : Item("This is the file 'beta'.\n"),
167 'mu' : Item("This is the file 'mu'.\n"),
170 expected_skip
= wc
.State(short_path
, {
171 'D/H/omega' : Item(),
174 saved_cwd
= os
.getcwd()
175 os
.chdir(svntest
.main
.work_dir
)
176 svntest
.actions
.run_and_verify_merge(short_path
, '4', '8',
183 None, None, None, None,
187 # Manually check the props on A_COPY/D/H/omega.
188 svntest
.actions
.run_and_verify_svn(None,
189 ["Properties on '" + omega_path
+ "':\n",
190 ' ' + SVN_PROP_MERGE_INFO
+ ' : ' +
192 [], 'pl', '-vR', omega_path
)
194 # Merge r4:8 into the restricted WC's A_COPY_2.
196 # As before we expect A_COPY_2/B/E to be skipped because we can't access the
197 # source but now the destination paths A_COPY_2/D/G, A_COPY_2/D/G/rho, and
198 # A_COPY_2/D/H/psi should also be skipped because our test user doesn't have
201 # After the merge the parents of the missing dest paths, A_COPY_2/D and
202 # A_COPY_2/D/H get non-inheritable mergeinfo. Those parents children that
203 # *are* present, A_COPY_2/D/gamma, A_COPY_2/D/H/chi, and A_COPY_2/D/H/omega
204 # get their own mergeinfo. Note that A_COPY_2/D/H is both the parent of
205 # a missing child and the sibling of missing child, but the former always
206 # takes precedence in terms of getting *non*-inheritable mergeinfo.
207 short_path
= shorten_path_kludge(A_COPY_2_path
)
208 expected_output
= wc
.State(short_path
, {
209 'D/H/omega' : Item(status
='U '),
211 expected_status
= wc
.State(short_path
, {
212 '' : Item(status
=' M', wc_rev
=8),
213 'D/H/chi' : Item(status
=' M', wc_rev
=8),
214 'D/H/omega' : Item(status
='MM', wc_rev
=8),
215 'D/H' : Item(status
=' M', wc_rev
=8),
216 'D/gamma' : Item(status
=' M', wc_rev
=8),
217 'D' : Item(status
=' M', wc_rev
=8),
218 'B/lambda' : Item(status
=' ', wc_rev
=8),
219 'B/E' : Item(status
=' M', wc_rev
=8),
220 'B/E/alpha' : Item(status
=' ', wc_rev
=8),
221 'B/E/beta' : Item(status
=' ', wc_rev
=8),
222 'B/F' : Item(status
=' ', wc_rev
=8),
223 'B' : Item(status
=' ', wc_rev
=8),
224 'mu' : Item(status
=' ', wc_rev
=8),
225 'C' : Item(status
=' ', wc_rev
=8),
227 expected_disk
= wc
.State('', {
228 '' : Item(props
={SVN_PROP_MERGE_INFO
: '/A:5-8'}),
229 'D/H/omega' : Item("New content",
230 props
={SVN_PROP_MERGE_INFO
: '/A/D/H/omega:5-8'}),
231 'D/H/chi' : Item("This is the file 'chi'.\n",
232 props
={SVN_PROP_MERGE_INFO
: '/A/D/H/chi:5-8'}),
233 'D/H' : Item(props
={SVN_PROP_MERGE_INFO
: '/A/D/H:5-8*'}),
234 'D/gamma' : Item("This is the file 'gamma'.\n",
235 props
={SVN_PROP_MERGE_INFO
: '/A/D/gamma:5-8'}),
236 'D' : Item(props
={SVN_PROP_MERGE_INFO
: '/A/D:5-8*'}),
237 'B/lambda' : Item("This is the file 'lambda'.\n"),
238 'B/E' : Item(props
={SVN_PROP_MERGE_INFO
: ''}),
239 'B/E/alpha' : Item("This is the file 'alpha'.\n"),
240 'B/E/beta' : Item("This is the file 'beta'.\n"),
243 'mu' : Item("This is the file 'mu'.\n"),
246 expected_skip
= wc
.State(short_path
, {
252 saved_cwd
= os
.getcwd()
253 os
.chdir(svntest
.main
.work_dir
)
254 svntest
.actions
.run_and_verify_merge(short_path
, '4', '8',
261 None, None, None, None,
265 # Merge r5:7 into the restricted WC's A_COPY_3.
267 # Again A_COPY_3/B/E should be skipped, but because we can't access the
268 # source *or* the destination we expect its parent A_COPY_3/B to get
269 # non-inheritable mergeinfo and its two existing siblings, A_COPY_3/B/F
270 # and A_COPY_3/B/lambda to get their own mergeinfo.
271 short_path
= shorten_path_kludge(A_COPY_3_path
)
272 expected_output
= wc
.State(short_path
, {
273 'D/G/rho' : Item(status
='U '),
275 expected_status
= wc
.State(short_path
, {
276 '' : Item(status
=' M', wc_rev
=8),
277 'D/H/chi' : Item(status
=' ', wc_rev
=8),
278 'D/H/omega' : Item(status
=' ', wc_rev
=8),
279 'D/H/psi' : Item(status
=' ', wc_rev
=8),
280 'D/H' : Item(status
=' ', wc_rev
=8),
281 'D/gamma' : Item(status
=' ', wc_rev
=8),
282 'D' : Item(status
=' ', wc_rev
=8),
283 'D/G' : Item(status
=' ', wc_rev
=8),
284 'D/G/pi' : Item(status
=' ', wc_rev
=8),
285 'D/G/rho' : Item(status
='M ', wc_rev
=8),
286 'D/G/tau' : Item(status
=' ', wc_rev
=8),
287 'B/lambda' : Item(status
=' M', wc_rev
=8),
288 'B/F' : Item(status
=' M', wc_rev
=8),
289 'B' : Item(status
=' M', wc_rev
=8),
290 'mu' : Item(status
=' ', wc_rev
=8),
291 'C' : Item(status
=' ', wc_rev
=8),
293 expected_disk
= wc
.State('', {
294 '' : Item(props
={SVN_PROP_MERGE_INFO
: '/A:6-7'}),
295 'D/H/omega' : Item("This is the file 'omega'.\n"),
296 'D/H/chi' : Item("This is the file 'chi'.\n"),
297 'D/H/psi' : Item("This is the file 'psi'.\n"),
299 'D/gamma' : Item("This is the file 'gamma'.\n"),
302 'D/G/pi' : Item("This is the file 'pi'.\n"),
303 'D/G/rho' : Item("New content"),
304 'D/G/tau' : Item("This is the file 'tau'.\n"),
305 'B/lambda' : Item("This is the file 'lambda'.\n",
306 props
={SVN_PROP_MERGE_INFO
: '/A/B/lambda:6-7'}),
307 'B/F' : Item(props
={SVN_PROP_MERGE_INFO
: '/A/B/F:6-7'}),
308 'B' : Item(props
={SVN_PROP_MERGE_INFO
: '/A/B:6-7*'}),
309 'mu' : Item("This is the file 'mu'.\n"),
312 expected_skip
= wc
.State(short_path
, {'B/E' : Item()})
313 saved_cwd
= os
.getcwd()
314 os
.chdir(svntest
.main
.work_dir
)
315 svntest
.actions
.run_and_verify_merge(short_path
, '5', '7',
322 None, None, None, None,
326 ########################################################################
330 # list all tests here, starting with None:
332 Skip(mergeinfo_and_skipped_paths
, svntest
.main
.is_ra_type_file
),
335 if __name__
== '__main__':
336 svntest
.main
.run_tests(test_list
, serial_only
= True)