subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From hwri...@apache.org
Subject svn commit: r1436688 [11/12] - in /subversion/branches/ev2-export: ./ contrib/client-side/emacs/ notes/commit-access-templates/ subversion/bindings/javahl/native/ subversion/bindings/swig/perl/native/t/ subversion/bindings/swig/ruby/test/ subversion/in...
Date Mon, 21 Jan 2013 23:37:04 GMT
Modified: subversion/branches/ev2-export/subversion/tests/cmdline/update_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/subversion/tests/cmdline/update_tests.py?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/subversion/tests/cmdline/update_tests.py (original)
+++ subversion/branches/ev2-export/subversion/tests/cmdline/update_tests.py Mon Jan 21 23:37:01 2013
@@ -990,21 +990,16 @@ def update_replace_dir(sbox):
                                         expected_status)
 
   # Update to revision 1 replaces the directory
-  ### I can't get this to work :-(
-  #expected_output = svntest.wc.State(wc_dir, {
-  #  'A/B/F'       : Item(verb='Adding'),
-  #  'A/B/F'       : Item(verb='Deleting'),
-  #  })
-  #expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
-  #svntest.actions.run_and_verify_update(wc_dir,
-  #                                      expected_output,
-  #                                      expected_disk,
-  #                                      expected_status,
-  #                                      None, None, None, None, None, 0,
-  #                                      '-r', '1', wc_dir)
-
-  # Update to revision 1 replaces the directory
-  svntest.actions.run_and_verify_svn(None, None, [], 'up', '-r', '1', wc_dir)
+  expected_output = svntest.wc.State(wc_dir, {
+    'A/B/F' : Item(status='A ', prev_status='D '),
+  })
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  svntest.actions.run_and_verify_update(wc_dir,
+                                        expected_output,
+                                        expected_disk,
+                                        expected_status,
+                                        None, None, None, None, None, 0,
+                                        '-r', '1', wc_dir)
 
   expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
 
@@ -1164,18 +1159,14 @@ def update_deleted_missing_dir(sbox):
 
   # Create expected output tree for an update of the missing items by name
   expected_output = svntest.wc.State(wc_dir, {
-    'A/B/E' : Item(status='D '),
-    'A/D/H' : Item(status='D '),
-    })
-
-  # In single-db mode the missing items are restored before the update
-  expected_output.add({
-      'A/D/H/psi'         : Item(verb='Restored'),
-      'A/D/H/omega'       : Item(verb='Restored'),
-      'A/D/H/chi'         : Item(verb='Restored'),
-      'A/B/E/beta'        : Item(verb='Restored'),
-      'A/B/E/alpha'       : Item(verb='Restored')
-      # A/B/E and A/D/H are also restored, but are then overriden by the delete
+    'A/D/H/psi'         : Item(verb='Restored'),
+    'A/D/H/omega'       : Item(verb='Restored'),
+    'A/D/H/chi'         : Item(verb='Restored'),
+    'A/B/E/beta'        : Item(verb='Restored'),
+    'A/B/E/alpha'       : Item(verb='Restored'),
+    # A/B/E and A/D/H are also restored, but are then overriden by the delete
+    'A/B/E'             : Item(status='D ', prev_verb='Restored'),
+    'A/D/H'             : Item(status='D ', prev_verb='Restored'),
   })
 
   # Create expected disk tree for the update.
@@ -2360,9 +2351,9 @@ def update_wc_on_windows_drive(sbox):
                                        sbox.repo_url, wc_dir)
 
     # Make some local modifications
-    mu_path = os.path.join(wc_dir, 'A', 'mu')
+    mu_path = os.path.join(wc_dir, 'A', 'mu').replace(os.sep, '/')
     svntest.main.file_append(mu_path, '\nAppended text for mu')
-    zeta_path = os.path.join(wc_dir, 'zeta')
+    zeta_path = os.path.join(wc_dir, 'zeta').replace(os.sep, '/')
     svntest.main.file_append(zeta_path, "This is the file 'zeta'\n")
     svntest.main.run_svn(None, 'add', zeta_path)
 
@@ -2383,7 +2374,7 @@ def update_wc_on_windows_drive(sbox):
                                           wc_dir, zeta_path)
 
     # Non recursive commit
-    dir1_path = os.path.join(wc_dir, 'dir1')
+    dir1_path = os.path.join(wc_dir, 'dir1').replace(os.sep, '/')
     os.mkdir(dir1_path)
     svntest.main.run_svn(None, 'add', '-N', dir1_path)
     file1_path = os.path.join(dir1_path, 'file1')
@@ -2452,10 +2443,11 @@ def update_wc_on_windows_drive(sbox):
     expected_disk.tweak('A/mu', contents = expected_disk.desc['A/mu'].contents
                         + '\nAppended text for mu')
 
+    # Use .old_tree() for status to avoid the entries validation
     svntest.actions.run_and_verify_update(wc_dir,
                                           expected_output,
                                           expected_disk,
-                                          expected_status)
+                                          expected_status.old_tree())
 
   finally:
     os.chdir(was_cwd)
@@ -3518,12 +3510,12 @@ def update_copied_from_replaced_and_chan
   sbox.build()
   wc_dir = sbox.wc_dir
 
-  fn1_relpath = os.path.join('A', 'B', 'E', 'aardvark')
-  fn2_relpath = os.path.join('A', 'B', 'E', 'alpha')
-  fn3_relpath = os.path.join('A', 'B', 'E', 'beta')
-  fn1_path = os.path.join(wc_dir, fn1_relpath)
-  fn2_path = os.path.join(wc_dir, fn2_relpath)
-  fn3_path = os.path.join(wc_dir, fn3_relpath)
+  fn1_relpath = 'A/B/E/aardvark'
+  fn2_relpath = 'A/B/E/alpha'
+  fn3_relpath = 'A/B/E/beta'
+  fn1_path = sbox.ospath(fn1_relpath)
+  fn2_path = sbox.ospath(fn2_relpath)
+  fn3_path = sbox.ospath(fn3_relpath)
 
   # Move fn2 to fn1
   svntest.actions.run_and_verify_svn(None, None, [],
@@ -3572,7 +3564,7 @@ def update_copied_from_replaced_and_chan
   # Go back to r1.
   expected_output = svntest.wc.State(wc_dir, {
     fn1_relpath: Item(status='D '),
-    fn2_relpath: Item(status='A '), # though actually should be D and A
+    fn2_relpath: Item(status='A ', prev_status='D '), # D then A
     fn3_relpath: Item(status='A '),
     })
 
@@ -3590,7 +3582,7 @@ def update_copied_from_replaced_and_chan
   # And back up to 3 again.
   expected_output = svntest.wc.State(wc_dir, {
     fn1_relpath: Item(status='A '),
-    fn2_relpath: Item(status='A '), # though actually should be D and A
+    fn2_relpath: Item(status='A ', prev_status='D '), # D then A
     fn3_relpath: Item(status='D '),
     })
 
@@ -5430,7 +5422,6 @@ def update_to_HEAD_plus_1(sbox):
                                         None, None,
                                         None, None, None, other_wc, '-r', '2')
 
-@XFail()
 def update_moved_dir_leaf_del(sbox):
   "update locally moved dir with leaf del"
   sbox.build()
@@ -5440,24 +5431,31 @@ def update_moved_dir_leaf_del(sbox):
                        sbox.repo_url + "/A/B/E/alpha")
   sbox.simple_move("A/B/E", "A/B/E2")
 
-  # since alpha isn't locally modified, the incoming delete should auto-merge
+  # Produce a tree conflict by updating the working copy to the
+  # revision which removed A/B/E/alpha. The deletion collides with
+  # the local move of A/B/E to A/B/E2.
   expected_output = svntest.wc.State(wc_dir, {
-    'A/B/E2/alpha' : Item(status='D '),
+    'A/B/E'       : Item(status='  ', treeconflict='C'),
+    'A/B/E/alpha' : Item(status='  ', treeconflict='D'),
   })
   expected_disk = svntest.main.greek_state.copy()
   expected_disk.remove('A/B/E/alpha', 'A/B/E/beta', 'A/B/E')
   expected_disk.add({
     'A/B/E2'           : Item(),
+    'A/B/E2/alpha'     : Item(contents="This is the file 'alpha'.\n"),
     'A/B/E2/beta'      : Item(contents="This is the file 'beta'.\n"),
   })
   expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
   expected_status.add({
-    'A/B/E2'            : Item(status='A ', copied='+', wc_rev='-'),
+    'A/B/E2'            : Item(status='A ', copied='+', wc_rev='-',
+                               moved_from='A/B/E'),
     'A/B/E2/beta'       : Item(status='  ', copied='+', wc_rev='-'),
-    'A/B/E2/alpha'      : Item(status='D ', copied='+', wc_rev='-'),
+    'A/B/E2/alpha'      : Item(status='  ', copied='+', wc_rev='-'),
   })
   expected_status.remove('A/B/E/alpha')
-  expected_status.tweak('A/B/E', 'A/B/E/beta', status='D ')
+  expected_status.tweak('A/B/E', status='D ', treeconflict='C',
+                        moved_to='A/B/E2')
+  expected_status.tweak('A/B/E/beta', status='D ')
   svntest.actions.run_and_verify_update(wc_dir,
                                         expected_output,
                                         expected_disk,
@@ -5465,7 +5463,20 @@ def update_moved_dir_leaf_del(sbox):
                                         None, None, None,
                                         None, None, 1)
 
+  # Now resolve the conflict, using --accept=mine-conflict applying
+  # the update to A/B/E2 causing a delete-delete conflict
+  svntest.actions.run_and_verify_svn("resolve failed", None, [],
+                                     'resolve',
+                                     '--accept=mine-conflict',
+                                     sbox.ospath('A/B/E'))
+  expected_status.tweak('A/B/E', treeconflict=None)
+  expected_status.tweak('A/B/E2/alpha', status='? ', treeconflict='C',
+                        copied=None, wc_rev=None)
+  svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
 @XFail()
+@Issue(3144,3630)
+# Like break_moved_dir_edited_leaf_del, but with --accept=mine-conflict
 def update_moved_dir_edited_leaf_del(sbox):
   "update locally moved dir with edited leaf del"
   sbox.build()
@@ -5477,9 +5488,12 @@ def update_moved_dir_edited_leaf_del(sbo
   svntest.main.file_write(sbox.ospath('A/B/E2/alpha'),
                           "This is a changed 'alpha'.\n")
 
-  # since alpha was modified post-move, the incoming delete should conflict
+  # Produce a tree conflict by updating the working copy to the
+  # revision which removed A/B/E/alpha. The deletion collides with
+  # the local move of A/B/E to A/B/E2.
   expected_output = svntest.wc.State(wc_dir, {
-    'A/B/E/alpha'       : Item(status='  ', treeconflict='C'),
+    'A/B/E'       : Item(status='  ', treeconflict='C'),
+    'A/B/E/alpha' : Item(status='  ', treeconflict='D'),
   })
   expected_disk = svntest.main.greek_state.copy()
   expected_disk.remove('A/B/E/alpha', 'A/B/E/beta', 'A/B/E')
@@ -5489,14 +5503,16 @@ def update_moved_dir_edited_leaf_del(sbo
     'A/B/E2/beta'      : Item(contents="This is the file 'beta'.\n"),
   })
   expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
