subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From br...@apache.org
Subject svn commit: r1842404 [10/11] - in /subversion/branches/better-pristines: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ contrib/client-side/svn_load_dirs/ doc/user/ notes/logo/ notes/shelving/ subversion/bindings/javahl/ subvers...
Date Sun, 30 Sep 2018 18:26:49 GMT
Modified: subversion/branches/better-pristines/subversion/tests/cmdline/shelf_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/shelf_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/shelf_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/shelf_tests.py Sun Sep 30 18:26:47 2018
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 #
-#  shelve_tests.py:  testing shelving
+#  shelf_tests.py:  testing shelving
 #
 #  Subversion is a tool for revision control.
 #  See http://subversion.apache.org for more information.
@@ -32,6 +32,10 @@ logger = logging.getLogger()
 # Our testing module
 import svntest
 from svntest import wc
+from svntest.verify import make_diff_header, make_no_diff_deleted_header, \
+                           make_git_diff_header, make_diff_prop_header, \
+                           make_diff_prop_val, make_diff_prop_deleted, \
+                           make_diff_prop_added, make_diff_prop_modified
 
 # (abbreviation)
 Skip = svntest.testcase.Skip_deco
@@ -44,38 +48,92 @@ Item = wc.StateItem
 
 #----------------------------------------------------------------------
 
-def state_from_status(wc_dir):
-  _, output, _ = svntest.main.run_svn(None, 'status', '-v', '-u', '-q',
-                                      wc_dir)
+def state_from_status(wc_dir,
+                      v=True, u=True, q=True):
+  opts = ()
+  if v:
+    opts += ('-v',)
+  if u:
+    opts += ('-u',)
+  if q:
+    opts += ('-q',)
+  _, output, _ = svntest.main.run_svn(None, 'status', wc_dir, *opts)
   return svntest.wc.State.from_status(output, wc_dir)
 
-def shelve_unshelve_verify(sbox, modifier):
+def get_wc_state(wc_dir):
+  """Return a description of the WC state. Include as much info as shelving
+     should be capable of restoring.
+  """
+  return (state_from_status(wc_dir),
+          svntest.wc.State.from_wc(wc_dir, load_props=True),
+          )
+
+def check_wc_state(wc_dir, expected):
+  """Check a description of the WC state. Include as much info as shelving
+     should be capable of restoring.
+  """
+  expect_st, expect_wc = expected
+  actual_st, actual_wc = get_wc_state(wc_dir)
+
+  # Verify actual status against expected status.
+  try:
+    expect_st.compare_and_display('status', actual_st)
+  except svntest.tree.SVNTreeError:
+    svntest.actions._log_tree_state("EXPECT STATUS TREE:", expect_st.old_tree(),
+                                    wc_dir)
+    svntest.actions._log_tree_state("ACTUAL STATUS TREE:", actual_st.old_tree(),
+                                    wc_dir)
+    raise
+
+  # Verify actual WC against expected WC.
+  try:
+    expect_wc.compare_and_display('status', actual_wc)
+  except svntest.tree.SVNTreeError:
+    svntest.actions._log_tree_state("EXPECT WC TREE:", expect_wc.old_tree(),
+                                    wc_dir)
+    svntest.actions._log_tree_state("ACTUAL WC TREE:", actual_wc.old_tree(),
+                                    wc_dir)
+    raise
+
+def shelve_unshelve_verify(sbox, modifier, cannot_shelve=False):
   """Round-trip: shelve; verify all changes are reverted;
      unshelve; verify all changes are restored.
   """
 
   wc_dir = sbox.wc_dir
-  virginal_state = state_from_status(wc_dir)
+  virginal_state = get_wc_state(wc_dir)
 
   # Make some changes to the working copy
   modifier(sbox)
 
   # Save the modified state
-  modified_state = state_from_status(wc_dir)
+  modified_state = get_wc_state(wc_dir)
+
+  if cannot_shelve:
+    svntest.actions.run_and_verify_svn(None, '.* could not be shelved.*',
+                                       'x-shelve', 'foo')
+    return
 
   # Shelve; check there are no longer any modifications
   svntest.actions.run_and_verify_svn(None, [],
-                                     'shelve', 'foo')
-  svntest.actions.run_and_verify_status(wc_dir, virginal_state)
+                                     'x-shelve', 'foo')
+  check_wc_state(wc_dir, virginal_state)
+
+  # List; ensure the shelf is listed
+  expected_output = svntest.verify.RegexListOutput(
+    [r'foo\s*version \d+.*',
+     r' ',
+    ])
+  svntest.actions.run_and_verify_svn(expected_output, [], 'x-shelves')
 
   # Unshelve; check the original modifications are here again
   svntest.actions.run_and_verify_svn(None, [],
-                                     'unshelve', 'foo')
-  svntest.actions.run_and_verify_status(wc_dir, modified_state)
+                                     'x-unshelve', 'foo')
+  check_wc_state(wc_dir, modified_state)
 
 #----------------------------------------------------------------------
 
-def shelve_unshelve(sbox, modifier):
+def shelve_unshelve(sbox, modifier, cannot_shelve=False):
   """Round-trip: build 'sbox'; apply changes by calling 'modifier(sbox)';
      shelve and unshelve; verify changes are fully reverted and restored.
   """
@@ -86,7 +144,7 @@ def shelve_unshelve(sbox, modifier):
   os.chdir(sbox.wc_dir)
   sbox.wc_dir = ''
 
-  shelve_unshelve_verify(sbox, modifier)
+  shelve_unshelve_verify(sbox, modifier, cannot_shelve)
 
   os.chdir(was_cwd)
 
@@ -139,7 +197,18 @@ def shelve_deletes(sbox):
 
 #----------------------------------------------------------------------
 
-@XFail()
+def shelve_replace(sbox):
+  "shelve replace"
+
+  def modifier(sbox):
+    sbox.simple_rm('A/mu')
+    sbox.simple_add_text('Replacement\n', 'A/mu')
+    sbox.simple_propset('p', 'v', 'A/mu')
+
+  shelve_unshelve(sbox, modifier)
+
+#----------------------------------------------------------------------
+
 def shelve_empty_adds(sbox):
   "shelve empty adds"
   sbox.build(empty=True)
@@ -153,7 +222,6 @@ def shelve_empty_adds(sbox):
 
 #----------------------------------------------------------------------
 
-@XFail()
 def shelve_empty_deletes(sbox):
   "shelve empty deletes"
   sbox.build(empty=True)
@@ -195,34 +263,35 @@ def save_revert_restore(sbox, modifier1,
   sbox.wc_dir = ''
   wc_dir = ''
 
+  initial_state = get_wc_state(wc_dir)
+
   # Make some changes to the working copy
   modifier1(sbox)
 
   # Remember the modified state
-  modified_state1 = state_from_status(wc_dir)
+  modified_state1 = get_wc_state(wc_dir)
 
   # Save a checkpoint; check nothing changed
   svntest.actions.run_and_verify_svn(None, [],
-                                     'shelf-save', 'foo')
-  svntest.actions.run_and_verify_status(wc_dir, modified_state1)
+                                     'x-shelf-save', 'foo')
+  check_wc_state(wc_dir, modified_state1)
 
   # Modify again; remember the state; save a checkpoint
   modifier2(sbox)
-  modified_state2 = state_from_status(wc_dir)
+  modified_state2 = get_wc_state(wc_dir)
   svntest.actions.run_and_verify_svn(None, [],
-                                     'shelf-save', 'foo')
-  svntest.actions.run_and_verify_status(wc_dir, modified_state2)
+                                     'x-shelf-save', 'foo')
+  check_wc_state(wc_dir, modified_state2)
 
   # Revert
   svntest.actions.run_and_verify_svn(None, [],
                                      'revert', '-R', '.')
-  virginal_state = svntest.actions.get_virginal_state(wc_dir, 1)
-  svntest.actions.run_and_verify_status(wc_dir, virginal_state)
+  check_wc_state(wc_dir, initial_state)
 
   # Restore; check the original modifications are here again
   svntest.actions.run_and_verify_svn(None, [],
-                                     'unshelve', 'foo', '1')
-  svntest.actions.run_and_verify_status(wc_dir, modified_state1)
+                                     'x-unshelve', 'foo', '1')
+  check_wc_state(wc_dir, modified_state1)
 
   os.chdir(was_cwd)
 
@@ -273,27 +342,27 @@ def unshelve_refuses_if_conflicts(sbox):
   sbox.simple_add_text('A\nB\nC\nD\n', 'alpha')
   sbox.simple_add_text('A\nB\nC\nD\n', 'beta')
   sbox.simple_commit()
