1#!/usr/bin/env python
2#
3#  revert_tests.py:  testing 'svn revert'.
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 re, os, stat, shutil
29
30# Our testing module
31import svntest
32from svntest import wc, main, actions
33from svntest.actions import run_and_verify_svn
34from svntest.main import file_append, file_write, run_svn
35
36# (abbreviation)
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
43Item = svntest.wc.StateItem
44
45
46######################################################################
47# Helpers
48
49def expected_output_revert(reverted_paths, skipped_paths=[]):
50  return svntest.verify.UnorderedRegexListOutput(
51    ["Reverted '%s'\n" % re.escape(path) for path in reverted_paths] +
52    ["Skipped '%s'.*\n" % re.escape(path) for path in skipped_paths])
53
54def run_and_verify_revert(targets, options=[],
55                          reverted_paths=None, skipped_paths=[]):
56  """Run 'svn revert OPTIONS TARGETS'. Verify that the printed output matches
57     REVERTED_PATHS and SKIPPED_PATHS. If REVERTED_PATHS is None, it defaults
58     to TARGETS.
59  """
60  if reverted_paths is None:
61    reverted_paths = targets
62  expected_output = expected_output_revert(reverted_paths, skipped_paths)
63  svntest.actions.run_and_verify_svn(expected_output, [],
64                                     *(['revert'] + options + targets))
65
66def revert_replacement_with_props(sbox, wc_copy):
67  """Helper implementing the core of
68  revert_{repos,wc}_to_wc_replace_with_props().
69
70  Uses a working copy (when wc_copy == True) or a URL (when wc_copy ==
71  False) source to copy from."""
72
73  sbox.build()
74  wc_dir = sbox.wc_dir
75
76  # Use a temp file to set properties with wildcards in their values
77  # otherwise Win32/VS2005 will expand them
78  prop_path = os.path.join(wc_dir, 'proptmp')
79  svntest.main.file_append(prop_path, '*')
80
81  # Set props on file which is copy-source later on
82  pi_path = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')
83  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
84  svntest.actions.run_and_verify_svn(None, [],
85                                     'ps', 'phony-prop', '-F', prop_path,
86                                     pi_path)
87  os.remove(prop_path)
88  svntest.actions.run_and_verify_svn(None, [],
89                                     'ps', 'svn:eol-style', 'LF', rho_path)
90
91  # Verify props having been set
92  expected_disk = svntest.main.greek_state.copy()
93  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
94  expected_disk.tweak('A/D/G/pi',
95                      props={ 'phony-prop': '*' })
96  expected_disk.tweak('A/D/G/rho',
97                      props={ 'svn:eol-style': 'LF' })
98
99  svntest.actions.verify_disk(wc_dir, expected_disk, True)
100
101  # Commit props
102  expected_output = svntest.wc.State(wc_dir, {
103    'A/D/G/pi':  Item(verb='Sending'),
104    'A/D/G/rho': Item(verb='Sending'),
105    })
106  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
107  expected_status.tweak('A/D/G/pi',  wc_rev='2')
108  expected_status.tweak('A/D/G/rho', wc_rev='2')
109  svntest.actions.run_and_verify_commit(wc_dir,
110                                        expected_output,
111                                        expected_status)
112
113  # Bring wc into sync
114  svntest.actions.run_and_verify_svn(None, [], 'up', wc_dir)
115
116  # File scheduled for deletion
117  svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path)
118
119  # Status before attempting copies
120  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
121  expected_status.tweak('A/D/G/rho', status='D ')
122  svntest.actions.run_and_verify_status(wc_dir, expected_status)
123
124  # The copy shouldn't fail
125  if wc_copy:
126    pi_src = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')
127  else:
128    pi_src = sbox.repo_url + '/A/D/G/pi'
129
130  svntest.actions.run_and_verify_svn(None, [],
131                                     'cp', pi_src, rho_path)
132
133  # Verify both content and props have been copied
134  if wc_copy:
135    props = { 'phony-prop' : '*' }
136  else:
137    props = { 'phony-prop' : '*' }
138
139  expected_disk.tweak('A/D/G/rho',
140                      contents="This is the file 'pi'.\n",
141                      props=props)
142  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)
143
144  # Now revert
145  expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-')
146  svntest.actions.run_and_verify_status(wc_dir, expected_status)
147
148  expected_status.tweak('A/D/G/rho', status='  ', copied=None, wc_rev='2')
149  run_and_verify_revert([wc_dir], ['-R'], [rho_path])
150  svntest.actions.run_and_verify_status(wc_dir, expected_status)
151
152  # Check disk status
153  expected_disk = svntest.main.greek_state.copy()
154  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
155  expected_disk.tweak('A/D/G/pi',
156                      props={ 'phony-prop': '*' })
157  expected_disk.tweak('A/D/G/rho',
158                      props={ 'svn:eol-style': 'LF' })
159  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)
160
161
162
163
164######################################################################
165# Tests
166#
167#   Each test must return on success or raise on failure.
168
169
170#----------------------------------------------------------------------
171
172def revert_from_wc_root(sbox):
173  "revert relative to wc root"
174
175  sbox.build(read_only = True)
176  wc_dir = sbox.wc_dir
177
178  os.chdir(wc_dir)
179
180  # Mostly taken from basic_revert
181  # Modify some files and props.
182  beta_path = os.path.join('A', 'B', 'E', 'beta')
183  gamma_path = os.path.join('A', 'D', 'gamma')
184  iota_path = 'iota'
185  rho_path = os.path.join('A', 'D', 'G', 'rho')
186  zeta_path = os.path.join('A', 'D', 'H', 'zeta')
187  svntest.main.file_append(beta_path, "Added some text to 'beta'.\n")
188  svntest.main.file_append(iota_path, "Added some text to 'iota'.\n")
189  svntest.main.file_append(rho_path, "Added some text to 'rho'.\n")
190  svntest.main.file_append(zeta_path, "Added some text to 'zeta'.\n")
191
192  svntest.actions.run_and_verify_svn(None, [],
193                                     'add', zeta_path)
194  svntest.actions.run_and_verify_svn(None, [],
195                                     'ps', 'random-prop', 'propvalue',
196                                     gamma_path)
197  svntest.actions.run_and_verify_svn(None, [],
198                                     'ps', 'random-prop', 'propvalue',
199                                     iota_path)
200  svntest.actions.run_and_verify_svn(None, [],
201                                     'ps', 'random-prop', 'propvalue',
202                                     '.')
203  svntest.actions.run_and_verify_svn(None, [],
204                                     'ps', 'random-prop', 'propvalue',
205                                     'A')
206
207  # Verify modified status.
208  expected_output = svntest.actions.get_virginal_state('', 1)
209  expected_output.tweak('A/B/E/beta', 'A/D/G/rho', status='M ')
210  expected_output.tweak('iota', status='MM')
211  expected_output.tweak('', 'A/D/gamma', 'A', status=' M')
212  expected_output.add({
213    'A/D/H/zeta' : Item(status='A ', wc_rev=0),
214    })
215
216  svntest.actions.run_and_verify_status('', expected_output)
217
218  # Run revert
219  svntest.actions.run_and_verify_svn(None, [],
220                                     'revert', beta_path)
221
222  svntest.actions.run_and_verify_svn(None, [],
223                                     'revert', gamma_path)
224
225  svntest.actions.run_and_verify_svn(None, [],
226                                     'revert', iota_path)
227
228  svntest.actions.run_and_verify_svn(None, [],
229                                     'revert', rho_path)
230
231  svntest.actions.run_and_verify_svn(None, [],
232                                     'revert', zeta_path)
233
234  svntest.actions.run_and_verify_svn(None, [],
235                                     'revert', '.')
236
237  svntest.actions.run_and_verify_svn(None, [],
238                                     'revert', 'A')
239
240  # Verify unmodified status.
241  expected_output = svntest.actions.get_virginal_state('', 1)
242
243  svntest.actions.run_and_verify_status('', expected_output)
244
245@Issue(1663)
246def revert_reexpand_keyword(sbox):
247  "revert reexpands manually contracted keyword"
248
249  # This is for issue #1663.  The bug is that if the only difference
250  # between a locally modified working file and the base version of
251  # same was that the former had a contracted keyword that would be
252  # expanded in the latter, then 'svn revert' wouldn't notice the
253  # difference, and therefore wouldn't revert.  And why wouldn't it
254  # notice?  Since text bases are always stored with keywords
255  # contracted, and working files are contracted before comparison
256  # with text base, there would appear to be no difference when the
257  # contraction is the only difference.  For most commands, this is
258  # correct -- but revert's job is to restore the working file, not
259  # the text base.
260
261  sbox.build()
262  wc_dir = sbox.wc_dir
263  newfile_path = os.path.join(wc_dir, "newfile")
264  unexpanded_contents = "This is newfile: $Rev$.\n"
265
266  # Put an unexpanded keyword into iota.
267  svntest.main.file_write(newfile_path, unexpanded_contents)
268
269  # Commit, without svn:keywords property set.
270  svntest.main.run_svn(None, 'add', newfile_path)
271  svntest.main.run_svn(None,
272                       'commit', '-m', 'r2', newfile_path)
273
274  # Set the property and commit.  This should expand the keyword.
275  svntest.main.run_svn(None, 'propset', 'svn:keywords', 'rev', newfile_path)
276  svntest.main.run_svn(None,
277                       'commit', '-m', 'r3', newfile_path)
278
279  # Verify that the keyword got expanded.
280  def check_expanded(path):
281    fp = open(path, 'r')
282    lines = fp.readlines()
283    fp.close()
284    if lines[0] != "This is newfile: $Rev: 3 $.\n":
285      raise svntest.Failure
286
287  check_expanded(newfile_path)
288
289  # Now un-expand the keyword again.
290  svntest.main.file_write(newfile_path, unexpanded_contents)
291
292  # Revert the file.  The keyword should reexpand.
293  svntest.main.run_svn(None, 'revert', newfile_path)
294
295  # Verify that the keyword got re-expanded.
296  check_expanded(newfile_path)
297
298  # Ok, the first part of this test was written in 2004. We are now in 2011
299  # and note that there is more to test:
300
301  # If the recorded timestamp and size match the file then revert won't
302  # reinstall the file as the file was not modified when last compared in
303  # the repository normal form.
304  #
305  # The easiest way to get the information recorded would be calling cleanup,
306  # because that 'repairs' the recorded information. But some developers
307  # (including me) would call that cheating, so I just use a failed commit.
308
309  # Un-expand the keyword again.
310  svntest.main.file_write(newfile_path, unexpanded_contents)
311
312  # And now we trick svn in ignoring the file on newfile_path
313  newfile2_path = newfile_path + '2'
314  svntest.main.file_write(newfile2_path, 'This is file 2')
315  svntest.main.run_svn(None, 'add', newfile2_path)
316  os.remove(newfile2_path)
317
318  # This commit fails because newfile2_path is missing, but only after
319  # we call svn_wc__internal_file_modified_p() on new_file.
320  svntest.actions.run_and_verify_commit(wc_dir, None, None, ".*2' is scheduled"+
321                                        " for addition, but is missing.*",
322                                        newfile_path, newfile2_path,
323                                        '-m', "Shouldn't be committed")
324
325  # Revert the file.  The file is not reverted!
326  svntest.actions.run_and_verify_svn([], [], 'revert', newfile_path)
327
328
329#----------------------------------------------------------------------
330# Regression test for issue #1775:
331# Should be able to revert a file with no properties i.e. no prop-base
332@Issue(1775)
333def revert_replaced_file_without_props(sbox):
334  "revert a replaced file with no properties"
335
336  sbox.build()
337  wc_dir = sbox.wc_dir
338
339  file1_path = os.path.join(wc_dir, 'file1')
340
341  # Add a new file, file1, that has no prop-base
342  svntest.main.file_append(file1_path, "This is the file 'file1' revision 2.")
343  svntest.actions.run_and_verify_svn(None, [], 'add', file1_path)
344
345  # commit file1
346  expected_output = svntest.wc.State(wc_dir, {
347    'file1' : Item(verb='Adding')
348    })
349
350  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
351  expected_status.add({
352    'file1' : Item(status='  ', wc_rev=2),
353    })
354
355  svntest.actions.run_and_verify_commit(wc_dir, expected_output,
356                                        expected_status)
357
358  # delete file1
359  svntest.actions.run_and_verify_svn(None, [], 'rm', file1_path)
360
361  # test that file1 is scheduled for deletion.
362  expected_status.tweak('file1', status='D ')
363  svntest.actions.run_and_verify_status(wc_dir, expected_status)
364
365  # recreate and add file1
366  svntest.main.file_append(file1_path, "This is the file 'file1' revision 3.")
367  svntest.actions.run_and_verify_svn(None, [], 'add', file1_path)
368
369  # Test to see if file1 is schedule for replacement
370  expected_status.tweak('file1', status='R ')
371  svntest.actions.run_and_verify_status(wc_dir, expected_status)
372
373  # revert file1
374  run_and_verify_revert([file1_path])
375
376  # test that file1 really was reverted
377  expected_status.tweak('file1', status='  ', wc_rev=2)
378  svntest.actions.run_and_verify_status(wc_dir, expected_status)
379
380#----------------------------------------------------------------------
381# Note that issue #876 has been rejected. This now basically tests that
382# reverting the delete side of a move does *not* also revert the copy side.
383@Issue(876)
384def revert_moved_file(sbox):
385  "revert a moved file"
386
387  # svntest.factory.make(sbox, """svn mv iota iota_moved
388  #                               svn st
389  #                               svn revert iota
390  #                               svn st
391  #                               """)
392
393  sbox.build()
394  wc_dir = sbox.wc_dir
395
396  iota = os.path.join(wc_dir, 'iota')
397  iota_moved = os.path.join(wc_dir, 'iota_moved')
398
399  # svn mv iota iota_moved
400  expected_stdout = svntest.verify.UnorderedOutput([
401    'A         ' + iota_moved + '\n',
402    'D         ' + iota + '\n',
403  ])
404
405  actions.run_and_verify_svn2(expected_stdout, [], 0, 'mv', iota,
406    iota_moved)
407
408  # svn st
409  expected_status = actions.get_virginal_state(wc_dir, 1)
410  expected_status.add({
411    'iota_moved'        : Item(status='A ', copied='+', wc_rev='-',
412                               moved_from='iota'),
413  })
414  expected_status.tweak('iota', status='D ', moved_to='iota_moved')
415
416  actions.run_and_verify_unquiet_status(wc_dir, expected_status)
417
418  # svn revert iota
419  run_and_verify_revert([iota])
420
421  # svn st
422  expected_status.tweak('iota', status='  ', moved_to=None)
423  expected_status.tweak('iota_moved', moved_from=None)
424
425  actions.run_and_verify_unquiet_status(wc_dir, expected_status)
426
427
428#----------------------------------------------------------------------
429# Test for issue 2135
430#
431# It is like merge_file_replace (in merge_tests.py), but reverts file
432# instead of commit.
433@Issue(2135)
434def revert_file_merge_replace_with_history(sbox):
435  "revert a merge replacement of file with history"
436
437  sbox.build()
438  wc_dir = sbox.wc_dir
439
440  # File scheduled for deletion
441  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
442  svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path)
443
444  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
445  expected_status.tweak('A/D/G/rho', status='D ')
446  svntest.actions.run_and_verify_status(wc_dir, expected_status)
447
448  expected_output = svntest.wc.State(wc_dir, {
449    'A/D/G/rho': Item(verb='Deleting'),
450    })
451
452  expected_status.remove('A/D/G/rho')
453
454  # Commit rev 2
455  svntest.actions.run_and_verify_commit(wc_dir,
456                                        expected_output,
457                                        expected_status)
458  # create new rho file
459  svntest.main.file_write(rho_path, "new rho\n")
460
461  # Add the new file
462  svntest.actions.run_and_verify_svn(None, [], 'add', rho_path)
463
464  # Commit revsion 3
465  expected_status.add({
466    'A/D/G/rho' : Item(status='A ', wc_rev='0')
467    })
468  svntest.actions.run_and_verify_status(wc_dir, expected_status)
469  expected_output = svntest.wc.State(wc_dir, {
470    'A/D/G/rho': Item(verb='Adding'),
471    })
472
473  svntest.actions.run_and_verify_commit(wc_dir,
474                                        expected_output,
475                                        None)
476
477  # Update working copy
478  expected_output = svntest.wc.State(wc_dir, {})
479  expected_disk   = svntest.main.greek_state.copy()
480  expected_disk.tweak('A/D/G/rho', contents='new rho\n' )
481  expected_status.tweak(wc_rev='3')
482  expected_status.tweak('A/D/G/rho', status='  ')
483
484  svntest.actions.run_and_verify_update(wc_dir,
485                                        expected_output,
486                                        expected_disk,
487                                        expected_status)
488
489  # merge changes from r3:1
490  expected_output = svntest.wc.State(wc_dir, {
491    'A/D/G/rho': Item(status='R ')
492    })
493  expected_mergeinfo_output = svntest.wc.State(wc_dir, {
494    '' : Item(status=' U')
495    })
496  expected_elision_output = svntest.wc.State(wc_dir, {
497    '' : Item(status=' U')
498    })
499  expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-')
500  expected_skip = wc.State(wc_dir, { })
501  expected_disk.tweak('A/D/G/rho', contents="This is the file 'rho'.\n")
502  svntest.actions.run_and_verify_merge(wc_dir, '3', '1',
503                                       sbox.repo_url, None,
504                                       expected_output,
505                                       expected_mergeinfo_output,
506                                       expected_elision_output,
507                                       expected_disk,
508                                       expected_status,
509                                       expected_skip)
510
511  # Now revert
512  svntest.actions.run_and_verify_svn(None,
513                                     [], 'revert', rho_path)
514
515  # test that rho really was reverted
516  expected_status.tweak('A/D/G/rho', copied=None, status='  ', wc_rev=3)
517  svntest.actions.run_and_verify_status(wc_dir, expected_status)
518
519  expected_disk.tweak('A/D/G/rho', contents="new rho\n")
520  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)
521
522  # Make sure the revert removed the copy from information.
523  expected_infos = [
524      { 'Copied' : None }
525    ]
526  svntest.actions.run_and_verify_info(expected_infos, rho_path)
527
528def revert_wc_to_wc_replace_with_props(sbox):
529  "revert svn cp PATH PATH replace file with props"
530
531  revert_replacement_with_props(sbox, 1)
532
533def revert_repos_to_wc_replace_with_props(sbox):
534  "revert svn cp URL PATH replace file with props"
535
536  revert_replacement_with_props(sbox, 0)
537
538def revert_after_second_replace(sbox):
539  "revert file after second replace"
540
541  sbox.build(read_only = True)
542  wc_dir = sbox.wc_dir
543
544  # File scheduled for deletion
545  rho_path = os.path.join(wc_dir, 'A', 'D', 'G', 'rho')
546  svntest.actions.run_and_verify_svn(None, [], 'rm', rho_path)
547
548  # Status before attempting copy
549  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
550  expected_status.tweak('A/D/G/rho', status='D ')
551  svntest.actions.run_and_verify_status(wc_dir, expected_status)
552
553  # Replace file for the first time
554  pi_src = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')
555
556  svntest.actions.run_and_verify_svn(None, [],
557                                     'cp', pi_src, rho_path)
558
559  expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-')
560  svntest.actions.run_and_verify_status(wc_dir, expected_status)
561
562  # Now delete replaced file.
563  svntest.actions.run_and_verify_svn(None, [], 'rm', '--force', rho_path)
564
565  # Status should be same as after first delete
566  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
567  expected_status.tweak('A/D/G/rho', status='D ')
568  svntest.actions.run_and_verify_status(wc_dir, expected_status)
569
570  # Replace file for the second time
571  pi_src = os.path.join(wc_dir, 'A', 'D', 'G', 'pi')
572
573  svntest.actions.run_and_verify_svn(None, [], 'cp', pi_src, rho_path)
574
575  expected_status.tweak('A/D/G/rho', status='R ', copied='+', wc_rev='-')
576  svntest.actions.run_and_verify_status(wc_dir, expected_status)
577
578  # Now revert
579  svntest.actions.run_and_verify_svn(None, [],
580                                     'revert', '-R', wc_dir)
581
582  # Check disk status
583  expected_disk = svntest.main.greek_state.copy()
584  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
585  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)
586
587
588#----------------------------------------------------------------------
589# Tests for issue #2517.
590#
591# Manual conflict resolution leads to spurious revert report.
592@Issue(2517)
593def revert_after_manual_conflict_resolution__text(sbox):
594  "revert after manual text-conflict resolution"
595
596  # Make two working copies
597  sbox.build()
598  wc_dir_1 = sbox.wc_dir
599  wc_dir_2 = sbox.add_wc_path('other')
600  svntest.actions.duplicate_dir(wc_dir_1, wc_dir_2)
601
602  # Cause a (text) conflict
603  iota_path_1 = os.path.join(wc_dir_1, 'iota')
604  iota_path_2 = os.path.join(wc_dir_2, 'iota')
605  svntest.main.file_write(iota_path_1, 'Modified iota text')
606  svntest.main.file_write(iota_path_2, 'Conflicting iota text')
607  svntest.main.run_svn(None,
608                       'commit', '-m', 'r2', wc_dir_1)
609  svntest.main.run_svn(None,
610                       'update', wc_dir_2)
611
612  # Resolve the conflict "manually"
613  svntest.main.file_write(iota_path_2, 'Modified iota text')
614  os.remove(iota_path_2 + '.mine')
615  os.remove(iota_path_2 + '.r1')
616  os.remove(iota_path_2 + '.r2')
617
618  # Verify no output from status, diff, or revert
619  svntest.actions.run_and_verify_svn([], [], "status", wc_dir_2)
620  svntest.actions.run_and_verify_svn([], [], "diff", wc_dir_2)
621  svntest.actions.run_and_verify_svn([], [], "revert", "-R", wc_dir_2)
622
623def revert_after_manual_conflict_resolution__prop(sbox):
624  "revert after manual property-conflict resolution"
625
626  # Make two working copies
627  sbox.build()
628  wc_dir_1 = sbox.wc_dir
629  wc_dir_2 = sbox.add_wc_path('other')
630  svntest.actions.duplicate_dir(wc_dir_1, wc_dir_2)
631
632  # Cause a (property) conflict
633  iota_path_1 = os.path.join(wc_dir_1, 'iota')
634  iota_path_2 = os.path.join(wc_dir_2, 'iota')
635  svntest.main.run_svn(None, 'propset', 'foo', '1', iota_path_1)
636  svntest.main.run_svn(None, 'propset', 'foo', '2', iota_path_2)
637  svntest.main.run_svn(None,
638                       'commit', '-m', 'r2', wc_dir_1)
639  svntest.main.run_svn(None,
640                       'update', wc_dir_2)
641
642  # Resolve the conflict "manually"
643  svntest.main.run_svn(None, 'propset', 'foo', '1', iota_path_2)
644  os.remove(iota_path_2 + '.prej')
645
646  # Verify no output from status, diff, or revert
647  svntest.actions.run_and_verify_svn([], [], "status", wc_dir_2)
648  svntest.actions.run_and_verify_svn([], [], "diff", wc_dir_2)
649  svntest.actions.run_and_verify_svn([], [], "revert", "-R", wc_dir_2)
650
651def revert_propset__dir(sbox):
652  "revert a simple propset on a dir"
653
654  sbox.build(read_only = True)
655  wc_dir = sbox.wc_dir
656  a_path = os.path.join(wc_dir, 'A')
657  svntest.main.run_svn(None, 'propset', 'foo', 'x', a_path)
658  run_and_verify_revert([a_path])
659
660def revert_propset__file(sbox):
661  "revert a simple propset on a file"
662
663  sbox.build(read_only = True)
664  wc_dir = sbox.wc_dir
665  iota_path = os.path.join(wc_dir, 'iota')
666  svntest.main.run_svn(None, 'propset', 'foo', 'x', iota_path)
667  run_and_verify_revert([iota_path])
668
669def revert_propdel__dir(sbox):
670  "revert a simple propdel on a dir"
671
672  sbox.build()
673  wc_dir = sbox.wc_dir
674  a_path = os.path.join(wc_dir, 'A')
675  svntest.main.run_svn(None, 'propset', 'foo', 'x', a_path)
676  svntest.main.run_svn(None,
677                       'commit', '-m', 'ps', a_path)
678  svntest.main.run_svn(None, 'propdel', 'foo', a_path)
679  run_and_verify_revert([a_path])
680
681def revert_propdel__file(sbox):
682  "revert a simple propdel on a file"
683
684  sbox.build()
685  wc_dir = sbox.wc_dir
686  iota_path = os.path.join(wc_dir, 'iota')
687  svntest.main.run_svn(None, 'propset', 'foo', 'x', iota_path)
688  svntest.main.run_svn(None,
689                       'commit', '-m', 'ps', iota_path)
690  svntest.main.run_svn(None, 'propdel', 'foo', iota_path)
691  run_and_verify_revert([iota_path])
692
693def revert_replaced_with_history_file_1(sbox):
694  "revert a committed replace-with-history == no-op"
695
696  sbox.build()
697  wc_dir = sbox.wc_dir
698  iota_path = os.path.join(wc_dir, 'iota')
699  mu_path = os.path.join(wc_dir, 'A', 'mu')
700
701  # Remember the original text of 'mu'
702  exit_code, text_r1, err = svntest.actions.run_and_verify_svn(None, [],
703                                                               'cat', mu_path)
704  # delete mu and replace it with a copy of iota
705  svntest.main.run_svn(None, 'rm', mu_path)
706  svntest.main.run_svn(None, 'mv', iota_path, mu_path)
707
708  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
709  expected_status.tweak('A/mu', status='  ', wc_rev=2)
710  expected_status.remove('iota')
711  expected_output = svntest.wc.State(wc_dir, {
712    'iota': Item(verb='Deleting'),
713    'A/mu': Item(verb='Replacing'),
714    })
715  svntest.actions.run_and_verify_commit(wc_dir,
716                                        expected_output,
717                                        expected_status)
718
719  # update the working copy
720  svntest.main.run_svn(None, 'up', wc_dir)
721
722  # now revert back to the state in r1
723  expected_output = svntest.wc.State(wc_dir, {
724    'A/mu': Item(status='R '),
725    'iota': Item(status='A ')
726    })
727  expected_mergeinfo_output = svntest.wc.State(wc_dir, {
728    '': Item(status=' U'),
729    })
730  expected_elision_output = svntest.wc.State(wc_dir, {
731    '': Item(status=' U'),
732    })
733  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
734  expected_status.tweak('A/mu', status='R ', copied='+', wc_rev='-')
735  expected_status.tweak('iota', status='A ', copied='+', wc_rev='-')
736  expected_skip = wc.State(wc_dir, { })
737  expected_disk = svntest.main.greek_state.copy()
738  svntest.actions.run_and_verify_merge(wc_dir, '2', '1',
739                                       sbox.repo_url, None,
740                                       expected_output,
741                                       expected_mergeinfo_output,
742                                       expected_elision_output,
743                                       expected_disk,
744                                       expected_status,
745                                       expected_skip)
746
747  # and commit in r3
748  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
749  expected_status.tweak('A/mu', status='  ', wc_rev=3)
750  expected_status.tweak('iota', status='  ', wc_rev=3)
751  expected_output = svntest.wc.State(wc_dir, {
752    'iota': Item(verb='Adding'),
753    'A/mu': Item(verb='Replacing'),
754    })
755  svntest.actions.run_and_verify_commit(wc_dir,
756                                        expected_output,
757                                        expected_status)
758
759  # Verify the content of 'mu'
760  svntest.actions.run_and_verify_svn(text_r1, [], 'cat', mu_path)
761
762  # situation: no local modifications, mu has its original content again.
763
764  # revert 'mu' locally, shouldn't change a thing.
765  svntest.actions.run_and_verify_svn([], [], "revert",
766                                     mu_path)
767
768  # Verify the content of 'mu'
769  svntest.actions.run_and_verify_svn(text_r1, [], 'cat', mu_path)
770
771#----------------------------------------------------------------------
772# Test for issue #2804.
773@Issue(2804)
774def status_of_missing_dir_after_revert(sbox):
775  "status after schedule-delete, revert, and local rm"
776
777  sbox.build(read_only = True)
778  wc_dir = sbox.wc_dir
779  A_D_G_path = os.path.join(wc_dir, "A", "D", "G")
780
781  svntest.actions.run_and_verify_svn(None, [], "rm", A_D_G_path)
782  run_and_verify_revert([A_D_G_path])
783
784  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
785  expected_status.tweak('A/D/G/rho', 'A/D/G/pi', 'A/D/G/tau',
786                        status='D ')
787  svntest.actions.run_and_verify_status(wc_dir,  expected_status)
788
789  svntest.main.safe_rmtree(A_D_G_path)
790  expected_status.tweak('A/D/G', status='! ')
791
792  svntest.actions.run_and_verify_status(wc_dir, expected_status)
793
794  # When using single-db, we can get back to the virginal state.
795  svntest.actions.run_and_verify_svn(None, [], "revert",
796                                     "-R", A_D_G_path)
797
798  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
799  svntest.actions.run_and_verify_status(wc_dir, expected_status)
800
801#----------------------------------------------------------------------
802# Test for issue #2804 with replaced directory
803@Issue(2804)
804def status_of_missing_dir_after_revert_replaced_with_history_dir(sbox):
805  "status after replace+, revert, and local rm"
806
807  sbox.build()
808  wc_dir = sbox.wc_dir
809  repo_url = sbox.repo_url
810
811  # delete A/D/G and commit
812  G_path = os.path.join(wc_dir, "A", "D", "G")
813  svntest.actions.run_and_verify_svn(None, [], "rm", G_path)
814  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
815  expected_status.remove('A/D/G', 'A/D/G/rho', 'A/D/G/pi', 'A/D/G/tau')
816  expected_output = svntest.wc.State(wc_dir, {
817    'A/D/G': Item(verb='Deleting'),
818    })
819  svntest.actions.run_and_verify_commit(wc_dir,
820                                        expected_output,
821                                        expected_status)
822
823  # copy A/D/G from A/B/E and commit
824  E_path = os.path.join(wc_dir, "A", "B", "E")
825  svntest.actions.run_and_verify_svn(None, [], "cp", E_path, G_path)
826  expected_status.add({
827    'A/D/G' : Item(status='  ', wc_rev='3'),
828    'A/D/G/alpha' : Item(status='  ', wc_rev='3'),
829    'A/D/G/beta' : Item(status='  ', wc_rev='3')
830    })
831  expected_output = svntest.wc.State(wc_dir, {
832    'A/D/G': Item(verb='Adding'),
833    })
834  svntest.actions.run_and_verify_commit(wc_dir,
835                                        expected_output,
836                                        expected_status)
837
838  # update the working copy
839  svntest.main.run_svn(None, 'up', wc_dir)
840
841  # now rollback to r1, thereby reinstating the old 'G'
842  expected_output = svntest.wc.State(wc_dir, {
843    'A/D/G': Item(status='R '),
844    'A/D/G/rho': Item(status='A '),
845    'A/D/G/pi': Item(status='A '),
846    'A/D/G/tau': Item(status='A '),
847    })
848  expected_mergeinfo_output = svntest.wc.State(wc_dir, {
849    '': Item(status=' U'),
850    })
851  expected_elision_output = svntest.wc.State(wc_dir, {
852    '': Item(status=' U'),
853    })
854  expected_status = svntest.actions.get_virginal_state(wc_dir, 3)
855  expected_status.tweak('A/D/G', status='R ', copied='+', wc_rev='-')
856  expected_status.tweak('A/D/G/rho',
857                        'A/D/G/pi',
858                        'A/D/G/tau',
859                        copied='+', wc_rev='-')
860  expected_status.add({
861    'A/D/G/alpha' : Item(status='D ', wc_rev='3'),
862    'A/D/G/beta' : Item(status='D ', wc_rev='3'),
863    })
864
865  expected_skip = wc.State(wc_dir, { })
866  expected_disk   = svntest.main.greek_state.copy()
867  svntest.actions.run_and_verify_merge(wc_dir, '3', '1',
868                                       sbox.repo_url, None,
869                                       expected_output,
870                                       expected_mergeinfo_output,
871                                       expected_elision_output,
872                                       expected_disk,
873                                       expected_status,
874                                       expected_skip,
875                                       dry_run = 0)
876
877  # now test if the revert works ok
878  revert_paths = [G_path] + [os.path.join(G_path, child)
879                             for child in ['alpha', 'beta', 'pi', 'rho', 'tau']]
880
881  run_and_verify_revert([G_path], ["-R"], revert_paths)
882
883  svntest.actions.run_and_verify_svn([], [],
884                                     "status", wc_dir)
885
886  svntest.main.safe_rmtree(G_path)
887
888  expected_output = svntest.verify.UnorderedOutput(
889      ["!       " + G_path + "\n",
890       "!       " + os.path.join(G_path, "alpha") + "\n",
891       "!       " + os.path.join(G_path, "beta") + "\n"])
892  svntest.actions.run_and_verify_svn(expected_output, [], "status",
893                                     wc_dir)
894
895# Test for issue #2928.
896@Issue(2928)
897def revert_replaced_with_history_file_2(sbox):
898  "reverted replace with history restores checksum"
899
900  sbox.build()
901  wc_dir = sbox.wc_dir
902  iota_path = os.path.join(wc_dir, 'iota')
903  mu_path = os.path.join(wc_dir, 'A', 'mu')
904
905  # Delete mu and replace it with a copy of iota
906  svntest.main.run_svn(None, 'rm', mu_path)
907  svntest.main.run_svn(None, 'cp', iota_path, mu_path)
908
909  # Revert mu.
910  svntest.main.run_svn(None, 'revert', mu_path)
911
912  # If we make local mods to the reverted mu the commit will
913  # fail if the checksum is incorrect.
914  svntest.main.file_write(mu_path, "new text")
915  expected_output = svntest.wc.State(wc_dir, {
916    'A/mu': Item(verb='Sending'),
917    })
918  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
919  expected_status.tweak('A/mu', status='  ', wc_rev=2)
920  svntest.actions.run_and_verify_commit(wc_dir,
921                                        expected_output,
922                                        expected_status)
923
924#----------------------------------------------------------------------
925
926def revert_tree_conflicts_in_updated_files(sbox):
927  "revert tree conflicts in updated files"
928
929  # See use cases 1-3 in notes/tree-conflicts/use-cases.txt for background.
930
931  svntest.actions.build_greek_tree_conflicts(sbox)
932  wc_dir = sbox.wc_dir
933  G = os.path.join(wc_dir, 'A', 'D', 'G')
934  G_pi  = os.path.join(G, 'pi')
935  G_rho = os.path.join(G, 'rho')
936  G_tau = os.path.join(G, 'tau')
937
938  # Duplicate wc for tests
939  wc_dir_2 =  sbox.add_wc_path('2')
940  svntest.actions.duplicate_dir(wc_dir, wc_dir_2)
941  G2 = os.path.join(wc_dir_2, 'A', 'D', 'G')
942  G2_pi  = os.path.join(G2, 'pi')
943  G2_rho = os.path.join(G2, 'rho')
944  G2_tau = os.path.join(G2, 'tau')
945
946  # Expectations
947  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
948  expected_status.tweak('A/D/G/pi',  status='  ')
949  expected_status.remove('A/D/G/rho')
950  expected_status.remove('A/D/G/tau')
951
952  expected_disk = svntest.main.greek_state.copy()
953  expected_disk.remove('A/D/G/rho')
954  expected_disk.tweak('A/D/G/pi',
955                      contents="This is the file 'pi'.\nIncoming edit.\n")
956  expected_disk.remove('A/D/G/tau')
957
958  # Revert individually in wc
959  run_and_verify_revert([G_pi, G_rho, G_tau])
960  svntest.actions.run_and_verify_status(wc_dir, expected_status)
961  svntest.actions.verify_disk(wc_dir, expected_disk)
962
963  # Expectations
964  expected_status.wc_dir = wc_dir_2
965
966  # Revert recursively in wc 2
967  run_and_verify_revert([G2], ['-R'], [G2_pi, G2_rho, G2_tau])
968  svntest.actions.run_and_verify_status(wc_dir_2, expected_status)
969  svntest.actions.verify_disk(wc_dir_2, expected_disk)
970
971def revert_add_over_not_present_dir(sbox):
972  "reverting an add over not present directory"
973
974  sbox.build()
975  wc_dir = sbox.wc_dir
976
977  main.run_svn(None, 'rm', os.path.join(wc_dir, 'A/C'))
978  sbox.simple_commit(message='Deleted dir')
979
980  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
981  expected_status.remove('A/C')
982  svntest.actions.run_and_verify_status(wc_dir, expected_status)
983
984  main.run_svn(None, 'mkdir', os.path.join(wc_dir, 'A/C'))
985
986  # This failed in some WC-NG intermediate format (r927318-r958992).
987  main.run_svn(None, 'revert', os.path.join(wc_dir, 'A/C'))
988
989  svntest.actions.run_and_verify_status(wc_dir, expected_status)
990
991
992def revert_added_tree(sbox):
993  "revert an added tree fails"
994
995  sbox.build()
996  wc_dir = sbox.wc_dir
997  svntest.actions.run_and_verify_svn(None, [],
998                                     'mkdir', sbox.ospath('X'), sbox.ospath('X/Y'))
999  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1000  expected_status.add({
1001    'X'   : Item(status='A ', wc_rev=0),
1002    'X/Y' : Item(status='A ', wc_rev=0),
1003    })
1004  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1005
1006  # Revert is non-recursive and fails, status is unchanged
1007  expected_error = '.*Try \'svn revert --depth infinity\'.*'
1008  svntest.actions.run_and_verify_svn(None, expected_error,
1009                                     'revert', sbox.ospath('X'))
1010  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1011
1012
1013@Issue(3834)
1014def revert_child_of_copy(sbox):
1015  "revert a child of a copied directory"
1016
1017  sbox.build()
1018  wc_dir = sbox.wc_dir
1019  svntest.actions.run_and_verify_svn(None, [],
1020                                     'cp',
1021                                     sbox.ospath('A/B/E'),
1022                                     sbox.ospath('A/B/E2'))
1023
1024
1025  svntest.main.file_append(sbox.ospath('A/B/E2/beta'), 'extra text\n')
1026  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1027  expected_status.add({
1028    'A/B/E2'       : Item(status='A ', copied='+', wc_rev='-'),
1029    'A/B/E2/alpha' : Item(status='  ', copied='+', wc_rev='-'),
1030    'A/B/E2/beta'  : Item(status='M ', copied='+', wc_rev='-'),
1031    })
1032  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1033
1034  # First revert removes text change, child is still copied
1035  run_and_verify_revert(sbox.ospaths(['A/B/E2/beta']))
1036  expected_status.tweak('A/B/E2/beta', status='  ')
1037  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1038
1039  # Second revert of child does nothing, child is still copied
1040  svntest.actions.run_and_verify_svn(None, [],
1041                                     'revert', sbox.ospath('A/B/E2/beta'))
1042  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1043
1044@Issue(3783)
1045def revert_non_recusive_after_delete(sbox):
1046  "non-recursive revert after delete"
1047
1048  sbox.build(read_only=True)
1049  wc_dir = sbox.wc_dir
1050
1051  svntest.actions.run_and_verify_svn(None, [], 'rm', sbox.ospath('A/B'))
1052  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1053  expected_status.tweak('A/B', 'A/B/E', 'A/B/E/alpha', 'A/B/E/beta', 'A/B/F',
1054                        'A/B/lambda', status='D ')
1055  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1056
1057  # This appears to work but gets the op-depth wrong
1058  run_and_verify_revert(sbox.ospaths(['A/B']))
1059  expected_status.tweak('A/B', status='  ')
1060  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1061
1062  svntest.actions.run_and_verify_svn(None, [],
1063                                     'mkdir', sbox.ospath('A/B/E'))
1064  expected_status.tweak('A/B/E', status='R ')
1065  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1066
1067  # Since the op-depth was wrong A/B/E erroneously remains deleted
1068  run_and_verify_revert(sbox.ospaths(['A/B/E']))
1069  expected_status.tweak('A/B/E', status='  ')
1070  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1071
1072def revert_permissions_only(sbox):
1073  "permission-only reverts"
1074
1075  sbox.build()
1076  wc_dir = sbox.wc_dir
1077
1078  # Helpers pinched/adapted from lock_tests.py.  Put them somewhere common?
1079  def check_writability(path, writable):
1080    bits = stat.S_IWGRP | stat.S_IWOTH | stat.S_IWRITE
1081    mode = os.stat(path)[0]
1082    if bool(mode & bits) != writable:
1083      raise svntest.Failure("path '%s' is unexpectedly %s (mode %o)"
1084                            % (path, ["writable", "read-only"][writable], mode))
1085
1086  def is_writable(path):
1087    "Raise if PATH is not writable."
1088    check_writability(path, True)
1089
1090  def is_readonly(path):
1091    "Raise if PATH is not readonly."
1092    check_writability(path, False)
1093
1094  def check_executability(path, executable):
1095    bits = stat.S_IXGRP | stat.S_IXOTH | stat.S_IEXEC
1096    mode = os.stat(path)[0]
1097    if bool(mode & bits) != executable:
1098      raise svntest.Failure("path '%s' is unexpectedly %s (mode %o)"
1099                            % (path,
1100                               ["executable", "non-executable"][executable],
1101                               mode))
1102
1103  def is_executable(path):
1104    "Raise if PATH is not executable."
1105    check_executability(path, True)
1106
1107  def is_non_executable(path):
1108    "Raise if PATH is executable."
1109    check_executability(path, False)
1110
1111
1112  os.chmod(sbox.ospath('A/B/E/alpha'), svntest.main.S_ALL_READ)  # read-only
1113  is_readonly(sbox.ospath('A/B/E/alpha'))
1114  run_and_verify_revert(sbox.ospaths(['A/B/E/alpha']))
1115  is_writable(sbox.ospath('A/B/E/alpha'))
1116
1117  if svntest.main.is_posix_os():
1118    os.chmod(sbox.ospath('A/B/E/beta'), svntest.main.S_ALL_RWX)   # executable
1119    is_executable(sbox.ospath('A/B/E/beta'))
1120    run_and_verify_revert(sbox.ospaths(['A/B/E/beta']))
1121    is_non_executable(sbox.ospath('A/B/E/beta'))
1122
1123  svntest.actions.run_and_verify_svn(None, [],
1124                                     'propset', 'svn:needs-lock', '1',
1125                                     sbox.ospath('A/B/E/alpha'))
1126  svntest.actions.run_and_verify_svn(None, [],
1127                                     'propset', 'svn:executable', '1',
1128                                     sbox.ospath('A/B/E/beta'))
1129
1130  expected_output = svntest.wc.State(wc_dir, {
1131    'A/B/E/alpha': Item(verb='Sending'),
1132    'A/B/E/beta':  Item(verb='Sending'),
1133    })
1134  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1135  expected_status.tweak('A/B/E/alpha', wc_rev='2')
1136  expected_status.tweak('A/B/E/beta',  wc_rev='2')
1137  svntest.actions.run_and_verify_commit(wc_dir,
1138                                        expected_output,
1139                                        expected_status)
1140
1141  os.chmod(sbox.ospath('A/B/E/alpha'), svntest.main.S_ALL_RW)  # not read-only
1142  is_writable(sbox.ospath('A/B/E/alpha'))
1143  run_and_verify_revert(sbox.ospaths(['A/B/E/alpha']))
1144  is_readonly(sbox.ospath('A/B/E/alpha'))
1145
1146  if svntest.main.is_posix_os():
1147    os.chmod(sbox.ospath('A/B/E/beta'), svntest.main.S_ALL_RW)   # not executable
1148    is_non_executable(sbox.ospath('A/B/E/beta'))
1149    run_and_verify_revert(sbox.ospaths(['A/B/E/beta']))
1150    is_executable(sbox.ospath('A/B/E/beta'))
1151
1152  # copied file is always writeable
1153  sbox.simple_update()
1154  expected_output = ["A         %s\n" % sbox.ospath('A/B/E2')]
1155  svntest.actions.run_and_verify_svn(expected_output, [], 'copy',
1156                                     sbox.ospath('A/B/E'),
1157                                     sbox.ospath('A/B/E2'))
1158  is_writable(sbox.ospath('A/B/E2/alpha'))
1159  svntest.actions.run_and_verify_svn([], [],
1160                                     'revert', sbox.ospath('A/B/E2/alpha'))
1161  is_writable(sbox.ospath('A/B/E2/alpha'))
1162
1163@XFail()
1164@Issue(3851)
1165def revert_copy_depth_files(sbox):
1166  "revert a copy with depth=files"
1167
1168  sbox.build(read_only=True)
1169  wc_dir = sbox.wc_dir
1170
1171  svntest.actions.run_and_verify_svn(None, [],
1172                                     'copy',
1173                                     sbox.ospath('A/B/E'),
1174                                     sbox.ospath('A/B/E2'))
1175
1176  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1177  expected_status.add({
1178    'A/B/E2'       : Item(status='A ', copied='+', wc_rev='-'),
1179    'A/B/E2/alpha' : Item(status='  ', copied='+', wc_rev='-'),
1180    'A/B/E2/beta'  : Item(status='  ', copied='+', wc_rev='-'),
1181    })
1182  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1183
1184  run_and_verify_revert(sbox.ospaths(['A/B/E2']), ['--depth', 'files'],
1185                        sbox.ospaths(['A/B/E2', 'A/B/E2/alpha', 'A/B/E2/beta']))
1186
1187  expected_status.remove('A/B/E2', 'A/B/E2/alpha', 'A/B/E2/beta')
1188  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1189
1190@XFail()
1191@Issue(3851)
1192def revert_nested_add_depth_immediates(sbox):
1193  "revert a nested add with depth=immediates"
1194
1195  sbox.build(read_only=True)
1196  wc_dir = sbox.wc_dir
1197
1198  svntest.actions.run_and_verify_svn(None, [],
1199                                     'mkdir', '--parents', sbox.ospath('A/X/Y'))
1200
1201  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1202  expected_status.add({
1203    'A/X'       : Item(status='A ', wc_rev='0'),
1204    'A/X/Y'     : Item(status='A ', wc_rev='0'),
1205    })
1206  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1207
1208  run_and_verify_revert(sbox.ospaths(['A/X']), ['--depth', 'immediates'],
1209                        sbox.ospaths(['A/X', 'A/X/Y']))
1210
1211  expected_status.remove('A/X', 'A/X/Y')
1212  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1213
1214def create_superflous_actual_node(sbox):
1215  "create a superfluous actual node"
1216
1217  sbox.build()
1218  wc_dir = sbox.wc_dir
1219
1220  svntest.main.file_append(sbox.ospath('A/B/E/alpha'), 'their text\n')
1221  sbox.simple_commit()
1222  sbox.simple_update()
1223
1224  # Create a NODES row with op-depth>0
1225  svntest.actions.run_and_verify_svn(None, [],
1226                                     'copy', '-r', '1',
1227                                     sbox.repo_url + '/A/B/E/alpha',
1228                                     sbox.ospath('alpha'))
1229
1230  # Merge to create an ACTUAL with a conflict
1231  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
1232  expected_status.add({
1233    'alpha' : Item(status='A ', copied='+', wc_rev='-'),
1234    })
1235  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1236  svntest.main.file_append(sbox.ospath('alpha'), 'my text\n')
1237  svntest.actions.run_and_verify_svn(None, [],
1238                                     'merge', '--accept', 'postpone',
1239                                     '^/A/B/E/alpha', sbox.ospath('alpha'))
1240  expected_status.tweak('alpha', status='CM', entry_status='A ')
1241  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1242
1243  # Clear merge property and remove conflict files
1244  sbox.simple_propdel('svn:mergeinfo', 'alpha')
1245  os.remove(sbox.ospath('alpha.merge-left.r1'))
1246  os.remove(sbox.ospath('alpha.merge-right.r2'))
1247  os.remove(sbox.ospath('alpha.working'))
1248
1249  expected_status.tweak('alpha', status='A ')
1250  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1251
1252@Issue(3859)
1253@SkipUnless(svntest.main.server_has_mergeinfo)
1254def revert_empty_actual(sbox):
1255  "revert with superfluous actual node"
1256
1257  create_superflous_actual_node(sbox)
1258  wc_dir = sbox.wc_dir
1259
1260  # Non-recursive code path works
1261  run_and_verify_revert(sbox.ospaths(['alpha']))
1262
1263  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
1264  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1265
1266@Issue(3859)
1267@SkipUnless(svntest.main.server_has_mergeinfo)
1268def revert_empty_actual_recursive(sbox):
1269  "recursive revert with superfluous actual node"
1270
1271  create_superflous_actual_node(sbox)
1272  wc_dir = sbox.wc_dir
1273
1274  # Recursive code path fails, the superfluous actual node suppresses the
1275  # notification
1276  run_and_verify_revert(sbox.ospaths(['alpha']), ['-R'])
1277
1278  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
1279  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1280
1281@Issue(3879)
1282def revert_tree_conflicts_with_replacements(sbox):
1283  "revert tree conflicts with replacements"
1284
1285  sbox.build()
1286  wc_dir = sbox.wc_dir
1287  wc = sbox.ospath
1288
1289  # Use case 1: local replace, incoming replace
1290  # A/mu
1291  # A/D/H --> A/D/H/chi, A/D/H/{loc,inc}_psi
1292
1293  # Use case 2: local edit, incoming replace
1294  # A/D/gamma
1295  # A/D/G --> A/D/G/pi, A/D/G/inc_rho
1296
1297  # Use case 3: local replace, incoming edit
1298  # A/B/lambda
1299  # A/B/E --> A/B/E/alpha, A/B/E/loc_beta
1300
1301  # Case 1: incoming replacements
1302  sbox.simple_rm('A/mu', 'A/D/H')
1303  file_write(wc('A/mu'), "A fresh file.\n")
1304  os.mkdir(wc('A/D/H'))
1305  file_write(wc('A/D/H/chi'), "A fresh file.\n")
1306  file_write(wc('A/D/H/inc_psi'), "A fresh file.\n")
1307  sbox.simple_add('A/mu', 'A/D/H')
1308
1309  # Case 2: incoming replacements
1310  sbox.simple_rm('A/D/gamma', 'A/D/G')
1311  file_write(wc('A/D/gamma'), "A fresh file.\n")
1312  os.mkdir(wc('A/D/G'))
1313  file_write(wc('A/D/G/pi'), "A fresh file.\n")
1314  file_write(wc('A/D/G/inc_rho'), "A fresh file.\n")
1315  sbox.simple_add('A/D/gamma','A/D/G')
1316
1317  # Case 3: incoming edits
1318  file_append(wc('A/B/lambda'), "Incoming!\n")
1319  file_write(wc('A/B/E/alpha'), "Incoming!.\n")
1320
1321  # Commit and roll back to r1.
1322  sbox.simple_commit()
1323  run_svn(None, 'up', wc_dir, '-r1', '-q')
1324
1325  # Case 1: local replacements
1326  sbox.simple_rm('A/mu', 'A/D/H')
1327  file_write(wc('A/mu'), "A fresh file.\n")
1328  os.mkdir(wc('A/D/H'))
1329  file_write(wc('A/D/H/chi'), "A fresh local file.\n")
1330  file_write(wc('A/D/H/loc_psi'), "A fresh local file.\n")
1331  sbox.simple_add('A/mu', 'A/D/H')
1332
1333  # Case 2: local edits
1334  file_append(wc('A/D/gamma'), "Local change.\n")
1335  file_append(wc('A/D/G/pi'), "Local change.\n")
1336
1337  # Case 3: local replacements
1338  sbox.simple_rm('A/B/lambda', 'A/B/E')
1339  file_write(wc('A/B/lambda'), "A fresh local file.\n")
1340  os.mkdir(wc('A/B/E'))
1341  file_write(wc('A/B/E/alpha'), "A fresh local file.\n")
1342  file_write(wc('A/B/E/loc_beta'), "A fresh local file.\n")
1343  sbox.simple_add('A/B/lambda', 'A/B/E')
1344
1345  # Update and check tree conflict status.
1346  run_svn(None, 'up', wc_dir)
1347  expected_status = svntest.wc.State(wc_dir, {
1348    ''                : Item(status='  ', wc_rev=2),
1349    'A'               : Item(status='  ', wc_rev=2),
1350    'A/B'             : Item(status='  ', wc_rev=2),
1351    'A/B/E'           : Item(status='R ', wc_rev=2, treeconflict='C'),
1352    'A/B/E/alpha'     : Item(status='A ', wc_rev='-'),
1353    'A/B/E/beta'      : Item(status='D ', wc_rev=2),
1354    'A/B/E/loc_beta'  : Item(status='A ', wc_rev='-'),
1355    'A/B/F'           : Item(status='  ', wc_rev=2),
1356    'A/B/lambda'      : Item(status='R ', wc_rev=2, treeconflict='C'),
1357    'A/C'             : Item(status='  ', wc_rev=2),
1358    'A/D'             : Item(status='  ', wc_rev=2),
1359    'A/D/G'           : Item(status='R ', wc_rev='-', copied='+',
1360                             treeconflict='C'),
1361    'A/D/G/inc_rho'   : Item(status='D ', wc_rev=2),
1362    'A/D/G/pi'        : Item(status='M ', wc_rev='-', copied='+'),
1363    'A/D/G/rho'       : Item(status='  ', wc_rev='-', copied='+'),
1364    'A/D/G/tau'       : Item(status='  ', wc_rev='-', copied='+'),
1365    'A/D/H'           : Item(status='R ', wc_rev=2, treeconflict='C'),
1366    'A/D/H/chi'       : Item(status='A ', wc_rev='-'),
1367    'A/D/H/inc_psi'   : Item(status='D ', wc_rev=2),
1368    'A/D/H/loc_psi'   : Item(status='A ', wc_rev='-'),
1369    'A/D/gamma'       : Item(status='R ', wc_rev='-', copied='+',
1370                             treeconflict='C'),
1371    'A/mu'            : Item(status='R ', wc_rev=2, treeconflict='C'),
1372    'iota'            : Item(status='  ', wc_rev=2),
1373    })
1374  svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status)
1375
1376  def cd_and_status_u(dir_target):
1377    was_cwd = os.getcwd()
1378    os.chdir(os.path.abspath(wc(dir_target)))
1379    run_svn(None, 'status', '-u')
1380    os.chdir(was_cwd)
1381
1382  cd_and_status_u('A')
1383  cd_and_status_u('A/D')
1384
1385  # Until r1102143, the following 'status -u' commands failed with "svn:
1386  # E165004: Two top-level reports with no target".
1387  cd_and_status_u('A/D/G')
1388  cd_and_status_u('A/D/H')
1389
1390  # Revert everything (i.e., accept "theirs-full").
1391  reverted_paths = [
1392    wc('A/B/E'),
1393    wc('A/B/E/alpha'),   # incoming & local
1394    wc('A/B/E/beta'),
1395    wc('A/B/E/loc_beta'),
1396    wc('A/B/lambda'),
1397    wc('A/D/G'),
1398    wc('A/D/G/pi'),
1399    wc('A/D/G/inc_rho'), # incoming
1400    wc('A/D/G/rho'),
1401    wc('A/D/G/tau'),
1402    wc('A/D/H'),
1403    wc('A/D/H/chi'),
1404    wc('A/D/H/inc_psi'), # incoming
1405    wc('A/D/H/loc_psi'),
1406    wc('A/D/gamma'),
1407    wc('A/mu'),
1408    ]
1409  run_and_verify_revert([wc_dir], ['-R'], reverted_paths)
1410
1411  # Remove a few unversioned files that revert left behind.
1412  os.remove(wc('A/B/E/loc_beta'))
1413  os.remove(wc('A/D/H/loc_psi'))
1414
1415  # The update operation should have put all incoming items in place.
1416  expected_status = svntest.wc.State(wc_dir, {
1417    ''                : Item(status='  ', wc_rev=2),
1418    'A'               : Item(status='  ', wc_rev=2),
1419    'A/B'             : Item(status='  ', wc_rev=2),
1420    'A/B/E'           : Item(status='  ', wc_rev=2),
1421    'A/B/E/alpha'     : Item(status='  ', wc_rev=2),
1422    'A/B/E/beta'      : Item(status='  ', wc_rev=2),
1423    'A/B/F'           : Item(status='  ', wc_rev=2),
1424    'A/B/lambda'      : Item(status='  ', wc_rev=2),
1425    'A/C'             : Item(status='  ', wc_rev=2),
1426    'A/D'             : Item(status='  ', wc_rev=2),
1427    'A/D/G'           : Item(status='  ', wc_rev=2),
1428    'A/D/G/inc_rho'   : Item(status='  ', wc_rev=2),
1429    'A/D/G/pi'        : Item(status='  ', wc_rev=2),
1430    'A/D/H'           : Item(status='  ', wc_rev=2),
1431    'A/D/H/chi'       : Item(status='  ', wc_rev=2),
1432    'A/D/H/inc_psi'   : Item(status='  ', wc_rev=2),
1433    'A/D/gamma'       : Item(status='  ', wc_rev=2),
1434    'A/mu'            : Item(status='  ', wc_rev=2),
1435    'iota'            : Item(status='  ', wc_rev=2),
1436    })
1437  svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status)
1438
1439def create_no_text_change_conflict(sbox):
1440  "create conflict with no text change"
1441
1442  sbox.build()
1443  wc_dir = sbox.wc_dir
1444
1445  shutil.copyfile(sbox.ospath('A/B/E/alpha'), sbox.ospath('A/B/E/alpha-copy'))
1446  svntest.main.file_append(sbox.ospath('A/B/E/alpha'), 'their text\n')
1447  sbox.simple_commit()
1448  sbox.simple_update()
1449
1450  # Update to create a conflict
1451  svntest.main.file_append(sbox.ospath('A/B/E/alpha'), 'my text\n')
1452  svntest.actions.run_and_verify_svn(None, [],
1453                                     'up', '-r1', '--accept', 'postpone',
1454                                     wc_dir)
1455  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1456  expected_status.tweak('A/B/E/alpha', status='C ')
1457  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1458
1459  # Reset the text with the file still marked as a conflict
1460  os.remove(sbox.ospath('A/B/E/alpha'))
1461  shutil.move(sbox.ospath('A/B/E/alpha-copy'), sbox.ospath('A/B/E/alpha'))
1462
1463@Issue(3859)
1464def revert_no_text_change_conflict(sbox):
1465  "revert conflict with no text change"
1466
1467  create_no_text_change_conflict(sbox)
1468  wc_dir = sbox.wc_dir
1469
1470  run_and_verify_revert(sbox.ospaths(['A/B/E/alpha']))
1471
1472  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1473  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1474
1475@Issue(3859)
1476def revert_no_text_change_conflict_recursive(sbox):
1477  "revert -R conflict with no text change"
1478
1479  create_no_text_change_conflict(sbox)
1480  wc_dir = sbox.wc_dir
1481
1482  run_and_verify_revert(sbox.ospaths(['A/B/E/alpha']), ['-R'])
1483
1484  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1485  svntest.actions.run_and_verify_status(wc_dir, expected_status)
1486
1487@Issue(3938)
1488def revert_with_unversioned_targets(sbox):
1489  "revert with unversioned targets"
1490
1491  sbox.build()
1492  wc_dir = sbox.wc_dir
1493
1494  chi_path = sbox.ospath('A/D/H/chi')
1495  delta_path = sbox.ospath('A/D/H/delta')
1496  psi_path = sbox.ospath('A/D/H/psi')
1497
1498  chi_contents = "modified chi\n"
1499  delta_contents = "This is the unversioned file 'delta'.\n"
1500  psi_contents = "modified psi\n"
1501
1502  # touch delta
1503  with open(delta_path, 'w') as f:
1504    f.write(delta_contents)
1505
1506  # modify chi psi
1507  with open(chi_path, 'w') as f:
1508    f.write(chi_contents)
1509  with open(psi_path, 'w') as f:
1510    f.write(psi_contents)
1511
1512  # revert
1513  run_and_verify_revert([chi_path, delta_path, psi_path], [],
1514                        [chi_path, psi_path], [delta_path])
1515
1516  # verify status
1517  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
1518  expected_status.add({
1519    'A/D/H/delta': Item(status='? '),
1520    })
1521  svntest.actions.run_and_verify_unquiet_status(wc_dir, expected_status)
1522
1523  # verify disk
1524  expected_disk = svntest.main.greek_state.copy()
1525  expected_disk.add({
1526    'A/D/H/delta': Item(delta_contents),
1527  })
1528  svntest.actions.verify_disk(wc_dir, expected_disk.old_tree(), True)
1529
1530def revert_nonexistent(sbox):
1531  'svn revert -R nonexistent'
1532  sbox.build(read_only=True)
1533  run_and_verify_revert(sbox.ospaths(['nonexistent']), ['-R'],
1534                        [], sbox.ospaths(['nonexistent']))
1535
1536@Issue(4168)
1537def revert_obstructing_wc(sbox):
1538  "revert with an obstructing working copy"
1539
1540  sbox.build(create_wc=False, read_only=True)
1541  wc_dir = sbox.wc_dir
1542
1543  expected_output = svntest.wc.State(wc_dir, {})
1544  expected_disk = svntest.wc.State(wc_dir, {})
1545
1546  # Checkout wc as depth empty
1547  svntest.actions.run_and_verify_checkout(sbox.repo_url, wc_dir,
1548                                          expected_output, expected_disk,
1549                                          [],
1550                                          '--depth', 'empty')
1551
1552  # And create an obstructing working copy as A
1553  svntest.actions.run_and_verify_checkout(sbox.repo_url, wc_dir + '/A',
1554                                          expected_output, expected_disk,
1555                                          [],
1556                                          '--depth', 'empty')
1557
1558  # Now try to fetch the entire wc, which will find an obstruction
1559  expected_output = svntest.wc.State(wc_dir, {
1560    'A'     : Item(verb='Skipped'),
1561    'iota'  : Item(status='A '),
1562  })
1563  expected_status = svntest.wc.State(wc_dir, {
1564    ''      : Item(status='  ', wc_rev='1'),
1565    'iota'  : Item(status='  ', wc_rev='1'),
1566    # A is not versioned but exists
1567  })
1568
1569  svntest.actions.run_and_verify_update(wc_dir,
1570                                        expected_output, None,
1571                                        expected_status,
1572                                        [], False,
1573                                        wc_dir, '--set-depth', 'infinity')
1574
1575  # Revert should do nothing (no local changes), and report the obstruction
1576  # (reporting the obstruction is nice for debugging, but not really required
1577  #  in this specific case, as the node was not modified)
1578  svntest.actions.run_and_verify_svn("Skipped '.*A' -- .*obstruct.*", [],
1579                                     'revert', '-R', wc_dir)
1580
1581def revert_moved_dir_partial(sbox):
1582  "partial revert moved_dir"
1583
1584  sbox.build(read_only = True)
1585
1586  sbox.simple_move('A', 'A_')
1587  svntest.actions.run_and_verify_svn(None, [], 'revert', sbox.ospath('A'))
1588
1589@XFail()
1590@Issue(4798)
1591def revert_remove_added(sbox):
1592  "revert_remove_added"
1593
1594  sbox.build(empty=True, read_only=True)
1595
1596  # We'll test the items named with a '1' as direct targets to 'revert',
1597  # and items named with a '2' as items found by recursion.
1598  sbox.simple_mkdir('D1', 'D2')
1599  sbox.simple_add_text('This is a new file.',
1600                       'D1/file', 'file1',
1601                       'D2/file', 'file2')
1602
1603  run_and_verify_revert(sbox.ospaths(['D1']), ['--remove-added', '-R'],
1604                        sbox.ospaths(['D1/file', 'D1']))
1605  assert(not os.path.exists(sbox.ospath('D1')))
1606
1607  run_and_verify_revert(sbox.ospaths(['file1']), ['--remove-added'],
1608                        sbox.ospaths(['file1']))
1609  assert(not os.path.exists(sbox.ospath('file1')))
1610
1611  run_and_verify_revert(sbox.ospaths(['.']), ['--remove-added', '-R'],
1612                        sbox.ospaths(['D2/file', 'D2', 'file2']))
1613  assert(not os.path.exists(sbox.ospath('file2')))
1614  assert(not os.path.exists(sbox.ospath('D2')))
1615
1616
1617########################################################################
1618# Run the tests
1619
1620
1621# list all tests here, starting with None:
1622test_list = [ None,
1623              revert_from_wc_root,
1624              revert_reexpand_keyword,
1625              revert_replaced_file_without_props,
1626              revert_moved_file,
1627              revert_wc_to_wc_replace_with_props,
1628              revert_file_merge_replace_with_history,
1629              revert_repos_to_wc_replace_with_props,
1630              revert_after_second_replace,
1631              revert_after_manual_conflict_resolution__text,
1632              revert_after_manual_conflict_resolution__prop,
1633              revert_propset__dir,
1634              revert_propset__file,
1635              revert_propdel__dir,
1636              revert_propdel__file,
1637              revert_replaced_with_history_file_1,
1638              status_of_missing_dir_after_revert,
1639              status_of_missing_dir_after_revert_replaced_with_history_dir,
1640              revert_replaced_with_history_file_2,
1641              revert_tree_conflicts_in_updated_files,
1642              revert_add_over_not_present_dir,
1643              revert_added_tree,
1644              revert_child_of_copy,
1645              revert_non_recusive_after_delete,
1646              revert_permissions_only,
1647              revert_copy_depth_files,
1648              revert_nested_add_depth_immediates,
1649              revert_empty_actual,
1650              revert_tree_conflicts_with_replacements,
1651              revert_empty_actual_recursive,
1652              revert_no_text_change_conflict,
1653              revert_no_text_change_conflict_recursive,
1654              revert_with_unversioned_targets,
1655              revert_nonexistent,
1656              revert_obstructing_wc,
1657              revert_moved_dir_partial,
1658              revert_remove_added,
1659             ]
1660
1661if __name__ == '__main__':
1662  svntest.main.run_tests(test_list)
1663  # NOTREACHED
1664
1665
1666### End of file.
1667