-  expected_status.tweak('A/B/E', 'A/B/E/beta', status='D ')
-  expected_status.remove('A/B/E/alpha')
   expected_status.add({
-    'A/B/E/alpha'       : Item(status='! ', treeconflict='C'),
-    'A/B/E2'            : Item(status='A ', copied='+', wc_rev='-'),
+    'A/B/E2'            : Item(status='A ', copied='+', wc_rev='-',
+                               moved_from='A/B/E'),
     'A/B/E2/beta'       : Item(status='  ', copied='+', wc_rev='-'),
     'A/B/E2/alpha'      : Item(status='M ', copied='+', wc_rev='-'),
   })
+  expected_status.remove('A/B/E/alpha')
+  expected_status.tweak('A/B/E', status='D ', treeconflict='C',
+                        moved_to='A/B/E2')
+  expected_status.tweak('A/B/E/beta', status='D ')
   svntest.actions.run_and_verify_update(wc_dir,
                                         expected_output,
                                         expected_disk,
@@ -5504,7 +5520,18 @@ def update_moved_dir_edited_leaf_del(sbo
                                         None, None, None,
                                         None, None, 1)
 
-@XFail()
+  # Now resolve the conflict, using --accept=mine-conflict.
+  # This should apply the update to A/B/E2, and flag a tree
+  # conflict on A/B/E2/alpha (incoming delete vs. local edit)
+  # XFAIL: Currently the A/B/E2/alpha is deleted during this update.
+  svntest.actions.run_and_verify_svn("resolve failed", None, [],
+                                     'resolve',
+                                     '--recursive',
+                                     '--accept=mine-conflict', wc_dir)
+  expected_status.tweak('A/B/E', treeconflict=None)
+  expected_status.tweak('A/B/E2/alpha', treeconflict='C')
+  svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
 def update_moved_dir_file_add(sbox):
   "update locally moved dir with incoming file"
   sbox.build()
@@ -5519,9 +5546,12 @@ def update_moved_dir_file_add(sbox):
   svntest.main.run_svn(False, 'update', '-r', '1', wc_dir)
   sbox.simple_move("A/B/E", "A/B/E2")
 
-  # the incoming file should auto-merge
+  # Produce a tree conflict by updating the working copy to the
+  # revision which created A/B/E/foo. The addition collides with
+  # the local move of A/B/E to A/B/E2.
   expected_output = svntest.wc.State(wc_dir, {
-    'A/B/E2/foo' : Item(status='A '),
+    'A/B/E'       : Item(status='  ', treeconflict='C'),
+    'A/B/E/foo'   : Item(status='  ', treeconflict='A'),
   })
   expected_disk = svntest.main.greek_state.copy()
   expected_disk.remove('A/B/E/alpha', 'A/B/E/beta', 'A/B/E')
@@ -5529,17 +5559,19 @@ def update_moved_dir_file_add(sbox):
     'A/B/E2'           : Item(),
     'A/B/E2/alpha'     : Item(contents="This is the file 'alpha'.\n"),
     'A/B/E2/beta'      : Item(contents="This is the file 'beta'.\n"),
-    'A/B/E2/foo'       : Item(contents=foo_content),
   })
   expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
-  expected_status.tweak('A/B/E', 'A/B/E/alpha', 'A/B/E/beta', status='D ')
   expected_status.add({
     'A/B/E/foo'         : Item(status='D ', wc_rev='2'),
-    'A/B/E2'            : Item(status='A ', copied='+', wc_rev='-'),
+    'A/B/E2'            : Item(status='A ', copied='+', wc_rev='-',
+                               moved_from='A/B/E'),
     'A/B/E2/beta'       : Item(status='  ', copied='+', wc_rev='-'),
     'A/B/E2/alpha'      : Item(status='  ', copied='+', wc_rev='-'),
-    'A/B/E2/foo'        : Item(status='A ', copied='+', wc_rev='-'),
   })
+  expected_status.tweak('A/B/E', status='D ', treeconflict='C',
+                        moved_to='A/B/E2')
+  expected_status.tweak('A/B/E/alpha', status='D ')
+  expected_status.tweak('A/B/E/beta', status='D ')
   svntest.actions.run_and_verify_update(wc_dir,
                                         expected_output,
                                         expected_disk,
@@ -5547,6 +5579,20 @@ def update_moved_dir_file_add(sbox):
                                         None, None, None,
                                         None, None, 1)
 
+  # Now resolve the conflict, using --accept=mine-conflict.
+  # This should apply the update to A/B/E2, adding A/B/E2/foo.
+  svntest.actions.run_and_verify_svn("resolve failed", None, [],
+                                     'resolve',
+                                     '--accept=mine-conflict',
+                                     sbox.ospath('A/B/E'))
+  # the incoming file should auto-merge
+  expected_status.tweak('A/B/E', treeconflict=None)
+  expected_status.add({
+    'A/B/E2/foo'        : Item(status='  ', copied='+', wc_rev='-'),
+  })
+  svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
+
 def update_moved_dir_dir_add(sbox):
   "update locally moved dir with incoming dir"
   sbox.build()
@@ -5653,15 +5699,15 @@ def update_moved_dir_file_move(sbox):
 
   # The incoming change is a delete as we don't yet track server-side
   # moves.  Resolving the tree-conflict as "mine-conflict" applies the
-  # delete to the move destination.  This is effectively accepting the
-  # move from the server.
+  # delete to the move destination creating a delete-delete conflict.
   svntest.actions.run_and_verify_svn("resolve failed", None, [],
                                      'resolve',
-                                     '--recursive',
-                                     '--accept=mine-conflict', wc_dir)
+                                     '--accept=mine-conflict',
+                                     sbox.ospath('A/B/E'))
 
   expected_status.tweak('A/B/E', treeconflict=None)
-  expected_status.remove('A/B/E2/alpha')
+  expected_status.tweak('A/B/E2/alpha', status='? ', treeconflict='C',
+                        copied=None, wc_rev=None)
   svntest.actions.run_and_verify_status(wc_dir, expected_status)
 
 
@@ -5831,9 +5877,10 @@ def update_edit_delete_obstruction(sbox)
   sbox.simple_commit()
 
   # r3
-  #sbox.simple_mkdir('iota')
-  #sbox.simple_copy('A/D/gamma', 'A/B')
-  #sbox.simple_commit()
+  sbox.simple_mkdir('iota')
+  sbox.simple_copy('A/D/gamma', 'A/B')
+  sbox.simple_rm('A/D/H/chi')
+  sbox.simple_commit()
 
   sbox.simple_update('', 1)
 
@@ -5865,13 +5912,15 @@ def update_edit_delete_obstruction(sbox)
     'A/D/H/psi'         : Item(status='! ', wc_rev='2'),
     'A/D/gamma'         : Item(status='! ', wc_rev='2'),
     'A/C'               : Item(status='  ', wc_rev='2'),