-  initial_state = state_from_status(wc_dir)
+  initial_state = get_wc_state(wc_dir)
 
   # Make initial mods; remember this modified state
   modifier1(sbox)
-  modified_state1 = state_from_status(wc_dir)
+  modified_state1 = get_wc_state(wc_dir)
   assert modified_state1 != initial_state
 
   # Shelve; check there are no longer any local mods
   svntest.actions.run_and_verify_svn(None, [],
-                                     'shelve', 'foo')
-  svntest.actions.run_and_verify_status(wc_dir, initial_state)
+                                     'x-shelve', 'foo')
+  check_wc_state(wc_dir, initial_state)
 
   # Make a different local mod that will conflict with the shelf
   modifier2(sbox)
-  modified_state2 = state_from_status(wc_dir)
+  modified_state2 = get_wc_state(wc_dir)
 
   # Try to unshelve; check it fails with an error about a conflict
   svntest.actions.run_and_verify_svn(None, '.*[Cc]onflict.*',
-                                     'unshelve', 'foo')
+                                     'x-unshelve', 'foo')
   # Check nothing changed in the attempt
-  svntest.actions.run_and_verify_status(wc_dir, modified_state2)
+  check_wc_state(wc_dir, modified_state2)
 
 #----------------------------------------------------------------------
 
@@ -301,12 +370,18 @@ def shelve_binary_file_mod(sbox):
   "shelve binary file mod"
 
   sbox.build(empty=True)
-  sbox.simple_add_text('\0\1\2\3\4\5', 'bin')
+
+  existing_files = ['A/B/existing']
+  mod_files = ['bin', 'A/B/bin']
+
+  sbox.simple_mkdir('A', 'A/B')
+  for f in existing_files + mod_files:
+    sbox.simple_add_text('\0\1\2\3\4\5', f)
   sbox.simple_commit()
-  sbox.simple_update()
 
   def modifier(sbox):
-    sbox.simple_append('bin', '\5\4\3\2\1\0', truncate=True)
+    for f in mod_files:
+      sbox.simple_append(f, '\6\5\4\3\2\1\0', truncate=True)
 
   shelve_unshelve(sbox, modifier)
 
@@ -315,8 +390,19 @@ def shelve_binary_file_mod(sbox):
 def shelve_binary_file_add(sbox):
   "shelve binary file add"
 
+  sbox.build(empty=True)
+
+  existing_files = ['A/B/existing']
+  mod_files = ['bin', 'A/B/bin']
+
+  sbox.simple_mkdir('A', 'A/B')
+  for f in existing_files:
+    sbox.simple_add_text('\0\1\2\3\4\5', f)
+  sbox.simple_commit()
+
   def modifier(sbox):
-    sbox.simple_add_text('\0\1\2\3\4\5', 'bin')
+    for f in mod_files:
+      sbox.simple_add_text('\0\1\2\3\4\5', f)
 
   shelve_unshelve(sbox, modifier)
 
@@ -326,15 +412,542 @@ def shelve_binary_file_del(sbox):
   "shelve binary file del"
 
   sbox.build(empty=True)
-  sbox.simple_add_text('\0\1\2\3\4\5', 'bin')
+
+  existing_files = ['A/B/existing']
+  mod_files = ['bin', 'A/B/bin']
+
+  sbox.simple_mkdir('A', 'A/B')
+  for f in existing_files + mod_files:
+    sbox.simple_add_text('\0\1\2\3\4\5', f)
   sbox.simple_commit()
-  sbox.simple_update()
 
   def modifier(sbox):
-    sbox.simple_rm('bin')
+    for f in mod_files:
+      sbox.simple_rm(f)
 
   shelve_unshelve(sbox, modifier)
 
