You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ph...@apache.org on 2012/03/15 12:49:51 UTC

svn commit: r1300933 - in /subversion/trunk: ./ subversion/libsvn_wc/wc-metadata.sql subversion/libsvn_wc/wc-queries.sql subversion/libsvn_wc/wc_db.c subversion/tests/libsvn_wc/db-test.c subversion/tests/libsvn_wc/op-depth-test.c

Author: philip
Date: Thu Mar 15 11:49:51 2012
New Revision: 1300933

URL: http://svn.apache.org/viewvc?rev=1300933&view=rev
Log:
Reintegrate the multi-layer-move branch.  This stores NODES.moved_to
in WORKING nodes and will break any working copy moves recorded by
older 1.8 clients transforming the moves into copies+deletes.

Modified:
    subversion/trunk/   (props changed)
    subversion/trunk/subversion/libsvn_wc/wc-metadata.sql
    subversion/trunk/subversion/libsvn_wc/wc-queries.sql
    subversion/trunk/subversion/libsvn_wc/wc_db.c
    subversion/trunk/subversion/tests/libsvn_wc/db-test.c
    subversion/trunk/subversion/tests/libsvn_wc/op-depth-test.c

Propchange: subversion/trunk/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Thu Mar 15 11:49:51 2012
@@ -37,6 +37,7 @@
 /subversion/branches/kwallet:870785-871314
 /subversion/branches/log-g-performance:870941-871032
 /subversion/branches/merge-skips-obstructions:874525-874615
+/subversion/branches/multi-layer-moves:1239019-1300930
 /subversion/branches/nfc-nfd-aware-client:870276,870376
 /subversion/branches/performance:979193,980118,981087,981090,981189,981194,981287,981684,981827,982043,982355,983398,983406,983430,983474,983488,983490,983760,983764,983766,983770,984927,984973,984984,985014,985037,985046,985472,985477,985482,985487-985488,985493,985497,985500,985514,985601,985603,985606,985669,985673,985695,985697,986453,986465,986485,986491-986492,986517,986521,986605,986608,986817,986832,987865,987868-987869,987872,987886-987888,987893,988319,988898,990330,990533,990535-990537,990541,990568,990572,990574-990575,990600,990759,992899,992904,992911,993127,993141,994956,995478,995507,995603,998012,998858,999098,1001413,1001417,1004291,1022668,1022670,1022676,1022715,1022719,1025660,1025672,1027193,1027203,1027206,1027214,1027227,1028077,1028092,1028094,1028104,1028107,1028111,1028354,1029038,1029042-1029043,1029054-1029055,1029062-1029063,1029078,1029080,1029090,1029092-1029093,1029111,1029151,1029158,1029229-1029230,1029232,1029335-1029336,1029339-1029340,10
 29342,1029344,1030763,1030827,1031203,1031235,1032285,1032333,1033040,1033057,1033294,1035869,1035882,1039511,1043705,1053735,1056015,1066452,1067683,1067697-1078365
 /subversion/branches/py-tests-as-modules:956579-1033052