-    'A/B'               : Item(status='~ ', treeconflict='C', wc_rev='-'),
-    'A/B/F'             : Item(status='! ', wc_rev='-'),
-    'A/B/E'             : Item(status='! ', wc_rev='-'),
-    'A/B/E/beta'        : Item(status='! ', wc_rev='-'),
-    'A/B/E/alpha'       : Item(status='! ', wc_rev='-'),
-    'A/B/lambda'        : Item(status='! ', wc_rev='-'),
-    'iota'              : Item(status='~ ', treeconflict='C', wc_rev='-'),
+    'A/B'               : Item(status='~ ', treeconflict='C', wc_rev='-',
+                               entry_status='A ', entry_copied='+'),
+    'A/B/F'             : Item(status='! ', wc_rev='-', entry_copied='+'),
+    'A/B/E'             : Item(status='! ', wc_rev='-', entry_copied='+'),
+    'A/B/E/beta'        : Item(status='! ', wc_rev='-', entry_copied='+'),
+    'A/B/E/alpha'       : Item(status='! ', wc_rev='-', entry_copied='+'),
+    'A/B/lambda'        : Item(status='! ', wc_rev='-', entry_copied='+'),
+    'iota'              : Item(status='~ ', treeconflict='C', wc_rev='-',
+                               entry_status='A ', entry_copied='+'),
   })
   expected_disk = svntest.wc.State('', {
     'A/D'               : Item(contents="Obstruction", props={'key':'value'}),
@@ -5898,6 +5947,418 @@ def update_edit_delete_obstruction(sbox)
                                         None, None, 1,
                                         '-r', '2', wc_dir)
 
+  # Cleanup obstructions
+  os.remove(sbox.ospath('A/B'))
+  os.remove(sbox.ospath('A/D'))
+  os.rmdir(sbox.ospath('iota'))
+  os.rmdir(sbox.ospath('A/mu'))
+
+  # Revert to remove working nodes and tree conflicts
+  svntest.actions.run_and_verify_svn('Reverting', None, [],
+                                     'revert', '-R',
+                                     sbox.ospath('A/B'),
+                                     sbox.ospath('A/mu'),
+                                     sbox.ospath('A/D'),
+                                     sbox.ospath('iota'))
+  sbox.simple_update('', 1)
+
+  # Now obstruct A (as parent of the changed node), and retry
+  svntest.main.safe_rmtree(sbox.ospath('A'))
+  svntest.main.file_append(sbox.ospath('A'), "Obstruction")
+
+  # And now update to delete B and iota
+
+  expected_output = svntest.wc.State(wc_dir, {
+    'A'         : Item(status='  ', treeconflict='C'),
+    'A/mu'      : Item(status='  ', treeconflict='U'),
+    'A/D'       : Item(status='  ', treeconflict='U'),
+    'A/D/G'     : Item(status='  ', treeconflict='U'),
+    'A/D/H'     : Item(status='  ', treeconflict='U'),
+    'A/D/H/chi' : Item(status='  ', treeconflict='D'),
+    'A/B'       : Item(prev_status='  ', prev_treeconflict='D', # Replacement
+                       status='  ', treeconflict='A'), 
+    'iota'      : Item(status='A ', prev_status='D '), # Replacement
+  })
+
+  expected_disk = svntest.wc.State('', {
+    'A'                 : Item(contents="Obstruction"),
+    'iota'              : Item(),
+  })
+
+  expected_status = svntest.wc.State(wc_dir, {
+    ''            : Item(status='  ', wc_rev='3'),
+    'A'           : Item(status='~ ', treeconflict='C', wc_rev='3'),
+    'A/mu'        : Item(status='! ', wc_rev='3'),
+    'A/D'         : Item(status='! ', wc_rev='3'),
+    'A/D/G'       : Item(status='! ', wc_rev='3'),
+    'A/D/G/rho'   : Item(status='! ', wc_rev='3'),
+    'A/D/G/pi'    : Item(status='! ', wc_rev='3'),
+    'A/D/G/tau'   : Item(status='! ', wc_rev='3'),
+    'A/D/gamma'   : Item(status='! ', wc_rev='3'),
+    'A/D/H'       : Item(status='! ', wc_rev='3'),
+    'A/D/H/psi'   : Item(status='! ', wc_rev='3'),
+    'A/D/H/omega' : Item(status='! ', wc_rev='3'),
+    'A/C'         : Item(status='! ', wc_rev='3'),
+    'A/B'         : Item(status='! ', wc_rev='3'),
+    'iota'        : Item(status='  ', wc_rev='3'),
+  })
+
+  svntest.actions.run_and_verify_update(wc_dir,
+                                        expected_output,
+                                        expected_disk,
+                                        expected_status,
+                                        None, None, None,
+                                        None, None, 1,
+                                        '-r', '3', wc_dir)
+
+def update_deleted(sbox):
+  "update a deleted tree"
+
+  sbox.build(read_only = True)
+  wc_dir = sbox.wc_dir
+  sbox.simple_rm('A')
+
+  expected_output = svntest.wc.State(wc_dir, {
+  })
+
+  expected_status = svntest.wc.State(wc_dir, {
+  })
+
+  # This runs an update anchored on A, which is deleted. The update editor
+  # shouldn't look at the ACTUAL/WORKING data in this case, but in 1.7 it did.
+  svntest.actions.run_and_verify_update(wc_dir,
+                                        expected_output,
+                                        None,
+                                        None,
+                                        None, None, None,
+                                        None, None, 1,
+                                        sbox.ospath('A/B'))
+
+@Issue(3144,3630)
+# Like update_moved_dir_edited_leaf_del, but with --accept=theirs-conflict
+def break_moved_dir_edited_leaf_del(sbox):
+  "break local move of dir with edited leaf del"
+  sbox.build()
+  wc_dir = sbox.wc_dir
+
+  svntest.main.run_svn(False, 'rm', '-m', 'remove /A/B/E/alpha',
+                       sbox.repo_url + "/A/B/E/alpha")
+  sbox.simple_move("A/B/E", "A/B/E2")
+  svntest.main.file_write(sbox.ospath('A/B/E2/alpha'),
+                          "This is a changed 'alpha'.\n")
+
+  # Produce a tree conflict by updating the working copy to the
+  # revision which removed A/B/E/alpha. The deletion collides with
+  # the local move of A/B/E to A/B/E2.
+  expected_output = svntest.wc.State(wc_dir, {
+    'A/B/E'       : Item(status='  ', treeconflict='C'),
+    'A/B/E/alpha' : Item(status='  ', treeconflict='D'),
+  })
+  expected_disk = svntest.main.greek_state.copy()
+  expected_disk.remove('A/B/E/alpha', 'A/B/E/beta', 'A/B/E')
+  expected_disk.add({
+    'A/B/E2'           : Item(),
+    'A/B/E2/alpha'     : Item(contents="This is a changed 'alpha'.\n"),
+    'A/B/E2/beta'      : Item(contents="This is the file 'beta'.\n"),
+  })
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
+  expected_status.add({
+    'A/B/E2'            : Item(status='A ', copied='+', wc_rev='-',
+                               moved_from='A/B/E'),
+    'A/B/E2/beta'       : Item(status='  ', copied='+', wc_rev='-'),
+    'A/B/E2/alpha'      : Item(status='M ', copied='+', wc_rev='-'),
+  })
+  expected_status.remove('A/B/E/alpha')
+  expected_status.tweak('A/B/E', status='D ', treeconflict='C',
+                        moved_to='A/B/E2')
+  expected_status.tweak('A/B/E/beta', status='D ')
+  svntest.actions.run_and_verify_update(wc_dir,
+                                        expected_output,
+                                        expected_disk,
+                                        expected_status,
+                                        None, None, None,
+                                        None, None, 1)
+
+  # Now resolve the conflict, using --accept=theirs-conflict.
+  # This should break the move of A/B/E to A/B/E2, leaving A/B/E2
+  # as a copy. The deletion of A/B/E is reverted (unless it has been
+  # replaced by a new A/B/E, which is a different test case).
+  # XFAIL: Currently the move is still recorded after 'svn resolve'.
+  svntest.actions.run_and_verify_svn("resolve failed", None, [],
+                                     'resolve', '--recursive',
+                                     '--accept=theirs-conflict', wc_dir)
+  expected_status.tweak('A/B/E', status='  ', treeconflict=None, moved_to=None)
+  expected_status.tweak('A/B/E/beta', status='  ')
+  expected_status.tweak('A/B/E2', moved_from=None)
+  svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
+@XFail()
+@Issue(3144,3630)
+def break_moved_replaced_dir(sbox):
+  "break local move of dir plus replace"
+  sbox.build()
+  wc_dir = sbox.wc_dir
+
+  svntest.main.run_svn(False, 'rm', '-m', 'remove /A/B/E/alpha',
+                       sbox.repo_url + "/A/B/E/alpha")
+  sbox.simple_move("A/B/E", "A/B/E2")
+  svntest.main.file_write(sbox.ospath('A/B/E2/alpha'),
+                          "This is a changed 'alpha'.\n")
+
+  # Locally replace A/B/E with something else
+  sbox.simple_copy('A/D/H', 'A/B/E')
+
+  # Produce a tree conflict by updating the working copy to the
+  # revision which removed A/B/E/alpha. The deletion collides with
+  # the local move of A/B/E to A/B/E2.
+  expected_output = svntest.wc.State(wc_dir, {
+    'A/B/E'       : Item(status='  ', treeconflict='C'),
+    'A/B/E/alpha' : Item(status='  ', treeconflict='D'),
+  })
+  expected_disk = svntest.main.greek_state.copy()
+  expected_disk.remove('A/B/E/alpha', 'A/B/E/beta')
+  expected_disk.add({
+    'A/B/E/chi'        : Item(contents="This is the file 'chi'.\n"),
+    'A/B/E/psi'        : Item(contents="This is the file 'psi'.\n"),
+    'A/B/E/omega'      : Item(contents="This is the file 'omega'.\n"),
+    'A/B/E2'           : Item(),
+    'A/B/E2/alpha'     : Item(contents="This is a changed 'alpha'.\n"),
+    'A/B/E2/beta'      : Item(contents="This is the file 'beta'.\n"),
+  })
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 2)
+  expected_status.add({
+    'A/B/E/chi'         : Item(status='  ', copied='+', wc_rev='-'),
+    'A/B/E/psi'         : Item(status='  ', copied='+', wc_rev='-'),
+    'A/B/E/omega'       : Item(status='  ', copied='+', wc_rev='-'),
+    'A/B/E2'            : Item(status='A ', copied='+', wc_rev='-',
+                               moved_from='A/B/E'),
+    'A/B/E2/beta'       : Item(status='  ', copied='+', wc_rev='-'),
+    'A/B/E2/alpha'      : Item(status='M ', copied='+', wc_rev='-'),
+  })
+  expected_status.remove('A/B/E/alpha')
+  expected_status.tweak('A/B/E', status='R ', copied='+', wc_rev='-',
+                        treeconflict='C', moved_to='A/B/E2')
+  expected_status.tweak('A/B/E/beta', status='D ')
+  svntest.actions.run_and_verify_update(wc_dir,
+                                        expected_output,
+                                        expected_disk,
+                                        expected_status,
+                                        None, None, None,
+                                        None, None, 1)
+
+  # Now resolve the conflict, using --accept=theirs-conflict.
+  # This should break the move of A/B/E to A/B/E2, leaving A/B/E2
+  # as a copy. A/B/E is not reverted since it has been replaced
+  # by a new A/B/E.
+  svntest.actions.run_and_verify_svn("resolve failed", None, [],
+                                     'resolve', '--recursive',
+                                     '--accept=theirs-conflict', wc_dir)
+  expected_status.tweak('A/B/E2', moved_from=None)
+  svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
+@Issue(4295)
+def update_removes_switched(sbox):
+  "update completely removes switched node"
+
+  sbox.build(create_wc = False)
+
+  wc_dir = sbox.wc_dir
+  repo_url = sbox.repo_url
+
+  svntest.actions.run_and_verify_svn(None, None, [],
+                                     'cp', repo_url + '/A',
+                                           repo_url + '/AA', '-m', 'Q')
+
+  svntest.actions.run_and_verify_svn(None, None, [],
+                                     'co', repo_url + '/A', sbox.wc_dir)
+  svntest.actions.run_and_verify_svn(None, None, [],
+                                     'switch', repo_url + '/AA/B',
+                                               wc_dir + '/B')
+
+  svntest.actions.run_and_verify_svn(None, None, [],
+                                     'rm', repo_url + '/AA/B', '-m', 'Q')
+
+  expected_output = svntest.wc.State(wc_dir, {
+    'B'                 : Item(status='D '),
+  })
+  expected_status = svntest.wc.State(wc_dir, {
+    ''                  : Item(status='  ', wc_rev='3'),
+    'D'                 : Item(status='  ', wc_rev='3'),
+    'D/G'               : Item(status='  ', wc_rev='3'),
+    'D/G/rho'           : Item(status='  ', wc_rev='3'),
+    'D/G/pi'            : Item(status='  ', wc_rev='3'),
+    'D/G/tau'           : Item(status='  ', wc_rev='3'),
+    'D/H'               : Item(status='  ', wc_rev='3'),
+    'D/H/omega'         : Item(status='  ', wc_rev='3'),
+    'D/H/chi'           : Item(status='  ', wc_rev='3'),
+    'D/H/psi'           : Item(status='  ', wc_rev='3'),
+    'D/gamma'           : Item(status='  ', wc_rev='3'),
+    'C'                 : Item(status='  ', wc_rev='3'),
+    'mu'                : Item(status='  ', wc_rev='3'),
+  })
+
+  # Before r1435684 the inherited properties code would try to fetch
+  # inherited properties for ^/AA/B and fail.
+  #
+  # The inherited properties fetch code would then bail and forget to reset
+  # the ra-session URL back to its original value.
+  #
+  # After that the update code (which ignored the specific error code) was
+  # continued the update against /AA/B (url of missing switched path)
+  # instead of against A (the working copy url).
+
+  # This update removes 'A/B', since its in-repository location is removed.
+  svntest.actions.run_and_verify_update(wc_dir,
+                                        expected_output,
+                                        None,
+                                        expected_status)
+
+  expected_output = svntest.wc.State(wc_dir, {
+    'B'          : Item(status='A '),
+    'B/lambda'   : Item(status='A '),
+    'B/E'        : Item(status='A '),
+    'B/E/alpha'  : Item(status='A '),
+    'B/E/beta'   : Item(status='A '),
+    'B/F'        : Item(status='A '),
+  })
+  expected_status = svntest.wc.State(wc_dir, {
+    ''                  : Item(status='  ', wc_rev='3'),
+    'D'                 : Item(status='  ', wc_rev='3'),
+    'D/G'               : Item(status='  ', wc_rev='3'),
+    'D/G/rho'           : Item(status='  ', wc_rev='3'),
+    'D/G/pi'            : Item(status='  ', wc_rev='3'),
+    'D/G/tau'           : Item(status='  ', wc_rev='3'),
+    'D/H'               : Item(status='  ', wc_rev='3'),
+    'D/H/omega'         : Item(status='  ', wc_rev='3'),
+    'D/H/chi'           : Item(status='  ', wc_rev='3'),
+    'D/H/psi'           : Item(status='  ', wc_rev='3'),
+    'D/gamma'           : Item(status='  ', wc_rev='3'),
+    'B'                 : Item(status='  ', wc_rev='3'),
+    'B/E'               : Item(status='  ', wc_rev='3'),
+    'B/E/alpha'         : Item(status='  ', wc_rev='3'),
+    'B/E/beta'          : Item(status='  ', wc_rev='3'),
+    'B/F'               : Item(status='  ', wc_rev='3'),
+    'B/lambda'          : Item(status='  ', wc_rev='3'),
+    'C'                 : Item(status='  ', wc_rev='3'),
+    'mu'                : Item(status='  ', wc_rev='3'),
+  })
+  
+  # And this final update brings back the node, as it was before switching.
+  svntest.actions.run_and_verify_update(wc_dir,
+                                       expected_output,
+                                       None,
+                                       expected_status)
+
+@Issue(3192)
+def incomplete_overcomplete(sbox):
+  "verify editor v1 incomplete behavior"
+
+  sbox.build()
+
+  wc_dir = sbox.wc_dir
+  repo_dir = sbox.repo_dir
+  repo_url = sbox.repo_url
+
+  # r2 - Make sure we have some dir properties in a clean wc
+  sbox.simple_rm('A', 'iota')
+  sbox.simple_propset('keep', 'keep-value', '')
+  sbox.simple_propset('del', 'del-value', '')
+  sbox.simple_commit()
+
+  # r3 -  Perform some changes that will be undone later
+  sbox.simple_mkdir('ADDED-dir')
+  sbox.simple_add_text('The added file', 'added-file')
+  sbox.simple_propset('prop-added', 'value', '')
+  sbox.simple_commit('')
+  sbox.simple_update('')
+
+  r3_disk = svntest.wc.State('', {
+    'added-file'        : Item(contents="The added file"),
+    '.'                 : Item(props={'prop-added':'value', 'del':'del-value', 'keep':'keep-value'}),
+    'ADDED-dir'         : Item(),
+  })
+
+  r3_status = svntest.wc.State(wc_dir, {
+    ''                  : Item(status='  ', wc_rev='3'),
+    'ADDED-dir'         : Item(status='  ', wc_rev='3'),
+    'added-file'        : Item(status='  ', wc_rev='3'),
+  })
+
+  # Verify assumptions for later check
+  svntest.actions.run_and_verify_status(wc_dir, r3_status)
+  svntest.actions.verify_disk(wc_dir, r3_disk, check_props = True)
+
+
+  # r4 - And we undo r3
+  sbox.simple_rm('ADDED-dir', 'added-file')
+  sbox.simple_propdel('prop-added', '')
+  sbox.simple_commit('')
+
+  # r5 - Create some alternate changes
+  sbox.simple_mkdir('NOT-ADDED-dir')
+  sbox.simple_add_text('The not added file', 'not-added-file')
+  sbox.simple_propset('prop-not-added', 'value', '')
+  sbox.simple_commit('')
+
+  # Nothing to do to bring the wc to single revision
+  expected_output = svntest.wc.State(wc_dir, {
+  })
+
+  r5_disk = svntest.wc.State('', {
+    ''                  : Item(props={'prop-not-added':'value',
+                                      'del':'del-value',
+                                      'keep':'keep-value'}),
+    'NOT-ADDED-dir'     : Item(),
+    'not-added-file'    : Item(contents="The not added file"),
+  })
+
+  expected_status = svntest.wc.State(wc_dir, {
+    ''                  : Item(status='  ', wc_rev='5'),
+    'NOT-ADDED-dir'     : Item(status='  ', wc_rev='5'),
+    'not-added-file'    : Item(status='  ', wc_rev='5'),
+  })
+
+
+  svntest.actions.run_and_verify_update(wc_dir,
+                                        expected_output,
+                                        r5_disk,
+                                        expected_status,
+                                        None, None, None, None, None,
+                                        True)
+
+  # And now we mark the directory incomplete, as if the update had failed
+  # half-way through an update to r3
+  svntest.actions.set_incomplete(wc_dir, 3)
+
+  # Tweak status to verify us breaking the wc
+  expected_status.tweak('', status='! ', wc_rev=3)
+  svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
+  # But the working copy is still 100% at r5
+  svntest.actions.verify_disk(wc_dir, r5_disk, check_props = True)
+
+  # And expect update to do the right thing even though r3 is already encoded
+  # in the parent. This includes fixing the list of children (reported to the
+  # server, which will report adds and deletes) and fixing the property list
+  # (received all; client should delete properties that shouldn't be here)
+
+  expected_output = svntest.wc.State(wc_dir, {
+    ''                  : Item(status=' U'),
+    'not-added-file'    : Item(status='D '),
+    'ADDED-dir'         : Item(status='A '),
+    'added-file'        : Item(status='A '),
+    'NOT-ADDED-dir'     : Item(status='D '),
+  })
+
+  svntest.actions.run_and_verify_update(wc_dir,
+                                        expected_output,
+                                        r3_disk,
+                                        r3_status,
+                                        None, None, None, None, None,
+                                        True,
+                                        wc_dir, '-r', 3)
+
+
 
 #######################################################################
 # Run the tests