+#----------------------------------------------------------------------
+
+def shelve_binary_file_replace(sbox):
+  "shelve binary file replace"
+
+  sbox.build(empty=True)
+
+  existing_files = ['A/B/existing']
+  mod_files = ['bin', 'A/B/bin']
+
+  sbox.simple_mkdir('A', 'A/B')
+  for f in existing_files + mod_files:
+    sbox.simple_add_text('\0\1\2\3\4\5', f)
+  sbox.simple_commit()
+
+  def modifier(sbox):
+    for f in mod_files:
+      sbox.simple_rm(f)
+      sbox.simple_add_text('\6\5\4\3\2\1\0', f)
+
+  shelve_unshelve(sbox, modifier)
+
+#----------------------------------------------------------------------
+
+def shelve_with_log_message(sbox):
+  "shelve with log message"
+
+  sbox.build(empty=True)
+  was_cwd = os.getcwd()
+  os.chdir(sbox.wc_dir)
+  sbox.wc_dir = ''
+
+  sbox.simple_add_text('New file', 'f')
+  log_message = 'Log message for foo'
+  svntest.actions.run_and_verify_svn(None, [],
+                                     'x-shelve', 'foo', '-m', log_message)
+  expected_output = svntest.verify.RegexListOutput(
+    ['foo .*',
+     ' ' + log_message
+    ])
+  svntest.actions.run_and_verify_svn(expected_output, [],
+                                     'x-shelf-list')
+
+  os.chdir(was_cwd)
+
+#----------------------------------------------------------------------
+
+def run_and_verify_status(wc_dir_name, status_tree, changelists=[]):
+  """Run 'status' on WC_DIR_NAME and compare it with the
+  expected STATUS_TREE.
+  Returns on success, raises on failure."""
+
+  if not isinstance(status_tree, wc.State):
+    raise TypeError('wc.State tree expected')
+
+  cl_opts = ('--cl=' + cl for cl in changelists)
+  exit_code, output, errput = svntest.main.run_svn(None, 'status', '-q',
+                                                   wc_dir_name, *cl_opts)
+
+  actual_status = svntest.wc.State.from_status(output, wc_dir=wc_dir_name)
+
+  # Verify actual output against expected output.
+  try:
+    status_tree.compare_and_display('status', actual_status)
+  except svntest.tree.SVNTreeError:
+    svntest.actions._log_tree_state("ACTUAL STATUS TREE:", actual_status.old_tree(),
+                                    wc_dir_name)
+    raise
+
+def run_and_verify_shelf_status(wc_dir, expected_status, shelf):
+  run_and_verify_status(wc_dir, expected_status,
+                        changelists=['svn:shelf:' + shelf])
+
+def shelf_status(sbox):
+  "shelf status"
+
+  sbox.build()
+  was_cwd = os.getcwd()
+  os.chdir(sbox.wc_dir)
+  sbox.wc_dir = ''
+
+  sbox.simple_add_text('New file', 'f')
+  sbox.simple_append('iota', 'New text')
+  sbox.simple_propset('p', 'v', 'A/mu')
+  sbox.simple_rm('A/B/lambda')
+  # Not yet supported:
+  #sbox.simple_rm('A/B/E')
+  expected_status = state_from_status(sbox.wc_dir, v=False, u=False, q=False)
+  run_and_verify_status(sbox.wc_dir, expected_status)
+
+  svntest.actions.run_and_verify_svn(None, [],
+                                     'x-shelve', 'foo')
+  run_and_verify_shelf_status(sbox.wc_dir, expected_status, shelf='foo')
+
+  os.chdir(was_cwd)
+
+#----------------------------------------------------------------------
+
+def shelve_mkdir(sbox):
+  "shelve mkdir"
+
+  sbox.build()
+
+  def modifier(sbox):
+    sbox.simple_mkdir('D', 'D/D2')
+    sbox.simple_propset('p', 'v', 'D', 'D/D2')
+
+  shelve_unshelve(sbox, modifier, cannot_shelve=True)
+
+#----------------------------------------------------------------------
+
+def shelve_rmdir(sbox):
+  "shelve rmdir"
+
+  sbox.build()
+  sbox.simple_propset('p', 'v', 'A/C')
+  sbox.simple_commit()
+
+  def modifier(sbox):
+    sbox.simple_rm('A/C', 'A/D/G')
+
+  shelve_unshelve(sbox, modifier, cannot_shelve=True)
+
+#----------------------------------------------------------------------
+
+def shelve_replace_dir(sbox):
+  "shelve replace dir"
+
+  sbox.build()
+  sbox.simple_propset('p', 'v', 'A/C')
+  sbox.simple_commit()
+
+  def modifier(sbox):
+    sbox.simple_rm('A/C', 'A/D/G')
+    sbox.simple_mkdir('A/C', 'A/C/D2')
+
+  shelve_unshelve(sbox, modifier, cannot_shelve=True)
+
+#----------------------------------------------------------------------
+
+def shelve_file_copy(sbox):
+  "shelve file copy"
+
+  sbox.build()
+
+  def modifier(sbox):
+    sbox.simple_copy('iota', 'A/ii')
+    sbox.simple_propset('p', 'v', 'A/ii')
+
+  shelve_unshelve(sbox, modifier, cannot_shelve=True)
+
+#----------------------------------------------------------------------
+
+def shelve_dir_copy(sbox):
+  "shelve dir copy"
+
+  sbox.build()
+
+  def modifier(sbox):
+    sbox.simple_copy('A/B', 'BB')
+    sbox.simple_propset('p', 'v', 'BB')
+
+  shelve_unshelve(sbox, modifier, cannot_shelve=True)
+
+#----------------------------------------------------------------------
+
+def list_shelves(sbox):
+  "list_shelves"
+
+  sbox.build()
+  was_cwd = os.getcwd()
+  os.chdir(sbox.wc_dir)
+  sbox.wc_dir = ''
+
+  # an empty list
+  svntest.actions.run_and_verify_svn([], [],
+                                     'x-shelf-list', '-q')
+
+  # make two shelves
+  sbox.simple_append('A/mu', 'appended mu text')
+  svntest.actions.run_and_verify_svn(None, [],
+                                     'x-shelf-save', 'foo')
+  sbox.simple_append('A/mu', 'appended more text')
+  svntest.actions.run_and_verify_svn(None, [],
+                                     'x-shelf-save', 'foo', '-m', 'log msg')
+  svntest.actions.run_and_verify_svn(None, [],
+                                     'x-shelf-save', 'bar', '-m', 'log msg')
+
+  # We don't check for time-ordering of the shelves. If we want to do so, we
+  # would need to sleep for timestamps to differ, between creating them.
+
+  # a quiet list
+  expected_out = svntest.verify.UnorderedRegexListOutput(['foo', 'bar'])
+  svntest.actions.run_and_verify_svn(expected_out, [],
+                                     'x-shelf-list', '-q')
+
+  # a detailed list
+  expected_out = svntest.verify.UnorderedRegexListOutput(['foo .* 1 path.*',
+                                                          ' log msg',
+                                                          'bar .* 1 path.*',
+                                                          ' log msg'])
+  svntest.actions.run_and_verify_svn(expected_out, [],
+                                     'x-shelf-list')
+
+  os.chdir(was_cwd)
+
+#----------------------------------------------------------------------
+
+def refuse_to_shelve_conflict(sbox):
+  "refuse to shelve conflict"
+
+  sbox.build(empty=True)
+  was_cwd = os.getcwd()
+  os.chdir(sbox.wc_dir)
+  sbox.wc_dir = ''
+
+  # create a tree conflict victim at an unversioned path
+  sbox.simple_mkdir('topdir')
+  sbox.simple_commit()
+  sbox.simple_mkdir('topdir/subdir')
+  sbox.simple_commit()
+  sbox.simple_update()
+  sbox.simple_rm('topdir')
+  sbox.simple_commit()
+  sbox.simple_update()
+  svntest.actions.run_and_verify_svn(
+    None, [],
+    'merge', '-c2', '.', '--ignore-ancestry', '--accept', 'postpone')
+  svntest.actions.run_and_verify_svn(
+    None, 'svn: E155015:.*existing.*conflict.*',
+    'merge', '-c1', '.', '--ignore-ancestry', '--accept', 'postpone')
+
+  # attempt to shelve
+  expected_out = svntest.verify.RegexListOutput([
+    r'--- .*',
+    r'--- .*',
+    r'\?     C topdir',
+    r'      > .*',
+    r'      >   not shelved'])
+  svntest.actions.run_and_verify_svn(expected_out,
+                                     '.* 1 path could not be shelved',
+                                     'x-shelf-save', 'foo')
+
+  os.chdir(was_cwd)
+
+#----------------------------------------------------------------------
+
+def unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state):
+  """Run a test scenario in which 'unshelve' needs to merge some shelved
+     changes made by modifier1() with some committed changes made by
+     modifier2(). tweak_expected_state() must produce the expected WC state.
+  """
+  sbox.build()
+  was_cwd = os.getcwd()
+  os.chdir(sbox.wc_dir)
+  sbox.wc_dir = ''
+  wc_dir = sbox.wc_dir
+
+  setup(sbox)
+  sbox.simple_commit()
+  initial_state = get_wc_state(wc_dir)
+
+  # Make some changes to the working copy
+  modifier1(sbox)
+  modified_state = get_wc_state(wc_dir)
+
+  # Shelve; check there are no longer any modifications
+  svntest.actions.run_and_verify_svn(None, [],
+                                     'x-shelve', 'foo')
+  check_wc_state(wc_dir, initial_state)
+
+  # Make a different change, with which we shall merge
+  modifier2(sbox)
+  sbox.simple_commit()
+  modified_state[0].tweak('A/mu', wc_rev='3')
+
+  # Unshelve; check the expected result of the merge
+  svntest.actions.run_and_verify_svn(None, [],
+                                     'x-unshelve', 'foo')
+  tweak_expected_state(modified_state)
+  check_wc_state(wc_dir, modified_state)
+
+  os.chdir(was_cwd)
+
+def unshelve_text_mod_merge(sbox):
+  "unshelve text mod merge"
+
+  orig_contents='A\nB\nC\nD\nE\n'
+  mod1_contents='A\nBB\nC\nD\nE\n'
+  mod2_contents='A\nB\nC\nDD\nE\n'
+  merged_contents='A\nBB\nC\nDD\nE\n'
+
+  def setup(sbox):
+    sbox.simple_append('A/mu', orig_contents, truncate=True)
+
+  def modifier1(sbox):
+    sbox.simple_append('A/mu', mod1_contents, truncate=True)
+
+  def modifier2(sbox):
+    sbox.simple_append('A/mu', mod2_contents, truncate=True)
+
+  def tweak_expected_state(modified_state):
+    modified_state[1].tweak('A/mu', contents=merged_contents)
+
+  unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state)
+
+#----------------------------------------------------------------------
+
+def unshelve_text_mod_conflict(sbox):
+  "unshelve text mod conflict"
+
+  orig_contents='A\nB\nC\nD\nE\n'
+  mod1_contents='A\nBB\nC\nD\nE\n'
+  mod2_contents='A\nBCD\nC\nD\nE\n'
+  merged_contents = 'A\n<<<<<<< .working\nBCD\n||||||| .merge-left\nB\n=======\nBB\n>>>>>>> .merge-right\nC\nD\nE\n'
+
+  def setup(sbox):
+    sbox.simple_append('A/mu', orig_contents, truncate=True)
+
+  def modifier1(sbox):
+    sbox.simple_append('A/mu', mod1_contents, truncate=True)
+
+  def modifier2(sbox):
+    sbox.simple_append('A/mu', mod2_contents, truncate=True)
+
+  def tweak_expected_state(modified_state):
+    modified_state[0].tweak('A/mu', status='C ')
+    modified_state[1].tweak('A/mu', contents=merged_contents)
+    modified_state[1].add({
+      'A/mu.merge-left':  Item(contents=orig_contents),
+      'A/mu.merge-right': Item(contents=mod1_contents),
+      'A/mu.working':     Item(contents=mod2_contents),
+      })
+
+  unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state)
+
+#----------------------------------------------------------------------
+
+def unshelve_undeclared_binary_mod_conflict(sbox):
+  "unshelve undeclared binary mod conflict"
+
+  orig_contents='\1\2\3\4\5'
+  mod1_contents='\1\2\2\3\4\5'
+  mod2_contents='\1\2\3\4\3\4\5'
+  merged_contents = '<<<<<<< .working\n' + mod2_contents + '||||||| .merge-left\n' + orig_contents + '=======\n' + mod1_contents + '>>>>>>> .merge-right\n'
+
+  def setup(sbox):
+    sbox.simple_append('A/mu', orig_contents, truncate=True)
+
+  def modifier1(sbox):
+    sbox.simple_append('A/mu', mod1_contents, truncate=True)
+
+  def modifier2(sbox):
+    sbox.simple_append('A/mu', mod2_contents, truncate=True)
+
+  def tweak_expected_state(modified_state):
+    modified_state[0].tweak('A/mu', status='C ')
+    modified_state[1].tweak('A/mu', contents=merged_contents)
+    modified_state[1].add({
+      'A/mu.merge-left':  Item(contents=orig_contents),
+      'A/mu.merge-right': Item(contents=mod1_contents),
+      'A/mu.working':     Item(contents=mod2_contents),
+      })
+
+  unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state)
+
+#----------------------------------------------------------------------
+
+def unshelve_binary_mod_conflict(sbox):
+  "unshelve binary mod conflict"
+
+  orig_contents='\1\2\3\4\5'
+  mod1_contents='\1\2\2\3\4\5'
+  mod2_contents='\1\2\3\4\3\4\5'
+
+  def setup(sbox):
+    sbox.simple_append('A/mu', orig_contents, truncate=True)
+    sbox.simple_propset('svn:mime-type', 'application/octet-stream', 'A/mu')
+
+  def modifier1(sbox):
+    sbox.simple_append('A/mu', mod1_contents, truncate=True)
+
+  def modifier2(sbox):
+    sbox.simple_append('A/mu', mod2_contents, truncate=True)
+
+  def tweak_expected_state(modified_state):
+    modified_state[0].tweak('A/mu', status='C ')
+    modified_state[1].tweak('A/mu', contents=mod2_contents)
+    modified_state[1].add({
+      'A/mu.merge-left':  Item(contents=orig_contents),
+      'A/mu.merge-right': Item(contents=mod1_contents),
+      })
+
+  unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state)
+
+#----------------------------------------------------------------------
+
+def unshelve_text_prop_merge(sbox):
+  "unshelve text prop merge"
+
+  def setup(sbox):
+    sbox.simple_propset('p1', 'v', 'A/mu')
+    sbox.simple_propset('p2', 'v', 'A/mu')
+
+  def modifier1(sbox):
+    sbox.simple_propset('p1', 'changed', 'A/mu')
+
+  def modifier2(sbox):
+    sbox.simple_propset('p2', 'changed', 'A/mu')
+
+  def tweak_expected_state(wc_state):
+    wc_state[1].tweak('A/mu', props={'p1':'changed',
+                                     'p2':'changed'})
+
+  unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state)
+
+#----------------------------------------------------------------------
+
+def unshelve_text_prop_conflict(sbox):
+  "unshelve text prop conflict"
+
+  orig_contents='A'
+  mod1_contents='B'
+  mod2_contents='C'
+  merged_contents='C'
+  prej_contents='''Trying to change property 'p'
+but the local property value conflicts with the incoming change.
+<<<<<<< (local property value)
+C||||||| (incoming 'changed from' value)
+A=======
+B>>>>>>> (incoming 'changed to' value)
+'''
+
+  def setup(sbox):
+    sbox.simple_propset('p', orig_contents, 'A/mu')
+
+  def modifier1(sbox):
+    sbox.simple_propset('p', mod1_contents, 'A/mu')
+
+  def modifier2(sbox):
+    sbox.simple_propset('p', mod2_contents, 'A/mu')
+
+  def tweak_expected_state(wc_state):
+    wc_state[0].tweak('A/mu', status=' C')
+    wc_state[1].tweak('A/mu', props={'p':merged_contents})
+    wc_state[1].add({
+      'A/mu.prej':     Item(contents=prej_contents),
+      })
+
+  unshelve_with_merge(sbox, setup, modifier1, modifier2, tweak_expected_state)
+
+#----------------------------------------------------------------------
+
+def run_and_verify_shelf_diff_summarize(output_tree, shelf, *args):
+  """Run 'svn shelf-diff --summarize' with the arguments *ARGS.
+
+  The subcommand output will be verified against OUTPUT_TREE.  Returns
+  on success, raises on failure.
+  """
+
+  if isinstance(output_tree, wc.State):
+    output_tree = output_tree.old_tree()
+
+  exit_code, output, errput = svntest.actions.run_and_verify_svn(
+                                None, [],
+                                'x-shelf-diff', '--summarize', shelf, *args)
+
+  actual = svntest.tree.build_tree_from_diff_summarize(output)
+
+  # Verify actual output against expected output.
+  try:
+    svntest.tree.compare_trees("output", actual, output_tree)
+  except svntest.tree.SVNTreeError:
+    svntest.verify.display_trees(None, 'DIFF OUTPUT TREE', output_tree, actual)
+    raise
+
+# Exercise a very basic case of shelf-diff.
+def shelf_diff_simple(sbox):
+  "shelf diff simple"
+
+  sbox.build()
+  was_cwd = os.getcwd()
+  os.chdir(sbox.wc_dir)
+  sbox.wc_dir = ''
+  wc_dir = sbox.wc_dir
+
+  def setup(sbox):
+    sbox.simple_propset('p1', 'v', 'A/mu')
+    sbox.simple_propset('p2', 'v', 'A/mu')
+
+  def modifier1(sbox):
+    sbox.simple_append('A/mu', 'New line.\n')
+    sbox.simple_propset('p1', 'changed', 'A/mu')
+
+  setup(sbox)
+  sbox.simple_commit()
+  initial_state = get_wc_state(wc_dir)
+
+  # Make some changes to the working copy
+  modifier1(sbox)
+  modified_state = get_wc_state(wc_dir)
+
+  svntest.actions.run_and_verify_svn(None, [],
+                                     'x-shelf-save', 'foo')
+
+  # basic svn-style diff
+  expected_output = make_diff_header('A/mu', 'revision 2', 'working copy') + [
+                      "@@ -1 +1,2 @@\n",
+                      " This is the file 'mu'.\n",
+                      "+New line.\n",
+                    ] + make_diff_prop_header('A/mu') \
+                    + make_diff_prop_modified('p1', 'v', 'changed')
+  svntest.actions.run_and_verify_svn(expected_output, [],
+                                     'x-shelf-diff', 'foo')
+
+  # basic summary diff
+  expected_diff = svntest.wc.State(wc_dir, {
+    'A/mu':           Item(status='MM'),
+  })
+  run_and_verify_shelf_diff_summarize(expected_diff, 'foo')
+
 
 ########################################################################
 # Run the tests
