You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2013/09/22 21:52:09 UTC

svn commit: r1525419 - in /subversion/trunk/subversion: include/svn_error_codes.h include/svn_fs.h libsvn_fs_fs/low_level.c libsvn_fs_fs/transaction.c libsvn_fs_fs/tree.c

Author: stefan2
Date: Sun Sep 22 19:52:09 2013
New Revision: 1525419

URL: http://svn.apache.org/r1525419
Log:
Teach FSFS to store and report MOVes.  However, this patch does not add
fs_move() to the FS API, yet.

This models MOVes similar to ADDs, i.e.
- there is move as well as move-replace
- fs_move is an add-with-history that requires the copy-from revision to
  be equal to the TXN's base revision
- move sources must be deleted explicitly in a separate DEL operation

Hence, all history traversal and TXN rebase code just works out of the box
and no other operation ordering is required in the commit editor other than
the one already in place for add / replaces.

During the final commit stage, we check for duplicate move targets including
intermediate revisions since the TXN's base revision.  We also check that
all move source paths have been deleted by that TXN.

* subversion/include/svn_error_codes.h
  (SVN_ERR_FS_AMBIGUOUS_MOVE,
   SVN_ERR_FS_INCOMPLETE_MOVE): declare new error codes

* subversion/include/svn_fs.h
  (svn_fs_path_change_kind_t): add move and move-replace

* subversion/libsvn_fs_fs/low_level.c
  (ACTION_MOVE,
   ACTION_MOVEREPLACE): declare new change type strings
  (read_change,
   write_change_entry): write / parse the new change types

* subversion/libsvn_fs_fs/transaction.c
  (replace_change):  factored out from ...
  (fold_change): ... this one; handle moves similar to adds
  (process_changes): handle move-replaces similar to replaces
  (write_final_changed_path_info): update move source revs to Rev-1;
                     make changed_paths an input parameter
  (check_for_duplicate_move_source,
   verify_moves): new move verification code
  (commit_body):  verify moves when finalizing the commit;
                  update function all

* subversion/libsvn_fs_fs/tree.c
  (enum copy_type_t): declare new parameter type
  (copy_helper): support moves just like ADDs; add extra param checks
  (fs_copy,
   fs_revision_link): update callers
  (fs_move): add new function for MOVes

Modified:
    subversion/trunk/subversion/include/svn_error_codes.h
    subversion/trunk/subversion/include/svn_fs.h
    subversion/trunk/subversion/libsvn_fs_fs/low_level.c
    subversion/trunk/subversion/libsvn_fs_fs/transaction.c
    subversion/trunk/subversion/libsvn_fs_fs/tree.c

Modified: subversion/trunk/subversion/include/svn_error_codes.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_error_codes.h?rev=1525419&r1=1525418&r2=1525419&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_error_codes.h (original)
+++ subversion/trunk/subversion/include/svn_error_codes.h Sun Sep 22 19:52:09 2013
@@ -836,6 +836,16 @@ SVN_ERROR_START
              SVN_ERR_FS_CATEGORY_START + 58,
              "Index files are inconsistent.")
 
+  /** @since New in 1.9. */
+  SVN_ERRDEF(SVN_ERR_FS_AMBIGUOUS_MOVE,
+             SVN_ERR_FS_CATEGORY_START + 59,
+             "Ambiguous move")
+
+  /** @since New in 1.9. */
+  SVN_ERRDEF(SVN_ERR_FS_INCOMPLETE_MOVE,
+             SVN_ERR_FS_CATEGORY_START + 60,
+             "Move without a suitable deletion")
+
   /* repos errors */
 
   SVN_ERRDEF(SVN_ERR_REPOS_LOCKED,

Modified: subversion/trunk/subversion/include/svn_fs.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/svn_fs.h?rev=1525419&r1=1525418&r2=1525419&view=diff
==============================================================================
--- subversion/trunk/subversion/include/svn_fs.h (original)
+++ subversion/trunk/subversion/include/svn_fs.h Sun Sep 22 19:52:09 2013
@@ -1311,7 +1311,13 @@ typedef enum svn_fs_path_change_kind_t
   svn_fs_path_change_replace,
 
   /** ignore all previous change items for path (internal-use only) */
-  svn_fs_path_change_reset
+  svn_fs_path_change_reset,
+
+  /** moved to this path in txn */
+  svn_fs_path_change_move,
+
+  /** path removed and replaced by moved path in txn */
+  svn_fs_path_change_movereplace
 
 } svn_fs_path_change_kind_t;
 