@@ -5974,6 +6435,11 @@ test_list = [ None,
               update_nested_move_text_mod,
               update_with_parents_and_exclude,
               update_edit_delete_obstruction,
+              update_deleted,
+              break_moved_dir_edited_leaf_del,
+              break_moved_replaced_dir,
+              update_removes_switched,
+              incomplete_overcomplete,
              ]
 
 if __name__ == '__main__':

Modified: subversion/branches/ev2-export/subversion/tests/cmdline/upgrade_tests.py
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/subversion/tests/cmdline/upgrade_tests.py?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/subversion/tests/cmdline/upgrade_tests.py (original)
+++ subversion/branches/ev2-export/subversion/tests/cmdline/upgrade_tests.py Mon Jan 21 23:37:01 2013
@@ -1206,6 +1206,7 @@ def upgrade_file_externals(sbox):
 
 
 @Issue(4035)
+@XFail()
 def upgrade_missing_replaced(sbox):
   "upgrade with missing replaced dir"
 
@@ -1220,7 +1221,7 @@ def upgrade_missing_replaced(sbox):
                                      sbox.wc_dir)
 
   expected_output = svntest.wc.State(sbox.wc_dir, {
-      'A/B/E'         : Item(status='  ', treeconflict='C'),
+      'A/B/E'         : Item(status='  ', treeconflict='C', prev_verb='Restored'),
       'A/B/E/alpha'   : Item(status='  ', treeconflict='A'),
       'A/B/E/beta'    : Item(status='  ', treeconflict='A'),
       })

Modified: subversion/branches/ev2-export/subversion/tests/libsvn_wc/op-depth-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/subversion/tests/libsvn_wc/op-depth-test.c?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/subversion/tests/libsvn_wc/op-depth-test.c (original)
+++ subversion/branches/ev2-export/subversion/tests/libsvn_wc/op-depth-test.c Mon Jan 21 23:37:01 2013
@@ -1107,7 +1107,9 @@ base_dir_insert_remove(svn_test__sandbox
   SVN_ERR(check_db_rows(b, "", after));
 
   SVN_ERR(svn_wc__db_base_remove(b->wc_ctx->db, dir_abspath,
-                                 FALSE, SVN_INVALID_REVNUM,
+                                 FALSE /* keep_as_Working */,
+                                 FALSE /* queue_deletes */,
+                                 SVN_INVALID_REVNUM,
                                  NULL, NULL, b->pool));
   SVN_ERR(svn_wc__wq_run(b->wc_ctx->db, dir_abspath,
                          NULL, NULL, b->pool));
@@ -5622,6 +5624,125 @@ move_update_conflicts(const svn_test_opt
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+move_update_delete_mods(const svn_test_opts_t *opts, apr_pool_t *pool)
+{
+  svn_test__sandbox_t b;
+
+  SVN_ERR(svn_test__sandbox_create(&b, "move_update_delete_mods", opts, pool));
+
+  SVN_ERR(sbox_wc_mkdir(&b, "A"));
+  SVN_ERR(sbox_wc_mkdir(&b, "A/B"));
+  SVN_ERR(sbox_wc_mkdir(&b, "A/B/C"));
+  SVN_ERR(sbox_wc_mkdir(&b, "A/B/D"));
+  sbox_file_write(&b, "A/B/C/f", "r1 content\n");
+  SVN_ERR(sbox_wc_add(&b, "A/B/C/f"));
+  SVN_ERR(sbox_wc_commit(&b, ""));
+  SVN_ERR(sbox_wc_delete(&b, "A/B/C"));
+  SVN_ERR(sbox_wc_delete(&b, "A/B/D"));
+  SVN_ERR(sbox_wc_commit(&b, ""));
+  SVN_ERR(sbox_wc_update(&b, "", 1));
+
+  SVN_ERR(sbox_wc_move(&b, "A/B", "B2"));
+  sbox_file_write(&b, "B2/C/f", "modified content\n");
+  SVN_ERR(sbox_wc_delete(&b, "B2/D"));
+  {
+    nodes_row_t nodes[] = {
+      {0, "",        "normal",       1, ""},
+      {0, "A",       "normal",       1, "A"},
+      {0, "A/B",     "normal",       1, "A/B"},
+      {0, "A/B/C",   "normal",       1, "A/B/C"},
+      {0, "A/B/C/f", "normal",       1, "A/B/C/f"},
+      {0, "A/B/D",   "normal",       1, "A/B/D"},
+      {2, "A/B",     "base-deleted",  NO_COPY_FROM, "B2"},
+      {2, "A/B/C",   "base-deleted",  NO_COPY_FROM},
+      {2, "A/B/C/f", "base-deleted",  NO_COPY_FROM},
+      {2, "A/B/D",   "base-deleted",  NO_COPY_FROM},
+      {1, "B2",      "normal",       1, "A/B", MOVED_HERE},
+      {1, "B2/C",    "normal",       1, "A/B/C", MOVED_HERE},
+      {1, "B2/C/f",  "normal",       1, "A/B/C/f", MOVED_HERE},
+      {1, "B2/D",    "normal",       1, "A/B/D", MOVED_HERE},
+      {2, "B2/D",    "base-deleted", NO_COPY_FROM},
+      {0}
+    };
+    SVN_ERR(check_db_rows(&b, "", nodes));
+  }
+
+  SVN_ERR(sbox_wc_update(&b, "A", 2));
+  SVN_ERR(sbox_wc_resolve(&b, "A/B", svn_wc_conflict_choose_mine_conflict));
+  {
+    nodes_row_t nodes[] = {
+      {0, "",        "normal",       1, ""},
+      {0, "A",       "normal",       2, "A"},
+      {0, "A/B",     "normal",       2, "A/B"},
+      {2, "A/B",     "base-deleted",  NO_COPY_FROM, "B2"},
+      {1, "B2",      "normal",       2, "A/B", MOVED_HERE},
+      {2, "B2/C",    "normal",       1, "A/B/C"},
+      {2, "B2/C/f",  "normal",       1, "A/B/C/f"},
+      {0}
+    };
+    SVN_ERR(check_db_rows(&b, "", nodes));
+  }
+
+  SVN_ERR(check_tree_conflict_repos_path(&b, "B2/C", "A/B/C", "A/B/C"));
+  SVN_ERR(check_tree_conflict_repos_path(&b, "B2/D", "A/B/D", "A/B/D"));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+nested_moves2(const svn_test_opts_t *opts, apr_pool_t *pool)
+{
+  svn_test__sandbox_t b;
+
+  SVN_ERR(svn_test__sandbox_create(&b, "nested_moves2", opts, pool));
+
+  SVN_ERR(sbox_wc_mkdir(&b, "A"));
+  SVN_ERR(sbox_wc_mkdir(&b, "A/A"));
+  SVN_ERR(sbox_wc_mkdir(&b, "A/A/A"));
+  SVN_ERR(sbox_wc_mkdir(&b, "A/A/A/A"));
+  SVN_ERR(sbox_wc_mkdir(&b, "A/A/A/A/A"));
+  SVN_ERR(sbox_wc_mkdir(&b, "A/A/A/A/A/A"));
+  SVN_ERR(sbox_wc_commit(&b, ""));
+
+  SVN_ERR(sbox_wc_move(&b, "A/A/A/A/A/A", "C"));
+  SVN_ERR(sbox_wc_move(&b, "A/A/A/A", "D"));
+  SVN_ERR(sbox_wc_move(&b, "A/A", "E"));
+
+  {
+    nodes_row_t nodes[] = {
+      {0, "",            "normal",       0, ""},
+      {0, "A",           "normal",       1, "A"},
+      {0, "A/A",         "normal",       1, "A/A"},
+      {0, "A/A/A",       "normal",       1, "A/A/A"},
+      {0, "A/A/A/A",     "normal",       1, "A/A/A/A"},
+      {0, "A/A/A/A/A",   "normal",       1, "A/A/A/A/A"},
+      {0, "A/A/A/A/A/A", "normal",       1, "A/A/A/A/A/A"},
+      {2, "A/A",         "base-deleted", NO_COPY_FROM, "E"},
+      {2, "A/A/A",       "base-deleted", NO_COPY_FROM},
+      {2, "A/A/A/A",     "base-deleted", NO_COPY_FROM},
+      {2, "A/A/A/A/A",   "base-deleted", NO_COPY_FROM},
+      {2, "A/A/A/A/A/A", "base-deleted", NO_COPY_FROM},
+      {1, "E",           "normal",       1, "A/A", MOVED_HERE},
+      {1, "E/A",         "normal",       1, "A/A/A", MOVED_HERE},
+      {1, "E/A/A",       "normal",       1, "A/A/A/A", MOVED_HERE},
+      {1, "E/A/A/A",     "normal",       1, "A/A/A/A/A", MOVED_HERE},
+      {1, "E/A/A/A/A",   "normal",       1, "A/A/A/A/A/A", MOVED_HERE},
+      {3, "E/A/A",       "base-deleted", NO_COPY_FROM, "D"},
+      {3, "E/A/A/A",     "base-deleted", NO_COPY_FROM},
+      {3, "E/A/A/A/A",   "base-deleted", NO_COPY_FROM},
+      {1, "D",           "normal",       1, "A/A/A/A", MOVED_HERE},
+      {1, "D/A",         "normal",       1, "A/A/A/A/A", MOVED_HERE},
+      {1, "D/A/A",       "normal",       1, "A/A/A/A/A/A", MOVED_HERE},
+      {3, "D/A/A",       "base-deleted", NO_COPY_FROM, "C"},
+      {1, "C",           "normal",       1, "A/A/A/A/A/A", MOVED_HERE},
+      {0}
+    };
+    SVN_ERR(check_db_rows(&b, "", nodes));
+  }
+  return SVN_NO_ERROR;
+}
+
 
 /* ---------------------------------------------------------------------- */
 /* The list of test functions */
@@ -5729,5 +5850,9 @@ struct svn_test_descriptor_t test_funcs[
                        "nested_move_update2"),
     SVN_TEST_OPTS_PASS(move_update_conflicts,
                        "move_update_conflicts"),
+    SVN_TEST_OPTS_PASS(move_update_delete_mods,
+                       "move_update_delete_mods"),
+    SVN_TEST_OPTS_PASS(nested_moves2,
+                       "nested_moves2"),
     SVN_TEST_NULL
   };

Modified: subversion/branches/ev2-export/tools/client-side/svn-bench/svn-bench.c
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/client-side/svn-bench/svn-bench.c?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/client-side/svn-bench/svn-bench.c (original)
+++ subversion/branches/ev2-export/tools/client-side/svn-bench/svn-bench.c Mon Jan 21 23:37:01 2013
@@ -684,7 +684,7 @@ sub_main(int argc, const char *argv[], a
                                                   first_arg, pool));
               svn_error_clear
                 (svn_cmdline_fprintf(stderr, pool,
-                                     _("Unknown command: '%s'\n"),
+                                     _("Unknown subcommand: '%s'\n"),
                                      first_arg_utf8));
               svn_cl__help(NULL, NULL, pool);
               return EXIT_FAILURE;

Modified: subversion/branches/ev2-export/tools/dev/benchmarks/suite1/benchmark.py
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/dev/benchmarks/suite1/benchmark.py?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/dev/benchmarks/suite1/benchmark.py (original)
+++ subversion/branches/ev2-export/tools/dev/benchmarks/suite1/benchmark.py Mon Jan 21 23:37:01 2013
@@ -1306,4 +1306,4 @@ if __name__ == '__main__':
       usage()
 
   else:
-    usage('Unknown command argument: %s' % cmd)
+    usage('Unknown subcommand argument: %s' % cmd)

Modified: subversion/branches/ev2-export/tools/examples/svnshell.rb
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/examples/svnshell.rb?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/examples/svnshell.rb (original)
+++ subversion/branches/ev2-export/tools/examples/svnshell.rb Mon Jan 21 23:37:01 2013
@@ -125,7 +125,7 @@ class SvnShell
         puts("Invalid argument for #{cmd}: #{args.join(' ')}")
       end
     else
-      puts("Unknown command: #{cmd}")
+      puts("Unknown subcommand: #{cmd}")
       puts("Try one of these commands: ", WORDS.sort.join(" "))
     end
   end

Modified: subversion/branches/ev2-export/tools/server-side/fsfs-stats.c
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/server-side/fsfs-stats.c?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/server-side/fsfs-stats.c (original)
+++ subversion/branches/ev2-export/tools/server-side/fsfs-stats.c Mon Jan 21 23:37:01 2013
@@ -160,6 +160,37 @@ typedef struct window_cache_key_t
   apr_size_t offset;
 } window_cache_key_t;
 