@@ -345,6 +958,7 @@ test_list = [ None,
               shelve_prop_changes,
               shelve_adds,
               shelve_deletes,
+              shelve_replace,
               shelve_empty_adds,
               shelve_empty_deletes,
               shelve_from_inner_path,
@@ -354,6 +968,23 @@ test_list = [ None,
               shelve_binary_file_mod,
               shelve_binary_file_add,
               shelve_binary_file_del,
+              shelve_binary_file_replace,
+              shelve_with_log_message,
+              shelf_status,
+              shelve_mkdir,
+              shelve_rmdir,
+              shelve_replace_dir,
+              shelve_file_copy,
+              shelve_dir_copy,
+              list_shelves,
+              refuse_to_shelve_conflict,
+              unshelve_text_mod_merge,
+              unshelve_text_mod_conflict,
+              unshelve_undeclared_binary_mod_conflict,
+              unshelve_binary_mod_conflict,
+              unshelve_text_prop_merge,
+              unshelve_text_prop_conflict,
+              shelf_diff_simple,
              ]
 
 if __name__ == '__main__':

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/special_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/special_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/special_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/special_tests.py Sun Sep 30 18:26:47 2018
@@ -535,7 +535,7 @@ def diff_symlink_to_dir(sbox):
     "+++ link\t(working copy)\n",
     "@@ -0,0 +1 @@\n",
     "+link A/D\n",
-    "\ No newline at end of file\n",
+    "\\ No newline at end of file\n",
     "\n",
     "Property changes on: link\n",
     "___________________________________________________________________\n",
@@ -730,7 +730,8 @@ def unrelated_changed_special_status(sbo
 
   os.chdir(os.path.join(sbox.wc_dir, 'A/D/H'))
 
-  open('chi', 'a').write('random local mod')
+  with open('chi', 'a') as f:
+    f.write('random local mod')
   os.unlink('psi')
   os.symlink('omega', 'psi') # omega is versioned!
   svntest.main.run_svn(None, 'changelist', 'chi cl', 'chi')

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/stat_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/stat_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/stat_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/stat_tests.py Sun Sep 30 18:26:47 2018
@@ -1949,7 +1949,8 @@ def modified_modulo_translation(sbox):
   sbox.simple_commit()
 
   # CRLF it.
-  open(sbox.ospath('iota'), 'wb').write("This is the file 'iota'.\r\n")
+  with open(sbox.ospath('iota'), 'wb') as f:
+    f.write("This is the file 'iota'.\r\n")
 
   # Run status.  Expect some output.
   # TODO: decide how such files should show in the output; whether they

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svnadmin_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svnadmin_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svnadmin_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svnadmin_tests.py Sun Sep 30 18:26:47 2018
@@ -53,6 +53,24 @@ Wimp = svntest.testcase.Wimp_deco
 SkipDumpLoadCrossCheck = svntest.testcase.SkipDumpLoadCrossCheck_deco
 Item = svntest.wc.StateItem
 
+def read_rep_cache(repo_dir):
+  """Return the rep-cache contents as a dict {hash: (rev, index, ...)}.
+  """
+  db_path = os.path.join(repo_dir, 'db', 'rep-cache.db')
+  db1 = svntest.sqlite3.connect(db_path)
+  schema1 = db1.execute("pragma user_version").fetchone()[0]
+  # Can't test newer rep-cache schemas with an old built-in SQLite; see the
+  # documentation of STMT_CREATE_SCHEMA_V2 in ../../libsvn_fs_fs/rep-cache-db.sql
+  if schema1 >= 2 and svntest.sqlite3.sqlite_version_info < (3, 8, 2):
+    raise svntest.Failure("Can't read rep-cache schema %d using old "
+                          "Python-SQLite version %s < (3,8,2)" %
+                           (schema1,
+                            svntest.sqlite3.sqlite_version_info))
+
+  content = { row[0]: row[1:] for row in
+              db1.execute("select * from rep_cache") }
+  return content
+
 def check_hotcopy_bdb(src, dst):
   "Verify that the SRC BDB repository has been correctly copied to DST."
   ### TODO: This function should be extended to verify all hotcopied files,
@@ -256,7 +274,8 @@ def patch_format(repo_dir, shard_size):
 
   new_contents = b"\n".join(processed_lines)
   os.chmod(format_path, svntest.main.S_ALL_RW)
-  open(format_path, 'wb').write(new_contents)
+  with open(format_path, 'wb') as f:
+    f.write(new_contents)
 
 def is_sharded(repo_dir):
   """Return whether the FSFS repository REPO_DIR is sharded."""
@@ -1633,9 +1652,9 @@ text
   sbox.build(empty=True)
 
   # Try to load the dumpstream, expecting a failure (because of mixed EOLs).
-  exp_err = svntest.verify.RegexListOutput(['svnadmin: E125005',
-                                            'svnadmin: E125005',
-                                            'svnadmin: E125017'],
+  exp_err = svntest.verify.RegexListOutput(['svnadmin: E125005:.*',
+                                            'svnadmin: E125005:.*',
+                                            'svnadmin: E125017:.*'],
                                            match_all=False)
   load_and_verify_dumpstream(sbox, [], exp_err, dumpfile_revisions,
                              False, dump_str, '--ignore-uuid')
@@ -1764,10 +1783,10 @@ def test_lslocks_and_rmlocks(sbox):
   def expected_output_list(path):
     return [
       "Path: " + path,
-      "UUID Token: opaquelocktoken",
+      "UUID Token: opaquelocktoken:.*",
       "Owner: jrandom",
-      "Created:",
-      "Expires:",
+      "Created:.*",
+      "Expires:.*",
       "Comment \(1 line\):",
       "Locking files",
       "\n", # empty line
@@ -2169,7 +2188,7 @@ def verify_keep_going(sbox):
                                                         sbox.repo_dir)
 
   if (svntest.main.is_fs_log_addressing()):
-    exp_out = svntest.verify.RegexListOutput([".*Verifying metadata at revision 0"])
+    exp_out = svntest.verify.RegexListOutput([".*Verifying metadata at revision 0.*"])
   else:
     exp_out = svntest.verify.RegexListOutput([".*Verified revision 0.",
                                               ".*Verified revision 1."])
@@ -3459,7 +3478,8 @@ def load_from_file(sbox):
   sbox.build(empty=True)
 
   file = sbox.get_tempname()
-  open(file, 'wb').writelines(clean_dumpfile())
+  with open(file, 'wb') as f:
+    f.writelines(clean_dumpfile())
   svntest.actions.run_and_verify_svnadmin2(None, [],
                                            0, 'load', '--file', file,
                                            '--ignore-uuid', sbox.repo_dir)
@@ -3763,7 +3783,7 @@ def dump_exclude_all_rev_changes(sbox):
   # Check log. Revision properties ('svn:log' etc.) should be empty for r2.
   expected_output = svntest.verify.RegexListOutput([
     '-+\\n',
-    'r3\ |\ jrandom\ |\ .*\ |\ 1\ line\\n',
+    'r3 | jrandom | .* | 1 line\\n',
     re.escape('Changed paths:'),
     re.escape('   A /r3a'),
     re.escape('   A /r3b'),
@@ -3775,7 +3795,7 @@ def dump_exclude_all_rev_changes(sbox):
     '',
     '',
     '-+\\n',
-    'r1\ |\ jrandom\ |\ .*\ |\ 1\ line\\n',
+    'r1 | jrandom | .* | 1 line\\n',
     re.escape('Changed paths:'),
     re.escape('   A /r1a'),
     re.escape('   A /r1b'),
@@ -3824,6 +3844,82 @@ def load_issue4725(sbox):
   sbox2.build(create_wc=False, empty=True)
   load_and_verify_dumpstream(sbox2, None, [], None, False, dump, '-M100')
 
+@Issue(4767)
+def dump_no_canonicalize_svndate(sbox):
+  "svnadmin dump shouldn't canonicalize svn:date"
+
+  sbox.build(create_wc=False, empty=True)
+  svntest.actions.enable_revprop_changes(sbox.repo_dir)
+
+  # set svn:date in a non-canonical format (not six decimal places)
+  propval = "2015-01-01T00:00:00.0Z"
+  svntest.actions.run_and_verify_svn(svntest.verify.AnyOutput, [],
+                                     "propset", "--revprop", "-r0", "svn:date",
+                                     propval,
+                                     sbox.repo_url)
+
+  dump_lines = svntest.actions.run_and_verify_dump(sbox.repo_dir)
+  assert propval + '\n' in dump_lines
+
+def check_recover_prunes_rep_cache(sbox, enable_rep_sharing):
+  """Check 'recover' prunes the rep-cache while enable-rep-sharing is
+     true/false.
+  """
+  # Remember the initial rep cache content.
+  rep_cache_r1 = read_rep_cache(sbox.repo_dir)
+  #print '\n'.join([h + ": " + repr(ref) for h, ref in rep_cache_r1.items()])
+
+  # Commit one new rep and check the rep-cache is extended.
+  sbox.simple_append('iota', 'New line.\n')
+  sbox.simple_commit()
+  rep_cache_r2 = read_rep_cache(sbox.repo_dir)
+  if not (len(rep_cache_r2) == len(rep_cache_r1) + 1):
+    raise svntest.Failure
+
+  fsfs_conf = svntest.main.get_fsfs_conf_file_path(sbox.repo_dir)
+  svntest.main.file_append(fsfs_conf,
+                           # Add a newline in case the existing file doesn't
+                           # end with one.
+                           "\n"
+                           "[rep-sharing]\n"
+                           "enable-rep-sharing = %s\n"
+                           % (('true' if enable_rep_sharing else 'false'),))
+
+  # Break r2 in such a way that 'recover' will discard it
+  head_rev_path = fsfs_file(sbox.repo_dir, 'revs', '2')
+  os.remove(head_rev_path)
+  current_path = os.path.join(sbox.repo_dir, 'db', 'current')
+  svntest.main.file_write(current_path, '1\n')
+
+  # Recover back to r1.
+  svntest.actions.run_and_verify_svnadmin(None, [],
+                                          "recover", sbox.repo_dir)
+  svntest.actions.run_and_verify_svnlook(['1\n'], [], 'youngest',
+                                         sbox.repo_dir)
+
+  # Check the rep-cache is pruned.
+  rep_cache_recovered = read_rep_cache(sbox.repo_dir)
+  if not (rep_cache_recovered == rep_cache_r1):
+    raise svntest.Failure
+
+@Issue(4077)
+@SkipUnless(svntest.main.is_fs_type_fsfs)
+@SkipUnless(svntest.main.python_sqlite_can_read_without_rowid)
+def recover_prunes_rep_cache_when_enabled(sbox):
+  "recover prunes rep cache when enabled"
+  sbox.build()
+
+  check_recover_prunes_rep_cache(sbox, enable_rep_sharing=True)
+
+@Issue(4077)
+@SkipUnless(svntest.main.is_fs_type_fsfs)
+@SkipUnless(svntest.main.python_sqlite_can_read_without_rowid)
+def recover_prunes_rep_cache_when_disabled(sbox):
+  "recover prunes rep cache when disabled"
+  sbox.build()
+
+  check_recover_prunes_rep_cache(sbox, enable_rep_sharing=False)
+
 ########################################################################
 # Run the tests
 
@@ -3898,6 +3994,9 @@ test_list = [ None,
               dump_exclude_all_rev_changes,
               dump_invalid_filtering_option,
               load_issue4725,
+              dump_no_canonicalize_svndate,
+              recover_prunes_rep_cache_when_enabled,
+              recover_prunes_rep_cache_when_disabled,
              ]
 
 if __name__ == '__main__':

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svnfsfs_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svnfsfs_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svnfsfs_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svnfsfs_tests.py Sun Sep 30 18:26:47 2018
@@ -94,7 +94,8 @@ def patch_format(repo_dir, shard_size):
 
   new_contents = b"\n".join(processed_lines)
   os.chmod(format_path, svntest.main.S_ALL_RW)
-  open(format_path, 'wb').write(new_contents)
+  with open(format_path, 'wb') as f:
+    f.write(new_contents)
 
 ######################################################################
 # Tests

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svnmover_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svnmover_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svnmover_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svnmover_tests.py Sun Sep 30 18:26:47 2018
@@ -469,7 +469,7 @@ rm A/B/C/Y
     '   D /top0/A/B/C/Y',
   ]))
   expected_output = svntest.verify.UnorderedRegexListOutput(escaped
-                          + ['^-', '^r2', '^-', '^Changed paths:',])
+                          + ['^--*', '^r2.*', '^--*', '^Changed paths:',])
   svntest.actions.run_and_verify_svn(expected_output, [],
                                      'log', '-qvr2', repo_url)
 
@@ -755,7 +755,7 @@ def simple_moves_within_a_branch(sbox):
                 'mv lib/foo/y2 y2')
   # move and rename, dir with children
   test_svnmover2(sbox, '/trunk',
-                 reported_br_diff('') +
+                 reported_br_diff('trunk') +
                  reported_add('subdir') +
                  reported_move('lib', 'subdir/lib2'),
                 'mkdir subdir',
@@ -765,7 +765,7 @@ def simple_moves_within_a_branch(sbox):
   # moves and renames together
   # (put it all back to how it was, in one commit)
   test_svnmover2(sbox, '/trunk',
-                 reported_br_diff('') +
+                 reported_br_diff('trunk') +
                  reported_move('subdir/lib2/README.txt', 'README') +
                  reported_move('subdir/lib2', 'lib') +
                  reported_move('y2', 'lib/foo/y') +

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svnmucc_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svnmucc_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svnmucc_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svnmucc_tests.py Sun Sep 30 18:26:47 2018
@@ -458,7 +458,7 @@ rm A/B/C/Y
     '   D /A/B/C/Y',
   ]))
   expected_output = svntest.verify.UnorderedRegexListOutput(excaped
-                    + ['^-', '^r3', '^-', '^Changed paths:',])
+                    + ['^--*', '^r3.*', '^--*', '^Changed paths:',])
   svntest.actions.run_and_verify_svn(expected_output, [],
                                      'log', '-qvr3', repo_url)
 

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svnrdump_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svnrdump_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svnrdump_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svnrdump_tests.py Sun Sep 30 18:26:47 2018
@@ -80,7 +80,7 @@ def compare_repos_dumps(sbox, other_dump
 
   ### This call kind-of assumes EXPECTED is first and ACTUAL is second.
   svntest.verify.compare_dump_files(
-    "Dump files", "DUMP", other_dumpfile, sbox_dumpfile)
+    None, None, other_dumpfile, sbox_dumpfile)
 
 def run_dump_test(sbox, dumpfile_name, expected_dumpfile_name = None,
                   subdir = None, bypass_prop_validation = False,

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svnsync_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svnsync_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svnsync_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svnsync_tests.py Sun Sep 30 18:26:47 2018
@@ -167,7 +167,7 @@ def verify_mirror(dest_sbox, exp_dump_fi
   dest_dump = svntest.actions.run_and_verify_dump(dest_sbox.repo_dir)
 
   svntest.verify.compare_dump_files(
-    "Dump files", "DUMP", exp_dump_file_contents, dest_dump)
+    None, None, exp_dump_file_contents, dest_dump)
 
 def run_test(sbox, dump_file_name, subdir=None, exp_dump_file_name=None,
              bypass_prop_validation=False, source_prop_encoding=None,

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svntest/main.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svntest/main.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svntest/main.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svntest/main.py Sun Sep 30 18:26:47 2018
@@ -56,7 +56,7 @@ import svntest
 from svntest import Failure
 from svntest import Skip
 
-SVN_VER_MINOR = 11
+SVN_VER_MINOR = 12
 
 ######################################################################
 #
@@ -456,9 +456,9 @@ def open_pipe(command, bufsize=-1, stdin
   should be passed to wait_on_pipe."""
   command = [str(x) for x in command]
 
-  # On Windows subprocess.Popen() won't accept a Python script as
-  # a valid program to execute, rather it wants the Python executable.
-  if (sys.platform == 'win32') and (command[0].endswith('.py')):
+  # Always run python scripts under the same Python executable as used
+  # for the test suite.
+  if command[0].endswith('.py'):
     command.insert(0, sys.executable)
 
   command_string = command[0] + ' ' + ' '.join(map(_quote_arg, command[1:]))
@@ -982,7 +982,8 @@ def file_write(path, contents, mode='w')
   which is (w)rite by default."""
 
   if sys.version_info < (3, 0):
-    open(path, mode).write(contents)
+    with open(path, mode) as f:
+      f.write(contents)
   else:
     # Python 3:  Write data in the format required by MODE, i.e. byte arrays
     #            to 'b' files, utf-8 otherwise."""
@@ -994,9 +995,11 @@ def file_write(path, contents, mode='w')
         contents = contents.decode("utf-8")
 
     if isinstance(contents, str):
-      codecs.open(path, mode, "utf-8").write(contents)
+      with codecs.open(path, mode, "utf-8") as f:
+        f.write(contents)
     else:
-      open(path, mode).write(contents)
+      with open(path, mode) as f:
+        f.write(contents)
 
 # For making local mods to files
 def file_append(path, new_text):
@@ -1012,7 +1015,8 @@ def file_append_binary(path, new_text):
 def file_substitute(path, contents, new_contents):
   """Replace the CONTENTS in the file at PATH using the NEW_CONTENTS"""
   fcontent = open(path, 'r').read().replace(contents, new_contents)
-  open(path, 'w').write(fcontent)
+  with open(path, 'w') as f:
+    f.write(fcontent)
 
 # For setting up authz, hooks and making other tweaks to created repos
 def _post_create_repos(path, minor_version = None):
@@ -1638,6 +1642,15 @@ def server_has_atomic_revprop():
 def server_has_reverse_get_file_revs():
   return options.server_caps.has_reverse_get_file_revs
 
+def python_sqlite_can_read_our_wc_db():
+  """Check if the Python builtin is capable enough to peek into wc.db"""
+  # Currently enough (1.7-1.9)
+  return svntest.sqlite3.sqlite_version_info >= (3, 6, 18)
+
+def python_sqlite_can_read_without_rowid():
+  """Check if the Python builtin is capable enough to read new rep-cache"""
+  return svntest.sqlite3.sqlite_version_info >= (3, 8, 2)
+
 def is_plaintext_password_storage_disabled():
   try:
     predicate = re.compile("^WARNING: Plaintext password storage is enabled!")

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svntest/sandbox.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svntest/sandbox.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svntest/sandbox.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svntest/sandbox.py Sun Sep 30 18:26:47 2018
@@ -168,7 +168,8 @@ class Sandbox:
                or open(self.authz_file,'r').read() != default_authz)):
 
         tmp_authz_file = os.path.join(svntest.main.work_dir, "authz-" + self.name)
-        open(tmp_authz_file, 'w').write(default_authz)
+        with open(tmp_authz_file, 'w') as f:
+          f.write(default_authz)
         shutil.move(tmp_authz_file, self.authz_file)
 
   def authz_name(self, repo_dir=None):
@@ -492,7 +493,8 @@ class Sandbox:
                        if not svnrdump_headers_always.match(l)]
     # Ignore differences in number of blank lines between node records,
     # as svnrdump puts 3 whereas svnadmin puts 2 after a replace-with-copy.