Modified: subversion/trunk/subversion/libsvn_wc/wc-metadata.sql
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/wc-metadata.sql?rev=1300933&r1=1300932&r2=1300933&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_wc/wc-metadata.sql (original)
+++ subversion/trunk/subversion/libsvn_wc/wc-metadata.sql Thu Mar 15 11:49:51 2012
@@ -389,12 +389,13 @@ CREATE TABLE NODES (
   moved_here  INTEGER,
 
   /* If the underlying node was moved away (rather than just deleted), this
-     specifies the local_relpath of where the BASE node was moved to.
+     specifies the local_relpath of where the node was moved to.
      This is set only on the root of a move, and is NULL for all children.
 
-     Note that moved_to never refers to *this* node. It always refers
-     to the "underlying" node in the BASE tree. A non-NULL moved_to column
-     is only valid in rows where op_depth == 0. */
+     The op-depth of the moved-to node is not recorded. A moved_to path
+     always points at a node within the highest op-depth layer at the
+     destination. This invariant must be maintained by operations which
+     change existing move information. */
   moved_to  TEXT,
 
 

Modified: subversion/trunk/subversion/libsvn_wc/wc-queries.sql
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/wc-queries.sql?rev=1300933&r1=1300932&r2=1300933&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_wc/wc-queries.sql (original)
+++ subversion/trunk/subversion/libsvn_wc/wc-queries.sql Thu Mar 15 11:49:51 2012
@@ -51,8 +51,7 @@ ORDER BY op_depth DESC
 -- STMT_SELECT_BASE_NODE
 SELECT repos_id, repos_path, presence, kind, revision, checksum,
   translated_size, changed_revision, changed_date, changed_author, depth,
-  symlink_target, last_mod_time, properties, file_external IS NOT NULL,
-  moved_to
+  symlink_target, last_mod_time, properties, file_external IS NOT NULL
 FROM nodes
 WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
 
@@ -60,7 +59,6 @@ WHERE wc_id = ?1 AND local_relpath = ?2 
 SELECT nodes.repos_id, nodes.repos_path, presence, kind, revision,
   checksum, translated_size, changed_revision, changed_date, changed_author,
   depth, symlink_target, last_mod_time, properties, file_external IS NOT NULL,
-  moved_to,
   /* All the columns until now must match those returned by
      STMT_SELECT_BASE_NODE. The implementation of svn_wc__db_base_get_info()
      assumes that these columns are followed by the lock information) */
@@ -283,13 +281,18 @@ WHERE wc_id = ?1 AND local_relpath = ?2 
 SELECT dav_cache FROM nodes
 WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
 
+/* ### FIXME.  modes_move.moved_to IS NOT NULL works when there is
+ only one move but we need something else when there are several. */
 -- STMT_SELECT_DELETION_INFO
-SELECT nodes_base.presence, nodes_work.presence, nodes_base.moved_to,
+SELECT nodes_base.presence, nodes_work.presence, nodes_move.moved_to,
        nodes_work.op_depth
 FROM nodes AS nodes_work
+LEFT OUTER JOIN nodes nodes_move ON nodes_move.wc_id = nodes_work.wc_id
+  AND nodes_move.local_relpath = nodes_work.local_relpath
+  AND nodes_move.moved_to IS NOT NULL
 LEFT OUTER JOIN nodes nodes_base ON nodes_base.wc_id = nodes_work.wc_id
-  AND nodes_base.local_relpath = nodes_work.local_relpath
-  AND nodes_base.op_depth = 0
+ AND nodes_base.local_relpath = nodes_work.local_relpath
+ AND nodes_base.op_depth = 0
 WHERE nodes_work.wc_id = ?1 AND nodes_work.local_relpath = ?2
   AND nodes_work.op_depth = (SELECT MAX(op_depth) FROM nodes
                              WHERE wc_id = ?1 AND local_relpath = ?2
@@ -884,28 +887,32 @@ INSERT OR REPLACE INTO nodes (
     wc_id, local_relpath, op_depth, parent_relpath, repos_id,
     repos_path, revision, presence, depth, moved_here, kind, changed_revision,
     changed_date, changed_author, checksum, properties, translated_size,
-    last_mod_time, symlink_target )
-SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/,
-    repos_id, repos_path, revision, ?6 /*presence*/, depth, ?7/*moved_here*/,
-    kind, changed_revision, changed_date, changed_author, checksum, properties,
-    translated_size, last_mod_time, symlink_target
-FROM nodes
-WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+    last_mod_time, symlink_target, moved_to )
+SELECT src.wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/,
+    src.repos_id, src.repos_path, src.revision, ?6 /*presence*/, src.depth,
+    ?7/*moved_here*/, src.kind, src.changed_revision, src.changed_date,
+    src.changed_author, src.checksum, src.properties, src.translated_size,
+    src.last_mod_time, src.symlink_target, dst.moved_to
+FROM nodes AS src
+LEFT OUTER JOIN nodes_current dst ON dst.wc_id = src.wc_id
+  AND dst.local_relpath = ?3 AND dst.op_depth = ?4
+WHERE src.wc_id = ?1 AND src.local_relpath = ?2 AND src.op_depth = 0
 
 -- STMT_INSERT_WORKING_NODE_COPY_FROM_WORKING
 INSERT OR REPLACE INTO nodes (
-    wc_id, local_relpath, op_depth, parent_relpath, repos_id, repos_path,
-    revision, presence, depth, moved_here, kind, changed_revision, changed_date,
-    changed_author, checksum, properties, translated_size, last_mod_time,
-    symlink_target )
-SELECT wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/,
-    repos_id, repos_path, revision, ?6 /*presence*/, depth, ?7 /*moved_here*/,
-    kind, changed_revision, changed_date, changed_author, checksum, properties,
-    translated_size, last_mod_time, symlink_target
-FROM nodes
-WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth > 0
-ORDER BY op_depth DESC
-LIMIT 1
+    wc_id, local_relpath, op_depth, parent_relpath, repos_id,
+    repos_path, revision, presence, depth, moved_here, kind, changed_revision,
+    changed_date, changed_author, checksum, properties, translated_size,
+    last_mod_time, symlink_target, moved_to )
+SELECT src.wc_id, ?3 /*local_relpath*/, ?4 /*op_depth*/, ?5 /*parent_relpath*/,
+    src.repos_id, src.repos_path, src.revision, ?6 /*presence*/, src.depth,
+    ?7 /*moved_here*/, src.kind, src.changed_revision, src.changed_date,
+    src.changed_author, src.checksum, src.properties, src.translated_size,
+    src.last_mod_time, src.symlink_target, dst.moved_to
+FROM nodes_current AS src
+LEFT OUTER JOIN nodes_current dst ON dst.wc_id = src.wc_id
+  AND dst.local_relpath = ?3  AND dst.op_depth = ?4
+WHERE src.wc_id = ?1 AND src.local_relpath = ?2 AND src.op_depth > 0
 
 -- STMT_INSERT_WORKING_NODE_COPY_FROM_DEPTH
 INSERT OR REPLACE INTO nodes (
@@ -1373,34 +1380,48 @@ WHERE wc_id = ?1
   AND presence='normal'
   AND file_external IS NULL
 
+/* ### FIXME: op-depth?  What about multiple moves? */
 -- STMT_SELECT_MOVED_FROM_RELPATH
-SELECT local_relpath FROM nodes
-WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth = 0
+SELECT local_relpath, op_depth FROM nodes
+WHERE wc_id = ?1 AND moved_to = ?2 AND op_depth > 0
 
 -- STMT_UPDATE_MOVED_TO_RELPATH
-UPDATE nodes SET moved_to = ?3
-WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
+UPDATE nodes SET moved_to = ?4
+WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3
 
+/* ### FIXME: op-depth?  What about multiple moves? */
 -- STMT_CLEAR_MOVED_TO_RELPATH
 UPDATE nodes SET moved_to = NULL
-WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = 0
-
--- STMT_CLEAR_MOVED_TO_RELPATH_RECURSIVE
-UPDATE nodes SET moved_to = NULL
-WHERE wc_id = ?1
-  AND (?2 = ''
-       OR local_relpath = ?2
-       OR IS_STRICT_DESCENDANT_OF(local_relpath, ?2))
-  AND op_depth = 0
+WHERE wc_id = ?1 AND local_relpath = ?2
 
 /* This statement returns pairs of move-roots below the path ?2 in WC_ID ?1.
  * Each row returns a moved-here path (always a child of ?2) in the first
  * column, and its matching moved-away (deleted) path in the second column. */
 -- STMT_SELECT_MOVED_HERE_CHILDREN
 SELECT moved_to, local_relpath FROM nodes
-WHERE wc_id = ?1 AND op_depth = 0
+WHERE wc_id = ?1 AND op_depth > 0
   AND IS_STRICT_DESCENDANT_OF(moved_to, ?2)
 
+/* This statement returns pairs of paths that define a move where the
+   destination of the move is within the subtree rooted at path ?2 in
+   WC_ID ?1. */
+-- STMT_SELECT_MOVED_PAIR
+SELECT local_relpath, moved_to, op_depth FROM nodes_current
+WHERE wc_id = ?1
+  AND (IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+       OR (IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+           AND moved_to IS NOT NULL))
+
+/* This statement returns pairs of move-roots below the path ?2 in WC_ID ?1,
+ * where the source of the move is within the subtree rooted at path ?2, and
+ * the destination of the move is outside the subtree rooted at path ?2. */
+-- STMT_SELECT_MOVED_PAIR2
+SELECT local_relpath, moved_to FROM nodes_current
+WHERE wc_id = ?1
+  AND IS_STRICT_DESCENDANT_OF(local_relpath, ?2)
+  AND moved_to IS NOT NULL
+  AND NOT IS_STRICT_DESCENDANT_OF(moved_to, ?2)
+
 /* ------------------------------------------------------------------------- */
 
 /* Queries for verification. */

Modified: subversion/trunk/subversion/libsvn_wc/wc_db.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_wc/wc_db.c?rev=1300933&r1=1300932&r2=1300933&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_wc/wc_db.c (original)
+++ subversion/trunk/subversion/libsvn_wc/wc_db.c Thu Mar 15 11:49:51 2012
@@ -368,6 +368,7 @@ scan_addition(svn_wc__db_status_t *statu
               svn_revnum_t *original_revision,
               const char **moved_from_relpath,
               const char **moved_from_op_root_relpath,
+              apr_int64_t *moved_from_op_depth,
               svn_wc__db_wcroot_t *wcroot,
               const char *local_relpath,
               apr_pool_t *result_pool,
@@ -781,7 +782,6 @@ insert_base_node(void *baton,
   svn_sqlite__stmt_t *stmt;
   svn_filesize_t recorded_size = SVN_INVALID_FILESIZE;
   apr_int64_t recorded_mod_time;
-  const char *moved_to_relpath = NULL;
   svn_boolean_t have_row;
 
   /* The directory at the WCROOT has a NULL parent_relpath. Otherwise,
@@ -813,15 +813,13 @@ insert_base_node(void *baton,
           recorded_size = get_recorded_size(stmt, 6);
           recorded_mod_time = svn_sqlite__column_int64(stmt, 12);
         }
-      /* Always preserve moved-to info. */
-      moved_to_relpath = svn_sqlite__column_text(stmt, 15, scratch_pool);
     }
   SVN_ERR(svn_sqlite__reset(stmt));
 
   SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_INSERT_NODE));
   SVN_ERR(svn_sqlite__bindf(stmt, "isisisr"
                             "tstr"               /* 8 - 11 */
-                            "isnnnnnsss",          /* 12 - 21 */
+                            "isnnnnns",          /* 12 - 19 */
                             wcroot->wc_id,       /* 1 */
                             local_relpath,       /* 2 */
                             (apr_int64_t)0, /* op_depth is 0 for base */
@@ -837,9 +835,7 @@ insert_base_node(void *baton,
                             pibb->changed_date,   /* 12 */
                             pibb->changed_author, /* 13 */
                             (pibb->kind == svn_kind_symlink) ?
-                                pibb->target : NULL, /* 19 */
-                            NULL /* 20 */,
-                            moved_to_relpath /* 21 */));
+                                pibb->target : NULL)); /* 19 */
   if (pibb->kind == svn_kind_file)
     {
       if (!pibb->checksum
@@ -971,38 +967,72 @@ insert_incomplete_children(svn_sqlite__d
 {
   svn_sqlite__stmt_t *stmt;
   int i;
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+  apr_hash_t *moved_to_relpaths = apr_hash_make(scratch_pool);
 
   SVN_ERR_ASSERT(repos_path != NULL || op_depth > 0);
   SVN_ERR_ASSERT((repos_id != INVALID_REPOS_ID)
                  == (repos_path != NULL));
 
+  /* If we're inserting WORKING nodes, we might be replacing existing
+   * nodes which were moved-away. We need to retain the moved-to relpath of
+   * such nodes in order not to lose move information during replace. */
+  if (op_depth > 0)
+    {
+      for (i = children->nelts; i--; )
+        {
+          const char *name = APR_ARRAY_IDX(children, i, const char *);
+          svn_boolean_t have_row;
+
+          svn_pool_clear(iterpool);
+
+          SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+                                            STMT_SELECT_WORKING_NODE));
+          SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id,
+                                    svn_relpath_join(local_relpath, name,
+                                                     iterpool)));
+          SVN_ERR(svn_sqlite__step(&have_row, stmt));
+          if (have_row && !svn_sqlite__column_is_null(stmt, 14))
+            apr_hash_set(moved_to_relpaths, name, APR_HASH_KEY_STRING,
+              svn_sqlite__column_text(stmt, 14, scratch_pool));
+
+          SVN_ERR(svn_sqlite__reset(stmt));
+        }
+    }
+
   SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
 
   for (i = children->nelts; i--; )
     {
       const char *name = APR_ARRAY_IDX(children, i, const char *);
 
-      SVN_ERR(svn_sqlite__bindf(stmt, "isisnnrsns",
+      svn_pool_clear(iterpool);
+
+      SVN_ERR(svn_sqlite__bindf(stmt, "isisnnrsnsnnnnnnnnnnsn",
                                 wc_id,
                                 svn_relpath_join(local_relpath, name,
-                                                 scratch_pool),
+                                                 iterpool),
                                 op_depth,
                                 local_relpath,
                                 revision,
                                 "incomplete", /* 8, presence */
-                                "unknown"));  /* 10, kind */
-
+                                "unknown",    /* 10, kind */
+                                /* 21, moved_to */
+                                apr_hash_get(moved_to_relpaths, name,
+                                             APR_HASH_KEY_STRING)));
       if (repos_id != INVALID_REPOS_ID)
         {
           SVN_ERR(svn_sqlite__bind_int64(stmt, 5, repos_id));
           SVN_ERR(svn_sqlite__bind_text(stmt, 6,
                                         svn_relpath_join(repos_path, name,
-                                                         scratch_pool)));
+                                                         iterpool)));
         }
 
       SVN_ERR(svn_sqlite__insert(NULL, stmt));
     }
 
+  svn_pool_destroy(iterpool);
+
   return SVN_NO_ERROR;
 }
 