+/* Description of one large representation.  It's content will be reused /
+ * overwritten when it gets replaced by an even larger representation.
+ */
+typedef struct large_change_info_t
+{
+  /* size of the (deltified) representation */
+  apr_size_t size;
+
+  /* revision of the representation */
+  svn_revnum_t revision;
+
+  /* node path. "" for unused instances */
+  svn_stringbuf_t *path;
+} large_change_info_t;
+
+/* Container for the largest representations found so far.  The capacity
+ * is fixed and entries will be inserted by reusing the last one and
+ * reshuffling the entry pointers.
+ */
+typedef struct largest_changes_t
+{
+  /* number of entries allocated in CHANGES */
+  apr_size_t count;
+
+  /* size of the smallest change */
+  apr_size_t min_size;
+
+  /* changes kept in this struct */
+  large_change_info_t **changes;
+} largest_changes_t;
+
 /* Root data structure containing all information about a given repository.
  */
 typedef struct fs_fs_t
@@ -191,6 +222,9 @@ typedef struct fs_fs_t
 
   /* undeltified txdelta window cache */
   svn_cache__t *window_cache;
+
+  /* track the biggest contributors to repo size */
+  largest_changes_t *largest_changes;
 } fs_fs_t;
 
 /* Return the rev pack folder for revision REV in FS.
@@ -327,6 +361,70 @@ set_cached_window(fs_fs_t *fs,
                                         pool));
 }
 
+/* Initialize the LARGEST_CHANGES member in FS with a capacity of COUNT
+ * entries.  Use POOL for allocations.
+ */
+static void
+initialize_largest_changes(fs_fs_t *fs,
+                           apr_size_t count,
+                           apr_pool_t *pool)
+{
+  apr_size_t i;
+  
+  fs->largest_changes = apr_pcalloc(pool, sizeof(*fs->largest_changes));
+  fs->largest_changes->count = count;
+  fs->largest_changes->min_size = 1;
+  fs->largest_changes->changes
+    = apr_palloc(pool, count * sizeof(*fs->largest_changes->changes));
+
+  /* allocate *all* entries before the path stringbufs.  This increases
+   * cache locality and enhances performance significantly. */
+  for (i = 0; i < count; ++i)
+    fs->largest_changes->changes[i]
+      = apr_palloc(pool, sizeof(**fs->largest_changes->changes));
+
+  /* now initialize them and allocate the stringbufs */
+  for (i = 0; i < count; ++i)
+    {
+      fs->largest_changes->changes[i]->size = 0;
+      fs->largest_changes->changes[i]->revision = SVN_INVALID_REVNUM;
+      fs->largest_changes->changes[i]->path
+        = svn_stringbuf_create_ensure(1024, pool);
+    }
+}
+
+/* Update data aggregators in FS with this representation of on-disk SIZE
+ * for PATH in REVSION.
+ */
+static void
+add_change(fs_fs_t *fs,
+           apr_size_t size,
+           svn_revnum_t revision,
+           const char *path)
+{
+  if (size >= fs->largest_changes->min_size)
+    {
+      apr_size_t i;
+      large_change_info_t *info
+        = fs->largest_changes->changes[fs->largest_changes->count - 1];
+      info->size = size;
+      info->revision = revision;
+      svn_stringbuf_set(info->path, path);
+
+      /* linear insertion but not too bad since count is low and insertions
+       * near the end are more likely than close to front */
+      for (i = fs->largest_changes->count - 1; i > 0; --i)
+        if (fs->largest_changes->changes[i-1]->size >= size)
+          break;
+        else
+          fs->largest_changes->changes[i] = fs->largest_changes->changes[i-1];
+
+      fs->largest_changes->changes[i] = info;
+      fs->largest_changes->min_size
+        = fs->largest_changes->changes[fs->largest_changes->count-1]->size;
+    }
+}
+
 /* Given rev pack PATH in FS, read the manifest file and return the offsets
  * in *MANIFEST. Use POOL for allocations.
  */
@@ -1036,6 +1134,7 @@ read_noderev(fs_fs_t *fs,
   representation_t *props = NULL;
   apr_size_t start_offset = offset;
   svn_boolean_t is_dir = FALSE;
+  const char *path = "???";
 
   scratch_pool = svn_pool_create(scratch_pool);
 
@@ -1093,8 +1192,14 @@ read_noderev(fs_fs_t *fs,
           if (++props->ref_count == 1)
             props->kind = is_dir ? dir_property_rep : file_property_rep;
         }
+      else if (key_matches(&key, "cpath"))
+        path = value.data;
     }
 
+  /* record largest changes */
+  if (text && text->ref_count == 1)
+    add_change(fs, text->size, text->revision, path);
+  
   /* if this is a directory and has not been processed, yet, read and
    * process it recursively */
   if (is_dir && text && text->ref_count == 1)
@@ -1303,6 +1408,7 @@ read_revisions(fs_fs_t **fs,
                                     (*fs)->max_revision + 1 - (*fs)->start_revision,
                                     sizeof(revision_info_t *));
   (*fs)->null_base = apr_pcalloc(pool, sizeof(*(*fs)->null_base));