-    svntest.verify.compare_dump_files(None, None,
+    svntest.verify.compare_dump_files('svnadmin dump, tweaked',
+                                      'svnrdump dump, tweaked',
                                       dumpfile_a_d_cmp,
                                       dumpfile_r_d_cmp,
                                       ignore_number_of_blank_lines=True)
@@ -523,20 +525,22 @@ class Sandbox:
     reloaded_dumpfile_a_n = svntest.actions.run_and_verify_dump(repo_dir_a_n)
     reloaded_dumpfile_a_d = svntest.actions.run_and_verify_dump(repo_dir_a_d)
     reloaded_dumpfile_r_d = svntest.actions.run_and_verify_dump(repo_dir_r_d)
-    svntest.verify.compare_dump_files(None, None,
+    svntest.verify.compare_dump_files('svnadmin dump no delta, loaded, dumped',
+                                      'svnadmin dump --deltas, loaded, dumped',
                                       reloaded_dumpfile_a_n,
                                       reloaded_dumpfile_a_d,
                                       ignore_uuid=True)
-    svntest.verify.compare_dump_files(None, None,
+    svntest.verify.compare_dump_files('svnadmin dump, loaded, dumped',
+                                      'svnrdump dump, loaded, dumped',
                                       reloaded_dumpfile_a_d,
                                       reloaded_dumpfile_r_d,
                                       ignore_uuid=True)
 
     # Run each dump through svndumpfilter and check for no further change.
-    for dumpfile in [dumpfile_a_n,
-                     dumpfile_a_d,
-                     dumpfile_r_d
-                     ]:
+    for dumpfile, dumpfile_desc in [(dumpfile_a_n, 'svnadmin dump'),
+                                    (dumpfile_a_d, 'svnadmin dump --deltas'),
+                                    (dumpfile_r_d, 'svnrdump dump'),
+                                    ]:
       ### No buffer size seems to work for update_tests-2. So skip that test?
       ### (Its dumpfile size is ~360 KB non-delta, ~180 KB delta.)
       if len(''.join(dumpfile)) > 100000:
@@ -550,7 +554,9 @@ class Sandbox:
       # svndumpfilter strips them.
       # Ignore differences in number of blank lines between node records,
       # as svndumpfilter puts 3 instead of 2 after an add or delete record.
-      svntest.verify.compare_dump_files(None, None, dumpfile, dumpfile2,
+      svntest.verify.compare_dump_files(dumpfile_desc,
+                                        'after svndumpfilter include /',
+                                        dumpfile, dumpfile2,
                                         expect_content_length_always=True,
                                         ignore_empty_prop_sections=True,
                                         ignore_number_of_blank_lines=True)

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svntest/tree.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svntest/tree.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svntest/tree.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svntest/tree.py Sun Sep 30 18:26:47 2018
@@ -267,19 +267,8 @@ class SVNTreeNode:
     line += "%-20s: Item(" % ("'%s'" % path.replace(os.sep, '/'))
     comma = False
 
-    mime_type = self.props.get("svn:mime-type")
-    if not mime_type or mime_type.startswith("text/"):
-      if self.contents is not None:
-        # Escape some characters for nicer script and readability.
-        # (This is error output. I guess speed is no consideration here.)
-        line += "contents=\"%s\"" % (self.contents
-                                     .replace('\n','\\n')
-                                     .replace('"','\\"')
-                                     .replace('\r','\\r')
-                                     .replace('\t','\\t'))
-        comma = True
-    else:
-      line += 'content is binary data'
+    if self.contents is not None:
+      line += "contents=" + repr(self.contents)
       comma = True
 
     if self.props:

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svntest/verify.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svntest/verify.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svntest/verify.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svntest/verify.py Sun Sep 30 18:26:47 2018
@@ -150,8 +150,9 @@ class ExpectedOutput(object):
        MESSAGE unless it is None, the expected lines, the ACTUAL lines,
        and a diff, all labeled with LABEL.
     """
-    display_lines(message, self.expected, actual, label, label)
-    display_lines_diff(self.expected, actual, label, label)
+    e_label = label + ' (match_all=%s)' % (self.match_all,)
+    display_lines(message, self.expected, actual, e_label, label)
+    display_lines_diff(self.expected, actual, e_label, label)
 
 
 class AnyOutput(ExpectedOutput):
@@ -181,12 +182,36 @@ class AnyOutput(ExpectedOutput):
       logger.warn(message)
 
 
+def re_fullmatch(pattern, string, flags=0):
+  """If the whole STRING matches the regular expression PATTERN,
+     return a corresponding match object.
+     Based on re.fullmatch() in Python 3.4.
+  """
+  if pattern.endswith('$'):
+    return re.match(pattern, string, flags)
+
+  return re.match(pattern + '$', string, flags)
+
+def regex_fullmatch(rx, string):
+  """If the whole STRING matches the compiled regular expression RX,
+     return a corresponding match object.
+     Based on regex.fullmatch() in Python 3.4.
+  """
+  if rx.pattern.endswith('$'):
+    return rx.match(string)
+
+  return re_fullmatch(rx.pattern, string, rx.flags)
+
 class RegexOutput(ExpectedOutput):
   """Matches a single regular expression.
 
      If MATCH_ALL is true, every actual line must match the RE.  If
      MATCH_ALL is false, at least one actual line must match the RE.  In
      any case, there must be at least one line of actual output.
+
+     The RE must match a prefix of the actual line, in contrast to the
+     RegexListOutput and UnorderedRegexListOutput classes which match
+     whole lines.
   """
 
   def __init__(self, expected, match_all=True):
@@ -212,7 +237,8 @@ class RegexOutput(ExpectedOutput):
       return any(self.expected_re.match(line) for line in actual)
 
   def display_differences(self, message, label, actual):
-    display_lines(message, self.expected, actual, label + ' (regexp)', label)
+    e_label = label + ' (regexp, match_all=%s)' % (self.match_all,)
+    display_lines(message, self.expected, actual, e_label, label)
 
   def insert(self, index, line):
     self.expected.insert(index, line)
@@ -228,6 +254,9 @@ class RegexListOutput(ExpectedOutput):
      ones.
 
      In any case, there must be at least one line of actual output.
+
+     The REs must match whole actual lines, in contrast to the RegexOutput
+     class which matches a prefix of the actual line.
   """
 
   def __init__(self, expected, match_all=True):
@@ -243,18 +272,19 @@ class RegexListOutput(ExpectedOutput):
 
     if self.match_all:
       return (len(self.expected_res) == len(actual) and
-              all(e.match(a) for e, a in zip(self.expected_res, actual)))
+              all(regex_fullmatch(e, a) for e, a in zip(self.expected_res, actual)))
 
     i_expected = 0
     for actual_line in actual:
-      if self.expected_res[i_expected].match(actual_line):
+      if regex_fullmatch(self.expected_res[i_expected], actual_line):
         i_expected += 1
         if i_expected == len(self.expected_res):
           return True
     return False
 
   def display_differences(self, message, label, actual):
-    display_lines(message, self.expected, actual, label + ' (regexp)', label)
+    e_label = label + ' (regexp, match_all=%s)' % (self.match_all,)
+    display_lines(message, self.expected, actual, e_label, label)
 
     assert actual is not None
     if not isinstance(actual, list):
@@ -266,13 +296,13 @@ class RegexListOutput(ExpectedOutput):
         logger.warn('# Expected %d lines; actual %d lines' %
                     (len(self.expected), len(actual)))
       for e, a in map(None, self.expected_res, actual):
-        if e is not None and a is not None and e.match(a):
+        if e is not None and a is not None and regex_fullmatch(e, a):
           logger.warn("|  " + a.rstrip())
         else:
           if e is not None:
-            logger.warn("| -" + e.pattern.rstrip())
+            logger.warn("| -" + repr(e.pattern))
           if a is not None:
-            logger.warn("| +" + a.rstrip())
+            logger.warn("| +" + repr(a))
 
   def insert(self, index, line):
     self.expected.insert(index, line)
@@ -297,8 +327,9 @@ class UnorderedOutput(ExpectedOutput):
     return sorted(self.expected) == sorted(actual)
 
   def display_differences(self, message, label, actual):
-    display_lines(message, self.expected, actual, label + ' (unordered)', label)
-    display_lines_diff(sorted(self.expected), sorted(actual), label + ' (unordered)', label)
+    e_label = label + ' (unordered)'
+    display_lines(message, self.expected, actual, e_label, label)
+    display_lines_diff(sorted(self.expected), sorted(actual), e_label, label)
 
 
 class UnorderedRegexListOutput(ExpectedOutput):
@@ -313,6 +344,9 @@ class UnorderedRegexListOutput(ExpectedO
      expressions.  The implementation matches each expression in turn to
      the first unmatched actual line that it can match, and does not try
      all the permutations when there are multiple possible matches.
+
+     The REs must match whole actual lines, in contrast to the RegexOutput
+     class which matches a prefix of the actual line.
   """
 
   def __init__(self, expected):
@@ -332,7 +366,7 @@ class UnorderedRegexListOutput(ExpectedO
     for e in self.expected:
       expect_re = re.compile(e)
       for actual_line in actual:
-        if expect_re.match(actual_line):
+        if regex_fullmatch(expect_re, actual_line):
           actual.remove(actual_line)
           break
       else:
@@ -341,8 +375,8 @@ class UnorderedRegexListOutput(ExpectedO
     return True
 
   def display_differences(self, message, label, actual):
-    display_lines(message, self.expected, actual,
-                  label + ' (regexp) (unordered)', label)
+    e_label = label + ' (regexp) (unordered)'
+    display_lines(message, self.expected, actual, e_label, label)
 
     assert actual is not None
     if not isinstance(actual, list):
@@ -358,7 +392,7 @@ class UnorderedRegexListOutput(ExpectedO
     for e in self.expected:
       expect_re = re.compile(e)
       for actual_line in actual:
-        if expect_re.match(actual_line):
+        if regex_fullmatch(expect_re, actual_line):
           actual.remove(actual_line)
           break
       else:
@@ -772,7 +806,8 @@ class DumpParser:
     self.parse_all_revisions()
     return self.parsed
 
-def compare_dump_files(message, label, expected, actual,
+def compare_dump_files(label_expected, label_actual,
+                       expected, actual,
                        ignore_uuid=False,
                        expect_content_length_always=False,
                        ignore_empty_prop_sections=False,
@@ -814,6 +849,8 @@ def compare_dump_files(message, label, e
 
   if parsed_expected != parsed_actual:
     print('DIFF of raw dumpfiles (including expected differences)')
+    print('--- ' + (label_expected or 'expected'))
+    print('+++ ' + (label_actual or 'actual'))
     print(''.join(ndiff(expected, actual)))
     raise svntest.Failure('DIFF of parsed dumpfiles (ignoring expected differences)\n'
                           + '\n'.join(ndiff(

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/svntest/wc.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/svntest/wc.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/svntest/wc.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/svntest/wc.py Sun Sep 30 18:26:47 2018
@@ -1092,8 +1092,7 @@ def svn_uri_quote(url):
 
 def python_sqlite_can_read_wc():
   """Check if the Python builtin is capable enough to peek into wc.db"""
-  # Currently enough (1.7-1.9)
-  return svntest.sqlite3.sqlite_version_info >= (3, 6, 18)
+  return svntest.main.python_sqlite_can_read_our_wc_db()
 
 def open_wc_db(local_path):
   """Open the SQLite DB for the WC path LOCAL_PATH.

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/trans_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/trans_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/trans_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/trans_tests.py Sun Sep 30 18:26:47 2018
@@ -814,7 +814,8 @@ def props_only_file_update(sbox):
                       ]
 
   # Create r2 with iota's contents and svn:keywords modified
-  open(iota_path, 'w').writelines(content)
+  with open(iota_path, 'w') as f:
+    f.writelines(content)
   svntest.main.run_svn(None, 'propset', 'svn:keywords', 'Author', iota_path)
 
   expected_output = wc.State(wc_dir, {
@@ -831,7 +832,8 @@ def props_only_file_update(sbox):
   # Create r3 that drops svn:keywords
 
   # put the content back to its untranslated form
-  open(iota_path, 'w').writelines(content)
+  with open(iota_path, 'w') as f:
+    f.writelines(content)
 
   svntest.main.run_svn(None, 'propdel', 'svn:keywords', iota_path)
 

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/tree_conflict_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/tree_conflict_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/tree_conflict_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/tree_conflict_tests.py Sun Sep 30 18:26:47 2018
@@ -473,6 +473,7 @@ def ensure_tree_conflict(sbox, operation
         run_and_verify_svn(expected_stdout, [],
                            'merge',
                            '--allow-mixed-revisions',
+                           '--accept=postpone',
                            '-r', str(source_left_rev) + ':' + str(source_right_rev),
                            source_url, target_path)
       else:
@@ -1096,13 +1097,15 @@ def at_directory_external(sbox):
   svntest.main.run_svn(None, 'update', wc_dir)
 
   # r3: modify ^/A/B/E/alpha
-  open(sbox.ospath('A/B/E/alpha'), 'a').write('This is still A/B/E/alpha.\n')
+  with open(sbox.ospath('A/B/E/alpha'), 'a') as f:
+    f.write('This is still A/B/E/alpha.\n')
   svntest.main.run_svn(None, 'commit', '-m', 'file mod', wc_dir)
   svntest.main.run_svn(None, 'update', wc_dir)
   merge_rev = svntest.main.youngest(sbox.repo_dir)
 
   # r4: create ^/A/B/E/alpha2
-  open(sbox.ospath('A/B/E/alpha2'), 'a').write("This is the file 'alpha2'.\n")
+  with open(sbox.ospath('A/B/E/alpha2'), 'a') as f:
+    f.write("This is the file 'alpha2'.\n")
   svntest.main.run_svn(None, 'add', sbox.ospath('A/B/E/alpha2'))
   svntest.main.run_svn(None, 'commit', '-m', 'file add', wc_dir)
   svntest.main.run_svn(None, 'update', wc_dir)
@@ -1503,6 +1506,47 @@ def update_delete_mixed_rev(sbox):
   }
   run_and_verify_info([expected_info], sbox.repo_url + '/A/B/E/alpha2')
 
+# NB: This test will run forever if the bug it is testing for is present!
+def local_missing_dir_endless_loop(sbox):
+  "endless loop when resolving local-missing dir"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  sbox.simple_copy('A', 'A1')
+  sbox.simple_commit()
+  sbox.simple_update()
+  sbox.simple_move('A/B', 'A/B2')
+  sbox.simple_commit()
+  sbox.simple_update()
+  main.file_append_binary(sbox.ospath("A/B2/lambda"), "This is more content.\n")
+  sbox.simple_commit()
+  sbox.simple_update()
+
+  # Create a config which enables the interactive conflict resolver
+  config_contents = '''\
+[auth]
+password-stores =
+
+[miscellany]
+interactive-conflicts = true
+'''
+  config_dir = sbox.create_config_dir(config_contents)
+
+  # Bug: 'svn' keeps retrying interactive conflict resolution while the library
+  # keeps signalling 'SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE' -> endless loop
+  main.run_svn("Tree conflict on '%s'" % sbox.ospath("A1/B2"),
+      'merge', '-c4', '^/A', sbox.ospath('A1'),
+      '--config-dir', config_dir, '--force-interactive')
+
+  # If everything works as expected the resolver will recommended a
+  # resolution option and 'svn' will resolve the conflict automatically.
+  # Verify that 'A1/B/lambda' contains the merged content:
+  contents = open(sbox.ospath('A1/B/lambda'), 'rb').readlines()
+  svntest.verify.compare_and_display_lines(
+    "A1/B/lambda has unexpectected contents", sbox.ospath("A1/B/lambda"),
+    [ "This is the file 'lambda'.\n", "This is more content.\n"], contents)
+
+
 #######################################################################
 # Run the tests
 
@@ -1534,6 +1578,7 @@ test_list = [ None,
               actual_only_node_behaviour,
               update_dir_with_not_present,
               update_delete_mixed_rev,
+              local_missing_dir_endless_loop,
              ]
 
 if __name__ == '__main__':

Modified: subversion/branches/better-pristines/subversion/tests/cmdline/upgrade_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/better-pristines/subversion/tests/cmdline/upgrade_tests.py?rev=1842404&r1=1842403&r2=1842404&view=diff
==============================================================================
--- subversion/branches/better-pristines/subversion/tests/cmdline/upgrade_tests.py (original)
+++ subversion/branches/better-pristines/subversion/tests/cmdline/upgrade_tests.py Sun Sep 30 18:26:47 2018
@@ -392,7 +392,8 @@ def xml_entries_relocate(path, from_url,
   entries = os.path.join(path, adm_name, 'entries')
   txt = open(entries).read().replace('url="' + from_url, 'url="' + to_url)
   os.chmod(entries, svntest.main.S_ALL_RWX)
-  open(entries, 'w').write(txt)
+  with open(entries, 'w') as f:
+    f.write(txt)
 
   for dirent in os.listdir(path):
     item_path = os.path.join(path, dirent)
@@ -410,7 +411,8 @@ def simple_entries_replace(path, from_ur
   entries = os.path.join(path, adm_name, 'entries')
   txt = open(entries).read().replace(from_url, to_url)
   os.chmod(entries, svntest.main.S_ALL_RWX)
-  open(entries, 'wb').write(txt.encode())
+  with open(entries, 'wb') as f:
+    f.write(txt.encode())
 
   for dirent in os.listdir(path):
     item_path = os.path.join(path, dirent)



Mime
View raw message