@@ -2039,7 +2069,7 @@ base_get_info(svn_wc__db_status_t *statu
       SVN_ERR_ASSERT(!repos_relpath || *repos_relpath);
       if (lock)
         {
-          *lock = lock_from_columns(stmt, 16, 17, 18, 19, result_pool);
+          *lock = lock_from_columns(stmt, 15, 16, 17, 18, result_pool);
         }
       if (changed_rev)
         {
@@ -3421,7 +3451,7 @@ get_info_for_copy(apr_int64_t *copyfrom_
       SVN_ERR(scan_addition(NULL, &op_root_relpath,
                             NULL, NULL, /* repos_* */
                             copyfrom_relpath, copyfrom_id, copyfrom_rev,
-                            NULL, NULL, wcroot, local_relpath,
+                            NULL, NULL, NULL, wcroot, local_relpath,
                             scratch_pool, scratch_pool));
       if (*copyfrom_relpath)
         {
@@ -3450,7 +3480,7 @@ get_info_for_copy(apr_int64_t *copyfrom_
           SVN_ERR(scan_addition(NULL, &op_root_relpath,
                                 NULL, NULL, /* repos_* */
                                 copyfrom_relpath, copyfrom_id, copyfrom_rev,
-                                NULL, NULL, wcroot, parent_del_relpath,
+                                NULL, NULL, NULL, wcroot, parent_del_relpath,
                                 scratch_pool, scratch_pool));
           *copyfrom_relpath
             = svn_relpath_join(*copyfrom_relpath,
@@ -3626,13 +3656,14 @@ db_op_copy(svn_wc__db_wcroot_t *src_wcro
         SVN_ERR(svn_sqlite__get_statement(&stmt, src_wcroot->sdb,
                           STMT_INSERT_WORKING_NODE_COPY_FROM_BASE));
 
-      SVN_ERR(svn_sqlite__bindf(stmt, "issisti",
+      SVN_ERR(svn_sqlite__bindf(stmt, "issist",
                     src_wcroot->wc_id, src_relpath,
                     dst_relpath,
                     dst_op_depth,
                     dst_parent_relpath,
-                    presence_map, dst_presence,
-                    (apr_int64_t)(is_move ? 1 : 0)));
+                    presence_map, dst_presence));
+      if (is_move)
+        SVN_ERR(svn_sqlite__bind_int64(stmt, 7, 1));
 
       SVN_ERR(svn_sqlite__step_done(stmt));
 
@@ -5373,14 +5404,9 @@ op_revert_txn(void *baton,
       SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
       SVN_ERR(svn_sqlite__step_done(stmt));
 
+      /* If this node was moved-here, clear moved-to at the move source. */
       if (moved_here)
         SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool));
-
-      /* Clear the moved-to path of the BASE node. */
-      SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
-                                        STMT_CLEAR_MOVED_TO_RELPATH));
-      SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
-      SVN_ERR(svn_sqlite__step_done(stmt));
     }
 
   SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
@@ -5512,12 +5538,6 @@ op_revert_recursive_txn(void *baton,
       && moved_here)
     SVN_ERR(clear_moved_to(local_relpath, wcroot, scratch_pool));
 
-  /* Clear any moved-to paths of potentially 'moved-away' nodes. */
-  SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
-                                    STMT_CLEAR_MOVED_TO_RELPATH_RECURSIVE));
-  SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
-  SVN_ERR(svn_sqlite__step_done(stmt));
-
   return SVN_NO_ERROR;
 }
 