+  initialize_largest_changes(*fs, 64, pool);
 
   SVN_ERR(svn_cache__create_membuffer_cache(&(*fs)->window_cache,
                                             svn_cache__get_global_membuffer_cache(),
@@ -1425,6 +1531,20 @@ print_rep_stats(representation_stats_t *
          svn__i64toa_sep(stats->references - stats->total.count, ',', pool));
 }
 
+/* Print the (used) contents of CHANGES.  Use POOL for allocations.
+ */
+static void
+print_largest_reps(largest_changes_t *changes,
+                   apr_pool_t *pool)
+{
+  apr_size_t i;
+  for (i = 0; i < changes->count && changes->changes[i]->size; ++i)
+    printf(_("%12s r%-8ld %s\n"),
+           svn__i64toa_sep(changes->changes[i]->size, ',', pool),
+           changes->changes[i]->revision,
+           changes->changes[i]->path->data);
+}
+
 /* Post-process stats for FS and print them to the console.
  * Use POOL for allocations.
  */
@@ -1555,6 +1675,8 @@ print_stats(fs_fs_t *fs,
   print_rep_stats(&dir_prop_rep_stats, pool);
   printf("\nFile property representation statistics:\n");
   print_rep_stats(&file_prop_rep_stats, pool);
+  printf("\nLargest representations:\n");
+  print_largest_reps(fs->largest_changes, pool);
 }
 
 /* Write tool usage info text to OSTREAM using PROGNAME as a prefix and

Modified: subversion/branches/ev2-export/tools/server-side/svnauthz.c
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/server-side/svnauthz.c?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/server-side/svnauthz.c (original)
+++ subversion/branches/ev2-export/tools/server-side/svnauthz.c Mon Jan 21 23:37:01 2013
@@ -141,6 +141,7 @@ static const svn_opt_subcommand_desc2_t 
     "    3   when --is argument doesn't match\n"
     ),
    {'t', svnauthz__username, svnauthz__path, svnauthz__repos, svnauthz__is} },
+  { NULL, NULL, {0}, NULL, {0} }
 };
 
 static svn_error_t *
@@ -492,9 +493,10 @@ sub_main(int argc, const char *argv[], a
               os->ind++;
               SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
                                                   first_arg, pool));
-              svn_error_clear(svn_cmdline_fprintf(stderr, pool,
-                                                  ("Unknown command: '%s'\n"),
-                                                  first_arg_utf8));
+              svn_error_clear(
+                svn_cmdline_fprintf(stderr, pool,
+                                    ("Unknown subcommand: '%s'\n"),
+                                    first_arg_utf8));
               SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
               return EXIT_FAILURE;
             }

Modified: subversion/branches/ev2-export/tools/server-side/svnpubsub/README.txt
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/server-side/svnpubsub/README.txt?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/server-side/svnpubsub/README.txt (original)
+++ subversion/branches/ev2-export/tools/server-side/svnpubsub/README.txt Mon Jan 21 23:37:01 2013
@@ -1,21 +1,3 @@
-### write a README
-
-
-TODO:
-- bulk update at startup time to avoid backlog warnings
-- switch to host:port format in config file
-- fold BDEC into Daemon
-- fold WorkingCopy._get_match() into __init__
-- remove wc_ready(). assume all WorkingCopy instances are usable.
-  place the instances into .watch at creation. the .update_applies()
-  just returns if the wc is disabled (eg. could not find wc dir)
-- figure out way to avoid the ASF-specific PRODUCTION_RE_FILTER
-  (a base path exclusion list should work for the ASF)
-- add support for SIGHUP to reread the config and reinitialize working copies
-- joes will write documentation for svnpubsub as these items become fulfilled
-- make LOGLEVEL configurable
-
-
 Installation instructions:
 
 1. Set up an svnpubsub service.
@@ -39,4 +21,4 @@ Installation instructions:
 3. Set up svnpubsub clients.
 
    (eg svnwcsub.py, svnpubsub/client.py,
-       'curl -i http://${hostname}:2069/commits/json')
+       'curl -sN http://${hostname}:2069/commits')

Modified: subversion/branches/ev2-export/tools/server-side/svnpubsub/commit-hook.py
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/server-side/svnpubsub/commit-hook.py?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/server-side/svnpubsub/commit-hook.py (original)
+++ subversion/branches/ev2-export/tools/server-side/svnpubsub/commit-hook.py Mon Jan 21 23:37:01 2013
@@ -48,17 +48,6 @@ def svncmd_info(repo, revision):
             'date': data[1].strip(),
             'log': "\n".join(data[3:]).strip()}
 
-def svncmd_dirs(repo, revision):
-    cmd = "%s dirs-changed  -r %s %s" % (SVNLOOK, revision, repo)
-    p = svncmd(cmd)
-    dirs = []
-    while True:
-        line = p.stdout.readline()
-        if not line:
-            break
-        dirs.append(line.strip())
-    return dirs
-
 def svncmd_changed(repo, revision):
     cmd = "%s changed -r %s %s" % (SVNLOOK, revision, repo)
     p = svncmd(cmd)
@@ -74,7 +63,7 @@ def svncmd_changed(repo, revision):
 
 def do_put(body):
     opener = urllib2.build_opener(urllib2.HTTPHandler)
-    request = urllib2.Request("http://%s:%d/dirs-changed" %(HOST, PORT), data=body)
+    request = urllib2.Request("http://%s:%d/commits" %(HOST, PORT), data=body)
     request.add_header('Content-Type', 'application/json')
     request.get_method = lambda: 'PUT'
     url = opener.open(request)
@@ -83,18 +72,17 @@ def do_put(body):
 def main(repo, revision):
     revision = revision.lstrip('r')
     i = svncmd_info(repo, revision)
-    data = {'revision': int(revision),
-            'dirs_changed': [],
+    data = {'type': 'svn',
+            'format': 1,
+            'id': int(revision),
             'changed': {},
-            'repos': svncmd_uuid(repo),
-            'author': i['author'],
+            'repository': svncmd_uuid(repo),
+            'committer': i['author'],
             'log': i['log'],
             'date': i['date'],
             }
-    data['dirs_changed'].extend(svncmd_dirs(repo, revision))
     data['changed'].update(svncmd_changed(repo, revision))
     body = json.dumps(data)
-    #print body
     do_put(body)
 
 if __name__ == "__main__":

Modified: subversion/branches/ev2-export/tools/server-side/svnpubsub/daemonize.py
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/server-side/svnpubsub/daemonize.py?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/server-side/svnpubsub/daemonize.py (original)
+++ subversion/branches/ev2-export/tools/server-side/svnpubsub/daemonize.py Mon Jan 21 23:37:01 2013
@@ -50,11 +50,11 @@ class Daemon(object):
   def daemonize_exit(self):
     try:
       result = self.daemonize()
-    except (ChildFailed, DaemonFailed), e:
+    except (ChildFailed, DaemonFailed) as e:
       # duplicate the exit code
       sys.exit(e.code)
     except (ChildTerminatedAbnormally, ChildForkFailed,
-            DaemonTerminatedAbnormally, DaemonForkFailed), e:
+            DaemonTerminatedAbnormally, DaemonForkFailed) as e:
       sys.stderr.write('ERROR: %s\n' % e)
       sys.exit(1)
     except ChildResumedIncorrectly:
@@ -74,7 +74,7 @@ class Daemon(object):
     # fork off a child that can detach itself from this process.
     try:
       pid = os.fork()
-    except OSError, e:
+    except OSError as e:
       raise ChildForkFailed(e.errno, e.strerror)
 
     if pid > 0:
@@ -113,7 +113,7 @@ class Daemon(object):
     # perform the second fork
     try:
       pid = os.fork()
-    except OSError, e:
+    except OSError as e:
       raise DaemonForkFailed(e.errno, e.strerror)
 
     if pid > 0:

Modified: subversion/branches/ev2-export/tools/server-side/svnpubsub/irkerbridge.py
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/server-side/svnpubsub/irkerbridge.py?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/server-side/svnpubsub/irkerbridge.py (original)
+++ subversion/branches/ev2-export/tools/server-side/svnpubsub/irkerbridge.py Mon Jan 21 23:37:01 2013
@@ -30,8 +30,6 @@
 #   Space separated list of URLs to streams.
 #   This option should only be in the DEFAULT section, is ignored in
 #   all other sections.
-#   NOTE: At current svnpubsub.client only accepts hostname and port
-#         combos so the path is ignored and /commits/xml is used.
 # irker=hostname:port 
 #   The hostname/port combination of the irker daemon.  If port is
 #   omitted it defaults to 6659.  Irker is connected to over UDP.
@@ -46,7 +44,7 @@
 # template=string
 #   A string to use to format the output.  The string is a Python 
 #   string Template.  The following variables are available:
-#   $author, $rev, $date, $uuid, $log, $log, $log_firstline,
+#   $committer, $id, $date, $repository, $log, $log_firstline,
 #   $log_firstparagraph, $dirs_changed, $dirs_count, $dirs_count_s,
 #   $subdirs_count, $subdirs_count_s, $dirs_root
 #   Most of them should be self explanatory.  $dirs_count is the number of
@@ -114,9 +112,9 @@ class Daemon(daemonize.Daemon):
   def run(self):
     print 'irkerbridge started, pid=%d' % (os.getpid())
 
-    mc = svnpubsub.client.MultiClient(self.bdec.hostports,
-        self.bdec.commit,
-        self.bdec.event)
+    mc = svnpubsub.client.MultiClient(self.bdec.urls,
+                                      self.bdec.commit,
+                                      self.bdec.event)
     mc.run_forever()
 
 
@@ -124,12 +122,9 @@ class BigDoEverythingClass(object):
   def __init__(self, config, options):
     self.config = config
     self.options = options
-    self.hostports = []
-    for url in config.get_value('streams').split():
-      parsed = urlparse.urlparse(url.strip())
-      self.hostports.append((parsed.hostname, parsed.port or 80))
+    self.urls = config.get_value('streams').split()
 
-  def locate_matching_configs(self, rev):
+  def locate_matching_configs(self, commit):
     result = [ ]
     for section in self.config.sections():
       match = self.config.get(section, "match").split('/', 1)
@@ -137,40 +132,62 @@ class BigDoEverythingClass(object):
         # No slash so assume all paths
         match.append('*')
       match_uuid, match_path = match
-      if rev.uuid == match_uuid or match_uuid == "*":
-        for path in rev.dirs_changed:
+      if commit.repository == match_uuid or match_uuid == "*":
+        for path in commit.changed:
           if fnmatch.fnmatch(path, match_path):
             result.append(section)
             break
     return result
 
-  def fill_in_extra_args(self, rev):
+  def _generate_dirs_changed(self, commit):
+    if hasattr(commit, 'dirs_changed') or not hasattr(commit, 'changed'):
+      return
+
+    dirs_changed = set() 
+    for p in commit.changed:
+      if p[-1] == '/' and commit.changed[p]['flags'][1] == 'U':
+        # directory with property changes add the directory itself.
+        dirs_changed.add(p)
+      else:
+        # everything else add the parent of the path
+        # directories have a trailing slash so if it's present remove
+        # it before finding the parent.  The result will be a directory
+        # so it needs a trailing slash
+        dirs_changed.add(posixpath.dirname(p.rstrip('/')) + '/')
+
+    commit.dirs_changed = dirs_changed
+    return
+
+  def fill_in_extra_args(self, commit):
     # Set any empty members to the string "<null>"
-    v = vars(rev)
+    v = vars(commit)
     for k in v.keys():
       if not v[k]:
         v[k] = '<null>'
-       
-    # Add entries to the rev object that are useful for
+
+    self._generate_dirs_changed(commit)
+    # Add entries to the commit object that are useful for
     # formatting.
-    rev.log_firstline = rev.log.split("\n",1)[0]
-    rev.log_firstparagraph = re.split("\r?\n\r?\n",rev.log,1)[0]
-    rev.log_firstparagraph = re.sub("\r?\n"," ",rev.log_firstparagraph)
-    if rev.dirs_changed:
-      rev.dirs_root = posixpath.commonprefix(rev.dirs_changed)
-      rev.dirs_count = len(rev.dirs_changed)
-      if rev.dirs_count > 1:
-        rev.dirs_count_s = " (%d dirs)" %(rev.dirs_count)
+    commit.log_firstline = commit.log.split("\n",1)[0]
+    commit.log_firstparagraph = re.split("\r?\n\r?\n",commit.log,1)[0]
+    commit.log_firstparagraph = re.sub("\r?\n"," ",commit.log_firstparagraph)
+    if commit.dirs_changed:
+      commit.dirs_root = posixpath.commonprefix(commit.dirs_changed)
+      if commit.dirs_root == '':
+        commit.dirs_root = '/'
+      commit.dirs_count = len(commit.dirs_changed)
+      if commit.dirs_count > 1:
+        commit.dirs_count_s = " (%d dirs)" %(commit.dirs_count)
       else:
-        rev.dirs_count_s = ""
+        commit.dirs_count_s = ""
 
-      rev.subdirs_count = rev.dirs_count
-      if rev.dirs_root in rev.dirs_changed:
-        rev.subdirs_count -= 1
-      if rev.subdirs_count > 1:
-        rev.subdirs_count_s = " + %d subdirs" % (rev.subdirs_count)
+      commit.subdirs_count = commit.dirs_count
+      if commit.dirs_root in commit.dirs_changed:
+        commit.subdirs_count -= 1
+      if commit.subdirs_count >= 1:
+        commit.subdirs_count_s = " + %d subdirs" % (commit.subdirs_count)
       else:
-        rev.subdirs_count_s = ""
+        commit.subdirs_count_s = ""
 
   def _send(self, irker, msg):
     sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
@@ -193,22 +210,22 @@ class BigDoEverythingClass(object):
         msg = {'to': to, 'privmsg': ''}
         self._send(irker, msg)
 
-  def commit(self, host, port, rev):
+  def commit(self, url, commit):
     if self.options.verbose:
-      print "RECV: from %s:%s" % (host, port)
-      print json.dumps(vars(rev), indent=2)
+      print "RECV: from %s" % url
+      print json.dumps(vars(commit), indent=2)
 
     try:
-      config_sections = self.locate_matching_configs(rev)
+      config_sections = self.locate_matching_configs(commit)
       if len(config_sections) > 0:
-        self.fill_in_extra_args(rev)
+        self.fill_in_extra_args(commit)
         for section in config_sections:
           irker = self.config.get(section, "irker")
           to_list = self.config.get(section, "to").split()
           template = self.config.get(section, "template")
           if not irker or not to_list or not template:
             continue
-          privmsg = Template(template).safe_substitute(vars(rev))
+          privmsg = Template(template).safe_substitute(vars(commit))
           if len(privmsg) > MAX_PRIVMSG:
             privmsg = privmsg[:MAX_PRIVMSG-3] + '...'
           for to in to_list:
@@ -221,9 +238,9 @@ class BigDoEverythingClass(object):
       sys.stdout.flush()
       raise
 
-  def event(self, host, port, event_name):
+  def event(self, url, event_name, event_arg):
     if self.options.verbose or event_name != "ping":
-      print 'EVENT: %s from %s:%s' % (event_name, host, port)
+      print 'EVENT: %s from %s' % (event_name, url)
       sys.stdout.flush()
 
 
@@ -279,6 +296,7 @@ def main(args):
     parser.error('CONFIG_FILE is requried')
   config_file = os.path.abspath(extra[0])
 
+  logfile, pidfile = None, None
   if options.daemon:
     if options.logfile:
       logfile = os.path.abspath(options.logfile)

Modified: subversion/branches/ev2-export/tools/server-side/svnpubsub/svnpubsub/client.py
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/server-side/svnpubsub/svnpubsub/client.py?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/server-side/svnpubsub/svnpubsub/client.py (original)
+++ subversion/branches/ev2-export/tools/server-side/svnpubsub/svnpubsub/client.py Mon Jan 21 23:37:01 2013
@@ -38,7 +38,11 @@ import asynchat
 import socket
 import functools
 import time
-import xml.sax
+import json
+try:
+  import urlparse
+except ImportError:
+  import urllib.parse as urlparse
 
 # How long the polling loop should wait for activity before returning.
 TIMEOUT = 30.0
@@ -53,24 +57,35 @@ RECONNECT_DELAY = 25.0
 STALE_DELAY = 60.0
 
 
+class SvnpubsubClientException(Exception):
+  pass
+
 class Client(asynchat.async_chat):
 
-  def __init__(self, host, port, commit_callback, event_callback):
+  def __init__(self, url, commit_callback, event_callback):
     asynchat.async_chat.__init__(self)
 
     self.last_activity = time.time()
+    self.ibuffer = []
 
-    self.host = host
-    self.port = port
-    self.event_callback = event_callback
+    self.url = url
+    parsed_url = urlparse.urlsplit(url)
+    if parsed_url.scheme != 'http':
+      raise ValueError("URL scheme must be http: '%s'" % url)
+    host = parsed_url.hostname
+    port = parsed_url.port
+    resource = parsed_url.path
+    if parsed_url.query:
+      resource += "?%s" % parsed_url.query
+    if parsed_url.fragment:
+      resource += "#%s" % parsed_url.fragment
 
-    handler = XMLStreamHandler(commit_callback, event_callback)
+    self.event_callback = event_callback
 
-    self.parser = xml.sax.make_parser(['xml.sax.expatreader'])
-    self.parser.setContentHandler(handler)
+    self.parser = JSONRecordHandler(commit_callback, event_callback)
 
-    # Wait for the end of headers. Then we start parsing XML.
-    self.set_terminator('\r\n\r\n')
+    # Wait for the end of headers. Then we start parsing JSON.
+    self.set_terminator(b'\r\n\r\n')
     self.skipping_headers = True
 
     self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -79,101 +94,66 @@ class Client(asynchat.async_chat):
     except:
       self.handle_error()
       return
-        
-    ### should we allow for repository restrictions?
-    self.push('GET /commits/xml HTTP/1.0\r\n\r\n')
+       
+    self.push(('GET %s HTTP/1.0\r\n\r\n' % resource).encode('ascii'))
 
   def handle_connect(self):
-    self.event_callback('connected')
+    self.event_callback('connected', None)
 
   def handle_close(self):
-    self.event_callback('closed')
+    self.event_callback('closed', None)
     self.close()
 
   def handle_error(self):
-    self.event_callback('error')
+    self.event_callback('error', None)
     self.close()
 
   def found_terminator(self):
-    self.skipping_headers = False
-
-    # From here on, collect everything. Never look for a terminator.
-    self.set_terminator(None)
+    if self.skipping_headers:
+      self.skipping_headers = False
+      # Each JSON record is terminated by a null character
+      self.set_terminator(b'\0')
+    else:
+      record = b"".join(self.ibuffer)
+      self.ibuffer = []
+      self.parser.feed(record.decode())
 
   def collect_incoming_data(self, data):
     # Remember the last time we saw activity
     self.last_activity = time.time()
 
     if not self.skipping_headers:
-      # Just shove this into the XML parser. As the elements are processed,
-      # we'll collect them into an appropriate structure, and then invoke
-      # the callback when we have fully received a commit.
-      self.parser.feed(data)
-
+      self.ibuffer.append(data) 
 
-class XMLStreamHandler(xml.sax.handler.ContentHandler):
 
+class JSONRecordHandler:
   def __init__(self, commit_callback, event_callback):
     self.commit_callback = commit_callback
     self.event_callback = event_callback
 
-    self.rev = None
-    self.chars = ''
-    self.parent = None
-    self.attrs = [ ] 
-
-  def startElement(self, name, attrs):
-    self.attrs = attrs
-    if name == 'commit':
-      self.rev = Revision(attrs['repository'], int(attrs['revision']))
-    elif name == "dirs_changed" or name == "changed":
-      self.parent = name
-    # No other elements to worry about.
-
-  def characters(self, data):
-    self.chars += data
-
-  def endElement(self, name):
-    if name == 'commit':
-      self.commit_callback(self.rev)
-      self.rev = None
-    elif name == 'stillalive':
-      self.event_callback('ping')
-    elif name == self.parent:
-      self.parent = None
-    elif self.chars and self.rev:
-      value = self.chars.strip()
-      if self.parent == 'dirs_changed' and name == 'path':
-        self.rev.dirs_changed.append(value.decode('unicode_escape'))
-      elif self.parent == 'changed' and name == 'path':
-        path = value.decode('unicode_escape')
-        self.rev.changed[path] = dict(p for p in self.attrs.items())
-      elif name == 'author':
-        self.rev.author = value.decode('unicode_escape')
-      elif name == 'date':
-        self.rev.date = value.decode('unicode_escape')
-      elif name == 'log':
-        self.rev.log = value.decode('unicode_escape')
-
-    # Toss out any accumulated characters for this element.
-    self.chars = ''
-    # Toss out the saved attributes for this element.
-    self.attrs = [ ]
-
-
-class Revision(object):
-  def __init__(self, uuid, rev):
-    self.uuid = uuid
-    self.rev = rev
-    self.dirs_changed = [ ]
-    self.changed = { } 
-    self.author = None
-    self.date = None
-    self.log = None
+  def feed(self, record):
+    obj = json.loads(record)
+    if 'svnpubsub' in obj:
+      actual_version = obj['svnpubsub'].get('version')
+      EXPECTED_VERSION = 1
+      if actual_version != EXPECTED_VERSION:
+        raise SvnpubsubClientException("Unknown svnpubsub format: %r != %d"
+                                       % (actual_format, expected_format))
+      self.event_callback('version', obj['svnpubsub']['version'])
+    elif 'commit' in obj:
+      commit = Commit(obj['commit'])
+      self.commit_callback(commit)
+    elif 'stillalive' in obj:
+      self.event_callback('ping', obj['stillalive'])
+
+
+class Commit(object):
+  def __init__(self, commit):
+    self.__dict__.update(commit)
 
 
 class MultiClient(object):
-  def __init__(self, hostports, commit_callback, event_callback):
+  def __init__(self, urls, commit_callback, event_callback):
     self.commit_callback = commit_callback
     self.event_callback = event_callback
 
@@ -181,33 +161,33 @@ class MultiClient(object):
     self.target_time = 0
     self.work_items = [ ]
 
-    for host, port in hostports:
-      self._add_channel(host, port)
+    for url in urls:
+      self._add_channel(url)
 
-  def _reconnect(self, host, port, event_name):
+  def _reconnect(self, url, event_name, event_arg):
     if event_name == 'closed' or event_name == 'error':
       # Stupid connection closed for some reason. Set up a reconnect. Note
       # that it should have been removed from asyncore.socket_map already.
-      self._reconnect_later(host, port)
+      self._reconnect_later(url)
 
     # Call the user's callback now.
-    self.event_callback(host, port, event_name)
+    self.event_callback(url, event_name, event_arg)
 
-  def _reconnect_later(self, host, port):
+  def _reconnect_later(self, url):
     # Set up a work item to reconnect in a little while.
-    self.work_items.append((host, port))
+    self.work_items.append(url)
 
     # Only set a target if one has not been set yet. Otherwise, we could
     # create a race condition of continually moving out towards the future
     if not self.target_time:
       self.target_time = time.time() + RECONNECT_DELAY
 
-  def _add_channel(self, host, port):
+  def _add_channel(self, url):
     # Simply instantiating the client will install it into the global map
     # for processing in the main event loop.
-    Client(host, port,
-           functools.partial(self.commit_callback, host, port),
-           functools.partial(self._reconnect, host, port))
+    Client(url,
+           functools.partial(self.commit_callback, url),
+           functools.partial(self._reconnect, url))
 
   def _check_stale(self):
     now = time.time()
@@ -215,12 +195,12 @@ class MultiClient(object):
       if client.last_activity + STALE_DELAY < now:
         # Whoops. No activity in a while. Signal this fact, Close the
         # Client, then have it reconnected later on.
-        self.event_callback(client.host, client.port, 'stale')
+        self.event_callback(client.url, 'stale', client.last_activity)
 
         # This should remove it from .socket_map.
         client.close()
 
-        self._reconnect_later(client.host, client.port)
+        self._reconnect_later(client.url)
 
   def _maybe_work(self):
     # If we haven't reach the targetted time, or have no work to do,
@@ -236,8 +216,8 @@ class MultiClient(object):
     work = self.work_items
     self.work_items = [ ]
 
-    for host, port in work:
-      self._add_channel(host, port)
+    for url in work:
+      self._add_channel(url)
 
   def run_forever(self):
     while True:

Modified: subversion/branches/ev2-export/tools/server-side/svnpubsub/svnpubsub/server.py
URL: http://svn.apache.org/viewvc/subversion/branches/ev2-export/tools/server-side/svnpubsub/svnpubsub/server.py?rev=1436688&r1=1436687&r2=1436688&view=diff
==============================================================================
--- subversion/branches/ev2-export/tools/server-side/svnpubsub/svnpubsub/server.py (original)
+++ subversion/branches/ev2-export/tools/server-side/svnpubsub/svnpubsub/server.py Mon Jan 21 23:37:01 2013
@@ -28,20 +28,24 @@
 # Currently supports both XML and JSON serialization.
 #
 # Example Sub clients:
-#   curl  -i http://127.0.0.1:2069/dirs-changed/xml
-#   curl  -i http://127.0.0.1:2069/dirs-changed/json
-#   curl  -i http://127.0.0.1:2069/commits/json
-#   curl  -i http://127.0.0.1:2069/commits/13f79535-47bb-0310-9956-ffa450edef68/json
-#   curl  -i http://127.0.0.1:2069/dirs-changed/13f79535-47bb-0310-9956-ffa450edef68/json
+#   curl -sN http://127.0.0.1:2069/commits
+#   curl -sN http://127.0.0.1:2069/commits/svn/*
+#   curl -sN http://127.0.0.1:2069/commits/svn
+#   curl -sN http://127.0.0.1:2069/commits/*/13f79535-47bb-0310-9956-ffa450edef68
+#   curl -sN http://127.0.0.1:2069/commits/svn/13f79535-47bb-0310-9956-ffa450edef68
 #
-#   URL is built into 3 parts:
-#       /${type}/${optional_repo_uuid}/${format}
+#   URL is built into 2 parts:
+#       /commits/${optional_type}/${optional_repository}
+#  
+#   If the type is included in the URL, you will only get commits of that type.
+#   The type can be * and then you will receive commits of any type.
 #
-#   If the repository UUID is included in the URl, you will only receive
-#   messages about that repository.
+#   If the repository is included in the URL, you will only receive
+#   messages about that repository.  The repository can be * and then you
+#   will receive messages about all repositories.
 #
 # Example Pub clients:
-#   curl -T revinfo.json -i http://127.0.0.1:2069/commit
+#   curl -T revinfo.json -i http://127.0.0.1:2069/commits
 #
 # TODO:
 #   - Add Real access controls (not just 127.0.0.1)
@@ -61,82 +65,53 @@ import sys
 import twisted
 from twisted.internet import reactor
 from twisted.internet import defer
-from twisted.web import server, static
+from twisted.web import server
 from twisted.web import resource
 from twisted.python import log
 
-try:
-    from xml.etree import cElementTree as ET
-except:
-    from xml.etree import ElementTree as ET
 import time
 
-class Revision:
+class Commit:
     def __init__(self, r):
-        # Don't escape the values; json handles binary values fine.
-        # ET will happily emit literal control characters (eg, NUL),
-        # thus creating invalid XML, so the XML code paths do escaping.
-        self.rev = r.get('revision')
-        self.repos = r.get('repos')
-        self.dirs_changed = r.get('dirs_changed')
-        self.changed = r.get('changed')
-        self.author = r.get('author')
-        self.log = r.get('log')
-        self.date = r.get('date')
-
-    def render_commit(self, format):
-        if format == "json":
-            return json.dumps({'commit': {'repository': self.repos,
-                                          'revision': self.rev,
-                                          'dirs_changed': self.dirs_changed,
-                                          'changed': self.changed,
-                                          'author': self.author,
-                                          'log': self.log,
-                                          'date': self.date}}) +","
-        elif format == "xml":
-            c = ET.Element('commit', {'repository': self.repos, 'revision': "%d" % (self.rev)})
-            ET.SubElement(c, 'author').text = self.author.encode('unicode_escape')
-            ET.SubElement(c, 'date').text = self.date.encode('unicode_escape')
-            ET.SubElement(c, 'log').text = self.log.encode('unicode_escape')
-            d = ET.SubElement(c, 'dirs_changed')
-            for p in self.dirs_changed:
-                x = ET.SubElement(d, 'path')
-                x.text = p.encode('unicode_escape')
-            ch = ET.SubElement(c, 'changed')
-            for chp in self.changed.keys():
-                x = ET.SubElement(ch, 'path', self.changed[chp])
-                x.text = chp.encode('unicode_escape')
-
-            str = ET.tostring(c, 'UTF-8') + "\n"
-            return str[39:]
-        else:
-            raise Exception("Ooops, invalid format")
-
-    def render_dirs_changed(self, format):
-        if format == "json":
-            return json.dumps({'commit': {'repository': self.repos,
-                                          'revision': self.rev,
-                                          'dirs_changed': self.dirs_changed}}) +","
-        elif format == "xml":
-            c = ET.Element('commit', {'repository': self.repos, 'revision': "%d" % (self.rev)})
-            d = ET.SubElement(c, 'dirs_changed')
-            for p in self.dirs_changed:
-                x = ET.SubElement(d, 'path')
-                x.text = p.encode('unicode_escape')
-            str = ET.tostring(c, 'UTF-8') + "\n"
-            return str[39:]
-        else:
-            raise Exception("Ooops, invalid format")
+        self.__dict__.update(r)
+        if not self.check_value('repository'):
+            raise ValueError('Invalid Repository Value')
+        if not self.check_value('type'):
+            raise ValueError('Invalid Type Value')
+        if not self.check_value('format'):
+            raise ValueError('Invalid Format Value')
+        if not self.check_value('id'):
+            raise ValueError('Invalid ID Value')
+ 
+    def check_value(self, k):
+        return hasattr(self, k) and self.__dict__[k]
+
+    def render_commit(self):
+        obj = {'commit': {}}
+        obj['commit'].update(self.__dict__)
+        return json.dumps(obj)
+
+    def render_log(self):
+        try:
+            paths_changed = " %d paths changed" % len(self.changed)
+        except:
+            paths_changed = ""
+        return "%s:%s repo '%s' id '%s'%s" % (self.type,
+                                  self.format,
+                                  self.repository,
+                                  self.id,
+                                  paths_changed)
+
 
 HEARTBEAT_TIME = 15
 
 class Client(object):
-    def __init__(self, pubsub, r, repos, fmt):
+    def __init__(self, pubsub, r, type, repository):
         self.pubsub = pubsub
         r.notifyFinish().addErrback(self.finished)
         self.r = r
-        self.format = fmt
-        self.repos = repos
+        self.type = type
+        self.repository = repository
         self.alive = True
         log.msg("OPEN: %s:%d (%d clients online)"% (r.getClientIP(), r.client.port, pubsub.cc()+1))
 
@@ -148,12 +123,14 @@ class Client(object):
         except ValueError:
             pass
 
-    def interested_in(self, uuid):
-        if self.repos is None:
-            return True
-        if uuid == self.repos:
-            return True
-        return False
+    def interested_in(self, commit):
+        if self.type and self.type != commit.type:
+            return False
+
+        if self.repository and self.repository != commit.repository:
+            return False
+
+        return True 
 
     def notify(self, data):
         self.write(data)
@@ -168,88 +145,67 @@ class Client(object):
             reactor.callLater(HEARTBEAT_TIME, self.heartbeat, None)
 
     def write_data(self, data):
-        self.write(data[self.format] + "\n")
+        self.write(data + "\n\0")
 
     """ "Data must not be unicode" is what the interfaces.ITransport says... grr. """
     def write(self, input):
         self.r.write(str(input))
 
-class JSONClient(Client):
     def write_start(self):
         self.r.setHeader('content-type', 'application/json')
-        self.write('{"commits": [\n')
+        self.write('{"svnpubsub": {"version": 1}}\n\0')
 
     def write_heartbeat(self):
-        self.write(json.dumps({"stillalive": time.time()}) + ",\n")
+        self.write(json.dumps({"stillalive": time.time()}) + "\n\0")
 
-class XMLClient(Client):
-    def write_start(self):
-        self.r.setHeader('content-type', 'application/xml')
-        self.write("<?xml version='1.0' encoding='UTF-8'?>\n<commits>")
-
-    def write_heartbeat(self):
-        self.write("<stillalive>%f</stillalive>\n" % (time.time()))
 
 class SvnPubSub(resource.Resource):
     isLeaf = True
-    clients = {'commits': [],
-               'dirs-changed': []}
+    clients = []
 
     def cc(self):
-        return reduce(lambda x,y: len(x)+len(y), self.clients.values())
+        return len(self.clients)
 
     def remove(self, c):
-        for k in self.clients.keys():
-            self.clients[k].remove(c)
+        self.clients.remove(c)
 
     def render_GET(self, request):
         log.msg("REQUEST: %s"  % (request.uri))
-        uri = request.uri.split('/')
-
         request.setHeader('content-type', 'text/plain')
-        if len(uri) != 3 and len(uri) != 4:
-            request.setResponseCode(400)
-            return "Invalid path\n"
 
-        uuid = None
-        fmt = None
-        type = uri[1]
-
-        if len(uri) == 3:
-            fmt = uri[2]
-        else:
-            fmt = uri[3]
-            uuid = uri[2]
+        repository = None 
+        type = None 
 
-        if type not in self.clients.keys():
+        uri = request.uri.split('/')
+        uri_len = len(uri)
+        if uri_len < 2 or uri_len > 4: 
             request.setResponseCode(400)
-            return "Invalid Reuqest Type\n"
+            return "Invalid path\n"
 
-        clients = {'json': JSONClient, 'xml': XMLClient}
-        clientCls = clients.get(fmt)
-        if clientCls == None:
-            request.setResponseCode(400)
-            return "Invalid Format Requested\n"
+        if uri_len >= 3: 
+          type = uri[2]
+        
+        if uri_len == 4:
+          repository = uri[3]
+
+        # Convert wild card to None.
+        if type == '*':
+          type = None
+        if repository == '*':
+          repository = None
 
-        c = clientCls(self, request, uuid, fmt)
-        self.clients[type].append(c)
+        c = Client(self, request, type, repository)
+        self.clients.append(c)
         c.start()
         return twisted.web.server.NOT_DONE_YET
 
-    def notifyAll(self, rev):
-        data = {'commits': {},
-               'dirs-changed': {}}
-        for x in ['xml', 'json']:
-            data['commits'][x] = rev.render_commit(x)
-            data['dirs-changed'][x] = rev.render_dirs_changed(x)
-
-        log.msg("COMMIT: r%d in %d paths (%d clients)" % (rev.rev,
-                                                        len(rev.dirs_changed),
-                                                        self.cc()))
-        for k in self.clients.keys():
-            for c in self.clients[k]:
-                if c.interested_in(rev.repos):
-                    c.write_data(data[k])
+    def notifyAll(self, commit):
+        data = commit.render_commit()
+
+        log.msg("COMMIT: %s (%d clients)" % (commit.render_log(), self.cc()))
+        for client in self.clients:
+            if client.interested_in(commit):
+                client.write_data(data)
 
     def render_PUT(self, request):
         request.setHeader('content-type', 'text/plain')
@@ -260,17 +216,20 @@ class SvnPubSub(resource.Resource):
         input = request.content.read()
         #import pdb;pdb.set_trace()
         #print "input: %s" % (input)
-        r = json.loads(input)
-        rev = Revision(r)
-        self.notifyAll(rev)
+        try:
+            c = json.loads(input)
+            commit = Commit(c)
+        except ValueError as e:
+            request.setResponseCode(400)
+            log.msg("COMMIT: failed due to: %s" % str(e))
+            return str(e)
+        self.notifyAll(commit)
         return "Ok"
 
 def svnpubsub_server():
-    root = static.File("/dev/null")
+    root = resource.Resource()
     s = SvnPubSub()
-    root.putChild("dirs-changed", s)
     root.putChild("commits", s)
-    root.putChild("commit", s)
     return server.Site(root)
 
 if __name__ == "__main__":



Mime
View raw message