Author: stefan2
Date: Sun Sep 22 20:17:26 2013
New Revision: 1525429
URL: http://svn.apache.org/r1525429
Log:
Add MOVe support to FSX. This is mainly duplicating changes from FSFS.
* subversion/libsvn_fs_x/changes.c
(CHANGE_KIND_MOVE,
CHANGE_KIND_MOVEREPLACE): pro forma declaration of the new change kinds
* subversion/libsvn_fs_x/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_x/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_x/tree.c
(enum copy_type_t): declare new parameter type
(copy_helper): support moves just like ADDs; add extra param checks
(x_copy,
x_revision_link): update callers
(x_move): add new function for MOVes
Modified:
subversion/trunk/subversion/libsvn_fs_x/changes.c
subversion/trunk/subversion/libsvn_fs_x/low_level.c
subversion/trunk/subversion/libsvn_fs_x/transaction.c
subversion/trunk/subversion/libsvn_fs_x/tree.c
Modified: subversion/trunk/subversion/libsvn_fs_x/changes.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_x/changes.c?rev=1525429&r1=1525428&r2=1525429&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_x/changes.c (original)
+++ subversion/trunk/subversion/libsvn_fs_x/changes.c Sun Sep 22 20:17:26 2013
@@ -60,6 +60,8 @@
#define CHANGE_KIND_DELETE 0x00040
#define CHANGE_KIND_REPLACE 0x00060
#define CHANGE_KIND_RESET 0x00080
+#define CHANGE_KIND_MOVE 0x000A0
+#define CHANGE_KIND_MOVEREPLACE 0x000C0
/* Our internal representation of a change */
typedef struct binary_change_t
Modified: subversion/trunk/subversion/libsvn_fs_x/low_level.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_x/low_level.c?rev=1525429&r1=1525428&r2=1525429&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_x/low_level.c (original)
+++ subversion/trunk/subversion/libsvn_fs_x/low_level.c Sun Sep 22 20:17:26 2013
@@ -53,6 +53,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"
@@ -828,6 +830,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,
@@ -961,6 +971,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_x/transaction.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_x/transaction.c?rev=1525429&r1=1525428&r2=1525429&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_x/transaction.c (original)
+++ subversion/trunk/subversion/libsvn_fs_x/transaction.c Sun Sep 22 20:17:26
2013
@@ -646,6 +646,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_x__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
single summarical (is that real word?) change per path. Also keep
@@ -683,11 +710,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,
@@ -712,7 +741,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
@@ -734,22 +764,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_rev_id = svn_fs_x__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:
@@ -816,7 +840,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;
@@ -2655,25 +2680,37 @@ 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 of transaction TXN_ID 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_x__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_fs_x__p2l_entry_t entry;
svn_fs_x__id_part_t rev_item
= {SVN_INVALID_REVNUM, SVN_FS_X__ITEM_INDEX_CHANGES};
SVN_ERR(svn_fs_x__get_file_offset(&offset, file, pool));
- SVN_ERR(svn_fs_x__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_x__write_changes(svn_stream_from_aprfile2(file, TRUE, pool),
fs, changed_paths, TRUE, pool));
@@ -2814,6 +2851,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_x__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;
@@ -2846,6 +3040,7 @@ commit_body(void *baton, apr_pool_t *poo
apr_array_header_t *txnprop_list;
svn_prop_t prop;
const svn_fs_x__id_part_t *txn_id = svn_fs_x__txn_get_id(cb->txn);
+ apr_hash_t *changed_paths;
/* Get the current youngest revision. */
SVN_ERR(svn_fs_x__youngest_rev(&old_rev, cb->fs, pool));
@@ -2862,6 +3057,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_x__txn_changes_fetch(&changed_paths, cb->fs, txn_id,
+ pool));
+
+ SVN_ERR(verify_moves(cb->fs, txn_id, old_rev, changed_paths, pool));
+
/* We are going to be one better than this puny old revision. */
new_rev = old_rev + 1;
@@ -2879,7 +3081,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, txn_id, changed_paths,
+ new_rev, pool));
SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
SVN_ERR(svn_io_file_close(proto_file, pool));
Modified: subversion/trunk/subversion/libsvn_fs_x/tree.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_x/tree.c?rev=1525429&r1=1525428&r2=1525429&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_x/tree.c (original)
+++ subversion/trunk/subversion/libsvn_fs_x/tree.c Sun Sep 22 20:17:26 2013
@@ -2330,15 +2330,30 @@ x_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;
@@ -2355,11 +2370,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));
@@ -2396,13 +2422,19 @@ 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;
+
SVN_ERR(svn_fs_x__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;
}
@@ -2418,12 +2450,12 @@ copy_helper(svn_fs_root_t *from_root,
SVN_ERR(svn_fs_x__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));
@@ -2477,7 +2509,7 @@ x_copy(svn_fs_root_t *from_root,
to_root,
svn_fs__canonicalize_abspath(to_path,
pool),
- TRUE, pool));
+ copy_type_add_with_history, pool));
}
@@ -2495,7 +2527,27 @@ x_revision_link(svn_fs_root_t *from_root
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 *
+x_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)
+{
+ 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));
}