1#!/usr/bin/env python
2#
3#  switch_tests.py:  testing `svn switch'.
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, re, os
29
30# Our testing module
31import svntest
32from svntest import verify, actions, main
33
34# (abbreviation)
35Skip = svntest.testcase.Skip_deco
36SkipUnless = svntest.testcase.SkipUnless_deco
37XFail = svntest.testcase.XFail_deco
38Issues = svntest.testcase.Issues_deco
39Issue = svntest.testcase.Issue_deco
40Wimp = svntest.testcase.Wimp_deco
41Item = svntest.wc.StateItem
42
43from svntest.main import SVN_PROP_MERGEINFO, server_has_mergeinfo
44from externals_tests import change_external
45from svntest.deeptrees import do_routine_switching
46
47#----------------------------------------------------------------------
48
49def relocate_deleted_missing_copied(sbox):
50  "relocate with deleted, missing and copied entries"
51  sbox.build()
52  wc_dir = sbox.wc_dir
53
54  # Delete A/mu to create a deleted entry for mu in A/.svn/entries
55  mu_path = os.path.join(wc_dir, 'A', 'mu')
56  svntest.actions.run_and_verify_svn(None, [], 'rm', mu_path)
57  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
58  expected_status.remove('A/mu')
59  expected_output = svntest.wc.State(wc_dir, {
60    'A/mu' : Item(verb='Deleting'),
61    })
62  svntest.actions.run_and_verify_commit(wc_dir,
63                                        expected_output,
64                                        expected_status)
65
66  # Remove A/B/F to create a missing entry
67  svntest.main.safe_rmtree(os.path.join(wc_dir, 'A', 'B', 'F'))
68
69  # Copy A/D to A/D2
70  D_path = os.path.join(wc_dir, 'A', 'D')
71  D2_path = os.path.join(wc_dir, 'A', 'D2')
72  svntest.actions.run_and_verify_svn(None, [], 'copy',
73                                     D_path, D2_path)
74  # Delete within the copy
75  D2G_path = os.path.join(wc_dir, 'A', 'D2', 'G')
76  svntest.actions.run_and_verify_svn(None, [], 'rm', D2G_path)
77
78  expected_status.add({
79    'A/D2'         : Item(status='A ', wc_rev='-', copied='+'),
80    'A/D2/gamma'   : Item(status='  ', wc_rev='-', copied='+'),
81    'A/D2/G'       : Item(status='D ', wc_rev='-', copied='+'),
82    'A/D2/G/pi'    : Item(status='D ', wc_rev='-', copied='+'),
83    'A/D2/G/rho'   : Item(status='D ', wc_rev='-', copied='+'),
84    'A/D2/G/tau'   : Item(status='D ', wc_rev='-', copied='+'),
85    'A/D2/H'       : Item(status='  ', wc_rev='-', copied='+'),
86    'A/D2/H/chi'   : Item(status='  ', wc_rev='-', copied='+'),
87    'A/D2/H/omega' : Item(status='  ', wc_rev='-', copied='+'),
88    'A/D2/H/psi'   : Item(status='  ', wc_rev='-', copied='+'),
89    })
90  expected_status.tweak('A/B/F', status='! ', wc_rev='1')
91  svntest.actions.run_and_verify_status(wc_dir, expected_status)
92
93  # Relocate
94  repo_dir = sbox.repo_dir
95  repo_url = sbox.repo_url
96  other_repo_dir, other_repo_url = sbox.add_repo_path('other')
97  svntest.main.copy_repos(repo_dir, other_repo_dir, 2, 0)
98  svntest.main.safe_rmtree(repo_dir, 1)
99  svntest.actions.run_and_verify_svn(None, [], 'switch', '--relocate',
100                                     repo_url, other_repo_url, wc_dir)
101
102  # Deleted and missing entries should be preserved, so update should
103  # show only A/B/F being reinstated
104  expected_output = svntest.wc.State(wc_dir, {
105        'A/B/F' : Item(verb='Restored'),
106  })
107
108  expected_disk = svntest.main.greek_state.copy()
109  expected_disk.remove('A/mu')
110  expected_disk.add({
111    'A/D2'       : Item(),
112    'A/D2/gamma'   : Item("This is the file 'gamma'.\n"),
113    'A/D2/H'       : Item(),
114    'A/D2/H/chi'   : Item("This is the file 'chi'.\n"),
115    'A/D2/H/omega' : Item("This is the file 'omega'.\n"),
116    'A/D2/H/psi'   : Item("This is the file 'psi'.\n"),
117    })
118
119  expected_status.add({
120    'A/B/F'       : Item(status='  ', wc_rev='2'),
121    })
122  expected_status.tweak(wc_rev=2)
123  expected_status.tweak('A/D2', 'A/D2/gamma',
124                        'A/D2/H', 'A/D2/H/chi', 'A/D2/H/omega', 'A/D2/H/psi',
125                        wc_rev='-')
126  expected_status.tweak('A/D2/G', 'A/D2/G/pi', 'A/D2/G/rho', 'A/D2/G/tau',
127                        copied='+', wc_rev='-')
128  svntest.actions.run_and_verify_update(wc_dir,
129                                        expected_output,
130                                        expected_disk,
131                                        expected_status)
132
133  # Commit to verify that copyfrom URLs have been relocated
134  expected_output = svntest.wc.State(wc_dir, {
135    'A/D2'       : Item(verb='Adding'),
136    'A/D2/G'     : Item(verb='Deleting'),
137    })
138  expected_status.tweak('A/D2', 'A/D2/gamma',
139                        'A/D2/H', 'A/D2/H/chi', 'A/D2/H/omega', 'A/D2/H/psi',
140                        status='  ', wc_rev='3', copied=None)
141  expected_status.remove('A/D2/G', 'A/D2/G/pi', 'A/D2/G/rho', 'A/D2/G/tau')
142  svntest.actions.run_and_verify_commit(wc_dir,
143                                        expected_output, expected_status)
144
145#----------------------------------------------------------------------
146
147@Issue(2380)
148def relocate_beyond_repos_root(sbox):
149  "relocate with prefixes longer than repo root"
150  sbox.build(read_only=True, create_wc=False)
151
152  wc_backup = sbox.add_wc_path('backup')
153
154  wc_dir = sbox.wc_dir
155  repo_dir = sbox.repo_dir
156  repo_url = sbox.repo_url
157  other_repo_dir, other_repo_url = sbox.add_repo_path('other')
158  A_url = repo_url + "/A"
159  A_wc_dir = wc_dir
160  other_A_url = other_repo_url + "/A"
161  other_B_url = other_repo_url + "/B"
162
163  svntest.main.safe_rmtree(wc_dir, 1)
164  svntest.actions.run_and_verify_svn(None, [], 'checkout',
165                                     repo_url + '/A', wc_dir)
166
167  svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 0)
168
169  # A relocate that changes the repo path part of the URL shouldn't work.
170  # This tests for issue #2380.
171  svntest.actions.run_and_verify_svn(None,
172                                     ".*Invalid relocation destination.*",
173                                     'relocate',
174                                     A_url, other_B_url, A_wc_dir)
175
176  # Another way of trying to change the fs path, leading to an invalid
177  # repository root.
178  svntest.actions.run_and_verify_svn(None,
179                                     ".*is not the root.*",
180                                     'relocate',
181                                     repo_url, other_B_url, A_wc_dir)
182
183  svntest.actions.run_and_verify_svn(None, [],
184                                     'relocate',
185                                     A_url, other_A_url, A_wc_dir)
186
187  # Check that we can contact the repository, meaning that the
188  # relocate actually changed the URI.  Escape the expected URI to
189  # avoid problems from any regex meta-characters it may contain
190  # (e.g. '+').
191  expected_infos = [
192      { 'URL'                : re.escape(other_A_url) + '$',
193        'Path'               : '.+',
194        'Repository UUID'    : '.+',
195        'Revision'           : '.+',
196        'Node Kind'          : '.+',
197        'Last Changed Date'  : '.+' },
198    ]
199  svntest.actions.run_and_verify_info(expected_infos, A_wc_dir, '-rHEAD')
200
201#----------------------------------------------------------------------
202# Issue 2578.
203def relocate_and_propset(sbox):
204  "out of date propset should fail after a relocate"
205
206  # Create virgin repos and working copy
207  svntest.main.safe_rmtree(sbox.repo_dir, 1)
208  svntest.main.create_repos(sbox.repo_dir)
209  svntest.actions.guarantee_greek_repository(
210      sbox.repo_dir, svntest.main.options.server_minor_version)
211
212  wc_dir = sbox.wc_dir
213  repo_dir = sbox.repo_dir
214  repo_url = sbox.repo_url
215
216  # checkout
217  svntest.main.safe_rmtree(wc_dir, 1)
218  svntest.actions.run_and_verify_svn(None, [],
219                                     'checkout',
220                                     repo_url, wc_dir)
221
222  # Relocate
223  other_repo_dir, other_repo_url = sbox.add_repo_path('other')
224  svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 0)
225  svntest.main.safe_rmtree(repo_dir, 1)
226  svntest.actions.run_and_verify_svn(None, [], 'relocate',
227                                     repo_url, other_repo_url, wc_dir)
228
229  # Remove gamma from the working copy.
230  D_path = os.path.join(wc_dir, 'A', 'D')
231  gamma_path = os.path.join(wc_dir, 'A', 'D', 'gamma')
232  svntest.main.run_svn(None, 'rm', gamma_path)
233
234  # Create expected commit output.
235  expected_output = svntest.wc.State(wc_dir, {
236    'A/D/gamma' : Item(verb='Deleting'),
237    })
238
239  # After committing, status should show no sign of gamma.
240  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
241  expected_status.remove('A/D/gamma')
242
243  # Commit the deletion of gamma and verify.
244  svntest.actions.run_and_verify_commit(wc_dir,
245                                        expected_output,
246                                        expected_status)
247
248  # Now gamma should be marked as `deleted' under the hood, at
249  # revision 2.  Meanwhile, A/D is still lagging at revision 1.
250
251  # Make a propchange on A/D
252  svntest.main.run_svn(None, 'ps', 'foo', 'bar', D_path)
253
254  # Commit and *expect* a repository Merge failure:
255  svntest.actions.run_and_verify_commit(wc_dir,
256                                        None,
257                                        None,
258                                        ".*[Oo]ut of date.*")
259
260#----------------------------------------------------------------------
261
262def single_file_relocate(sbox):
263  "relocate a single file"
264
265  # Create virgin repos and working copy
266  svntest.main.safe_rmtree(sbox.repo_dir, 1)
267  svntest.actions.guarantee_greek_repository(
268      sbox.repo_dir, svntest.main.options.server_minor_version)
269
270  wc_dir = sbox.wc_dir
271  iota_path = os.path.join(sbox.wc_dir, 'iota')
272  repo_dir = sbox.repo_dir
273  repo_url = sbox.repo_url
274  iota_url = repo_url + '/iota'
275  greek_dump_dir = sbox.add_wc_path('greek-dump')
276
277  # checkout
278  svntest.main.safe_rmtree(wc_dir, 1)
279  svntest.actions.run_and_verify_svn(None, [],
280                                     'checkout',
281                                     repo_url, wc_dir)
282
283  # Relocate
284  other_repo_dir, other_repo_url = sbox.add_repo_path('other')
285  other_iota_url = other_repo_url + '/iota'
286  svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 0)
287  svntest.main.safe_rmtree(repo_dir, 1)
288  svntest.actions.run_and_verify_svn(None,
289                                     ".*Cannot relocate.*",
290                                     'relocate',
291                                     iota_url, other_iota_url, iota_path)
292
293#----------------------------------------------------------------------
294
295def relocate_with_switched_children(sbox):
296  "relocate a directory with switched children"
297  sbox.build()
298  wc_dir = sbox.wc_dir
299
300  # Setup (and verify) some switched things
301  do_routine_switching(sbox.wc_dir, sbox.repo_url, False)
302
303  # Relocate
304  repo_dir = sbox.repo_dir
305  repo_url = sbox.repo_url
306  other_repo_dir, other_repo_url = sbox.add_repo_path('other')
307  svntest.main.copy_repos(repo_dir, other_repo_dir, 1, 0)
308  svntest.main.safe_rmtree(repo_dir, 1)
309
310  # Do the switch and check the results in three ways.
311  svntest.actions.run_and_verify_svn(None, [], 'relocate',
312                                     repo_url, other_repo_url, wc_dir)
313
314  # Attempt to commit changes and examine results
315  expected_output = svntest.wc.State(wc_dir, { })
316  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
317  expected_status.tweak('A/B', 'iota',
318                        switched='S')
319  expected_status.remove('A/B/E', 'A/B/F', 'A/B/E/alpha', 'A/B/E/beta',
320                         'A/B/lambda')
321  expected_status.add({
322    'A/B/pi'       : Item(status='  ', wc_rev='1'),
323    'A/B/rho'      : Item(status='  ', wc_rev='1'),
324    'A/B/tau'      : Item(status='  ', wc_rev='1'),
325    })
326
327  # This won't actually do a commit, because nothing should be modified.
328  svntest.actions.run_and_verify_commit(wc_dir,
329                                        expected_output, expected_status)
330
331  # Check the URLs of various nodes.
332  info_output = {
333        wc_dir:                                '.*.other$',
334        os.path.join(wc_dir, 'iota'):          '.*.other/A/D/gamma$',
335        os.path.join(wc_dir, 'A', 'B'):        '.*.other/A/D/G$',
336        os.path.join(wc_dir, 'A', 'B', 'pi'):  '.*.other/A/D/G/pi$',
337    }
338
339  for path, pattern in info_output.items():
340    expected_info = { 'URL' : pattern }
341    svntest.actions.run_and_verify_info([expected_info], path)
342
343#----------------------------------------------------------------------
344
345
346### regression test for issue #3597
347@Issue(3597)
348def relocate_with_relative_externals(sbox):
349  "relocate a directory containing relative externals"
350
351  sbox.build()
352  wc_dir = sbox.wc_dir
353  repo_dir = sbox.repo_dir
354  repo_url = sbox.repo_url
355
356  # Add a relative external.
357  change_external(os.path.join(wc_dir, 'A', 'B'),
358                  "^/A/D/G G-ext\n../D/H H-ext", commit=True)
359  svntest.actions.run_and_verify_svn(None, [], 'update', wc_dir)
360
361  # A second wc not at the repository root
362  other_wc = sbox.add_wc_path('other')
363  svntest.main.safe_rmtree(other_wc, 1)
364  svntest.actions.run_and_verify_svn(None, [], 'checkout',
365                                     repo_url + '/A/B', other_wc)
366  # Move our repository to another location.
367  other_repo_dir, other_repo_url = sbox.add_repo_path('other')
368  svntest.main.copy_repos(repo_dir, other_repo_dir, 2, 0)
369  svntest.main.safe_rmtree(repo_dir, 1)
370
371  # Now relocate our working copy.
372  svntest.actions.run_and_verify_svn(None, [], 'relocate',
373                                     repo_url, other_repo_url, wc_dir)
374
375  # Check the URLs of the externals -- were they updated to point to the
376  # .other repository URL?
377  svntest.actions.run_and_verify_info([{ 'URL' : '.*.other/A/D/G$' }],
378                                      os.path.join(wc_dir, 'A', 'B', 'G-ext'))
379  svntest.actions.run_and_verify_info([{ 'URL' : '.*.other/A/D/H$' }],
380                                      os.path.join(wc_dir, 'A', 'B', 'H-ext'))
381
382  # Relocate with prefix too long to be valid for externals.
383  svntest.actions.run_and_verify_svn(None, [], 'relocate',
384                                     repo_url + '/A/B',
385                                     other_repo_url + '/A/B',
386                                     other_wc)
387
388  svntest.actions.run_and_verify_info([{ 'URL' : '.*.other/A/D/G$' }],
389                                      os.path.join(other_wc, 'G-ext'))
390  svntest.actions.run_and_verify_info([{ 'URL' : '.*.other/A/D/H$' }],
391                                      os.path.join(other_wc, 'H-ext'))
392
393def prefix_partial_component(sbox):
394  """prefix with a partial component"""
395
396  sbox.build()
397  wc_dir = sbox.wc_dir
398  repo_dir = sbox.repo_dir
399  repo_url = sbox.repo_url
400  other1_repo_dir, other1_repo_url = sbox.add_repo_path('xxxother')
401  other2_repo_dir, other2_repo_url = sbox.add_repo_path('yyyother')
402
403  # Relocate to 'xxxother'.
404  svntest.main.copy_repos(repo_dir, other1_repo_dir, 1, 0)
405  svntest.main.safe_rmtree(repo_dir, 1)
406  svntest.actions.run_and_verify_svn(None, [], 'relocate',
407                                     repo_url, other1_repo_url, wc_dir)
408  svntest.actions.run_and_verify_info([{ 'URL' : '.*.xxxother$' }],
409                                      wc_dir)
410
411  # Now relocate from 'xxx' to 'yyy' omitting 'other'.
412  svntest.main.copy_repos(other1_repo_dir, other2_repo_dir, 1, 0)
413  svntest.main.safe_rmtree(other1_repo_url, 1)
414  svntest.actions.run_and_verify_svn(None, [], 'relocate',
415                                     other1_repo_url[:-5],
416                                     other2_repo_url[:-5],
417                                     wc_dir)
418  svntest.actions.run_and_verify_info([{ 'URL' : '.*.yyyother$' }],
419                                      wc_dir)
420
421
422########################################################################
423# Run the tests
424
425# list all tests here, starting with None:
426test_list = [ None,
427              relocate_deleted_missing_copied,
428              relocate_beyond_repos_root,
429              relocate_and_propset,
430              single_file_relocate,
431              relocate_with_switched_children,
432              relocate_with_relative_externals,
433              prefix_partial_component,
434              ]
435
436if __name__ == '__main__':
437  svntest.main.run_tests(test_list)
438  # NOTREACHED
439
440
441### End of file.
442