Modified: subversion/trunk/subversion/libsvn_fs_fs/low_level.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/low_level.c?rev=1525419&r1=1525418&r2=1525419&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/low_level.c (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/low_level.c Sun Sep 22 19:52:09 2013
@@ -50,6 +50,8 @@
 #define ACTION_DELETE      "delete"
 #define ACTION_REPLACE     "replace"
 #define ACTION_RESET       "reset"
+#define ACTION_MOVE        "move"
+#define ACTION_MOVEREPLACE "movereplace"
 
 /* True and False flags. */
 #define FLAG_TRUE          "true"
@@ -239,6 +241,14 @@ read_change(change_t **change_p,
     {
       info->change_kind = svn_fs_path_change_reset;
     }
+  else if (strcmp(str, ACTION_MOVE) == 0)
+    {
+      info->change_kind = svn_fs_path_change_move;
+    }
+  else if (strcmp(str, ACTION_MOVEREPLACE) == 0)
+    {
+      info->change_kind = svn_fs_path_change_movereplace;
+    }
   else
     {
       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
@@ -373,6 +383,12 @@ write_change_entry(svn_stream_t *stream,
     case svn_fs_path_change_reset:
       change_string = ACTION_RESET;
       break;
+    case svn_fs_path_change_move:
+      change_string = ACTION_MOVE;
+      break;
+    case svn_fs_path_change_movereplace:
+      change_string = ACTION_MOVEREPLACE;
+      break;
     default:
       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
                                _("Invalid change type %d"),

Modified: subversion/trunk/subversion/libsvn_fs_fs/transaction.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/transaction.c?rev=1525419&r1=1525418&r2=1525419&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/transaction.c (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/transaction.c Sun Sep 22 19:52:09 2013
@@ -598,6 +598,33 @@ unparse_dir_entries(apr_hash_t **str_ent
   return SVN_NO_ERROR;
 }
 
+/* Copy the contents of NEW_CHANGE into OLD_CHANGE assuming that both
+   belong to the same path.  Allocate copies in POOL.
+ */
+static void
+replace_change(svn_fs_path_change2_t *old_change,
+               const svn_fs_path_change2_t *new_change,
+               apr_pool_t *pool)
+{
+  /* An add at this point must be following a previous delete,
+      so treat it just like a replace. */
+  old_change->node_kind = new_change->node_kind;
+  old_change->node_rev_id = svn_fs_fs__id_copy(new_change->node_rev_id,
+                                               pool);
+  old_change->text_mod = new_change->text_mod;
+  old_change->prop_mod = new_change->prop_mod;
+  if (new_change->copyfrom_rev == SVN_INVALID_REVNUM)
+    {
+      old_change->copyfrom_rev = SVN_INVALID_REVNUM;
+      old_change->copyfrom_path = NULL;
+    }
+  else
+    {
+      old_change->copyfrom_rev = new_change->copyfrom_rev;
+      old_change->copyfrom_path = apr_pstrdup(pool,
+                                              new_change->copyfrom_path);
+    }
+}
 
 /* Merge the internal-use-only CHANGE into a hash of public-FS
    svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
@@ -636,11 +663,13 @@ fold_change(apr_hash_t *changes,
            _("Invalid change ordering: new node revision ID "
              "without delete"));
 
-      /* Sanity check: an add, replacement, or reset must be the first
+      /* Sanity check: an add, replacement, move, or reset must be the first
          thing to follow a deletion. */
       if ((old_change->change_kind == svn_fs_path_change_delete)
           && (! ((info->change_kind == svn_fs_path_change_replace)
                  || (info->change_kind == svn_fs_path_change_reset)
+                 || (info->change_kind == svn_fs_path_change_movereplace)
+                 || (info->change_kind == svn_fs_path_change_move)
                  || (info->change_kind == svn_fs_path_change_add))))
         return svn_error_create
           (SVN_ERR_FS_CORRUPT, NULL,
@@ -665,7 +694,8 @@ fold_change(apr_hash_t *changes,
           break;
 
         case svn_fs_path_change_delete:
-          if (old_change->change_kind == svn_fs_path_change_add)
+          if ((old_change->change_kind == svn_fs_path_change_add)
+              || (old_change->change_kind == svn_fs_path_change_move))
             {
               /* If the path was introduced in this transaction via an
                  add, and we are deleting it, just remove the path
@@ -687,23 +717,16 @@ fold_change(apr_hash_t *changes,
         case svn_fs_path_change_replace:
           /* An add at this point must be following a previous delete,
              so treat it just like a replace. */
+          replace_change(old_change, info, pool);
           old_change->change_kind = svn_fs_path_change_replace;
-          old_change->node_kind = info->node_kind;
-          old_change->node_rev_id = svn_fs_fs__id_copy(info->node_rev_id,
-                                                       pool);
-          old_change->text_mod = info->text_mod;
-          old_change->prop_mod = info->prop_mod;
-          if (info->copyfrom_rev == SVN_INVALID_REVNUM)
-            {
-              old_change->copyfrom_rev = SVN_INVALID_REVNUM;
-              old_change->copyfrom_path = NULL;
-            }
-          else
-            {
-              old_change->copyfrom_rev = info->copyfrom_rev;
-              old_change->copyfrom_path = apr_pstrdup(pool,
-                                                      info->copyfrom_path);
-            }
+          break;
+
+        case svn_fs_path_change_move:
+        case svn_fs_path_change_movereplace:
+          /* A move at this point must be following a previous delete,
+             so treat it just like a replacing move. */
+          replace_change(old_change, info, pool);
+          old_change->change_kind = svn_fs_path_change_movereplace;
           break;
 
         case svn_fs_path_change_modify:
@@ -769,7 +792,8 @@ process_changes(apr_hash_t *changed_path
       */
 
       if ((change->info.change_kind == svn_fs_path_change_delete)
-           || (change->info.change_kind == svn_fs_path_change_replace))
+           || (change->info.change_kind == svn_fs_path_change_replace)
+           || (change->info.change_kind == svn_fs_path_change_movereplace))
         {
           apr_hash_index_t *hi;
 
@@ -2663,23 +2687,32 @@ write_final_rev(const svn_fs_id_t **new_
   return SVN_NO_ERROR;
 }
 
-/* Write the changed path info from transaction TXN_ID in filesystem
-   FS to the permanent rev-file FILE.  *OFFSET_P is set the to offset
-   in the file of the beginning of this information.  Perform
-   temporary allocations in POOL. */
+/* Write the changed path info CHANGED_PATHS to the permanent rev-file FILE
+   representing NEW_REV in filesystem FS.  *OFFSET_P is set the to offset in
+   the file of the beginning of this information.
+   Perform temporary allocations in POOL. */
 static svn_error_t *
 write_final_changed_path_info(apr_off_t *offset_p,
                               apr_file_t *file,
                               svn_fs_t *fs,
-                              const svn_fs_fs__id_part_t *txn_id,
+                              apr_hash_t *changed_paths,
+                              svn_revnum_t new_rev,
                               apr_pool_t *pool)
 {
-  apr_hash_t *changed_paths;
   apr_off_t offset;
+  apr_hash_index_t *hi;
 
   SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
 
-  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, fs, txn_id, pool));
+  /* all moves specify the "copy-from-rev" as REV-1 */
+  for (hi = apr_hash_first(pool, changed_paths); hi; hi = apr_hash_next(hi))
+    {
+      svn_fs_path_change2_t *change;
+      apr_hash_this(hi, NULL, NULL, (void **)&change);
+
+      if (change->change_kind == svn_fs_path_change_move)
+        change->copyfrom_rev = new_rev - 1;
+    }
 
   SVN_ERR(svn_fs_fs__write_changes(svn_stream_from_aprfile2(file, TRUE, pool),
                                    fs, changed_paths, TRUE, pool));
@@ -2838,6 +2871,163 @@ verify_locks(svn_fs_t *fs,
   return SVN_NO_ERROR;
 }
 
+/* If CHANGE is move, verify that there is no other move with the same
+   copy-from path in SOURCE_PATHS already (parent or sub-node moves are fine).
+   Add the source path to SOURCE_PATHS after successful verification. */
+static svn_error_t *
+check_for_duplicate_move_source(apr_hash_t *source_paths,
+                                change_t *change)
+{
+  if (   change->info.change_kind == svn_fs_path_change_move
+      || change->info.change_kind == svn_fs_path_change_movereplace)
+    if (change->info.copyfrom_path)
+      {
+        if (apr_hash_get(source_paths, change->info.copyfrom_path,
+                         APR_HASH_KEY_STRING))
+          return svn_error_createf(SVN_ERR_FS_AMBIGUOUS_MOVE, NULL,
+                      _("Path '%s' has been moved to more than one target"),
+                                   change->info.copyfrom_path);
+
+        apr_hash_set(source_paths, change->info.copyfrom_path,
+                     APR_HASH_KEY_STRING, change->info.copyfrom_path);
+      }
+
+  return SVN_NO_ERROR;
+}
+
+/* Verify that the moves we are about to commit with TXN_ID in FS are unique
+   and the respective copy sources have been deleted.  OLD_REV is the last
+   committed revision.  CHANGED_PATHS is the list of changes paths in this
+   txn.  Use POOL for temporary allocations. */
+static svn_error_t *
+verify_moves(svn_fs_t *fs,
+             const svn_fs_fs__id_part_t *txn_id,
+             svn_revnum_t old_rev,
+             apr_hash_t *changed_paths,
+             apr_pool_t *pool)
+{
+  apr_hash_t *source_paths = apr_hash_make(pool);
+  svn_revnum_t revision;
+  apr_pool_t *iter_pool = svn_pool_create(pool);
+  apr_hash_index_t *hi;
+  int i;
+  apr_array_header_t *moves
+    = apr_array_make(pool, 16, sizeof(svn_sort__item_t));
+  apr_array_header_t *deletions
+    = apr_array_make(pool, 16, sizeof(const char *));
+
+  /* extract moves and deletions from the current txn's change list */
+
+  for (hi = apr_hash_first(pool, changed_paths); hi; hi = apr_hash_next(hi))
+    {
+      const char *path;
+      apr_ssize_t len;
+      change_t *change;
+      apr_hash_this(hi, (const void**)&path, &len, (void**)&change);
+
+      if (   change->info.copyfrom_path
+          && (   change->info.change_kind == svn_fs_path_change_move
+              || change->info.change_kind == svn_fs_path_change_movereplace))
+        {
+          svn_sort__item_t *item = apr_array_push(moves);
+          item->key = path;
+          item->klen = len;
+          item->value = change;
+        }
+
+      if (   change->info.change_kind == svn_fs_path_change_delete
+          || change->info.change_kind == svn_fs_path_change_replace
+          || change->info.change_kind == svn_fs_path_change_movereplace)
+        APR_ARRAY_PUSH(deletions, const char *) = path;
+    }
+
+  /* no moves? -> done here */
+
+  if (moves->nelts == 0)
+    return SVN_NO_ERROR;
+
+  /* correct the deletions that refer to moved paths and make them refer to
+     the paths in OLD_REV */
+
+  qsort(moves->elts, moves->nelts, moves->elt_size,
+        svn_sort_compare_paths);
+
+  for (i = 0; i < deletions->nelts; ++i)
+    {
+      const char *deleted_path = APR_ARRAY_IDX(deletions, i, const char*);
+      int closest_move_idx
+        = svn_sort__bsearch_lower_bound(deleted_path, moves,
+                                        svn_sort_compare_paths);
+
+      if (closest_move_idx < moves->nelts)
+        {
+          svn_sort__item_t *closest_move_item
+            = &APR_ARRAY_IDX(moves, closest_move_idx, svn_sort__item_t);
+          const char *relpath
+            = svn_dirent_skip_ancestor(closest_move_item->key,
+                                       deleted_path);
+          if (relpath)
+            {
+              change_t *closed_move = closest_move_item->value;
+              APR_ARRAY_IDX(deletions, i, const char*)
+                = svn_dirent_join(closed_move->info.copyfrom_path, relpath,
+                                  pool);
+            }
+        }
+    }
+
+  qsort(deletions->elts, deletions->nelts, deletions->elt_size,
+        svn_sort_compare_paths);
+
+  /* The _same_ source paths must never occur more than once in any move 
+     since our base revision. */
+
+  for (i = 0; moves->nelts; ++i)
+    SVN_ERR(check_for_duplicate_move_source (source_paths,
+                          APR_ARRAY_IDX(moves, i, svn_sort__item_t).value));
+
+  for (revision = txn_id->revision + 1; revision <= old_rev; ++revision)
+    {
+      apr_array_header_t *changes;
+      change_t **changes_p;
+
+      svn_pool_clear(iter_pool);
+      svn_fs_fs__get_changes(&changes, fs, revision, iter_pool);
+
+      changes_p = (change_t **)&changes->elts;
+      for (i = 0; i < changes->nelts; ++i)
+        SVN_ERR(check_for_duplicate_move_source(source_paths, changes_p[i]));
+    }
+
+  /* The move source paths must been deleted in this txn. */
+
+  for (i = 0; i < moves->nelts; ++i)
+    {
+      change_t *change = APR_ARRAY_IDX(moves, i, svn_sort__item_t).value;
+
+      /* there must be a deletion of move's copy-from path
+         (or any of its parents) */
+
+      int closest_deletion_idx
+        = svn_sort__bsearch_lower_bound(change->info.copyfrom_path, deletions,
+                                        svn_sort_compare_paths);
+      if (closest_deletion_idx < deletions->nelts)
+        {
+          const char *closest_deleted_path
+            = APR_ARRAY_IDX(deletions, closest_deletion_idx, const char *);
+          if (!svn_dirent_is_ancestor(closest_deleted_path,
+                                      change->info.copyfrom_path))
+            return svn_error_createf(SVN_ERR_FS_INCOMPLETE_MOVE, NULL,
+                        _("Path '%s' has been moved without being deleted"),
+                                     change->info.copyfrom_path);
+        }
+    }
+
+  svn_pool_destroy(iter_pool);
+
+  return SVN_NO_ERROR;
+}
+
 /* Baton used for commit_body below. */
 struct commit_baton {
   svn_revnum_t *new_rev_p;
@@ -2871,6 +3061,7 @@ commit_body(void *baton, apr_pool_t *poo
   svn_prop_t prop;
   const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__txn_get_id(cb->txn);
   svn_stringbuf_t *trailer;
+  apr_hash_t *changed_paths;
 
   /* Get the current youngest revision. */
   SVN_ERR(svn_fs_fs__youngest_rev(&old_rev, cb->fs, pool));
@@ -2887,6 +3078,13 @@ commit_body(void *baton, apr_pool_t *poo
      discovered locks. */
   SVN_ERR(verify_locks(cb->fs, txn_id, pool));
 
+  /* we need the changes list for verification as well as for writing it
+     to the final rev file */
+  SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, cb->fs, txn_id,
+                                       pool));
+
+  SVN_ERR(verify_moves(cb->fs, txn_id, old_rev, changed_paths, pool));
+
   /* Get the next node_id and copy_id to use. */
   if (ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
     SVN_ERR(get_next_revision_ids(&start_node_id, &start_copy_id, cb->fs,
@@ -2909,7 +3107,8 @@ commit_body(void *baton, apr_pool_t *poo
 
   /* Write the changed-path information. */
   SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
-                                        cb->fs, txn_id, pool));
+                                        cb->fs, changed_paths, new_rev,
+                                        pool));
 
   /* Write the final line. */
   trailer = svn_fs_fs__unparse_revision_trailer

Modified: subversion/trunk/subversion/libsvn_fs_fs/tree.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/tree.c?rev=1525419&r1=1525418&r2=1525419&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/tree.c (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/tree.c Sun Sep 22 19:52:09 2013
@@ -2385,15 +2385,30 @@ fs_same_p(svn_boolean_t *same_p,
   return SVN_NO_ERROR;
 }
 
+/* Type to select the various behavioral modes of copy_helper.
+ */
+typedef enum copy_type_t
+{
+  /* add without history */
+  copy_type_plain_add,
+
+  /* add with history */
+  copy_type_add_with_history,
+
+  /* move (always with history) */
+  copy_type_move
+} copy_type_t;
+
 /* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
-   TO_ROOT.  If PRESERVE_HISTORY is set, then the copy is recorded in
-   the copies table.  Perform temporary allocations in POOL. */
+   TO_ROOT.  COPY_TYPE determines whether then the copy is recorded in
+   the copies table and whether it is being marked as a move.
+   Perform temporary allocations in POOL. */
 static svn_error_t *
 copy_helper(svn_fs_root_t *from_root,
             const char *from_path,
             svn_fs_root_t *to_root,
             const char *to_path,
-            svn_boolean_t preserve_history,
+            copy_type_t copy_type,
             apr_pool_t *pool)
 {
   dag_node_t *from_node;
@@ -2410,11 +2425,22 @@ copy_helper(svn_fs_root_t *from_root,
        _("Cannot copy between two different filesystems ('%s' and '%s')"),
        from_root->fs->path, to_root->fs->path);
 
+  /* more things that we can't do ATM */
   if (from_root->is_txn_root)
     return svn_error_create
       (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
        _("Copy from mutable tree not currently supported"));
 
+  if (! to_root->is_txn_root)
+    return svn_error_create
+      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+       _("Copy immutable tree not supported"));
+
+  if (copy_type == copy_type_move && from_root->rev != txn_id->revision)
+    return svn_error_create
+      (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+       _("Move from non-HEAD revision not currently supported"));
+
   /* Get the NODE for FROM_PATH in FROM_ROOT.*/
   SVN_ERR(get_dag(&from_node, from_root, from_path, TRUE, pool));
 
@@ -2451,14 +2477,20 @@ copy_helper(svn_fs_root_t *from_root,
          operation is a replacement, not an addition. */
       if (to_parent_path->node)
         {
-          kind = svn_fs_path_change_replace;
+          kind = copy_type == copy_type_move
+               ? svn_fs_path_change_movereplace
+               : svn_fs_path_change_replace;
+
           if (svn_fs_fs__fs_supports_mergeinfo(to_root->fs))
             SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_start,
                                                        to_parent_path->node));
         }
       else
         {
-          kind = svn_fs_path_change_add;
+          kind = copy_type == copy_type_move
+               ? svn_fs_path_change_move
+               : svn_fs_path_change_add;
+
           mergeinfo_start = 0;
         }
 
@@ -2476,12 +2508,12 @@ copy_helper(svn_fs_root_t *from_root,
       SVN_ERR(svn_fs_fs__dag_copy(to_parent_path->parent->node,
                                   to_parent_path->entry,
                                   from_node,
-                                  preserve_history,
+                                  copy_type != copy_type_plain_add,
                                   from_root->rev,
                                   from_canonpath,
                                   txn_id, pool));
 
-      if (kind == svn_fs_path_change_replace)
+      if (kind != svn_fs_path_change_add)
         SVN_ERR(dag_node_cache_invalidate(to_root,
                                           parent_path_path(to_parent_path,
                                                            pool), pool));
@@ -2538,7 +2570,7 @@ fs_copy(svn_fs_root_t *from_root,
                                      to_root,
                                      svn_fs__canonicalize_abspath(to_path,
                                                                   pool),
-                                     TRUE, pool));
+                                     copy_type_add_with_history, pool));
 }
 
 
@@ -2556,7 +2588,29 @@ fs_revision_link(svn_fs_root_t *from_roo
 
   path = svn_fs__canonicalize_abspath(path, pool);
   return svn_error_trace(copy_helper(from_root, path, to_root, path,
-                                     FALSE, pool));
+                                     copy_type_plain_add, pool));
+}
+
+
+/* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT and mark
+   it as a Move.  If FROM_PATH is a directory, copy it recursively. 
+   Temporary allocations are from POOL.*/
+static svn_error_t *
+fs_move(svn_fs_root_t *from_root,
+        const char *from_path,
+        svn_fs_root_t *to_root,
+        const char *to_path,
+        apr_pool_t *pool)
+{
+  SVN_ERR(check_newline(to_path, pool));
+
+  return svn_error_trace(copy_helper(from_root,
+                                     svn_fs__canonicalize_abspath(from_path,
+                                                                  pool),
+                                     to_root,
+                                     svn_fs__canonicalize_abspath(to_path,
+                                                                  pool),
+                                     copy_type_move, pool));
 }