@@ -6274,6 +6294,7 @@ info_below_working(svn_boolean_t *have_b
 static svn_error_t *
 delete_update_movedto(svn_wc__db_wcroot_t *wcroot,
                       const char *child_moved_from_relpath,
+                      apr_int64_t op_depth,
                       const char *new_moved_to_relpath,
                       apr_pool_t *scratch_pool)
 {
@@ -6282,9 +6303,10 @@ delete_update_movedto(svn_wc__db_wcroot_
   SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
                                     STMT_UPDATE_MOVED_TO_RELPATH));
 
-  SVN_ERR(svn_sqlite__bindf(stmt, "iss",
+  SVN_ERR(svn_sqlite__bindf(stmt, "isis",
                             wcroot->wc_id,
                             child_moved_from_relpath,
+                            op_depth,
                             new_moved_to_relpath));
   SVN_ERR(svn_sqlite__step_done(stmt));
 
@@ -6297,6 +6319,37 @@ struct op_delete_baton_t {
   const char *moved_to_relpath; /* NULL if delete is not part of a move */
 };
 
+/* This structure is used while rewriting move information for nodes.
+ * 
+ * The most simple case of rewriting move information happens when
+ * a moved-away subtree is moved again:  mv A B; mv B C
+ * The second move requires rewriting moved-to info at or within A.
+ *
+ * Another example is a move of a subtree which had nodes moved into it:
+ *   mv A B/F; mv B G
+ * This requires rewriting such that A/F is marked has having moved to G/F.
+ *
+ * Another case is where a node becomes a nested moved node.
+ * A nested move happens when a subtree child is moved before or after
+ * the subtree itself is moved. For example:
+ *   mv A/F A/G; mv A B
+ * In this case, the move A/F -> A/G is rewritten to B/F -> B/G.
+ * Note that the following sequence results in the same DB state:
+ *   mv A B; mv B/F B/G
+ * We do not care about the order the moves were performed in.
+ * For details, see http://wiki.apache.org/subversion/MultiLayerMoves
+ */
+struct moved_node_t {
+  /* The source of the move. */
+  const char *local_relpath;
+
+  /* The move destination. */
+  const char *moved_to_relpath;
+
+  /* The op-depth of the deleted node at the source of the move. */
+  apr_int64_t op_depth;
+};
+
 static svn_error_t *
 delete_node(void *baton,
             svn_wc__db_wcroot_t *wcroot,
@@ -6311,6 +6364,7 @@ delete_node(void *baton,
   apr_int64_t select_depth; /* Depth of what is to be deleted */
   svn_boolean_t refetch_depth = FALSE;
   svn_kind_t kind;
+  apr_array_header_t *moved_nodes = NULL;
 
   SVN_ERR(read_info(&status,
                     &kind, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
@@ -6345,116 +6399,129 @@ delete_node(void *baton,
 
   if (b->moved_to_relpath)
     {
-      const char *moved_from_relpath = NULL;
-
-      /* ### call scan_addition_txn() directly? */
+      const char *moved_from_op_root_relpath;
+      struct moved_node_t *moved_node
+        = apr_palloc(scratch_pool, sizeof(struct moved_node_t));
+
+      /* The node is being moved-away.
+       * Figure out if the node was moved-here before, or whether this
+       * is the first time the node is moved. */
       if (status == svn_wc__db_status_added)
-        SVN_ERR(scan_addition(&status, NULL, NULL, NULL,
-                              NULL, NULL, NULL,
-                              &moved_from_relpath, NULL,
+        SVN_ERR(scan_addition(&status, NULL, NULL, NULL, NULL, NULL, NULL,
+                              &moved_node->local_relpath,
+                              &moved_from_op_root_relpath,
+                              &moved_node->op_depth,
                               wcroot, local_relpath,
                               scratch_pool, scratch_pool));
 
-      if (status == svn_wc__db_status_moved_here)
+      if (status != svn_wc__db_status_moved_here ||
+          strcmp(moved_from_op_root_relpath, moved_node->local_relpath) != 0)
         {
-          /* The node has already been moved, possibly along with a parent,
-           * and is being moved again. Update the existing moved_to path
-           * in the BASE node. */
-          SVN_ERR(delete_update_movedto(wcroot, moved_from_relpath,
-                                        b->moved_to_relpath, scratch_pool));
-        }
+          /* The node is becoming a move-root for the first time,
+           * possibly because of a nested move operation. */
+          moved_node->local_relpath = local_relpath;
+          moved_node->op_depth = b->delete_depth;
+        }
+      moved_node->moved_to_relpath = b->moved_to_relpath;
+
+      /* ### Use array of struct rather than pointers? */
+      moved_nodes = apr_array_make(scratch_pool, 1,
+                                   sizeof(struct moved_node_t *));
+      APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node;
 
       /* If a subtree is being moved-away, we need to update moved-to
-       * information in BASE for all children that were moved into this
-       * subtree. */
+       * information for all children that were moved into, or within,
+       * this subtree. */
       if (kind == svn_kind_dir)
         {
-          apr_pool_t *iterpool;
-
           SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
-                                            STMT_SELECT_MOVED_HERE_CHILDREN));
+                                            STMT_SELECT_MOVED_PAIR));
           SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
-
           SVN_ERR(svn_sqlite__step(&have_row, stmt));
 
-          iterpool = svn_pool_create(scratch_pool);
           while (have_row)
             {
-              svn_wc__db_status_t child_status;
-              const char *child_moved_from_relpath = NULL;
-              const char *child_delete_op_root_relpath = NULL;
-              const char *moved_here_child_relpath =
-                svn_sqlite__column_text(stmt, 0, scratch_pool);
-              svn_error_t *err;
-
-              svn_pool_clear(iterpool);
-
-              /* The moved-here-children query returns info based on the
-               * delete-half of the move. Check if that the copied-half of
-               * the move matches this information. */
-              err = read_info(&child_status, NULL, NULL, NULL, NULL,
-                              NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                              NULL, NULL, NULL, NULL, NULL, NULL,
-                              wcroot, moved_here_child_relpath,
-                              iterpool, iterpool);
-              if (err)
-                return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+              const char *move_relpath
+                = svn_sqlite__column_text(stmt, 0, NULL);
+              const char *move_subtree_relpath
+                = svn_relpath_skip_ancestor(local_relpath, move_relpath);
+              const char *child_moved_to
+                = svn_sqlite__column_text(stmt, 1, NULL);
+              const char *child_moved_to_subtree_relpath
+                = svn_relpath_skip_ancestor(local_relpath, child_moved_to);
+              apr_int64_t child_op_depth = svn_sqlite__column_int64(stmt, 2);
+
+              moved_node = apr_palloc(scratch_pool,
+                                      sizeof(struct moved_node_t));
+              if (move_subtree_relpath)
+                moved_node->local_relpath
+                  = svn_relpath_join(b->moved_to_relpath,
+                                     move_subtree_relpath, scratch_pool);
+              else
+                moved_node->local_relpath
+                  = apr_pstrdup(scratch_pool, move_relpath);
 
-              if (child_status == svn_wc__db_status_added)
-                {
-                  err = scan_addition(&child_status, NULL, NULL, NULL,
-                                      NULL, NULL, NULL,
-                                      &child_moved_from_relpath,
-                                      &child_delete_op_root_relpath,
-                                      wcroot, moved_here_child_relpath,
-                                      iterpool, iterpool);
-                  if (err)
-                    return svn_error_compose_create(err,
-                                                    svn_sqlite__reset(stmt));
-                }
-#ifdef SVN_DEBUG
-              /* This catches incorrectly recorded moves.
-               * It is possible to hit this during normal operation
-               * if a move was interrupted mid-way so only perform
-               * this check in debug mode. */
-              SVN_ERR_ASSERT(child_moved_from_relpath &&
-                             !strcmp(child_moved_from_relpath,
-                                     svn_sqlite__column_text(stmt, 1, NULL)));
-#endif
-              if (child_status == svn_wc__db_status_moved_here)
-                {
-                  const char *child_subtree_relpath;
-                  const char *new_moved_to_relpath;
+              if (child_moved_to_subtree_relpath)
+                moved_node->moved_to_relpath
+                  = svn_relpath_join(b->moved_to_relpath,
+                                     child_moved_to_subtree_relpath,
+                                     scratch_pool);
+              else
+                moved_node->moved_to_relpath
+                  = apr_pstrdup(scratch_pool, child_moved_to);
 
-                  /* Compute the new moved-to path for this child... */
-                  child_subtree_relpath =
-                    svn_relpath_skip_ancestor(local_relpath,
-                                              moved_here_child_relpath);
-                  SVN_ERR_ASSERT(child_subtree_relpath);
-
-                  new_moved_to_relpath =
-                    svn_relpath_join(b->moved_to_relpath,
-                                     child_subtree_relpath, iterpool);
-                  /* ... and update the BASE moved-to record. */
-                  err = delete_update_movedto(wcroot, child_moved_from_relpath,
-                                              new_moved_to_relpath,
-                                              scratch_pool);
+              if (child_op_depth > b->delete_depth
+                  && svn_relpath_skip_ancestor(local_relpath,
+                                               moved_node->local_relpath))
+                moved_node->op_depth = b->delete_depth;
+              else
+                moved_node->op_depth = child_op_depth;
 
-                  if (err)
-                    return svn_error_trace(svn_error_compose_create(
-                                                    err,
-                                                    svn_sqlite__reset(stmt)));
-                }
+              APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *)
+                = moved_node;
 
               SVN_ERR(svn_sqlite__step(&have_row, stmt));
             }
-          svn_pool_destroy(iterpool);
-
           SVN_ERR(svn_sqlite__reset(stmt));
         }
     }
 
+  /* Find children that were moved out of the subtree rooted at this node.
+   * We'll need to update their op-depth columns because their deletion
+   * is now implied by the deletion of their parent (i.e. this node). */
+  if (kind == svn_kind_dir && !b->moved_to_relpath)
+    {
+      apr_pool_t *iterpool;
+
+      SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+                                        STMT_SELECT_MOVED_PAIR2));
+      SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+
+      SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+      iterpool = svn_pool_create(scratch_pool);
+      while (have_row)
+        {
+          struct moved_node_t *moved_node
+            = apr_palloc(scratch_pool, sizeof(struct moved_node_t));
+
+          moved_node->local_relpath
+            = svn_sqlite__column_text(stmt, 0, scratch_pool);
+          moved_node->moved_to_relpath
+            = svn_sqlite__column_text(stmt, 1, scratch_pool);
+          moved_node->op_depth = b->delete_depth;
+
+          if (!moved_nodes)
+            moved_nodes = apr_array_make(scratch_pool, 1,
+                                         sizeof(struct moved_node_t *));
+          APR_ARRAY_PUSH(moved_nodes, const struct moved_node_t *) = moved_node;
+
+          SVN_ERR(svn_sqlite__step(&have_row, stmt));
+        }
+      svn_pool_destroy(iterpool);
+      SVN_ERR(svn_sqlite__reset(stmt));
+    }
+
   if (op_root)
     {
       svn_boolean_t below_base;
@@ -6487,7 +6554,7 @@ delete_node(void *baton,
 
           SVN_ERR(scan_addition(&status, NULL, NULL, NULL, NULL, NULL, NULL,
                                 &moved_from_relpath,
-                                &moved_from_op_root_relpath,
+                                &moved_from_op_root_relpath, NULL,
                                 wcroot, local_relpath,
                                 scratch_pool, scratch_pool));
           if (status == svn_wc__db_status_moved_here &&
@@ -6547,12 +6614,6 @@ delete_node(void *baton,
   if (add_work)
     {
       /* Delete the node at LOCAL_RELPATH, and possibly mark it as moved. */
-      if (b->moved_to_relpath)
-        {
-          /* Record moved-to relpath in BASE. */
-          SVN_ERR(delete_update_movedto(wcroot, local_relpath,
-                                        b->moved_to_relpath, scratch_pool));
-        }
 
       /* Delete the node and possible descendants. */
       SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
@@ -6563,6 +6624,23 @@ delete_node(void *baton,
       SVN_ERR(svn_sqlite__step_done(stmt));
     }
 
+  if (moved_nodes)
+    {
+      int i;
+
+      for (i = 0; i < moved_nodes->nelts; ++i)
+        {
+          const struct moved_node_t *moved_node
+            = APR_ARRAY_IDX(moved_nodes, i, void *);
+
+          SVN_ERR(delete_update_movedto(wcroot,
+                                        moved_node->local_relpath,
+                                        moved_node->op_depth,
+                                        moved_node->moved_to_relpath,
+                                        scratch_pool));
+        }
+    }
+
   return SVN_NO_ERROR;
 }
 
@@ -7422,29 +7500,29 @@ read_children_info(void *baton,
 
       if (op_depth == 0)
         {
-          const char *moved_to_relpath;
-
           child_item->info.have_base = TRUE;
 
           /* Get the lock info, available only at op_depth 0. */
           child_item->info.lock = lock_from_columns(stmt, 15, 16, 17, 18,
                                                     result_pool);
 
-          /* Moved-to is only stored at op_depth 0. */
-          moved_to_relpath = svn_sqlite__column_text(stmt, 21, NULL);
-          if (moved_to_relpath)
-            child_item->info.moved_to_abspath =
-              svn_dirent_join(wcroot->abspath, moved_to_relpath, result_pool);
-
           /* FILE_EXTERNAL flag only on op_depth 0. */
           child_item->info.file_external = svn_sqlite__column_boolean(stmt,
                                                                       22);
         }
       else
         {
+          const char *moved_to_relpath;
+
           child_item->nr_layers++;
           child_item->info.have_more_work = (child_item->nr_layers > 1);
 
+          /* Moved-to can only exist at op_depth > 0. */
+          moved_to_relpath = svn_sqlite__column_text(stmt, 21, NULL); 
+          if (moved_to_relpath)
+            child_item->info.moved_to_abspath =
+              svn_dirent_join(wcroot->abspath, moved_to_relpath, result_pool);
+
           /* Moved-here can only exist at op_depth > 0. */
           child_item->info.moved_here = svn_sqlite__column_boolean(stmt, 20);
         }
@@ -7851,7 +7929,8 @@ read_url_txn(void *baton,
       if (status == svn_wc__db_status_added)
         {
           SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id, NULL,
-                                NULL, NULL, NULL, NULL, wcroot, local_relpath,
+                                NULL, NULL, NULL, NULL, NULL,
+                                wcroot, local_relpath,
                                 scratch_pool, scratch_pool));
         }
       else if (status == svn_wc__db_status_deleted)
@@ -7884,7 +7963,7 @@ read_url_txn(void *baton,
                                                              scratch_pool);
 
               SVN_ERR(scan_addition(NULL, NULL, &repos_relpath, &repos_id,
-                                    NULL, NULL, NULL, NULL, NULL,
+                                    NULL, NULL, NULL, NULL, NULL, NULL,
                                     wcroot, work_relpath,
                                     scratch_pool, scratch_pool));
 
@@ -8633,7 +8712,7 @@ svn_wc__db_global_relocate(svn_wc__db_t 
       if (status == svn_wc__db_status_added)
         {
           SVN_ERR(scan_addition(NULL, NULL, NULL, &rb.old_repos_id,
-                                NULL, NULL, NULL, NULL, NULL,
+                                NULL, NULL, NULL, NULL, NULL, NULL,
                                 wcroot, local_dir_relpath,
                                 scratch_pool, scratch_pool));
         }
@@ -9567,6 +9646,7 @@ get_moved_from_info(svn_wc__db_status_t 
                     const char **moved_from_relpath,
                     const char **moved_from_op_root_relpath,
                     const char *moved_to_op_root_relpath,
+                    apr_int64_t *op_depth,
                     svn_wc__db_wcroot_t *wcroot,
                     const char *local_relpath,
                     apr_pool_t *result_pool,
@@ -9602,6 +9682,9 @@ get_moved_from_info(svn_wc__db_status_t 
   if (status)
     *status = svn_wc__db_status_moved_here;
 
+  if (op_depth)
+    *op_depth = svn_sqlite__column_int64(stmt, 1);
+
   if (moved_from_relpath || moved_from_op_root_relpath)
     {
       const char *db_delete_op_root_relpath;
@@ -9663,6 +9746,7 @@ struct scan_addition_baton_t
   svn_revnum_t *original_revision;
   const char **moved_from_relpath;
   const char **moved_from_op_root_relpath;
+  apr_int64_t *moved_from_op_depth;
   apr_pool_t *result_pool;
 };
 
@@ -9692,6 +9776,8 @@ scan_addition_txn(void *baton,
     *sab->moved_from_relpath = NULL;
   if (sab->moved_from_op_root_relpath)
     *sab->moved_from_op_root_relpath = NULL;
+  if (sab->moved_from_op_depth)
+    *sab->moved_from_op_depth = 0;
 
   {
     svn_sqlite__stmt_t *stmt;
@@ -9822,8 +9908,9 @@ scan_addition_txn(void *baton,
                   SVN_ERR(get_moved_from_info(sab->status,
                                               sab->moved_from_relpath,
                                               sab->moved_from_op_root_relpath,
-                                              op_root_relpath, wcroot,
-                                              local_relpath,
+                                              op_root_relpath,
+                                              sab->moved_from_op_depth,
+                                              wcroot, local_relpath,
                                               sab->result_pool,
                                               scratch_pool));
                 else if (sab->status)
@@ -9944,6 +10031,7 @@ scan_addition(svn_wc__db_status_t *statu
               svn_revnum_t *original_revision,
               const char **moved_from_relpath,
               const char **moved_from_op_root_relpath,
+              apr_int64_t *moved_from_op_depth,
               svn_wc__db_wcroot_t *wcroot,
               const char *local_relpath,
               apr_pool_t *result_pool,
@@ -9960,6 +10048,7 @@ scan_addition(svn_wc__db_status_t *statu
   sab.original_revision = original_revision;
   sab.moved_from_relpath = moved_from_relpath;
   sab.moved_from_op_root_relpath = moved_from_op_root_relpath;
+  sab.moved_from_op_depth = moved_from_op_depth;
   sab.result_pool = result_pool;
 
   return svn_error_trace(svn_wc__db_with_txn(wcroot, local_relpath,
@@ -10006,8 +10095,8 @@ svn_wc__db_scan_addition(svn_wc__db_stat
   SVN_ERR(scan_addition(status, &op_root_relpath, repos_relpath, repos_id_p,
                         original_repos_relpath, original_repos_id_p,
                         original_revision, &moved_from_relpath,
-                        &moved_from_op_root_relpath, wcroot, local_relpath,
-                        result_pool, scratch_pool));
+                        &moved_from_op_root_relpath, NULL,
+                        wcroot, local_relpath, result_pool, scratch_pool));
 
   if (op_root_abspath)
     *op_root_abspath = svn_dirent_join(wcroot->abspath, op_root_relpath,

Modified: subversion/trunk/subversion/tests/libsvn_wc/db-test.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/libsvn_wc/db-test.c?rev=1300933&r1=1300932&r2=1300933&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/libsvn_wc/db-test.c (original)
+++ subversion/trunk/subversion/tests/libsvn_wc/db-test.c Thu Mar 15 11:49:51 2012
@@ -228,8 +228,12 @@ static const char * const TESTING_DATA =
   "  1, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 2, " TIME_2s ", '" AUTHOR_2 "',"
   "  10, null, null, null);"
   "insert into nodes values ("
-  "  1, 'moved/file', 0, 'moved', 2, 'moved/file', 2, 'base-deleted',"
-  "  0, 'J/J-d', 'file', '()', null, '$sha1$" SHA1_1 "', null, 2, " TIME_2s ", '" AUTHOR_2 "',"
+  "  1, 'moved/file', 0, 'moved', 2, 'moved/file', 2, 'normal',"
+  "  0, null, 'file', '()', null, '$sha1$" SHA1_1 "', null, 2, " TIME_2s ", '" AUTHOR_2 "',"
+  "  10, null, null, null);"
+  "insert into nodes values ("
+  "  1, 'moved/file', 2, 'moved', 2, 'moved/file', 2, 'base-deleted',"
+  "  0, 'J/J-d', 'file', '()', null, null, null, null, null, null,"
   "  10, null, null, null);"
   "insert into nodes values ("
   "  1, 'J/J-e', 1, 'J', null, null, null, 'normal',"

Modified: subversion/trunk/subversion/tests/libsvn_wc/op-depth-test.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/libsvn_wc/op-depth-test.c?rev=1300933&r1=1300932&r2=1300933&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/libsvn_wc/op-depth-test.c (original)
+++ subversion/trunk/subversion/tests/libsvn_wc/op-depth-test.c Thu Mar 15 11:49:51 2012
@@ -328,7 +328,7 @@ typedef struct nodes_row_t {
 
 /* Macro for filling in the REPO_* fields of a non-base NODES_ROW_T
  * that has no copy-from info. */
-#define NO_COPY_FROM SVN_INVALID_REVNUM, NULL
+#define NO_COPY_FROM SVN_INVALID_REVNUM, NULL, FALSE
 #define MOVED_HERE FALSE, NULL, TRUE
 
 /* Return a human-readable string representing ROW. */
@@ -1713,8 +1713,8 @@ test_wc_move(const svn_test_opts_t *opts
       { 0, "",           "normal",       1, "" },
       { 0, "A",          "normal",       1, "A" },
       { 0, "A/B",        "normal",       1, "A/B" },
-      { 0, "A/B/C",      "normal",       1, "A/B/C", FALSE, "A/B/C-move" },
-      { 3, "A/B/C",      "base-deleted", NO_COPY_FROM },
+      { 0, "A/B/C",      "normal",       1, "A/B/C"},
+      { 3, "A/B/C",      "base-deleted", NO_COPY_FROM, "A/B/C-move" },
       { 3, "A/B/C-move", "normal",       1, "A/B/C", MOVED_HERE },
       { 0 }
     };
@@ -1726,13 +1726,13 @@ test_wc_move(const svn_test_opts_t *opts
     nodes_row_t rows[] = {
       { 0, "",                "normal",       1, "" },
       { 0, "A",               "normal",       1, "A" },
-      { 0, "A/B",             "normal",       1, "A/B", FALSE, "A/B-move" },
-      { 0, "A/B/C",           "normal",       1, "A/B/C", FALSE, "A/B-move/C-move" },
-      { 2, "A/B",             "base-deleted", NO_COPY_FROM },
-      { 2, "A/B/C",           "base-deleted", NO_COPY_FROM },
+      { 0, "A/B",             "normal",       1, "A/B"},
+      { 0, "A/B/C",           "normal",       1, "A/B/C"},
+      { 2, "A/B",             "base-deleted", NO_COPY_FROM, "A/B-move" },
+      { 2, "A/B/C",           "base-deleted", NO_COPY_FROM},
       { 2, "A/B-move",        "normal",       1, "A/B", MOVED_HERE },
       { 2, "A/B-move/C",      "normal",       1, "A/B/C", MOVED_HERE },
-      { 3, "A/B-move/C",      "base-deleted", NO_COPY_FROM },
+      { 3, "A/B-move/C",      "base-deleted", NO_COPY_FROM, "A/B-move/C-move" },
       { 3, "A/B-move/C-move", "normal",       1, "A/B/C", MOVED_HERE },
       { 0 }
     };
@@ -3773,8 +3773,8 @@ nested_moves_child_first(const svn_test_
       {0, "",       "normal",       1, ""},
       {0, "A",      "normal",       1, "A"},
       {0, "A/B",    "normal",       1, "A/B"},
-      {0, "A/B/C",  "normal",       1, "A/B/C", FALSE, "A/B/C2"},
-      {3, "A/B/C",  "base-deleted", NO_COPY_FROM},
+      {0, "A/B/C",  "normal",       1, "A/B/C"},
+      {3, "A/B/C",  "base-deleted", NO_COPY_FROM, "A/B/C2"},
       {3, "A/B/C2", "normal",       1, "A/B/C", MOVED_HERE},
       {0}
     };
@@ -3785,13 +3785,13 @@ nested_moves_child_first(const svn_test_
     nodes_row_t nodes[] = {
       {0, "",        "normal",       1, ""},
       {0, "A",       "normal",       1, "A"},
-      {0, "A/B",     "normal",       1, "A/B",   FALSE, "A/B2"},
-      {0, "A/B/C",   "normal",       1, "A/B/C", FALSE, "A/B2/C2"},
-      {2, "A/B",     "base-deleted", NO_COPY_FROM},
+      {0, "A/B",     "normal",       1, "A/B"},
+      {0, "A/B/C",   "normal",       1, "A/B/C"},
+      {2, "A/B",     "base-deleted", NO_COPY_FROM, "A/B2"},
       {2, "A/B/C",   "base-deleted", NO_COPY_FROM},
       {2, "A/B2",    "normal",       1, "A/B",   MOVED_HERE},
       {2, "A/B2/C",  "normal",       1, "A/B/C", MOVED_HERE},
-      {3, "A/B2/C",  "base-deleted", NO_COPY_FROM},
+      {3, "A/B2/C",  "base-deleted", NO_COPY_FROM, "A/B2/C2"},
       {3, "A/B2/C2", "normal",       1, "A/B/C", MOVED_HERE},
       {0}
     };
@@ -3801,20 +3801,20 @@ nested_moves_child_first(const svn_test_
   {
     nodes_row_t nodes[] = {
       {0, "",        "normal",       1, ""},
-      {0, "A",       "normal",       1, "A",     FALSE, "A2"},
-      {0, "A/B",     "normal",       1, "A/B",   FALSE, "A2/B2"},
-      {0, "A/B/C",   "normal",       1, "A/B/C", FALSE, "A2/B2/C2"},
-      {1, "A",       "base-deleted", NO_COPY_FROM},
+      {0, "A",       "normal",       1, "A"},
+      {0, "A/B",     "normal",       1, "A/B"},
+      {0, "A/B/C",   "normal",       1, "A/B/C"},
+      {1, "A",       "base-deleted", NO_COPY_FROM, "A2"},
       {1, "A/B",     "base-deleted", NO_COPY_FROM},
       {1, "A/B/C",   "base-deleted", NO_COPY_FROM},
       {1, "A2",      "normal",       1, "A",     MOVED_HERE},
       {1, "A2/B",    "normal",       1, "A/B",   MOVED_HERE},
       {1, "A2/B/C",  "normal",       1, "A/B/C", MOVED_HERE},
-      {2, "A2/B",    "base-deleted", NO_COPY_FROM},
+      {2, "A2/B",    "base-deleted", NO_COPY_FROM, "A2/B2"},
       {2, "A2/B/C",  "base-deleted", NO_COPY_FROM},
       {2, "A2/B2",   "normal",       1, "A/B",   MOVED_HERE},
       {2, "A2/B2/C", "normal",       1, "A/B/C", MOVED_HERE},
-      {3, "A2/B2/C", "base-deleted", NO_COPY_FROM},
+      {3, "A2/B2/C", "base-deleted", NO_COPY_FROM, "A2/B2/C2"},
       {3, "A2/B2/C2","normal",       1, "A/B/C", MOVED_HERE},
       {0}
     };
@@ -3827,10 +3827,10 @@ nested_moves_child_first(const svn_test_
   {
     nodes_row_t nodes[] = {
       {0, "",        "normal",       1, ""},
-      {0, "A",       "normal",       1, "A",     FALSE, "A2"},
+      {0, "A",       "normal",       1, "A"},
       {0, "A/B",     "normal",       1, "A/B"},
       {0, "A/B/C",   "normal",       1, "A/B/C"},
-      {1, "A",       "base-deleted", NO_COPY_FROM},
+      {1, "A",       "base-deleted", NO_COPY_FROM, "A2"},
       {1, "A/B",     "base-deleted", NO_COPY_FROM},
       {1, "A/B/C",   "base-deleted", NO_COPY_FROM},
       {1, "A2",      "normal",       1, "A",     MOVED_HERE},
@@ -3871,10 +3871,10 @@ nested_moves_child_last(const svn_test_o
   {
     nodes_row_t nodes[] = {
       {0, "",        "normal",       1, ""},
-      {0, "A",       "normal",       1, "A",     FALSE, "A2"},
+      {0, "A",       "normal",       1, "A"},
       {0, "A/B",     "normal",       1, "A/B"},
       {0, "A/B/C",   "normal",       1, "A/B/C"},
-      {1, "A",       "base-deleted", NO_COPY_FROM},
+      {1, "A",       "base-deleted", NO_COPY_FROM, "A2"},
       {1, "A/B",     "base-deleted", NO_COPY_FROM},
       {1, "A/B/C",   "base-deleted", NO_COPY_FROM},
       {1, "A2",      "normal",       1, "A",     MOVED_HERE},
@@ -3888,16 +3888,16 @@ nested_moves_child_last(const svn_test_o
   {
     nodes_row_t nodes[] = {
       {0, "",        "normal",       1, ""},
-      {0, "A",       "normal",       1, "A",     FALSE, "A2"},
-      {0, "A/B",     "normal",       1, "A/B",   FALSE, "A2/B2"},
+      {0, "A",       "normal",       1, "A"},
+      {0, "A/B",     "normal",       1, "A/B"},
       {0, "A/B/C",   "normal",       1, "A/B/C"},
-      {1, "A",       "base-deleted", NO_COPY_FROM},
+      {1, "A",       "base-deleted", NO_COPY_FROM, "A2"},
       {1, "A/B",     "base-deleted", NO_COPY_FROM},
       {1, "A/B/C",   "base-deleted", NO_COPY_FROM},
       {1, "A2",      "normal",       1, "A",     MOVED_HERE},
       {1, "A2/B",    "normal",       1, "A/B",   MOVED_HERE},
       {1, "A2/B/C",  "normal",       1, "A/B/C", MOVED_HERE},
-      {2, "A2/B",    "base-deleted", NO_COPY_FROM},
+      {2, "A2/B",    "base-deleted", NO_COPY_FROM, "A2/B2"},
       {2, "A2/B/C",  "base-deleted", NO_COPY_FROM},
       {2, "A2/B2",   "normal",       1, "A/B",   MOVED_HERE},
       {2, "A2/B2/C", "normal",       1, "A/B/C", MOVED_HERE},
@@ -3909,20 +3909,20 @@ nested_moves_child_last(const svn_test_o
   {
     nodes_row_t nodes[] = {
       {0, "",        "normal",       1, ""},
-      {0, "A",       "normal",       1, "A",     FALSE, "A2"},
-      {0, "A/B",     "normal",       1, "A/B",   FALSE, "A2/B2"},
-      {0, "A/B/C",   "normal",       1, "A/B/C", FALSE, "A2/B2/C2"},
-      {1, "A",       "base-deleted", NO_COPY_FROM},
+      {0, "A",       "normal",       1, "A"},
+      {0, "A/B",     "normal",       1, "A/B"},
+      {0, "A/B/C",   "normal",       1, "A/B/C"},
+      {1, "A",       "base-deleted", NO_COPY_FROM, "A2"},
       {1, "A/B",     "base-deleted", NO_COPY_FROM},
       {1, "A/B/C",   "base-deleted", NO_COPY_FROM},
       {1, "A2",      "normal",       1, "A",     MOVED_HERE},
       {1, "A2/B",    "normal",       1, "A/B",   MOVED_HERE},
       {1, "A2/B/C",  "normal",       1, "A/B/C", MOVED_HERE},
-      {2, "A2/B",    "base-deleted", NO_COPY_FROM},
+      {2, "A2/B",    "base-deleted", NO_COPY_FROM, "A2/B2"},
       {2, "A2/B/C",  "base-deleted", NO_COPY_FROM},
       {2, "A2/B2",   "normal",       1, "A/B",   MOVED_HERE},
       {2, "A2/B2/C", "normal",       1, "A/B/C", MOVED_HERE},
-      {3, "A2/B2/C", "base-deleted", NO_COPY_FROM},
+      {3, "A2/B2/C", "base-deleted", NO_COPY_FROM, "A2/B2/C2"},
       {3, "A2/B2/C2","normal",       1, "A/B/C", MOVED_HERE},
       {0}
     };
@@ -3935,10 +3935,10 @@ nested_moves_child_last(const svn_test_o
   {
     nodes_row_t nodes[] = {
       {0, "",        "normal",       1, ""},
-      {0, "A",       "normal",       1, "A",     FALSE, "A2"},
+      {0, "A",       "normal",       1, "A"},
       {0, "A/B",     "normal",       1, "A/B"},
       {0, "A/B/C",   "normal",       1, "A/B/C"},
-      {1, "A",       "base-deleted", NO_COPY_FROM},
+      {1, "A",       "base-deleted", NO_COPY_FROM, "A2"},
       {1, "A/B",     "base-deleted", NO_COPY_FROM},
       {1, "A/B/C",   "base-deleted", NO_COPY_FROM},
       {1, "A2",      "normal",       1, "A",     MOVED_HERE},
@@ -3976,8 +3976,7 @@ move_in_copy(const svn_test_opts_t *opts
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
   }
-  SVN_ERR(wc_move(&b, "A2/B", "A2/B2")); /* ### Moved-here gets recorded, but
-                                            not moved-to. */
+  SVN_ERR(wc_move(&b, "A2/B", "A2/B2"));
   {
     nodes_row_t nodes[] = {
       {0, "",      "normal",       1, ""},
@@ -3985,8 +3984,8 @@ move_in_copy(const svn_test_opts_t *opts
       {0, "A/B",   "normal",       1, "A/B"},
       {1, "A2",    "normal",       1, "A"},
       {1, "A2/B",  "normal",       1, "A/B"},
-      {2, "A2/B",  "base-deleted", NO_COPY_FROM},
-      {2, "A2/B2", "normal",       1, "A/B"},
+      {2, "A2/B",  "base-deleted", NO_COPY_FROM, "A2/B2"},
+      {2, "A2/B2", "normal",       1, "A/B", MOVED_HERE},
       {0}
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
@@ -4024,9 +4023,7 @@ move_in_replace(const svn_test_opts_t *o
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
   }
-  SVN_ERR(wc_move(&b, "A/B", "A/B2")); /* ### Moved-to gets recorded on A/B
-                                          at op-depth=0, that's not the node
-                                          that got moved. */
+  SVN_ERR(wc_move(&b, "A/B", "A/B2"));
   {
     nodes_row_t nodes[] = {
       {0, "",     "normal",       1, ""},
@@ -4036,7 +4033,7 @@ move_in_replace(const svn_test_opts_t *o
       {0, "X/B",  "normal",       1, "X/B"},
       {1, "A",    "normal",       1, "X"},
       {1, "A/B",  "normal",       1, "X/B"},
-      {2, "A/B",  "base-deleted", NO_COPY_FROM},
+      {2, "A/B",  "base-deleted", NO_COPY_FROM, "A/B2"},
       {2, "A/B2", "normal",       1, "X/B", MOVED_HERE},
       {0}
     };
@@ -4075,9 +4072,9 @@ copy_a_move(const svn_test_opts_t *opts,
       {0, "",      "normal",       1, ""},
       {0, "A",     "normal",       1, "A"},
       {0, "A/B",   "normal",       1, "A/B"},
-      {0, "A/B/C", "normal",       1, "A/B/C", FALSE, "A/C2"},
+      {0, "A/B/C", "normal",       1, "A/B/C"},
       {2, "A/C2",  "normal",       1, "A/B/C", MOVED_HERE},
-      {3, "A/B/C", "base-deleted", NO_COPY_FROM},
+      {3, "A/B/C", "base-deleted", NO_COPY_FROM, "A/C2"},
       {0}
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
@@ -4091,14 +4088,14 @@ copy_a_move(const svn_test_opts_t *opts,
       {0, "",       "normal",       1, ""},
       {0, "A",      "normal",       1, "A"},
       {0, "A/B",    "normal",       1, "A/B"},
-      {0, "A/B/C",  "normal",       1, "A/B/C", FALSE, "A/C2"},
+      {0, "A/B/C",  "normal",       1, "A/B/C"},
       {2, "A/C2",   "normal",       1, "A/B/C", MOVED_HERE},
-      {3, "A/B/C",  "base-deleted", NO_COPY_FROM},
+      {3, "A/B/C",  "base-deleted", NO_COPY_FROM, "A/C2"},
       {1, "A2",     "normal",       1, "A"},
       {1, "A2/B",   "normal",       1, "A/B"},
       {1, "A2/B/C", "normal",       1, "A/B/C"},
-      {2, "A2/C2",  "normal",       1, "A/B/C"},
-      {3, "A2/B/C", "base-deleted", NO_COPY_FROM},
+      {2, "A2/C2",  "normal",       1, "A/B/C"},   /* MOVED_HERE? */
+      {3, "A2/B/C", "base-deleted", NO_COPY_FROM}, /* "A2/C2"? */
       {0}
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
@@ -4128,12 +4125,12 @@ move_to_swap(const svn_test_opts_t *opts
     nodes_row_t nodes[] = {
       {0, "",    "normal",       1, ""},
       {0, "A",   "normal",       1, "A"},
-      {0, "A/B", "normal",       1, "A/B", FALSE, "X/B"},
+      {0, "A/B", "normal",       1, "A/B"},
       {0, "X",   "normal",       1, "X"},
-      {0, "X/Y", "normal",       1, "X/Y", FALSE, "A/Y"},
-      {2, "A/B", "base-deleted", NO_COPY_FROM},
+      {0, "X/Y", "normal",       1, "X/Y"},
+      {2, "A/B", "base-deleted", NO_COPY_FROM, "X/B"},
       {2, "A/Y", "normal",       1, "X/Y", MOVED_HERE},
-      {2, "X/Y", "base-deleted", NO_COPY_FROM},
+      {2, "X/Y", "base-deleted", NO_COPY_FROM, "A/Y"},
       {2, "X/B", "normal",       1, "A/B", MOVED_HERE},
       {0}
     };
@@ -4141,26 +4138,69 @@ move_to_swap(const svn_test_opts_t *opts
   }
 
   SVN_ERR(wc_move(&b, "A", "A2"));
+
+  {
+    nodes_row_t nodes[] = {
+      {0, "",     "normal",       1, ""},
+      {0, "A",    "normal",       1, "A"},
+      {0, "A/B",  "normal",       1, "A/B"},
+      {0, "X",    "normal",       1, "X"},
+      {0, "X/Y",  "normal",       1, "X/Y"},
+      {1, "A",    "base-deleted", NO_COPY_FROM, "A2"},
+      {1, "A/B",  "base-deleted", NO_COPY_FROM},
+      {1, "A2",   "normal",       1, "A", MOVED_HERE},
+      {1, "A2/B", "normal",       1, "A/B", MOVED_HERE},
+      {2, "A2/B", "base-deleted", NO_COPY_FROM, "X/B"},
+      {2, "A2/Y", "normal",       1, "X/Y", MOVED_HERE},
+      {2, "X/Y",  "base-deleted", NO_COPY_FROM, "A2/Y"},
+      {2, "X/B",  "normal",       1, "A/B", MOVED_HERE},
+      {0}
+    };
+    SVN_ERR(check_db_rows(&b, "", nodes));
+  }
+
   SVN_ERR(wc_move(&b, "X", "A"));
+
+  {
+    nodes_row_t nodes[] = {
+      {0, "",     "normal",       1, ""},
+      {0, "A",    "normal",       1, "A"},
+      {0, "A/B",  "normal",       1, "A/B"},
+      {0, "X",    "normal",       1, "X"},
+      {0, "X/Y",  "normal",       1, "X/Y"},
+      {1, "A",    "normal",       1, "X", FALSE, "A2", TRUE},
+      {1, "A/B",  "base-deleted", NO_COPY_FROM},
+      {1, "A/Y",  "normal",       1, "X/Y", MOVED_HERE},
+      {1, "A2",   "normal",       1, "A", MOVED_HERE},
+      {1, "A2/B", "normal",       1, "A/B", MOVED_HERE},
+      {1, "X",    "base-deleted", NO_COPY_FROM, "A"},
+      {1, "X/Y",  "base-deleted", NO_COPY_FROM},
+      {2, "A/B",  "normal",       1, "A/B", MOVED_HERE},
+      {2, "A/Y",  "base-deleted", NO_COPY_FROM, "A2/Y"},
+      {2, "A2/B", "base-deleted", NO_COPY_FROM, "A/B"},
+      {2, "A2/Y", "normal",       1, "X/Y", MOVED_HERE},
+      {0}
+    };
+    SVN_ERR(check_db_rows(&b, "", nodes));
+  }
+
   SVN_ERR(wc_move(&b, "A2", "X"));
 
-  /* Is this correct or should A/Y and X/B at op-depth=1 be marked
-     moved-here? */
   {
     nodes_row_t nodes[] = {
       {0, "",    "normal",       1, ""},
-      {0, "A",   "normal",       1, "A",   FALSE, "X"},
-      {0, "A/B", "normal",       1, "A/B", FALSE, "A/B"},
-      {0, "X",   "normal",       1, "X",   FALSE, "A"},
-      {0, "X/Y", "normal",       1, "X/Y", FALSE, "X/Y"},
-      {1, "A",   "normal",       1, "X",   MOVED_HERE},
+      {0, "A",   "normal",       1, "A"},
+      {0, "A/B", "normal",       1, "A/B"},
+      {0, "X",   "normal",       1, "X"},
+      {0, "X/Y", "normal",       1, "X/Y"},
+      {1, "A",   "normal",       1, "X",   FALSE, "X", TRUE},
       {1, "A/Y", "normal",       1, "X/Y", MOVED_HERE},
       {1, "A/B", "base-deleted", NO_COPY_FROM},
-      {1, "X",   "normal",       1, "A",   MOVED_HERE},
+      {1, "X",   "normal",       1, "A",   FALSE, "A", TRUE},
       {1, "X/B", "normal",       1, "A/B", MOVED_HERE},
       {1, "X/Y", "base-deleted", NO_COPY_FROM},
-      {2, "A/Y", "base-deleted", NO_COPY_FROM},
-      {2, "X/B", "base-deleted", NO_COPY_FROM},
+      {2, "A/Y", "base-deleted", NO_COPY_FROM, "X/Y"},
+      {2, "X/B", "base-deleted", NO_COPY_FROM, "A/B"},
       {2, "A/B", "normal",       1, "A/B", MOVED_HERE},
       {2, "X/Y", "normal",       1, "X/Y", MOVED_HERE},
       {0}
@@ -4178,14 +4218,14 @@ move_to_swap(const svn_test_opts_t *opts
   {
     nodes_row_t nodes[] = {
       {0, "",    "normal",       1, ""},
-      {0, "A",   "normal",       1, "A",   FALSE, "X"},
+      {0, "A",   "normal",       1, "A"},
       {0, "A/B", "normal",       1, "A/B"},
-      {0, "X",   "normal",       1, "X",   FALSE, "A"},
+      {0, "X",   "normal",       1, "X"},
       {0, "X/Y", "normal",       1, "X/Y"},
-      {1, "A",   "normal",       1, "X",   MOVED_HERE},
+      {1, "A",   "normal",       1, "X",   FALSE, "X", TRUE},
       {1, "A/Y", "normal",       1, "X/Y", MOVED_HERE},
       {1, "A/B", "base-deleted", NO_COPY_FROM},
-      {1, "X",   "normal",       1, "A",   MOVED_HERE},
+      {1, "X",   "normal",       1, "A",   FALSE, "A", TRUE},
       {1, "X/B", "normal",       1, "A/B", MOVED_HERE},
       {1, "X/Y", "base-deleted", NO_COPY_FROM},
       {0}
@@ -4199,18 +4239,18 @@ move_to_swap(const svn_test_opts_t *opts
   {
     nodes_row_t nodes[] = {
       {0, "",    "normal",       1, ""},
-      {0, "A",   "normal",       1, "A",   FALSE, "X"},
-      {0, "A/B", "normal",       1, "A/B", FALSE, "A/B"},
-      {0, "X",   "normal",       1, "X",   FALSE, "A"},
-      {0, "X/Y", "normal",       1, "X/Y", FALSE, "X/Y"},
-      {1, "A",   "normal",       1, "X",   MOVED_HERE},
+      {0, "A",   "normal",       1, "A"},
+      {0, "A/B", "normal",       1, "A/B"},
+      {0, "X",   "normal",       1, "X"},
+      {0, "X/Y", "normal",       1, "X/Y"},
+      {1, "A",   "normal",       1, "X",   FALSE, "X", TRUE},
       {1, "A/Y", "normal",       1, "X/Y", MOVED_HERE},
       {1, "A/B", "base-deleted", NO_COPY_FROM},
-      {1, "X",   "normal",       1, "A",   MOVED_HERE},
+      {1, "X",   "normal",       1, "A",   FALSE, "A", TRUE},
       {1, "X/B", "normal",       1, "A/B", MOVED_HERE},
       {1, "X/Y", "base-deleted", NO_COPY_FROM},
-      {2, "A/Y", "base-deleted", NO_COPY_FROM},
-      {2, "X/B", "base-deleted", NO_COPY_FROM},
+      {2, "A/Y", "base-deleted", NO_COPY_FROM, "X/Y"},
+      {2, "X/B", "base-deleted", NO_COPY_FROM, "A/B"},
       {2, "A/B", "normal",       1, "A/B", MOVED_HERE},
       {2, "X/Y", "normal",       1, "X/Y", MOVED_HERE},
       {0}
@@ -4227,10 +4267,10 @@ revert_nested_move(const svn_test_opts_t
   svn_test__sandbox_t b;
   nodes_row_t nodes_A_moved[] = {
     {0, "",       "normal",       1, ""},
-    {0, "A",      "normal",       1, "A",     FALSE, "A2"},
+    {0, "A",      "normal",       1, "A"},
     {0, "A/B",    "normal",       1, "A/B"},
     {0, "A/B/C",  "normal",       1, "A/B/C"},
-    {1, "A",      "base-deleted", NO_COPY_FROM},
+    {1, "A",      "base-deleted", NO_COPY_FROM, "A2"},
     {1, "A/B",    "base-deleted", NO_COPY_FROM},
     {1, "A/B/C",  "base-deleted", NO_COPY_FROM},
     {1, "A2",     "normal",       1, "A",     MOVED_HERE},
@@ -4240,16 +4280,16 @@ revert_nested_move(const svn_test_opts_t
   };
   nodes_row_t nodes_AB_moved[] = {
     {0, "",        "normal",       1, ""},
-    {0, "A",       "normal",       1, "A",     FALSE, "A2"},
-    {0, "A/B",     "normal",       1, "A/B",   FALSE, "A2/B2"},
+    {0, "A",       "normal",       1, "A"},
+    {0, "A/B",     "normal",       1, "A/B"},
     {0, "A/B/C",   "normal",       1, "A/B/C"},
-    {1, "A",       "base-deleted", NO_COPY_FROM},
+    {1, "A",       "base-deleted", NO_COPY_FROM, "A2"},
     {1, "A/B",     "base-deleted", NO_COPY_FROM},
     {1, "A/B/C",   "base-deleted", NO_COPY_FROM},
     {1, "A2",      "normal",       1, "A",     MOVED_HERE},
     {1, "A2/B",    "normal",       1, "A/B",   MOVED_HERE},
     {1, "A2/B/C",  "normal",       1, "A/B/C", MOVED_HERE},
-    {2, "A2/B",    "base-deleted", NO_COPY_FROM},
+    {2, "A2/B",    "base-deleted", NO_COPY_FROM, "A2/B2"},
     {2, "A2/B/C",  "base-deleted", NO_COPY_FROM},
     {2, "A2/B2",   "normal",       1, "A/B",   MOVED_HERE},
     {2, "A2/B2/C", "normal",       1, "A/B/C", MOVED_HERE},
@@ -4257,20 +4297,20 @@ revert_nested_move(const svn_test_opts_t
   };
   nodes_row_t nodes_ABC_moved[] = {
     {0, "",         "normal",       1, ""},
-    {0, "A",        "normal",       1, "A",     FALSE, "A2"},
-    {0, "A/B",      "normal",       1, "A/B",   FALSE, "A2/B2"},
-    {0, "A/B/C",    "normal",       1, "A/B/C", FALSE, "A2/B2/C2"},
-    {1, "A",        "base-deleted", NO_COPY_FROM},
+    {0, "A",        "normal",       1, "A"},
+    {0, "A/B",      "normal",       1, "A/B"},
+    {0, "A/B/C",    "normal",       1, "A/B/C"},
+    {1, "A",        "base-deleted", NO_COPY_FROM, "A2"},
     {1, "A/B",      "base-deleted", NO_COPY_FROM},
     {1, "A/B/C",    "base-deleted", NO_COPY_FROM},
     {1, "A2",       "normal",       1, "A",     MOVED_HERE},
     {1, "A2/B",     "normal",       1, "A/B",   MOVED_HERE},
     {1, "A2/B/C",   "normal",       1, "A/B/C", MOVED_HERE},
-    {2, "A2/B",     "base-deleted", NO_COPY_FROM},
+    {2, "A2/B",     "base-deleted", NO_COPY_FROM, "A2/B2"},
     {2, "A2/B/C",   "base-deleted", NO_COPY_FROM},
     {2, "A2/B2",    "normal",       1, "A/B",   MOVED_HERE},
     {2, "A2/B2/C",  "normal",       1, "A/B/C", MOVED_HERE},
-    {3, "A2/B2/C",  "base-deleted", NO_COPY_FROM},
+    {3, "A2/B2/C",  "base-deleted", NO_COPY_FROM, "A2/B2/C2"},
     {3, "A2/B2/C2", "normal",       1, "A/B/C", MOVED_HERE},
     {0}
   };
@@ -4308,6 +4348,13 @@ revert_nested_move(const svn_test_opts_t
   SVN_ERR(wc_revert(&b, "A2/B2", svn_depth_infinity));
   SVN_ERR(check_db_rows(&b, "", nodes_A_moved));
 
+  /* Check moves in reverse order */
+  SVN_ERR(wc_revert(&b, "", svn_depth_infinity));
+  SVN_ERR(wc_move(&b, "A/B/C", "A/B/C2"));
+  SVN_ERR(wc_move(&b, "A/B", "A/B2"));
+  SVN_ERR(wc_move(&b, "A", "A2"));
+  SVN_ERR(check_db_rows(&b, "", nodes_ABC_moved));
+
   return SVN_NO_ERROR;
 }
 
@@ -4333,34 +4380,30 @@ move_on_move(const svn_test_opts_t *opts
     nodes_row_t nodes[] = {
       {0, "",         "normal",       1, ""},
       {0, "A",        "normal",       1, "A"},
-      {0, "A/B",      "normal",       1, "A/B", FALSE, "B2"},
+      {0, "A/B",      "normal",       1, "A/B"},
       {0, "X",        "normal",       1, "X"},
       {0, "X/B",      "normal",       1, "X/B"},
       {1, "B2",       "normal",       1, "A/B", MOVED_HERE},
       {1, "A",        "normal",       1, "X"},
-      {1, "A/B",      "normal",       1, "X/B"},
+      {1, "A/B",      "normal",       1, "X/B", FALSE, "B2"},
       {0}
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
   }
 
-  /* A/B to B2 is already recorded in A/B but the copy has given us
-     another A/B that we can move.  A second move overwites the first
-     move stored in A/B even though it's a different node being moved,
-     and that breaks the recording of the move to B2. */
   SVN_ERR(wc_move(&b, "A/B", "B3"));
   {
     nodes_row_t nodes[] = {
       {0, "",         "normal",       1, ""},
       {0, "A",        "normal",       1, "A"},
-      {0, "A/B",      "normal",       1, "A/B",   FALSE, "B2"}, /* XFAIL */
+      {0, "A/B",      "normal",       1, "A/B"},
       {0, "X",        "normal",       1, "X"},
       {0, "X/B",      "normal",       1, "X/B"},
       {1, "B2",       "normal",       1, "A/B",   MOVED_HERE},
       {1, "B3",       "normal",       1, "X/B",   MOVED_HERE},
       {1, "A",        "normal",       1, "X"},
-      {1, "A/B",      "normal",       1, "X/B"},         /* moved_to=B3? */
-      {2, "A/B",      "base-deleted", NO_COPY_FROM},
+      {1, "A/B",      "normal",       1, "X/B", FALSE, "B2"},
+      {2, "A/B",      "base-deleted", NO_COPY_FROM, "B3"},
       {0}
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
@@ -4390,37 +4433,33 @@ move_on_move2(const svn_test_opts_t *opt
   {
     nodes_row_t nodes[] = {
       {0, "",         "normal",       1, ""},
-      {0, "A",        "normal",       1, "A",   FALSE, "A2"},
+      {0, "A",        "normal",       1, "A"},
       {0, "A/B",      "normal",       1, "A/B"},
       {0, "X",        "normal",       1, "X"},
       {0, "X/B",      "normal",       1, "X/B"},
       {1, "A2",       "normal",       1, "A",   MOVED_HERE},
       {1, "A2/B",     "normal",       1, "A/B", MOVED_HERE},
-      {1, "A",        "normal",       1, "X"},
+      {1, "A",        "normal",       1, "X", FALSE, "A2"},
       {1, "A/B",      "normal",       1, "X/B"},
       {0}
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
   }
 
-  /* A/B is already moved to A2/B but there is no explicit moved_to,
-     we derive it from A.  The copy has given us another A/B that we
-     can move doing so stores explicit moved_to in A/B that breaks the
-     recording of the first move to A2/B. */
   SVN_ERR(wc_move(&b, "A/B", "B3"));
   {
     nodes_row_t nodes[] = {
       {0, "",         "normal",       1, ""},
-      {0, "A",        "normal",       1, "A",   FALSE, "A2"},
-      {0, "A/B",      "normal",       1, "A/B"},               /* XFAIL */
+      {0, "A",        "normal",       1, "A"},
+      {0, "A/B",      "normal",       1, "A/B"},
       {0, "X",        "normal",       1, "X"},
       {0, "X/B",      "normal",       1, "X/B"},
       {1, "A2",       "normal",       1, "A",   MOVED_HERE},
       {1, "A2/B",     "normal",       1, "A/B", MOVED_HERE},
       {1, "B3",       "normal",       1, "X/B", MOVED_HERE},
-      {1, "A",        "normal",       1, "X"},
-      {1, "A/B",      "normal",       1, "X/B"},           /* moved_to=B3? */
-      {2, "A/B",      "base-deleted", NO_COPY_FROM},
+      {1, "A",        "normal",       1, "X", FALSE, "A2"},
+      {1, "A/B",      "normal",       1, "X/B"},
+      {2, "A/B",      "base-deleted", NO_COPY_FROM, "B3"},
       {0}
     };
     SVN_ERR(check_db_rows(&b, "", nodes));
@@ -4450,9 +4489,9 @@ move_added(const svn_test_opts_t *opts, 
   {
     nodes_row_t nodes[] = {
       {0, "",         "normal",       1, ""},
-      {0, "A",        "normal",       1, "A",   FALSE, "A2"},
+      {0, "A",        "normal",       1, "A"},
       {0, "A/B",      "normal",       1, "A/B"},
-      {1, "A",        "base-deleted", NO_COPY_FROM},
+      {1, "A",        "base-deleted", NO_COPY_FROM, "A2"},
       {1, "A/B",      "base-deleted", NO_COPY_FROM},
       {1, "A2",       "normal",       1, "A",   MOVED_HERE},
       {1, "A2/B",     "normal",       1, "A/B", MOVED_HERE},
@@ -4540,9 +4579,9 @@ struct svn_test_descriptor_t test_funcs[
                        "nested_moves_child_first"),
     SVN_TEST_OPTS_PASS(nested_moves_child_last,
                        "nested_moves_child_last"),
-    SVN_TEST_OPTS_XFAIL(move_in_copy,
+    SVN_TEST_OPTS_PASS(move_in_copy,
                        "move_in_copy"),
-    SVN_TEST_OPTS_XFAIL(move_in_replace,
+    SVN_TEST_OPTS_PASS(move_in_replace,
                        "move_in_replace"),
     SVN_TEST_OPTS_PASS(copy_a_move,
                        "copy_a_move"),
@@ -4550,9 +4589,9 @@ struct svn_test_descriptor_t test_funcs[
                        "move_to_swap"),
     SVN_TEST_OPTS_PASS(revert_nested_move,
                        "revert_nested_move"),
-    SVN_TEST_OPTS_XFAIL(move_on_move,
+    SVN_TEST_OPTS_PASS(move_on_move,
                        "move_on_move"),
-    SVN_TEST_OPTS_XFAIL(move_on_move2,
+    SVN_TEST_OPTS_PASS(move_on_move2,
                        "move_on_move2"),
     SVN_TEST_OPTS_XFAIL(move_added,
                        "move_added"),