aboutsummaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_client
diff options
context:
space:
mode:
authorDimitry Andric <dim@FreeBSD.org>2020-05-31 20:58:28 +0000
committerDimitry Andric <dim@FreeBSD.org>2020-05-31 20:58:28 +0000
commitbbee6e0814d5875b85b81f26fd4ca7a28b6f9570 (patch)
tree726fcf32b39ca8976d7aa51b67c7236509f1bde4 /subversion/libsvn_client
parent38cef28c88864beaadac7a7cffdec6da952c3eb2 (diff)
downloadsrc-bbee6e0814d5875b85b81f26fd4ca7a28b6f9570.tar.gz
src-bbee6e0814d5875b85b81f26fd4ca7a28b6f9570.zip
Notes
Notes: svn path=/vendor/subversion/dist/; revision=361669 svn path=/vendor/subversion/subversion-1.14.0/; revision=361670; tag=vendor/subversion/subversion-1.14.0
Diffstat (limited to 'subversion/libsvn_client')
-rw-r--r--subversion/libsvn_client/add.c7
-rw-r--r--subversion/libsvn_client/blame.c28
-rw-r--r--subversion/libsvn_client/client.h103
-rw-r--r--subversion/libsvn_client/commit.c185
-rw-r--r--subversion/libsvn_client/commit_util.c34
-rw-r--r--subversion/libsvn_client/conflicts.c3729
-rw-r--r--subversion/libsvn_client/copy.c753
-rw-r--r--subversion/libsvn_client/copy_foreign.c575
-rw-r--r--subversion/libsvn_client/delete.c7
-rw-r--r--subversion/libsvn_client/deprecated.c156
-rw-r--r--subversion/libsvn_client/diff.c892
-rw-r--r--subversion/libsvn_client/diff_local.c126
-rw-r--r--subversion/libsvn_client/diff_summarize.c20
-rw-r--r--subversion/libsvn_client/export.c33
-rw-r--r--subversion/libsvn_client/info.c4
-rw-r--r--subversion/libsvn_client/layout.c289
-rw-r--r--subversion/libsvn_client/libsvn_client.pc.in8
-rw-r--r--subversion/libsvn_client/list.c2
-rw-r--r--subversion/libsvn_client/merge.c413
-rw-r--r--subversion/libsvn_client/mtcc.c4
-rw-r--r--subversion/libsvn_client/patch.c15
-rw-r--r--subversion/libsvn_client/ra.c28
-rw-r--r--subversion/libsvn_client/repos_diff.c24
-rw-r--r--subversion/libsvn_client/revert.c30
-rw-r--r--subversion/libsvn_client/revisions.c9
-rw-r--r--subversion/libsvn_client/shelf.c1274
-rw-r--r--subversion/libsvn_client/shelf2.c2124
-rw-r--r--subversion/libsvn_client/shelve.c552
-rw-r--r--subversion/libsvn_client/status.c80
-rw-r--r--subversion/libsvn_client/update.c10
-rw-r--r--subversion/libsvn_client/upgrade.c31
-rw-r--r--subversion/libsvn_client/util.c1
-rw-r--r--subversion/libsvn_client/wc_editor.c655
33 files changed, 9197 insertions, 3004 deletions
diff --git a/subversion/libsvn_client/add.c b/subversion/libsvn_client/add.c
index ce7891afb27c..3bb548ae8b45 100644
--- a/subversion/libsvn_client/add.c
+++ b/subversion/libsvn_client/add.c
@@ -983,12 +983,13 @@ svn_client_add5(const char *path,
static svn_error_t *
path_driver_cb_func(void **dir_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
void *parent_baton,
void *callback_baton,
const char *path,
apr_pool_t *pool)
{
- const svn_delta_editor_t *editor = callback_baton;
SVN_ERR(svn_path_check_valid(path, pool));
return editor->add_directory(path, parent_baton, NULL,
SVN_INVALID_REVNUM, pool, dir_baton);
@@ -1177,8 +1178,8 @@ mkdir_urls(const apr_array_header_t *urls,
/* Call the path-based editor driver. */
err = svn_error_trace(
- svn_delta_path_driver2(editor, edit_baton, targets, TRUE,
- path_driver_cb_func, (void *)editor, pool));
+ svn_delta_path_driver3(editor, edit_baton, targets, TRUE,
+ path_driver_cb_func, NULL, pool));
if (err)
{
diff --git a/subversion/libsvn_client/blame.c b/subversion/libsvn_client/blame.c
index b9363e20dd3e..f78b3041f5b2 100644
--- a/subversion/libsvn_client/blame.c
+++ b/subversion/libsvn_client/blame.c
@@ -456,7 +456,7 @@ file_rev_handler(void *baton, const char *path, svn_revnum_t revnum,
SVN_ERR_CLIENT_IS_BINARY_FILE, NULL,
_("Cannot calculate blame information for binary file '%s'"),
(svn_path_is_url(frb->target)
- ? frb->target
+ ? frb->target
: svn_dirent_local_style(frb->target, pool)));
}
}
@@ -553,7 +553,7 @@ file_rev_handler(void *baton, const char *path, svn_revnum_t revnum,
|| frb->include_merged_revisions);
/* The file existed before start_rev; generate no blame info for
- lines from this revision (or before).
+ lines from this revision (or before).
This revision specifies the state as it was at the start revision */
@@ -656,14 +656,16 @@ normalize_blames(struct blame_chain *chain,
}
svn_error_t *
-svn_client_blame5(const char *target,
+svn_client_blame6(svn_revnum_t *start_revnum_p,
+ svn_revnum_t *end_revnum_p,
+ const char *target,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *start,
const svn_opt_revision_t *end,
const svn_diff_file_options_t *diff_options,
svn_boolean_t ignore_mime_type,
svn_boolean_t include_merged_revisions,
- svn_client_blame_receiver3_t receiver,
+ svn_client_blame_receiver4_t receiver,
void *receiver_baton,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
@@ -696,10 +698,13 @@ svn_client_blame5(const char *target,
SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx,
target_abspath_or_url, ra_session,
start, pool));
-
+ if (start_revnum_p)
+ *start_revnum_p = start_revnum;
SVN_ERR(svn_client__get_revision_number(&end_revnum, NULL, ctx->wc_ctx,
target_abspath_or_url, ra_session,
end, pool));
+ if (end_revnum_p)
+ *end_revnum_p = end_revnum;
{
svn_client__pathrev_t *loc;
@@ -734,7 +739,7 @@ svn_client_blame5(const char *target,
mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE);
}
- else
+ else
{
const svn_string_t *value;
@@ -941,18 +946,21 @@ svn_client_blame5(const char *target,
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
if (!eof || sb->len)
{
+ svn_string_t line;
+ line.data = sb->data;
+ line.len = sb->len;
if (walk->rev)
- SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum,
+ SVN_ERR(receiver(receiver_baton,
line_no, walk->rev->revision,
walk->rev->rev_props, merged_rev,
merged_rev_props, merged_path,
- sb->data, FALSE, iterpool));
+ &line, FALSE, iterpool));
else
- SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum,
+ SVN_ERR(receiver(receiver_baton,
line_no, SVN_INVALID_REVNUM,
NULL, SVN_INVALID_REVNUM,
NULL, NULL,
- sb->data, TRUE, iterpool));
+ &line, TRUE, iterpool));
}
if (eof) break;
}
diff --git a/subversion/libsvn_client/client.h b/subversion/libsvn_client/client.h
index c0a794712f3f..97cd1e297c02 100644
--- a/subversion/libsvn_client/client.h
+++ b/subversion/libsvn_client/client.h
@@ -682,34 +682,6 @@ svn_client__get_diff_editor2(const svn_delta_editor_t **editor,
/* ---------------------------------------------------------------- */
-/*** Editor for diff summary ***/
-
-/* Set *DIFF_PROCESSOR to a diff processor that will report a diff summary
- to SUMMARIZE_FUNC.
-
- P_ROOT_RELPATH will return a pointer to a string that must be set to
- the root of the operation before the processor is called.
-
- ORIGINAL_PATH specifies the original path and will be used with
- **ANCHOR_PATH to create paths as the user originally provided them
- to the diff function.
-
- SUMMARIZE_FUNC is called with SUMMARIZE_BATON as parameter by the
- created callbacks for each changed item.
-*/
-svn_error_t *
-svn_client__get_diff_summarize_callbacks(
- const svn_diff_tree_processor_t **diff_processor,
- const char ***p_root_relpath,
- svn_client_diff_summarize_func_t summarize_func,
- void *summarize_baton,
- const char *original_target,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool);
-
-/* ---------------------------------------------------------------- */
-
-
/*** Copy Stuff ***/
/* This structure is used to associate a specific copy or move SRC with a
@@ -754,40 +726,23 @@ typedef struct svn_client__copy_pair_t
/*** Commit Stuff ***/
-/* WARNING: This is all new, untested, un-peer-reviewed conceptual
- stuff.
+/* The "Harvest Committables" System
- The day that 'svn switch' came into existence, our old commit
- crawler (svn_wc_crawl_local_mods) became obsolete. It relied far
- too heavily on the on-disk hierarchy of files and directories, and
- simply had no way to support disjoint working copy trees or nest
- working copies. The primary reason for this is that commit
- process, in order to guarantee atomicity, is a single drive of a
+ The commit process requires, per repository, a single drive of a
commit editor which is based not on working copy paths, but on
- URLs. With the completion of 'svn switch', it became all too
- likely that the on-disk working copy hierarchy would no longer be
- guaranteed to map to a similar in-repository hierarchy.
-
- Aside from this new brokenness of the old system, an unrelated
- feature request had cropped up -- the ability to know in advance of
- your commit, exactly what would be committed (so that log messages
- could be initially populated with this information). Since the old
- crawler discovered commit candidates while in the process of
- committing, it was impossible to harvest this information upfront.
- As a workaround, svn_wc_statuses() was used to stat the whole
- working copy for changes before the commit started...and then the
- commit would again stat the whole tree for changes.
-
- Enter the new system.
+ URLs. The on-disk working copy hierarchy does not, in general,
+ map to a similar in-repository hierarchy, due to switched subtrees
+ and disjoint working copies.
+
+ Also we wish to know exactly what would be committed, in advance of
+ the commit, so that a log message editor can be initially populated
+ with this information.
The primary goal of this system is very straightforward: harvest
all commit candidate information up front, and cache enough info in
the process to use this to drive a URL-sorted commit.
- *** END-OF-KNOWLEDGE ***
-
- The prototypes below are still in development. In general, the
- idea is that commit-y processes ('svn mkdir URL', 'svn delete URL',
+ The idea is that commit-y processes ('svn mkdir URL', 'svn delete URL',
'svn commit', 'svn copy WC_PATH URL', 'svn copy URL1 URL2', 'svn
move URL1 URL2', others?) generate the cached commit candidate
information, and hand this information off to a consumer which is
@@ -844,7 +799,7 @@ typedef svn_error_t *(*svn_client__check_url_kind_t)(void *baton,
- if the candidate has a lock token, add it to the LOCK_TOKENS hash.
- if the candidate is a directory scheduled for deletion, crawl
- the directories children recursively for any lock tokens and
+ the directory's children recursively for any lock tokens and
add them to the LOCK_TOKENS array.
At the successful return of this function, COMMITTABLES will point
@@ -915,6 +870,18 @@ svn_client__condense_commit_items(const char **base_url,
apr_array_header_t *commit_items,
apr_pool_t *pool);
+/* Rewrite the COMMIT_ITEMS array to be sorted by URL.
+ Rewrite the items' URLs to be relative to BASE_URL.
+
+ COMMIT_ITEMS is an array of (svn_client_commit_item3_t *) items.
+
+ Afterwards, some of the items in COMMIT_ITEMS may contain data
+ allocated in POOL. */
+svn_error_t *
+svn_client__condense_commit_items2(const char *base_url,
+ apr_array_header_t *commit_items,
+ apr_pool_t *pool);
+
/* Commit the items in the COMMIT_ITEMS array using EDITOR/EDIT_BATON
to describe the committed local mods. Prior to this call,
COMMIT_ITEMS should have been run through (and BASE_URL generated
@@ -1129,24 +1096,26 @@ svn_client__resolve_conflicts(svn_boolean_t *conflicts_remain,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool);
-/* Produce a diff with depth DEPTH between two files or two directories at
- * LEFT_ABSPATH1 and RIGHT_ABSPATH, using the provided diff callbacks to
- * show changes in files. The files and directories involved may be part of
- * a working copy or they may be unversioned. For versioned files, show
- * property changes, too.
+/* Produce a diff with depth DEPTH between the file or directory at
+ * LEFT_ABSPATH and the file or directory at RIGHT_ABSPATH, reporting
+ * differences to DIFF_PROCESSOR.
+ *
+ * The files and directories involved may be part of a working copy or
+ * they may be unversioned. For versioned files, show property changes,
+ * too.
*
- * If ANCHOR_ABSPATH is not null, set it to the anchor of the diff before
- * the first processor call. (The anchor is LEFT_ABSPATH or an ancestor of it)
+ * No copy or move information is reported to the diff processor.
+ *
+ * Anchor the DIFF_PROCESSOR at the requested diff targets (LEFT_ABSPATH,
+ * RIGHT_ABSPATH). As any children reached by recursion are matched by
+ * name, a diff processor relpath applies equally to both sides of the diff.
*/
svn_error_t *
-svn_client__arbitrary_nodes_diff(const char **root_relpath,
- svn_boolean_t *root_is_dir,
- const char *left_abspath,
+svn_client__arbitrary_nodes_diff(const char *left_abspath,
const char *right_abspath,
svn_depth_t depth,
const svn_diff_tree_processor_t *diff_processor,
svn_client_ctx_t *ctx,
- apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
diff --git a/subversion/libsvn_client/commit.c b/subversion/libsvn_client/commit.c
index 4a945c887aa9..df2f5f7c5224 100644
--- a/subversion/libsvn_client/commit.c
+++ b/subversion/libsvn_client/commit.c
@@ -500,6 +500,129 @@ append_externals_as_explicit_targets(apr_array_header_t *rel_targets,
return SVN_NO_ERROR;
}
+/* Crawl the working copy for commit items.
+ */
+static svn_error_t *
+harvest_committables(apr_array_header_t **commit_items_p,
+ apr_hash_t **committables_by_path_p,
+ apr_hash_t **lock_tokens,
+ const char *base_dir_abspath,
+ const apr_array_header_t *targets,
+ int depth_empty_start,
+ svn_depth_t depth,
+ svn_boolean_t just_locked,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ struct check_url_kind_baton cukb;
+ svn_client__committables_t *committables;
+ apr_hash_index_t *hi;
+
+ /* Prepare for when we have a copy containing not-present nodes. */
+ cukb.pool = scratch_pool;
+ cukb.session = NULL; /* ### Can we somehow reuse session? */
+ cukb.repos_root_url = NULL;
+ cukb.ctx = ctx;
+
+ SVN_ERR(svn_client__harvest_committables(&committables, lock_tokens,
+ base_dir_abspath, targets,
+ depth_empty_start, depth,
+ just_locked,
+ changelists,
+ check_url_kind, &cukb,
+ ctx, result_pool, scratch_pool));
+ if (apr_hash_count(committables->by_repository) == 0)
+ {
+ *commit_items_p = NULL;
+ return SVN_NO_ERROR; /* Nothing to do */
+ }
+ else if (apr_hash_count(committables->by_repository) > 1)
+ {
+ return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Commit can only commit to a single repository at a time.\n"
+ "Are all targets part of the same working copy?"));
+ }
+
+ hi = apr_hash_first(scratch_pool, committables->by_repository);
+ *commit_items_p = apr_hash_this_val(hi);
+ if (committables_by_path_p)
+ *committables_by_path_p = committables->by_path;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__wc_replay(const char *src_wc_abspath,
+ const apr_array_header_t *targets,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *base_abspath;
+ apr_array_header_t *rel_targets;
+ apr_hash_t *lock_tokens;
+ apr_array_header_t *commit_items;
+ svn_client__pathrev_t *base;
+ const char *base_url;
+ svn_wc_notify_func2_t saved_notify_func;
+ void *saved_notify_baton;
+
+ /* Condense the target list. This makes all targets absolute. */
+ SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets,
+ FALSE, pool, pool));
+
+ /* No targets means nothing to commit, so just return. */
+ if (base_abspath == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(rel_targets != NULL);
+
+ /* If we calculated only a base and no relative targets, this
+ must mean that we are being asked to commit (effectively) a
+ single path. */
+ if (rel_targets->nelts == 0)
+ APR_ARRAY_PUSH(rel_targets, const char *) = "";
+
+ /* Crawl the working copy for commit items. */
+ SVN_ERR(harvest_committables(&commit_items, NULL /*committables_by_path_p*/,
+ &lock_tokens,
+ base_abspath, rel_targets,
+ -1 /*depth_empty_start*/,
+ depth,
+ FALSE /*just_locked*/,
+ changelists,
+ ctx, pool, pool));
+ if (!commit_items)
+ {
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_client__wc_node_get_base(&base,
+ src_wc_abspath, ctx->wc_ctx, pool, pool));
+ base_url = base->url;
+ /* Sort our COMMIT_ITEMS by URL and find their relative URL-paths. */
+ SVN_ERR(svn_client__condense_commit_items2(base_url, commit_items, pool));
+
+ saved_notify_func = ctx->notify_func2;
+ saved_notify_baton = ctx->notify_baton2;
+ ctx->notify_func2 = notify_func;
+ ctx->notify_baton2 = notify_baton;
+ /* BASE_URL is only used here in notifications & errors */
+ SVN_ERR(svn_client__do_commit(base_url, commit_items,
+ editor, edit_baton,
+ NULL /*notify_prefix*/, NULL /*sha1_checksums*/,
+ ctx, pool, pool));
+ ctx->notify_func2 = saved_notify_func;
+ ctx->notify_baton2 = saved_notify_baton;
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_client_commit6(const apr_array_header_t *targets,
svn_depth_t depth,
@@ -525,7 +648,7 @@ svn_client_commit6(const apr_array_header_t *targets,
apr_array_header_t *rel_targets;
apr_array_header_t *lock_targets;
apr_array_header_t *locks_obtained;
- svn_client__committables_t *committables;
+ apr_hash_t *committables_by_path;
apr_hash_t *lock_tokens;
apr_hash_t *sha1_checksums;
apr_array_header_t *commit_items;
@@ -615,55 +738,27 @@ svn_client_commit6(const apr_array_header_t *targets,
pool);
/* Crawl the working copy for commit items. */
- {
- struct check_url_kind_baton cukb;
-
- /* Prepare for when we have a copy containing not-present nodes. */
- cukb.pool = iterpool;
- cukb.session = NULL; /* ### Can we somehow reuse session? */
- cukb.repos_root_url = NULL;
- cukb.ctx = ctx;
-
- cmt_err = svn_error_trace(
- svn_client__harvest_committables(&committables,
- &lock_tokens,
- base_abspath,
- rel_targets,
- depth_empty_after,
- depth,
- ! keep_locks,
- changelists,
- check_url_kind,
- &cukb,
- ctx,
- pool,
- iterpool));
-
- svn_pool_clear(iterpool);
- }
+ cmt_err = svn_error_trace(
+ harvest_committables(&commit_items, &committables_by_path,
+ &lock_tokens,
+ base_abspath,
+ rel_targets,
+ depth_empty_after,
+ depth,
+ ! keep_locks,
+ changelists,
+ ctx,
+ pool,
+ iterpool));
+ svn_pool_clear(iterpool);
if (cmt_err)
goto cleanup;
- if (apr_hash_count(committables->by_repository) == 0)
+ if (!commit_items)
{
goto cleanup; /* Nothing to do */
}
- else if (apr_hash_count(committables->by_repository) > 1)
- {
- cmt_err = svn_error_create(
- SVN_ERR_UNSUPPORTED_FEATURE, NULL,
- _("Commit can only commit to a single repository at a time.\n"
- "Are all targets part of the same working copy?"));
- goto cleanup;
- }
-
- {
- apr_hash_index_t *hi = apr_hash_first(iterpool,
- committables->by_repository);
-
- commit_items = apr_hash_this_val(hi);
- }
/* If our array of targets contains only locks (and no actual file
or prop modifications), then we return here to avoid committing a
@@ -713,7 +808,7 @@ svn_client_commit6(const apr_array_header_t *targets,
if (moved_from_abspath && delete_op_root_abspath)
{
svn_client_commit_item3_t *delete_half =
- svn_hash_gets(committables->by_path, delete_op_root_abspath);
+ svn_hash_gets(committables_by_path, delete_op_root_abspath);
if (!delete_half)
{
@@ -769,7 +864,7 @@ svn_client_commit6(const apr_array_header_t *targets,
if (moved_to_abspath && copy_op_root_abspath &&
strcmp(moved_to_abspath, copy_op_root_abspath) == 0 &&
- svn_hash_gets(committables->by_path, copy_op_root_abspath)
+ svn_hash_gets(committables_by_path, copy_op_root_abspath)
== NULL)
{
cmt_err = svn_error_createf(
diff --git a/subversion/libsvn_client/commit_util.c b/subversion/libsvn_client/commit_util.c
index 1f3d87783436..2be3ecd5ce39 100644
--- a/subversion/libsvn_client/commit_util.c
+++ b/subversion/libsvn_client/commit_util.c
@@ -1392,6 +1392,29 @@ sort_commit_item_urls(const void *a, const void *b)
}
+svn_error_t *
+svn_client__condense_commit_items2(const char *base_url,
+ apr_array_header_t *commit_items,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *ci = commit_items; /* convenience */
+ int i;
+
+ /* Sort our commit items by their URLs. */
+ svn_sort__array(ci, sort_commit_item_urls);
+
+ /* Hack BASE_URL off each URL; store the result as session_relpath. */
+ for (i = 0; i < ci->nelts; i++)
+ {
+ svn_client_commit_item3_t *this_item
+ = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *);
+
+ this_item->session_relpath = svn_uri_skip_ancestor(base_url,
+ this_item->url, pool);
+ }
+
+ return SVN_NO_ERROR;
+}
svn_error_t *
svn_client__condense_commit_items(const char **base_url,
@@ -1501,8 +1524,6 @@ struct file_mod_t
/* A baton for use while driving a path-based editor driver for commit */
struct item_commit_baton
{
- const svn_delta_editor_t *editor; /* commit editor */
- void *edit_baton; /* commit editor's baton */
apr_hash_t *file_mods; /* hash: path->file_mod_t */
const char *notify_path_prefix; /* notification path prefix
(NULL is okay, else abs path) */
@@ -1524,6 +1545,8 @@ struct item_commit_baton
* This implements svn_delta_path_driver_cb_func_t. */
static svn_error_t *
do_item_commit(void **dir_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
void *parent_baton,
void *callback_baton,
const char *path,
@@ -1535,7 +1558,6 @@ do_item_commit(void **dir_baton,
svn_node_kind_t kind = item->kind;
void *file_baton = NULL;
apr_pool_t *file_pool = NULL;
- const svn_delta_editor_t *editor = icb->editor;
apr_hash_t *file_mods = icb->file_mods;
svn_client_ctx_t *ctx = icb->ctx;
svn_error_t *err;
@@ -1737,7 +1759,7 @@ do_item_commit(void **dir_baton,
{
if (! parent_baton)
{
- err = editor->open_root(icb->edit_baton, item->revision,
+ err = editor->open_root(edit_baton, item->revision,
pool, dir_baton);
}
else
@@ -1871,8 +1893,6 @@ svn_client__do_commit(const char *base_url,
}
/* Setup the callback baton. */
- cb_baton.editor = editor;
- cb_baton.edit_baton = edit_baton;
cb_baton.file_mods = file_mods;
cb_baton.notify_path_prefix = notify_path_prefix;
cb_baton.ctx = ctx;
@@ -1880,7 +1900,7 @@ svn_client__do_commit(const char *base_url,
cb_baton.base_url = base_url;
/* Drive the commit editor! */
- SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
+ SVN_ERR(svn_delta_path_driver3(editor, edit_baton, paths, TRUE,
do_item_commit, &cb_baton, scratch_pool));
/* Transmit outstanding text deltas. */
diff --git a/subversion/libsvn_client/conflicts.c b/subversion/libsvn_client/conflicts.c
index 0b342e0e0a1e..9a58703c4238 100644
--- a/subversion/libsvn_client/conflicts.c
+++ b/subversion/libsvn_client/conflicts.c
@@ -255,7 +255,7 @@ struct repos_move_info {
/* The revision in which this move was committed. */
svn_revnum_t rev;
- /* The author who commited the revision in which this move was committed. */
+ /* The author who committed the revision in which this move was committed. */
const char *rev_author;
/* The repository relpath the node was moved from in this revision. */
@@ -383,7 +383,7 @@ add_new_move(struct repos_move_info **new_move,
const char *author,
apr_hash_t *moved_paths,
svn_ra_session_t *ra_session,
- const char *repos_root_url,
+ const char *repos_root_url,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
@@ -661,7 +661,7 @@ match_copies_to_deletion(const char *deleted_repos_relpath,
TRUE, iterpool));
if (!related)
continue;
-
+
/* Remember details of this move. */
SVN_ERR(add_new_move(&move, deleted_repos_relpath,
copy->copyto_path, copy->copyfrom_rev,
@@ -669,7 +669,7 @@ match_copies_to_deletion(const char *deleted_repos_relpath,
moved_paths, ra_session, repos_root_url,
result_pool, iterpool));
push_move(move, moves_table, result_pool);
- }
+ }
}
else
{
@@ -782,7 +782,7 @@ map_deleted_path_to_move(const char *deleted_relpath,
{
const char *relpath;
struct repos_move_info *move;
-
+
move = APR_ARRAY_IDX(moves, i, struct repos_move_info *);
if (strcmp(move->moved_from_repos_relpath, deleted_relpath) == 0)
return move;
@@ -806,18 +806,20 @@ map_deleted_path_to_move(const char *deleted_relpath,
if (closest_move)
{
const char *relpath;
- const char *moved_along_path;
- struct repos_move_info *move;
-
+
/* See if we can find an even closer move for this moved-along path. */
relpath = svn_relpath_skip_ancestor(closest_move->moved_to_repos_relpath,
deleted_relpath);
- moved_along_path =
- svn_relpath_join(closest_move->moved_from_repos_relpath, relpath,
- scratch_pool);
- move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool);
- if (move)
- return move;
+ if (relpath && relpath[0] != '\0')
+ {
+ struct repos_move_info *move;
+ const char *moved_along_path =
+ svn_relpath_join(closest_move->moved_from_repos_relpath, relpath,
+ scratch_pool);
+ move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool);
+ if (move)
+ return move;
+ }
}
return closest_move;
@@ -965,7 +967,7 @@ cache_copied_item(apr_hash_t *copies, const char *changed_path,
* This function answers the same question as svn_ra_get_deleted_rev() but
* works in cases where we do not already know a revision in which the deleted
* node once used to exist.
- *
+ *
* If the node was moved, rather than deleted, return move information
* in BATON->MOVE.
*/
@@ -1096,7 +1098,7 @@ find_deleted_rev(void *baton,
b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data);
else
b->deleted_rev_author = _("unknown author");
-
+
b->replacing_node_kind = replacing_node_kind;
/* We're done. Abort the log operation. */
@@ -1171,7 +1173,7 @@ describe_local_file_node_change(const char **description,
const char *moved_to_abspath;
svn_error_t *err;
- err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
+ err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
ctx->wc_ctx,
conflict->local_abspath,
scratch_pool,
@@ -1255,7 +1257,7 @@ describe_local_file_node_change(const char **description,
{
const char *moved_from_abspath;
- SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
+ SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
ctx->wc_ctx,
conflict->local_abspath,
scratch_pool,
@@ -1396,7 +1398,7 @@ describe_local_dir_node_change(const char **description,
const char *moved_to_abspath;
svn_error_t *err;
- err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
+ err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
ctx->wc_ctx,
conflict->local_abspath,
scratch_pool,
@@ -1481,7 +1483,7 @@ describe_local_dir_node_change(const char **description,
{
const char *moved_from_abspath;
- SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
+ SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
ctx->wc_ctx,
conflict->local_abspath,
scratch_pool,
@@ -1579,7 +1581,7 @@ struct find_moves_baton
* rB: mv b->c
* rC: mv c->d
* we map each revision number to all the moves which happened in the
- * revision, which looks as follows:
+ * revision, which looks as follows:
* rA : [(x->z), (a->b)]
* rB : [(b->c)]
* rC : [(c->d)]
@@ -2100,33 +2102,6 @@ trace_moved_node_backwards(apr_hash_t *moves_table,
return SVN_NO_ERROR;
}
-static svn_error_t *
-reparent_session_and_fetch_node_kind(svn_node_kind_t *node_kind,
- svn_ra_session_t *ra_session,
- const char *url,
- svn_revnum_t peg_rev,
- apr_pool_t *scratch_pool)
-{
- svn_error_t *err;
-
- err = svn_ra_reparent(ra_session, url, scratch_pool);
- if (err)
- {
- if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
- {
- svn_error_clear(err);
- *node_kind = svn_node_unknown;
- return SVN_NO_ERROR;
- }
-
- return svn_error_trace(err);
- }
-
- SVN_ERR(svn_ra_check_path(ra_session, "", peg_rev, node_kind, scratch_pool));
-
- return SVN_NO_ERROR;
-}
-
/* Scan MOVES_TABLE for moves which affect a particular deleted node, and
* build a set of new move information for this node.
* Return heads of all possible move chains in *MOVES.
@@ -2173,22 +2148,29 @@ find_operative_moves(apr_array_header_t **moves,
svn_pool_clear(iterpool);
move = APR_ARRAY_IDX(moves_in_deleted_rev, i, struct repos_move_info *);
- relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath,
+ if (strcmp(move->moved_from_repos_relpath, deleted_repos_relpath) == 0)
+ {
+ APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
+ continue;
+ }
+
+ /* Test for an operative nested move. */
+ relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath,
deleted_repos_relpath);
if (relpath && relpath[0] != '\0')
{
- svn_node_kind_t node_kind;
-
- url = svn_path_url_add_component2(repos_root_url,
- deleted_repos_relpath,
- iterpool);
- SVN_ERR(reparent_session_and_fetch_node_kind(&node_kind,
- ra_session, url,
- rev_below(deleted_rev),
- iterpool));
- move = new_path_adjusted_move(move, relpath, node_kind, result_pool);
+ struct repos_move_info *nested_move;
+ const char *actual_deleted_repos_relpath;
+
+ actual_deleted_repos_relpath =
+ svn_relpath_join(move->moved_from_repos_relpath, relpath,
+ iterpool);
+ nested_move = map_deleted_path_to_move(actual_deleted_repos_relpath,
+ moves_in_deleted_rev,
+ iterpool);
+ if (nested_move)
+ APR_ARRAY_PUSH(*moves, struct repos_move_info *) = nested_move;
}
- APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move;
}
if (url != NULL)
@@ -2368,6 +2350,8 @@ struct conflict_tree_local_missing_details
/* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */
svn_revnum_t deleted_rev;
+ /* ### Add 'added_rev', like in conflict_tree_incoming_delete_details? */
+
/* Author who committed DELETED_REV. */
const char *deleted_rev_author;
@@ -2375,21 +2359,49 @@ struct conflict_tree_local_missing_details
const char *deleted_repos_relpath;
/* Move information about the conflict victim. If not NULL, this is an
- * array of repos_move_info elements. Each element is the head of a
- * move chain which starts in DELETED_REV. */
+ * array of 'struct repos_move_info *' elements. Each element is the
+ * head of a move chain which starts in DELETED_REV. */
apr_array_header_t *moves;
+ /* If moves is not NULL, a map of repos_relpaths and working copy nodes.
+ *
+ * Each key is a "const char *" repository relpath corresponding to a
+ * possible repository-side move destination node in the revision which
+ * is the merge-right revision in case of a merge.
+ *
+ * Each value is an apr_array_header_t *.
+ * Each array consists of "const char *" absolute paths to working copy
+ * nodes which correspond to the repository node selected by the map key.
+ * Each such working copy node is a potential local move target which can
+ * be chosen to find a suitable merge target when resolving a tree conflict.
+ *
+ * This may be an empty hash map in case if there is no move target path
+ * in the working copy. */
+ apr_hash_t *wc_move_targets;
+
+ /* If not NULL, the preferred move target repository relpath. This is our key
+ * into the WC_MOVE_TARGETS map above (can be overridden by the user). */
+ const char *move_target_repos_relpath;
+
+ /* The current index into the list of working copy nodes corresponding to
+ * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */
+ int wc_move_target_idx;
+
/* Move information about siblings. Siblings are nodes which share
* a youngest common ancestor with the conflict victim. E.g. in case
* of a merge operation they are part of the merge source branch.
- * If not NULL, this is an array of repos_move_info elements.
+ * If not NULL, this is an array of 'struct repos_move_info *' elements.
* Each element is the head of a move chain, which starts at some
* point in history after siblings and conflict victim forked off
* their common ancestor. */
apr_array_header_t *sibling_moves;
- /* If not NULL, this is the move target abspath. */
- const char *moved_to_abspath;
+ /* List of nodes in the WC which are suitable merge targets for changes
+ * merged from any moved sibling. Array elements are 'const char *'
+ * absolute paths of working copy nodes. This array contains multiple
+ * elements only if ambiguous matches were found in the WC. */
+ apr_array_header_t *wc_siblings;
+ int preferred_sibling_idx;
};
static svn_error_t *
@@ -2608,6 +2620,168 @@ find_moves_in_natural_history(apr_array_header_t **moves,
return SVN_NO_ERROR;
}
+static svn_error_t *
+collect_sibling_move_candidates(apr_array_header_t *candidates,
+ const char *victim_abspath,
+ svn_node_kind_t victim_kind,
+ struct repos_move_info *move,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *basename;
+ apr_array_header_t *abspaths;
+ int i;
+
+ basename = svn_relpath_basename(move->moved_from_repos_relpath, scratch_pool);
+ SVN_ERR(svn_wc__find_working_nodes_with_basename(&abspaths, victim_abspath,
+ basename, victim_kind,
+ ctx->wc_ctx, result_pool,
+ scratch_pool));
+ apr_array_cat(candidates, abspaths);
+
+ if (move->next)
+ {
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < move->next->nelts; i++)
+ {
+ struct repos_move_info *next_move;
+ next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
+ SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath,
+ victim_kind, next_move, ctx,
+ result_pool, iterpool));
+ svn_pool_clear(iterpool);
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Follow each move chain starting a MOVE all the way to the end to find
+ * the possible working copy locations for VICTIM_ABSPATH which corresponds
+ * to VICTIM_REPOS_REPLATH@VICTIM_REVISION.
+ * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the
+ * repos_relpath which is the corresponding move destination in the repository.
+ * This function is recursive. */
+static svn_error_t *
+follow_move_chains(apr_hash_t *wc_move_targets,
+ struct repos_move_info *move,
+ svn_client_ctx_t *ctx,
+ const char *victim_abspath,
+ svn_node_kind_t victim_node_kind,
+ const char *victim_repos_relpath,
+ svn_revnum_t victim_revision,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *candidate_abspaths;
+
+ /* Gather candidate nodes which represent this moved_to_repos_relpath. */
+ SVN_ERR(svn_wc__guess_incoming_move_target_nodes(
+ &candidate_abspaths, ctx->wc_ctx,
+ victim_abspath, victim_node_kind,
+ move->moved_to_repos_relpath,
+ scratch_pool, scratch_pool));
+
+ if (candidate_abspaths->nelts > 0)
+ {
+ apr_array_header_t *moved_to_abspaths;
+ int i;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+ moved_to_abspaths = apr_array_make(result_pool, 1,
+ sizeof (const char *));
+
+ for (i = 0; i < candidate_abspaths->nelts; i++)
+ {
+ const char *candidate_abspath;
+ const char *repos_root_url;
+ const char *repos_uuid;
+ const char *candidate_repos_relpath;
+ svn_revnum_t candidate_revision;
+
+ svn_pool_clear(iterpool);
+
+ candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
+ const char *);
+ SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
+ &candidate_repos_relpath,
+ &repos_root_url,
+ &repos_uuid,
+ NULL, NULL,
+ ctx->wc_ctx,
+ candidate_abspath,
+ FALSE,
+ iterpool, iterpool));
+
+ if (candidate_revision == SVN_INVALID_REVNUM)
+ continue;
+
+ /* If the conflict victim and the move target candidate
+ * are not from the same revision we must ensure that
+ * they are related. */
+ if (candidate_revision != victim_revision)
+ {
+ svn_client__pathrev_t *yca_loc;
+ svn_error_t *err;
+
+ err = find_yca(&yca_loc, victim_repos_relpath,
+ victim_revision,
+ candidate_repos_relpath,
+ candidate_revision,
+ repos_root_url, repos_uuid,
+ NULL, ctx, iterpool, iterpool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ yca_loc = NULL;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ if (yca_loc == NULL)
+ continue;
+ }
+
+ APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
+ apr_pstrdup(result_pool, candidate_abspath);
+ }
+ svn_pool_destroy(iterpool);
+
+ svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
+ moved_to_abspaths);
+ }
+
+ if (move->next)
+ {
+ int i;
+ apr_pool_t *iterpool;
+
+ /* Recurse into each of the possible move chains. */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < move->next->nelts; i++)
+ {
+ struct repos_move_info *next_move;
+
+ svn_pool_clear(iterpool);
+
+ next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
+ SVN_ERR(follow_move_chains(wc_move_targets, next_move,
+ ctx, victim_abspath, victim_node_kind,
+ victim_repos_relpath, victim_revision,
+ result_pool, iterpool));
+
+ }
+ svn_pool_destroy(iterpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* Implements tree_conflict_get_details_func_t. */
static svn_error_t *
conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
@@ -2621,12 +2795,15 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
svn_revnum_t old_rev;
svn_revnum_t new_rev;
svn_revnum_t deleted_rev;
+ svn_node_kind_t old_kind;
+ svn_node_kind_t new_kind;
const char *deleted_rev_author;
svn_node_kind_t replacing_node_kind;
const char *deleted_basename;
struct conflict_tree_local_missing_details *details;
apr_array_header_t *moves = NULL;
apr_array_header_t *sibling_moves = NULL;
+ apr_array_header_t *wc_siblings = NULL;
const char *related_repos_relpath;
svn_revnum_t related_peg_rev;
const char *repos_root_url;
@@ -2637,10 +2814,10 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
svn_revnum_t end_rev;
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
- &old_repos_relpath, &old_rev, NULL, conflict,
+ &old_repos_relpath, &old_rev, &old_kind, conflict,
scratch_pool, scratch_pool));
SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
- &new_repos_relpath, &new_rev, NULL, conflict,
+ &new_repos_relpath, &new_rev, &new_kind, conflict,
scratch_pool, scratch_pool));
/* Scan the conflict victim's parent's log to find a revision which
@@ -2656,9 +2833,14 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
scratch_pool,
scratch_pool));
+ /* If the parent is not part of the repository-side tree checked out
+ * into this working copy, then bail. We do not support this case yet. */
+ if (parent_peg_rev == SVN_INVALID_REVNUM)
+ return SVN_NO_ERROR;
+
/* Pick the younger incoming node as our 'related node' which helps
* pin-pointing the deleted conflict victim in history. */
- related_repos_relpath =
+ related_repos_relpath =
(old_rev < new_rev ? new_repos_relpath : old_repos_relpath);
related_peg_rev = (old_rev < new_rev ? new_rev : old_rev);
@@ -2711,6 +2893,9 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
{
const char *victim_abspath;
svn_node_kind_t related_node_kind;
+ apr_array_header_t *candidates;
+ int i;
+ apr_pool_t *iterpool;
/* ### The following describes all moves in terms of forward-merges,
* should do we something else for reverse-merges? */
@@ -2744,7 +2929,81 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
if (sibling_moves == NULL)
return SVN_NO_ERROR;
- /* ## TODO: Find the missing node in the WC. */
+ /* Find the missing node in the WC. In theory, this requires tracing
+ * back history of every node in the WC to check for a YCA with the
+ * conflict victim. This operation would obviously be quite expensive.
+ *
+ * However, assuming that the victim was not moved in the merge target,
+ * we can take a short-cut: The basename of the node cannot have changed,
+ * so we can limit history tracing to nodes with a matching basename.
+ *
+ * This approach solves the conflict case where an edit to a file which
+ * was moved on one branch is cherry-picked to another branch where the
+ * corresponding file has not been moved (yet). It does not solve move
+ * vs. move conflicts, but such conflicts are not yet supported by the
+ * resolver anyway and are hard to solve without server-side support. */
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < sibling_moves->nelts; i++)
+ {
+ struct repos_move_info *move;
+ int j;
+
+ svn_pool_clear(iterpool);
+
+ move = APR_ARRAY_IDX(sibling_moves, i, struct repos_move_info *);
+ candidates = apr_array_make(iterpool, 1, sizeof(const char *));
+ SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath,
+ old_rev < new_rev
+ ? new_kind : old_kind,
+ move, ctx, iterpool,
+ iterpool));
+
+ /* Determine whether a candidate node shares a YCA with the victim. */
+ for (j = 0; j < candidates->nelts; j++)
+ {
+ const char *candidate_abspath;
+ const char *candidate_repos_relpath;
+ svn_revnum_t candidate_revision;
+ svn_error_t *err;
+
+ candidate_abspath = APR_ARRAY_IDX(candidates, j, const char *);
+ SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
+ &candidate_repos_relpath,
+ NULL, NULL, NULL, NULL,
+ ctx->wc_ctx,
+ candidate_abspath,
+ FALSE,
+ iterpool, iterpool));
+ err = find_yca(&yca_loc,
+ old_rev < new_rev
+ ? new_repos_relpath : old_repos_relpath,
+ old_rev < new_rev ? new_rev : old_rev,
+ candidate_repos_relpath,
+ candidate_revision,
+ repos_root_url, repos_uuid,
+ NULL, ctx, iterpool, iterpool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ yca_loc = NULL;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ if (yca_loc)
+ {
+ if (wc_siblings == NULL)
+ wc_siblings = apr_array_make(conflict->pool, 1,
+ sizeof(const char *));
+ APR_ARRAY_PUSH(wc_siblings, const char *) =
+ apr_pstrdup(conflict->pool, candidate_abspath);
+ }
+ }
+ }
+ svn_pool_destroy(iterpool);
}
details = apr_pcalloc(conflict->pool, sizeof(*details));
@@ -2753,10 +3012,85 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict,
if (deleted_rev != SVN_INVALID_REVNUM)
details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath,
deleted_basename,
- conflict->pool);
+ conflict->pool);
details->moves = moves;
+ if (details->moves != NULL)
+ {
+ apr_pool_t *iterpool;
+ int i;
+
+ details->wc_move_targets = apr_hash_make(conflict->pool);
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < details->moves->nelts; i++)
+ {
+ struct repos_move_info *move;
+
+ svn_pool_clear(iterpool);
+ move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *);
+ SVN_ERR(follow_move_chains(details->wc_move_targets, move, ctx,
+ conflict->local_abspath,
+ new_kind,
+ new_repos_relpath,
+ new_rev,
+ scratch_pool, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ if (apr_hash_count(details->wc_move_targets) > 0)
+ {
+ apr_array_header_t *move_target_repos_relpaths;
+ const svn_sort__item_t *item;
+
+ /* Initialize to the first possible move target. Hopefully,
+ * in most cases there will only be one candidate anyway. */
+ move_target_repos_relpaths = svn_sort__hash(
+ details->wc_move_targets,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
+ item = &APR_ARRAY_IDX(move_target_repos_relpaths,
+ 0, svn_sort__item_t);
+ details->move_target_repos_relpath = item->key;
+ details->wc_move_target_idx = 0;
+ }
+ else
+ {
+ details->move_target_repos_relpath = NULL;
+ details->wc_move_target_idx = 0;
+ }
+ }
+
details->sibling_moves = sibling_moves;
-
+ details->wc_siblings = wc_siblings;
+ if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) == 1)
+ {
+ apr_array_header_t *wc_abspaths;
+
+ wc_abspaths = svn_hash_gets(details->wc_move_targets,
+ details->move_target_repos_relpath);
+ if (wc_abspaths->nelts == 1)
+ {
+ svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind;
+
+ if (kind == svn_node_file)
+ conflict->recommended_option_id =
+ svn_client_conflict_option_local_move_file_text_merge;
+ else if (kind == svn_node_dir)
+ conflict->recommended_option_id =
+ svn_client_conflict_option_local_move_dir_merge;
+ }
+ }
+ else if (details->wc_siblings && details->wc_siblings->nelts == 1)
+ {
+ svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind;
+
+ if (kind == svn_node_file)
+ conflict->recommended_option_id =
+ svn_client_conflict_option_sibling_move_file_text_merge;
+ else if (kind == svn_node_dir)
+ conflict->recommended_option_id =
+ svn_client_conflict_option_sibling_move_dir_merge;
+ }
+
conflict->tree_conflict_local_details = details;
return SVN_NO_ERROR;
@@ -2910,7 +3244,7 @@ conflict_tree_get_description_local_missing(const char **description,
if (details->moves || details->sibling_moves)
{
struct repos_move_info *move;
-
+
*description = _("No such file or directory was found in the "
"merge target working copy.\n");
@@ -3590,7 +3924,7 @@ describe_incoming_deletion_upon_update(
struct repos_move_info *move;
move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
- description =
+ description =
apr_psprintf(result_pool,
_("Item updated from r%ld to r%ld was moved "
"to '^/%s' by %s in r%ld."), old_rev, new_rev,
@@ -3728,7 +4062,7 @@ describe_incoming_deletion_upon_switch(
result_pool,
scratch_pool);
}
- return description;
+ return description;
}
else if (victim_node_kind == svn_node_file ||
victim_node_kind == svn_node_symlink)
@@ -3881,7 +4215,7 @@ describe_incoming_deletion_upon_switch(
{
struct repos_move_info *move;
const char *description;
-
+
move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *);
description =
apr_psprintf(result_pool,
@@ -4512,7 +4846,7 @@ conflict_tree_get_description_incoming_delete(
action = describe_incoming_reverse_addition_upon_switch(
details, victim_node_kind, old_repos_relpath, old_rev,
new_repos_relpath, new_rev, result_pool);
-
+
}
}
else if (conflict_operation == svn_wc_operation_merge)
@@ -4665,133 +4999,6 @@ get_incoming_delete_details_for_reverse_addition(
return SVN_NO_ERROR;
}
-/* Follow each move chain starting a MOVE all the way to the end to find
- * the possible working copy locations for VICTIM_ABSPATH which corresponds
- * to VICTIM_REPOS_REPLATH@VICTIM_REVISION.
- * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the
- * repos_relpath which is the corresponding move destination in the repository.
- * This function is recursive. */
-static svn_error_t *
-follow_move_chains(apr_hash_t *wc_move_targets,
- struct repos_move_info *move,
- svn_client_ctx_t *ctx,
- const char *victim_abspath,
- svn_node_kind_t victim_node_kind,
- const char *victim_repos_relpath,
- svn_revnum_t victim_revision,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- /* If this is the end of a move chain, look for matching paths in
- * the working copy and add them to our collection if found. */
- if (move->next == NULL)
- {
- apr_array_header_t *candidate_abspaths;
-
- /* Gather candidate nodes which represent this moved_to_repos_relpath. */
- SVN_ERR(svn_wc__guess_incoming_move_target_nodes(
- &candidate_abspaths, ctx->wc_ctx,
- victim_abspath, victim_node_kind,
- move->moved_to_repos_relpath,
- scratch_pool, scratch_pool));
- if (candidate_abspaths->nelts > 0)
- {
- apr_array_header_t *moved_to_abspaths;
- int i;
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
-
- moved_to_abspaths = apr_array_make(result_pool, 1,
- sizeof (const char *));
-
- for (i = 0; i < candidate_abspaths->nelts; i++)
- {
- const char *candidate_abspath;
- const char *repos_root_url;
- const char *repos_uuid;
- const char *candidate_repos_relpath;
- svn_revnum_t candidate_revision;
-
- svn_pool_clear(iterpool);
-
- candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i,
- const char *);
- SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision,
- &candidate_repos_relpath,
- &repos_root_url,
- &repos_uuid,
- NULL, NULL,
- ctx->wc_ctx,
- candidate_abspath,
- FALSE,
- iterpool, iterpool));
-
- if (candidate_revision == SVN_INVALID_REVNUM)
- continue;
-
- /* If the conflict victim and the move target candidate
- * are not from the same revision we must ensure that
- * they are related. */
- if (candidate_revision != victim_revision)
- {
- svn_client__pathrev_t *yca_loc;
- svn_error_t *err;
-
- err = find_yca(&yca_loc, victim_repos_relpath,
- victim_revision,
- candidate_repos_relpath,
- candidate_revision,
- repos_root_url, repos_uuid,
- NULL, ctx, iterpool, iterpool);
- if (err)
- {
- if (err->apr_err == SVN_ERR_FS_NOT_FOUND)
- {
- svn_error_clear(err);
- yca_loc = NULL;
- }
- else
- return svn_error_trace(err);
- }
-
- if (yca_loc == NULL)
- continue;
- }
-
- APR_ARRAY_PUSH(moved_to_abspaths, const char *) =
- apr_pstrdup(result_pool, candidate_abspath);
- }
- svn_pool_destroy(iterpool);
-
- svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath,
- moved_to_abspaths);
- }
- }
- else
- {
- int i;
- apr_pool_t *iterpool;
-
- /* Recurse into each of the possible move chains. */
- iterpool = svn_pool_create(scratch_pool);
- for (i = 0; i < move->next->nelts; i++)
- {
- struct repos_move_info *next_move;
-
- svn_pool_clear(iterpool);
-
- next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *);
- SVN_ERR(follow_move_chains(wc_move_targets, next_move,
- ctx, victim_abspath, victim_node_kind,
- victim_repos_relpath, victim_revision,
- result_pool, iterpool));
-
- }
- svn_pool_destroy(iterpool);
- }
-
- return SVN_NO_ERROR;
-}
-
static svn_error_t *
init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
svn_client_conflict_t *conflict,
@@ -4803,11 +5010,9 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
svn_node_kind_t victim_node_kind;
const char *incoming_new_repos_relpath;
svn_revnum_t incoming_new_pegrev;
- svn_wc_operation_t operation;
victim_abspath = svn_client_conflict_get_local_abspath(conflict);
victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
- operation = svn_client_conflict_get_operation(conflict);
/* ### Should we get the old location in case of reverse-merges? */
SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
&incoming_new_repos_relpath, &incoming_new_pegrev,
@@ -4833,11 +5038,8 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
get_moved_to_repos_relpath(details, scratch_pool);
details->wc_move_target_idx = 0;
- /* If only one move target exists after an update or switch,
- * recommend a resolution option which follows the incoming move. */
- if (apr_hash_count(details->wc_move_targets) == 1 &&
- (operation == svn_wc_operation_update ||
- operation == svn_wc_operation_switch))
+ /* If only one move target exists recommend a resolution option. */
+ if (apr_hash_count(details->wc_move_targets) == 1)
{
apr_array_header_t *wc_abspaths;
@@ -4850,7 +5052,10 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details,
/* Only one of these will be present for any given conflict. */
svn_client_conflict_option_incoming_move_file_text_merge,
svn_client_conflict_option_incoming_move_dir_merge,
- svn_client_conflict_option_local_move_file_text_merge
+ svn_client_conflict_option_local_move_file_text_merge,
+ svn_client_conflict_option_local_move_dir_merge,
+ svn_client_conflict_option_sibling_move_file_text_merge,
+ svn_client_conflict_option_sibling_move_dir_merge,
};
apr_array_header_t *options;
@@ -4907,6 +5112,7 @@ conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict,
const char *parent_repos_relpath;
svn_revnum_t parent_peg_rev;
svn_revnum_t deleted_rev;
+ svn_revnum_t end_rev;
const char *deleted_rev_author;
svn_node_kind_t replacing_node_kind;
apr_array_header_t *moves;
@@ -4939,12 +5145,15 @@ conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict,
related_peg_rev = SVN_INVALID_REVNUM;
}
+ end_rev = (new_kind == svn_node_none ? 0 : old_rev);
+ if (end_rev >= parent_peg_rev)
+ end_rev = (parent_peg_rev > 0 ? parent_peg_rev - 1 : 0);
+
SVN_ERR(find_revision_for_suspected_deletion(
&deleted_rev, &deleted_rev_author, &replacing_node_kind,
&moves, conflict,
svn_dirent_basename(conflict->local_abspath, scratch_pool),
- parent_repos_relpath, parent_peg_rev,
- new_kind == svn_node_none ? 0 : old_rev,
+ parent_repos_relpath, parent_peg_rev, end_rev,
related_repos_relpath, related_peg_rev,
ctx, conflict->pool, scratch_pool));
if (deleted_rev == SVN_INVALID_REVNUM)
@@ -5077,7 +5286,7 @@ conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict,
const char *repos_root_url;
svn_revnum_t old_rev;
svn_revnum_t new_rev;
- struct conflict_tree_incoming_add_details *details;
+ struct conflict_tree_incoming_add_details *details = NULL;
svn_wc_operation_t operation;
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
@@ -5170,7 +5379,8 @@ conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict,
}
}
}
- else if (operation == svn_wc_operation_merge)
+ else if (operation == svn_wc_operation_merge &&
+ strcmp(old_repos_relpath, new_repos_relpath) == 0)
{
if (old_rev < new_rev)
{
@@ -5221,7 +5431,7 @@ conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict,
details->deleted_rev = SVN_INVALID_REVNUM;
details->deleted_rev_author = NULL;
}
- else
+ else if (old_rev > new_rev)
{
/* The merge operation was a reverse-merge.
* This addition is in fact a deletion, applied in reverse,
@@ -5261,10 +5471,6 @@ conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict,
details->moves = moves;
}
}
- else
- {
- details = NULL;
- }
conflict->tree_conflict_incoming_details = details;
@@ -5727,7 +5933,10 @@ find_modified_rev(void *baton,
if (log_item->copyfrom_path)
b->repos_relpath = apr_pstrdup(b->scratch_pool,
- log_item->copyfrom_path);
+ /* ### remove leading slash */
+ svn_relpath_canonicalize(
+ log_item->copyfrom_path,
+ iterpool));
}
else if (b->node_kind == svn_node_dir &&
svn_relpath_skip_ancestor(b->repos_relpath, path) != NULL)
@@ -5931,6 +6140,9 @@ describe_incoming_edit_list_modified_revs(apr_array_header_t *edits,
const char *s = "";
int i;
+ if (edits->nelts == 0)
+ return _(" (no revisions found)");
+
if (edits->nelts <= max_revs_to_display)
num_revs_to_skip = 0;
else
@@ -5967,15 +6179,18 @@ describe_incoming_edit_list_modified_revs(apr_array_header_t *edits,
{
if (i == edits->nelts - (max_revs_to_display / 2))
s = apr_psprintf(result_pool,
- _("%s\n [%d revisions omitted for "
- "brevity],\n"),
+ Q_("%s\n [%d revision omitted for "
+ "brevity],\n",
+ "%s\n [%d revisions omitted for "
+ "brevity],\n",
+ num_revs_to_skip),
s, num_revs_to_skip);
s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
details->rev, details->author,
i < edits->nelts - 1 ? "," : "");
}
- }
+ }
else
s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s,
details->rev, details->author,
@@ -6124,7 +6339,7 @@ conflict_tree_get_description_incoming_edit(
"during reverse-merge of\n"
"'^/%s:%ld-%ld'"),
new_repos_relpath, new_rev + 1, old_rev);
-
+
else
action = apr_psprintf(scratch_pool,
_("Changes from the following revisions "
@@ -6159,7 +6374,7 @@ svn_client_conflict_tree_get_description(
SVN_ERR(conflict->tree_conflict_get_local_description_func(
local_change_description,
conflict, ctx, result_pool, scratch_pool));
-
+
return SVN_NO_ERROR;
}
@@ -6683,8 +6898,10 @@ resolve_merge_incoming_added_file_text_update(
apr_hash_t *working_props;
apr_array_header_t *propdiffs;
svn_error_t *err;
+ svn_wc_conflict_reason_t local_change;
local_abspath = svn_client_conflict_get_local_abspath(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
/* Set up tempory storage for the working version of file. */
SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
@@ -6695,20 +6912,31 @@ resolve_merge_incoming_added_file_text_update(
svn_io_file_del_none,
scratch_pool, scratch_pool));
- /* Copy the detranslated working file to temporary storage. */
- SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx,
- local_abspath, local_abspath,
- SVN_WC_TRANSLATE_TO_NF,
- scratch_pool, scratch_pool));
+ if (local_change == svn_wc_conflict_reason_unversioned)
+ {
+ /* Copy the unversioned file to temporary storage. */
+ SVN_ERR(svn_stream_open_readonly(&working_file_stream, local_abspath,
+ scratch_pool, scratch_pool));
+ /* Unversioned files have no properties. */
+ working_props = apr_hash_make(scratch_pool);
+ }
+ else
+ {
+ /* Copy the detranslated working file to temporary storage. */
+ SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx,
+ local_abspath, local_abspath,
+ SVN_WC_TRANSLATE_TO_NF,
+ scratch_pool, scratch_pool));
+ /* Get a copy of the working file's properties. */
+ SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ filter_props(working_props, scratch_pool);
+ }
+
SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream,
ctx->cancel_func, ctx->cancel_baton,
scratch_pool));
- /* Get a copy of the working file's properties. */
- SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath,
- scratch_pool, scratch_pool));
- filter_props(working_props, scratch_pool);
-
/* Create an empty file as fake "merge-base" for the two added files.
* The files are not ancestrally related so this is the best we can do. */
SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
@@ -6727,8 +6955,9 @@ resolve_merge_incoming_added_file_text_update(
/* Revert the path in order to restore the repository's line of
* history, which is part of the BASE tree. This revert operation
* is why are being careful about not losing the temporary copy. */
- err = svn_wc_revert5(ctx->wc_ctx, local_abspath, svn_depth_empty,
+ err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_empty,
FALSE, NULL, TRUE, FALSE,
+ TRUE /*added_keep_local*/,
NULL, NULL, /* no cancellation */
ctx->notify_func2, ctx->notify_baton2,
scratch_pool);
@@ -6760,7 +6989,7 @@ unlock_wc:
scratch_pool));
svn_io_sleep_for_timestamps(local_abspath, scratch_pool);
SVN_ERR(err);
-
+
if (ctx->notify_func2)
{
svn_wc_notify_t *notify;
@@ -7545,7 +7774,6 @@ merge_newly_added_dir(const char *added_repos_relpath,
diff_processor = processor;
if (reverse_merge)
diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
- NULL,
scratch_pool);
/* Filter the first path component using a filter processor, until we fixed
@@ -7707,20 +7935,47 @@ resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option,
const char *local_abspath;
const char *lock_abspath;
svn_error_t *err;
+ svn_wc_conflict_reason_t local_change;
local_abspath = svn_client_conflict_get_local_abspath(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
- SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
- &lock_abspath, ctx->wc_ctx, local_abspath,
- scratch_pool, scratch_pool));
+ if (local_change == svn_wc_conflict_reason_unversioned)
+ {
+ char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+ &lock_abspath, ctx->wc_ctx, parent_abspath,
+ scratch_pool, scratch_pool));
- err = svn_wc__conflict_tree_update_local_add(ctx->wc_ctx,
- local_abspath,
- ctx->cancel_func,
- ctx->cancel_baton,
- ctx->notify_func2,
- ctx->notify_baton2,
- scratch_pool);
+ /* The update/switch operation has added the incoming versioned
+ * directory as a deleted op-depth layer. We can revert this layer
+ * to make the incoming tree appear in the working copy.
+ * This meta-data-only revert operation effecively merges the
+ * versioned and unversioned trees but leaves all unversioned files as
+ * they were. This is the best we can do; 3-way merging of unversioned
+ * files with files from the repository is impossible because there is
+ * no known merge base. No unversioned data will be lost, and any
+ * differences to files in the repository will show up in 'svn diff'. */
+ err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_infinity,
+ FALSE, NULL, TRUE, TRUE /* metadata_only */,
+ TRUE /*added_keep_local*/,
+ NULL, NULL, /* no cancellation */
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool);
+ }
+ else
+ {
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+ &lock_abspath, ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ err = svn_wc__conflict_tree_update_local_add(ctx->wc_ctx,
+ local_abspath,
+ ctx->cancel_func,
+ ctx->cancel_baton,
+ ctx->notify_func2,
+ ctx->notify_baton2,
+ scratch_pool);
+ }
err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
lock_abspath,
@@ -7730,35 +7985,6 @@ resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option,
return SVN_NO_ERROR;
}
-/* A baton for notification_adjust_func(). */
-struct notification_adjust_baton
-{
- svn_wc_notify_func2_t inner_func;
- void *inner_baton;
- const char *checkout_abspath;
- const char *final_abspath;
-};
-
-/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
- * baton is BATON->inner_baton) and adjusts the notification paths that
- * start with BATON->checkout_abspath to start instead with
- * BATON->final_abspath. */
-static void
-notification_adjust_func(void *baton,
- const svn_wc_notify_t *notify,
- apr_pool_t *pool)
-{
- struct notification_adjust_baton *nb = baton;
- svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
- const char *relpath;
-
- relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
- inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
-
- if (nb->inner_func)
- nb->inner_func(nb->inner_baton, inner_notify, pool);
-}
-
/* Resolve a dir/dir "incoming add vs local obstruction" tree conflict by
* replacing the local directory with the incoming directory.
* If MERGE_DIRS is set, also merge the directories after replacing. */
@@ -7777,14 +8003,8 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
svn_revnum_t incoming_new_pegrev;
const char *local_abspath;
const char *lock_abspath;
- const char *tmpdir_abspath, *tmp_abspath;
svn_error_t *err;
- svn_revnum_t copy_src_revnum;
- svn_opt_revision_t copy_src_peg_revision;
svn_boolean_t timestamp_sleep;
- svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
- void *old_notify_baton2 = ctx->notify_baton2;
- struct notification_adjust_baton nb;
local_abspath = svn_client_conflict_get_local_abspath(conflict);
@@ -7805,46 +8025,6 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
if (corrected_url)
url = corrected_url;
-
- /* Find a temporary location in which to check out the copy source. */
- SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, local_abspath,
- scratch_pool, scratch_pool));
-
- SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
- svn_io_file_del_on_close,
- scratch_pool, scratch_pool));
-
- /* Make a new checkout of the requested source. While doing so,
- * resolve copy_src_revnum to an actual revision number in case it
- * was until now 'invalid' meaning 'head'. Ask this function not to
- * sleep for timestamps, by passing a sleep_needed output param.
- * Send notifications for all nodes except the root node, and adjust
- * them to refer to the destination rather than this temporary path. */
-
- nb.inner_func = ctx->notify_func2;
- nb.inner_baton = ctx->notify_baton2;
- nb.checkout_abspath = tmp_abspath;
- nb.final_abspath = local_abspath;
- ctx->notify_func2 = notification_adjust_func;
- ctx->notify_baton2 = &nb;
-
- copy_src_peg_revision.kind = svn_opt_revision_number;
- copy_src_peg_revision.value.number = incoming_new_pegrev;
-
- err = svn_client__checkout_internal(&copy_src_revnum, &timestamp_sleep,
- url, tmp_abspath,
- &copy_src_peg_revision,
- &copy_src_peg_revision,
- svn_depth_infinity,
- TRUE, /* we want to ignore externals */
- FALSE, /* we don't allow obstructions */
- ra_session, ctx, scratch_pool);
-
- ctx->notify_func2 = old_notify_func2;
- ctx->notify_baton2 = old_notify_baton2;
-
- SVN_ERR(err);
-
/* ### The following WC modifications should be atomic. */
SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx,
@@ -7861,31 +8041,11 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option,
if (err)
goto unlock_wc;
- /* Schedule dst_path for addition in parent, with copy history.
- Don't send any notification here.
- Then remove the temporary checkout's .svn dir in preparation for
- moving the rest of it into the final destination. */
- err = svn_wc_copy3(ctx->wc_ctx, tmp_abspath, local_abspath,
- TRUE /* metadata_only */,
- NULL, NULL, /* don't allow user to cancel here */
- NULL, NULL, scratch_pool);
- if (err)
- goto unlock_wc;
-
- err = svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
- FALSE, scratch_pool, scratch_pool);
- if (err)
- goto unlock_wc;
- err = svn_wc_remove_from_revision_control2(ctx->wc_ctx,
- tmp_abspath,
- FALSE, FALSE,
- NULL, NULL, /* don't cancel */
- scratch_pool);
- if (err)
- goto unlock_wc;
-
- /* Move the temporary disk tree into place. */
- err = svn_io_file_rename2(tmp_abspath, local_abspath, FALSE, scratch_pool);
+ err = svn_client__repos_to_wc_copy_by_editor(&timestamp_sleep,
+ svn_node_dir,
+ url, incoming_new_pegrev,
+ local_abspath,
+ ra_session, ctx, scratch_pool);
if (err)
goto unlock_wc;
@@ -8012,6 +8172,112 @@ resolve_merge_incoming_added_dir_replace_and_merge(
scratch_pool));
}
+/* Ensure the conflict victim is a copy of itself from before it was deleted.
+ * Update and switch are supposed to set this up when flagging the conflict. */
+static svn_error_t *
+ensure_local_edit_vs_incoming_deletion_copied_state(
+ struct conflict_tree_incoming_delete_details *details,
+ svn_wc_operation_t operation,
+ const char *wcroot_abspath,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+
+ svn_boolean_t is_copy;
+ svn_revnum_t copyfrom_rev;
+ const char *copyfrom_repos_relpath;
+
+ SVN_ERR_ASSERT(operation == svn_wc_operation_update ||
+ operation == svn_wc_operation_switch);
+
+ SVN_ERR(svn_wc__node_get_origin(&is_copy, &copyfrom_rev,
+ &copyfrom_repos_relpath,
+ NULL, NULL, NULL, NULL,
+ ctx->wc_ctx, conflict->local_abspath,
+ FALSE, scratch_pool, scratch_pool));
+ if (!is_copy)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' "
+ "(expected a copied item, but the item "
+ "is not a copy)"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath,
+ conflict->local_abspath),
+ scratch_pool));
+ else if (details->deleted_rev != SVN_INVALID_REVNUM &&
+ copyfrom_rev >= details->deleted_rev)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' "
+ "(expected an item copied from a revision "
+ "smaller than r%ld, but the item was "
+ "copied from r%ld)"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath, conflict->local_abspath),
+ scratch_pool),
+ details->deleted_rev, copyfrom_rev);
+ else if (details->added_rev != SVN_INVALID_REVNUM &&
+ copyfrom_rev < details->added_rev)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' "
+ "(expected an item copied from a revision "
+ "larger than r%ld, but the item was "
+ "copied from r%ld)"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath, conflict->local_abspath),
+ scratch_pool),
+ details->added_rev, copyfrom_rev);
+ else if (operation == svn_wc_operation_update)
+ {
+ const char *old_repos_relpath;
+
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &old_repos_relpath, NULL, NULL, conflict,
+ scratch_pool, scratch_pool));
+ if (strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0 &&
+ strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' "
+ "(expected an item copied from '^/%s' "
+ "or from '^/%s' but the item was "
+ "copied from '^/%s@%ld')"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath, conflict->local_abspath),
+ scratch_pool),
+ details->repos_relpath,
+ old_repos_relpath,
+ copyfrom_repos_relpath, copyfrom_rev);
+ }
+ else if (operation == svn_wc_operation_switch)
+ {
+ const char *old_repos_relpath;
+
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &old_repos_relpath, NULL, NULL, conflict,
+ scratch_pool, scratch_pool));
+
+ if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Cannot resolve tree conflict on '%s' "
+ "(expected an item copied from '^/%s', "
+ "but the item was copied from "
+ "'^/%s@%ld')"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath,
+ conflict->local_abspath),
+ scratch_pool),
+ old_repos_relpath,
+ copyfrom_repos_relpath, copyfrom_rev);
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* Verify the local working copy state matches what we expect when an
* incoming deletion tree conflict exists.
* We assume update/merge/switch operations leave the working copy in a
@@ -8022,26 +8288,25 @@ resolve_merge_incoming_added_dir_replace_and_merge(
static svn_error_t *
verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict,
svn_client_conflict_option_t *option,
- svn_client_ctx_t *ctx,
+ svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
const char *local_abspath;
const char *wcroot_abspath;
svn_wc_operation_t operation;
+ svn_wc_conflict_reason_t local_change;
local_abspath = svn_client_conflict_get_local_abspath(conflict);
SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
local_abspath, scratch_pool,
scratch_pool));
operation = svn_client_conflict_get_operation(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
if (operation == svn_wc_operation_update ||
operation == svn_wc_operation_switch)
{
struct conflict_tree_incoming_delete_details *details;
- svn_boolean_t is_copy;
- svn_revnum_t copyfrom_rev;
- const char *copyfrom_repos_relpath;
details = conflict->tree_conflict_incoming_details;
if (details == NULL)
@@ -8053,26 +8318,8 @@ verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict,
svn_dirent_local_style(local_abspath,
scratch_pool));
- /* Ensure that the item is a copy of itself from before it was deleted.
- * Update and switch are supposed to set this up when flagging the
- * conflict. */
- SVN_ERR(svn_wc__node_get_origin(&is_copy, &copyfrom_rev,
- &copyfrom_repos_relpath,
- NULL, NULL, NULL, NULL,
- ctx->wc_ctx, local_abspath, FALSE,
- scratch_pool, scratch_pool));
- if (!is_copy)
- return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
- _("Cannot resolve tree conflict on '%s' "
- "(expected a copied item, but the item "
- "is not a copy)"),
- svn_dirent_local_style(
- svn_dirent_skip_ancestor(
- wcroot_abspath,
- conflict->local_abspath),
- scratch_pool));
- else if (details->deleted_rev == SVN_INVALID_REVNUM &&
- details->added_rev == SVN_INVALID_REVNUM)
+ if (details->deleted_rev == SVN_INVALID_REVNUM &&
+ details->added_rev == SVN_INVALID_REVNUM)
return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
_("Could not find the revision in which '%s' "
"was deleted from the repository"),
@@ -8081,75 +8328,11 @@ verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict,
wcroot_abspath,
conflict->local_abspath),
scratch_pool));
- else if (details->deleted_rev != SVN_INVALID_REVNUM &&
- copyfrom_rev >= details->deleted_rev)
- return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
- _("Cannot resolve tree conflict on '%s' "
- "(expected an item copied from a revision "
- "smaller than r%ld, but the item was "
- "copied from r%ld)"),
- svn_dirent_local_style(
- svn_dirent_skip_ancestor(
- wcroot_abspath, conflict->local_abspath),
- scratch_pool),
- details->deleted_rev, copyfrom_rev);
- else if (details->added_rev != SVN_INVALID_REVNUM &&
- copyfrom_rev < details->added_rev)
- return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
- _("Cannot resolve tree conflict on '%s' "
- "(expected an item copied from a revision "
- "larger than r%ld, but the item was "
- "copied from r%ld)"),
- svn_dirent_local_style(
- svn_dirent_skip_ancestor(
- wcroot_abspath, conflict->local_abspath),
- scratch_pool),
- details->added_rev, copyfrom_rev);
- else if (operation == svn_wc_operation_update)
- {
- const char *old_repos_relpath;
-
- SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
- &old_repos_relpath, NULL, NULL, conflict,
- scratch_pool, scratch_pool));
- if (strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0 &&
- strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
- return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
- _("Cannot resolve tree conflict on '%s' "
- "(expected an item copied from '^/%s' "
- "or from '^/%s' but the item was "
- "copied from '^/%s@%ld')"),
- svn_dirent_local_style(
- svn_dirent_skip_ancestor(
- wcroot_abspath, conflict->local_abspath),
- scratch_pool),
- details->repos_relpath,
- old_repos_relpath,
- copyfrom_repos_relpath, copyfrom_rev);
- }
- else if (operation == svn_wc_operation_switch)
- {
- const char *old_repos_relpath;
-
- SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
- &old_repos_relpath, NULL, NULL, conflict,
- scratch_pool, scratch_pool));
-
- if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0)
- return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
- _("Cannot resolve tree conflict on '%s' "
- "(expected an item copied from '^/%s', "
- "but the item was copied from "
- "'^/%s@%ld')"),
- svn_dirent_local_style(
- svn_dirent_skip_ancestor(
- wcroot_abspath,
- conflict->local_abspath),
- scratch_pool),
- old_repos_relpath,
- copyfrom_repos_relpath, copyfrom_rev);
- }
+ if (local_change == svn_wc_conflict_reason_edited)
+ SVN_ERR(ensure_local_edit_vs_incoming_deletion_copied_state(
+ details, operation, wcroot_abspath, conflict, ctx,
+ scratch_pool));
}
else if (operation == svn_wc_operation_merge)
{
@@ -8299,7 +8482,9 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
apr_pool_t *scratch_pool)
{
svn_client_conflict_option_id_t option_id;
- const char *local_abspath;
+ const char *victim_abspath;
+ const char *merge_source_abspath;
+ svn_wc_conflict_reason_t local_change;
svn_wc_operation_t operation;
const char *lock_abspath;
svn_error_t *err;
@@ -8325,7 +8510,8 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
const char *moved_to_abspath;
const char *incoming_abspath = NULL;
- local_abspath = svn_client_conflict_get_local_abspath(conflict);
+ victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
operation = svn_client_conflict_get_operation(conflict);
details = conflict->tree_conflict_incoming_details;
if (details == NULL || details->moves == NULL)
@@ -8333,21 +8519,21 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
_("The specified conflict resolution option "
"requires details for tree conflict at '%s' "
"to be fetched from the repository first."),
- svn_dirent_local_style(local_abspath,
+ svn_dirent_local_style(victim_abspath,
scratch_pool));
if (operation == svn_wc_operation_none)
return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
_("Invalid operation code '%d' recorded for "
"conflict at '%s'"), operation,
- svn_dirent_local_style(local_abspath,
+ svn_dirent_local_style(victim_abspath,
scratch_pool));
option_id = svn_client_conflict_option_get_id(option);
SVN_ERR_ASSERT(option_id ==
svn_client_conflict_option_incoming_move_file_text_merge ||
option_id ==
- svn_client_conflict_option_incoming_move_dir_merge);
-
+ svn_client_conflict_option_both_moved_file_move_merge);
+
SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
conflict, scratch_pool,
scratch_pool));
@@ -8361,7 +8547,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
scratch_pool));
/* Set up temporary storage for the common ancestor version of the file. */
- SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath,
+ SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
scratch_pool, scratch_pool));
SVN_ERR(svn_stream_open_unique(&ancestor_stream,
&ancestor_abspath, wc_tmpdir,
@@ -8391,21 +8577,41 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
details->wc_move_target_idx,
const char *);
+ if (local_change == svn_wc_conflict_reason_missing)
+ {
+ /* This is an incoming move vs local move conflict.
+ * Merge from the local move's target location to the
+ * incoming move's target location. */
+ struct conflict_tree_local_missing_details *local_details;
+ apr_array_header_t *moves;
+
+ local_details = conflict->tree_conflict_local_details;
+ moves = svn_hash_gets(local_details->wc_move_targets,
+ local_details->move_target_repos_relpath);
+ merge_source_abspath =
+ APR_ARRAY_IDX(moves, local_details->wc_move_target_idx, const char *);
+ }
+ else
+ merge_source_abspath = victim_abspath;
+
/* ### The following WC modifications should be atomic. */
SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
&lock_abspath, ctx->wc_ctx,
- svn_dirent_get_longest_ancestor(local_abspath,
+ svn_dirent_get_longest_ancestor(victim_abspath,
moved_to_abspath,
scratch_pool),
scratch_pool, scratch_pool));
- err = verify_local_state_for_incoming_delete(conflict, option, ctx,
- scratch_pool);
- if (err)
- goto unlock_wc;
+ if (local_change != svn_wc_conflict_reason_missing)
+ {
+ err = verify_local_state_for_incoming_delete(conflict, option, ctx,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+ }
/* Get a copy of the conflict victim's properties. */
- err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, local_abspath,
+ err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, merge_source_abspath,
scratch_pool, scratch_pool);
if (err)
goto unlock_wc;
@@ -8426,10 +8632,10 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
if (operation == svn_wc_operation_update ||
operation == svn_wc_operation_switch)
{
- svn_stream_t *working_stream;
+ svn_stream_t *moved_to_stream;
svn_stream_t *incoming_stream;
- /* Create a temporary copy of the working file in repository-normal form.
+ /* Create a temporary copy of the moved file in repository-normal form.
* Set up this temporary file to be automatically removed. */
err = svn_stream_open_unique(&incoming_stream,
&incoming_abspath, wc_tmpdir,
@@ -8438,18 +8644,31 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
if (err)
goto unlock_wc;
- err = svn_wc__translated_stream(&working_stream, ctx->wc_ctx,
- local_abspath, local_abspath,
+ err = svn_wc__translated_stream(&moved_to_stream, ctx->wc_ctx,
+ moved_to_abspath,
+ moved_to_abspath,
SVN_WC_TRANSLATE_TO_NF,
scratch_pool, scratch_pool);
if (err)
goto unlock_wc;
- err = svn_stream_copy3(working_stream, incoming_stream,
+ err = svn_stream_copy3(moved_to_stream, incoming_stream,
NULL, NULL, /* no cancellation */
scratch_pool);
if (err)
goto unlock_wc;
+
+ /* Overwrite the moved file with the conflict victim's content.
+ * Incoming changes will be merged in from the temporary file created
+ * above. This is required to correctly make local changes show up as
+ * 'mine' during the three-way text merge between the ancestor file,
+ * the conflict victim ('mine'), and the moved file ('theirs') which
+ * was brought in by the update/switch operation and occupies the path
+ * of the merge target. */
+ err = svn_io_copy_file(merge_source_abspath, moved_to_abspath, FALSE,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
}
else if (operation == svn_wc_operation_merge)
{
@@ -8486,7 +8705,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool);
if (err)
goto unlock_wc;
- err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath,
+ err = svn_wc__move2(ctx->wc_ctx, merge_source_abspath, moved_to_abspath,
FALSE, /* ordinary (not meta-data only) move */
FALSE, /* mixed-revisions don't apply to files */
NULL, NULL, /* don't allow user to cancel here */
@@ -8522,7 +8741,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
goto unlock_wc;
incoming_abspath = NULL;
}
-
+
if (ctx->notify_func2)
{
svn_wc_notify_t *notify;
@@ -8544,19 +8763,27 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option,
operation == svn_wc_operation_switch)
{
/* Delete the tree conflict victim (clears the tree conflict marker). */
- err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE,
+ err = svn_wc_delete4(ctx->wc_ctx, victim_abspath, FALSE, FALSE,
NULL, NULL, /* don't allow user to cancel here */
NULL, NULL, /* no extra notification */
scratch_pool);
if (err)
goto unlock_wc;
}
+ else if (local_change == svn_wc_conflict_reason_missing)
+ {
+ /* Clear tree conflict marker. */
+ err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+ }
if (ctx->notify_func2)
{
svn_wc_notify_t *notify;
- notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree,
+ notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
scratch_pool);
ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
}
@@ -8577,6 +8804,521 @@ unlock_wc:
return SVN_NO_ERROR;
}
+/* Implements conflict_option_resolve_func_t.
+ * Resolve an incoming move vs local move conflict by merging from the
+ * incoming move's target location to the local move's target location,
+ * overriding the incoming move. */
+static svn_error_t *
+resolve_both_moved_file_text_merge(svn_client_conflict_option_t *option,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_conflict_option_id_t option_id;
+ const char *victim_abspath;
+ const char *local_moved_to_abspath;
+ svn_wc_operation_t operation;
+ const char *lock_abspath;
+ svn_error_t *err;
+ const char *repos_root_url;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
+ const char *incoming_new_repos_relpath;
+ svn_revnum_t incoming_new_pegrev;
+ const char *wc_tmpdir;
+ const char *ancestor_abspath;
+ svn_stream_t *ancestor_stream;
+ apr_hash_t *ancestor_props;
+ apr_hash_t *incoming_props;
+ apr_hash_t *local_props;
+ const char *ancestor_url;
+ const char *corrected_url;
+ svn_ra_session_t *ra_session;
+ svn_wc_merge_outcome_t merge_content_outcome;
+ svn_wc_notify_state_t merge_props_outcome;
+ apr_array_header_t *propdiffs;
+ struct conflict_tree_incoming_delete_details *incoming_details;
+ apr_array_header_t *possible_moved_to_abspaths;
+ const char *incoming_moved_to_abspath;
+ struct conflict_tree_local_missing_details *local_details;
+ apr_array_header_t *local_moves;
+
+ victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+ operation = svn_client_conflict_get_operation(conflict);
+ incoming_details = conflict->tree_conflict_incoming_details;
+ if (incoming_details == NULL || incoming_details->moves == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("The specified conflict resolution option "
+ "requires details for tree conflict at '%s' "
+ "to be fetched from the repository first."),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+ if (operation == svn_wc_operation_none)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid operation code '%d' recorded for "
+ "conflict at '%s'"), operation,
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ option_id = svn_client_conflict_option_get_id(option);
+ SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge);
+
+ SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
+ conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, &incoming_old_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+ &incoming_new_repos_relpath, &incoming_new_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+
+ /* Set up temporary storage for the common ancestor version of the file. */
+ SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_unique(&ancestor_stream,
+ &ancestor_abspath, wc_tmpdir,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+
+ /* Fetch the ancestor file's content. */
+ ancestor_url = svn_path_url_add_component2(repos_root_url,
+ incoming_old_repos_relpath,
+ scratch_pool);
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+ ancestor_url, NULL, NULL,
+ FALSE, FALSE, ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
+ ancestor_stream, NULL, /* fetched_rev */
+ &ancestor_props, scratch_pool));
+ filter_props(ancestor_props, scratch_pool);
+
+ /* Close stream to flush ancestor file to disk. */
+ SVN_ERR(svn_stream_close(ancestor_stream));
+
+ possible_moved_to_abspaths =
+ svn_hash_gets(incoming_details->wc_move_targets,
+ get_moved_to_repos_relpath(incoming_details, scratch_pool));
+ incoming_moved_to_abspath =
+ APR_ARRAY_IDX(possible_moved_to_abspaths,
+ incoming_details->wc_move_target_idx, const char *);
+
+ local_details = conflict->tree_conflict_local_details;
+ local_moves = svn_hash_gets(local_details->wc_move_targets,
+ local_details->move_target_repos_relpath);
+ local_moved_to_abspath =
+ APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *);
+
+ /* ### The following WC modifications should be atomic. */
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+ &lock_abspath, ctx->wc_ctx,
+ svn_dirent_get_longest_ancestor(victim_abspath,
+ local_moved_to_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ /* Get a copy of the incoming moved item's properties. */
+ err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx,
+ incoming_moved_to_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Get a copy of the local move target's properties. */
+ err = svn_wc_prop_list2(&local_props, ctx->wc_ctx,
+ local_moved_to_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Create a property diff for the files. */
+ err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Perform the file merge. */
+ err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
+ ctx->wc_ctx, ancestor_abspath,
+ incoming_moved_to_abspath, local_moved_to_abspath,
+ NULL, NULL, NULL, /* labels */
+ NULL, NULL, /* conflict versions */
+ FALSE, /* dry run */
+ NULL, NULL, /* diff3_cmd, merge_options */
+ apr_hash_count(ancestor_props) ? ancestor_props : NULL,
+ propdiffs,
+ NULL, NULL, /* conflict func/baton */
+ NULL, NULL, /* don't allow user to cancel here */
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ /* Tell the world about the file merge that just happened. */
+ notify = svn_wc_create_notify(local_moved_to_abspath,
+ svn_wc_notify_update_update,
+ scratch_pool);
+ if (merge_content_outcome == svn_wc_merge_conflict)
+ notify->content_state = svn_wc_notify_state_conflicted;
+ else
+ notify->content_state = svn_wc_notify_state_merged;
+ notify->prop_state = merge_props_outcome;
+ notify->kind = svn_node_file;
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ /* Revert local addition of the incoming move's target. */
+ err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath,
+ svn_depth_infinity, FALSE, NULL, TRUE, FALSE,
+ FALSE /*added_keep_local*/,
+ NULL, NULL, /* no cancellation */
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
+ scratch_pool);
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
+
+ conflict->resolution_tree = option_id;
+
+unlock_wc:
+ err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+ lock_abspath,
+ scratch_pool));
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements conflict_option_resolve_func_t.
+ * Resolve an incoming move vs local move conflict by moving the locally moved
+ * directory to the incoming move target location, and then merging changes. */
+static svn_error_t *
+resolve_both_moved_dir_merge(svn_client_conflict_option_t *option,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_conflict_option_id_t option_id;
+ const char *victim_abspath;
+ const char *local_moved_to_abspath;
+ svn_wc_operation_t operation;
+ const char *lock_abspath;
+ svn_error_t *err;
+ const char *repos_root_url;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
+ const char *incoming_new_repos_relpath;
+ svn_revnum_t incoming_new_pegrev;
+ const char *incoming_moved_repos_relpath;
+ struct conflict_tree_incoming_delete_details *incoming_details;
+ apr_array_header_t *possible_moved_to_abspaths;
+ const char *incoming_moved_to_abspath;
+ struct conflict_tree_local_missing_details *local_details;
+ apr_array_header_t *local_moves;
+ svn_client__conflict_report_t *conflict_report;
+ const char *incoming_old_url;
+ const char *incoming_moved_url;
+ svn_opt_revision_t incoming_old_opt_rev;
+ svn_opt_revision_t incoming_moved_opt_rev;
+
+ victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+ operation = svn_client_conflict_get_operation(conflict);
+ incoming_details = conflict->tree_conflict_incoming_details;
+ if (incoming_details == NULL || incoming_details->moves == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("The specified conflict resolution option "
+ "requires details for tree conflict at '%s' "
+
+ "to be fetched from the repository first."),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+ if (operation == svn_wc_operation_none)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid operation code '%d' recorded for "
+ "conflict at '%s'"), operation,
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ option_id = svn_client_conflict_option_get_id(option);
+ SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_dir_merge);
+
+ SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
+ conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, &incoming_old_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+ &incoming_new_repos_relpath, &incoming_new_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+
+ possible_moved_to_abspaths =
+ svn_hash_gets(incoming_details->wc_move_targets,
+ get_moved_to_repos_relpath(incoming_details, scratch_pool));
+ incoming_moved_to_abspath =
+ APR_ARRAY_IDX(possible_moved_to_abspaths,
+ incoming_details->wc_move_target_idx, const char *);
+
+ local_details = conflict->tree_conflict_local_details;
+ local_moves = svn_hash_gets(local_details->wc_move_targets,
+ local_details->move_target_repos_relpath);
+ local_moved_to_abspath =
+ APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *);
+
+ /* ### The following WC modifications should be atomic. */
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+ &lock_abspath, ctx->wc_ctx,
+ svn_dirent_get_longest_ancestor(victim_abspath,
+ local_moved_to_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ /* Perform the merge. */
+ incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+ incoming_old_repos_relpath, SVN_VA_NULL);
+ incoming_old_opt_rev.kind = svn_opt_revision_number;
+ incoming_old_opt_rev.value.number = incoming_old_pegrev;
+
+ incoming_moved_repos_relpath =
+ get_moved_to_repos_relpath(incoming_details, scratch_pool);
+ incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+ incoming_moved_repos_relpath, SVN_VA_NULL);
+ incoming_moved_opt_rev.kind = svn_opt_revision_number;
+ incoming_moved_opt_rev.value.number = incoming_new_pegrev;
+ err = svn_client__merge_locked(&conflict_report,
+ incoming_old_url, &incoming_old_opt_rev,
+ incoming_moved_url, &incoming_moved_opt_rev,
+ local_moved_to_abspath, svn_depth_infinity,
+ TRUE, TRUE, /* do a no-ancestry merge */
+ FALSE, FALSE, FALSE,
+ TRUE, /* Allow mixed-rev just in case,
+ * since conflict victims can't be
+ * updated to straighten out
+ * mixed-rev trees. */
+ NULL, ctx, scratch_pool, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Revert local addition of the incoming move's target. */
+ err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath,
+ svn_depth_infinity, FALSE, NULL, TRUE, FALSE,
+ FALSE /*added_keep_local*/,
+ NULL, NULL, /* no cancellation */
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
+ scratch_pool);
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
+
+ conflict->resolution_tree = option_id;
+
+unlock_wc:
+ err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+ lock_abspath,
+ scratch_pool));
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements conflict_option_resolve_func_t.
+ * Resolve an incoming move vs local move conflict by merging from the
+ * incoming move's target location to the local move's target location,
+ * overriding the incoming move. */
+static svn_error_t *
+resolve_both_moved_dir_move_merge(svn_client_conflict_option_t *option,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_conflict_option_id_t option_id;
+ const char *victim_abspath;
+ const char *local_moved_to_abspath;
+ svn_wc_operation_t operation;
+ const char *lock_abspath;
+ svn_error_t *err;
+ const char *repos_root_url;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
+ const char *incoming_new_repos_relpath;
+ svn_revnum_t incoming_new_pegrev;
+ struct conflict_tree_incoming_delete_details *incoming_details;
+ apr_array_header_t *possible_moved_to_abspaths;
+ const char *incoming_moved_to_abspath;
+ struct conflict_tree_local_missing_details *local_details;
+ apr_array_header_t *local_moves;
+ svn_client__conflict_report_t *conflict_report;
+ const char *incoming_old_url;
+ const char *incoming_moved_url;
+ svn_opt_revision_t incoming_old_opt_rev;
+ svn_opt_revision_t incoming_moved_opt_rev;
+
+ victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+ operation = svn_client_conflict_get_operation(conflict);
+ incoming_details = conflict->tree_conflict_incoming_details;
+ if (incoming_details == NULL || incoming_details->moves == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("The specified conflict resolution option "
+ "requires details for tree conflict at '%s' "
+
+ "to be fetched from the repository first."),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+ if (operation == svn_wc_operation_none)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid operation code '%d' recorded for "
+ "conflict at '%s'"), operation,
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ option_id = svn_client_conflict_option_get_id(option);
+ SVN_ERR_ASSERT(option_id ==
+ svn_client_conflict_option_both_moved_dir_move_merge);
+
+ SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
+ conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, &incoming_old_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+ &incoming_new_repos_relpath, &incoming_new_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+
+ possible_moved_to_abspaths =
+ svn_hash_gets(incoming_details->wc_move_targets,
+ get_moved_to_repos_relpath(incoming_details, scratch_pool));
+ incoming_moved_to_abspath =
+ APR_ARRAY_IDX(possible_moved_to_abspaths,
+ incoming_details->wc_move_target_idx, const char *);
+
+ local_details = conflict->tree_conflict_local_details;
+ local_moves = svn_hash_gets(local_details->wc_move_targets,
+ local_details->move_target_repos_relpath);
+ local_moved_to_abspath =
+ APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *);
+
+ /* ### The following WC modifications should be atomic. */
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+ &lock_abspath, ctx->wc_ctx,
+ svn_dirent_get_longest_ancestor(victim_abspath,
+ local_moved_to_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ /* Revert the incoming move target directory. */
+ err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath,
+ svn_depth_infinity,
+ FALSE, NULL, TRUE, FALSE,
+ TRUE /*added_keep_local*/,
+ NULL, NULL, /* no cancellation */
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* The move operation is not part of natural history. We must replicate
+ * this move in our history. Record a move in the working copy. */
+ err = svn_wc__move2(ctx->wc_ctx, local_moved_to_abspath,
+ incoming_moved_to_abspath,
+ FALSE, /* this is not a meta-data only move */
+ TRUE, /* allow mixed-revisions just in case */
+ NULL, NULL, /* don't allow user to cancel here */
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Merge INCOMING_OLD_URL@MERGE_LEFT->INCOMING_MOVED_URL@MERGE_RIGHT
+ * into the locally moved merge target. */
+ incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+ incoming_old_repos_relpath, SVN_VA_NULL);
+ incoming_old_opt_rev.kind = svn_opt_revision_number;
+ incoming_old_opt_rev.value.number = incoming_old_pegrev;
+
+ incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+ incoming_details->move_target_repos_relpath,
+ SVN_VA_NULL);
+ incoming_moved_opt_rev.kind = svn_opt_revision_number;
+ incoming_moved_opt_rev.value.number = incoming_new_pegrev;
+ err = svn_client__merge_locked(&conflict_report,
+ incoming_old_url, &incoming_old_opt_rev,
+ incoming_moved_url, &incoming_moved_opt_rev,
+ incoming_moved_to_abspath, svn_depth_infinity,
+ TRUE, TRUE, /* do a no-ancestry merge */
+ FALSE, FALSE, FALSE,
+ TRUE, /* Allow mixed-rev just in case,
+ * since conflict victims can't be
+ * updated to straighten out
+ * mixed-rev trees. */
+ NULL, ctx, scratch_pool, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
+ scratch_pool);
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
+
+ conflict->resolution_tree = option_id;
+
+unlock_wc:
+ err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+ lock_abspath,
+ scratch_pool));
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
/* Implements conflict_option_resolve_func_t. */
static svn_error_t *
resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
@@ -8602,8 +9344,8 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
struct conflict_tree_incoming_delete_details *details;
apr_array_header_t *possible_moved_to_abspaths;
const char *moved_to_abspath;
- svn_client__pathrev_t *yca_loc;
- svn_opt_revision_t yca_opt_rev;
+ const char *incoming_old_url;
+ svn_opt_revision_t incoming_old_opt_rev;
svn_client__conflict_report_t *conflict_report;
svn_boolean_t is_copy;
svn_boolean_t is_modified;
@@ -8622,7 +9364,7 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
option_id = svn_client_conflict_option_get_id(option);
SVN_ERR_ASSERT(option_id ==
svn_client_conflict_option_incoming_move_dir_merge);
-
+
SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid,
conflict, scratch_pool,
scratch_pool));
@@ -8696,30 +9438,6 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
goto unlock_wc;
}
- /* Now find the youngest common ancestor of these nodes. */
- err = find_yca(&yca_loc, victim_repos_relpath, victim_peg_rev,
- moved_to_repos_relpath, moved_to_peg_rev,
- repos_root_url, repos_uuid,
- NULL, ctx, scratch_pool, scratch_pool);
- if (err)
- goto unlock_wc;
-
- if (yca_loc == NULL)
- {
- err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
- _("Cannot resolve tree conflict on '%s' "
- "(could not find common ancestor of '^/%s@%ld' "
- " and '^/%s@%ld')"),
- svn_dirent_local_style(local_abspath,
- scratch_pool),
- victim_repos_relpath, victim_peg_rev,
- moved_to_repos_relpath, moved_to_peg_rev);
- goto unlock_wc;
- }
-
- yca_opt_rev.kind = svn_opt_revision_number;
- yca_opt_rev.value.number = yca_loc->rev;
-
err = verify_local_state_for_incoming_delete(conflict, option, ctx,
scratch_pool);
if (err)
@@ -8731,11 +9449,14 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
svn_opt_revision_t incoming_new_opt_rev;
/* Revert the incoming move target directory. */
- SVN_ERR(svn_wc_revert5(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity,
- FALSE, NULL, TRUE, FALSE,
- NULL, NULL, /* no cancellation */
- ctx->notify_func2, ctx->notify_baton2,
- scratch_pool));
+ err = svn_wc_revert6(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity,
+ FALSE, NULL, TRUE, FALSE,
+ TRUE /*added_keep_local*/,
+ NULL, NULL, /* no cancellation */
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
/* The move operation is not part of natural history. We must replicate
* this move in our history. Record a move in the working copy. */
@@ -8748,7 +9469,12 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
if (err)
goto unlock_wc;
- /* Merge YCA_URL@YCA_REV->MOVE_TARGET_URL@MERGE_RIGHT into move target. */
+ /* Merge INCOMING_OLD_URL@MERGE_LEFT->MOVE_TARGET_URL@MERGE_RIGHT
+ * into move target. */
+ incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+ incoming_old_repos_relpath, SVN_VA_NULL);
+ incoming_old_opt_rev.kind = svn_opt_revision_number;
+ incoming_old_opt_rev.value.number = incoming_old_pegrev;
move_target_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
get_moved_to_repos_relpath(details,
scratch_pool),
@@ -8756,7 +9482,7 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option,
incoming_new_opt_rev.kind = svn_opt_revision_number;
incoming_new_opt_rev.value.number = incoming_new_pegrev;
err = svn_client__merge_locked(&conflict_report,
- yca_loc->url, &yca_opt_rev,
+ incoming_old_url, &incoming_old_opt_rev,
move_target_url, &incoming_new_opt_rev,
moved_to_abspath, svn_depth_infinity,
TRUE, TRUE, /* do a no-ancestry merge */
@@ -8825,7 +9551,9 @@ unlock_wc:
return SVN_NO_ERROR;
}
-/* Implements conflict_option_resolve_func_t. */
+/* Implements conflict_option_resolve_func_t.
+ * Handles svn_client_conflict_option_local_move_file_text_merge
+ * and svn_client_conflict_option_sibling_move_file_text_merge. */
static svn_error_t *
resolve_local_move_file_merge(svn_client_conflict_option_t *option,
svn_client_conflict_t *conflict,
@@ -8853,6 +9581,12 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
svn_wc_notify_state_t merge_props_outcome;
apr_array_header_t *propdiffs;
struct conflict_tree_local_missing_details *details;
+ const char *merge_target_abspath;
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath, scratch_pool,
+ scratch_pool));
details = conflict->tree_conflict_local_details;
@@ -8868,8 +9602,31 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
NULL, conflict, scratch_pool,
scratch_pool));
+ if (details->wc_siblings)
+ {
+ merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
+ details->preferred_sibling_idx,
+ const char *);
+ }
+ else if (details->wc_move_targets && details->move_target_repos_relpath)
+ {
+ apr_array_header_t *moves;
+ moves = svn_hash_gets(details->wc_move_targets,
+ details->move_target_repos_relpath);
+ merge_target_abspath = APR_ARRAY_IDX(moves, details->wc_move_target_idx,
+ const char *);
+ }
+ else
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Corresponding working copy node not found "
+ "for '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(
+ wcroot_abspath, conflict->local_abspath),
+ scratch_pool));
+
SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx,
- details->moved_to_abspath,
+ merge_target_abspath,
scratch_pool, scratch_pool));
/* Fetch the common ancestor file's content. */
@@ -8914,7 +9671,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
&lock_abspath, ctx->wc_ctx,
svn_dirent_get_longest_ancestor(conflict->local_abspath,
- details->moved_to_abspath,
+ merge_target_abspath,
scratch_pool),
scratch_pool, scratch_pool));
@@ -8922,7 +9679,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
ctx->wc_ctx,
ancestor_tmp_abspath, incoming_tmp_abspath,
- details->moved_to_abspath,
+ merge_target_abspath,
NULL, NULL, NULL, /* labels */
NULL, NULL, /* conflict versions */
FALSE, /* dry run */
@@ -8932,7 +9689,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
NULL, NULL, /* conflict func/baton */
NULL, NULL, /* don't allow user to cancel here */
scratch_pool);
- svn_io_sleep_for_timestamps(details->moved_to_abspath, scratch_pool);
+ svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool);
if (err)
return svn_error_compose_create(err,
svn_wc__release_write_lock(ctx->wc_ctx,
@@ -8953,7 +9710,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
svn_wc_notify_t *notify;
/* Tell the world about the file merge that just happened. */
- notify = svn_wc_create_notify(details->moved_to_abspath,
+ notify = svn_wc_create_notify(merge_target_abspath,
svn_wc_notify_update_update,
scratch_pool);
if (merge_content_outcome == svn_wc_merge_conflict)
@@ -8976,6 +9733,127 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option,
return SVN_NO_ERROR;
}
+/* Implements conflict_option_resolve_func_t. */
+static svn_error_t *
+resolve_local_move_dir_merge(svn_client_conflict_option_t *option,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *lock_abspath;
+ svn_error_t *err;
+ const char *repos_root_url;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
+ const char *incoming_new_repos_relpath;
+ svn_revnum_t incoming_new_pegrev;
+ struct conflict_tree_local_missing_details *details;
+ const char *merge_target_abspath;
+ const char *incoming_old_url;
+ const char *incoming_new_url;
+ svn_opt_revision_t incoming_old_opt_rev;
+ svn_opt_revision_t incoming_new_opt_rev;
+ svn_client__conflict_report_t *conflict_report;
+
+ details = conflict->tree_conflict_local_details;
+
+ SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
+ conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, &incoming_old_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+ &incoming_new_repos_relpath, &incoming_new_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+
+ if (details->wc_move_targets)
+ {
+ apr_array_header_t *moves;
+
+ moves = svn_hash_gets(details->wc_move_targets,
+ details->move_target_repos_relpath);
+ merge_target_abspath =
+ APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *);
+ }
+ else
+ merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings,
+ details->preferred_sibling_idx,
+ const char *);
+
+ /* ### The following WC modifications should be atomic. */
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+ &lock_abspath, ctx->wc_ctx,
+ svn_dirent_get_longest_ancestor(conflict->local_abspath,
+ merge_target_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ /* Resolve to current working copy state.
+ * svn_client__merge_locked() requires this. */
+ err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Merge outstanding changes to the merge target. */
+ incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+ incoming_old_repos_relpath, SVN_VA_NULL);
+ incoming_old_opt_rev.kind = svn_opt_revision_number;
+ incoming_old_opt_rev.value.number = incoming_old_pegrev;
+ incoming_new_url = apr_pstrcat(scratch_pool, repos_root_url, "/",
+ incoming_new_repos_relpath, SVN_VA_NULL);
+ incoming_new_opt_rev.kind = svn_opt_revision_number;
+ incoming_new_opt_rev.value.number = incoming_new_pegrev;
+ err = svn_client__merge_locked(&conflict_report,
+ incoming_old_url, &incoming_old_opt_rev,
+ incoming_new_url, &incoming_new_opt_rev,
+ merge_target_abspath, svn_depth_infinity,
+ TRUE, TRUE, /* do a no-ancestry merge */
+ FALSE, FALSE, FALSE,
+ TRUE, /* Allow mixed-rev just in case,
+ * since conflict victims can't be
+ * updated to straighten out
+ * mixed-rev trees. */
+ NULL, ctx, scratch_pool, scratch_pool);
+unlock_wc:
+ svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool);
+ err = svn_error_compose_create(err,
+ svn_wc__release_write_lock(ctx->wc_ctx,
+ lock_abspath,
+ scratch_pool));
+ if (err)
+ return svn_error_trace(err);
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ /* Tell the world about the file merge that just happened. */
+ notify = svn_wc_create_notify(merge_target_abspath,
+ svn_wc_notify_update_update,
+ scratch_pool);
+ if (conflict_report)
+ notify->content_state = svn_wc_notify_state_conflicted;
+ else
+ notify->content_state = svn_wc_notify_state_merged;
+ notify->kind = svn_node_dir;
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+
+ /* And also about the successfully resolved tree conflict. */
+ notify = svn_wc_create_notify(conflict->local_abspath,
+ svn_wc_notify_resolved_tree,
+ scratch_pool);
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ conflict->resolution_tree = svn_client_conflict_option_get_id(option);
+
+ return SVN_NO_ERROR;
+}
+
static svn_error_t *
assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool)
{
@@ -9112,13 +9990,13 @@ svn_client_conflict_text_get_resolution_options(apr_array_header_t **options,
add_resolution_option(*options, conflict,
svn_client_conflict_option_incoming_text_where_conflicted,
_("Accept incoming for conflicts"),
- _("accept changes only where they conflict"),
+ _("accept incoming changes only where they conflict"),
resolve_text_conflict);
add_resolution_option(*options, conflict,
svn_client_conflict_option_working_text_where_conflicted,
_("Reject conflicts"),
- _("reject changes which conflict and accept the rest"),
+ _("reject incoming changes which conflict and accept the rest"),
resolve_text_conflict);
add_resolution_option(*options, conflict,
@@ -9392,6 +10270,7 @@ configure_option_incoming_added_file_text_merge(svn_client_conflict_t *conflict,
incoming_new_kind == svn_node_file &&
incoming_change == svn_wc_conflict_action_add &&
(local_change == svn_wc_conflict_reason_obstructed ||
+ local_change == svn_wc_conflict_reason_unversioned ||
local_change == svn_wc_conflict_reason_added))
{
const char *description;
@@ -9517,8 +10396,9 @@ configure_option_incoming_added_dir_merge(svn_client_conflict_t *conflict,
incoming_change == svn_wc_conflict_action_add &&
(local_change == svn_wc_conflict_reason_added ||
(operation == svn_wc_operation_merge &&
- local_change == svn_wc_conflict_reason_obstructed)))
-
+ local_change == svn_wc_conflict_reason_obstructed) ||
+ (operation != svn_wc_operation_merge &&
+ local_change == svn_wc_conflict_reason_unversioned)))
{
const char *description;
const char *wcroot_abspath;
@@ -9527,13 +10407,18 @@ configure_option_incoming_added_dir_merge(svn_client_conflict_t *conflict,
conflict->local_abspath, scratch_pool,
scratch_pool));
if (operation == svn_wc_operation_merge)
- description =
- apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"),
- incoming_new_repos_relpath, incoming_new_pegrev,
- svn_dirent_local_style(
- svn_dirent_skip_ancestor(wcroot_abspath,
- conflict->local_abspath),
- scratch_pool));
+ {
+ if (conflict->tree_conflict_incoming_details == NULL)
+ return SVN_NO_ERROR;
+
+ description =
+ apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"),
+ incoming_new_repos_relpath, incoming_new_pegrev,
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath,
+ conflict->local_abspath),
+ scratch_pool));
+ }
else
description =
apr_psprintf(scratch_pool, _("merge local '%s' and '^/%s@%ld'"),
@@ -9723,7 +10608,7 @@ configure_option_incoming_delete_ignore(svn_client_conflict_t *conflict,
is_local_move = (local_details != NULL &&
local_details->moves != NULL);
- if (!is_incoming_move && !is_local_move)
+ if (is_incoming_move || is_local_move)
return SVN_NO_ERROR;
}
@@ -9769,7 +10654,8 @@ configure_option_incoming_delete_accept(svn_client_conflict_t *conflict,
incoming_details->moves != NULL);
if (is_incoming_move &&
(local_change == svn_wc_conflict_reason_edited ||
- local_change == svn_wc_conflict_reason_moved_away))
+ local_change == svn_wc_conflict_reason_moved_away ||
+ local_change == svn_wc_conflict_reason_missing))
{
/* An option which accepts the incoming deletion makes no sense
* if we know there was a local move and/or an incoming move. */
@@ -9806,39 +10692,75 @@ describe_incoming_move_merge_conflict_option(
const char **description,
svn_client_conflict_t *conflict,
svn_client_ctx_t *ctx,
- struct conflict_tree_incoming_delete_details *details,
+ const char *moved_to_abspath,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- apr_array_header_t *move_target_wc_abspaths;
svn_wc_operation_t operation;
const char *victim_abspath;
- const char *moved_to_abspath;
+ svn_node_kind_t victim_node_kind;
const char *wcroot_abspath;
- move_target_wc_abspaths =
- svn_hash_gets(details->wc_move_targets,
- get_moved_to_repos_relpath(details, scratch_pool));
- moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
- details->wc_move_target_idx,
- const char *);
-
victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+ victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
victim_abspath, scratch_pool,
scratch_pool));
operation = svn_client_conflict_get_operation(conflict);
if (operation == svn_wc_operation_merge)
- *description =
- apr_psprintf(
- result_pool, _("move '%s' to '%s' and merge"),
- svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
- victim_abspath),
- scratch_pool),
- svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
- moved_to_abspath),
- scratch_pool));
+ {
+ const char *incoming_moved_abspath = NULL;
+
+ if (victim_node_kind == svn_node_none)
+ {
+ /* This is an incoming move vs local move conflict. */
+ struct conflict_tree_incoming_delete_details *details;
+
+ details = conflict->tree_conflict_incoming_details;
+ if (details->wc_move_targets)
+ {
+ apr_array_header_t *moves;
+
+ moves = svn_hash_gets(details->wc_move_targets,
+ details->move_target_repos_relpath);
+ incoming_moved_abspath =
+ APR_ARRAY_IDX(moves, details->wc_move_target_idx,
+ const char *);
+ }
+ }
+
+ if (incoming_moved_abspath)
+ {
+ /* The 'move and merge' option follows the incoming move; note that
+ * moved_to_abspath points to the current location of an item which
+ * was moved in the history of our merge target branch. If the user
+ * chooses 'move and merge', that item will be moved again (i.e. it
+ * will be moved to and merged with incoming_moved_abspath's item). */
+ *description =
+ apr_psprintf(
+ result_pool, _("move '%s' to '%s' and merge"),
+ svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
+ moved_to_abspath),
+ scratch_pool),
+ svn_dirent_local_style(svn_dirent_skip_ancestor(
+ wcroot_abspath,
+ incoming_moved_abspath),
+ scratch_pool));
+ }
+ else
+ {
+ *description =
+ apr_psprintf(
+ result_pool, _("move '%s' to '%s' and merge"),
+ svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
+ victim_abspath),
+ scratch_pool),
+ svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath,
+ moved_to_abspath),
+ scratch_pool));
+ }
+ }
else
*description =
apr_psprintf(
@@ -9863,13 +10785,16 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict,
{
svn_node_kind_t victim_node_kind;
svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_reason_t local_change;
const char *incoming_old_repos_relpath;
svn_revnum_t incoming_old_pegrev;
svn_node_kind_t incoming_old_kind;
const char *incoming_new_repos_relpath;
svn_revnum_t incoming_new_pegrev;
svn_node_kind_t incoming_new_kind;
+
incoming_change = svn_client_conflict_get_incoming_change(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
&incoming_old_repos_relpath, &incoming_old_pegrev,
@@ -9883,10 +10808,13 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict,
if (victim_node_kind == svn_node_file &&
incoming_old_kind == svn_node_file &&
incoming_new_kind == svn_node_none &&
- incoming_change == svn_wc_conflict_action_delete)
+ incoming_change == svn_wc_conflict_action_delete &&
+ local_change == svn_wc_conflict_reason_edited)
{
struct conflict_tree_incoming_delete_details *details;
const char *description;
+ apr_array_header_t *move_target_wc_abspaths;
+ const char *moved_to_abspath;
details = conflict->tree_conflict_incoming_details;
if (details == NULL || details->moves == NULL)
@@ -9895,9 +10823,15 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict,
if (apr_hash_count(details->wc_move_targets) == 0)
return SVN_NO_ERROR;
+ move_target_wc_abspaths =
+ svn_hash_gets(details->wc_move_targets,
+ get_moved_to_repos_relpath(details, scratch_pool));
+ moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
+ details->wc_move_target_idx,
+ const char *);
SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
conflict, ctx,
- details,
+ moved_to_abspath,
scratch_pool,
scratch_pool));
add_resolution_option(
@@ -9948,6 +10882,8 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict,
{
struct conflict_tree_incoming_delete_details *details;
const char *description;
+ apr_array_header_t *move_target_wc_abspaths;
+ const char *moved_to_abspath;
details = conflict->tree_conflict_incoming_details;
if (details == NULL || details->moves == NULL)
@@ -9956,9 +10892,15 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict,
if (apr_hash_count(details->wc_move_targets) == 0)
return SVN_NO_ERROR;
+ move_target_wc_abspaths =
+ svn_hash_gets(details->wc_move_targets,
+ get_moved_to_repos_relpath(details, scratch_pool));
+ moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
+ details->wc_move_target_idx,
+ const char *);
SVN_ERR(describe_incoming_move_merge_conflict_option(&description,
conflict, ctx,
- details,
+ moved_to_abspath,
scratch_pool,
scratch_pool));
add_resolution_option(options, conflict,
@@ -9973,23 +10915,32 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict,
/* Configure 'local move file merge' resolution option for
* a tree conflict. */
static svn_error_t *
-configure_option_local_move_file_merge(svn_client_conflict_t *conflict,
- svn_client_ctx_t *ctx,
- apr_array_header_t *options,
- apr_pool_t *scratch_pool)
+configure_option_local_move_file_or_dir_merge(
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_array_header_t *options,
+ apr_pool_t *scratch_pool)
{
svn_wc_operation_t operation;
svn_wc_conflict_action_t incoming_change;
svn_wc_conflict_reason_t local_change;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
+ svn_node_kind_t incoming_old_kind;
const char *incoming_new_repos_relpath;
svn_revnum_t incoming_new_pegrev;
+ svn_node_kind_t incoming_new_kind;
operation = svn_client_conflict_get_operation(conflict);
incoming_change = svn_client_conflict_get_incoming_change(conflict);
local_change = svn_client_conflict_get_local_change(conflict);
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, &incoming_old_pegrev,
+ &incoming_old_kind, conflict, scratch_pool,
+ scratch_pool));
SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
&incoming_new_repos_relpath, &incoming_new_pegrev,
- NULL, conflict, scratch_pool,
+ &incoming_new_kind, conflict, scratch_pool,
scratch_pool));
if (operation == svn_wc_operation_merge &&
@@ -9997,62 +10948,37 @@ configure_option_local_move_file_merge(svn_client_conflict_t *conflict,
local_change == svn_wc_conflict_reason_missing)
{
struct conflict_tree_local_missing_details *details;
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath,
+ scratch_pool, scratch_pool));
details = conflict->tree_conflict_local_details;
- if (details != NULL && details->moves != NULL)
+ if (details != NULL && details->moves != NULL &&
+ details->move_target_repos_relpath != NULL)
{
- apr_hash_t *wc_move_targets = apr_hash_make(scratch_pool);
- apr_pool_t *iterpool;
- int i;
+ apr_array_header_t *moves;
+ const char *moved_to_abspath;
+ const char *description;
- iterpool = svn_pool_create(scratch_pool);
- for (i = 0; i < details->moves->nelts; i++)
- {
- struct repos_move_info *move;
+ moves = svn_hash_gets(details->wc_move_targets,
+ details->move_target_repos_relpath);
+ moved_to_abspath =
+ APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *);
- svn_pool_clear(iterpool);
- move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *);
- SVN_ERR(follow_move_chains(wc_move_targets, move, ctx,
- conflict->local_abspath,
- svn_node_file,
- incoming_new_repos_relpath,
- incoming_new_pegrev,
- scratch_pool, iterpool));
- }
- svn_pool_destroy(iterpool);
+ description =
+ apr_psprintf(
+ scratch_pool, _("apply changes to move destination '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, moved_to_abspath),
+ scratch_pool));
- if (apr_hash_count(wc_move_targets) > 0)
+ if ((incoming_old_kind == svn_node_file ||
+ incoming_old_kind == svn_node_none) &&
+ (incoming_new_kind == svn_node_file ||
+ incoming_new_kind == svn_node_none))
{
- apr_array_header_t *move_target_repos_relpaths;
- const svn_sort__item_t *item;
- apr_array_header_t *moved_to_abspaths;
- const char *description;
- const char *wcroot_abspath;
-
- /* Initialize to the first possible move target. Hopefully,
- * in most cases there will only be one candidate anyway. */
- move_target_repos_relpaths = svn_sort__hash(
- wc_move_targets,
- svn_sort_compare_items_as_paths,
- scratch_pool);
- item = &APR_ARRAY_IDX(move_target_repos_relpaths,
- 0, svn_sort__item_t);
- moved_to_abspaths = item->value;
- details->moved_to_abspath =
- apr_pstrdup(conflict->pool,
- APR_ARRAY_IDX(moved_to_abspaths, 0, const char *));
-
- SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
- conflict->local_abspath,
- scratch_pool, scratch_pool));
- description =
- apr_psprintf(
- scratch_pool, _("apply changes to move destination '%s'"),
- svn_dirent_local_style(
- svn_dirent_skip_ancestor(wcroot_abspath,
- details->moved_to_abspath),
- scratch_pool));
-
add_resolution_option(
options, conflict,
svn_client_conflict_option_local_move_file_text_merge,
@@ -10060,50 +10986,943 @@ configure_option_local_move_file_merge(svn_client_conflict_t *conflict,
description, resolve_local_move_file_merge);
}
else
- details->moved_to_abspath = NULL;
+ {
+ add_resolution_option(
+ options, conflict,
+ svn_client_conflict_option_local_move_dir_merge,
+ _("Apply to move destination"),
+ description, resolve_local_move_dir_merge);
+ }
}
}
return SVN_NO_ERROR;
}
-svn_error_t *
-svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
- apr_array_header_t **possible_moved_to_repos_relpaths,
+/* Configure 'sibling move file/dir merge' resolution option for
+ * a tree conflict. */
+static svn_error_t *
+configure_option_sibling_move_merge(svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_array_header_t *options,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_operation_t operation;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_reason_t local_change;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
+ svn_node_kind_t incoming_old_kind;
+ const char *incoming_new_repos_relpath;
+ svn_revnum_t incoming_new_pegrev;
+ svn_node_kind_t incoming_new_kind;
+
+ operation = svn_client_conflict_get_operation(conflict);
+ incoming_change = svn_client_conflict_get_incoming_change(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, &incoming_old_pegrev,
+ &incoming_old_kind, conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+ &incoming_new_repos_relpath, &incoming_new_pegrev,
+ &incoming_new_kind, conflict, scratch_pool,
+ scratch_pool));
+
+ if (operation == svn_wc_operation_merge &&
+ incoming_change == svn_wc_conflict_action_edit &&
+ local_change == svn_wc_conflict_reason_missing)
+ {
+ struct conflict_tree_local_missing_details *details;
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath,
+ scratch_pool, scratch_pool));
+
+ details = conflict->tree_conflict_local_details;
+ if (details != NULL && details->wc_siblings != NULL)
+ {
+ const char *description;
+ const char *sibling;
+
+ sibling =
+ apr_pstrdup(conflict->pool,
+ APR_ARRAY_IDX(details->wc_siblings,
+ details->preferred_sibling_idx,
+ const char *));
+ description =
+ apr_psprintf(
+ scratch_pool, _("apply changes to '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, sibling),
+ scratch_pool));
+
+ if ((incoming_old_kind == svn_node_file ||
+ incoming_old_kind == svn_node_none) &&
+ (incoming_new_kind == svn_node_file ||
+ incoming_new_kind == svn_node_none))
+ {
+ add_resolution_option(
+ options, conflict,
+ svn_client_conflict_option_sibling_move_file_text_merge,
+ _("Apply to corresponding local location"),
+ description, resolve_local_move_file_merge);
+ }
+ else
+ {
+ add_resolution_option(
+ options, conflict,
+ svn_client_conflict_option_sibling_move_dir_merge,
+ _("Apply to corresponding local location"),
+ description, resolve_local_move_dir_merge);
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+struct conflict_tree_update_local_moved_away_details {
+ /*
+ * This array consists of "const char *" absolute paths to working copy
+ * nodes which are uncomitted copies and correspond to the repository path
+ * of the conflict victim.
+ * Each such working copy node is a potential local move target which can
+ * be chosen to find a suitable merge target when resolving a tree conflict.
+ *
+ * This may be an empty array in case if there is no move target path in
+ * the working copy. */
+ apr_array_header_t *wc_move_targets;
+
+ /* Current index into the list of working copy paths in WC_MOVE_TARGETS. */
+ int preferred_move_target_idx;
+};
+
+/* Implements conflict_option_resolve_func_t.
+ * Resolve an incoming move vs local move conflict by merging from the
+ * incoming move's target location to the local move's target location,
+ * overriding the incoming move. The original local move was broken during
+ * update/switch, so overriding the incoming move involves recording a new
+ * move from the incoming move's target location to the local move's target
+ * location. */
+static svn_error_t *
+resolve_both_moved_file_update_keep_local_move(
svn_client_conflict_option_t *option,
- apr_pool_t *result_pool,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
- svn_client_conflict_t *conflict = option->conflict;
- struct conflict_tree_incoming_delete_details *details;
+ svn_client_conflict_option_id_t option_id;
const char *victim_abspath;
- apr_array_header_t *sorted_repos_relpaths;
- int i;
+ const char *local_moved_to_abspath;
+ svn_wc_operation_t operation;
+ const char *lock_abspath;
+ svn_error_t *err;
+ const char *repos_root_url;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
+ const char *incoming_new_repos_relpath;
+ svn_revnum_t incoming_new_pegrev;
+ const char *wc_tmpdir;
+ const char *ancestor_abspath;
+ svn_stream_t *ancestor_stream;
+ apr_hash_t *ancestor_props;
+ apr_hash_t *incoming_props;
+ apr_hash_t *local_props;
+ const char *ancestor_url;
+ const char *corrected_url;
+ svn_ra_session_t *ra_session;
+ svn_wc_merge_outcome_t merge_content_outcome;
+ svn_wc_notify_state_t merge_props_outcome;
+ apr_array_header_t *propdiffs;
+ struct conflict_tree_incoming_delete_details *incoming_details;
+ apr_array_header_t *possible_moved_to_abspaths;
+ const char *incoming_moved_to_abspath;
+ struct conflict_tree_update_local_moved_away_details *local_details;
- SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
- svn_client_conflict_option_incoming_move_file_text_merge ||
- svn_client_conflict_option_get_id(option) ==
- svn_client_conflict_option_incoming_move_dir_merge);
+ victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+ operation = svn_client_conflict_get_operation(conflict);
+ incoming_details = conflict->tree_conflict_incoming_details;
+ if (incoming_details == NULL || incoming_details->moves == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("The specified conflict resolution option "
+ "requires details for tree conflict at '%s' "
+ "to be fetched from the repository first."),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+ if (operation == svn_wc_operation_none)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid operation code '%d' recorded for "
+ "conflict at '%s'"), operation,
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ option_id = svn_client_conflict_option_get_id(option);
+ SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge);
+
+ SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
+ conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, &incoming_old_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+ &incoming_new_repos_relpath, &incoming_new_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+
+ /* Set up temporary storage for the common ancestor version of the file. */
+ SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_unique(&ancestor_stream,
+ &ancestor_abspath, wc_tmpdir,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+
+ /* Fetch the ancestor file's content. */
+ ancestor_url = svn_path_url_add_component2(repos_root_url,
+ incoming_old_repos_relpath,
+ scratch_pool);
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+ ancestor_url, NULL, NULL,
+ FALSE, FALSE, ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
+ ancestor_stream, NULL, /* fetched_rev */
+ &ancestor_props, scratch_pool));
+ filter_props(ancestor_props, scratch_pool);
+
+ /* Close stream to flush ancestor file to disk. */
+ SVN_ERR(svn_stream_close(ancestor_stream));
+
+ possible_moved_to_abspaths =
+ svn_hash_gets(incoming_details->wc_move_targets,
+ get_moved_to_repos_relpath(incoming_details, scratch_pool));
+ incoming_moved_to_abspath =
+ APR_ARRAY_IDX(possible_moved_to_abspaths,
+ incoming_details->wc_move_target_idx, const char *);
+
+ local_details = conflict->tree_conflict_local_details;
+ local_moved_to_abspath =
+ APR_ARRAY_IDX(local_details->wc_move_targets,
+ local_details->preferred_move_target_idx, const char *);
+
+ /* ### The following WC modifications should be atomic. */
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+ &lock_abspath, ctx->wc_ctx,
+ svn_dirent_get_longest_ancestor(victim_abspath,
+ local_moved_to_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ /* Get a copy of the incoming moved item's properties. */
+ err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx,
+ incoming_moved_to_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Get a copy of the local move target's properties. */
+ err = svn_wc_prop_list2(&local_props, ctx->wc_ctx,
+ local_moved_to_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Create a property diff for the files. */
+ err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Perform the file merge. */
+ err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
+ ctx->wc_ctx, ancestor_abspath,
+ incoming_moved_to_abspath, local_moved_to_abspath,
+ NULL, NULL, NULL, /* labels */
+ NULL, NULL, /* conflict versions */
+ FALSE, /* dry run */
+ NULL, NULL, /* diff3_cmd, merge_options */
+ apr_hash_count(ancestor_props) ? ancestor_props : NULL,
+ propdiffs,
+ NULL, NULL, /* conflict func/baton */
+ NULL, NULL, /* don't allow user to cancel here */
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ /* Tell the world about the file merge that just happened. */
+ notify = svn_wc_create_notify(local_moved_to_abspath,
+ svn_wc_notify_update_update,
+ scratch_pool);
+ if (merge_content_outcome == svn_wc_merge_conflict)
+ notify->content_state = svn_wc_notify_state_conflicted;
+ else
+ notify->content_state = svn_wc_notify_state_merged;
+ notify->prop_state = merge_props_outcome;
+ notify->kind = svn_node_file;
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ /* Record a new move which overrides the incoming move. */
+ err = svn_wc__move2(ctx->wc_ctx, incoming_moved_to_abspath,
+ local_moved_to_abspath,
+ TRUE, /* meta-data only move */
+ FALSE, /* mixed-revisions don't apply to files */
+ NULL, NULL, /* don't allow user to cancel here */
+ NULL, NULL, /* no extra notification */
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Remove moved-away file from disk. */
+ err = svn_io_remove_file2(incoming_moved_to_abspath, TRUE, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
+ scratch_pool);
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
+
+ conflict->resolution_tree = option_id;
+
+unlock_wc:
+ err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+ lock_abspath,
+ scratch_pool));
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements conflict_option_resolve_func_t.
+ * Resolve an incoming move vs local move conflict by merging from the
+ * local move's target location to the incoming move's target location,
+ * and reverting the local move. */
+static svn_error_t *
+resolve_both_moved_file_update_keep_incoming_move(
+ svn_client_conflict_option_t *option,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_conflict_option_id_t option_id;
+ const char *victim_abspath;
+ const char *local_moved_to_abspath;
+ svn_wc_operation_t operation;
+ const char *lock_abspath;
+ svn_error_t *err;
+ const char *repos_root_url;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
+ const char *incoming_new_repos_relpath;
+ svn_revnum_t incoming_new_pegrev;
+ const char *wc_tmpdir;
+ const char *ancestor_abspath;
+ svn_stream_t *ancestor_stream;
+ apr_hash_t *ancestor_props;
+ apr_hash_t *incoming_props;
+ apr_hash_t *local_props;
+ const char *ancestor_url;
+ const char *corrected_url;
+ svn_ra_session_t *ra_session;
+ svn_wc_merge_outcome_t merge_content_outcome;
+ svn_wc_notify_state_t merge_props_outcome;
+ apr_array_header_t *propdiffs;
+ struct conflict_tree_incoming_delete_details *incoming_details;
+ apr_array_header_t *possible_moved_to_abspaths;
+ const char *incoming_moved_to_abspath;
+ struct conflict_tree_update_local_moved_away_details *local_details;
victim_abspath = svn_client_conflict_get_local_abspath(conflict);
- details = conflict->tree_conflict_incoming_details;
- if (details == NULL || details->wc_move_targets == NULL)
+ operation = svn_client_conflict_get_operation(conflict);
+ incoming_details = conflict->tree_conflict_incoming_details;
+ if (incoming_details == NULL || incoming_details->moves == NULL)
return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
- _("Getting a list of possible move targets "
+ _("The specified conflict resolution option "
"requires details for tree conflict at '%s' "
- "to be fetched from the repository first"),
+ "to be fetched from the repository first."),
svn_dirent_local_style(victim_abspath,
scratch_pool));
+ if (operation == svn_wc_operation_none)
+ return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+ _("Invalid operation code '%d' recorded for "
+ "conflict at '%s'"), operation,
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ option_id = svn_client_conflict_option_get_id(option);
+ SVN_ERR_ASSERT(option_id ==
+ svn_client_conflict_option_both_moved_file_move_merge);
+
+ SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL,
+ conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, &incoming_old_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+ &incoming_new_repos_relpath, &incoming_new_pegrev,
+ NULL, conflict, scratch_pool,
+ scratch_pool));
+
+ /* Set up temporary storage for the common ancestor version of the file. */
+ SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_unique(&ancestor_stream,
+ &ancestor_abspath, wc_tmpdir,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+
+ /* Fetch the ancestor file's content. */
+ ancestor_url = svn_path_url_add_component2(repos_root_url,
+ incoming_old_repos_relpath,
+ scratch_pool);
+ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
+ ancestor_url, NULL, NULL,
+ FALSE, FALSE, ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev,
+ ancestor_stream, NULL, /* fetched_rev */
+ &ancestor_props, scratch_pool));
+ filter_props(ancestor_props, scratch_pool);
+
+ /* Close stream to flush ancestor file to disk. */
+ SVN_ERR(svn_stream_close(ancestor_stream));
+
+ possible_moved_to_abspaths =
+ svn_hash_gets(incoming_details->wc_move_targets,
+ get_moved_to_repos_relpath(incoming_details, scratch_pool));
+ incoming_moved_to_abspath =
+ APR_ARRAY_IDX(possible_moved_to_abspaths,
+ incoming_details->wc_move_target_idx, const char *);
+
+ local_details = conflict->tree_conflict_local_details;
+ local_moved_to_abspath =
+ APR_ARRAY_IDX(local_details->wc_move_targets,
+ local_details->preferred_move_target_idx, const char *);
+
+ /* ### The following WC modifications should be atomic. */
+ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(
+ &lock_abspath, ctx->wc_ctx,
+ svn_dirent_get_longest_ancestor(victim_abspath,
+ local_moved_to_abspath,
+ scratch_pool),
+ scratch_pool, scratch_pool));
+
+ /* Get a copy of the incoming moved item's properties. */
+ err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx,
+ incoming_moved_to_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Get a copy of the local move target's properties. */
+ err = svn_wc_prop_list2(&local_props, ctx->wc_ctx,
+ local_moved_to_abspath,
+ scratch_pool, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Create a property diff for the files. */
+ err = svn_prop_diffs(&propdiffs, incoming_props, local_props,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ /* Perform the file merge. */
+ err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome,
+ ctx->wc_ctx, ancestor_abspath,
+ local_moved_to_abspath, incoming_moved_to_abspath,
+ NULL, NULL, NULL, /* labels */
+ NULL, NULL, /* conflict versions */
+ FALSE, /* dry run */
+ NULL, NULL, /* diff3_cmd, merge_options */
+ apr_hash_count(ancestor_props) ? ancestor_props : NULL,
+ propdiffs,
+ NULL, NULL, /* conflict func/baton */
+ NULL, NULL, /* don't allow user to cancel here */
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ /* Tell the world about the file merge that just happened. */
+ notify = svn_wc_create_notify(local_moved_to_abspath,
+ svn_wc_notify_update_update,
+ scratch_pool);
+ if (merge_content_outcome == svn_wc_merge_conflict)
+ notify->content_state = svn_wc_notify_state_conflicted;
+ else
+ notify->content_state = svn_wc_notify_state_merged;
+ notify->prop_state = merge_props_outcome;
+ notify->kind = svn_node_file;
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ /* Revert the copy-half of the local move. The delete-half of this move
+ * has already been deleted during the update/switch operation. */
+ err = svn_wc_revert6(ctx->wc_ctx, local_moved_to_abspath, svn_depth_empty,
+ FALSE, NULL, TRUE, FALSE,
+ TRUE /*added_keep_local*/,
+ NULL, NULL, /* no cancellation */
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool);
+ if (err)
+ goto unlock_wc;
+
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
+
+ notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree,
+ scratch_pool);
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+
+ svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool);
+
+ conflict->resolution_tree = option_id;
+
+unlock_wc:
+ err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx,
+ lock_abspath,
+ scratch_pool));
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* Implements tree_conflict_get_details_func_t. */
+static svn_error_t *
+conflict_tree_get_details_update_local_moved_away(
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ struct conflict_tree_update_local_moved_away_details *details;
+ const char *incoming_old_repos_relpath;
+ svn_node_kind_t incoming_old_kind;
+
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, NULL, &incoming_old_kind,
+ conflict, scratch_pool, scratch_pool));
+
+ details = apr_pcalloc(conflict->pool, sizeof(*details));
+
+ details->wc_move_targets = apr_array_make(conflict->pool, 1,
+ sizeof(const char *));
+
+ /* Search the WC for copies of the conflict victim. */
+ SVN_ERR(svn_wc__find_copies_of_repos_path(&details->wc_move_targets,
+ conflict->local_abspath,
+ incoming_old_repos_relpath,
+ incoming_old_kind,
+ ctx->wc_ctx,
+ conflict->pool,
+ scratch_pool));
+
+ conflict->tree_conflict_local_details = details;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+get_both_moved_file_paths(const char **incoming_moved_to_abspath,
+ const char **local_moved_to_abspath,
+ svn_client_conflict_t *conflict,
+ apr_pool_t *scratch_pool)
+{
+ struct conflict_tree_incoming_delete_details *incoming_details;
+ apr_array_header_t *incoming_move_target_wc_abspaths;
+ svn_wc_operation_t operation;
+
+ operation = svn_client_conflict_get_operation(conflict);
+
+ *incoming_moved_to_abspath = NULL;
+ *local_moved_to_abspath = NULL;
+
+ incoming_details = conflict->tree_conflict_incoming_details;
+ if (incoming_details == NULL || incoming_details->moves == NULL ||
+ apr_hash_count(incoming_details->wc_move_targets) == 0)
+ return SVN_NO_ERROR;
+
+ incoming_move_target_wc_abspaths =
+ svn_hash_gets(incoming_details->wc_move_targets,
+ get_moved_to_repos_relpath(incoming_details,
+ scratch_pool));
+ *incoming_moved_to_abspath =
+ APR_ARRAY_IDX(incoming_move_target_wc_abspaths,
+ incoming_details->wc_move_target_idx, const char *);
+
+ if (operation == svn_wc_operation_merge)
+ {
+ struct conflict_tree_local_missing_details *local_details;
+ apr_array_header_t *local_moves;
+
+ local_details = conflict->tree_conflict_local_details;
+ if (local_details == NULL ||
+ apr_hash_count(local_details->wc_move_targets) == 0)
+ return SVN_NO_ERROR;
+
+ local_moves = svn_hash_gets(local_details->wc_move_targets,
+ local_details->move_target_repos_relpath);
+ *local_moved_to_abspath =
+ APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx,
+ const char *);
+ }
+ else
+ {
+ struct conflict_tree_update_local_moved_away_details *local_details;
+
+ local_details = conflict->tree_conflict_local_details;
+ if (local_details == NULL ||
+ local_details->wc_move_targets->nelts == 0)
+ return SVN_NO_ERROR;
+
+ *local_moved_to_abspath =
+ APR_ARRAY_IDX(local_details->wc_move_targets,
+ local_details->preferred_move_target_idx,
+ const char *);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+conflict_tree_get_description_update_both_moved_file_merge(
+ const char **description,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *incoming_moved_to_abspath;
+ const char *local_moved_to_abspath;
+ svn_wc_operation_t operation;
+ const char *wcroot_abspath;
+
+ *description = NULL;
+
+ SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath,
+ &local_moved_to_abspath,
+ conflict, scratch_pool));
+ if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath, scratch_pool,
+ scratch_pool));
+
+ operation = svn_client_conflict_get_operation(conflict);
+
+ if (operation == svn_wc_operation_merge)
+ {
+ /* In case of a merge, the incoming move has A+ (copied) status... */
+ *description =
+ apr_psprintf(
+ scratch_pool,
+ _("apply changes to '%s' and revert addition of '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
+ scratch_pool),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
+ scratch_pool));
+ }
+ else
+ {
+ /* ...but in case of update/switch the local move has "A+" status. */
+ *description =
+ apr_psprintf(
+ scratch_pool,
+ _("override incoming move and merge incoming changes from '%s' "
+ "to '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
+ scratch_pool),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+conflict_tree_get_description_update_both_moved_file_move_merge(
+ const char **description,
+ svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *incoming_moved_to_abspath;
+ const char *local_moved_to_abspath;
+ svn_wc_operation_t operation;
+ const char *wcroot_abspath;
+
+ *description = NULL;
+
+ SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath,
+ &local_moved_to_abspath,
+ conflict, scratch_pool));
+ if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath, scratch_pool,
+ scratch_pool));
+
+ operation = svn_client_conflict_get_operation(conflict);
+
+ if (operation == svn_wc_operation_merge)
+ {
+ SVN_ERR(describe_incoming_move_merge_conflict_option(
+ description, conflict, ctx, local_moved_to_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ *description =
+ apr_psprintf(
+ scratch_pool,
+ _("accept incoming move and merge local changes from "
+ "'%s' to '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
+ scratch_pool),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Configure 'both moved file merge' resolution options for a tree conflict. */
+static svn_error_t *
+configure_option_both_moved_file_merge(svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_array_header_t *options,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_operation_t operation;
+ svn_node_kind_t victim_node_kind;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_reason_t local_change;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
+ svn_node_kind_t incoming_old_kind;
+ const char *incoming_new_repos_relpath;
+ svn_revnum_t incoming_new_pegrev;
+ svn_node_kind_t incoming_new_kind;
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath, scratch_pool,
+ scratch_pool));
+
+ operation = svn_client_conflict_get_operation(conflict);
+ victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
+ incoming_change = svn_client_conflict_get_incoming_change(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, &incoming_old_pegrev,
+ &incoming_old_kind, conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+ &incoming_new_repos_relpath, &incoming_new_pegrev,
+ &incoming_new_kind, conflict, scratch_pool,
+ scratch_pool));
+
+ /* ### what about the switch operation? */
+ if (((operation == svn_wc_operation_merge &&
+ victim_node_kind == svn_node_none) ||
+ (operation == svn_wc_operation_update &&
+ victim_node_kind == svn_node_file)) &&
+ incoming_old_kind == svn_node_file &&
+ incoming_new_kind == svn_node_none &&
+ ((operation == svn_wc_operation_merge &&
+ local_change == svn_wc_conflict_reason_missing) ||
+ (operation == svn_wc_operation_update &&
+ local_change == svn_wc_conflict_reason_moved_away)) &&
+ incoming_change == svn_wc_conflict_action_delete)
+ {
+ const char *description;
+
+ SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge(
+ &description, conflict, ctx, conflict->pool, scratch_pool));
+
+ if (description == NULL) /* details not fetched yet */
+ return SVN_NO_ERROR;
+
+ add_resolution_option(
+ options, conflict, svn_client_conflict_option_both_moved_file_merge,
+ _("Merge to corresponding local location"),
+ description,
+ operation == svn_wc_operation_merge ?
+ resolve_both_moved_file_text_merge :
+ resolve_both_moved_file_update_keep_local_move);
+
+ SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge(
+ &description, conflict, ctx, conflict->pool, scratch_pool));
+
+ add_resolution_option(options, conflict,
+ svn_client_conflict_option_both_moved_file_move_merge,
+ _("Move and merge"), description,
+ operation == svn_wc_operation_merge ?
+ resolve_incoming_move_file_text_merge :
+ resolve_both_moved_file_update_keep_incoming_move);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Configure 'both moved dir merge' resolution options for a tree conflict. */
+static svn_error_t *
+configure_option_both_moved_dir_merge(svn_client_conflict_t *conflict,
+ svn_client_ctx_t *ctx,
+ apr_array_header_t *options,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_operation_t operation;
+ svn_node_kind_t victim_node_kind;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_reason_t local_change;
+ const char *incoming_old_repos_relpath;
+ svn_revnum_t incoming_old_pegrev;
+ svn_node_kind_t incoming_old_kind;
+ const char *incoming_new_repos_relpath;
+ svn_revnum_t incoming_new_pegrev;
+ svn_node_kind_t incoming_new_kind;
+ const char *wcroot_abspath;
+
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
+ conflict->local_abspath, scratch_pool,
+ scratch_pool));
+
+ operation = svn_client_conflict_get_operation(conflict);
+ victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict);
+ incoming_change = svn_client_conflict_get_incoming_change(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
+ SVN_ERR(svn_client_conflict_get_incoming_old_repos_location(
+ &incoming_old_repos_relpath, &incoming_old_pegrev,
+ &incoming_old_kind, conflict, scratch_pool,
+ scratch_pool));
+ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location(
+ &incoming_new_repos_relpath, &incoming_new_pegrev,
+ &incoming_new_kind, conflict, scratch_pool,
+ scratch_pool));
+
+ if (operation == svn_wc_operation_merge &&
+ victim_node_kind == svn_node_none &&
+ incoming_old_kind == svn_node_dir &&
+ incoming_new_kind == svn_node_none &&
+ local_change == svn_wc_conflict_reason_missing &&
+ incoming_change == svn_wc_conflict_action_delete)
+ {
+ struct conflict_tree_incoming_delete_details *incoming_details;
+ struct conflict_tree_local_missing_details *local_details;
+ const char *description;
+ apr_array_header_t *local_moves;
+ const char *local_moved_to_abspath;
+ const char *incoming_moved_to_abspath;
+ apr_array_header_t *incoming_move_target_wc_abspaths;
+
+ incoming_details = conflict->tree_conflict_incoming_details;
+ if (incoming_details == NULL || incoming_details->moves == NULL ||
+ apr_hash_count(incoming_details->wc_move_targets) == 0)
+ return SVN_NO_ERROR;
+
+ local_details = conflict->tree_conflict_local_details;
+ if (local_details == NULL ||
+ apr_hash_count(local_details->wc_move_targets) == 0)
+ return SVN_NO_ERROR;
+
+ local_moves = svn_hash_gets(local_details->wc_move_targets,
+ local_details->move_target_repos_relpath);
+ local_moved_to_abspath =
+ APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx,
+ const char *);
+
+ incoming_move_target_wc_abspaths =
+ svn_hash_gets(incoming_details->wc_move_targets,
+ get_moved_to_repos_relpath(incoming_details,
+ scratch_pool));
+ incoming_moved_to_abspath =
+ APR_ARRAY_IDX(incoming_move_target_wc_abspaths,
+ incoming_details->wc_move_target_idx, const char *);
+
+ description =
+ apr_psprintf(
+ scratch_pool, _("apply changes to '%s' and revert addition of '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath),
+ scratch_pool),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath),
+ scratch_pool));
+ add_resolution_option(
+ options, conflict, svn_client_conflict_option_both_moved_dir_merge,
+ _("Merge to corresponding local location"),
+ description, resolve_both_moved_dir_merge);
+
+ SVN_ERR(describe_incoming_move_merge_conflict_option(
+ &description, conflict, ctx, local_moved_to_abspath,
+ scratch_pool, scratch_pool));
+ add_resolution_option(options, conflict,
+ svn_client_conflict_option_both_moved_dir_move_merge,
+ _("Move and merge"), description,
+ resolve_both_moved_dir_move_merge);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return a copy of the repos replath candidate list. */
+static svn_error_t *
+get_repos_relpath_candidates(
+ apr_array_header_t **possible_moved_to_repos_relpaths,
+ apr_hash_t *wc_move_targets,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *sorted_repos_relpaths;
+ int i;
- /* Return a copy of the repos replath candidate list. */
- sorted_repos_relpaths = svn_sort__hash(details->wc_move_targets,
+ sorted_repos_relpaths = svn_sort__hash(wc_move_targets,
svn_sort_compare_items_as_paths,
scratch_pool);
- *possible_moved_to_repos_relpaths = apr_array_make(
- result_pool,
- sorted_repos_relpaths->nelts,
- sizeof (const char *));
+ *possible_moved_to_repos_relpaths =
+ apr_array_make(result_pool, sorted_repos_relpaths->nelts,
+ sizeof (const char *));
for (i = 0; i < sorted_repos_relpaths->nelts; i++)
{
svn_sort__item_t item;
@@ -10119,37 +11938,115 @@ svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
}
svn_error_t *
-svn_client_conflict_option_set_moved_to_repos_relpath(
+svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(
+ apr_array_header_t **possible_moved_to_repos_relpaths,
svn_client_conflict_option_t *option,
- int preferred_move_target_idx,
- svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_client_conflict_t *conflict = option->conflict;
- struct conflict_tree_incoming_delete_details *details;
const char *victim_abspath;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_reason_t local_change;
+ svn_client_conflict_option_id_t id;
+
+ id = svn_client_conflict_option_get_id(option);
+ if (id != svn_client_conflict_option_incoming_move_file_text_merge &&
+ id != svn_client_conflict_option_incoming_move_dir_merge &&
+ id != svn_client_conflict_option_local_move_file_text_merge &&
+ id != svn_client_conflict_option_local_move_dir_merge &&
+ id != svn_client_conflict_option_sibling_move_file_text_merge &&
+ id != svn_client_conflict_option_sibling_move_dir_merge &&
+ id != svn_client_conflict_option_both_moved_file_merge &&
+ id != svn_client_conflict_option_both_moved_file_move_merge &&
+ id != svn_client_conflict_option_both_moved_dir_merge &&
+ id != svn_client_conflict_option_both_moved_dir_move_merge)
+ {
+ /* We cannot operate on this option. */
+ *possible_moved_to_repos_relpaths = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+ operation = svn_client_conflict_get_operation(conflict);
+ incoming_change = svn_client_conflict_get_incoming_change(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
+
+ if (operation == svn_wc_operation_merge &&
+ incoming_change == svn_wc_conflict_action_edit &&
+ local_change == svn_wc_conflict_reason_missing)
+ {
+ struct conflict_tree_local_missing_details *details;
+
+ details = conflict->tree_conflict_local_details;
+ if (details == NULL ||
+ (details->wc_move_targets == NULL && details->wc_siblings == NULL))
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Getting a list of possible move targets "
+ "requires details for tree conflict at '%s' "
+ "to be fetched from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ if (details->wc_move_targets)
+ SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths,
+ details->wc_move_targets,
+ result_pool, scratch_pool));
+ else
+ *possible_moved_to_repos_relpaths = NULL;
+ }
+ else
+ {
+ struct conflict_tree_incoming_delete_details *details;
+
+ details = conflict->tree_conflict_incoming_details;
+ if (details == NULL || details->wc_move_targets == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Getting a list of possible move targets "
+ "requires details for tree conflict at '%s' "
+ "to be fetched from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths,
+ details->wc_move_targets,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_conflict_option_get_moved_to_repos_relpath_candidates(
+ apr_array_header_t **possible_moved_to_repos_relpaths,
+ svn_client_conflict_option_t *option,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* The only difference to API version 2 is an assertion failure if
+ * an unexpected option is passed.
+ * We do not emulate this old behaviour since clients written against
+ * the previous API will just keep working. */
+ return svn_error_trace(
+ svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(
+ possible_moved_to_repos_relpaths, option, result_pool, scratch_pool));
+}
+
+static svn_error_t *
+set_wc_move_target(const char **new_hash_key,
+ apr_hash_t *wc_move_targets,
+ int preferred_move_target_idx,
+ const char *victim_abspath,
+ apr_pool_t *scratch_pool)
+{
apr_array_header_t *move_target_repos_relpaths;
svn_sort__item_t item;
const char *move_target_repos_relpath;
apr_hash_index_t *hi;
- SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
- svn_client_conflict_option_incoming_move_file_text_merge ||
- svn_client_conflict_option_get_id(option) ==
- svn_client_conflict_option_incoming_move_dir_merge);
-
- victim_abspath = svn_client_conflict_get_local_abspath(conflict);
- details = conflict->tree_conflict_incoming_details;
- if (details == NULL || details->wc_move_targets == NULL)
- return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
- _("Setting a move target requires details "
- "for tree conflict at '%s' to be fetched "
- "from the repository first"),
- svn_dirent_local_style(victim_abspath,
- scratch_pool));
-
if (preferred_move_target_idx < 0 ||
- preferred_move_target_idx >= apr_hash_count(details->wc_move_targets))
+ preferred_move_target_idx >= apr_hash_count(wc_move_targets))
return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
_("Index '%d' is out of bounds of the possible "
"move target list for '%s'"),
@@ -10158,15 +12055,14 @@ svn_client_conflict_option_set_moved_to_repos_relpath(
scratch_pool));
/* Translate the index back into a hash table key. */
- move_target_repos_relpaths =
- svn_sort__hash(details->wc_move_targets,
- svn_sort_compare_items_as_paths,
- scratch_pool);
+ move_target_repos_relpaths = svn_sort__hash(wc_move_targets,
+ svn_sort_compare_items_as_paths,
+ scratch_pool);
item = APR_ARRAY_IDX(move_target_repos_relpaths, preferred_move_target_idx,
svn_sort__item_t);
move_target_repos_relpath = item.key;
/* Find our copy of the hash key and remember the user's preference. */
- for (hi = apr_hash_first(scratch_pool, details->wc_move_targets);
+ for (hi = apr_hash_first(scratch_pool, wc_move_targets);
hi != NULL;
hi = apr_hash_next(hi))
{
@@ -10174,15 +12070,7 @@ svn_client_conflict_option_set_moved_to_repos_relpath(
if (strcmp(move_target_repos_relpath, repos_relpath) == 0)
{
- details->move_target_repos_relpath = repos_relpath;
- /* Update option description. */
- SVN_ERR(describe_incoming_move_merge_conflict_option(
- &option->description,
- conflict, ctx,
- details,
- conflict->pool,
- scratch_pool));
-
+ *new_hash_key = repos_relpath;
return SVN_NO_ERROR;
}
}
@@ -10196,107 +12084,501 @@ svn_client_conflict_option_set_moved_to_repos_relpath(
}
svn_error_t *
-svn_client_conflict_option_get_moved_to_abspath_candidates(
+svn_client_conflict_option_set_moved_to_repos_relpath2(
+ svn_client_conflict_option_t *option,
+ int preferred_move_target_idx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_conflict_t *conflict = option->conflict;
+ const char *victim_abspath;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_reason_t local_change;
+ svn_client_conflict_option_id_t id;
+
+ id = svn_client_conflict_option_get_id(option);
+ if (id != svn_client_conflict_option_incoming_move_file_text_merge &&
+ id != svn_client_conflict_option_incoming_move_dir_merge &&
+ id != svn_client_conflict_option_local_move_file_text_merge &&
+ id != svn_client_conflict_option_local_move_dir_merge &&
+ id != svn_client_conflict_option_sibling_move_file_text_merge &&
+ id != svn_client_conflict_option_sibling_move_dir_merge &&
+ id != svn_client_conflict_option_both_moved_file_merge &&
+ id != svn_client_conflict_option_both_moved_file_move_merge &&
+ id != svn_client_conflict_option_both_moved_dir_merge &&
+ id != svn_client_conflict_option_both_moved_dir_move_merge)
+ return SVN_NO_ERROR; /* We cannot operate on this option. Nothing to do. */
+
+ victim_abspath = svn_client_conflict_get_local_abspath(conflict);
+ operation = svn_client_conflict_get_operation(conflict);
+ incoming_change = svn_client_conflict_get_incoming_change(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
+
+ if (operation == svn_wc_operation_merge &&
+ incoming_change == svn_wc_conflict_action_edit &&
+ local_change == svn_wc_conflict_reason_missing)
+ {
+ struct conflict_tree_local_missing_details *details;
+
+ details = conflict->tree_conflict_local_details;
+ if (details == NULL || details->wc_move_targets == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Setting a move target requires details "
+ "for tree conflict at '%s' to be fetched "
+ "from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath,
+ details->wc_move_targets,
+ preferred_move_target_idx,
+ victim_abspath, scratch_pool));
+ details->wc_move_target_idx = 0;
+
+ /* Update option description. */
+ SVN_ERR(conflict_tree_get_description_local_missing(
+ &option->description, conflict, ctx,
+ conflict->pool, scratch_pool));
+ }
+ else
+ {
+ struct conflict_tree_incoming_delete_details *details;
+ apr_array_header_t *move_target_wc_abspaths;
+ const char *moved_to_abspath;
+
+ details = conflict->tree_conflict_incoming_details;
+ if (details == NULL || details->wc_move_targets == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Setting a move target requires details "
+ "for tree conflict at '%s' to be fetched "
+ "from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath,
+ details->wc_move_targets,
+ preferred_move_target_idx,
+ victim_abspath, scratch_pool));
+ details->wc_move_target_idx = 0;
+
+ /* Update option description. */
+ move_target_wc_abspaths =
+ svn_hash_gets(details->wc_move_targets,
+ get_moved_to_repos_relpath(details, scratch_pool));
+ moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
+ details->wc_move_target_idx,
+ const char *);
+ SVN_ERR(describe_incoming_move_merge_conflict_option(
+ &option->description,
+ conflict, ctx,
+ moved_to_abspath,
+ conflict->pool,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_conflict_option_set_moved_to_repos_relpath(
+ svn_client_conflict_option_t *option,
+ int preferred_move_target_idx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ /* The only difference to API version 2 is an assertion failure if
+ * an unexpected option is passed.
+ * We do not emulate this old behaviour since clients written against
+ * the previous API will just keep working. */
+ return svn_error_trace(
+ svn_client_conflict_option_set_moved_to_repos_relpath2(option,
+ preferred_move_target_idx, ctx, scratch_pool));
+}
+
+svn_error_t *
+svn_client_conflict_option_get_moved_to_abspath_candidates2(
apr_array_header_t **possible_moved_to_abspaths,
svn_client_conflict_option_t *option,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_client_conflict_t *conflict = option->conflict;
- struct conflict_tree_incoming_delete_details *details;
const char *victim_abspath;
- apr_array_header_t *move_target_wc_abspaths;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_reason_t local_change;
int i;
+ svn_client_conflict_option_id_t id;
- SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
- svn_client_conflict_option_incoming_move_file_text_merge ||
- svn_client_conflict_option_get_id(option) ==
- svn_client_conflict_option_incoming_move_dir_merge);
+ id = svn_client_conflict_option_get_id(option);
+ if (id != svn_client_conflict_option_incoming_move_file_text_merge &&
+ id != svn_client_conflict_option_incoming_move_dir_merge &&
+ id != svn_client_conflict_option_local_move_file_text_merge &&
+ id != svn_client_conflict_option_local_move_dir_merge &&
+ id != svn_client_conflict_option_sibling_move_file_text_merge &&
+ id != svn_client_conflict_option_sibling_move_dir_merge &&
+ id != svn_client_conflict_option_both_moved_file_merge &&
+ id != svn_client_conflict_option_both_moved_file_move_merge &&
+ id != svn_client_conflict_option_both_moved_dir_merge &&
+ id != svn_client_conflict_option_both_moved_dir_move_merge)
+ {
+ /* We cannot operate on this option. */
+ *possible_moved_to_abspaths = NULL;
+ return NULL;
+ }
victim_abspath = svn_client_conflict_get_local_abspath(conflict);
- details = conflict->tree_conflict_incoming_details;
- if (details == NULL || details->wc_move_targets == NULL)
- return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
- _("Getting a list of possible move targets "
- "requires details for tree conflict at '%s' "
- "to be fetched from the repository first"),
- svn_dirent_local_style(victim_abspath,
- scratch_pool));
+ operation = svn_client_conflict_get_operation(conflict);
+ incoming_change = svn_client_conflict_get_incoming_change(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
- move_target_wc_abspaths =
- svn_hash_gets(details->wc_move_targets,
- get_moved_to_repos_relpath(details, scratch_pool));
+ if (operation == svn_wc_operation_merge &&
+ incoming_change == svn_wc_conflict_action_edit &&
+ local_change == svn_wc_conflict_reason_missing)
+ {
+ struct conflict_tree_local_missing_details *details;
- /* Return a copy of the option's move target candidate list. */
- *possible_moved_to_abspaths =
- apr_array_make(result_pool, move_target_wc_abspaths->nelts,
- sizeof (const char *));
- for (i = 0; i < move_target_wc_abspaths->nelts; i++)
+ details = conflict->tree_conflict_local_details;
+ if (details == NULL ||
+ (details->wc_move_targets == NULL && details->wc_siblings == NULL))
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Getting a list of possible move siblings "
+ "requires details for tree conflict at '%s' "
+ "to be fetched from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ *possible_moved_to_abspaths = apr_array_make(result_pool, 1,
+ sizeof (const char *));
+ if (details->wc_move_targets)
+ {
+ apr_array_header_t *move_target_wc_abspaths;
+ move_target_wc_abspaths =
+ svn_hash_gets(details->wc_move_targets,
+ details->move_target_repos_relpath);
+ for (i = 0; i < move_target_wc_abspaths->nelts; i++)
+ {
+ const char *moved_to_abspath;
+
+ moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i,
+ const char *);
+ APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
+ apr_pstrdup(result_pool, moved_to_abspath);
+ }
+ }
+
+ /* ### Siblings are actually 'corresponding nodes', not 'move targets'.
+ ### But we provide them here to avoid another API function. */
+ if (details->wc_siblings)
+ {
+ for (i = 0; i < details->wc_siblings->nelts; i++)
+ {
+ const char *sibling_abspath;
+
+ sibling_abspath = APR_ARRAY_IDX(details->wc_siblings, i,
+ const char *);
+ APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
+ apr_pstrdup(result_pool, sibling_abspath);
+ }
+ }
+ }
+ else if ((operation == svn_wc_operation_update ||
+ operation == svn_wc_operation_switch) &&
+ incoming_change == svn_wc_conflict_action_delete &&
+ local_change == svn_wc_conflict_reason_moved_away)
{
- const char *moved_to_abspath;
+ struct conflict_tree_update_local_moved_away_details *details;
- moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i,
- const char *);
- APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
- apr_pstrdup(result_pool, moved_to_abspath);
+ details = conflict->tree_conflict_local_details;
+ if (details == NULL || details->wc_move_targets == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Getting a list of possible move targets "
+ "requires details for tree conflict at '%s' "
+ "to be fetched from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ /* Return a copy of the option's move target candidate list. */
+ *possible_moved_to_abspaths =
+ apr_array_make(result_pool, details->wc_move_targets->nelts,
+ sizeof (const char *));
+ for (i = 0; i < details->wc_move_targets->nelts; i++)
+ {
+ const char *moved_to_abspath;
+
+ moved_to_abspath = APR_ARRAY_IDX(details->wc_move_targets, i,
+ const char *);
+ APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
+ apr_pstrdup(result_pool, moved_to_abspath);
+ }
+ }
+ else
+ {
+ struct conflict_tree_incoming_delete_details *details;
+ apr_array_header_t *move_target_wc_abspaths;
+
+ details = conflict->tree_conflict_incoming_details;
+ if (details == NULL || details->wc_move_targets == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Getting a list of possible move targets "
+ "requires details for tree conflict at '%s' "
+ "to be fetched from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ move_target_wc_abspaths =
+ svn_hash_gets(details->wc_move_targets,
+ get_moved_to_repos_relpath(details, scratch_pool));
+
+ /* Return a copy of the option's move target candidate list. */
+ *possible_moved_to_abspaths =
+ apr_array_make(result_pool, move_target_wc_abspaths->nelts,
+ sizeof (const char *));
+ for (i = 0; i < move_target_wc_abspaths->nelts; i++)
+ {
+ const char *moved_to_abspath;
+
+ moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i,
+ const char *);
+ APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) =
+ apr_pstrdup(result_pool, moved_to_abspath);
+ }
}
return SVN_NO_ERROR;
}
svn_error_t *
-svn_client_conflict_option_set_moved_to_abspath(
+svn_client_conflict_option_get_moved_to_abspath_candidates(
+ apr_array_header_t **possible_moved_to_abspaths,
+ svn_client_conflict_option_t *option,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* The only difference to API version 2 is an assertion failure if
+ * an unexpected option is passed.
+ * We do not emulate this old behaviour since clients written against
+ * the previous API will just keep working. */
+ return svn_error_trace(
+ svn_client_conflict_option_get_moved_to_abspath_candidates2(
+ possible_moved_to_abspaths, option, result_pool, scratch_pool));
+}
+
+svn_error_t *
+svn_client_conflict_option_set_moved_to_abspath2(
svn_client_conflict_option_t *option,
int preferred_move_target_idx,
svn_client_ctx_t *ctx,
apr_pool_t *scratch_pool)
{
svn_client_conflict_t *conflict = option->conflict;
- struct conflict_tree_incoming_delete_details *details;
const char *victim_abspath;
- apr_array_header_t *move_target_wc_abspaths;
+ svn_wc_operation_t operation;
+ svn_wc_conflict_action_t incoming_change;
+ svn_wc_conflict_reason_t local_change;
+ svn_client_conflict_option_id_t id;
- SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) ==
- svn_client_conflict_option_incoming_move_file_text_merge ||
- svn_client_conflict_option_get_id(option) ==
- svn_client_conflict_option_incoming_move_dir_merge);
+ id = svn_client_conflict_option_get_id(option);
+ if (id != svn_client_conflict_option_incoming_move_file_text_merge &&
+ id != svn_client_conflict_option_incoming_move_dir_merge &&
+ id != svn_client_conflict_option_local_move_file_text_merge &&
+ id != svn_client_conflict_option_local_move_dir_merge &&
+ id != svn_client_conflict_option_sibling_move_file_text_merge &&
+ id != svn_client_conflict_option_sibling_move_dir_merge &&
+ id != svn_client_conflict_option_both_moved_file_merge &&
+ id != svn_client_conflict_option_both_moved_file_move_merge &&
+ id != svn_client_conflict_option_both_moved_dir_merge &&
+ id != svn_client_conflict_option_both_moved_dir_move_merge)
+ return NULL; /* We cannot operate on this option. Nothing to do. */
victim_abspath = svn_client_conflict_get_local_abspath(conflict);
- details = conflict->tree_conflict_incoming_details;
- if (details == NULL || details->wc_move_targets == NULL)
- return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
- _("Setting a move target requires details "
- "for tree conflict at '%s' to be fetched "
- "from the repository first"),
- svn_dirent_local_style(victim_abspath,
- scratch_pool));
+ operation = svn_client_conflict_get_operation(conflict);
+ incoming_change = svn_client_conflict_get_incoming_change(conflict);
+ local_change = svn_client_conflict_get_local_change(conflict);
- move_target_wc_abspaths =
- svn_hash_gets(details->wc_move_targets,
- get_moved_to_repos_relpath(details, scratch_pool));
+ if (operation == svn_wc_operation_merge &&
+ incoming_change == svn_wc_conflict_action_edit &&
+ local_change == svn_wc_conflict_reason_missing)
+ {
+ struct conflict_tree_local_missing_details *details;
+ const char *wcroot_abspath;
+ const char *preferred_sibling;
- if (preferred_move_target_idx < 0 ||
- preferred_move_target_idx > move_target_wc_abspaths->nelts)
- return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
- _("Index '%d' is out of bounds of the possible "
- "move target list for '%s'"),
- preferred_move_target_idx,
- svn_dirent_local_style(victim_abspath,
- scratch_pool));
+ SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath,
+ ctx->wc_ctx,
+ conflict->local_abspath,
+ scratch_pool,
+ scratch_pool));
- /* Record the user's preference. */
- details->wc_move_target_idx = preferred_move_target_idx;
+ details = conflict->tree_conflict_local_details;
+ if (details == NULL || (details->wc_siblings == NULL &&
+ details->wc_move_targets == NULL))
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Setting a move target requires details "
+ "for tree conflict at '%s' to be fetched "
+ "from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
- /* Update option description. */
- SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description,
- conflict, ctx,
- details,
- conflict->pool,
+ if (details->wc_siblings)
+ {
+ if (preferred_move_target_idx < 0 ||
+ preferred_move_target_idx > details->wc_siblings->nelts)
+ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Index '%d' is out of bounds of the "
+ "possible move sibling list for '%s'"),
+ preferred_move_target_idx,
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+ /* Record the user's preference. */
+ details->preferred_sibling_idx = preferred_move_target_idx;
+
+ /* Update option description. */
+ preferred_sibling = APR_ARRAY_IDX(details->wc_siblings,
+ details->preferred_sibling_idx,
+ const char *);
+ option->description =
+ apr_psprintf(
+ conflict->pool, _("apply changes to '%s'"),
+ svn_dirent_local_style(
+ svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling),
+ scratch_pool));
+ }
+ else if (details->wc_move_targets)
+ {
+ apr_array_header_t *move_target_wc_abspaths;
+ move_target_wc_abspaths =
+ svn_hash_gets(details->wc_move_targets,
+ details->move_target_repos_relpath);
+
+ if (preferred_move_target_idx < 0 ||
+ preferred_move_target_idx > move_target_wc_abspaths->nelts)
+ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Index '%d' is out of bounds of the possible "
+ "move target list for '%s'"),
+ preferred_move_target_idx,
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ /* Record the user's preference. */
+ details->wc_move_target_idx = preferred_move_target_idx;
+
+ /* Update option description. */
+ SVN_ERR(conflict_tree_get_description_local_missing(
+ &option->description, conflict, ctx,
+ conflict->pool, scratch_pool));
+ }
+ }
+ else if ((operation == svn_wc_operation_update ||
+ operation == svn_wc_operation_switch) &&
+ incoming_change == svn_wc_conflict_action_delete &&
+ local_change == svn_wc_conflict_reason_moved_away)
+ {
+ struct conflict_tree_update_local_moved_away_details *details;
+
+ details = conflict->tree_conflict_local_details;
+ if (details == NULL || details->wc_move_targets == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Setting a move target requires details "
+ "for tree conflict at '%s' to be fetched "
+ "from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ if (preferred_move_target_idx < 0 ||
+ preferred_move_target_idx > details->wc_move_targets->nelts)
+ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Index '%d' is out of bounds of the "
+ "possible move target list for '%s'"),
+ preferred_move_target_idx,
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ /* Record the user's preference. */
+ details->preferred_move_target_idx = preferred_move_target_idx;
+
+ /* Update option description. */
+ if (id == svn_client_conflict_option_both_moved_file_merge)
+ SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge(
+ &option->description, conflict, ctx, conflict->pool,
+ scratch_pool));
+ else if (id == svn_client_conflict_option_both_moved_file_move_merge)
+ SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge(
+ &option->description, conflict, ctx, conflict->pool, scratch_pool));
+#if 0 /* ### TODO: Also handle options for directories! */
+ else if (id == svn_client_conflict_option_both_moved_dir_merge)
+ {
+ }
+ else if (id == svn_client_conflict_option_both_moved_dir_move_merge)
+ {
+ }
+#endif
+ else
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Unexpected option id '%d'"), id);
+ }
+ else
+ {
+ struct conflict_tree_incoming_delete_details *details;
+ apr_array_header_t *move_target_wc_abspaths;
+ const char *moved_to_abspath;
+
+ details = conflict->tree_conflict_incoming_details;
+ if (details == NULL || details->wc_move_targets == NULL)
+ return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL,
+ _("Setting a move target requires details "
+ "for tree conflict at '%s' to be fetched "
+ "from the repository first"),
+ svn_dirent_local_style(victim_abspath,
+ scratch_pool));
+
+ move_target_wc_abspaths =
+ svn_hash_gets(details->wc_move_targets,
+ get_moved_to_repos_relpath(details, scratch_pool));
+
+ if (preferred_move_target_idx < 0 ||
+ preferred_move_target_idx > move_target_wc_abspaths->nelts)
+ return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Index '%d' is out of bounds of the possible "
+ "move target list for '%s'"),
+ preferred_move_target_idx,
+ svn_dirent_local_style(victim_abspath,
scratch_pool));
+
+ /* Record the user's preference. */
+ details->wc_move_target_idx = preferred_move_target_idx;
+
+ /* Update option description. */
+ moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths,
+ details->wc_move_target_idx,
+ const char *);
+ SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description,
+ conflict, ctx,
+ moved_to_abspath,
+ conflict->pool,
+ scratch_pool));
+ }
return SVN_NO_ERROR;
}
svn_error_t *
+svn_client_conflict_option_set_moved_to_abspath(
+ svn_client_conflict_option_t *option,
+ int preferred_move_target_idx,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ /* The only difference to API version 2 is an assertion failure if
+ * an unexpected option is passed.
+ * We do not emulate this old behaviour since clients written against
+ * the previous API will just keep working. */
+ return svn_error_trace(
+ svn_client_conflict_option_set_moved_to_abspath2(option,
+ preferred_move_target_idx, ctx, scratch_pool));
+}
+
+svn_error_t *
svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options,
svn_client_conflict_t *conflict,
svn_client_ctx_t *ctx,
@@ -10349,8 +12631,15 @@ svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options,
scratch_pool));
SVN_ERR(configure_option_incoming_dir_merge(conflict, ctx, *options,
scratch_pool));
- SVN_ERR(configure_option_local_move_file_merge(conflict, ctx, *options,
+ SVN_ERR(configure_option_local_move_file_or_dir_merge(conflict, ctx,
+ *options,
+ scratch_pool));
+ SVN_ERR(configure_option_sibling_move_merge(conflict, ctx, *options,
+ scratch_pool));
+ SVN_ERR(configure_option_both_moved_file_merge(conflict, ctx, *options,
scratch_pool));
+ SVN_ERR(configure_option_both_moved_dir_merge(conflict, ctx, *options,
+ scratch_pool));
return SVN_NO_ERROR;
}
@@ -10443,7 +12732,7 @@ svn_client_conflict_get_recommended_option_id(svn_client_conflict_t *conflict)
{
return conflict->recommended_option_id;
}
-
+
svn_error_t *
svn_client_conflict_text_resolve(svn_client_conflict_t *conflict,
svn_client_conflict_option_t *option,
@@ -10466,7 +12755,7 @@ svn_client_conflict_option_find_by_id(apr_array_header_t *options,
{
svn_client_conflict_option_t *this_option;
svn_client_conflict_option_id_t this_option_id;
-
+
this_option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *);
this_option_id = svn_client_conflict_option_get_id(this_option);
@@ -10895,6 +13184,7 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict,
apr_pool_t *scratch_pool)
{
svn_boolean_t tree_conflicted;
+ svn_wc_operation_t operation;
svn_wc_conflict_action_t incoming_change;
svn_wc_conflict_reason_t local_change;
@@ -10911,6 +13201,7 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict,
conflict->tree_conflict_get_local_description_func =
conflict_tree_get_local_description_generic;
+ operation = svn_client_conflict_get_operation(conflict);
incoming_change = svn_client_conflict_get_incoming_change(conflict);
local_change = svn_client_conflict_get_local_change(conflict);
@@ -10945,6 +13236,12 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict,
conflict->tree_conflict_get_local_details_func =
conflict_tree_get_details_local_missing;
}
+ else if (local_change == svn_wc_conflict_reason_moved_away &&
+ operation == svn_wc_operation_update /* ### what about switch? */)
+ {
+ conflict->tree_conflict_get_local_details_func =
+ conflict_tree_get_details_update_local_moved_away;
+ }
return SVN_NO_ERROR;
}
@@ -11019,7 +13316,7 @@ tree_conflict_collector(void *baton,
{
const char *tc_abspath;
apr_pool_t *hash_pool;
-
+
hash_pool = apr_hash_pool_get(cswb->unresolved_tree_conflicts);
tc_abspath = apr_pstrdup(hash_pool, notify->path);
svn_hash_sets(cswb->unresolved_tree_conflicts, tc_abspath, "");
@@ -11027,7 +13324,7 @@ tree_conflict_collector(void *baton,
}
}
-/*
+/*
* Record a tree conflict resolution failure due to error condition ERR
* in the RESOLVE_LATER hash table. If the hash table is not available
* (meaning the caller does not wish to retry resolution later), or if
@@ -11190,7 +13487,7 @@ svn_client_conflict_walk(const char *local_abspath,
if (err)
break;
}
-
+
if (!err && !cswb.resolved_a_tree_conflict && tc_abspath &&
apr_hash_count(cswb.unresolved_tree_conflicts))
{
diff --git a/subversion/libsvn_client/copy.c b/subversion/libsvn_client/copy.c
index b2e3a44d797b..b3f2bacc7c8d 100644
--- a/subversion/libsvn_client/copy.c
+++ b/subversion/libsvn_client/copy.c
@@ -578,7 +578,7 @@ pin_externals_prop(svn_string_t **pinned_externals,
static svn_error_t *
resolve_pinned_externals(apr_hash_t **pinned_externals,
const apr_hash_t *externals_to_pin,
- svn_client__copy_pair_t *pair,
+ const svn_client__copy_pair_t *pair,
svn_ra_session_t *ra_session,
const char *repos_root_url,
svn_client_ctx_t *ctx,
@@ -1099,14 +1099,13 @@ verify_wc_dsts(const apr_array_header_t *copy_pairs,
return SVN_NO_ERROR;
}
+/* Verify that the WC sources in COPY_PAIRS exist, and set pair->src_kind
+ for each.
+ */
static svn_error_t *
-verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
- svn_boolean_t make_parents,
- svn_boolean_t is_move,
- svn_boolean_t metadata_only,
- svn_client_ctx_t *ctx,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
+verify_wc_srcs(const apr_array_header_t *copy_pairs,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
{
int i;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
@@ -1133,10 +1132,6 @@ verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
pair->src_abspath_or_url,
scratch_pool));
}
-
- SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
- result_pool, iterpool));
-
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
@@ -1163,10 +1158,6 @@ typedef struct path_driver_info_t
or move operation. */
struct path_driver_cb_baton
{
- /* The editor (and its state) used to perform the operation. */
- const svn_delta_editor_t *editor;
- void *edit_baton;
-
/* A hash of path -> path_driver_info_t *'s. */
apr_hash_t *action_hash;
@@ -1176,6 +1167,8 @@ struct path_driver_cb_baton
static svn_error_t *
path_driver_cb_func(void **dir_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
void *parent_baton,
void *callback_baton,
const char *path,
@@ -1196,9 +1189,9 @@ path_driver_cb_func(void **dir_baton,
/* Check to see if we need to add the path as a parent directory. */
if (path_info->dir_add)
{
- return cb_baton->editor->add_directory(path, parent_baton, NULL,
- SVN_INVALID_REVNUM, pool,
- dir_baton);
+ return editor->add_directory(path, parent_baton, NULL,
+ SVN_INVALID_REVNUM, pool,
+ dir_baton);
}
/* If this is a resurrection, we know the source and dest paths are
@@ -1230,8 +1223,8 @@ path_driver_cb_func(void **dir_baton,
if (do_delete)
{
- SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
- parent_baton, pool));
+ SVN_ERR(editor->delete_entry(path, SVN_INVALID_REVNUM,
+ parent_baton, pool));
}
if (do_add)
{
@@ -1240,40 +1233,40 @@ path_driver_cb_func(void **dir_baton,
if (path_info->src_kind == svn_node_file)
{
void *file_baton;
- SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
- path_info->src_url,
- path_info->src_revnum,
- pool, &file_baton));
+ SVN_ERR(editor->add_file(path, parent_baton,
+ path_info->src_url,
+ path_info->src_revnum,
+ pool, &file_baton));
if (path_info->mergeinfo)
- SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
- SVN_PROP_MERGEINFO,
- path_info->mergeinfo,
- pool));
- SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
+ SVN_ERR(editor->change_file_prop(file_baton,
+ SVN_PROP_MERGEINFO,
+ path_info->mergeinfo,
+ pool));
+ SVN_ERR(editor->close_file(file_baton, NULL, pool));
}
else
{
- SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
- path_info->src_url,
- path_info->src_revnum,
- pool, dir_baton));
+ SVN_ERR(editor->add_directory(path, parent_baton,
+ path_info->src_url,
+ path_info->src_revnum,
+ pool, dir_baton));
if (path_info->mergeinfo)
- SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
- SVN_PROP_MERGEINFO,
- path_info->mergeinfo,
- pool));
+ SVN_ERR(editor->change_dir_prop(*dir_baton,
+ SVN_PROP_MERGEINFO,
+ path_info->mergeinfo,
+ pool));
}
}
if (path_info->externals)
{
if (*dir_baton == NULL)
- SVN_ERR(cb_baton->editor->open_directory(path, parent_baton,
- SVN_INVALID_REVNUM,
- pool, dir_baton));
+ SVN_ERR(editor->open_directory(path, parent_baton,
+ SVN_INVALID_REVNUM,
+ pool, dir_baton));
- SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
- path_info->externals, pool));
+ SVN_ERR(editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
+ path_info->externals, pool));
}
return SVN_NO_ERROR;
@@ -1857,13 +1850,11 @@ repos_to_repos_copy(const apr_array_header_t *copy_pairs,
pool));
/* Setup the callback baton. */
- cb_baton.editor = editor;
- cb_baton.edit_baton = edit_baton;
cb_baton.action_hash = action_hash;
cb_baton.is_move = is_move;
/* Call the path-based editor driver. */
- err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
+ err = svn_delta_path_driver3(editor, edit_baton, paths, TRUE,
path_driver_cb_func, &cb_baton, pool);
if (err)
{
@@ -2318,9 +2309,15 @@ struct notification_adjust_baton
};
/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
- * baton is BATON->inner_baton) and adjusts the notification paths that
- * start with BATON->checkout_abspath to start instead with
- * BATON->final_abspath. */
+ * baton is BATON->inner_baton) to turn the result of a 'checkout' into
+ * what we want to see for a 'copy to WC' operation.
+ *
+ * - Adjust the notification paths that start with BATON->checkout_abspath
+ * to start instead with BATON->final_abspath.
+ * - Change start-of-update notification into a plain WC 'add' for the root.
+ * - Change checkout 'add' notifications into a plain WC 'add'.
+ * - Discard 'update_completed' notifications.
+ */
static void
notification_adjust_func(void *baton,
const svn_wc_notify_t *notify,
@@ -2333,18 +2330,372 @@ notification_adjust_func(void *baton,
relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
+ /* Convert 'update' notifications to plain 'add' notifications; discard
+ notifications about checkout/update starting/finishing. */
+ if (notify->action == svn_wc_notify_update_started /* root */
+ || notify->action == svn_wc_notify_update_add) /* non-root */
+ {
+ inner_notify->action = svn_wc_notify_add;
+ }
+ else if (notify->action == svn_wc_notify_update_update
+ || notify->action == svn_wc_notify_update_completed)
+ {
+ /* update_update happens only for a prop mod on root; the root was
+ already notified so discard this */
+ return;
+ }
+
if (nb->inner_func)
nb->inner_func(nb->inner_baton, inner_notify, pool);
}
+/** Copy a directory tree from a remote repository.
+ *
+ * Copy from RA_SESSION:LOCATION to WC_CTX:DST_ABSPATH.
+ *
+ * Create the directory DST_ABSPATH, if not present. Its parent should be
+ * already under version control in the WC and in a suitable state for
+ * scheduling the addition of a child.
+ *
+ * Ignore any incoming non-regular properties (entry-props, DAV/WC-props).
+ * Remove any incoming 'svn:mergeinfo' properties.
+ */
+static svn_error_t *
+copy_foreign_dir(svn_ra_session_t *ra_session,
+ const svn_client__pathrev_t *location,
+ const char *dst_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const svn_delta_editor_t *editor;
+ void *eb;
+ const svn_delta_editor_t *wrapped_editor;
+ void *wrapped_baton;
+ const svn_ra_reporter3_t *reporter;
+ void *reporter_baton;
+
+ /* Get a WC editor. It does not need an RA session because we will not
+ be sending it any 'copy from' requests, only 'add' requests. */
+ SVN_ERR(svn_client__wc_editor_internal(&editor, &eb,
+ dst_abspath,
+ TRUE /*root_dir_add*/,
+ TRUE /*ignore_mergeinfo_changes*/,
+ FALSE /*manage_wc_write_lock*/,
+ notify_func, notify_baton,
+ NULL /*ra_session*/,
+ ctx, scratch_pool));
+
+ SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
+ editor, eb,
+ &wrapped_editor, &wrapped_baton,
+ scratch_pool));
+
+ SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton,
+ location->rev, "", svn_depth_infinity,
+ FALSE, FALSE, wrapped_editor, wrapped_baton,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(reporter->set_path(reporter_baton, "", location->rev,
+ svn_depth_infinity /* irrelevant */,
+ TRUE /*start_empty*/,
+ NULL, scratch_pool));
+
+ SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implementation of svn_client__repos_to_wc_copy() for a dir.
+ */
+static svn_error_t *
+svn_client__repos_to_wc_copy_dir(svn_boolean_t *timestamp_sleep,
+ const char *src_url,
+ svn_revnum_t src_revnum,
+ const char *dst_abspath,
+ svn_boolean_t same_repositories,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *tmpdir_abspath, *tmp_abspath;
+
+ SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+ if (!same_repositories)
+ {
+ svn_client__pathrev_t *location;
+
+ *timestamp_sleep = TRUE;
+
+ /* ### Reparenting "ra_session" can't be right, can it? As this is
+ a foreign repo, surely we need a new RA session? */
+ SVN_ERR(svn_client__pathrev_create_with_session(&location, ra_session,
+ src_revnum, src_url,
+ scratch_pool));
+ SVN_ERR(svn_ra_reparent(ra_session, src_url, scratch_pool));
+ SVN_ERR(copy_foreign_dir(ra_session, location,
+ dst_abspath,
+ ctx->notify_func2, ctx->notify_baton2,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx, scratch_pool));
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Find a temporary location in which to check out the copy source. */
+ SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
+ scratch_pool, scratch_pool));
+
+ /* Get a temporary path. The crude way we do this is to create a
+ temporary file, remember its name, and let it be deleted immediately. */
+ SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
+ svn_io_file_del_on_close,
+ scratch_pool, scratch_pool));
+
+ /* Make a new checkout of the requested source. While doing so,
+ * resolve copy_src_revnum to an actual revision number in case it
+ * was until now 'invalid' meaning 'head'. Ask this function not to
+ * sleep for timestamps, by passing a sleep_needed output param.
+ * Send notifications for all nodes except the root node, and adjust
+ * them to refer to the destination rather than this temporary path. */
+ {
+ svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
+ void *old_notify_baton2 = ctx->notify_baton2;
+ struct notification_adjust_baton nb;
+ svn_error_t *err;
+ svn_opt_revision_t copy_src_revision;
+
+ copy_src_revision.kind = svn_opt_revision_number;
+ copy_src_revision.value.number = src_revnum;
+
+ nb.inner_func = ctx->notify_func2;
+ nb.inner_baton = ctx->notify_baton2;
+ nb.checkout_abspath = tmp_abspath;
+ nb.final_abspath = dst_abspath;
+ ctx->notify_func2 = notification_adjust_func;
+ ctx->notify_baton2 = &nb;
+
+ err = svn_client__checkout_internal(NULL /*result_rev*/, timestamp_sleep,
+ src_url,
+ tmp_abspath,
+ &copy_src_revision,
+ &copy_src_revision,
+ svn_depth_infinity,
+ TRUE /*ignore_externals*/,
+ FALSE, /* we don't allow obstructions */
+ ra_session, ctx, scratch_pool);
+
+ ctx->notify_func2 = old_notify_func2;
+ ctx->notify_baton2 = old_notify_baton2;
+
+ SVN_ERR(err);
+ }
+
+ /* Schedule dst_path for addition in parent, with copy history.
+ Don't send any notification here.
+ Then remove the temporary checkout's .svn dir in preparation for
+ moving the rest of it into the final destination. */
+ SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
+ TRUE /* metadata_only */,
+ NULL, NULL, /* don't allow user to cancel here */
+ NULL, NULL, scratch_pool));
+ SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
+ FALSE, scratch_pool, scratch_pool));
+ SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
+ tmp_abspath,
+ FALSE, FALSE,
+ NULL, NULL, /* don't cancel */
+ scratch_pool));
+
+ /* Move the temporary disk tree into place. */
+ SVN_ERR(svn_io_file_rename2(tmp_abspath, dst_abspath, FALSE, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Implementation of svn_client__repos_to_wc_copy() for a file.
+ *
+ * This has no 'ignore_externals' parameter because we don't support the
+ * 'svn:externals' property being set on a file.
+ */
+static svn_error_t *
+svn_client__repos_to_wc_copy_file(svn_boolean_t *timestamp_sleep,
+ const char *src_url,
+ svn_revnum_t src_rev,
+ const char *dst_abspath,
+ svn_boolean_t same_repositories,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *src_rel;
+ apr_hash_t *new_props;
+ svn_stream_t *new_base_contents = svn_stream_buffered(scratch_pool);
+
+ SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, src_url,
+ scratch_pool));
+ /* Fetch the file content. */
+ SVN_ERR(svn_ra_get_file(ra_session, src_rel, src_rev,
+ new_base_contents, NULL, &new_props,
+ scratch_pool));
+ if (!same_repositories)
+ svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
+
+ *timestamp_sleep = TRUE;
+ SVN_ERR(svn_wc_add_repos_file4(
+ ctx->wc_ctx, dst_abspath,
+ new_base_contents, NULL, new_props, NULL,
+ same_repositories ? src_url : NULL,
+ same_repositories ? src_rev : SVN_INVALID_REVNUM,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool));
+ /* Do our own notification for the root node, even if we could possibly
+ have delegated it. See also issue #2198. */
+ if (ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, scratch_pool);
+
+ notify->kind = svn_node_file;
+ ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Are RA_SESSION and the versioned *parent* dir of WC_TARGET_ABSPATH in
+ * the same repository?
+ */
+static svn_error_t *
+is_same_repository(svn_boolean_t *same_repository,
+ svn_ra_session_t *ra_session,
+ const char *wc_target_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *src_uuid, *dst_uuid;
+
+ /* Get the repository UUIDs of copy source URL and WC parent path */
+ SVN_ERR(svn_ra_get_uuid2(ra_session, &src_uuid, scratch_pool));
+ SVN_ERR(svn_client_get_repos_root(NULL /*root_url*/, &dst_uuid,
+ svn_dirent_dirname(wc_target_abspath,
+ scratch_pool),
+ ctx, scratch_pool, scratch_pool));
+ *same_repository = (strcmp(src_uuid, dst_uuid) == 0);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__repos_to_wc_copy_internal(svn_boolean_t *timestamp_sleep,
+ svn_node_kind_t kind,
+ const char *src_url,
+ svn_revnum_t src_rev,
+ const char *dst_abspath,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const char *old_session_url;
+ svn_boolean_t timestamp_sleep_ignored;
+ svn_boolean_t same_repositories;
+
+ SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
+ src_url, scratch_pool));
+
+ SVN_ERR(is_same_repository(&same_repositories,
+ ra_session, dst_abspath, ctx, scratch_pool));
+
+ if (!timestamp_sleep)
+ timestamp_sleep = &timestamp_sleep_ignored;
+
+ if (kind == svn_node_dir)
+ {
+ SVN_ERR(svn_client__repos_to_wc_copy_dir(timestamp_sleep,
+ src_url, src_rev,
+ dst_abspath,
+ same_repositories,
+ ra_session,
+ ctx, scratch_pool));
+ }
+ else if (kind == svn_node_file)
+ {
+ SVN_ERR(svn_client__repos_to_wc_copy_file(timestamp_sleep,
+ src_url, src_rev,
+ dst_abspath,
+ same_repositories,
+ ra_session,
+ ctx, scratch_pool));
+ }
+
+ /* Reparent the session back to the original URL. */
+ SVN_ERR(svn_ra_reparent(ra_session, old_session_url, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__repos_to_wc_copy_by_editor(svn_boolean_t *timestamp_sleep,
+ svn_node_kind_t kind,
+ const char *src_url,
+ svn_revnum_t src_rev,
+ const char *dst_abspath,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ const svn_delta_editor_t *editor;
+ void *eb;
+ const char *src_anchor = svn_uri_dirname(src_url, scratch_pool);
+ const char *dst_target = svn_dirent_basename(dst_abspath, scratch_pool);
+ void *rb, *db;
+
+ SVN_ERR(svn_ra_reparent(ra_session, src_anchor, scratch_pool));
+
+ SVN_ERR(svn_client__wc_editor_internal(
+ &editor, &eb,
+ svn_dirent_dirname(dst_abspath, scratch_pool),
+ FALSE /*root_dir_add*/,
+ FALSE /*ignore_mergeinfo_changes*/,
+ FALSE /*manage_wc_write_lock*/,
+ ctx->notify_func2, ctx->notify_baton2,
+ ra_session,
+ ctx, scratch_pool));
+
+ SVN_ERR(editor->open_root(eb, SVN_INVALID_REVNUM, scratch_pool, &rb));
+ if (kind == svn_node_dir)
+ {
+ SVN_ERR(editor->add_directory(dst_target, rb,
+ src_url, src_rev,
+ scratch_pool,
+ &db));
+ SVN_ERR(editor->close_directory(db, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(editor->add_file(dst_target, rb,
+ src_url, src_rev,
+ scratch_pool,
+ &db));
+ SVN_ERR(editor->close_file(db, NULL, scratch_pool));
+ }
+ SVN_ERR(editor->close_edit(eb, scratch_pool));
+
+ if (timestamp_sleep)
+ *timestamp_sleep = TRUE;
+ return SVN_NO_ERROR;
+}
+
/* Peform each individual copy operation for a repos -> wc copy. A
helper for repos_to_wc_copy().
- Resolve PAIR->src_revnum to a real revision number if it isn't already. */
+ PAIR->src_revnum PAIR->src_abspath_or_url should already have been
+ resolved to the operative revision number and operative URL.
+ */
static svn_error_t *
repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
- svn_client__copy_pair_t *pair,
- svn_boolean_t same_repositories,
+ const svn_client__copy_pair_t *pair,
svn_boolean_t ignore_externals,
svn_boolean_t pin_externals,
const apr_hash_t *externals_to_pin,
@@ -2354,9 +2705,14 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
{
apr_hash_t *src_mergeinfo;
const char *dst_abspath = pair->dst_abspath_or_url;
+ svn_boolean_t same_repositories;
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(pair->src_revnum));
+ SVN_ERR_ASSERT(svn_path_is_url(pair->src_abspath_or_url));
SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+ SVN_ERR(is_same_repository(&same_repositories,
+ ra_session, dst_abspath, ctx, pool));
if (!same_repositories && ctx->notify_func2)
{
svn_wc_notify_t *notify;
@@ -2372,135 +2728,59 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
}
- if (pair->src_kind == svn_node_dir)
+ SVN_ERR(svn_client__repos_to_wc_copy_by_editor(
+ timestamp_sleep,
+ pair->src_kind,
+ pair->src_abspath_or_url,
+ pair->src_revnum,
+ dst_abspath,
+ ra_session, ctx, pool));
+
+ /* Fetch externals, pinning them if requested */
+ if (!ignore_externals && pair->src_kind == svn_node_dir)
{
if (same_repositories)
{
- const char *tmpdir_abspath, *tmp_abspath;
-
- /* Find a temporary location in which to check out the copy source. */
- SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
- pool, pool));
-
- SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
- svn_io_file_del_on_close, pool, pool));
-
- /* Make a new checkout of the requested source. While doing so,
- * resolve pair->src_revnum to an actual revision number in case it
- * was until now 'invalid' meaning 'head'. Ask this function not to
- * sleep for timestamps, by passing a sleep_needed output param.
- * Send notifications for all nodes except the root node, and adjust
- * them to refer to the destination rather than this temporary path. */
- {
- svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
- void *old_notify_baton2 = ctx->notify_baton2;
- struct notification_adjust_baton nb;
- svn_error_t *err;
-
- nb.inner_func = ctx->notify_func2;
- nb.inner_baton = ctx->notify_baton2;
- nb.checkout_abspath = tmp_abspath;
- nb.final_abspath = dst_abspath;
- ctx->notify_func2 = notification_adjust_func;
- ctx->notify_baton2 = &nb;
-
- /* Avoid a chicken-and-egg problem:
- * If pinning externals we'll need to adjust externals
- * properties before checking out any externals.
- * But copy needs to happen before pinning because else there
- * are no svn:externals properties to pin. */
- if (pin_externals)
- ignore_externals = TRUE;
-
- err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep,
- pair->src_original,
- tmp_abspath,
- &pair->src_peg_revision,
- &pair->src_op_revision,
- svn_depth_infinity,
- ignore_externals, FALSE,
- ra_session, ctx, pool);
-
- ctx->notify_func2 = old_notify_func2;
- ctx->notify_baton2 = old_notify_baton2;
-
- SVN_ERR(err);
- }
-
- *timestamp_sleep = TRUE;
-
- /* Schedule dst_path for addition in parent, with copy history.
- Don't send any notification here.
- Then remove the temporary checkout's .svn dir in preparation for
- moving the rest of it into the final destination. */
- SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
- TRUE /* metadata_only */,
- ctx->cancel_func, ctx->cancel_baton,
- NULL, NULL, pool));
- SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
- FALSE, pool, pool));
- SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
- tmp_abspath,
- FALSE, FALSE,
- ctx->cancel_func,
- ctx->cancel_baton,
- pool));
-
- /* Move the temporary disk tree into place. */
- SVN_ERR(svn_io_file_rename2(tmp_abspath, dst_abspath, FALSE, pool));
- }
- else
- {
- *timestamp_sleep = TRUE;
-
- SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
- dst_abspath,
- &pair->src_peg_revision,
- &pair->src_op_revision,
- svn_depth_infinity,
- FALSE /* make_parents */,
- TRUE /* already_locked */,
- ctx, pool));
-
- return SVN_NO_ERROR;
- }
-
- if (pin_externals)
- {
- apr_hash_t *pinned_externals;
- apr_hash_index_t *hi;
- apr_pool_t *iterpool;
const char *repos_root_url;
apr_hash_t *new_externals;
apr_hash_t *new_depths;
SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
- SVN_ERR(resolve_pinned_externals(&pinned_externals,
- externals_to_pin, pair,
- ra_session, repos_root_url,
- ctx, pool, pool));
- iterpool = svn_pool_create(pool);
- for (hi = apr_hash_first(pool, pinned_externals);
- hi;
- hi = apr_hash_next(hi))
+ if (pin_externals)
{
- const char *dst_relpath = apr_hash_this_key(hi);
- svn_string_t *externals_propval = apr_hash_this_val(hi);
- const char *local_abspath;
+ apr_hash_t *pinned_externals;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(resolve_pinned_externals(&pinned_externals,
+ externals_to_pin, pair,
+ ra_session, repos_root_url,
+ ctx, pool, pool));
+
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, pinned_externals);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const char *dst_relpath = apr_hash_this_key(hi);
+ svn_string_t *externals_propval = apr_hash_this_val(hi);
+ const char *local_abspath;
- svn_pool_clear(iterpool);
+ svn_pool_clear(iterpool);
- local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
- dst_relpath, iterpool);
- /* ### use a work queue? */
- SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
- SVN_PROP_EXTERNALS, externals_propval,
- svn_depth_empty, TRUE /* skip_checks */,
- NULL /* changelist_filter */,
- ctx->cancel_func, ctx->cancel_baton,
- NULL, NULL, /* no extra notification */
- iterpool));
+ local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
+ dst_relpath, iterpool);
+ /* ### use a work queue? */
+ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
+ SVN_PROP_EXTERNALS, externals_propval,
+ svn_depth_empty, TRUE /* skip_checks */,
+ NULL /* changelist_filter */,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL, /* no extra notification */
+ iterpool));
+ }
+ svn_pool_destroy(iterpool);
}
/* Now update all externals in the newly created copy. */
@@ -2509,65 +2789,30 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
ctx->wc_ctx,
dst_abspath,
svn_depth_infinity,
- iterpool, iterpool));
+ pool, pool));
SVN_ERR(svn_client__handle_externals(new_externals,
new_depths,
repos_root_url, dst_abspath,
svn_depth_infinity,
timestamp_sleep,
ra_session,
- ctx, iterpool));
- svn_pool_destroy(iterpool);
+ ctx, pool));
}
- } /* end directory case */
-
- else if (pair->src_kind == svn_node_file)
- {
- apr_hash_t *new_props;
- const char *src_rel;
- svn_stream_t *new_base_contents = svn_stream_buffered(pool);
-
- SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
- pair->src_abspath_or_url,
- pool));
- /* Fetch the file content. While doing so, resolve pair->src_revnum
- * to an actual revision number if it's 'invalid' meaning 'head'. */
- SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
- new_base_contents,
- &pair->src_revnum, &new_props, pool));
-
- if (new_props && ! same_repositories)
- svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
-
- *timestamp_sleep = TRUE;
-
- SVN_ERR(svn_wc_add_repos_file4(
- ctx->wc_ctx, dst_abspath,
- new_base_contents, NULL, new_props, NULL,
- same_repositories ? pair->src_abspath_or_url : NULL,
- same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
- ctx->cancel_func, ctx->cancel_baton,
- pool));
}
- /* Record the implied mergeinfo (before the notification callback
- is invoked for the root node). */
- SVN_ERR(svn_client__get_repos_mergeinfo(
- &src_mergeinfo, ra_session,
- pair->src_abspath_or_url, pair->src_revnum,
- svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
- SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
-
- /* Do our own notification for the root node, even if we could possibly
- have delegated it. See also issue #1552.
-
- ### Maybe this notification should mention the mergeinfo change. */
- if (ctx->notify_func2)
+ if (same_repositories)
{
- svn_wc_notify_t *notify = svn_wc_create_notify(
- dst_abspath, svn_wc_notify_add, pool);
- notify->kind = pair->src_kind;
- ctx->notify_func2(ctx->notify_baton2, notify, pool);
+ /* Record the implied mergeinfo. */
+ SVN_ERR(svn_client__get_repos_mergeinfo(&src_mergeinfo, ra_session,
+ pair->src_abspath_or_url,
+ pair->src_revnum,
+ svn_mergeinfo_inherited,
+ TRUE /*squelch_incapable*/,
+ pool));
+ SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
+
+ /* ### Maybe the notification should mention this mergeinfo change. */
+ /* ### Maybe we should do this during rather than after the copy. */
}
return SVN_NO_ERROR;
@@ -2585,38 +2830,8 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
apr_pool_t *scratch_pool)
{
int i;
- svn_boolean_t same_repositories;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- /* We've already checked for physical obstruction by a working file.
- But there could also be logical obstruction by an entry whose
- working file happens to be missing.*/
- SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
- ctx, scratch_pool, iterpool));
-
- /* Decide whether the two repositories are the same or not. */
- {
- const char *parent_abspath;
- const char *src_uuid, *dst_uuid;
-
- /* Get the repository uuid of SRC_URL */
- SVN_ERR(svn_ra_get_uuid2(ra_session, &src_uuid, iterpool));
-
- /* Get repository uuid of dst's parent directory, since dst may
- not exist. ### TODO: we should probably walk up the wc here,
- in case the parent dir has an imaginary URL. */
- if (copy_pairs->nelts == 1)
- parent_abspath = svn_dirent_dirname(top_dst_abspath, scratch_pool);
- else
- parent_abspath = top_dst_abspath;
-
- SVN_ERR(svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
- parent_abspath, ctx,
- iterpool, iterpool));
- /* ### Also check repos_root_url? */
- same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
- }
-
/* Perform the move for each of the copy_pairs. */
for (i = 0; i < copy_pairs->nelts; i++)
{
@@ -2629,7 +2844,6 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
APR_ARRAY_IDX(copy_pairs, i,
svn_client__copy_pair_t *),
- same_repositories,
ignore_externals,
pin_externals, externals_to_pin,
ra_session, ctx, iterpool));
@@ -2642,7 +2856,6 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
static svn_error_t *
repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
const apr_array_header_t *copy_pairs,
- svn_boolean_t make_parents,
svn_boolean_t ignore_externals,
svn_boolean_t pin_externals,
const apr_hash_t *externals_to_pin,
@@ -2696,8 +2909,6 @@ repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
{
svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
svn_client__copy_pair_t *);
- svn_node_kind_t dst_parent_kind, dst_kind;
- const char *dst_parent;
const char *src_rel;
svn_pool_clear(iterpool);
@@ -2721,33 +2932,6 @@ repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
_("Path '%s' not found in head revision"),
pair->src_abspath_or_url);
}
-
- /* Figure out about dst. */
- SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
- iterpool));
- if (dst_kind != svn_node_none)
- {
- return svn_error_createf(
- SVN_ERR_ENTRY_EXISTS, NULL,
- _("Path '%s' already exists"),
- svn_dirent_local_style(pair->dst_abspath_or_url, pool));
- }
-
- /* Make sure the destination parent is a directory and produce a clear
- error message if it is not. */
- dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
- SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
- if (make_parents && dst_parent_kind == svn_node_none)
- {
- SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
- iterpool));
- }
- else if (dst_parent_kind != svn_node_dir)
- {
- return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
- _("Path '%s' is not a directory"),
- svn_dirent_local_style(dst_parent, pool));
- }
}
svn_pool_destroy(iterpool);
@@ -3070,8 +3254,9 @@ try_copy(svn_boolean_t *timestamp_sleep,
/* Now, call the right handler for the operation. */
if ((! srcs_are_urls) && (! dst_is_url))
{
- SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
- metadata_only, ctx, pool, pool));
+ SVN_ERR(verify_wc_srcs(copy_pairs, ctx, pool));
+ SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only,
+ ctx, pool, pool));
/* Copy or move all targets. */
if (is_move)
@@ -3101,9 +3286,13 @@ try_copy(svn_boolean_t *timestamp_sleep,
}
else if ((srcs_are_urls) && (! dst_is_url))
{
+ SVN_ERR(verify_wc_dsts(copy_pairs, make_parents,
+ FALSE, FALSE /* metadata_only */,
+ ctx, pool, pool));
+
return svn_error_trace(
repos_to_wc_copy(timestamp_sleep,
- copy_pairs, make_parents, ignore_externals,
+ copy_pairs, ignore_externals,
pin_externals, externals_to_pin, ctx, pool));
}
else
diff --git a/subversion/libsvn_client/copy_foreign.c b/subversion/libsvn_client/copy_foreign.c
deleted file mode 100644
index cfe6aea05846..000000000000
--- a/subversion/libsvn_client/copy_foreign.c
+++ /dev/null
@@ -1,575 +0,0 @@
-/*
- * copy_foreign.c: copy from other repository support.
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- */
-
-/* ==================================================================== */
-
-/*** Includes. ***/
-
-#include <string.h>
-#include "svn_hash.h"
-#include "svn_client.h"
-#include "svn_delta.h"
-#include "svn_dirent_uri.h"
-#include "svn_error.h"
-#include "svn_error_codes.h"
-#include "svn_path.h"
-#include "svn_pools.h"
-#include "svn_props.h"
-#include "svn_ra.h"
-#include "svn_wc.h"
-
-#include <apr_md5.h>
-
-#include "client.h"
-#include "private/svn_subr_private.h"
-#include "private/svn_wc_private.h"
-#include "svn_private_config.h"
-
-struct edit_baton_t
-{
- apr_pool_t *pool;
- const char *anchor_abspath;
-
- svn_wc_context_t *wc_ctx;
- svn_wc_notify_func2_t notify_func;
- void *notify_baton;
-};
-
-struct dir_baton_t
-{
- apr_pool_t *pool;
-
- struct dir_baton_t *pb;
- struct edit_baton_t *eb;
-
- const char *local_abspath;
-
- svn_boolean_t created;
- apr_hash_t *properties;
-
- int users;
-};
-
-/* svn_delta_editor_t function */
-static svn_error_t *
-edit_open(void *edit_baton,
- svn_revnum_t base_revision,
- apr_pool_t *result_pool,
- void **root_baton)
-{
- struct edit_baton_t *eb = edit_baton;
- apr_pool_t *dir_pool = svn_pool_create(eb->pool);
- struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
-
- db->pool = dir_pool;
- db->eb = eb;
- db->users = 1;
- db->local_abspath = eb->anchor_abspath;
-
- SVN_ERR(svn_io_make_dir_recursively(eb->anchor_abspath, dir_pool));
-
- *root_baton = db;
-
- return SVN_NO_ERROR;
-}
-
-/* svn_delta_editor_t function */
-static svn_error_t *
-edit_close(void *edit_baton,
- apr_pool_t *scratch_pool)
-{
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-dir_add(const char *path,
- void *parent_baton,
- const char *copyfrom_path,
- svn_revnum_t copyfrom_revision,
- apr_pool_t *result_pool,
- void **child_baton)
-{
- struct dir_baton_t *pb = parent_baton;
- struct edit_baton_t *eb = pb->eb;
- apr_pool_t *dir_pool = svn_pool_create(pb->pool);
- struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
- svn_boolean_t under_root;
-
- pb->users++;
-
- db->pb = pb;
- db->eb = pb->eb;
- db->pool = dir_pool;
- db->users = 1;
-
- SVN_ERR(svn_dirent_is_under_root(&under_root, &db->local_abspath,
- eb->anchor_abspath, path, db->pool));
- if (! under_root)
- {
- return svn_error_createf(
- SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
- _("Path '%s' is not in the working copy"),
- svn_dirent_local_style(path, db->pool));
- }
-
- SVN_ERR(svn_io_make_dir_recursively(db->local_abspath, db->pool));
-
- *child_baton = db;
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-dir_change_prop(void *dir_baton,
- const char *name,
- const svn_string_t *value,
- apr_pool_t *scratch_pool)
-{
- struct dir_baton_t *db = dir_baton;
- struct edit_baton_t *eb = db->eb;
- svn_prop_kind_t prop_kind;
-
- prop_kind = svn_property_kind2(name);
-
- if (prop_kind != svn_prop_regular_kind
- || ! strcmp(name, SVN_PROP_MERGEINFO))
- {
- /* We can't handle DAV, ENTRY and merge specific props here */
- return SVN_NO_ERROR;
- }
-
- if (! db->created)
- {
- /* We can still store them in the hash for immediate addition
- with the svn_wc_add_from_disk3() call */
- if (! db->properties)
- db->properties = apr_hash_make(db->pool);
-
- if (value != NULL)
- svn_hash_sets(db->properties, apr_pstrdup(db->pool, name),
- svn_string_dup(value, db->pool));
- }
- else
- {
- /* We have already notified for this directory, so don't do that again */
- SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value,
- svn_depth_empty, FALSE, NULL,
- NULL, NULL, /* Cancellation */
- NULL, NULL, /* Notification */
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Releases the directory baton if there are no more users */
-static svn_error_t *
-maybe_done(struct dir_baton_t *db)
-{
- db->users--;
-
- if (db->users == 0)
- {
- struct dir_baton_t *pb = db->pb;
-
- svn_pool_clear(db->pool);
-
- if (pb)
- SVN_ERR(maybe_done(pb));
- }
-
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-ensure_added(struct dir_baton_t *db,
- apr_pool_t *scratch_pool)
-{
- if (db->created)
- return SVN_NO_ERROR;
-
- if (db->pb)
- SVN_ERR(ensure_added(db->pb, scratch_pool));
-
- db->created = TRUE;
-
- /* Add the directory with all the already collected properties */
- SVN_ERR(svn_wc_add_from_disk3(db->eb->wc_ctx,
- db->local_abspath,
- db->properties,
- TRUE /* skip checks */,
- db->eb->notify_func,
- db->eb->notify_baton,
- scratch_pool));
-
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-dir_close(void *dir_baton,
- apr_pool_t *scratch_pool)
-{
- struct dir_baton_t *db = dir_baton;
- /*struct edit_baton_t *eb = db->eb;*/
-
- SVN_ERR(ensure_added(db, scratch_pool));
-
- SVN_ERR(maybe_done(db));
-
- return SVN_NO_ERROR;
-}
-
-struct file_baton_t
-{
- apr_pool_t *pool;
-
- struct dir_baton_t *pb;
- struct edit_baton_t *eb;
-
- const char *local_abspath;
- apr_hash_t *properties;
-
- svn_boolean_t writing;
- unsigned char digest[APR_MD5_DIGESTSIZE];
-
- const char *tmp_path;
-};
-
-static svn_error_t *
-file_add(const char *path,
- void *parent_baton,
- const char *copyfrom_path,
- svn_revnum_t copyfrom_revision,
- apr_pool_t *result_pool,
- void **file_baton)
-{
- struct dir_baton_t *pb = parent_baton;
- struct edit_baton_t *eb = pb->eb;
- apr_pool_t *file_pool = svn_pool_create(pb->pool);
- struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
- svn_boolean_t under_root;
-
- pb->users++;
-
- fb->pool = file_pool;
- fb->eb = eb;
- fb->pb = pb;
-
- SVN_ERR(svn_dirent_is_under_root(&under_root, &fb->local_abspath,
- eb->anchor_abspath, path, fb->pool));
- if (! under_root)
- {
- return svn_error_createf(
- SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
- _("Path '%s' is not in the working copy"),
- svn_dirent_local_style(path, fb->pool));
- }
-
- *file_baton = fb;
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-file_change_prop(void *file_baton,
- const char *name,
- const svn_string_t *value,
- apr_pool_t *scratch_pool)
-{
- struct file_baton_t *fb = file_baton;
- svn_prop_kind_t prop_kind;
-
- prop_kind = svn_property_kind2(name);
-
- if (prop_kind != svn_prop_regular_kind
- || ! strcmp(name, SVN_PROP_MERGEINFO))
- {
- /* We can't handle DAV, ENTRY and merge specific props here */
- return SVN_NO_ERROR;
- }
-
- /* We store all properties in the hash for immediate addition
- with the svn_wc_add_from_disk3() call */
- if (! fb->properties)
- fb->properties = apr_hash_make(fb->pool);
-
- if (value != NULL)
- svn_hash_sets(fb->properties, apr_pstrdup(fb->pool, name),
- svn_string_dup(value, fb->pool));
-
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-file_textdelta(void *file_baton,
- const char *base_checksum,
- apr_pool_t *result_pool,
- svn_txdelta_window_handler_t *handler,
- void **handler_baton)
-{
- struct file_baton_t *fb = file_baton;
- svn_stream_t *target;
-
- SVN_ERR_ASSERT(! fb->writing);
-
- SVN_ERR(svn_stream_open_writable(&target, fb->local_abspath, fb->pool,
- fb->pool));
-
- fb->writing = TRUE;
- svn_txdelta_apply(svn_stream_empty(fb->pool) /* source */,
- target,
- fb->digest,
- fb->local_abspath,
- fb->pool,
- /* Provide the handler directly */
- handler, handler_baton);
-
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-file_close(void *file_baton,
- const char *text_checksum,
- apr_pool_t *scratch_pool)
-{
- struct file_baton_t *fb = file_baton;
- struct edit_baton_t *eb = fb->eb;
- struct dir_baton_t *pb = fb->pb;
-
- SVN_ERR(ensure_added(pb, fb->pool));
-
- if (text_checksum)
- {
- svn_checksum_t *expected_checksum;
- svn_checksum_t *actual_checksum;
-
- SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
- text_checksum, fb->pool));
- actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool);
-
- if (! svn_checksum_match(expected_checksum, actual_checksum))
- return svn_error_trace(
- svn_checksum_mismatch_err(expected_checksum,
- actual_checksum,
- fb->pool,
- _("Checksum mismatch for '%s'"),
- svn_dirent_local_style(
- fb->local_abspath,
- fb->pool)));
- }
-
- SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, fb->local_abspath, fb->properties,
- TRUE /* skip checks */,
- eb->notify_func, eb->notify_baton,
- fb->pool));
-
- svn_pool_destroy(fb->pool);
- SVN_ERR(maybe_done(pb));
-
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-copy_foreign_dir(svn_ra_session_t *ra_session,
- svn_client__pathrev_t *location,
- svn_wc_context_t *wc_ctx,
- const char *dst_abspath,
- svn_depth_t depth,
- svn_wc_notify_func2_t notify_func,
- void *notify_baton,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *scratch_pool)
-{
- struct edit_baton_t eb;
- svn_delta_editor_t *editor = svn_delta_default_editor(scratch_pool);
- const svn_delta_editor_t *wrapped_editor;
- void *wrapped_baton;
- const svn_ra_reporter3_t *reporter;
- void *reporter_baton;
-
- eb.pool = scratch_pool;
- eb.anchor_abspath = dst_abspath;
-
- eb.wc_ctx = wc_ctx;
- eb.notify_func = notify_func;
- eb.notify_baton = notify_baton;
-
- editor->open_root = edit_open;
- editor->close_edit = edit_close;
-
- editor->add_directory = dir_add;
- editor->change_dir_prop = dir_change_prop;
- editor->close_directory = dir_close;
-
- editor->add_file = file_add;
- editor->change_file_prop = file_change_prop;
- editor->apply_textdelta = file_textdelta;
- editor->close_file = file_close;
-
- SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
- editor, &eb,
- &wrapped_editor, &wrapped_baton,
- scratch_pool));
-
- SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton,
- location->rev, "", svn_depth_infinity,
- FALSE, FALSE, wrapped_editor, wrapped_baton,
- scratch_pool, scratch_pool));
-
- SVN_ERR(reporter->set_path(reporter_baton, "", location->rev, depth,
- TRUE /* incomplete */,
- NULL, scratch_pool));
-
- SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
-
- return SVN_NO_ERROR;
-}
-
-
-svn_error_t *
-svn_client__copy_foreign(const char *url,
- const char *dst_abspath,
- svn_opt_revision_t *peg_revision,
- svn_opt_revision_t *revision,
- svn_depth_t depth,
- svn_boolean_t make_parents,
- svn_boolean_t already_locked,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
-{
- svn_ra_session_t *ra_session;
- svn_client__pathrev_t *loc;
- svn_node_kind_t kind;
- svn_node_kind_t wc_kind;
- const char *dir_abspath;
-
- SVN_ERR_ASSERT(svn_path_is_url(url));
- SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
-
- /* Do we need to validate/update revisions? */
-
- SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
- url, NULL,
- peg_revision,
- revision, ctx,
- scratch_pool));
-
- SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, scratch_pool));
-
- if (kind != svn_node_file && kind != svn_node_dir)
- return svn_error_createf(
- SVN_ERR_ILLEGAL_TARGET, NULL,
- _("'%s' is not a valid location inside a repository"),
- url);
-
- SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dst_abspath, FALSE, TRUE,
- scratch_pool));
-
- if (wc_kind != svn_node_none)
- {
- return svn_error_createf(
- SVN_ERR_ENTRY_EXISTS, NULL,
- _("'%s' is already under version control"),
- svn_dirent_local_style(dst_abspath, scratch_pool));
- }
-
- dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool);
- SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath,
- FALSE, FALSE, scratch_pool));
-
- if (wc_kind == svn_node_none)
- {
- if (make_parents)
- SVN_ERR(svn_client__make_local_parents(dir_abspath, make_parents, ctx,
- scratch_pool));
-
- SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath,
- FALSE, FALSE, scratch_pool));
- }
-
- if (wc_kind != svn_node_dir)
- return svn_error_createf(
- SVN_ERR_ENTRY_NOT_FOUND, NULL,
- _("Can't add '%s', because no parent directory is found"),
- svn_dirent_local_style(dst_abspath, scratch_pool));
-
-
- if (kind == svn_node_file)
- {
- svn_stream_t *target;
- apr_hash_t *props;
- apr_hash_index_t *hi;
- SVN_ERR(svn_stream_open_writable(&target, dst_abspath, scratch_pool,
- scratch_pool));
-
- SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, target, NULL, &props,
- scratch_pool));
-
- if (props != NULL)
- for (hi = apr_hash_first(scratch_pool, props); hi;
- hi = apr_hash_next(hi))
- {
- const char *name = apr_hash_this_key(hi);
-
- if (svn_property_kind2(name) != svn_prop_regular_kind
- || ! strcmp(name, SVN_PROP_MERGEINFO))
- {
- /* We can't handle DAV, ENTRY and merge specific props here */
- svn_hash_sets(props, name, NULL);
- }
- }
-
- if (!already_locked)
- SVN_WC__CALL_WITH_WRITE_LOCK(
- svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props,
- TRUE /* skip checks */,
- ctx->notify_func2, ctx->notify_baton2,
- scratch_pool),
- ctx->wc_ctx, dir_abspath, FALSE, scratch_pool);
- else
- SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props,
- TRUE /* skip checks */,
- ctx->notify_func2, ctx->notify_baton2,
- scratch_pool));
- }
- else
- {
- if (!already_locked)
- SVN_WC__CALL_WITH_WRITE_LOCK(
- copy_foreign_dir(ra_session, loc,
- ctx->wc_ctx, dst_abspath,
- depth,
- ctx->notify_func2, ctx->notify_baton2,
- ctx->cancel_func, ctx->cancel_baton,
- scratch_pool),
- ctx->wc_ctx, dir_abspath, FALSE, scratch_pool);
- else
- SVN_ERR(copy_foreign_dir(ra_session, loc,
- ctx->wc_ctx, dst_abspath,
- depth,
- ctx->notify_func2, ctx->notify_baton2,
- ctx->cancel_func, ctx->cancel_baton,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
diff --git a/subversion/libsvn_client/delete.c b/subversion/libsvn_client/delete.c
index 943cdd9a4496..29a3395af2ea 100644
--- a/subversion/libsvn_client/delete.c
+++ b/subversion/libsvn_client/delete.c
@@ -181,12 +181,13 @@ can_delete_node(svn_boolean_t *target_missing,
static svn_error_t *
path_driver_cb_func(void **dir_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
void *parent_baton,
void *callback_baton,
const char *path,
apr_pool_t *pool)
{
- const svn_delta_editor_t *editor = callback_baton;
*dir_baton = NULL;
return editor->delete_entry(path, SVN_INVALID_REVNUM, parent_baton, pool);
}
@@ -248,8 +249,8 @@ single_repos_delete(svn_ra_session_t *ra_session,
pool));
/* Call the path-based editor driver. */
- err = svn_delta_path_driver2(editor, edit_baton, relpaths, TRUE,
- path_driver_cb_func, (void *)editor, pool);
+ err = svn_delta_path_driver3(editor, edit_baton, relpaths, TRUE,
+ path_driver_cb_func, NULL, pool);
if (err)
{
diff --git a/subversion/libsvn_client/deprecated.c b/subversion/libsvn_client/deprecated.c
index dc20b2772286..b8e202609e74 100644
--- a/subversion/libsvn_client/deprecated.c
+++ b/subversion/libsvn_client/deprecated.c
@@ -166,6 +166,61 @@ svn_client_mkdir(svn_client_commit_info_t **commit_info_p,
}
/*** From blame.c ***/
+struct blame_receiver_wrapper_baton3 {
+ void *baton;
+ svn_client_blame_receiver3_t receiver;
+ svn_revnum_t start_revnum;
+ svn_revnum_t end_revnum;
+};
+
+static svn_error_t *
+blame_wrapper_receiver3(void *baton,
+ apr_int64_t line_no,
+ svn_revnum_t revision,
+ apr_hash_t *rev_props,
+ svn_revnum_t merged_revision,
+ apr_hash_t *merged_rev_props,
+ const char *merged_path,
+ const svn_string_t *line,
+ svn_boolean_t local_change,
+ apr_pool_t *pool)
+{
+ struct blame_receiver_wrapper_baton3 *brwb = baton;
+
+ if (brwb->receiver)
+ return brwb->receiver(brwb->baton, brwb->start_revnum, brwb->end_revnum,
+ line_no,
+ revision, rev_props, merged_revision,
+ merged_rev_props, merged_path, line->data,
+ local_change, pool);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client_blame5(const char *target,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start,
+ const svn_opt_revision_t *end,
+ const svn_diff_file_options_t *diff_options,
+ svn_boolean_t ignore_mime_type,
+ svn_boolean_t include_merged_revisions,
+ svn_client_blame_receiver3_t receiver,
+ void *receiver_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct blame_receiver_wrapper_baton3 baton;
+
+ baton.receiver = receiver;
+ baton.baton = receiver_baton;
+
+ return svn_client_blame6(&baton.start_revnum, &baton.end_revnum,
+ target, peg_revision, start, end,
+ diff_options,
+ ignore_mime_type, include_merged_revisions,
+ blame_wrapper_receiver3, &baton, ctx, pool);
+}
struct blame_receiver_wrapper_baton2 {
void *baton;
@@ -936,6 +991,42 @@ svn_client_delete(svn_client_commit_info_t **commit_info_p,
/*** From diff.c ***/
svn_error_t *
+svn_client_diff6(const apr_array_header_t *diff_options,
+ const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_added,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t ignore_properties,
+ svn_boolean_t properties_only,
+ svn_boolean_t use_git_diff_format,
+ const char *header_encoding,
+ svn_stream_t *outstream,
+ svn_stream_t *errstream,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff7(diff_options,
+ path_or_url1, revision1,
+ path_or_url2, revision2,
+ relative_to_dir, depth,
+ ignore_ancestry, no_diff_added,
+ no_diff_deleted, show_copies_as_adds,
+ ignore_content_type, ignore_properties,
+ properties_only, use_git_diff_format,
+ TRUE /*pretty_print_mergeinfo*/,
+ header_encoding,
+ outstream, errstream, changelists, ctx, pool);
+}
+
+svn_error_t *
svn_client_diff5(const apr_array_header_t *diff_options,
const char *path1,
const svn_opt_revision_t *revision1,
@@ -1058,6 +1149,53 @@ svn_client_diff(const apr_array_header_t *options,
}
svn_error_t *
+svn_client_diff_peg6(const apr_array_header_t *options,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *start_revision,
+ const svn_opt_revision_t *end_revision,
+ const char *relative_to_dir,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t no_diff_added,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t ignore_properties,
+ svn_boolean_t properties_only,
+ svn_boolean_t use_git_diff_format,
+ const char *header_encoding,
+ svn_stream_t *outstream,
+ svn_stream_t *errstream,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return svn_client_diff_peg7(options,
+ path_or_url,
+ peg_revision,
+ start_revision,
+ end_revision,
+ relative_to_dir,
+ depth,
+ ignore_ancestry,
+ no_diff_added,
+ no_diff_deleted,
+ show_copies_as_adds,
+ ignore_content_type,
+ ignore_properties,
+ properties_only,
+ use_git_diff_format,
+ TRUE /*pretty_print_mergeinfo*/,
+ header_encoding,
+ outstream,
+ errstream,
+ changelists,
+ ctx,
+ pool);
+}
+
+svn_error_t *
svn_client_diff_peg5(const apr_array_header_t *diff_options,
const char *path,
const svn_opt_revision_t *peg_revision,
@@ -1643,7 +1781,7 @@ svn_client_log(const apr_array_header_t *targets,
* we just invoke the receiver manually on a hand-constructed log
* message for revision 0.
*
- * See also http://subversion.tigris.org/issues/show_bug.cgi?id=692.
+ * See also https://issues.apache.org/jira/browse/SVN-692.
*/
if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
&& (start->kind == svn_opt_revision_head)
@@ -2853,6 +2991,22 @@ svn_client_resolved(const char *path,
}
/*** From revert.c ***/
svn_error_t *
+svn_client_revert3(const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_boolean_t clear_changelists,
+ svn_boolean_t metadata_only,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_client_revert4(paths, depth, changelists,
+ clear_changelists, metadata_only,
+ TRUE /*added_keep_local*/,
+ ctx, pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
svn_client_revert2(const apr_array_header_t *paths,
svn_depth_t depth,
const apr_array_header_t *changelists,
diff --git a/subversion/libsvn_client/diff.c b/subversion/libsvn_client/diff.c
index ba6e91eb2e7b..f019294f05c0 100644
--- a/subversion/libsvn_client/diff.c
+++ b/subversion/libsvn_client/diff.c
@@ -23,6 +23,9 @@
/* ==================================================================== */
+/* We define this here to remove any further warnings about the usage of
+ experimental functions in this file. */
+#define SVN_EXPERIMENTAL
/*** Includes. ***/
@@ -48,6 +51,7 @@
#include "svn_subst.h"
#include "client.h"
+#include "private/svn_client_shelf.h"
#include "private/svn_wc_private.h"
#include "private/svn_diff_private.h"
#include "private/svn_subr_private.h"
@@ -66,6 +70,31 @@
_("Path '%s' must be an immediate child of " \
"the directory '%s'"), path, relative_to_dir)
+/* State provided by the diff drivers; used by the diff writer */
+typedef struct diff_driver_info_t
+{
+ /* The anchor to prefix before wc paths */
+ const char *anchor;
+
+ /* Relative path of ra session from repos_root_url.
+
+ Used only in printing git diff headers. The repository-root-relative
+ path of ... ### what user-visible property of the diff? */
+ const char *session_relpath;
+
+ /* Used only in printing git diff headers. Used to find the
+ repository-root-relative path of a WC path. */
+ svn_wc_context_t *wc_ctx;
+
+ /* The original targets passed to the diff command. We may need
+ these to construct distinctive diff labels when comparing the
+ same relative path in the same revision, under different anchors
+ (for example, when comparing a trunk against a branch). */
+ const char *orig_path_1;
+ const char *orig_path_2;
+} diff_driver_info_t;
+
+
/* Calculate the repository relative path of DIFF_RELPATH, using
* SESSION_RELPATH and WC_CTX, and return the result in *REPOS_RELPATH.
* ORIG_TARGET is the related original target passed to the diff command,
@@ -120,25 +149,47 @@ make_repos_relpath(const char **repos_relpath,
return SVN_NO_ERROR;
}
-/* Adjust *INDEX_PATH, *ORIG_PATH_1 and *ORIG_PATH_2, representing the changed
- * node and the two original targets passed to the diff command, to handle the
- * case when we're dealing with different anchors. RELATIVE_TO_DIR is the
- * directory the diff target should be considered relative to.
- * ANCHOR is the local path where the diff editor is anchored. The resulting
- * values are allocated in RESULT_POOL and temporary allocations are performed
- * in SCRATCH_POOL. */
+/* Adjust paths to handle the case when we're dealing with different anchors.
+ *
+ * Set *INDEX_PATH to the new relative path. Set *LABEL_PATH1 and
+ * *LABEL_PATH2 to that path annotated with the unique parts of ORIG_PATH_1
+ * and ORIG_PATH_2 respectively, like this:
+ *
+ * INDEX_PATH: "path"
+ * LABEL_PATH1: "path\t(.../branches/branch1)"
+ * LABEL_PATH2: "path\t(.../trunk)"
+ *
+ * Make the output paths relative to RELATIVE_TO_DIR (if not null) by
+ * removing it from the beginning of (ANCHOR + RELPATH).
+ *
+ * ANCHOR (if not null) is the local path where the diff editor is anchored.
+ * RELPATH is the path to the changed node within the diff editor, so
+ * relative to ANCHOR.
+ *
+ * RELATIVE_TO_DIR and ANCHOR are of the same form -- either absolute local
+ * paths or relative paths relative to the same base.
+ *
+ * ORIG_PATH_1 and ORIG_PATH_2 represent the two original target paths or
+ * URLs passed to the diff command.
+ *
+ * Allocate results in RESULT_POOL (or as a pointer to RELPATH) and
+ * temporary data in SCRATCH_POOL.
+ */
static svn_error_t *
adjust_paths_for_diff_labels(const char **index_path,
- const char **orig_path_1,
- const char **orig_path_2,
+ const char **label_path1,
+ const char **label_path2,
const char *relative_to_dir,
const char *anchor,
+ const char *relpath,
+ const char *orig_path_1,
+ const char *orig_path_2,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- const char *new_path = *index_path;
- const char *new_path1 = *orig_path_1;
- const char *new_path2 = *orig_path_2;
+ const char *new_path = relpath;
+ const char *new_path1 = orig_path_1;
+ const char *new_path2 = orig_path_2;
if (anchor)
new_path = svn_dirent_join(anchor, new_path, result_pool);
@@ -177,6 +228,7 @@ adjust_paths_for_diff_labels(const char **index_path,
/* ### BH: We can now just construct the repos_relpath, etc. as the
anchor is available. See also make_repos_relpath() */
+ /* Remove the common prefix of NEW_PATH1 and NEW_PATH2. */
is_url1 = svn_path_is_url(new_path1);
is_url2 = svn_path_is_url(new_path2);
@@ -220,8 +272,8 @@ adjust_paths_for_diff_labels(const char **index_path,
new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2);
*index_path = new_path;
- *orig_path_1 = new_path1;
- *orig_path_2 = new_path2;
+ *label_path1 = new_path1;
+ *label_path2 = new_path2;
return SVN_NO_ERROR;
}
@@ -383,28 +435,33 @@ maybe_print_mode_change(svn_stream_t *os,
}
/* Print a git diff header showing the OPERATION to the stream OS using
- * HEADER_ENCODING. Return suitable diff labels for the git diff in *LABEL1
- * and *LABEL2. REPOS_RELPATH1 and REPOS_RELPATH2 are relative to reposroot.
- * are the paths passed to the original diff command. REV1 and REV2 are
- * revisions being diffed. COPYFROM_PATH and COPYFROM_REV indicate where the
+ * HEADER_ENCODING.
+ *
+ * Return suitable diff labels for the git diff in *LABEL1 and *LABEL2.
+ *
+ * REV1 and REV2 are the revisions being diffed.
+ * COPYFROM_PATH and COPYFROM_REV indicate where the
* diffed item was copied from.
* Use SCRATCH_POOL for temporary allocations. */
static svn_error_t *
print_git_diff_header(svn_stream_t *os,
const char **label1, const char **label2,
svn_diff_operation_kind_t operation,
- const char *repos_relpath1,
- const char *repos_relpath2,
svn_revnum_t rev1,
svn_revnum_t rev2,
+ const char *diff_relpath,
const char *copyfrom_path,
svn_revnum_t copyfrom_rev,
apr_hash_t *left_props,
apr_hash_t *right_props,
const char *git_index_shas,
const char *header_encoding,
+ const diff_driver_info_t *ddi,
apr_pool_t *scratch_pool)
{
+ const char *repos_relpath1;
+ const char *repos_relpath2;
+ const char *copyfrom_repos_relpath = NULL;
svn_boolean_t exec_bit1 = (svn_prop_get_value(left_props,
SVN_PROP_EXECUTABLE) != NULL);
svn_boolean_t exec_bit2 = (svn_prop_get_value(right_props,
@@ -414,6 +471,26 @@ print_git_diff_header(svn_stream_t *os,
svn_boolean_t symlink_bit2 = (svn_prop_get_value(right_props,
SVN_PROP_SPECIAL) != NULL);
+ SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
+ ddi->orig_path_1,
+ ddi->session_relpath,
+ ddi->wc_ctx,
+ ddi->anchor,
+ scratch_pool, scratch_pool));
+ SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
+ ddi->orig_path_2,
+ ddi->session_relpath,
+ ddi->wc_ctx,
+ ddi->anchor,
+ scratch_pool, scratch_pool));
+ if (copyfrom_path)
+ SVN_ERR(make_repos_relpath(&copyfrom_repos_relpath, copyfrom_path,
+ ddi->orig_path_2,
+ ddi->session_relpath,
+ ddi->wc_ctx,
+ ddi->anchor,
+ scratch_pool, scratch_pool));
+
if (operation == svn_diff_op_deleted)
{
SVN_ERR(print_git_diff_header_deleted(os, header_encoding,
@@ -487,26 +564,45 @@ print_git_diff_header(svn_stream_t *os,
return SVN_NO_ERROR;
}
+/* Print the "Index:" and "=====" lines.
+ * Show the paths in platform-independent format ('/' separators)
+ */
+static svn_error_t *
+print_diff_index_header(svn_stream_t *outstream,
+ const char *header_encoding,
+ const char *index_path,
+ const char *suffix,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ header_encoding, scratch_pool,
+ "Index: %s%s" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path, suffix));
+ return SVN_NO_ERROR;
+}
+
/* A helper func that writes out verbal descriptions of property diffs
to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was
- passed to svn_client_diff6(), which is probably stdout.
+ passed to svn_client_diff7(), which is probably stdout.
### FIXME needs proper docstring
If USE_GIT_DIFF_FORMAT is TRUE, pring git diff headers, which always
- show paths relative to the repository root. RA_SESSION and WC_CTX are
- needed to normalize paths relative the repository root, and are ignored
- if USE_GIT_DIFF_FORMAT is FALSE.
-
- ANCHOR is the local path where the diff editor is anchored. */
+ show paths relative to the repository root. DDI->session_relpath and
+ DDI->wc_ctx are needed to normalize paths relative the repository root,
+ and are ignored if USE_GIT_DIFF_FORMAT is FALSE.
+
+ If @a pretty_print_mergeinfo is true, then describe 'svn:mergeinfo'
+ property changes in a human-readable form that says what changes were
+ merged or reverse merged; otherwise (or if the mergeinfo property values
+ don't parse correctly) display them just like any other property.
+ */
static svn_error_t *
display_prop_diffs(const apr_array_header_t *propchanges,
apr_hash_t *left_props,
apr_hash_t *right_props,
const char *diff_relpath,
- const char *anchor,
- const char *orig_path1,
- const char *orig_path2,
svn_revnum_t rev1,
svn_revnum_t rev2,
const char *encoding,
@@ -514,32 +610,29 @@ display_prop_diffs(const apr_array_header_t *propchanges,
const char *relative_to_dir,
svn_boolean_t show_diff_header,
svn_boolean_t use_git_diff_format,
- const char *ra_session_relpath,
+ svn_boolean_t pretty_print_mergeinfo,
+ const diff_driver_info_t *ddi,
svn_cancel_func_t cancel_func,
void *cancel_baton,
- svn_wc_context_t *wc_ctx,
apr_pool_t *scratch_pool)
{
const char *repos_relpath1 = NULL;
- const char *repos_relpath2 = NULL;
- const char *index_path = diff_relpath;
- const char *adjusted_path1 = orig_path1;
- const char *adjusted_path2 = orig_path2;
+ const char *index_path;
+ const char *label_path1, *label_path2;
if (use_git_diff_format)
{
- SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, orig_path1,
- ra_session_relpath, wc_ctx, anchor,
- scratch_pool, scratch_pool));
- SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, orig_path2,
- ra_session_relpath, wc_ctx, anchor,
+ SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, ddi->orig_path_1,
+ ddi->session_relpath, ddi->wc_ctx, ddi->anchor,
scratch_pool, scratch_pool));
}
/* If we're creating a diff on the wc root, path would be empty. */
- SVN_ERR(adjust_paths_for_diff_labels(&index_path, &adjusted_path1,
- &adjusted_path2,
- relative_to_dir, anchor,
+ SVN_ERR(adjust_paths_for_diff_labels(&index_path,
+ &label_path1, &label_path2,
+ relative_to_dir, ddi->anchor,
+ diff_relpath,
+ ddi->orig_path_1, ddi->orig_path_2,
scratch_pool, scratch_pool));
if (show_diff_header)
@@ -547,27 +640,21 @@ display_prop_diffs(const apr_array_header_t *propchanges,
const char *label1;
const char *label2;
- label1 = diff_label(adjusted_path1, rev1, scratch_pool);
- label2 = diff_label(adjusted_path2, rev2, scratch_pool);
-
- /* ### Should we show the paths in platform specific format,
- * ### diff_content_changed() does not! */
+ label1 = diff_label(label_path1, rev1, scratch_pool);
+ label2 = diff_label(label_path2, rev2, scratch_pool);
- SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
- "Index: %s" APR_EOL_STR
- SVN_DIFF__EQUAL_STRING APR_EOL_STR,
- index_path));
+ SVN_ERR(print_diff_index_header(outstream, encoding,
+ index_path, "", scratch_pool));
if (use_git_diff_format)
SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
svn_diff_op_modified,
- repos_relpath1, repos_relpath2,
- rev1, rev2, NULL,
- SVN_INVALID_REVNUM,
- left_props,
- right_props,
+ rev1, rev2,
+ diff_relpath,
+ NULL, SVN_INVALID_REVNUM,
+ left_props, right_props,
NULL,
- encoding, scratch_pool));
+ encoding, ddi, scratch_pool));
/* --- label1
* +++ label2 */
@@ -588,7 +675,7 @@ display_prop_diffs(const apr_array_header_t *propchanges,
SVN_ERR(svn_diff__display_prop_diffs(
outstream, encoding, propchanges, left_props,
- TRUE /* pretty_print_mergeinfo */,
+ pretty_print_mergeinfo,
-1 /* context_size */,
cancel_func, cancel_baton, scratch_pool));
@@ -599,24 +686,6 @@ display_prop_diffs(const apr_array_header_t *propchanges,
/*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
-/* State provided by the diff drivers; used by the diff writer */
-typedef struct diff_driver_info_t
-{
- /* The anchor to prefix before wc paths */
- const char *anchor;
-
- /* Relative path of ra session from repos_root_url */
- const char *session_relpath;
-
- /* The original targets passed to the diff command. We may need
- these to construct distinctive diff labels when comparing the
- same relative path in the same revision, under different anchors
- (for example, when comparing a trunk against a branch). */
- const char *orig_path_1;
- const char *orig_path_2;
-} diff_driver_info_t;
-
-
/* Diff writer state */
typedef struct diff_writer_info_t
{
@@ -668,11 +737,12 @@ typedef struct diff_writer_info_t
/* Whether to ignore copyfrom information when showing adds */
svn_boolean_t show_copies_as_adds;
+ /* Whether to show mergeinfo prop changes in human-readable form */
+ svn_boolean_t pretty_print_mergeinfo;
+
/* Empty files for creating diffs or NULL if not used yet */
const char *empty_file;
- svn_wc_context_t *wc_ctx;
-
svn_cancel_func_t cancel_func;
void *cancel_baton;
@@ -708,9 +778,6 @@ diff_props_changed(const char *diff_relpath,
* dir_props_changed(). */
SVN_ERR(display_prop_diffs(props, left_props, right_props,
diff_relpath,
- dwi->ddi.anchor,
- dwi->ddi.orig_path_1,
- dwi->ddi.orig_path_2,
rev1,
rev2,
dwi->header_encoding,
@@ -718,10 +785,10 @@ diff_props_changed(const char *diff_relpath,
dwi->relative_to_dir,
show_diff_header,
dwi->use_git_diff_format,
- dwi->ddi.session_relpath,
+ dwi->pretty_print_mergeinfo,
+ &dwi->ddi,
dwi->cancel_func,
dwi->cancel_baton,
- dwi->wc_ctx,
scratch_pool));
}
@@ -785,9 +852,12 @@ transform_link_to_git(const char **new_tmpfile,
}
/* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and
- REV2 are used in the headers to indicate the file and revisions. If either
- MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
- but instead print a warning message.
+ REV2 are used in the headers to indicate the file and revisions.
+
+ If either side has an svn:mime-type property that indicates 'binary'
+ content, then if DWI->force_binary is set, attempt to produce the
+ diff in the usual way, otherwise produce a 'GIT binary diff' in git mode
+ or print a warning message in non-git mode.
If FORCE_DIFF is TRUE, always write a diff, even for empty diffs.
@@ -812,9 +882,8 @@ diff_content_changed(svn_boolean_t *wrote_header,
svn_stream_t *outstream = dwi->outstream;
const char *label1, *label2;
svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
- const char *index_path = diff_relpath;
- const char *path1 = dwi->ddi.orig_path_1;
- const char *path2 = dwi->ddi.orig_path_2;
+ const char *index_path;
+ const char *label_path1, *label_path2;
const char *mimetype1 = svn_prop_get_value(left_props, SVN_PROP_MIME_TYPE);
const char *mimetype2 = svn_prop_get_value(right_props, SVN_PROP_MIME_TYPE);
const char *index_shas = NULL;
@@ -824,12 +893,15 @@ diff_content_changed(svn_boolean_t *wrote_header,
return SVN_NO_ERROR;
/* Generate the diff headers. */
- SVN_ERR(adjust_paths_for_diff_labels(&index_path, &path1, &path2,
+ SVN_ERR(adjust_paths_for_diff_labels(&index_path,
+ &label_path1, &label_path2,
rel_to_dir, dwi->ddi.anchor,
+ diff_relpath,
+ dwi->ddi.orig_path_1, dwi->ddi.orig_path_2,
scratch_pool, scratch_pool));
- label1 = diff_label(path1, rev1, scratch_pool);
- label2 = diff_label(path2, rev2, scratch_pool);
+ label1 = diff_label(label_path1, rev1, scratch_pool);
+ label2 = diff_label(label_path2, rev2, scratch_pool);
/* Possible easy-out: if either mime-type is binary and force was not
specified, don't attempt to generate a viewable diff at all.
@@ -869,12 +941,8 @@ diff_content_changed(svn_boolean_t *wrote_header,
if (! dwi->force_binary && (mt1_binary || mt2_binary))
{
/* Print out the diff header. */
- SVN_ERR(svn_stream_printf_from_utf8(outstream,
- dwi->header_encoding, scratch_pool,
- "Index: %s" APR_EOL_STR
- SVN_DIFF__EQUAL_STRING APR_EOL_STR,
- index_path));
-
+ SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding,
+ index_path, "", scratch_pool));
*wrote_header = TRUE;
/* ### Print git diff headers. */
@@ -883,40 +951,17 @@ diff_content_changed(svn_boolean_t *wrote_header,
{
svn_stream_t *left_stream;
svn_stream_t *right_stream;
- const char *repos_relpath1;
- const char *repos_relpath2;
- const char *copyfrom_repos_relpath = NULL;
-
- SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
- dwi->ddi.orig_path_1,
- dwi->ddi.session_relpath,
- dwi->wc_ctx,
- dwi->ddi.anchor,
- scratch_pool, scratch_pool));
- SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
- dwi->ddi.orig_path_2,
- dwi->ddi.session_relpath,
- dwi->wc_ctx,
- dwi->ddi.anchor,
- scratch_pool, scratch_pool));
- if (copyfrom_path)
- SVN_ERR(make_repos_relpath(&copyfrom_repos_relpath, copyfrom_path,
- dwi->ddi.orig_path_2,
- dwi->ddi.session_relpath,
- dwi->wc_ctx,
- dwi->ddi.anchor,
- scratch_pool, scratch_pool));
- SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
+
+ SVN_ERR(print_git_diff_header(outstream,
+ &label1, &label2,
operation,
- repos_relpath1, repos_relpath2,
rev1, rev2,
- copyfrom_repos_relpath,
- copyfrom_rev,
- left_props,
- right_props,
+ diff_relpath,
+ copyfrom_path, copyfrom_rev,
+ left_props, right_props,
index_shas,
dwi->header_encoding,
- scratch_pool));
+ &dwi->ddi, scratch_pool));
SVN_ERR(svn_stream_open_readonly(&left_stream, tmpfile1,
scratch_pool, scratch_pool));
@@ -973,11 +1018,9 @@ diff_content_changed(svn_boolean_t *wrote_header,
int exitcode;
/* Print out the diff header. */
- SVN_ERR(svn_stream_printf_from_utf8(outstream,
- dwi->header_encoding, scratch_pool,
- "Index: %s" APR_EOL_STR
- SVN_DIFF__EQUAL_STRING APR_EOL_STR,
- index_path));
+ SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding,
+ index_path, "", scratch_pool));
+ *wrote_header = TRUE;
/* ### Do we want to add git diff headers here too? I'd say no. The
* ### 'Index' and '===' line is something subversion has added. The rest
@@ -1030,10 +1073,6 @@ diff_content_changed(svn_boolean_t *wrote_header,
scratch_pool),
NULL, NULL, scratch_pool));
}
-
- /* If we have printed a diff for this path, mark it as visited. */
- if (exitcode == 1)
- *wrote_header = TRUE;
}
else /* use libsvn_diff to generate the diff */
{
@@ -1048,49 +1087,22 @@ diff_content_changed(svn_boolean_t *wrote_header,
|| svn_diff_contains_diffs(diff))
{
/* Print out the diff header. */
- SVN_ERR(svn_stream_printf_from_utf8(outstream,
- dwi->header_encoding, scratch_pool,
- "Index: %s" APR_EOL_STR
- SVN_DIFF__EQUAL_STRING APR_EOL_STR,
- index_path));
+ SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding,
+ index_path, "", scratch_pool));
+ *wrote_header = TRUE;
if (dwi->use_git_diff_format)
{
- const char *repos_relpath1;
- const char *repos_relpath2;
- const char *copyfrom_repos_relpath = NULL;
-
- SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
- dwi->ddi.orig_path_1,
- dwi->ddi.session_relpath,
- dwi->wc_ctx,
- dwi->ddi.anchor,
- scratch_pool, scratch_pool));
- SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
- dwi->ddi.orig_path_2,
- dwi->ddi.session_relpath,
- dwi->wc_ctx,
- dwi->ddi.anchor,
- scratch_pool, scratch_pool));
- if (copyfrom_path)
- SVN_ERR(make_repos_relpath(&copyfrom_repos_relpath,
- copyfrom_path,
- dwi->ddi.orig_path_2,
- dwi->ddi.session_relpath,
- dwi->wc_ctx,
- dwi->ddi.anchor,
- scratch_pool, scratch_pool));
- SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
+ SVN_ERR(print_git_diff_header(outstream,
+ &label1, &label2,
operation,
- repos_relpath1, repos_relpath2,
rev1, rev2,
- copyfrom_repos_relpath,
- copyfrom_rev,
- left_props,
- right_props,
+ diff_relpath,
+ copyfrom_path, copyfrom_rev,
+ left_props, right_props,
index_shas,
dwi->header_encoding,
- scratch_pool));
+ &dwi->ddi, scratch_pool));
}
/* Output the actual diff */
@@ -1102,10 +1114,6 @@ diff_content_changed(svn_boolean_t *wrote_header,
dwi->options.for_internal->context_size,
dwi->cancel_func, dwi->cancel_baton,
scratch_pool));
-
- /* If we have printed a diff for this path, mark it as visited. */
- if (dwi->use_git_diff_format || svn_diff_contains_diffs(diff))
- *wrote_header = TRUE;
}
}
@@ -1180,11 +1188,9 @@ diff_file_added(const char *relpath,
index_path = svn_dirent_join(dwi->ddi.anchor, relpath,
scratch_pool);
- SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream,
- dwi->header_encoding, scratch_pool,
- "Index: %s (added)" APR_EOL_STR
- SVN_DIFF__EQUAL_STRING APR_EOL_STR,
- index_path));
+ SVN_ERR(print_diff_index_header(dwi->outstream, dwi->header_encoding,
+ index_path, " (added)",
+ scratch_pool));
wrote_header = TRUE;
return SVN_NO_ERROR;
}
@@ -1270,11 +1276,9 @@ diff_file_deleted(const char *relpath,
index_path = svn_dirent_join(dwi->ddi.anchor, relpath,
scratch_pool);
- SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream,
- dwi->header_encoding, scratch_pool,
- "Index: %s (deleted)" APR_EOL_STR
- SVN_DIFF__EQUAL_STRING APR_EOL_STR,
- index_path));
+ SVN_ERR(print_diff_index_header(dwi->outstream, dwi->header_encoding,
+ index_path, " (deleted)",
+ scratch_pool));
}
else
{
@@ -1537,11 +1541,22 @@ check_diff_target_exists(const char *url,
/** Prepare a repos repos diff between PATH_OR_URL1 and
* PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2.
- * Return URLs and peg revisions in *URL1, *REV1 and in *URL2, *REV2.
- * Return suitable anchors in *ANCHOR1 and *ANCHOR2, and targets in
- * *TARGET1 and *TARGET2, based on *URL1 and *URL2.
- * Indicate the corresponding node kinds in *KIND1 and *KIND2, and verify
+ *
+ * Return the resolved URL and peg revision pairs in *URL1, *REV1 and in
+ * *URL2, *REV2.
+ *
+ * Return suitable anchor URL and target pairs in *ANCHOR1, *TARGET1 and
+ * in *ANCHOR2, *TARGET2, corresponding to *URL1 and *URL2.
+ *
+ * (The choice of anchor URLs here appears to be: start with *URL1, *URL2;
+ * then take the parent dir on both sides, unless either of *URL1 or *URL2
+ * is the repository root or the parent dir of *URL1 is unreadable.)
+ *
+ * Set *KIND1 and *KIND2 to the node kinds of *URL1 and *URL2, and verify
* that at least one of the diff targets exists.
+ *
+ * Set *RA_SESSION to an RA session parented at the URL *ANCHOR1.
+ *
* Use client context CTX. Do all allocations in POOL. */
static svn_error_t *
diff_prepare_repos_repos(const char **url1,
@@ -1766,8 +1781,8 @@ diff_prepare_repos_repos(const char **url1,
/* A Theoretical Note From Ben, regarding do_diff().
- This function is really svn_client_diff6(). If you read the public
- API description for svn_client_diff6(), it sounds quite Grand. It
+ This function is really svn_client_diff7(). If you read the public
+ API description for svn_client_diff7(), it sounds quite Grand. It
sounds really generalized and abstract and beautiful: that it will
diff any two paths, be they working-copy paths or URLs, at any two
revisions.
@@ -1791,7 +1806,7 @@ diff_prepare_repos_repos(const char **url1,
pigeonholed into one of these use-cases, we currently bail with a
friendly apology.
- Perhaps someday a brave soul will truly make svn_client_diff6()
+ Perhaps someday a brave soul will truly make svn_client_diff7()
perfectly general. For now, we live with the 90% case. Certainly,
the commandline client only calls this function in legal ways.
When there are other users of svn_client.h, maybe this will become
@@ -1804,7 +1819,7 @@ static svn_error_t *
unsupported_diff_error(svn_error_t *child_err)
{
return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
- _("Sorry, svn_client_diff6 was called in a way "
+ _("Sorry, svn_client_diff7 was called in a way "
"that is not yet supported"));
}
@@ -1813,12 +1828,14 @@ unsupported_diff_error(svn_error_t *child_err)
PATH1 and PATH2 are both working copy paths. REVISION1 and
REVISION2 are their respective revisions.
- All other options are the same as those passed to svn_client_diff6(). */
+ For now, require PATH1=PATH2, REVISION1='base', REVISION2='working',
+ otherwise return an error.
+
+ Anchor DIFF_PROCESSOR at the requested diff targets.
+
+ All other options are the same as those passed to svn_client_diff7(). */
static svn_error_t *
-diff_wc_wc(const char **root_relpath,
- svn_boolean_t *root_is_dir,
- struct diff_driver_info_t *ddi,
- const char *path1,
+diff_wc_wc(const char *path1,
const svn_opt_revision_t *revision1,
const char *path2,
const svn_opt_revision_t *revision2,
@@ -1844,24 +1861,12 @@ diff_wc_wc(const char **root_relpath,
&& (revision2->kind == svn_opt_revision_working))))
return unsupported_diff_error(
svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
- _("Only diffs between a path's text-base "
- "and its working files are supported at this time"
+ _("A non-URL diff at this time must be either from "
+ "a path's base to the same path's working version "
+ "or between the working versions of two paths"
)));
- if (ddi)
- {
- svn_node_kind_t kind;
-
- SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
- TRUE, FALSE, scratch_pool));
-
- if (kind != svn_node_dir)
- ddi->anchor = svn_dirent_dirname(path1, scratch_pool);
- else
- ddi->anchor = path1;
- }
-
- SVN_ERR(svn_wc__diff7(root_relpath, root_is_dir,
+ SVN_ERR(svn_wc__diff7(TRUE,
ctx->wc_ctx, abspath1, depth,
ignore_ancestry, changelists,
diff_processor,
@@ -1878,11 +1883,31 @@ diff_wc_wc(const char **root_relpath,
and the actual two paths compared are determined by following copy
history from PATH_OR_URL2.
- All other options are the same as those passed to svn_client_diff6(). */
+ If DDI is null, anchor the DIFF_PROCESSOR at the requested diff
+ targets. (This case is used by diff-summarize.)
+
+ If DDI is non-null: Set DDI->orig_path_* to the two diff target URLs as
+ resolved at the given revisions; set DDI->anchor to an anchor WC path
+ if either of PATH_OR_URL* is given as a WC path, else to null; set
+ DDI->session_relpath to the repository-relpath of the anchor URL for
+ DDI->orig_path_1. Anchor the DIFF_PROCESSOR at the anchor chosen
+ for the underlying diff implementation if the target on either side
+ is a file, else at the actual requested targets.
+
+ (The choice of WC anchor implementated here for DDI->anchor appears to
+ be: choose PATH_OR_URL2 (if it's a WC path) or else PATH_OR_URL1 (if
+ it's a WC path); then take its parent dir unless both resolved URLs
+ refer to directories.)
+
+ (For the choice of URL anchor for DDI->session_relpath, see
+ diff_prepare_repos_repos().)
+
+ ### Bizarre anchoring. TODO: always anchor DIFF_PROCESSOR at the
+ requested targets.
+
+ All other options are the same as those passed to svn_client_diff7(). */
static svn_error_t *
-diff_repos_repos(const char **root_relpath,
- svn_boolean_t *root_is_dir,
- struct diff_driver_info_t *ddi,
+diff_repos_repos(struct diff_driver_info_t *ddi,
const char *path_or_url1,
const char *path_or_url2,
const svn_opt_revision_t *revision1,
@@ -1974,14 +1999,16 @@ diff_repos_repos(const char **root_relpath,
target1 = str_tmp;
diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
- NULL,
scratch_pool);
}
/* Filter the first path component using a filter processor, until we fixed
the diff processing to handle this directly */
- if (root_relpath)
- *root_relpath = apr_pstrdup(result_pool, target1);
+ if (!ddi)
+ {
+ diff_processor = svn_diff__tree_processor_filter_create(
+ diff_processor, target1, scratch_pool);
+ }
else if ((kind1 != svn_node_file && kind2 != svn_node_file)
&& target1[0] != '\0')
{
@@ -2048,11 +2075,17 @@ diff_repos_repos(const char **root_relpath,
If REVERSE is TRUE, the diff will be reported in reverse.
- All other options are the same as those passed to svn_client_diff6(). */
+ If DDI is null, anchor the DIFF_PROCESSOR at the requested diff
+ targets. (This case is used by diff-summarize.)
+
+ If DDI is non-null: Set DDI->orig_path_* to the URLs of the two diff
+ targets as resolved at the given revisions; set DDI->anchor to a WC path
+ anchor for PATH2; set DDI->session_relpath to the repository-relpath of
+ the URL of that same anchor WC path.
+
+ All other options are the same as those passed to svn_client_diff7(). */
static svn_error_t *
-diff_repos_wc(const char **root_relpath,
- svn_boolean_t *root_is_dir,
- struct diff_driver_info_t *ddi,
+diff_repos_wc(struct diff_driver_info_t *ddi,
const char *path_or_url1,
const svn_opt_revision_t *revision1,
const svn_opt_revision_t *peg_revision1,
@@ -2130,11 +2163,6 @@ diff_repos_wc(const char **root_relpath,
target = "";
}
- if (root_relpath)
- *root_relpath = apr_pstrdup(result_pool, target);
- if (root_is_dir)
- *root_is_dir = (*target == '\0');
-
/* Fetch the URL of the anchor directory. */
SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, scratch_pool));
SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
@@ -2143,7 +2171,7 @@ diff_repos_wc(const char **root_relpath,
target_url = NULL;
}
- else /* is_copy && revision2->kind == svn_opt_revision_base */
+ else /* is_copy && revision2->kind != svn_opt_revision_base */
{
#if 0
svn_node_kind_t kind;
@@ -2232,10 +2260,14 @@ diff_repos_wc(const char **root_relpath,
anchor_url,
result_pool);
}
+ else
+ {
+ diff_processor = svn_diff__tree_processor_filter_create(
+ diff_processor, target, scratch_pool);
+ }
if (reverse)
- diff_processor = svn_diff__tree_processor_reverse_create(
- diff_processor, NULL, scratch_pool);
+ diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, scratch_pool);
/* Use the diff editor to generate the diff. */
SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
@@ -2311,12 +2343,84 @@ diff_repos_wc(const char **root_relpath,
return SVN_NO_ERROR;
}
+/* Run diff on shelf SHELF_NAME, if it exists.
+ */
+static svn_error_t *
+diff_shelf(const char *shelf_name,
+ const char *target_abspath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const svn_diff_tree_processor_t *diff_processor,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_client__shelf_t *shelf;
+ svn_client__shelf_version_t *shelf_version;
+ const char *wc_relpath;
+
+ err = svn_client__shelf_open_existing(&shelf,
+ shelf_name, target_abspath,
+ ctx, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ SVN_ERR(svn_client__shelf_version_open(&shelf_version,
+ shelf, shelf->max_version,
+ scratch_pool, scratch_pool));
+ wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath);
+ SVN_ERR(svn_client__shelf_diff(shelf_version, wc_relpath,
+ depth, ignore_ancestry,
+ diff_processor, scratch_pool));
+ SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Run diff on all shelves named in CHANGELISTS by a changelist name
+ * of the form "svn:shelf:SHELF_NAME", if they exist.
+ */
+static svn_error_t *
+diff_shelves(const apr_array_header_t *changelists,
+ const char *target_abspath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const svn_diff_tree_processor_t *diff_processor,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ static const char PREFIX[] = "svn:shelf:";
+ static const int PREFIX_LEN = 10;
+ int i;
+
+ if (! changelists)
+ return SVN_NO_ERROR;
+ for (i = 0; i < changelists->nelts; i++)
+ {
+ const char *cl = APR_ARRAY_IDX(changelists, i, const char *);
+
+ if (strncmp(cl, PREFIX, PREFIX_LEN) == 0)
+ {
+ const char *shelf_name = cl + PREFIX_LEN;
+
+ SVN_ERR(diff_shelf(shelf_name, target_abspath,
+ depth, ignore_ancestry,
+ diff_processor, ctx, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* This is basically just the guts of svn_client_diff[_summarize][_peg]6(). */
static svn_error_t *
-do_diff(const char **root_relpath,
- svn_boolean_t *root_is_dir,
- diff_driver_info_t *ddi,
+do_diff(diff_driver_info_t *ddi,
const char *path_or_url1,
const char *path_or_url2,
const svn_opt_revision_t *revision1,
@@ -2344,8 +2448,7 @@ do_diff(const char **root_relpath,
if (is_repos2)
{
/* Ignores changelists. */
- SVN_ERR(diff_repos_repos(root_relpath, root_is_dir,
- ddi,
+ SVN_ERR(diff_repos_repos(ddi,
path_or_url1, path_or_url2,
revision1, revision2,
peg_revision, depth, ignore_ancestry,
@@ -2355,7 +2458,7 @@ do_diff(const char **root_relpath,
}
else /* path_or_url2 is a working copy path */
{
- SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi,
+ SVN_ERR(diff_repos_wc(ddi,
path_or_url1, revision1,
no_peg_revision ? revision1
: peg_revision,
@@ -2370,7 +2473,7 @@ do_diff(const char **root_relpath,
{
if (is_repos2)
{
- SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi,
+ SVN_ERR(diff_repos_wc(ddi,
path_or_url2, revision2,
no_peg_revision ? revision2
: peg_revision,
@@ -2394,19 +2497,51 @@ do_diff(const char **root_relpath,
SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2,
scratch_pool));
- /* ### What about ddi? */
+ if (ddi)
+ {
+ svn_node_kind_t kind1, kind2;
+
+ SVN_ERR(svn_io_check_resolved_path(abspath1, &kind1,
+ scratch_pool));
+ SVN_ERR(svn_io_check_resolved_path(abspath2, &kind2,
+ scratch_pool));
+ if (kind1 == svn_node_dir && kind2 == svn_node_dir)
+ {
+ ddi->anchor = "";
+ }
+ else
+ {
+ ddi->anchor = svn_dirent_basename(abspath1, NULL);
+ }
+ ddi->orig_path_1 = path_or_url1;
+ ddi->orig_path_2 = path_or_url2;
+ }
+
/* Ignores changelists, ignore_ancestry */
- SVN_ERR(svn_client__arbitrary_nodes_diff(root_relpath, root_is_dir,
- abspath1, abspath2,
+ SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2,
depth,
diff_processor,
- ctx,
- result_pool, scratch_pool));
+ ctx, scratch_pool));
}
else
{
- SVN_ERR(diff_wc_wc(root_relpath, root_is_dir, ddi,
- path_or_url1, revision1,
+ if (ddi)
+ {
+ ddi->anchor = path_or_url1;
+ ddi->orig_path_1 = path_or_url1;
+ ddi->orig_path_2 = path_or_url2;
+ }
+
+ {
+ const char *abspath1;
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1,
+ scratch_pool));
+ SVN_ERR(diff_shelves(changelists, abspath1,
+ depth, ignore_ancestry,
+ diff_processor, ctx, scratch_pool));
+ }
+ SVN_ERR(diff_wc_wc(path_or_url1, revision1,
path_or_url2, revision2,
depth, ignore_ancestry, changelists,
diff_processor, ctx,
@@ -2482,6 +2617,115 @@ create_diff_writer_info(diff_writer_info_t *dwi,
return SVN_NO_ERROR;
}
+/* Set up *DIFF_PROCESSOR and *DDI for normal and git-style diffs (but not
+ * summary diffs).
+ */
+static svn_error_t *
+get_diff_processor(svn_diff_tree_processor_t **diff_processor,
+ struct diff_driver_info_t **ddi,
+ const apr_array_header_t *options,
+ const char *relative_to_dir,
+ svn_boolean_t no_diff_added,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t ignore_properties,
+ svn_boolean_t properties_only,
+ svn_boolean_t use_git_diff_format,
+ svn_boolean_t pretty_print_mergeinfo,
+ const char *header_encoding,
+ svn_stream_t *outstream,
+ svn_stream_t *errstream,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ diff_writer_info_t *dwi = apr_pcalloc(pool, sizeof(*dwi));
+ svn_diff_tree_processor_t *processor;
+
+ /* setup callback and baton */
+
+ SVN_ERR(create_diff_writer_info(dwi, options,
+ ctx->config, pool));
+ dwi->pool = pool;
+ dwi->outstream = outstream;
+ dwi->errstream = errstream;
+ dwi->header_encoding = header_encoding;
+
+ dwi->force_binary = ignore_content_type;
+ dwi->ignore_properties = ignore_properties;
+ dwi->properties_only = properties_only;
+ dwi->relative_to_dir = relative_to_dir;
+ dwi->use_git_diff_format = use_git_diff_format;
+ dwi->no_diff_added = no_diff_added;
+ dwi->no_diff_deleted = no_diff_deleted;
+ dwi->show_copies_as_adds = show_copies_as_adds;
+ dwi->pretty_print_mergeinfo = pretty_print_mergeinfo;
+
+ dwi->cancel_func = ctx->cancel_func;
+ dwi->cancel_baton = ctx->cancel_baton;
+
+ dwi->ddi.wc_ctx = ctx->wc_ctx;
+ dwi->ddi.session_relpath = NULL;
+ dwi->ddi.anchor = NULL;
+
+ processor = svn_diff__tree_processor_create(dwi, pool);
+
+ processor->dir_added = diff_dir_added;
+ processor->dir_changed = diff_dir_changed;
+ processor->dir_deleted = diff_dir_deleted;
+
+ processor->file_added = diff_file_added;
+ processor->file_changed = diff_file_changed;
+ processor->file_deleted = diff_file_deleted;
+
+ *diff_processor = processor;
+ *ddi = &dwi->ddi;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__get_diff_writer_svn(
+ svn_diff_tree_processor_t **diff_processor,
+ const char *anchor,
+ const char *orig_path_1,
+ const char *orig_path_2,
+ const apr_array_header_t *options,
+ const char *relative_to_dir,
+ svn_boolean_t no_diff_added,
+ svn_boolean_t no_diff_deleted,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t ignore_content_type,
+ svn_boolean_t ignore_properties,
+ svn_boolean_t properties_only,
+ svn_boolean_t pretty_print_mergeinfo,
+ const char *header_encoding,
+ svn_stream_t *outstream,
+ svn_stream_t *errstream,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ struct diff_driver_info_t *ddi;
+
+ SVN_ERR(get_diff_processor(diff_processor, &ddi,
+ options,
+ relative_to_dir,
+ no_diff_added,
+ no_diff_deleted,
+ show_copies_as_adds,
+ ignore_content_type,
+ ignore_properties,
+ properties_only,
+ FALSE /*use_git_diff_format*/,
+ pretty_print_mergeinfo,
+ header_encoding,
+ outstream, errstream,
+ ctx, pool));
+ ddi->anchor = anchor;
+ ddi->orig_path_1 = orig_path_1;
+ ddi->orig_path_2 = orig_path_2;
+ return SVN_NO_ERROR;
+}
+
/*----------------------------------------------------------------------- */
/*** Public Interfaces. ***/
@@ -2519,7 +2763,7 @@ create_diff_writer_info(diff_writer_info_t *dwi,
* These cases require server communication.
*/
svn_error_t *
-svn_client_diff6(const apr_array_header_t *options,
+svn_client_diff7(const apr_array_header_t *options,
const char *path_or_url1,
const svn_opt_revision_t *revision1,
const char *path_or_url2,
@@ -2534,6 +2778,7 @@ svn_client_diff6(const apr_array_header_t *options,
svn_boolean_t ignore_properties,
svn_boolean_t properties_only,
svn_boolean_t use_git_diff_format,
+ svn_boolean_t pretty_print_mergeinfo,
const char *header_encoding,
svn_stream_t *outstream,
svn_stream_t *errstream,
@@ -2541,10 +2786,9 @@ svn_client_diff6(const apr_array_header_t *options,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
- diff_writer_info_t dwi = { 0 };
svn_opt_revision_t peg_revision;
- const svn_diff_tree_processor_t *diff_processor;
- svn_diff_tree_processor_t *processor;
+ svn_diff_tree_processor_t *diff_processor;
+ struct diff_driver_info_t *ddi;
if (ignore_properties && properties_only)
return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
@@ -2554,50 +2798,26 @@ svn_client_diff6(const apr_array_header_t *options,
/* We will never do a pegged diff from here. */
peg_revision.kind = svn_opt_revision_unspecified;
- /* setup callback and baton */
- dwi.ddi.orig_path_1 = path_or_url1;
- dwi.ddi.orig_path_2 = path_or_url2;
-
- SVN_ERR(create_diff_writer_info(&dwi, options,
- ctx->config, pool));
- dwi.pool = pool;
- dwi.outstream = outstream;
- dwi.errstream = errstream;
- dwi.header_encoding = header_encoding;
-
- dwi.force_binary = ignore_content_type;
- dwi.ignore_properties = ignore_properties;
- dwi.properties_only = properties_only;
- dwi.relative_to_dir = relative_to_dir;
- dwi.use_git_diff_format = use_git_diff_format;
- dwi.no_diff_added = no_diff_added;
- dwi.no_diff_deleted = no_diff_deleted;
- dwi.show_copies_as_adds = show_copies_as_adds;
-
- dwi.cancel_func = ctx->cancel_func;
- dwi.cancel_baton = ctx->cancel_baton;
-
- dwi.wc_ctx = ctx->wc_ctx;
- dwi.ddi.session_relpath = NULL;
- dwi.ddi.anchor = NULL;
-
- processor = svn_diff__tree_processor_create(&dwi, pool);
-
- processor->dir_added = diff_dir_added;
- processor->dir_changed = diff_dir_changed;
- processor->dir_deleted = diff_dir_deleted;
-
- processor->file_added = diff_file_added;
- processor->file_changed = diff_file_changed;
- processor->file_deleted = diff_file_deleted;
-
- diff_processor = processor;
-
/* --show-copies-as-adds and --git imply --notice-ancestry */
if (show_copies_as_adds || use_git_diff_format)
ignore_ancestry = FALSE;
- return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi,
+ SVN_ERR(get_diff_processor(&diff_processor, &ddi,
+ options,
+ relative_to_dir,
+ no_diff_added,
+ no_diff_deleted,
+ show_copies_as_adds,
+ ignore_content_type,
+ ignore_properties,
+ properties_only,
+ use_git_diff_format,
+ pretty_print_mergeinfo,
+ header_encoding,
+ outstream, errstream,
+ ctx, pool));
+
+ return svn_error_trace(do_diff(ddi,
path_or_url1, path_or_url2,
revision1, revision2,
&peg_revision, TRUE /* no_peg_revision */,
@@ -2607,7 +2827,7 @@ svn_client_diff6(const apr_array_header_t *options,
}
svn_error_t *
-svn_client_diff_peg6(const apr_array_header_t *options,
+svn_client_diff_peg7(const apr_array_header_t *options,
const char *path_or_url,
const svn_opt_revision_t *peg_revision,
const svn_opt_revision_t *start_revision,
@@ -2622,6 +2842,7 @@ svn_client_diff_peg6(const apr_array_header_t *options,
svn_boolean_t ignore_properties,
svn_boolean_t properties_only,
svn_boolean_t use_git_diff_format,
+ svn_boolean_t pretty_print_mergeinfo,
const char *header_encoding,
svn_stream_t *outstream,
svn_stream_t *errstream,
@@ -2629,59 +2850,34 @@ svn_client_diff_peg6(const apr_array_header_t *options,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
- diff_writer_info_t dwi = { 0 };
- const svn_diff_tree_processor_t *diff_processor;
- svn_diff_tree_processor_t *processor;
+ svn_diff_tree_processor_t *diff_processor;
+ struct diff_driver_info_t *ddi;
if (ignore_properties && properties_only)
return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
_("Cannot ignore properties and show only "
"properties at the same time"));
- /* setup callback and baton */
- dwi.ddi.orig_path_1 = path_or_url;
- dwi.ddi.orig_path_2 = path_or_url;
-
- SVN_ERR(create_diff_writer_info(&dwi, options,
- ctx->config, pool));
- dwi.pool = pool;
- dwi.outstream = outstream;
- dwi.errstream = errstream;
- dwi.header_encoding = header_encoding;
-
- dwi.force_binary = ignore_content_type;
- dwi.ignore_properties = ignore_properties;
- dwi.properties_only = properties_only;
- dwi.relative_to_dir = relative_to_dir;
- dwi.use_git_diff_format = use_git_diff_format;
- dwi.no_diff_added = no_diff_added;
- dwi.no_diff_deleted = no_diff_deleted;
- dwi.show_copies_as_adds = show_copies_as_adds;
-
- dwi.cancel_func = ctx->cancel_func;
- dwi.cancel_baton = ctx->cancel_baton;
-
- dwi.wc_ctx = ctx->wc_ctx;
- dwi.ddi.session_relpath = NULL;
- dwi.ddi.anchor = NULL;
-
- processor = svn_diff__tree_processor_create(&dwi, pool);
-
- processor->dir_added = diff_dir_added;
- processor->dir_changed = diff_dir_changed;
- processor->dir_deleted = diff_dir_deleted;
-
- processor->file_added = diff_file_added;
- processor->file_changed = diff_file_changed;
- processor->file_deleted = diff_file_deleted;
-
- diff_processor = processor;
-
/* --show-copies-as-adds and --git imply --notice-ancestry */
if (show_copies_as_adds || use_git_diff_format)
ignore_ancestry = FALSE;
- return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi,
+ SVN_ERR(get_diff_processor(&diff_processor, &ddi,
+ options,
+ relative_to_dir,
+ no_diff_added,
+ no_diff_deleted,
+ show_copies_as_adds,
+ ignore_content_type,
+ ignore_properties,
+ properties_only,
+ use_git_diff_format,
+ pretty_print_mergeinfo,
+ header_encoding,
+ outstream, errstream,
+ ctx, pool));
+
+ return svn_error_trace(do_diff(ddi,
path_or_url, path_or_url,
start_revision, end_revision,
peg_revision, FALSE /* no_peg_revision */,
@@ -2703,19 +2899,17 @@ svn_client_diff_summarize2(const char *path_or_url1,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
- const svn_diff_tree_processor_t *diff_processor;
+ svn_diff_tree_processor_t *diff_processor;
svn_opt_revision_t peg_revision;
- const char **p_root_relpath;
/* We will never do a pegged diff from here. */
peg_revision.kind = svn_opt_revision_unspecified;
- SVN_ERR(svn_client__get_diff_summarize_callbacks(
- &diff_processor, &p_root_relpath,
+ SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor,
summarize_func, summarize_baton,
- path_or_url1, pool, pool));
+ pool, pool));
- return svn_error_trace(do_diff(p_root_relpath, NULL, NULL,
+ return svn_error_trace(do_diff(NULL,
path_or_url1, path_or_url2,
revision1, revision2,
&peg_revision, TRUE /* no_peg_revision */,
@@ -2737,15 +2931,13 @@ svn_client_diff_summarize_peg2(const char *path_or_url,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
- const svn_diff_tree_processor_t *diff_processor;
- const char **p_root_relpath;
+ svn_diff_tree_processor_t *diff_processor;
- SVN_ERR(svn_client__get_diff_summarize_callbacks(
- &diff_processor, &p_root_relpath,
+ SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor,
summarize_func, summarize_baton,
- path_or_url, pool, pool));
+ pool, pool));
- return svn_error_trace(do_diff(p_root_relpath, NULL, NULL,
+ return svn_error_trace(do_diff(NULL,
path_or_url, path_or_url,
start_revision, end_revision,
peg_revision, FALSE /* no_peg_revision */,
diff --git a/subversion/libsvn_client/diff_local.c b/subversion/libsvn_client/diff_local.c
index 056ee53e8f28..feac90d788bd 100644
--- a/subversion/libsvn_client/diff_local.c
+++ b/subversion/libsvn_client/diff_local.c
@@ -647,20 +647,17 @@ do_dir_diff(const char *left_abspath,
}
svn_error_t *
-svn_client__arbitrary_nodes_diff(const char **root_relpath,
- svn_boolean_t *root_is_dir,
- const char *left_abspath,
+svn_client__arbitrary_nodes_diff(const char *left_abspath,
const char *right_abspath,
svn_depth_t depth,
const svn_diff_tree_processor_t *diff_processor,
svn_client_ctx_t *ctx,
- apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_node_kind_t left_kind;
svn_node_kind_t right_kind;
- const char *left_root_abspath;
- const char *right_root_abspath;
+ const char *left_root_abspath = left_abspath;
+ const char *right_root_abspath = right_abspath;
svn_boolean_t left_before_right = TRUE; /* Future argument? */
if (depth == svn_depth_unknown)
@@ -671,28 +668,6 @@ svn_client__arbitrary_nodes_diff(const char **root_relpath,
if (left_kind == svn_node_dir && right_kind == svn_node_dir)
{
- left_root_abspath = left_abspath;
- right_root_abspath = right_abspath;
-
- if (root_relpath)
- *root_relpath = "";
- if (root_is_dir)
- *root_is_dir = TRUE;
- }
- else
- {
- svn_dirent_split(&left_root_abspath, root_relpath, left_abspath,
- scratch_pool);
- right_root_abspath = svn_dirent_dirname(right_abspath, scratch_pool);
-
- if (root_relpath)
- *root_relpath = apr_pstrdup(result_pool, *root_relpath);
- if (root_is_dir)
- *root_is_dir = FALSE;
- }
-
- if (left_kind == svn_node_dir && right_kind == svn_node_dir)
- {
SVN_ERR(do_dir_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
FALSE, FALSE, left_before_right,
@@ -710,79 +685,48 @@ svn_client__arbitrary_nodes_diff(const char **root_relpath,
else if (left_kind == svn_node_file || left_kind == svn_node_dir
|| right_kind == svn_node_file || right_kind == svn_node_dir)
{
- void *dir_baton;
- svn_boolean_t skip = FALSE;
- svn_boolean_t skip_children = FALSE;
- svn_diff_source_t *left_src;
- svn_diff_source_t *right_src;
-
- left_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
- right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
-
- /* The root is replaced... */
- /* Report delete and/or add */
-
- SVN_ERR(diff_processor->dir_opened(&dir_baton, &skip, &skip_children, "",
- left_src,
- right_src,
- NULL /* copyfrom_src */,
- NULL,
- diff_processor,
- scratch_pool, scratch_pool));
-
- if (skip)
- return SVN_NO_ERROR;
- else if (!skip_children)
+ /* The root is added/deleted/replaced. Report delete and/or add. */
+ if (left_before_right)
{
- if (left_before_right)
- {
- if (left_kind == svn_node_file)
- SVN_ERR(do_file_diff(left_abspath, right_abspath,
- left_root_abspath, right_root_abspath,
- TRUE, FALSE, NULL /* parent_baton */,
- diff_processor, ctx, scratch_pool));
- else if (left_kind == svn_node_dir)
- SVN_ERR(do_dir_diff(left_abspath, right_abspath,
- left_root_abspath, right_root_abspath,
- TRUE, FALSE, left_before_right,
- depth, NULL /* parent_baton */,
- diff_processor, ctx, scratch_pool));
- }
-
- if (right_kind == svn_node_file)
+ if (left_kind == svn_node_file)
SVN_ERR(do_file_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
- FALSE, TRUE, NULL /* parent_baton */,
+ TRUE, FALSE, NULL /* parent_baton */,
diff_processor, ctx, scratch_pool));
- else if (right_kind == svn_node_dir)
+ else if (left_kind == svn_node_dir)
SVN_ERR(do_dir_diff(left_abspath, right_abspath,
left_root_abspath, right_root_abspath,
- FALSE, TRUE, left_before_right,
+ TRUE, FALSE, left_before_right,
depth, NULL /* parent_baton */,
diff_processor, ctx, scratch_pool));
-
- if (! left_before_right)
- {
- if (left_kind == svn_node_file)
- SVN_ERR(do_file_diff(left_abspath, right_abspath,
- left_root_abspath, right_root_abspath,
- TRUE, FALSE, NULL /* parent_baton */,
- diff_processor, ctx, scratch_pool));
- else if (left_kind == svn_node_dir)
- SVN_ERR(do_dir_diff(left_abspath, right_abspath,
- left_root_abspath, right_root_abspath,
- TRUE, FALSE, left_before_right,
- depth, NULL /* parent_baton */,
- diff_processor, ctx, scratch_pool));
- }
}
- SVN_ERR(diff_processor->dir_closed("",
- left_src,
- right_src,
- dir_baton,
- diff_processor,
- scratch_pool));
+ if (right_kind == svn_node_file)
+ SVN_ERR(do_file_diff(left_abspath, right_abspath,
+ left_root_abspath, right_root_abspath,
+ FALSE, TRUE, NULL /* parent_baton */,
+ diff_processor, ctx, scratch_pool));
+ else if (right_kind == svn_node_dir)
+ SVN_ERR(do_dir_diff(left_abspath, right_abspath,
+ left_root_abspath, right_root_abspath,
+ FALSE, TRUE, left_before_right,
+ depth, NULL /* parent_baton */,
+ diff_processor, ctx, scratch_pool));
+
+ if (! left_before_right)
+ {
+ if (left_kind == svn_node_file)
+ SVN_ERR(do_file_diff(left_abspath, right_abspath,
+ left_root_abspath, right_root_abspath,
+ TRUE, FALSE, NULL /* parent_baton */,
+ diff_processor, ctx, scratch_pool));
+ else if (left_kind == svn_node_dir)
+ SVN_ERR(do_dir_diff(left_abspath, right_abspath,
+ left_root_abspath, right_root_abspath,
+ TRUE, FALSE, left_before_right,
+ depth, NULL /* parent_baton */,
+ diff_processor, ctx, scratch_pool));
+ }
}
else
return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
diff --git a/subversion/libsvn_client/diff_summarize.c b/subversion/libsvn_client/diff_summarize.c
index 9e258cdfcbcb..f4972a39e194 100644
--- a/subversion/libsvn_client/diff_summarize.c
+++ b/subversion/libsvn_client/diff_summarize.c
@@ -36,11 +36,6 @@
/* Diff callbacks baton. */
struct summarize_baton_t {
- apr_pool_t *baton_pool; /* For allocating skip_path */
-
- /* The target path of the diff, relative to the anchor; "" if target == anchor. */
- const char *skip_relpath;
-
/* The summarize callback passed down from the API */
svn_client_diff_summarize_func_t summarize_func;
@@ -49,9 +44,8 @@ struct summarize_baton_t {
};
/* Call B->summarize_func with B->summarize_func_baton, passing it a
- * summary object composed from PATH (but made to be relative to the target
- * of the diff), SUMMARIZE_KIND, PROP_CHANGED (or FALSE if the action is an
- * add or delete) and NODE_KIND. */
+ * summary object composed from PATH, SUMMARIZE_KIND, PROP_CHANGED (or
+ * FALSE if the action is an add or delete) and NODE_KIND. */
static svn_error_t *
send_summary(struct summarize_baton_t *b,
const char *path,
@@ -65,9 +59,7 @@ send_summary(struct summarize_baton_t *b,
SVN_ERR_ASSERT(summarize_kind != svn_client_diff_summarize_kind_normal
|| prop_changed);
- /* PATH is relative to the anchor of the diff, but SUM->path needs to be
- relative to the target of the diff. */
- sum->path = svn_relpath_skip_ancestor(b->skip_relpath, path);
+ sum->path = path;
sum->summarize_kind = summarize_kind;
if (summarize_kind == svn_client_diff_summarize_kind_modified
|| summarize_kind == svn_client_diff_summarize_kind_normal)
@@ -265,18 +257,15 @@ diff_file_deleted(const char *relpath,
svn_error_t *
svn_client__get_diff_summarize_callbacks(
- const svn_diff_tree_processor_t **diff_processor,
- const char ***p_root_relpath,
+ svn_diff_tree_processor_t **diff_processor,
svn_client_diff_summarize_func_t summarize_func,
void *summarize_baton,
- const char *original_target,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_diff_tree_processor_t *dp;
struct summarize_baton_t *b = apr_pcalloc(result_pool, sizeof(*b));
- b->baton_pool = result_pool;
b->summarize_func = summarize_func;
b->summarize_func_baton = summarize_baton;
@@ -293,7 +282,6 @@ svn_client__get_diff_summarize_callbacks(
dp->dir_added = diff_dir_added;
*diff_processor = dp;
- *p_root_relpath = &b->skip_relpath;
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_client/export.c b/subversion/libsvn_client/export.c
index 2fc2dc8ffd53..b335f2a69eca 100644
--- a/subversion/libsvn_client/export.c
+++ b/subversion/libsvn_client/export.c
@@ -453,12 +453,12 @@ export_node(void *baton,
* If PATH exists but is a file, then error with SVN_ERR_WC_NOT_WORKING_COPY.
*
* If PATH is a already a directory, then error with
- * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just
+ * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless OVERWRITE, in which case just
* export into PATH with no error.
*/
static svn_error_t *
open_root_internal(const char *path,
- svn_boolean_t force,
+ svn_boolean_t overwrite,
svn_wc_notify_func2_t notify_func,
void *notify_baton,
apr_pool_t *pool)
@@ -472,7 +472,7 @@ open_root_internal(const char *path,
return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
_("'%s' exists and is not a directory"),
svn_dirent_local_style(path, pool));
- else if ((kind != svn_node_dir) || (! force))
+ else if ((kind != svn_node_dir) || (! overwrite))
return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
_("'%s' already exists"),
svn_dirent_local_style(path, pool));
@@ -501,7 +501,7 @@ struct edit_baton
const char *repos_root_url;
const char *root_path;
const char *root_url;
- svn_boolean_t force;
+ svn_boolean_t overwrite;
svn_revnum_t *target_revision;
apr_hash_t *externals;
const char *native_eol;
@@ -587,7 +587,7 @@ open_root(void *edit_baton,
struct edit_baton *eb = edit_baton;
struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
- SVN_ERR(open_root_internal(eb->root_path, eb->force,
+ SVN_ERR(open_root_internal(eb->root_path, eb->overwrite,
eb->notify_func, eb->notify_baton, pool));
/* Build our dir baton. */
@@ -621,7 +621,7 @@ add_directory(const char *path,
return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
_("'%s' exists and is not a directory"),
svn_dirent_local_style(full_path, pool));
- else if (! (kind == svn_node_dir && eb->force))
+ else if (! (kind == svn_node_dir && eb->overwrite))
return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
_("'%s' already exists"),
svn_dirent_local_style(full_path, pool));
@@ -1077,7 +1077,7 @@ add_directory_ev2(void *baton,
return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
_("'%s' exists and is not a directory"),
svn_dirent_local_style(full_path, scratch_pool));
- else if (! (kind == svn_node_dir && eb->force))
+ else if (! (kind == svn_node_dir && eb->overwrite))
return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
_("'%s' already exists"),
svn_dirent_local_style(full_path, scratch_pool));
@@ -1141,7 +1141,7 @@ get_editor_ev2(const svn_delta_editor_t **export_editor,
exb, result_pool));
/* Create the root of the export. */
- SVN_ERR(open_root_internal(eb->root_path, eb->force, eb->notify_func,
+ SVN_ERR(open_root_internal(eb->root_path, eb->overwrite, eb->notify_func,
eb->notify_baton, scratch_pool));
return SVN_NO_ERROR;
@@ -1153,7 +1153,6 @@ export_file_ev2(const char *from_url,
struct edit_baton *eb,
svn_client__pathrev_t *loc,
svn_ra_session_t *ra_session,
- svn_boolean_t overwrite,
apr_pool_t *scratch_pool)
{
apr_hash_t *props;
@@ -1177,7 +1176,7 @@ export_file_ev2(const char *from_url,
SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
- ! overwrite)
+ ! eb->overwrite)
return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
_("Destination file '%s' exists, and "
"will not be overwritten unless forced"),
@@ -1207,7 +1206,6 @@ export_file(const char *from_url,
struct edit_baton *eb,
svn_client__pathrev_t *loc,
svn_ra_session_t *ra_session,
- svn_boolean_t overwrite,
apr_pool_t *scratch_pool)
{
apr_hash_t *props;
@@ -1232,7 +1230,7 @@ export_file(const char *from_url,
SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
- ! overwrite)
+ ! eb->overwrite)
return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
_("Destination file '%s' exists, and "
"will not be overwritten unless forced"),
@@ -1289,7 +1287,6 @@ export_directory(const char *from_url,
struct edit_baton *eb,
svn_client__pathrev_t *loc,
svn_ra_session_t *ra_session,
- svn_boolean_t overwrite,
svn_boolean_t ignore_externals,
svn_boolean_t ignore_keywords,
svn_depth_t depth,
@@ -1344,7 +1341,7 @@ export_directory(const char *from_url,
SVN_ERR(svn_io_check_path(to_path, &kind, scratch_pool));
if (kind == svn_node_none)
SVN_ERR(open_root_internal
- (to_path, overwrite, ctx->notify_func2,
+ (to_path, eb->overwrite, ctx->notify_func2,
ctx->notify_baton2, scratch_pool));
if (! ignore_externals && depth == svn_depth_infinity)
@@ -1415,7 +1412,7 @@ svn_client_export5(svn_revnum_t *result_rev,
SVN_ERR(svn_ra_get_repos_root2(ra_session, &eb->repos_root_url, pool));
eb->root_path = to_path;
eb->root_url = loc->url;
- eb->force = overwrite;
+ eb->overwrite = overwrite;
eb->target_revision = &edit_revision;
eb->externals = apr_hash_make(pool);
eb->native_eol = native_eol;
@@ -1431,15 +1428,15 @@ svn_client_export5(svn_revnum_t *result_rev,
{
if (!ENABLE_EV2_IMPL)
SVN_ERR(export_file(from_url, to_path, eb, loc, ra_session,
- overwrite, pool));
+ pool));
else
SVN_ERR(export_file_ev2(from_url, to_path, eb, loc,
- ra_session, overwrite, pool));
+ ra_session, pool));
}
else if (kind == svn_node_dir)
{
SVN_ERR(export_directory(from_url, to_path,
- eb, loc, ra_session, overwrite,
+ eb, loc, ra_session,
ignore_externals, ignore_keywords, depth,
native_eol, ctx, pool));
}
diff --git a/subversion/libsvn_client/info.c b/subversion/libsvn_client/info.c
index 3331647c9587..2aa4c916cd8e 100644
--- a/subversion/libsvn_client/info.c
+++ b/subversion/libsvn_client/info.c
@@ -167,7 +167,8 @@ build_info_from_dirent(svn_client_info2_t **info,
#define DIRENT_FIELDS (SVN_DIRENT_KIND | \
SVN_DIRENT_CREATED_REV | \
SVN_DIRENT_TIME | \
- SVN_DIRENT_LAST_AUTHOR)
+ SVN_DIRENT_LAST_AUTHOR | \
+ SVN_DIRENT_SIZE)
/* Helper func for recursively fetching svn_dirent_t's from a remote
@@ -267,6 +268,7 @@ same_resource_in_head(svn_boolean_t *same_p,
ctx, pool);
if (err &&
((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) ||
+ (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY) ||
(err->apr_err == SVN_ERR_FS_NOT_FOUND)))
{
svn_error_clear(err);
diff --git a/subversion/libsvn_client/layout.c b/subversion/libsvn_client/layout.c
new file mode 100644
index 000000000000..bfa7ec1039f1
--- /dev/null
+++ b/subversion/libsvn_client/layout.c
@@ -0,0 +1,289 @@
+/*
+* layout.c: code to list and update the working copy layout
+*
+* ====================================================================
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+* ====================================================================
+*/
+
+/* ==================================================================== */
+
+
+
+/*** Includes. ***/
+
+#include "svn_hash.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_wc.h"
+#include "svn_client.h"
+#include "svn_error.h"
+#include "svn_pools.h"
+#include "client.h"
+
+#include "svn_private_config.h"
+#include "private/svn_wc_private.h"
+
+struct layout_item_t
+{
+ const char *local_abspath;
+ const char *url;
+ svn_revnum_t revision;
+ svn_depth_t depth;
+ struct layout_item_t *ancestor;
+ apr_pool_t *pool;
+};
+
+struct client_layout_baton_t
+{
+ const char *root_abspath;
+ svn_wc_context_t *wc_ctx;
+ const char *repos_root_url;
+
+ struct layout_item_t *stack;
+ apr_pool_t *root_pool;
+
+ svn_client__layout_func_t layout;
+ void *layout_baton;
+};
+
+
+static svn_error_t *
+layout_set_path(void *report_baton,
+ const char *path,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ struct client_layout_baton_t *lb = report_baton;
+ const char *local_abspath = svn_dirent_join(lb->root_abspath, path, pool);
+ struct layout_item_t *it;
+ apr_pool_t *item_pool;
+ svn_depth_t expected_depth;
+
+ while (lb->stack
+ && !svn_dirent_is_ancestor(lb->stack->local_abspath, local_abspath))
+ {
+ it = lb->stack;
+ lb->stack = it->ancestor;
+ svn_pool_destroy(it->pool);
+ }
+
+ item_pool = svn_pool_create(lb->stack ? lb->stack->pool
+ : lb->root_pool);
+
+ it = apr_pcalloc(item_pool, sizeof(*it));
+ it->pool = item_pool;
+ it->local_abspath = apr_pstrdup(item_pool, local_abspath);
+ it->depth = depth;
+ it->revision = revision;
+ if (lb->stack)
+ {
+ it->url = svn_path_url_add_component2(
+ lb->stack->url,
+ svn_dirent_skip_ancestor(lb->stack->local_abspath,
+ local_abspath),
+ item_pool);
+ }
+ else
+ {
+ const char *repos_relpath, *repos_root_url;
+
+ SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath,
+ &repos_root_url, NULL, NULL,
+ lb->wc_ctx, local_abspath,
+ FALSE /* ignore_enoent */,
+ pool, pool));
+
+ lb->repos_root_url = apr_pstrdup(lb->root_pool, repos_root_url);
+ it->url = svn_path_url_add_component2(repos_root_url, repos_relpath,
+ item_pool);
+ }
+ it->ancestor = lb->stack;
+ lb->stack = it;
+
+ if (!it->ancestor)
+ expected_depth = depth;
+ else if (it->ancestor->depth == svn_depth_infinity)
+ expected_depth = svn_depth_infinity;
+ else
+ expected_depth = svn_depth_empty;
+
+ return svn_error_trace(lb->layout(lb->layout_baton,
+ it->local_abspath,
+ lb->repos_root_url,
+ FALSE /* not-present */,
+ FALSE /* url changed */,
+ it->url,
+ it->ancestor
+ ? it->ancestor->revision != it->revision
+ : FALSE,
+ it->revision,
+ (depth != expected_depth),
+ it->depth,
+ pool));
+ }
+
+static svn_error_t *
+layout_link_path(void *report_baton,
+ const char *path,
+ const char *url,
+ svn_revnum_t revision,
+ svn_depth_t depth,
+ svn_boolean_t start_empty,
+ const char *lock_token,
+ apr_pool_t *pool)
+{
+ struct client_layout_baton_t *lb = report_baton;
+ const char *local_abspath = svn_dirent_join(lb->root_abspath, path, pool);
+ struct layout_item_t *it;
+ apr_pool_t *item_pool;
+ svn_depth_t expected_depth;
+
+ SVN_ERR_ASSERT(lb->stack); /* Always below root entry */
+
+ while (!svn_dirent_is_ancestor(lb->stack->local_abspath, local_abspath))
+ {
+ it = lb->stack;
+ lb->stack = it->ancestor;
+ svn_pool_destroy(it->pool);
+ }
+
+ item_pool = svn_pool_create(lb->stack ? lb->stack->pool
+ : lb->root_pool);
+
+ it = apr_pcalloc(item_pool, sizeof(*it));
+ it->pool = item_pool;
+ it->local_abspath = apr_pstrdup(item_pool, local_abspath);
+ it->depth = depth;
+ it->revision = revision;
+ it->url = apr_pstrdup(item_pool, url);
+
+ it->ancestor = lb->stack;
+ lb->stack = it;
+
+ if (it->ancestor->depth == svn_depth_infinity)
+ expected_depth = svn_depth_infinity;
+ else
+ expected_depth = svn_depth_empty;
+
+ return svn_error_trace(lb->layout(lb->layout_baton,
+ it->local_abspath,
+ lb->repos_root_url,
+ FALSE /* not-present */,
+ TRUE /* url changed */,
+ it->url,
+ it->ancestor
+ ? it->ancestor->revision != it->revision
+ : FALSE,
+ it->revision,
+ (depth != expected_depth),
+ it->depth,
+ pool));
+}
+
+static svn_error_t *
+layout_delete_path(void *report_baton,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct client_layout_baton_t *lb = report_baton;
+ const char *local_abspath = svn_dirent_join(lb->root_abspath, path, pool);
+ struct layout_item_t *it;
+
+ SVN_ERR_ASSERT(lb->stack); /* Always below root entry */
+
+ while (!svn_dirent_is_ancestor(lb->stack->local_abspath, local_abspath))
+ {
+ it = lb->stack;
+ lb->stack = it->ancestor;
+ svn_pool_destroy(it->pool);
+ }
+
+ return svn_error_trace(lb->layout(lb->layout_baton,
+ local_abspath,
+ lb->repos_root_url,
+ TRUE /* not-present */,
+ FALSE /* url changed */,
+ NULL /* no-url */,
+ FALSE /* revision changed */,
+ SVN_INVALID_REVNUM,
+ FALSE /* depth changed */,
+ svn_depth_unknown,
+ pool));
+}
+
+static svn_error_t *
+layout_finish_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ /*struct client_layout_baton_t *lb = report_baton;*/
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+layout_abort_report(void *report_baton,
+ apr_pool_t *pool)
+{
+ /*struct client_layout_baton_t *lb = report_baton;*/
+ return SVN_NO_ERROR;
+}
+
+static const svn_ra_reporter3_t layout_reporter =
+{
+ layout_set_path,
+ layout_delete_path,
+ layout_link_path,
+ layout_finish_report,
+ layout_abort_report
+};
+
+svn_error_t *
+svn_client__layout_list(const char *local_abspath,
+ svn_client__layout_func_t layout,
+ void *layout_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ struct client_layout_baton_t lb;
+
+ lb.root_abspath = local_abspath;
+ lb.root_pool = scratch_pool;
+ lb.wc_ctx = ctx->wc_ctx;
+ lb.repos_root_url = NULL; /* Filled in later */
+ lb.stack = NULL;
+
+ lb.layout = layout;
+ lb.layout_baton = layout_baton;
+
+ /* Drive the reporter structure, describing the revisions within
+ LOCAL_ABSPATH. */
+ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath,
+ &layout_reporter, &lb,
+ FALSE /* restore_files */,
+ svn_depth_infinity,
+ TRUE /* honor_depth_exclude */,
+ FALSE /* depth_compatibility_trick */,
+ FALSE /* use_commit_times */,
+ ctx->cancel_func, ctx->cancel_baton,
+ ctx->notify_func2, ctx->notify_baton2,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_client/libsvn_client.pc.in b/subversion/libsvn_client/libsvn_client.pc.in
index 7cc7865643a9..4e475d895adf 100644
--- a/subversion/libsvn_client/libsvn_client.pc.in
+++ b/subversion/libsvn_client/libsvn_client.pc.in
@@ -6,7 +6,7 @@ includedir=@includedir@
Name: libsvn_client
Description: Subversion Client Library
Version: @PACKAGE_VERSION@
-Requires: apr-@SVN_APR_MAJOR_VERSION@
-Requires.private: libsvn_wc libsvn_ra libsvn_delta libsvn_diff libsvn_subr
-Libs: -L${libdir} -lsvn_client
-Cflags: -I${includedir}
+Requires: apr-@SVN_APR_MAJOR_VERSION@
+Requires.private: libsvn_wc, libsvn_ra, libsvn_delta, libsvn_diff, libsvn_subr
+Libs: -L${libdir} -lsvn_client-1
+Cflags: -I${includedir}/subversion-1
diff --git a/subversion/libsvn_client/list.c b/subversion/libsvn_client/list.c
index 78433c337e58..d52de6d10ac3 100644
--- a/subversion/libsvn_client/list.c
+++ b/subversion/libsvn_client/list.c
@@ -394,7 +394,7 @@ list_internal(const char *path_or_url,
svn_membuf__create(&scratch_buffer, 256, pool);
/* Report the dirent for the target. */
- if (match_patterns(svn_dirent_dirname(fs_path, pool), patterns,
+ if (match_patterns(svn_dirent_basename(fs_path, pool), patterns,
&scratch_buffer))
SVN_ERR(list_func(baton, "", dirent, locks
? (svn_hash_gets(locks, fs_path))
diff --git a/subversion/libsvn_client/merge.c b/subversion/libsvn_client/merge.c
index 21341b9b0a7b..9d9a1c300a10 100644
--- a/subversion/libsvn_client/merge.c
+++ b/subversion/libsvn_client/merge.c
@@ -215,6 +215,21 @@
/*** Repos-Diff Editor Callbacks ***/
+struct merge_cmd_baton_t;
+
+struct notify_begin_state_t
+{
+ /* Cache of which abspath was last notified. */
+ const char *last_abspath;
+
+ /* Reference to the main merge baton */
+ struct merge_cmd_baton_t *merge_b;
+
+ /* the wrapped notification callback */
+ svn_wc_notify_func2_t notify_func2;
+ void *notify_baton2;
+};
+
typedef struct merge_cmd_baton_t {
svn_boolean_t force_delete; /* Delete a file/dir even if modified */
svn_boolean_t dry_run;
@@ -242,11 +257,15 @@ typedef struct merge_cmd_baton_t {
/* Rangelist containing single range which describes the gap, if any,
in the natural history of the merge source currently being processed.
- See http://subversion.tigris.org/issues/show_bug.cgi?id=3432.
+ See https://issues.apache.org/jira/browse/SVN-3432.
Updated during each call to do_directory_merge(). May be NULL if there
is no gap. */
svn_rangelist_t *implicit_src_gap;
+ /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global
+ comment) or a similar list for single-file-merges */
+ const apr_array_header_t *children_with_mergeinfo;
+
svn_client_ctx_t *ctx; /* Client context for callbacks, etc. */
/* The list of any paths which remained in conflict after a
@@ -319,17 +338,10 @@ typedef struct merge_cmd_baton_t {
or do_file_merge() in do_merge(). */
apr_pool_t *pool;
-
- /* State for notify_merge_begin() */
- struct notify_begin_state_t
- {
- /* Cache of which abspath was last notified. */
- const char *last_abspath;
-
- /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global
- comment) or a similar list for single-file-merges */
- const apr_array_header_t *nodes_with_mergeinfo;
- } notify_begin;
+ /* Our notification callback, that adds a 'begin' notification */
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+ struct notify_begin_state_t notify_begin;
} merge_cmd_baton_t;
@@ -340,17 +352,25 @@ typedef struct merge_cmd_baton_t {
merge source is an ancestor of the right-side (or vice-versa), the merge
source is in the same repository as the merge target, and we are not
ignoring mergeinfo. */
-#define HONOR_MERGEINFO(merge_b) ((merge_b)->mergeinfo_capable \
- && (merge_b)->merge_source.ancestral \
- && (merge_b)->same_repos \
- && (! (merge_b)->ignore_mergeinfo))
+static svn_boolean_t
+HONOR_MERGEINFO(const merge_cmd_baton_t *merge_b)
+{
+ return (merge_b->mergeinfo_capable
+ && merge_b->merge_source.ancestral
+ && merge_b->same_repos
+ && (!merge_b->ignore_mergeinfo));
+}
/* Return TRUE iff we should be recording mergeinfo for the merge described
by MERGE_B. Specifically, that is if we are honoring mergeinfo and the
merge is not a dry run. */
-#define RECORD_MERGEINFO(merge_b) (HONOR_MERGEINFO(merge_b) \
- && !(merge_b)->dry_run)
+static svn_boolean_t
+RECORD_MERGEINFO(const merge_cmd_baton_t *merge_b)
+{
+ return (HONOR_MERGEINFO(merge_b)
+ && !merge_b->dry_run);
+}
/*-----------------------------------------------------------------------*/
@@ -1226,13 +1246,6 @@ struct merge_file_baton_t
svn_boolean_t add_is_replace; /* Add is second part of replace */
};
-/* Forward declaration */
-static svn_error_t *
-notify_merge_begin(merge_cmd_baton_t *merge_b,
- const char *local_abspath,
- svn_boolean_t delete_action,
- apr_pool_t *scratch_pool);
-
/* Record the skip for future processing and (later) produce the
skip notification */
static svn_error_t *
@@ -1253,18 +1266,16 @@ record_skip(merge_cmd_baton_t *merge_b,
store_path(merge_b->skipped_abspaths, local_abspath);
}
- if (merge_b->ctx->notify_func2)
+ if (merge_b->notify_func)
{
svn_wc_notify_t *notify;
- SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool));
-
notify = svn_wc_create_notify(local_abspath, action, scratch_pool);
notify->kind = kind;
notify->content_state = notify->prop_state = state;
- merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify,
- scratch_pool);
+ merge_b->notify_func(merge_b->notify_baton, notify,
+ scratch_pool);
}
return SVN_NO_ERROR;
}
@@ -1364,7 +1375,7 @@ record_tree_conflict(merge_cmd_baton_t *merge_b,
* but figure out the actual revision range merged. */
(void)find_nearest_ancestor_with_intersecting_ranges(
&(range.start), &(range.end),
- merge_b->notify_begin.nodes_with_mergeinfo,
+ merge_b->children_with_mergeinfo,
action != svn_wc_conflict_action_delete,
local_abspath);
loc1 = svn_client__pathrev_dup(merge_b->merge_source.loc1,
@@ -1423,18 +1434,16 @@ record_tree_conflict(merge_cmd_baton_t *merge_b,
}
/* On a replacement we currently get two tree conflicts */
- if (merge_b->ctx->notify_func2 && notify_tc)
+ if (merge_b->notify_func && notify_tc)
{
svn_wc_notify_t *notify;
- SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool));
-
notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict,
scratch_pool);
notify->kind = local_node_kind;
- merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify,
- scratch_pool);
+ merge_b->notify_func(merge_b->notify_baton, notify,
+ scratch_pool);
}
return SVN_NO_ERROR;
@@ -1455,21 +1464,19 @@ record_update_add(merge_cmd_baton_t *merge_b,
store_path(merge_b->merged_abspaths, local_abspath);
}
- if (merge_b->ctx->notify_func2)
+ if (merge_b->notify_func)
{
svn_wc_notify_t *notify;
svn_wc_notify_action_t action = svn_wc_notify_update_add;
- SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool));
-
if (notify_replaced)
action = svn_wc_notify_update_replace;
notify = svn_wc_create_notify(local_abspath, action, scratch_pool);
notify->kind = kind;
- merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify,
- scratch_pool);
+ merge_b->notify_func(merge_b->notify_baton, notify,
+ scratch_pool);
}
return SVN_NO_ERROR;
@@ -1490,20 +1497,18 @@ record_update_update(merge_cmd_baton_t *merge_b,
store_path(merge_b->merged_abspaths, local_abspath);
}
- if (merge_b->ctx->notify_func2)
+ if (merge_b->notify_func)
{
svn_wc_notify_t *notify;
- SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool));
-
notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_update,
scratch_pool);
notify->kind = kind;
notify->content_state = content_state;
notify->prop_state = prop_state;
- merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify,
- scratch_pool);
+ merge_b->notify_func(merge_b->notify_baton, notify,
+ scratch_pool);
}
return SVN_NO_ERROR;
@@ -1529,8 +1534,6 @@ record_update_delete(merge_cmd_baton_t *merge_b,
store_path(merge_b->merged_abspaths, local_abspath);
}
- SVN_ERR(notify_merge_begin(merge_b, local_abspath, TRUE, scratch_pool));
-
if (parent_db)
{
const char *dup_abspath = apr_pstrdup(parent_db->pool, local_abspath);
@@ -1552,7 +1555,7 @@ handle_pending_notifications(merge_cmd_baton_t *merge_b,
struct merge_dir_baton_t *db,
apr_pool_t *scratch_pool)
{
- if (merge_b->ctx->notify_func2 && db->pending_deletes)
+ if (merge_b->notify_func && db->pending_deletes)
{
apr_hash_index_t *hi;
@@ -1569,8 +1572,8 @@ handle_pending_notifications(merge_cmd_baton_t *merge_b,
notify->kind = svn_node_kind_from_word(
apr_hash_this_val(hi));
- merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2,
- notify, scratch_pool);
+ merge_b->notify_func(merge_b->notify_baton,
+ notify, scratch_pool);
}
db->pending_deletes = NULL;
@@ -1620,13 +1623,10 @@ mark_dir_edited(merge_cmd_baton_t *merge_b,
for clarity we produce a skip for this node that
most likely isn't touched by the merge itself */
- if (merge_b->ctx->notify_func2)
+ if (merge_b->notify_func)
{
svn_wc_notify_t *notify;
- SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE,
- scratch_pool));
-
notify = svn_wc_create_notify(
local_abspath,
(db->tree_conflict_reason == CONFLICT_REASON_SKIP)
@@ -1636,9 +1636,9 @@ mark_dir_edited(merge_cmd_baton_t *merge_b,
notify->kind = svn_node_dir;
notify->content_state = notify->prop_state = db->skip_reason;
- merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2,
- notify,
- scratch_pool);
+ merge_b->notify_func(merge_b->notify_baton,
+ notify,
+ scratch_pool);
}
if (merge_b->merge_source.ancestral
@@ -1706,21 +1706,18 @@ mark_file_edited(merge_cmd_baton_t *merge_b,
for clarity we produce a skip for this node that
most likely isn't touched by the merge itself */
- if (merge_b->ctx->notify_func2)
+ if (merge_b->notify_func)
{
svn_wc_notify_t *notify;
- SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE,
- scratch_pool));
-
notify = svn_wc_create_notify(local_abspath, svn_wc_notify_skip,
scratch_pool);
notify->kind = svn_node_file;
notify->content_state = notify->prop_state = fb->skip_reason;
- merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2,
- notify,
- scratch_pool);
+ merge_b->notify_func(merge_b->notify_baton,
+ notify,
+ scratch_pool);
}
if (merge_b->merge_source.ancestral
@@ -3413,6 +3410,49 @@ merge_node_absent(const char *relpath,
return SVN_NO_ERROR;
}
+/* Return a diff processor that will apply the merge to the WC.
+ */
+static svn_diff_tree_processor_t *
+merge_apply_processor(merge_cmd_baton_t *merge_cmd_baton,
+ apr_pool_t *result_pool)
+{
+ svn_diff_tree_processor_t *merge_processor;
+
+ merge_processor = svn_diff__tree_processor_create(merge_cmd_baton,
+ result_pool);
+
+ merge_processor->dir_opened = merge_dir_opened;
+ merge_processor->dir_changed = merge_dir_changed;
+ merge_processor->dir_added = merge_dir_added;
+ merge_processor->dir_deleted = merge_dir_deleted;
+ merge_processor->dir_closed = merge_dir_closed;
+
+ merge_processor->file_opened = merge_file_opened;
+ merge_processor->file_changed = merge_file_changed;
+ merge_processor->file_added = merge_file_added;
+ merge_processor->file_deleted = merge_file_deleted;
+ /* Not interested in file_closed() */
+
+ merge_processor->node_absent = merge_node_absent;
+
+ return merge_processor;
+}
+
+/* Initialize minimal dir baton to allow calculating 'R'eplace
+ from 'D'elete + 'A'dd. */
+static void *
+open_dir_for_replace_single_file(apr_pool_t *result_pool)
+{
+ struct merge_dir_baton_t *dir_baton = apr_pcalloc(result_pool, sizeof(*dir_baton));
+
+ dir_baton->pool = result_pool;
+ dir_baton->tree_conflict_reason = CONFLICT_REASON_NONE;
+ dir_baton->tree_conflict_action = svn_wc_conflict_action_edit;
+ dir_baton->skip_reason = svn_wc_notify_state_unknown;
+
+ return dir_baton;
+}
+
/*-----------------------------------------------------------------------*/
/*** Merge Notification ***/
@@ -3608,20 +3648,9 @@ notify_merge_completed(const char *target_abspath,
}
}
-/* Is the notification the result of a real operative merge? */
-#define IS_OPERATIVE_NOTIFICATION(notify) \
- (notify->content_state == svn_wc_notify_state_conflicted \
- || notify->content_state == svn_wc_notify_state_merged \
- || notify->content_state == svn_wc_notify_state_changed \
- || notify->prop_state == svn_wc_notify_state_conflicted \
- || notify->prop_state == svn_wc_notify_state_merged \
- || notify->prop_state == svn_wc_notify_state_changed \
- || notify->action == svn_wc_notify_update_add \
- || notify->action == svn_wc_notify_tree_conflict)
-
/* Remove merge source gaps from range used for merge notifications.
- See http://subversion.tigris.org/issues/show_bug.cgi?id=4138
+ See https://issues.apache.org/jira/browse/SVN-4138
If IMPLICIT_SRC_GAP is not NULL then it is a rangelist containing a
single range (see the implicit_src_gap member of merge_cmd_baton_t).
@@ -3656,27 +3685,28 @@ remove_source_gap(svn_merge_range_t *range,
* This calls the client's notification receiver (as found in the client
* context), with a WC abspath.
*/
-static svn_error_t *
-notify_merge_begin(merge_cmd_baton_t *merge_b,
+static void
+notify_merge_begin(struct notify_begin_state_t *notify_begin_state,
const char *local_abspath,
svn_boolean_t delete_action,
apr_pool_t *scratch_pool)
{
+ merge_cmd_baton_t *merge_b = notify_begin_state->merge_b;
svn_wc_notify_t *notify;
svn_merge_range_t n_range =
{SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, TRUE};
const char *notify_abspath;
- if (! merge_b->ctx->notify_func2)
- return SVN_NO_ERROR;
+ if (! notify_begin_state->notify_func2)
+ return;
/* If our merge sources are ancestors of one another... */
if (merge_b->merge_source.ancestral)
{
const svn_client__merge_path_t *child;
- /* Find NOTIFY->PATH's nearest ancestor in
- NOTIFY->CHILDREN_WITH_MERGEINFO. Normally we consider a child in
- NOTIFY->CHILDREN_WITH_MERGEINFO representing PATH to be an
+ /* Find LOCAL_ABSPATH's nearest ancestor in
+ CHILDREN_WITH_MERGEINFO. Normally we consider a child in
+ CHILDREN_WITH_MERGEINFO representing PATH to be an
ancestor of PATH, but if this is a deletion of PATH then the
notification must be for a proper ancestor of PATH. This ensures
we don't get notifications like:
@@ -3692,47 +3722,47 @@ notify_merge_begin(merge_cmd_baton_t *merge_b,
child = find_nearest_ancestor_with_intersecting_ranges(
&(n_range.start), &(n_range.end),
- merge_b->notify_begin.nodes_with_mergeinfo,
+ merge_b->children_with_mergeinfo,
! delete_action, local_abspath);
if (!child && delete_action)
{
/* Triggered by file replace in single-file-merge */
- child = find_nearest_ancestor(merge_b->notify_begin.nodes_with_mergeinfo,
+ child = find_nearest_ancestor(merge_b->children_with_mergeinfo,
TRUE, local_abspath);
}
assert(child != NULL); /* Should always find the merge anchor */
if (! child)
- return SVN_NO_ERROR;
+ return;
- if (merge_b->notify_begin.last_abspath != NULL
- && strcmp(child->abspath, merge_b->notify_begin.last_abspath) == 0)
+ if (notify_begin_state->last_abspath != NULL
+ && strcmp(child->abspath, notify_begin_state->last_abspath) == 0)
{
/* Don't notify the same merge again */
- return SVN_NO_ERROR;
+ return;
}
- merge_b->notify_begin.last_abspath = child->abspath;
+ notify_begin_state->last_abspath = child->abspath;
if (child->absent || child->remaining_ranges->nelts == 0
|| !SVN_IS_VALID_REVNUM(n_range.start))
{
/* No valid information for an header */
- return SVN_NO_ERROR;
+ return;
}
notify_abspath = child->abspath;
}
else
{
- if (merge_b->notify_begin.last_abspath)
- return SVN_NO_ERROR; /* already notified */
+ if (notify_begin_state->last_abspath)
+ return; /* already notified */
notify_abspath = merge_b->target->abspath;
/* Store something in last_abspath. Any value would do */
- merge_b->notify_begin.last_abspath = merge_b->target->abspath;
+ notify_begin_state->last_abspath = merge_b->target->abspath;
}
notify = svn_wc_create_notify(notify_abspath,
@@ -3753,10 +3783,23 @@ notify_merge_begin(merge_cmd_baton_t *merge_b,
notify->merge_range = NULL;
}
- merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify,
- scratch_pool);
+ notify_begin_state->notify_func2(notify_begin_state->notify_baton2, notify,
+ scratch_pool);
+}
+
+/* Our notification callback, that adds a 'begin' notification */
+static void
+notify_merging(void *baton,
+ const svn_wc_notify_t *notify,
+ apr_pool_t *pool)
+{
+ struct notify_begin_state_t *b = baton;
+
+ notify_merge_begin(b, notify->path,
+ notify->action == svn_wc_notify_update_delete,
+ pool);
- return SVN_NO_ERROR;
+ b->notify_func2(b->notify_baton2, notify, pool);
}
/* Set *OUT_RANGELIST to the intersection of IN_RANGELIST with the simple
@@ -5445,7 +5488,7 @@ record_skips_in_mergeinfo(const char *mergeinfo_path,
### TODO: An empty range is fine if the skipped path doesn't
### inherit any mergeinfo from a parent, but if it does
### we need to account for that. See issue #3440
- ### http://subversion.tigris.org/issues/show_bug.cgi?id=3440. */
+ ### https://issues.apache.org/jira/browse/SVN-3440. */
svn_hash_sets(merges, skipped_abspath,
apr_array_make(scratch_pool, 0,
sizeof(svn_merge_range_t *)));
@@ -5554,7 +5597,7 @@ svn_client__make_merge_conflict_error(svn_client__conflict_report_t *report,
defined in get_mergeinfo_paths(). Remove any paths absent from disk
or scheduled for deletion from CHILDREN_WITH_MERGEINFO which are equal to
or are descendants of TARGET_WCPATH by setting those children to NULL. */
-static void
+static svn_error_t *
remove_absent_children(const char *target_wcpath,
apr_array_header_t *children_with_mergeinfo)
{
@@ -5569,9 +5612,10 @@ remove_absent_children(const char *target_wcpath,
if ((child->absent || child->scheduled_for_deletion)
&& svn_dirent_is_ancestor(target_wcpath, child->abspath))
{
- svn_sort__array_delete(children_with_mergeinfo, i--, 1);
+ SVN_ERR(svn_sort__array_delete2(children_with_mergeinfo, i--, 1));
}
}
+ return SVN_NO_ERROR;
}
/* Helper for do_directory_merge() to handle the case where a merge editor
@@ -5586,14 +5630,14 @@ remove_absent_children(const char *target_wcpath,
MERGE_B->target->abspath, this must always be present in
CHILDREN_WITH_MERGEINFO so this is never removed by this
function. */
-static void
+static svn_error_t *
remove_children_with_deleted_mergeinfo(merge_cmd_baton_t *merge_b,
apr_array_header_t *children_with_mergeinfo)
{
int i;
if (!merge_b->paths_with_deleted_mergeinfo)
- return;
+ return SVN_NO_ERROR;
/* CHILDREN_WITH_MERGEINFO[0] is the always the merge target
so start at the first child. */
@@ -5604,9 +5648,10 @@ remove_children_with_deleted_mergeinfo(merge_cmd_baton_t *merge_b,
if (svn_hash_gets(merge_b->paths_with_deleted_mergeinfo, child->abspath))
{
- svn_sort__array_delete(children_with_mergeinfo, i--, 1);
+ SVN_ERR(svn_sort__array_delete2(children_with_mergeinfo, i--, 1));
}
}
+ return SVN_NO_ERROR;
}
/* Helper for do_directory_merge().
@@ -5932,7 +5977,7 @@ get_most_inclusive_rev(const apr_array_header_t *children_with_mergeinfo,
remaining_ranges is inclusive of END_REV, Slice the first range in
to two at END_REV. All the allocations are persistent and allocated
from POOL. */
-static void
+static svn_error_t *
slice_remaining_ranges(apr_array_header_t *children_with_mergeinfo,
svn_boolean_t is_rollback, svn_revnum_t end_rev,
apr_pool_t *pool)
@@ -5962,10 +6007,12 @@ slice_remaining_ranges(apr_array_header_t *children_with_mergeinfo,
split_range2->start = end_rev;
APR_ARRAY_IDX(child->remaining_ranges, 0,
svn_merge_range_t *) = split_range1;
- svn_sort__array_insert(child->remaining_ranges, &split_range2, 1);
+ SVN_ERR(svn_sort__array_insert2(child->remaining_ranges,
+ &split_range2, 1));
}
}
}
+ return SVN_NO_ERROR;
}
/* Helper for do_directory_merge().
@@ -5977,7 +6024,7 @@ slice_remaining_ranges(apr_array_header_t *children_with_mergeinfo,
If a range is removed from a child's remaining_ranges array, allocate the
new remaining_ranges array in POOL.
*/
-static void
+static svn_error_t *
remove_first_range_from_remaining_ranges(svn_revnum_t revision,
apr_array_header_t
*children_with_mergeinfo,
@@ -5998,10 +6045,11 @@ remove_first_range_from_remaining_ranges(svn_revnum_t revision,
APR_ARRAY_IDX(child->remaining_ranges, 0, svn_merge_range_t *);
if (first_range->end == revision)
{
- svn_sort__array_delete(child->remaining_ranges, 0, 1);
+ SVN_ERR(svn_sort__array_delete2(child->remaining_ranges, 0, 1));
}
}
}
+ return SVN_NO_ERROR;
}
/* Get a file's content and properties from the repository.
@@ -6087,7 +6135,7 @@ get_child_with_mergeinfo(const apr_array_header_t *children_with_mergeinfo,
out of order and then sort afterwards. (One caller is doing a qsort
after calling this anyway.)
*/
-static void
+static svn_error_t *
insert_child_to_merge(apr_array_header_t *children_with_mergeinfo,
const svn_client__merge_path_t *insert_element,
apr_pool_t *pool)
@@ -6101,7 +6149,9 @@ insert_child_to_merge(apr_array_header_t *children_with_mergeinfo,
compare_merge_path_t_as_paths);
new_element = svn_client__merge_path_dup(insert_element, pool);
- svn_sort__array_insert(children_with_mergeinfo, &new_element, insert_index);
+ SVN_ERR(svn_sort__array_insert2(children_with_mergeinfo,
+ &new_element, insert_index));
+ return SVN_NO_ERROR;
}
/* Helper for get_mergeinfo_paths().
@@ -6162,7 +6212,7 @@ insert_parent_and_sibs_of_sw_absent_del_subtree(
parent->missing_child = child->absent;
parent->switched_child = child->switched;
/* Insert PARENT into CHILDREN_WITH_MERGEINFO. */
- insert_child_to_merge(children_with_mergeinfo, parent, pool);
+ SVN_ERR(insert_child_to_merge(children_with_mergeinfo, parent, pool));
/* Increment for loop index so we don't process the inserted element. */
(*curr_index)++;
} /*(parent == NULL) */
@@ -6199,8 +6249,8 @@ insert_parent_and_sibs_of_sw_absent_del_subtree(
sibling_of_missing = svn_client__merge_path_create(child_abspath,
pool);
- insert_child_to_merge(children_with_mergeinfo, sibling_of_missing,
- pool);
+ SVN_ERR(insert_child_to_merge(children_with_mergeinfo,
+ sibling_of_missing, pool));
}
}
@@ -6541,8 +6591,8 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo,
svn_client__merge_path_t *switched_child =
svn_client__merge_path_create(wc_path, result_pool);
switched_child->switched = TRUE;
- insert_child_to_merge(children_with_mergeinfo, switched_child,
- result_pool);
+ SVN_ERR(insert_child_to_merge(children_with_mergeinfo,
+ switched_child, result_pool));
}
}
}
@@ -6594,8 +6644,8 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo,
}
if (new_shallow_child)
- insert_child_to_merge(children_with_mergeinfo, shallow_child,
- result_pool);
+ SVN_ERR(insert_child_to_merge(children_with_mergeinfo,
+ shallow_child, result_pool));
}
}
@@ -6624,8 +6674,8 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo,
svn_client__merge_path_t *absent_child =
svn_client__merge_path_create(wc_path, result_pool);
absent_child->absent = TRUE;
- insert_child_to_merge(children_with_mergeinfo, absent_child,
- result_pool);
+ SVN_ERR(insert_child_to_merge(children_with_mergeinfo,
+ absent_child, result_pool));
}
}
}
@@ -6638,8 +6688,8 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo,
svn_client__merge_path_t *target_child =
svn_client__merge_path_create(target->abspath,
result_pool);
- insert_child_to_merge(children_with_mergeinfo, target_child,
- result_pool);
+ SVN_ERR(insert_child_to_merge(children_with_mergeinfo, target_child,
+ result_pool));
}
/* Case 8: Path is an immediate *directory* child of
@@ -6682,8 +6732,8 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo,
&& depth == svn_depth_immediates)
immediate_child->immediate_child_dir = TRUE;
- insert_child_to_merge(children_with_mergeinfo,
- immediate_child, result_pool);
+ SVN_ERR(insert_child_to_merge(children_with_mergeinfo,
+ immediate_child, result_pool));
}
}
}
@@ -6775,9 +6825,9 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo,
child_of_noninheritable =
svn_client__merge_path_create(child_abspath, result_pool);
child_of_noninheritable->child_of_noninheritable = TRUE;
- insert_child_to_merge(children_with_mergeinfo,
- child_of_noninheritable,
- result_pool);
+ SVN_ERR(insert_child_to_merge(children_with_mergeinfo,
+ child_of_noninheritable,
+ result_pool));
if (!dry_run && same_repos)
{
svn_mergeinfo_t mergeinfo;
@@ -7201,7 +7251,7 @@ normalize_merge_sources_internal(apr_array_header_t **merge_sources_p,
new_segment->path = original_repos_relpath;
new_segment->range_start = original_revision;
new_segment->range_end = original_revision;
- svn_sort__array_insert(segments, &new_segment, 0);
+ SVN_ERR(svn_sort__array_insert2(segments, &new_segment, 0));
}
}
}
@@ -7596,16 +7646,15 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog,
/* ### Create a fake copy of merge_target as we don't keep
remaining_ranges in sync (yet). */
- target_info = apr_pcalloc(scratch_pool, sizeof(*target_info));
-
- target_info->abspath = merge_target->abspath;
+ target_info = svn_client__merge_path_create(merge_target->abspath,
+ scratch_pool);
target_info->remaining_ranges = ranges_to_merge;
APR_ARRAY_PUSH(child_with_mergeinfo, svn_client__merge_path_t *)
= target_info;
/* And store in baton to allow using it from notify_merge_begin() */
- merge_b->notify_begin.nodes_with_mergeinfo = child_with_mergeinfo;
+ merge_b->children_with_mergeinfo = child_with_mergeinfo;
}
while (ranges_to_merge->nelts > 0)
@@ -7648,19 +7697,10 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog,
do a text-n-props merge; otherwise, do a delete-n-add merge. */
if (! (merge_b->diff_ignore_ancestry || sources_related))
{
- struct merge_dir_baton_t dir_baton;
+ void *dir_baton = open_dir_for_replace_single_file(iterpool);
void *file_baton;
svn_boolean_t skip;
- /* Initialize minimal dir baton to allow calculating 'R'eplace
- from 'D'elete + 'A'dd. */
-
- memset(&dir_baton, 0, sizeof(dir_baton));
- dir_baton.pool = iterpool;
- dir_baton.tree_conflict_reason = CONFLICT_REASON_NONE;
- dir_baton.tree_conflict_action = svn_wc_conflict_action_edit;
- dir_baton.skip_reason = svn_wc_notify_state_unknown;
-
/* Delete... */
file_baton = NULL;
skip = FALSE;
@@ -7668,7 +7708,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog,
left_source,
NULL /* right_source */,
NULL /* copyfrom_source */,
- &dir_baton,
+ dir_baton,
processor,
iterpool, iterpool));
if (! skip)
@@ -7687,7 +7727,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog,
NULL /* left_source */,
right_source,
NULL /* copyfrom_source */,
- &dir_baton,
+ dir_baton,
processor,
iterpool, iterpool));
if (! skip)
@@ -7758,7 +7798,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog,
(This list is used from notify_merge_begin)
Directory merges use remove_first_range_from_remaining_ranges() */
- svn_sort__array_delete(ranges_to_merge, 0, 1);
+ SVN_ERR(svn_sort__array_delete2(ranges_to_merge, 0, 1));
}
merge_b->notify_begin.last_abspath = NULL;
} /* !merge_b->record_only */
@@ -7819,7 +7859,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog,
}
}
- merge_b->notify_begin.nodes_with_mergeinfo = NULL;
+ merge_b->children_with_mergeinfo = NULL;
svn_pool_destroy(iterpool);
@@ -7878,7 +7918,7 @@ process_children_with_new_mergeinfo(merge_cmd_baton_t *merge_b,
was added (with preexisting mergeinfo) by the merge. That's actually
more correct, since the inherited mergeinfo likely describes
non-existent or unrelated merge history, but it's not quite so simple
- as that, see http://subversion.tigris.org/issues/show_bug.cgi?id=4309
+ as that, see https://issues.apache.org/jira/browse/SVN-4309
*/
/* Get the path's new explicit mergeinfo... */
@@ -7945,7 +7985,8 @@ process_children_with_new_mergeinfo(merge_cmd_baton_t *merge_b,
/* Set the path's remaining_ranges equal to its parent's. */
new_child->remaining_ranges = svn_rangelist_dup(
parent->remaining_ranges, pool);
- insert_child_to_merge(children_with_mergeinfo, new_child, pool);
+ SVN_ERR(insert_child_to_merge(children_with_mergeinfo,
+ new_child, pool));
}
}
}
@@ -8269,7 +8310,7 @@ flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge,
merge_b->target->abspath, depth, merge_b->ctx->wc_ctx,
merge_b->ra_session1, scratch_pool, iterpool));
- /* Issue #4056: Walk NOTIFY_B->CHILDREN_WITH_MERGEINFO reverse depth-first
+ /* Issue #4056: Walk CHILDREN_WITH_MERGEINFO reverse depth-first
order. This way each child knows if it has operative missing/switched
children which necessitates non-inheritable mergeinfo. */
for (i = children_with_mergeinfo->nelts - 1; i >= 0; i--)
@@ -8432,7 +8473,7 @@ flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge,
}
else /* child->record_mergeinfo */
{
- /* If CHILD is in NOTIFY_B->CHILDREN_WITH_MERGEINFO simply
+ /* If CHILD is in CHILDREN_WITH_MERGEINFO simply
because it had no explicit mergeinfo of its own at the
start of the merge but is the child of of some path with
non-inheritable mergeinfo, then the explicit mergeinfo it
@@ -8457,7 +8498,7 @@ flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge,
If RESULT_CATALOG is NULL then record mergeinfo describing a merge of
MERGED_RANGE->START:MERGED_RANGE->END from the repository relative path
MERGEINFO_FSPATH to the merge target (and possibly its subtrees) described
- by NOTIFY_B->CHILDREN_WITH_MERGEINFO -- see the global comment
+ by CHILDREN_WITH_MERGEINFO -- see the global comment
'THE CHILDREN_WITH_MERGEINFO ARRAY'. Obviously this should only
be called if recording mergeinfo -- see doc string for RECORD_MERGEINFO().
@@ -8508,10 +8549,10 @@ record_mergeinfo_for_dir_merge(svn_mergeinfo_catalog_t result_catalog,
range.inheritable = TRUE;
/* Remove absent children at or under MERGE_B->target->abspath from
- NOTIFY_B->CHILDREN_WITH_MERGEINFO
+ CHILDREN_WITH_MERGEINFO
before we calculate the merges performed. */
- remove_absent_children(merge_b->target->abspath,
- children_with_mergeinfo);
+ SVN_ERR(remove_absent_children(merge_b->target->abspath,
+ children_with_mergeinfo));
/* Determine which subtrees of interest need mergeinfo recorded... */
SVN_ERR(flag_subtrees_needing_mergeinfo(operative_merge, &range,
@@ -9334,7 +9375,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog,
/* Point our RA_SESSION to the URL of our youngest merge source side. */
ra_session = is_rollback ? merge_b->ra_session1 : merge_b->ra_session2;
- /* Fill NOTIFY_B->CHILDREN_WITH_MERGEINFO with child paths (const
+ /* Fill CHILDREN_WITH_MERGEINFO with child paths (const
svn_client__merge_path_t *) which might have intersecting merges
because they meet one or more of the criteria described in
get_mergeinfo_paths(). Here the paths are arranged in a depth
@@ -9344,13 +9385,13 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog,
merge_b->dry_run, merge_b->same_repos,
merge_b->ctx, scratch_pool, scratch_pool));
- /* The first item from the NOTIFY_B->CHILDREN_WITH_MERGEINFO is always
+ /* The first item from the CHILDREN_WITH_MERGEINFO is always
the target thanks to depth-first ordering. */
target_merge_path = APR_ARRAY_IDX(children_with_mergeinfo, 0,
svn_client__merge_path_t *);
/* If we are honoring mergeinfo, then for each item in
- NOTIFY_B->CHILDREN_WITH_MERGEINFO, we need to calculate what needs to be
+ CHILDREN_WITH_MERGEINFO, we need to calculate what needs to be
merged, and then merge it. Otherwise, we just merge what we were asked
to merge across the whole tree. */
SVN_ERR(populate_remaining_ranges(children_with_mergeinfo,
@@ -9370,7 +9411,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog,
/* The merge target TARGET_ABSPATH and/or its subtrees may not need all
of SOURCE->rev1:rev2 applied. So examine
- NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the oldest starting
+ CHILDREN_WITH_MERGEINFO to find the oldest starting
revision that actually needs to be merged (for reverse merges this is
the youngest starting revision).
@@ -9408,7 +9449,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog,
/* Is there anything to merge? */
if (SVN_IS_VALID_REVNUM(start_rev))
{
- /* Now examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the oldest
+ /* Now examine CHILDREN_WITH_MERGEINFO to find the oldest
ending revision that actually needs to be merged (for reverse
merges this is the youngest ending revision). */
svn_revnum_t end_rev =
@@ -9417,7 +9458,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog,
/* While END_REV is valid, do the following:
- 1. Tweak each NOTIFY_B->CHILDREN_WITH_MERGEINFO element so that
+ 1. Tweak each CHILDREN_WITH_MERGEINFO element so that
the element's remaining_ranges member has as its first element
a range that ends with end_rev.
@@ -9425,17 +9466,17 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog,
on MERGE_B->target->abspath for start_rev:end_rev.
3. Remove the first element from each
- NOTIFY_B->CHILDREN_WITH_MERGEINFO element's remaining_ranges
+ CHILDREN_WITH_MERGEINFO element's remaining_ranges
member.
- 4. Again examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the most
+ 4. Again examine CHILDREN_WITH_MERGEINFO to find the most
inclusive starting revision that actually needs to be merged and
update start_rev. This prevents us from needlessly contacting the
repository and doing a diff where we describe the entire target
tree as *not* needing any of the requested range. This can happen
whenever we have mergeinfo with gaps in it for the merge source.
- 5. Again examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the most
+ 5. Again examine CHILDREN_WITH_MERGEINFO to find the most
inclusive ending revision that actually needs to be merged and
update end_rev.
@@ -9477,8 +9518,9 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog,
svn_pool_clear(iterpool);
- slice_remaining_ranges(children_with_mergeinfo,
- is_rollback, end_rev, scratch_pool);
+ SVN_ERR(slice_remaining_ranges(children_with_mergeinfo,
+ is_rollback, end_rev,
+ scratch_pool));
/* Reset variables that must be reset for every drive */
merge_b->notify_begin.last_abspath = NULL;
@@ -9496,23 +9538,23 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog,
/* If any paths picked up explicit mergeinfo as a result of
the merge we need to make sure any mergeinfo those paths
inherited is recorded and then add these paths to
- NOTIFY_B->CHILDREN_WITH_MERGEINFO.*/
+ CHILDREN_WITH_MERGEINFO.*/
SVN_ERR(process_children_with_new_mergeinfo(
merge_b, children_with_mergeinfo,
scratch_pool));
/* If any subtrees had their explicit mergeinfo deleted as a
result of the merge then remove these paths from
- NOTIFY_B->CHILDREN_WITH_MERGEINFO since there is no need
+ CHILDREN_WITH_MERGEINFO since there is no need
to consider these subtrees for subsequent editor drives
nor do we want to record mergeinfo on them describing
the merge itself. */
- remove_children_with_deleted_mergeinfo(
- merge_b, children_with_mergeinfo);
+ SVN_ERR(remove_children_with_deleted_mergeinfo(
+ merge_b, children_with_mergeinfo));
/* Prepare for the next iteration (if any). */
- remove_first_range_from_remaining_ranges(
- end_rev, children_with_mergeinfo, scratch_pool);
+ SVN_ERR(remove_first_range_from_remaining_ranges(
+ end_rev, children_with_mergeinfo, scratch_pool));
/* If we raised any conflicts, break out and report how much
we have merged. */
@@ -9634,7 +9676,7 @@ do_directory_merge(svn_mergeinfo_catalog_t result_catalog,
apr_array_make(scratch_pool, 16, sizeof(svn_client__merge_path_t *));
/* And make it read-only accessible from the baton */
- merge_b->notify_begin.nodes_with_mergeinfo = children_with_mergeinfo;
+ merge_b->children_with_mergeinfo = children_with_mergeinfo;
/* If we are not honoring mergeinfo we can skip right to the
business of merging changes! */
@@ -9652,7 +9694,7 @@ do_directory_merge(svn_mergeinfo_catalog_t result_catalog,
processor, depth,
merge_b, result_pool, scratch_pool));
- merge_b->notify_begin.nodes_with_mergeinfo = NULL;
+ merge_b->children_with_mergeinfo = NULL;
return SVN_NO_ERROR;
}
@@ -9889,28 +9931,13 @@ do_merge(apr_hash_t **modified_subtrees,
merge_cmd_baton.added_abspaths = apr_hash_make(result_pool);
merge_cmd_baton.tree_conflicted_abspaths = apr_hash_make(result_pool);
- {
- svn_diff_tree_processor_t *merge_processor;
-
- merge_processor = svn_diff__tree_processor_create(&merge_cmd_baton,
- scratch_pool);
-
- merge_processor->dir_opened = merge_dir_opened;
- merge_processor->dir_changed = merge_dir_changed;
- merge_processor->dir_added = merge_dir_added;
- merge_processor->dir_deleted = merge_dir_deleted;
- merge_processor->dir_closed = merge_dir_closed;
-
- merge_processor->file_opened = merge_file_opened;
- merge_processor->file_changed = merge_file_changed;
- merge_processor->file_added = merge_file_added;
- merge_processor->file_deleted = merge_file_deleted;
- /* Not interested in file_closed() */
-
- merge_processor->node_absent = merge_node_absent;
+ merge_cmd_baton.notify_func = notify_merging;
+ merge_cmd_baton.notify_baton = &merge_cmd_baton.notify_begin;
+ merge_cmd_baton.notify_begin.merge_b = &merge_cmd_baton;
+ merge_cmd_baton.notify_begin.notify_func2 = ctx->notify_func2;
+ merge_cmd_baton.notify_begin.notify_baton2 = ctx->notify_baton2;
- processor = merge_processor;
- }
+ processor = merge_apply_processor(&merge_cmd_baton, scratch_pool);
if (src_session)
{
diff --git a/subversion/libsvn_client/mtcc.c b/subversion/libsvn_client/mtcc.c
index 75889cad2746..48ddcccbc164 100644
--- a/subversion/libsvn_client/mtcc.c
+++ b/subversion/libsvn_client/mtcc.c
@@ -604,7 +604,7 @@ mtcc_op_contains_non_delete(const mtcc_op_t *op)
static svn_error_t *
mtcc_add_delete(const char *relpath,
svn_boolean_t for_move,
- svn_client__mtcc_t *mtcc,
+ svn_client__mtcc_t *mtcc,
apr_pool_t *scratch_pool)
{
mtcc_op_t *op;
@@ -636,7 +636,7 @@ mtcc_add_delete(const char *relpath,
{
/* Allow deleting directories, that are unmodified except for
one or more deleted descendants */
-
+
SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE,
FALSE, FALSE, mtcc->pool, scratch_pool));
diff --git a/subversion/libsvn_client/patch.c b/subversion/libsvn_client/patch.c
index 1b2d86b1da94..549d20c11484 100644
--- a/subversion/libsvn_client/patch.c
+++ b/subversion/libsvn_client/patch.c
@@ -343,7 +343,9 @@ strip_path(const char **result, const char *path, int strip_count,
components = svn_path_decompose(path, scratch_pool);
if (strip_count > components->nelts)
return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL,
- _("Cannot strip %u components from '%s'"),
+ Q_("Cannot strip %u component from '%s'",
+ "Cannot strip %u components from '%s'",
+ strip_count),
strip_count,
svn_dirent_local_style(path, scratch_pool));
@@ -1018,6 +1020,7 @@ init_patch_target(patch_target_t **patch_target,
target_content_t *content;
svn_boolean_t has_text_changes = FALSE;
svn_boolean_t follow_moves;
+ const char *tempdir_abspath;
has_text_changes = ((patch->hunks && patch->hunks->nelts > 0)
|| patch->binary_patch);
@@ -1223,8 +1226,10 @@ init_patch_target(patch_target_t **patch_target,
}
/* Open a temporary file to write the patched result to. */
+ SVN_ERR(svn_wc__get_tmpdir(&tempdir_abspath, wc_ctx,
+ target->local_abspath, scratch_pool, scratch_pool));
SVN_ERR(svn_io_open_unique_file3(&target->patched_file,
- &target->patched_path, NULL,
+ &target->patched_path, tempdir_abspath,
remove_tempfiles ?
svn_io_file_del_on_pool_cleanup :
svn_io_file_del_none,
@@ -1236,7 +1241,7 @@ init_patch_target(patch_target_t **patch_target,
/* Open a temporary stream to write rejected hunks to. */
SVN_ERR(svn_stream_open_unique(&target->reject_stream,
- &target->reject_path, NULL,
+ &target->reject_path, tempdir_abspath,
remove_tempfiles ?
svn_io_file_del_on_pool_cleanup :
svn_io_file_del_none,
@@ -2145,8 +2150,8 @@ reject_hunk(patch_target_t *target, target_content_t *content,
if (prop_name)
{
/* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. */
- svn_stream_printf(target->reject_stream,
- pool, "Property: %s" APR_EOL_STR, prop_name);
+ SVN_ERR(svn_stream_printf(target->reject_stream,
+ pool, "Property: %s" APR_EOL_STR, prop_name));
atat = prop_atat;
}
else
diff --git a/subversion/libsvn_client/ra.c b/subversion/libsvn_client/ra.c
index d50e720bd0f1..c1b71e65fc61 100644
--- a/subversion/libsvn_client/ra.c
+++ b/subversion/libsvn_client/ra.c
@@ -402,8 +402,7 @@ svn_client__open_ra_session_internal(svn_ra_session_t **ra_session,
}
}
- /* If the caller allows for auto-following redirections, and the
- RA->open() call above reveals a CORRECTED_URL, try the new URL.
+ /* If the caller allows for auto-following redirections, try the new URL.
We'll do this in a loop up to some maximum number follow-and-retry
attempts. */
if (corrected_url)
@@ -414,12 +413,14 @@ svn_client__open_ra_session_internal(svn_ra_session_t **ra_session,
*corrected_url = NULL;
while (attempts_left--)
{
- const char *corrected = NULL;
+ const char *corrected = NULL; /* canonicalized version */
+ const char *redirect_url = NULL; /* non-canonicalized version */
/* Try to open the RA session. If this is our last attempt,
don't accept corrected URLs from the RA provider. */
- SVN_ERR(svn_ra_open4(ra_session,
+ SVN_ERR(svn_ra_open5(ra_session,
attempts_left == 0 ? NULL : &corrected,
+ attempts_left == 0 ? NULL : &redirect_url,
base_url, uuid, cbtable, cb, ctx->config,
result_pool));
@@ -441,19 +442,28 @@ svn_client__open_ra_session_internal(svn_ra_session_t **ra_session,
*corrected_url = corrected;
/* Make sure we've not attempted this URL before. */
- if (svn_hash_gets(attempted, corrected))
+ if (svn_hash_gets(attempted, redirect_url))
return svn_error_createf(SVN_ERR_CLIENT_CYCLE_DETECTED, NULL,
_("Redirect cycle detected for URL '%s'"),
- corrected);
+ redirect_url);
+
+ /*
+ * Remember this redirect URL so we don't wind up in a loop.
+ *
+ * Store the non-canonicalized version of the URL. The canonicalized
+ * version is insufficient for loop detection because we might not get
+ * an exact match against URLs used by the RA protocol-layer (the URL
+ * used by the protocol may contain trailing slashes, for example,
+ * which are stripped during canonicalization).
+ */
+ svn_hash_sets(attempted, redirect_url, (void *)1);
- /* Remember this CORRECTED_URL so we don't wind up in a loop. */
- svn_hash_sets(attempted, corrected, (void *)1);
base_url = corrected;
}
}
else
{
- SVN_ERR(svn_ra_open4(ra_session, NULL, base_url,
+ SVN_ERR(svn_ra_open5(ra_session, NULL, NULL, base_url,
uuid, cbtable, cb, ctx->config, result_pool));
}
diff --git a/subversion/libsvn_client/repos_diff.c b/subversion/libsvn_client/repos_diff.c
index 58fe8aaa1dae..885b5eba2bc0 100644
--- a/subversion/libsvn_client/repos_diff.c
+++ b/subversion/libsvn_client/repos_diff.c
@@ -51,6 +51,7 @@
#include "private/svn_subr_private.h"
#include "private/svn_wc_private.h"
#include "private/svn_editor.h"
+#include "private/svn_sorts_private.h"
/* Overall crawler editor baton. */
struct edit_baton {
@@ -380,10 +381,10 @@ get_file_from_ra(struct file_baton *fb,
way. Hence this little hack: We populate FILE_BATON->PROPCHANGES only
with *actual* property changes.
- See http://subversion.tigris.org/issues/show_bug.cgi?id=3657#desc9 and
+ See https://issues.apache.org/jira/browse/SVN-3657#desc9 and
http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details.
*/
-static void
+static svn_error_t *
remove_non_prop_changes(apr_hash_t *pristine_props,
apr_array_header_t *changes)
{
@@ -391,7 +392,7 @@ remove_non_prop_changes(apr_hash_t *pristine_props,
/* For added nodes, there is nothing to filter. */
if (apr_hash_count(pristine_props) == 0)
- return;
+ return SVN_NO_ERROR;
for (i = 0; i < changes->nelts; i++)
{
@@ -404,18 +405,13 @@ remove_non_prop_changes(apr_hash_t *pristine_props,
if (old_val && svn_string_compare(old_val, change->value))
{
- int j;
-
- /* Remove the matching change by shifting the rest */
- for (j = i; j < changes->nelts - 1; j++)
- {
- APR_ARRAY_IDX(changes, j, svn_prop_t)
- = APR_ARRAY_IDX(changes, j+1, svn_prop_t);
- }
- changes->nelts--;
+ /* Remove the matching change and re-check the current index */
+ SVN_ERR(svn_sort__array_delete2(changes, i, 1));
+ i--;
}
}
}
+ return SVN_NO_ERROR;
}
/* Get the empty file associated with the edit baton. This is cached so
@@ -1015,7 +1011,7 @@ close_file(void *file_baton,
}
if (fb->pristine_props)
- remove_non_prop_changes(fb->pristine_props, fb->propchanges);
+ SVN_ERR(remove_non_prop_changes(fb->pristine_props, fb->propchanges));
right_props = svn_prop__patch(fb->pristine_props, fb->propchanges,
fb->pool);
@@ -1088,7 +1084,7 @@ close_directory(void *dir_baton,
if (db->propchanges->nelts > 0)
{
- remove_non_prop_changes(pristine_props, db->propchanges);
+ SVN_ERR(remove_non_prop_changes(pristine_props, db->propchanges));
}
if (db->propchanges->nelts > 0 || db->added)
diff --git a/subversion/libsvn_client/revert.c b/subversion/libsvn_client/revert.c
index d827014ebe1e..96495f15d257 100644
--- a/subversion/libsvn_client/revert.c
+++ b/subversion/libsvn_client/revert.c
@@ -51,42 +51,32 @@ struct revert_with_write_lock_baton {
const apr_array_header_t *changelists;
svn_boolean_t clear_changelists;
svn_boolean_t metadata_only;
+ svn_boolean_t added_keep_local;
svn_client_ctx_t *ctx;
};
/* (Note: All arguments are in the baton above.)
- Attempt to revert LOCAL_ABSPATH.
+ Attempt to revert LOCAL_ABSPATH by calling svn_wc_revert6(), which
+ see for further details.
- If DEPTH is svn_depth_empty, revert just the properties on the
- directory; else if svn_depth_files, revert the properties and any
- files immediately under the directory; else if
- svn_depth_immediates, revert all of the preceding plus properties
- on immediate subdirectories; else if svn_depth_infinity, revert
- path and everything under it fully recursively.
-
- CHANGELISTS is an array of const char * changelist names, used as a
- restrictive filter on items reverted; that is, don't revert any
- item unless it's a member of one of those changelists. If
- CHANGELISTS is empty (or altogether NULL), no changelist filtering occurs.
-
- Consult CTX to determine whether or not to revert timestamp to the
- time of last commit ('use-commit-times = yes').
-
- If PATH is unversioned, return SVN_ERR_UNVERSIONED_RESOURCE. */
+ If the target isn't versioned, send a 'skip' notification and return
+ no error.
+ */
static svn_error_t *
revert(void *baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
struct revert_with_write_lock_baton *b = baton;
svn_error_t *err;
- err = svn_wc_revert5(b->ctx->wc_ctx,
+ err = svn_wc_revert6(b->ctx->wc_ctx,
b->local_abspath,
b->depth,
b->use_commit_times,
b->changelists,
b->clear_changelists,
b->metadata_only,
+ b->added_keep_local,
b->ctx->cancel_func, b->ctx->cancel_baton,
b->ctx->notify_func2, b->ctx->notify_baton2,
scratch_pool);
@@ -123,11 +113,12 @@ revert(void *baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool)
svn_error_t *
-svn_client_revert3(const apr_array_header_t *paths,
+svn_client_revert4(const apr_array_header_t *paths,
svn_depth_t depth,
const apr_array_header_t *changelists,
svn_boolean_t clear_changelists,
svn_boolean_t metadata_only,
+ svn_boolean_t added_keep_local,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
@@ -183,6 +174,7 @@ svn_client_revert3(const apr_array_header_t *paths,
baton.changelists = changelists;
baton.clear_changelists = clear_changelists;
baton.metadata_only = metadata_only;
+ baton.added_keep_local = added_keep_local;
baton.ctx = ctx;
err = svn_wc__is_wcroot(&wc_root, ctx->wc_ctx, local_abspath, iterpool);
diff --git a/subversion/libsvn_client/revisions.c b/subversion/libsvn_client/revisions.c
index 4bfbfc3fc32a..c209c9ea385e 100644
--- a/subversion/libsvn_client/revisions.c
+++ b/subversion/libsvn_client/revisions.c
@@ -146,7 +146,14 @@ svn_client__get_revision_number(svn_revnum_t *revnum,
scratch_pool));
if (revision->kind == svn_opt_revision_previous)
- (*revnum)--;
+ {
+ if (*revnum == 0)
+ return svn_error_createf(
+ SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Path '%s' has no previous revision"),
+ svn_dirent_local_style(local_abspath, scratch_pool));
+ --(*revnum);
+ }
}
break;
diff --git a/subversion/libsvn_client/shelf.c b/subversion/libsvn_client/shelf.c
new file mode 100644
index 000000000000..2bba1935384a
--- /dev/null
+++ b/subversion/libsvn_client/shelf.c
@@ -0,0 +1,1274 @@
+/*
+ * shelf.c: implementation of shelving
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+/* We define this here to remove any further warnings about the usage of
+ experimental functions in this file. */
+#define SVN_EXPERIMENTAL
+
+#include "svn_client.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_utf.h"
+#include "svn_ctype.h"
+#include "svn_props.h"
+
+#include "client.h"
+#include "private/svn_client_shelf.h"
+#include "private/svn_client_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_sorts_private.h"
+#include "svn_private_config.h"
+
+
+static svn_error_t *
+shelf_name_encode(char **encoded_name_p,
+ const char *name,
+ apr_pool_t *result_pool)
+{
+ char *encoded_name
+ = apr_palloc(result_pool, strlen(name) * 2 + 1);
+ char *out_pos = encoded_name;
+
+ if (name[0] == '\0')
+ return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
+ _("Shelf name cannot be the empty string"));
+
+ while (*name)
+ {
+ apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++));
+ out_pos += 2;
+ }
+ *encoded_name_p = encoded_name;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+shelf_name_decode(char **decoded_name_p,
+ const char *codename,
+ apr_pool_t *result_pool)
+{
+ svn_stringbuf_t *sb
+ = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool);
+ const char *input = codename;
+
+ while (*input)
+ {
+ int c;
+ int nchars;
+ int nitems = sscanf(input, "%02x%n", &c, &nchars);
+
+ if (nitems != 1 || nchars != 2)
+ return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
+ _("Shelve: Bad encoded name '%s'"), codename);
+ svn_stringbuf_appendbyte(sb, c);
+ input += 2;
+ }
+ *decoded_name_p = sb->data;
+ return SVN_NO_ERROR;
+}
+
+/* Set *NAME to the shelf name from FILENAME, if FILENAME names a '.current'
+ * file, else to NULL. */
+static svn_error_t *
+shelf_name_from_filename(char **name,
+ const char *filename,
+ apr_pool_t *result_pool)
+{
+ size_t len = strlen(filename);
+ static const char suffix[] = ".current";
+ size_t suffix_len = sizeof(suffix) - 1;
+
+ if (len > suffix_len && strcmp(filename + len - suffix_len, suffix) == 0)
+ {
+ char *codename = apr_pstrndup(result_pool, filename, len - suffix_len);
+ SVN_ERR(shelf_name_decode(name, codename, result_pool));
+ }
+ else
+ {
+ *name = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Set *DIR to the shelf storage directory inside the WC's administrative
+ * area. Ensure the directory exists. */
+static svn_error_t *
+get_shelves_dir(char **dir,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *experimental_abspath;
+
+ SVN_ERR(svn_wc__get_experimental_dir(&experimental_abspath,
+ wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ *dir = svn_dirent_join(experimental_abspath, "shelves/v3", result_pool);
+
+ /* Ensure the directory exists. (Other versions of svn don't create it.) */
+ SVN_ERR(svn_io_make_dir_recursively(*dir, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *ABSPATH to the abspath of the file storage dir for SHELF
+ * version VERSION, no matter whether it exists.
+ */
+static svn_error_t *
+shelf_version_files_dir_abspath(const char **abspath,
+ svn_client__shelf_t *shelf,
+ int version,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *codename;
+ char *filename;
+
+ SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
+ filename = apr_psprintf(scratch_pool, "%s-%03d.wc", codename, version);
+ *abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Create a shelf-version object for a version that may or may not already
+ * exist on disk.
+ */
+static svn_error_t *
+shelf_version_create(svn_client__shelf_version_t **new_version_p,
+ svn_client__shelf_t *shelf,
+ int version_number,
+ apr_pool_t *result_pool)
+{
+ svn_client__shelf_version_t *shelf_version
+ = apr_pcalloc(result_pool, sizeof(*shelf_version));
+
+ shelf_version->shelf = shelf;
+ shelf_version->version_number = version_number;
+ SVN_ERR(shelf_version_files_dir_abspath(&shelf_version->files_dir_abspath,
+ shelf, version_number,
+ result_pool, result_pool));
+ *new_version_p = shelf_version;
+ return SVN_NO_ERROR;
+}
+
+/* Delete the storage for SHELF:VERSION. */
+static svn_error_t *
+shelf_version_delete(svn_client__shelf_t *shelf,
+ int version,
+ apr_pool_t *scratch_pool)
+{
+ const char *files_dir_abspath;
+
+ SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath,
+ shelf, version,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/,
+ NULL, NULL, /*cancel*/
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+get_log_abspath(char **log_abspath,
+ svn_client__shelf_t *shelf,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *codename;
+ const char *filename;
+
+ SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
+ filename = apr_pstrcat(scratch_pool, codename, ".log", SVN_VA_NULL);
+ *log_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Set SHELF->revprops by reading from its storage (the '.log' file).
+ * Set SHELF->revprops to empty if the storage file does not exist; this
+ * is not an error.
+ */
+static svn_error_t *
+shelf_read_revprops(svn_client__shelf_t *shelf,
+ apr_pool_t *scratch_pool)
+{
+ char *log_abspath;
+ svn_error_t *err;
+ svn_stream_t *stream;
+
+ SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
+
+ shelf->revprops = apr_hash_make(shelf->pool);
+ err = svn_stream_open_readonly(&stream, log_abspath,
+ scratch_pool, scratch_pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+ SVN_ERR(svn_hash_read2(shelf->revprops, stream, "PROPS-END", shelf->pool));
+ SVN_ERR(svn_stream_close(stream));
+ return SVN_NO_ERROR;
+}
+
+/* Write SHELF's revprops to its file storage.
+ */
+static svn_error_t *
+shelf_write_revprops(svn_client__shelf_t *shelf,
+ apr_pool_t *scratch_pool)
+{
+ char *log_abspath;
+ apr_file_t *file;
+ svn_stream_t *stream;
+
+ SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_io_file_open(&file, log_abspath,
+ APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE,
+ APR_FPROT_OS_DEFAULT, scratch_pool));
+ stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool);
+
+ SVN_ERR(svn_hash_write2(shelf->revprops, stream, "PROPS-END", scratch_pool));
+ SVN_ERR(svn_stream_close(stream));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_revprop_set(svn_client__shelf_t *shelf,
+ const char *prop_name,
+ const svn_string_t *prop_val,
+ apr_pool_t *scratch_pool)
+{
+ svn_hash_sets(shelf->revprops, apr_pstrdup(shelf->pool, prop_name),
+ svn_string_dup(prop_val, shelf->pool));
+ SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_revprop_set_all(svn_client__shelf_t *shelf,
+ apr_hash_t *revprop_table,
+ apr_pool_t *scratch_pool)
+{
+ if (revprop_table)
+ shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool);
+ else
+ shelf->revprops = apr_hash_make(shelf->pool);
+
+ SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_revprop_get(svn_string_t **prop_val,
+ svn_client__shelf_t *shelf,
+ const char *prop_name,
+ apr_pool_t *result_pool)
+{
+ *prop_val = svn_hash_gets(shelf->revprops, prop_name);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_revprop_list(apr_hash_t **props,
+ svn_client__shelf_t *shelf,
+ apr_pool_t *result_pool)
+{
+ *props = shelf->revprops;
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+get_current_abspath(char **current_abspath,
+ svn_client__shelf_t *shelf,
+ apr_pool_t *result_pool)
+{
+ char *codename;
+ char *filename;
+
+ SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
+ filename = apr_psprintf(result_pool, "%s.current", codename);
+ *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Read SHELF->max_version from its storage (the '.current' file).
+ * Set SHELF->max_version to -1 if that file does not exist.
+ */
+static svn_error_t *
+shelf_read_current(svn_client__shelf_t *shelf,
+ apr_pool_t *scratch_pool)
+{
+ char *current_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(get_current_abspath(&current_abspath, shelf, scratch_pool));
+ err = svn_io_read_version_file(&shelf->max_version,
+ current_abspath, scratch_pool);
+ if (err)
+ {
+ shelf->max_version = -1;
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+shelf_write_current(svn_client__shelf_t *shelf,
+ apr_pool_t *scratch_pool)
+{
+ char *current_abspath;
+
+ SVN_ERR(get_current_abspath(&current_abspath, shelf, scratch_pool));
+ SVN_ERR(svn_io_write_version_file(current_abspath, shelf->max_version,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/*-------------------------------------------------------------------------*/
+/* Status Reporting */
+
+/* Adjust a status STATUS_IN obtained from the shelf storage WC, to add
+ * shelf-related metadata:
+ * - changelist: 'svn:shelf:SHELFNAME'
+ */
+static svn_error_t *
+status_augment(svn_wc_status3_t **status_p,
+ const svn_wc_status3_t *status_in,
+ svn_client__shelf_version_t *shelf_version,
+ apr_pool_t *result_pool)
+{
+ *status_p = svn_wc_dup_status3(status_in, result_pool);
+ (*status_p)->changelist = apr_psprintf(result_pool, "svn:shelf:%s",
+ shelf_version->shelf->name);
+ return SVN_NO_ERROR;
+}
+
+/* Read status from shelf storage.
+ */
+static svn_error_t *
+status_read(svn_wc_status3_t **status,
+ svn_client__shelf_version_t *shelf_version,
+ const char *relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
+ char *abspath
+ = svn_dirent_join(shelf_version->files_dir_abspath, relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_wc_status3(status, ctx->wc_ctx, abspath,
+ result_pool, scratch_pool));
+ SVN_ERR(status_augment(status, *status, shelf_version, result_pool));
+ return SVN_NO_ERROR;
+}
+
+/* A visitor function type for use with shelf_status_walk().
+ * The same as svn_wc_status_func4_t except relpath instead of abspath.
+ */
+typedef svn_error_t *(*shelf_status_visitor_t)(void *baton,
+ const char *relpath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool);
+
+/* Baton for shelved_files_walk_visitor(). */
+struct shelf_status_baton_t
+{
+ svn_client__shelf_version_t *shelf_version;
+ shelf_status_visitor_t walk_func;
+ void *walk_baton;
+};
+
+/* Convert a svn_wc_status_func4_t callback invocation to call a
+ * shelf_status_visitor_t callback.
+ *
+ * Call BATON->walk_func(BATON->walk_baton, relpath, ...) for the shelved
+ * storage path ABSPATH, converting ABSPATH to a WC-relative path, and
+ * augmenting the STATUS.
+ *
+ * The opposite of wc_status_visitor().
+ *
+ * Implements svn_wc_status_func4_t. */
+static svn_error_t *
+shelf_status_visitor(void *baton,
+ const char *abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct shelf_status_baton_t *b = baton;
+ const char *relpath;
+ svn_wc_status3_t *new_status;
+
+ relpath = svn_dirent_skip_ancestor(b->shelf_version->files_dir_abspath,
+ abspath);
+ SVN_ERR(status_augment(&new_status, status, b->shelf_version, scratch_pool));
+ SVN_ERR(b->walk_func(b->walk_baton, relpath, new_status, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Report the shelved status of the path SHELF_VERSION:WC_RELPATH
+ * via WALK_FUNC(WALK_BATON, ...).
+ */
+static svn_error_t *
+shelf_status_visit_path(svn_client__shelf_version_t *shelf_version,
+ const char *wc_relpath,
+ shelf_status_visitor_t walk_func,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_status3_t *status;
+
+ SVN_ERR(status_read(&status, shelf_version, wc_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(walk_func(walk_baton, wc_relpath, status, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Report the shelved status of all the shelved paths in SHELF_VERSION
+ * at and under WC_RELPATH, via WALK_FUNC(WALK_BATON, ...).
+ */
+static svn_error_t *
+shelf_status_walk(svn_client__shelf_version_t *shelf_version,
+ const char *wc_relpath,
+ shelf_status_visitor_t walk_func,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
+ char *walk_root_abspath
+ = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
+ scratch_pool);
+ struct shelf_status_baton_t baton;
+ svn_error_t *err;
+
+ baton.shelf_version = shelf_version;
+ baton.walk_func = walk_func;
+ baton.walk_baton = walk_baton;
+ err = svn_wc_walk_status(ctx->wc_ctx, walk_root_abspath,
+ svn_depth_infinity,
+ FALSE /*get_all*/,
+ TRUE /*no_ignore*/,
+ FALSE /*ignore_text_mods*/,
+ NULL /*ignore_patterns: use the defaults*/,
+ shelf_status_visitor, &baton,
+ NULL, NULL, /*cancellation*/
+ scratch_pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for wc_status_visitor(). */
+typedef struct wc_status_baton_t
+{
+ svn_client__shelf_version_t *shelf_version;
+ svn_wc_status_func4_t walk_func;
+ void *walk_baton;
+} wc_status_baton_t;
+
+/* Convert a shelf_status_visitor_t callback invocation to call a
+ * svn_wc_status_func4_t callback.
+ *
+ * Call BATON->walk_func(BATON->walk_baton, abspath, ...) for the WC-
+ * relative path RELPATH, converting RELPATH to an abspath in the user's WC.
+ *
+ * The opposite of shelf_status_visitor().
+ *
+ * Implements shelf_status_visitor_t. */
+static svn_error_t *
+wc_status_visitor(void *baton,
+ const char *relpath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct wc_status_baton_t *b = baton;
+ svn_client__shelf_t *shelf = b->shelf_version->shelf;
+ const char *abspath = svn_dirent_join(shelf->wc_root_abspath, relpath,
+ scratch_pool);
+ SVN_ERR(b->walk_func(b->walk_baton, abspath, status, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_version_status_walk(svn_client__shelf_version_t *shelf_version,
+ const char *wc_relpath,
+ svn_wc_status_func4_t walk_func,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ wc_status_baton_t baton;
+
+ baton.shelf_version = shelf_version;
+ baton.walk_func = walk_func;
+ baton.walk_baton = walk_baton;
+ SVN_ERR(shelf_status_walk(shelf_version, wc_relpath,
+ wc_status_visitor, &baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/*-------------------------------------------------------------------------*/
+/* Shelf Storage */
+
+/* Construct a shelf object representing an empty shelf: no versions,
+ * no revprops, no looking to see if such a shelf exists on disk.
+ */
+static svn_error_t *
+shelf_construct(svn_client__shelf_t **shelf_p,
+ const char *name,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool)
+{
+ svn_client__shelf_t *shelf = apr_palloc(result_pool, sizeof(*shelf));
+ char *shelves_dir;
+
+ SVN_ERR(svn_client_get_wc_root(&shelf->wc_root_abspath,
+ local_abspath, ctx,
+ result_pool, result_pool));
+ SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
+ result_pool, result_pool));
+ shelf->shelves_dir = shelves_dir;
+ shelf->ctx = ctx;
+ shelf->pool = result_pool;
+
+ shelf->name = apr_pstrdup(result_pool, name);
+ shelf->revprops = apr_hash_make(result_pool);
+ shelf->max_version = 0;
+
+ *shelf_p = shelf;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_open_existing(svn_client__shelf_t **shelf_p,
+ const char *name,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(shelf_construct(shelf_p, name,
+ local_abspath, ctx, result_pool));
+ SVN_ERR(shelf_read_revprops(*shelf_p, result_pool));
+ SVN_ERR(shelf_read_current(*shelf_p, result_pool));
+ if ((*shelf_p)->max_version < 0)
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Shelf '%s' not found"),
+ name);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_open_or_create(svn_client__shelf_t **shelf_p,
+ const char *name,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool)
+{
+ svn_client__shelf_t *shelf;
+
+ SVN_ERR(shelf_construct(&shelf, name,
+ local_abspath, ctx, result_pool));
+ SVN_ERR(shelf_read_revprops(shelf, result_pool));
+ SVN_ERR(shelf_read_current(shelf, result_pool));
+ if (shelf->max_version < 0)
+ {
+ shelf->max_version = 0;
+ SVN_ERR(shelf_write_current(shelf, result_pool));
+ }
+ *shelf_p = shelf;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_close(svn_client__shelf_t *shelf,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_delete(const char *name,
+ const char *local_abspath,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__shelf_t *shelf;
+ int i;
+ char *abspath;
+
+ SVN_ERR(svn_client__shelf_open_existing(&shelf, name,
+ local_abspath, ctx, scratch_pool));
+
+ /* Remove the versions. */
+ for (i = shelf->max_version; i > 0; i--)
+ {
+ SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
+ }
+
+ /* Remove the other files */
+ SVN_ERR(get_log_abspath(&abspath, shelf, scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
+ SVN_ERR(get_current_abspath(&abspath, shelf, scratch_pool));
+ SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
+
+ SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Baton for paths_changed_visitor(). */
+struct paths_changed_walk_baton_t
+{
+ apr_hash_t *paths_hash;
+ const char *wc_root_abspath;
+ apr_pool_t *pool;
+};
+
+/* Add to the list(s) in BATON, the RELPATH of a shelved 'binary' file.
+ * Implements shelved_files_walk_func_t. */
+static svn_error_t *
+paths_changed_visitor(void *baton,
+ const char *relpath,
+ const svn_wc_status3_t *s,
+ apr_pool_t *scratch_pool)
+{
+ struct paths_changed_walk_baton_t *b = baton;
+
+ relpath = apr_pstrdup(b->pool, relpath);
+ svn_hash_sets(b->paths_hash, relpath, relpath);
+ return SVN_NO_ERROR;
+}
+
+/* Get the paths changed, relative to WC root or as abspaths, as a hash
+ * and/or an array (in no particular order).
+ */
+static svn_error_t *
+shelf_paths_changed(apr_hash_t **paths_hash_p,
+ apr_array_header_t **paths_array_p,
+ svn_client__shelf_version_t *shelf_version,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__shelf_t *shelf = shelf_version->shelf;
+ apr_hash_t *paths_hash = apr_hash_make(result_pool);
+ struct paths_changed_walk_baton_t baton;
+
+ baton.paths_hash = paths_hash;
+ baton.wc_root_abspath = shelf->wc_root_abspath;
+ baton.pool = result_pool;
+ SVN_ERR(shelf_status_walk(shelf_version, "",
+ paths_changed_visitor, &baton,
+ scratch_pool));
+
+ if (paths_hash_p)
+ *paths_hash_p = paths_hash;
+ if (paths_array_p)
+ SVN_ERR(svn_hash_keys(paths_array_p, paths_hash, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_paths_changed(apr_hash_t **affected_paths,
+ svn_client__shelf_version_t *shelf_version,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(shelf_paths_changed(affected_paths, NULL, shelf_version,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_replay(svn_client__shelf_version_t *shelf_version,
+ const char *top_relpath,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
+ apr_array_header_t *src_targets = apr_array_make(scratch_pool, 1,
+ sizeof(char *));
+ const char *src_wc_abspath
+ = svn_dirent_join(shelf_version->files_dir_abspath, top_relpath, scratch_pool);
+
+ APR_ARRAY_PUSH(src_targets, const char *) = src_wc_abspath;
+ SVN_ERR(svn_client__wc_replay(src_wc_abspath,
+ src_targets, svn_depth_infinity, NULL,
+ editor, edit_baton,
+ notify_func, notify_baton,
+ ctx, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Baton for test_apply_file_visitor(). */
+struct test_apply_files_baton_t
+{
+ svn_client__shelf_version_t *shelf_version;
+ svn_boolean_t conflict; /* would it conflict? */
+ svn_client_ctx_t *ctx;
+};
+
+/* Ideally, set BATON->conflict if we can't apply a change to WC
+ * at RELPATH without conflict. But in fact, just check
+ * if WC at RELPATH is locally modified.
+ *
+ * Implements shelved_files_walk_func_t. */
+static svn_error_t *
+test_apply_file_visitor(void *baton,
+ const char *relpath,
+ const svn_wc_status3_t *s,
+ apr_pool_t *scratch_pool)
+{
+ struct test_apply_files_baton_t *b = baton;
+ const char *wc_root_abspath = b->shelf_version->shelf->wc_root_abspath;
+ const char *to_wc_abspath = svn_dirent_join(wc_root_abspath, relpath,
+ scratch_pool);
+ svn_wc_status3_t *status;
+
+ SVN_ERR(svn_wc_status3(&status, b->ctx->wc_ctx, to_wc_abspath,
+ scratch_pool, scratch_pool));
+ switch (status->node_status)
+ {
+ case svn_wc_status_normal:
+ case svn_wc_status_none:
+ break;
+ default:
+ b->conflict = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_test_apply_file(svn_boolean_t *conflict_p,
+ svn_client__shelf_version_t *shelf_version,
+ const char *file_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct test_apply_files_baton_t baton = {0};
+
+ baton.shelf_version = shelf_version;
+ baton.conflict = FALSE;
+ baton.ctx = shelf_version->shelf->ctx;
+ SVN_ERR(shelf_status_visit_path(shelf_version, file_relpath,
+ test_apply_file_visitor, &baton,
+ scratch_pool));
+ *conflict_p = baton.conflict;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+wc_mods_editor(const svn_delta_editor_t **editor_p,
+ void **edit_baton_p,
+ const char *dst_wc_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__pathrev_t *base;
+ const char *dst_wc_url;
+ svn_ra_session_t *ra_session;
+
+ /* We'll need an RA session to obtain the base of any copies */
+ SVN_ERR(svn_client__wc_node_get_base(&base,
+ dst_wc_abspath, ctx->wc_ctx,
+ scratch_pool, scratch_pool));
+ dst_wc_url = base->url;
+ SVN_ERR(svn_client_open_ra_session2(&ra_session,
+ dst_wc_url, dst_wc_abspath,
+ ctx, result_pool, scratch_pool));
+ SVN_ERR(svn_client__wc_editor(editor_p, edit_baton_p,
+ dst_wc_abspath,
+ notify_func, notify_baton,
+ ra_session, ctx, result_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_mods_editor(const svn_delta_editor_t **editor_p,
+ void **edit_baton_p,
+ svn_client__shelf_version_t *shelf_version,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(wc_mods_editor(editor_p, edit_baton_p,
+ shelf_version->files_dir_abspath,
+ notify_func, notify_baton,
+ ctx, result_pool, result_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_apply(svn_client__shelf_version_t *shelf_version,
+ svn_boolean_t dry_run,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__shelf_t *shelf = shelf_version->shelf;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+
+ SVN_ERR(wc_mods_editor(&editor, &edit_baton,
+ shelf->wc_root_abspath,
+ NULL, NULL, /*notification*/
+ shelf->ctx, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_client__shelf_replay(shelf_version, "",
+ editor, edit_baton,
+ shelf->ctx->notify_func2, shelf->ctx->notify_baton2,
+ scratch_pool));
+
+ svn_io_sleep_for_timestamps(shelf->wc_root_abspath,
+ scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Baton for paths_changed_visitor(). */
+struct unapply_walk_baton_t
+{
+ const char *wc_root_abspath;
+ svn_boolean_t dry_run;
+ svn_boolean_t use_commit_times;
+ svn_client_ctx_t *ctx;
+ apr_pool_t *pool;
+};
+
+/* Revert the change at RELPATH in the user's WC.
+ * Implements shelved_files_walk_func_t. */
+static svn_error_t *
+unapply_visitor(void *baton,
+ const char *relpath,
+ const svn_wc_status3_t *s,
+ apr_pool_t *scratch_pool)
+{
+ struct unapply_walk_baton_t *b = baton;
+ const char *abspath = svn_dirent_join(b->wc_root_abspath, relpath,
+ scratch_pool);
+
+ if (!b->dry_run)
+ {
+ apr_array_header_t *targets
+ = apr_array_make(scratch_pool, 1, sizeof(char *));
+ svn_depth_t depth;
+
+ APR_ARRAY_PUSH(targets, const char *) = abspath;
+
+ /* If the local modification is a "delete" then revert it all
+ (recursively). Otherwise we'd have to walk paths in
+ top-down order to revert a delete, whereas we need bottom-up
+ order to revert children of an added directory. */
+ if (s->node_status == svn_wc_status_deleted
+ || s->node_status == svn_wc_status_replaced
+ || s->node_status == svn_wc_status_added)
+ depth = svn_depth_infinity;
+ else
+ depth = svn_depth_empty;
+ SVN_ERR(svn_wc_revert6(b->ctx->wc_ctx,
+ abspath,
+ depth,
+ b->use_commit_times,
+ NULL /*changelists*/,
+ FALSE /*clear_changelists*/,
+ FALSE /*metadata_only*/,
+ FALSE /*added_keep_local*/,
+ b->ctx->cancel_func, b->ctx->cancel_baton,
+ NULL, NULL, /*notification*/
+ scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_unapply(svn_client__shelf_version_t *shelf_version,
+ svn_boolean_t dry_run,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
+ svn_client__shelf_t *shelf = shelf_version->shelf;
+ struct unapply_walk_baton_t baton;
+ svn_config_t *cfg;
+
+ baton.wc_root_abspath = shelf->wc_root_abspath;
+ baton.dry_run = dry_run;
+ baton.ctx = ctx;
+ baton.pool = scratch_pool;
+
+ cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
+ : NULL;
+ SVN_ERR(svn_config_get_bool(cfg, &baton.use_commit_times,
+ SVN_CONFIG_SECTION_MISCELLANY,
+ SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
+
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ shelf_status_walk(shelf_version, "",
+ unapply_visitor, &baton,
+ scratch_pool),
+ ctx->wc_ctx, shelf_version->shelf->wc_root_abspath,
+ FALSE /*lock_anchor*/, scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_delete_newer_versions(svn_client__shelf_t *shelf,
+ svn_client__shelf_version_t *shelf_version,
+ apr_pool_t *scratch_pool)
+{
+ int previous_version = shelf_version ? shelf_version->version_number : 0;
+ int i;
+
+ /* Delete any newer checkpoints */
+ for (i = shelf->max_version; i > previous_version; i--)
+ {
+ SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
+ }
+
+ shelf->max_version = previous_version;
+ SVN_ERR(shelf_write_current(shelf, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_diff(svn_client__shelf_version_t *shelf_version,
+ const char *shelf_relpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const svn_diff_tree_processor_t *diff_processor,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_ctx_t *ctx = shelf_version->shelf->ctx;
+ char *local_abspath
+ = svn_dirent_join(shelf_version->files_dir_abspath, shelf_relpath,
+ scratch_pool);
+
+ if (shelf_version->version_number == 0)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_wc__diff7(FALSE /*anchor_at_given_paths*/,
+ ctx->wc_ctx, local_abspath,
+ depth,
+ ignore_ancestry,
+ NULL /*changelists*/,
+ diff_processor,
+ NULL, NULL, /*cancellation*/
+ scratch_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Populate the storage a new shelf-version object NEW_SHELF_VERSION,
+ * by creating a shelf storage WC with its base state copied from the
+ * 'real' WC.
+ */
+static svn_error_t *
+shelf_copy_base(svn_client__shelf_version_t *new_shelf_version,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_ctx_t *ctx = new_shelf_version->shelf->ctx;
+ const char *users_wc_abspath = new_shelf_version->shelf->wc_root_abspath;
+ svn_client__pathrev_t *users_wc_root_base;
+ svn_opt_revision_t users_wc_root_rev;
+ svn_ra_session_t *ra_session = NULL;
+ svn_boolean_t sleep_here = FALSE;
+
+ SVN_ERR(svn_client__wc_node_get_base(&users_wc_root_base,
+ users_wc_abspath, ctx->wc_ctx,
+ scratch_pool, scratch_pool));
+
+ /* ### We need to read and recreate the mixed-rev, switched-URL,
+ mixed-depth WC state; but for a rough start we'll just use
+ HEAD, unswitched, depth-infinity. */
+ users_wc_root_rev.kind = svn_opt_revision_head;
+
+ /* ### TODO: Create an RA session that reads from the user's WC.
+ For a rough start, we'll just let 'checkout' read from the repo. */
+
+ SVN_ERR(svn_client__checkout_internal(NULL /*result_rev*/, &sleep_here,
+ users_wc_root_base->url,
+ new_shelf_version->files_dir_abspath,
+ &users_wc_root_rev, &users_wc_root_rev,
+ svn_depth_infinity,
+ TRUE /*ignore_externals*/,
+ FALSE /*allow_unver_obstructions*/,
+ ra_session,
+ ctx, scratch_pool));
+ /* ### hopefully we won't eventually need to sleep_here... */
+ if (sleep_here)
+ svn_io_sleep_for_timestamps(new_shelf_version->files_dir_abspath,
+ scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+/* */
+struct shelf_save_notifer_baton_t
+{
+ svn_client__shelf_version_t *shelf_version;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+ svn_client_status_func_t shelved_func;
+ void *shelved_baton;
+ svn_boolean_t any_shelved;
+};
+
+/* */
+static void
+shelf_save_notifier(void *baton,
+ const svn_wc_notify_t *notify,
+ apr_pool_t *pool)
+{
+ struct shelf_save_notifer_baton_t *nb = baton;
+ const char *wc_relpath
+ = svn_dirent_skip_ancestor(nb->shelf_version->shelf->wc_root_abspath,
+ notify->path);
+ svn_client_status_t *cst = NULL;
+#if 0
+ svn_wc_status3_t *wc_status;
+
+ svn_error_clear(status_read(&wc_status, nb->shelf_version, wc_relpath,
+ pool, pool));
+ svn_error_clear(svn_client__create_status(
+ &cst, nb->shelf_version->shelf->ctx->wc_ctx,
+ notify->path, wc_status, pool, pool));
+#endif
+ svn_error_clear(nb->shelved_func(nb->shelved_baton, wc_relpath, cst, pool));
+ nb->any_shelved = TRUE;
+
+ nb->notify_func(nb->notify_baton, notify, pool);
+}
+
+svn_error_t *
+svn_client__shelf_save_new_version3(svn_client__shelf_version_t **new_version_p,
+ svn_client__shelf_t *shelf,
+ const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_status_func_t shelved_func,
+ void *shelved_baton,
+ svn_client_status_func_t not_shelved_func,
+ void *not_shelved_baton,
+ apr_pool_t *scratch_pool)
+{
+ svn_client_ctx_t *ctx = shelf->ctx;
+ int next_version = shelf->max_version + 1;
+ svn_client__shelf_version_t *new_shelf_version;
+ struct shelf_save_notifer_baton_t nb;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+
+ SVN_ERR(shelf_version_create(&new_shelf_version,
+ shelf, next_version, scratch_pool));
+ SVN_ERR(shelf_copy_base(new_shelf_version, scratch_pool));
+
+ nb.shelf_version = new_shelf_version;
+ nb.notify_func = ctx->notify_func2;
+ nb.notify_baton = ctx->notify_baton2;
+ nb.shelved_func = shelved_func;
+ nb.shelved_baton = shelved_baton;
+ nb.any_shelved = FALSE;
+ SVN_ERR(svn_client__shelf_mods_editor(&editor, &edit_baton,
+ new_shelf_version,
+ NULL, NULL, /*notification*/
+ ctx, scratch_pool));
+ SVN_ERR(svn_client__wc_replay(shelf->wc_root_abspath,
+ paths, depth, changelists,
+ editor, edit_baton,
+ shelf_save_notifier, &nb,
+ ctx, scratch_pool));
+
+ if (nb.any_shelved)
+ {
+ shelf->max_version = next_version;
+ SVN_ERR(shelf_write_current(shelf, scratch_pool));
+
+ if (new_version_p)
+ SVN_ERR(svn_client__shelf_version_open(new_version_p, shelf, next_version,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ if (new_version_p)
+ *new_version_p = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_get_log_message(char **log_message,
+ svn_client__shelf_t *shelf,
+ apr_pool_t *result_pool)
+{
+ svn_string_t *propval = svn_hash_gets(shelf->revprops, SVN_PROP_REVISION_LOG);
+
+ if (propval)
+ *log_message = apr_pstrdup(result_pool, propval->data);
+ else
+ *log_message = NULL;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_set_log_message(svn_client__shelf_t *shelf,
+ const char *message,
+ apr_pool_t *scratch_pool)
+{
+ svn_string_t *propval
+ = message ? svn_string_create(message, shelf->pool) : NULL;
+
+ SVN_ERR(svn_client__shelf_revprop_set(shelf, SVN_PROP_REVISION_LOG, propval,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_list(apr_hash_t **shelf_infos,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *wc_root_abspath;
+ char *shelves_dir;
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_wc__get_wcroot(&wc_root_abspath, ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/,
+ result_pool, scratch_pool));
+
+ *shelf_infos = apr_hash_make(result_pool);
+
+ /* Remove non-shelves */
+ for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *filename = apr_hash_this_key(hi);
+ svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
+ char *name = NULL;
+
+ svn_error_clear(shelf_name_from_filename(&name, filename, result_pool));
+ if (name && dirent->kind == svn_node_file)
+ {
+ svn_client__shelf_info_t *info
+ = apr_palloc(result_pool, sizeof(*info));
+
+ info->mtime = dirent->mtime;
+ svn_hash_sets(*shelf_infos, name, info);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_version_open(svn_client__shelf_version_t **shelf_version_p,
+ svn_client__shelf_t *shelf,
+ int version_number,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__shelf_version_t *shelf_version;
+ const svn_io_dirent2_t *dirent;
+
+ SVN_ERR(shelf_version_create(&shelf_version,
+ shelf, version_number, result_pool));
+ SVN_ERR(svn_io_stat_dirent2(&dirent,
+ shelf_version->files_dir_abspath,
+ FALSE /*verify_truename*/,
+ TRUE /*ignore_enoent*/,
+ result_pool, scratch_pool));
+ if (dirent->kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Shelf '%s' version %d not found"),
+ shelf->name, version_number);
+ }
+ shelf_version->mtime = dirent->mtime;
+ *shelf_version_p = shelf_version;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_get_newest_version(svn_client__shelf_version_t **shelf_version_p,
+ svn_client__shelf_t *shelf,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (shelf->max_version == 0)
+ {
+ *shelf_version_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_client__shelf_version_open(shelf_version_p,
+ shelf, shelf->max_version,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf_get_all_versions(apr_array_header_t **versions_p,
+ svn_client__shelf_t *shelf,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ *versions_p = apr_array_make(result_pool, shelf->max_version - 1,
+ sizeof(svn_client__shelf_version_t *));
+
+ for (i = 1; i <= shelf->max_version; i++)
+ {
+ svn_client__shelf_version_t *shelf_version;
+
+ SVN_ERR(svn_client__shelf_version_open(&shelf_version,
+ shelf, i,
+ result_pool, scratch_pool));
+ APR_ARRAY_PUSH(*versions_p, svn_client__shelf_version_t *) = shelf_version;
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_client/shelf2.c b/subversion/libsvn_client/shelf2.c
new file mode 100644
index 000000000000..533a961e260d
--- /dev/null
+++ b/subversion/libsvn_client/shelf2.c
@@ -0,0 +1,2124 @@
+/*
+ * shelf2.c: implementation of shelving v2
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+/* We define this here to remove any further warnings about the usage of
+ experimental functions in this file. */
+#define SVN_EXPERIMENTAL
+
+#include "svn_client.h"
+#include "svn_wc.h"
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_utf.h"
+#include "svn_ctype.h"
+#include "svn_props.h"
+
+#include "client.h"
+#include "private/svn_client_shelf2.h"
+#include "private/svn_client_private.h"
+#include "private/svn_wc_private.h"
+#include "private/svn_sorts_private.h"
+#include "svn_private_config.h"
+
+
+static svn_error_t *
+shelf_name_encode(char **encoded_name_p,
+ const char *name,
+ apr_pool_t *result_pool)
+{
+ char *encoded_name
+ = apr_palloc(result_pool, strlen(name) * 2 + 1);
+ char *out_pos = encoded_name;
+
+ if (name[0] == '\0')
+ return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
+ _("Shelf name cannot be the empty string"));
+
+ while (*name)
+ {
+ apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++));
+ out_pos += 2;
+ }
+ *encoded_name_p = encoded_name;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+shelf_name_decode(char **decoded_name_p,
+ const char *codename,
+ apr_pool_t *result_pool)
+{
+ svn_stringbuf_t *sb
+ = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool);
+ const char *input = codename;
+
+ while (*input)
+ {
+ int c;
+ int nchars;
+ int nitems = sscanf(input, "%02x%n", &c, &nchars);
+
+ if (nitems != 1 || nchars != 2)
+ return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
+ _("Shelve: Bad encoded name '%s'"), codename);
+ svn_stringbuf_appendbyte(sb, c);
+ input += 2;
+ }
+ *decoded_name_p = sb->data;
+ return SVN_NO_ERROR;
+}
+
+/* Set *NAME to the shelf name from FILENAME, if FILENAME names a '.current'
+ * file, else to NULL. */
+static svn_error_t *
+shelf_name_from_filename(char **name,
+ const char *filename,
+ apr_pool_t *result_pool)
+{
+ size_t len = strlen(filename);
+ static const char suffix[] = ".current";
+ int suffix_len = sizeof(suffix) - 1;
+
+ if (len > suffix_len && strcmp(filename + len - suffix_len, suffix) == 0)
+ {
+ char *codename = apr_pstrndup(result_pool, filename, len - suffix_len);
+ SVN_ERR(shelf_name_decode(name, codename, result_pool));
+ }
+ else
+ {
+ *name = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Set *DIR to the shelf storage directory inside the WC's administrative
+ * area. Ensure the directory exists. */
+static svn_error_t *
+get_shelves_dir(char **dir,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *experimental_abspath;
+
+ SVN_ERR(svn_wc__get_experimental_dir(&experimental_abspath,
+ wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ *dir = svn_dirent_join(experimental_abspath, "shelves/v2", result_pool);
+
+ /* Ensure the directory exists. (Other versions of svn don't create it.) */
+ SVN_ERR(svn_io_make_dir_recursively(*dir, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Set *ABSPATH to the abspath of the file storage dir for SHELF
+ * version VERSION, no matter whether it exists.
+ */
+static svn_error_t *
+shelf_version_files_dir_abspath(const char **abspath,
+ svn_client__shelf2_t *shelf,
+ int version,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *codename;
+ char *filename;
+
+ SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
+ filename = apr_psprintf(scratch_pool, "%s-%03d.d", codename, version);
+ *abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Create a shelf-version object for a version that may or may not already
+ * exist on disk.
+ */
+static svn_error_t *
+shelf_version_create(svn_client__shelf2_version_t **new_version_p,
+ svn_client__shelf2_t *shelf,
+ int version_number,
+ apr_pool_t *result_pool)
+{
+ svn_client__shelf2_version_t *shelf_version
+ = apr_pcalloc(result_pool, sizeof(*shelf_version));
+
+ shelf_version->shelf = shelf;
+ shelf_version->version_number = version_number;
+ SVN_ERR(shelf_version_files_dir_abspath(&shelf_version->files_dir_abspath,
+ shelf, version_number,
+ result_pool, result_pool));
+ *new_version_p = shelf_version;
+ return SVN_NO_ERROR;
+}
+
+/* Set *ABSPATH to the abspath of the metadata file for SHELF_VERSION
+ * node at RELPATH, no matter whether it exists.
+ */
+static svn_error_t *
+get_metadata_abspath(char **abspath,
+ svn_client__shelf2_version_t *shelf_version,
+ const char *wc_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_relpath = apr_psprintf(scratch_pool, "%s.meta", wc_relpath);
+ *abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
+ result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Set *ABSPATH to the abspath of the base text file for SHELF_VERSION
+ * node at RELPATH, no matter whether it exists.
+ */
+static svn_error_t *
+get_base_file_abspath(char **base_abspath,
+ svn_client__shelf2_version_t *shelf_version,
+ const char *wc_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_relpath = apr_psprintf(scratch_pool, "%s.base", wc_relpath);
+ *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
+ result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Set *ABSPATH to the abspath of the working text file for SHELF_VERSION
+ * node at RELPATH, no matter whether it exists.
+ */
+static svn_error_t *
+get_working_file_abspath(char **work_abspath,
+ svn_client__shelf2_version_t *shelf_version,
+ const char *wc_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_relpath = apr_psprintf(scratch_pool, "%s.work", wc_relpath);
+ *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
+ result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Set *ABSPATH to the abspath of the base props file for SHELF_VERSION
+ * node at RELPATH, no matter whether it exists.
+ */
+static svn_error_t *
+get_base_props_abspath(char **base_abspath,
+ svn_client__shelf2_version_t *shelf_version,
+ const char *wc_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_relpath = apr_psprintf(scratch_pool, "%s.base-props", wc_relpath);
+ *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
+ result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Set *ABSPATH to the abspath of the working props file for SHELF_VERSION
+ * node at RELPATH, no matter whether it exists.
+ */
+static svn_error_t *
+get_working_props_abspath(char **work_abspath,
+ svn_client__shelf2_version_t *shelf_version,
+ const char *wc_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ wc_relpath = apr_psprintf(scratch_pool, "%s.work-props", wc_relpath);
+ *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath,
+ result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Delete the storage for SHELF:VERSION. */
+static svn_error_t *
+shelf_version_delete(svn_client__shelf2_t *shelf,
+ int version,
+ apr_pool_t *scratch_pool)
+{
+ const char *files_dir_abspath;
+
+ SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath,
+ shelf, version,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/,
+ NULL, NULL, /*cancel*/
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+get_log_abspath(char **log_abspath,
+ svn_client__shelf2_t *shelf,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *codename;
+ const char *filename;
+
+ SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
+ filename = apr_pstrcat(scratch_pool, codename, ".log", SVN_VA_NULL);
+ *log_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Set SHELF->revprops by reading from its storage (the '.log' file).
+ * Set SHELF->revprops to empty if the storage file does not exist; this
+ * is not an error.
+ */
+static svn_error_t *
+shelf_read_revprops(svn_client__shelf2_t *shelf,
+ apr_pool_t *scratch_pool)
+{
+ char *log_abspath;
+ svn_error_t *err;
+ svn_stream_t *stream;
+
+ SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
+
+ shelf->revprops = apr_hash_make(shelf->pool);
+ err = svn_stream_open_readonly(&stream, log_abspath,
+ scratch_pool, scratch_pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+ SVN_ERR(svn_hash_read2(shelf->revprops, stream, "PROPS-END", shelf->pool));
+ SVN_ERR(svn_stream_close(stream));
+ return SVN_NO_ERROR;
+}
+
+/* Write SHELF's revprops to its file storage.
+ */
+static svn_error_t *
+shelf_write_revprops(svn_client__shelf2_t *shelf,
+ apr_pool_t *scratch_pool)
+{
+ char *log_abspath;
+ apr_file_t *file;
+ svn_stream_t *stream;
+
+ SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_io_file_open(&file, log_abspath,
+ APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE,
+ APR_FPROT_OS_DEFAULT, scratch_pool));
+ stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool);
+
+ SVN_ERR(svn_hash_write2(shelf->revprops, stream, "PROPS-END", scratch_pool));
+ SVN_ERR(svn_stream_close(stream));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_revprop_set(svn_client__shelf2_t *shelf,
+ const char *prop_name,
+ const svn_string_t *prop_val,
+ apr_pool_t *scratch_pool)
+{
+ svn_hash_sets(shelf->revprops, apr_pstrdup(shelf->pool, prop_name),
+ svn_string_dup(prop_val, shelf->pool));
+ SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_revprop_set_all(svn_client__shelf2_t *shelf,
+ apr_hash_t *revprop_table,
+ apr_pool_t *scratch_pool)
+{
+ if (revprop_table)
+ shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool);
+ else
+ shelf->revprops = apr_hash_make(shelf->pool);
+
+ SVN_ERR(shelf_write_revprops(shelf, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_revprop_get(svn_string_t **prop_val,
+ svn_client__shelf2_t *shelf,
+ const char *prop_name,
+ apr_pool_t *result_pool)
+{
+ *prop_val = svn_hash_gets(shelf->revprops, prop_name);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_revprop_list(apr_hash_t **props,
+ svn_client__shelf2_t *shelf,
+ apr_pool_t *result_pool)
+{
+ *props = shelf->revprops;
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+get_current_abspath(char **current_abspath,
+ svn_client__shelf2_t *shelf,
+ apr_pool_t *result_pool)
+{
+ char *codename;
+ char *filename;
+
+ SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool));
+ filename = apr_psprintf(result_pool, "%s.current", codename);
+ *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Read SHELF->max_version from its storage (the '.current' file).
+ * Set SHELF->max_version to -1 if that file does not exist.
+ */
+static svn_error_t *
+shelf_read_current(svn_client__shelf2_t *shelf,
+ apr_pool_t *scratch_pool)
+{
+ char *current_abspath;
+ svn_error_t *err;
+
+ SVN_ERR(get_current_abspath(&current_abspath, shelf, scratch_pool));
+ err = svn_io_read_version_file(&shelf->max_version,
+ current_abspath, scratch_pool);
+ if (err)
+ {
+ shelf->max_version = -1;
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+shelf_write_current(svn_client__shelf2_t *shelf,
+ apr_pool_t *scratch_pool)
+{
+ char *current_abspath;
+
+ SVN_ERR(get_current_abspath(&current_abspath, shelf, scratch_pool));
+ SVN_ERR(svn_io_write_version_file(current_abspath, shelf->max_version,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/*-------------------------------------------------------------------------*/
+/* Status Reporting */
+
+/* Create a status struct with all fields initialized to valid values
+ * representing 'uninteresting' or 'unknown' status.
+ */
+static svn_wc_status3_t *
+status_create(apr_pool_t *result_pool)
+{
+ svn_wc_status3_t *s = apr_pcalloc(result_pool, sizeof(*s));
+
+ s->filesize = SVN_INVALID_FILESIZE;
+ s->versioned = TRUE;
+ s->node_status = svn_wc_status_none;
+ s->text_status = svn_wc_status_none;
+ s->prop_status = svn_wc_status_none;
+ s->revision = SVN_INVALID_REVNUM;
+ s->changed_rev = SVN_INVALID_REVNUM;
+ s->repos_node_status = svn_wc_status_none;
+ s->repos_text_status = svn_wc_status_none;
+ s->repos_prop_status = svn_wc_status_none;
+ s->ood_changed_rev = SVN_INVALID_REVNUM;
+ return s;
+}
+
+/* Convert from svn_node_kind_t to a single character representation. */
+static char
+kind_to_char(svn_node_kind_t kind)
+{
+ return (kind == svn_node_dir ? 'd'
+ : kind == svn_node_file ? 'f'
+ : kind == svn_node_symlink ? 'l'
+ : '?');
+}
+
+/* Convert to svn_node_kind_t from a single character representation. */
+static svn_node_kind_t
+char_to_kind(char kind)
+{
+ return (kind == 'd' ? svn_node_dir
+ : kind == 'f' ? svn_node_file
+ : kind == 'l' ? svn_node_symlink
+ : svn_node_unknown);
+}
+
+/* Return the single character representation of STATUS.
+ * (Similar to subversion/svn/status.c:generate_status_code()
+ * and subversion/tests/libsvn_client/client-test.c:status_to_char().) */
+static char
+status_to_char(enum svn_wc_status_kind status)
+{
+ switch (status)
+ {
+ case svn_wc_status_none: return '.';
+ case svn_wc_status_unversioned: return '?';
+ case svn_wc_status_normal: return ' ';
+ case svn_wc_status_added: return 'A';
+ case svn_wc_status_missing: return '!';
+ case svn_wc_status_deleted: return 'D';
+ case svn_wc_status_replaced: return 'R';
+ case svn_wc_status_modified: return 'M';
+ case svn_wc_status_merged: return 'G';
+ case svn_wc_status_conflicted: return 'C';
+ case svn_wc_status_ignored: return 'I';
+ case svn_wc_status_obstructed: return '~';
+ case svn_wc_status_external: return 'X';
+ case svn_wc_status_incomplete: return ':';
+ default: return '*';
+ }
+}
+
+static enum svn_wc_status_kind
+char_to_status(char status)
+{
+ switch (status)
+ {
+ case '.': return svn_wc_status_none;
+ case '?': return svn_wc_status_unversioned;
+ case ' ': return svn_wc_status_normal;
+ case 'A': return svn_wc_status_added;
+ case '!': return svn_wc_status_missing;
+ case 'D': return svn_wc_status_deleted;
+ case 'R': return svn_wc_status_replaced;
+ case 'M': return svn_wc_status_modified;
+ case 'G': return svn_wc_status_merged;
+ case 'C': return svn_wc_status_conflicted;
+ case 'I': return svn_wc_status_ignored;
+ case '~': return svn_wc_status_obstructed;
+ case 'X': return svn_wc_status_external;
+ case ':': return svn_wc_status_incomplete;
+ default: return (enum svn_wc_status_kind)0;
+ }
+}
+
+/* Write a serial representation of (some fields of) STATUS to STREAM.
+ */
+static svn_error_t *
+wc_status_serialize(svn_stream_t *stream,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_stream_printf(stream, scratch_pool, "%c %c%c%c %ld",
+ kind_to_char(status->kind),
+ status_to_char(status->node_status),
+ status_to_char(status->text_status),
+ status_to_char(status->prop_status),
+ status->revision));
+ return SVN_NO_ERROR;
+}
+
+/* Read a serial representation of (some fields of) STATUS from STREAM.
+ */
+static svn_error_t *
+wc_status_unserialize(svn_wc_status3_t *status,
+ svn_stream_t *stream,
+ apr_pool_t *result_pool)
+{
+ svn_stringbuf_t *sb;
+ char *string;
+
+ SVN_ERR(svn_stringbuf_from_stream(&sb, stream, 100, result_pool));
+ string = sb->data;
+ status->kind = char_to_kind(string[0]);
+ status->node_status = char_to_status(string[2]);
+ status->text_status = char_to_status(string[3]);
+ status->prop_status = char_to_status(string[4]);
+ sscanf(string + 6, "%ld", &status->revision);
+ return SVN_NO_ERROR;
+}
+
+/* Write status to shelf storage.
+ */
+static svn_error_t *
+status_write(svn_client__shelf2_version_t *shelf_version,
+ const char *relpath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ char *file_abspath;
+ svn_stream_t *stream;
+
+ SVN_ERR(get_metadata_abspath(&file_abspath, shelf_version, relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_writable(&stream, file_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(wc_status_serialize(stream, status, scratch_pool));
+ SVN_ERR(svn_stream_close(stream));
+ return SVN_NO_ERROR;
+}
+
+/* Read status from shelf storage.
+ */
+static svn_error_t *
+status_read(svn_wc_status3_t **status,
+ svn_client__shelf2_version_t *shelf_version,
+ const char *relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_status3_t *s = status_create(result_pool);
+ char *file_abspath;
+ svn_stream_t *stream;
+
+ SVN_ERR(get_metadata_abspath(&file_abspath, shelf_version, relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_readonly(&stream, file_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(wc_status_unserialize(s, stream, result_pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ s->changelist = apr_psprintf(result_pool, "svn:shelf:%s",
+ shelf_version->shelf->name);
+ *status = s;
+ return SVN_NO_ERROR;
+}
+
+/* A visitor function type for use with shelf_status_walk().
+ * The same as svn_wc_status_func4_t except relpath instead of abspath.
+ * Only some fields in STATUS are available.
+ */
+typedef svn_error_t *(*shelf_status_visitor_t)(void *baton,
+ const char *relpath,
+ svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool);
+
+/* Baton for shelved_files_walk_visitor(). */
+struct shelf_status_baton_t
+{
+ svn_client__shelf2_version_t *shelf_version;
+ const char *top_relpath;
+ const char *walk_root_abspath;
+ shelf_status_visitor_t walk_func;
+ void *walk_baton;
+};
+
+/* Call BATON->walk_func(BATON->walk_baton, relpath, ...) for the shelved
+ * 'binary' file stored at ABSPATH.
+ * Implements svn_io_walk_func_t. */
+static svn_error_t *
+shelf_status_visitor(void *baton,
+ const char *abspath,
+ const apr_finfo_t *finfo,
+ apr_pool_t *scratch_pool)
+{
+ struct shelf_status_baton_t *b = baton;
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(b->walk_root_abspath, abspath);
+ if (finfo->filetype == APR_REG
+ && (strlen(relpath) >= 5 && strcmp(relpath+strlen(relpath)-5, ".meta") == 0))
+ {
+ svn_wc_status3_t *s;
+
+ relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5);
+ if (!svn_relpath_skip_ancestor(b->top_relpath, relpath))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(status_read(&s, b->shelf_version, relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(b->walk_func(b->walk_baton, relpath, s, scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Report the shelved status of the path SHELF_VERSION:WC_RELPATH
+ * via WALK_FUNC(WALK_BATON, ...).
+ */
+static svn_error_t *
+shelf_status_visit_path(svn_client__shelf2_version_t *shelf_version,
+ const char *wc_relpath,
+ shelf_status_visitor_t walk_func,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct shelf_status_baton_t baton;
+ char *abspath;
+ apr_finfo_t finfo;
+
+ baton.shelf_version = shelf_version;
+ baton.top_relpath = wc_relpath;
+ baton.walk_root_abspath = shelf_version->files_dir_abspath;
+ baton.walk_func = walk_func;
+ baton.walk_baton = walk_baton;
+ SVN_ERR(get_metadata_abspath(&abspath, shelf_version, wc_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_stat(&finfo, abspath, APR_FINFO_TYPE, scratch_pool));
+ SVN_ERR(shelf_status_visitor(&baton, abspath, &finfo, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Report the shelved status of all the shelved paths in SHELF_VERSION
+ * via WALK_FUNC(WALK_BATON, ...).
+ */
+static svn_error_t *
+shelf_status_walk(svn_client__shelf2_version_t *shelf_version,
+ const char *wc_relpath,
+ shelf_status_visitor_t walk_func,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct shelf_status_baton_t baton;
+ svn_error_t *err;
+
+ baton.shelf_version = shelf_version;
+ baton.top_relpath = wc_relpath;
+ baton.walk_root_abspath = shelf_version->files_dir_abspath;
+ baton.walk_func = walk_func;
+ baton.walk_baton = walk_baton;
+ err = svn_io_dir_walk2(baton.walk_root_abspath, 0 /*wanted*/,
+ shelf_status_visitor, &baton,
+ scratch_pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ svn_error_clear(err);
+ else
+ SVN_ERR(err);
+
+ return SVN_NO_ERROR;
+}
+
+typedef struct wc_status_baton_t
+{
+ svn_client__shelf2_version_t *shelf_version;
+ svn_wc_status_func4_t walk_func;
+ void *walk_baton;
+} wc_status_baton_t;
+
+static svn_error_t *
+wc_status_visitor(void *baton,
+ const char *relpath,
+ svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct wc_status_baton_t *b = baton;
+ svn_client__shelf2_t *shelf = b->shelf_version->shelf;
+ const char *abspath = svn_dirent_join(shelf->wc_root_abspath, relpath,
+ scratch_pool);
+ SVN_ERR(b->walk_func(b->walk_baton, abspath, status, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_version_status_walk(svn_client__shelf2_version_t *shelf_version,
+ const char *wc_relpath,
+ svn_wc_status_func4_t walk_func,
+ void *walk_baton,
+ apr_pool_t *scratch_pool)
+{
+ wc_status_baton_t baton;
+
+ baton.shelf_version = shelf_version;
+ baton.walk_func = walk_func;
+ baton.walk_baton = walk_baton;
+ SVN_ERR(shelf_status_walk(shelf_version, wc_relpath,
+ wc_status_visitor, &baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/*-------------------------------------------------------------------------*/
+/* Shelf Storage */
+
+/* A baton for use with write_changes_visitor(). */
+typedef struct write_changes_baton_t {
+ const char *wc_root_abspath;
+ svn_client__shelf2_version_t *shelf_version;
+ svn_client_ctx_t *ctx;
+ svn_boolean_t any_shelved; /* were any paths successfully shelved? */
+ svn_client_status_func_t was_shelved_func;
+ void *was_shelved_baton;
+ svn_client_status_func_t was_not_shelved_func;
+ void *was_not_shelved_baton;
+ apr_pool_t *pool; /* pool for data in 'unshelvable', etc. */
+} write_changes_baton_t;
+
+/* */
+static svn_error_t *
+notify_shelved(write_changes_baton_t *wb,
+ const char *wc_relpath,
+ const char *local_abspath,
+ const svn_wc_status3_t *wc_status,
+ apr_pool_t *scratch_pool)
+{
+ if (wb->was_shelved_func)
+ {
+ svn_client_status_t *cst;
+
+ SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath,
+ wc_status,
+ scratch_pool, scratch_pool));
+ SVN_ERR(wb->was_shelved_func(wb->was_shelved_baton,
+ wc_relpath, cst, scratch_pool));
+ }
+
+ wb->any_shelved = TRUE;
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+notify_not_shelved(write_changes_baton_t *wb,
+ const char *wc_relpath,
+ const char *local_abspath,
+ const svn_wc_status3_t *wc_status,
+ apr_pool_t *scratch_pool)
+{
+ if (wb->was_not_shelved_func)
+ {
+ svn_client_status_t *cst;
+
+ SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath,
+ wc_status,
+ scratch_pool, scratch_pool));
+ SVN_ERR(wb->was_not_shelved_func(wb->was_not_shelved_baton,
+ wc_relpath, cst, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read BASE_PROPS and WORK_PROPS from the WC, setting each to null if
+ * the node has no base or working version (respectively).
+ */
+static svn_error_t *
+read_props_from_wc(apr_hash_t **base_props,
+ apr_hash_t **work_props,
+ enum svn_wc_status_kind node_status,
+ const char *from_wc_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (node_status != svn_wc_status_added)
+ SVN_ERR(svn_wc_get_pristine_props(base_props, ctx->wc_ctx, from_wc_abspath,
+ result_pool, scratch_pool));
+ else
+ *base_props = NULL;
+ if (node_status != svn_wc_status_deleted)
+ SVN_ERR(svn_wc_prop_list2(work_props, ctx->wc_ctx, from_wc_abspath,
+ result_pool, scratch_pool));
+ else
+ *work_props = NULL;
+ return SVN_NO_ERROR;
+}
+
+/* Write BASE_PROPS and WORK_PROPS to storage in SHELF_VERSION:WC_RELPATH.
+ */
+static svn_error_t *
+write_props_to_shelf(svn_client__shelf2_version_t *shelf_version,
+ const char *wc_relpath,
+ apr_hash_t *base_props,
+ apr_hash_t *work_props,
+ apr_pool_t *scratch_pool)
+{
+ char *stored_props_abspath;
+ svn_stream_t *stream;
+
+ if (base_props)
+ {
+ SVN_ERR(get_base_props_abspath(&stored_props_abspath,
+ shelf_version, wc_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_writable(&stream, stored_props_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_hash_write2(base_props, stream, NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(stream));
+ }
+
+ if (work_props)
+ {
+ SVN_ERR(get_working_props_abspath(&stored_props_abspath,
+ shelf_version, wc_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_writable(&stream, stored_props_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_hash_write2(work_props, stream, NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(stream));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Read BASE_PROPS and WORK_PROPS from storage in SHELF_VERSION:WC_RELPATH.
+ */
+static svn_error_t *
+read_props_from_shelf(apr_hash_t **base_props,
+ apr_hash_t **work_props,
+ enum svn_wc_status_kind node_status,
+ svn_client__shelf2_version_t *shelf_version,
+ const char *wc_relpath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ char *stored_props_abspath;
+ svn_stream_t *stream;
+
+ if (node_status != svn_wc_status_added)
+ {
+ *base_props = apr_hash_make(result_pool);
+ SVN_ERR(get_base_props_abspath(&stored_props_abspath,
+ shelf_version, wc_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_readonly(&stream, stored_props_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_hash_read2(*base_props, stream, NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(stream));
+ }
+ else
+ *base_props = NULL;
+
+ if (node_status != svn_wc_status_deleted)
+ {
+ *work_props = apr_hash_make(result_pool);
+ SVN_ERR(get_working_props_abspath(&stored_props_abspath,
+ shelf_version, wc_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_readonly(&stream, stored_props_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_hash_read2(*work_props, stream, NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(stream));
+ }
+ else
+ *work_props = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+/* Store metadata for any node, and base and working files if it's a file.
+ *
+ * Copy the WC base and working files at FROM_WC_ABSPATH to the storage
+ * area in SHELF_VERSION.
+ */
+static svn_error_t *
+store_file(const char *from_wc_abspath,
+ const char *wc_relpath,
+ svn_client__shelf2_version_t *shelf_version,
+ const svn_wc_status3_t *status,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ char *stored_abspath;
+ apr_hash_t *base_props, *work_props;
+
+ SVN_ERR(get_working_file_abspath(&stored_abspath,
+ shelf_version, wc_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(stored_abspath,
+ scratch_pool),
+ scratch_pool));
+ SVN_ERR(status_write(shelf_version, wc_relpath,
+ status, scratch_pool));
+
+ /* properties */
+ SVN_ERR(read_props_from_wc(&base_props, &work_props,
+ status->node_status,
+ from_wc_abspath, ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(write_props_to_shelf(shelf_version, wc_relpath,
+ base_props, work_props,
+ scratch_pool));
+
+ /* file text */
+ if (status->kind == svn_node_file)
+ {
+ svn_stream_t *wc_base_stream;
+ svn_node_kind_t work_kind;
+
+ /* Copy the base file (copy-from base, if copied/moved), if present */
+ SVN_ERR(svn_wc_get_pristine_contents2(&wc_base_stream,
+ ctx->wc_ctx, from_wc_abspath,
+ scratch_pool, scratch_pool));
+ if (wc_base_stream)
+ {
+ char *stored_base_abspath;
+ svn_stream_t *stored_base_stream;
+
+ SVN_ERR(get_base_file_abspath(&stored_base_abspath,
+ shelf_version, wc_relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_open_writable(&stored_base_stream,
+ stored_base_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(wc_base_stream, stored_base_stream,
+ NULL, NULL, scratch_pool));
+ }
+
+ /* Copy the working file, if present */
+ SVN_ERR(svn_io_check_path(from_wc_abspath, &work_kind, scratch_pool));
+ if (work_kind == svn_node_file)
+ {
+ SVN_ERR(svn_io_copy_file(from_wc_abspath, stored_abspath,
+ TRUE /*copy_perms*/, scratch_pool));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+/* An implementation of svn_wc_status_func4_t. */
+static svn_error_t *
+write_changes_visitor(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ write_changes_baton_t *wb = baton;
+ const char *wc_relpath = svn_dirent_skip_ancestor(wb->wc_root_abspath,
+ local_abspath);
+
+ /* Catch any conflict, even a tree conflict on a path that has
+ node-status 'unversioned'. */
+ if (status->conflicted)
+ {
+ SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
+ status, scratch_pool));
+ }
+ else switch (status->node_status)
+ {
+ case svn_wc_status_deleted:
+ case svn_wc_status_added:
+ case svn_wc_status_replaced:
+ if (status->kind != svn_node_file
+ || status->copied)
+ {
+ SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
+ status, scratch_pool));
+ break;
+ }
+ /* fall through */
+ case svn_wc_status_modified:
+ {
+ /* Store metadata, and base and working versions if it's a file */
+ SVN_ERR(store_file(local_abspath, wc_relpath, wb->shelf_version,
+ status, wb->ctx, scratch_pool));
+ SVN_ERR(notify_shelved(wb, wc_relpath, local_abspath,
+ status, scratch_pool));
+ break;
+ }
+
+ case svn_wc_status_incomplete:
+ if ((status->text_status != svn_wc_status_normal
+ && status->text_status != svn_wc_status_none)
+ || (status->prop_status != svn_wc_status_normal
+ && status->prop_status != svn_wc_status_none))
+ {
+ /* Incomplete, but local modifications */
+ SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
+ status, scratch_pool));
+ }
+ break;
+
+ case svn_wc_status_conflicted:
+ case svn_wc_status_missing:
+ case svn_wc_status_obstructed:
+ SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath,
+ status, scratch_pool));
+ break;
+
+ case svn_wc_status_normal:
+ case svn_wc_status_ignored:
+ case svn_wc_status_none:
+ case svn_wc_status_external:
+ case svn_wc_status_unversioned:
+ default:
+ break;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* A baton for use with changelist_filter_func(). */
+struct changelist_filter_baton_t {
+ apr_hash_t *changelist_hash;
+ svn_wc_status_func4_t status_func;
+ void *status_baton;
+};
+
+/* Filter out paths that are not in the requested changelist(s).
+ * Implements svn_wc_status_func4_t. */
+static svn_error_t *
+changelist_filter_func(void *baton,
+ const char *local_abspath,
+ const svn_wc_status3_t *status,
+ apr_pool_t *scratch_pool)
+{
+ struct changelist_filter_baton_t *b = baton;
+
+ if (b->changelist_hash
+ && (! status->changelist
+ || ! svn_hash_gets(b->changelist_hash, status->changelist)))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(b->status_func(b->status_baton, local_abspath, status,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/*
+ * Walk the WC tree(s) rooted at PATHS, to depth DEPTH, omitting paths that
+ * are not in one of the CHANGELISTS (if not null).
+ *
+ * Call STATUS_FUNC(STATUS_BATON, ...) for each visited path.
+ *
+ * PATHS are absolute, or relative to CWD.
+ */
+static svn_error_t *
+wc_walk_status_multi(const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ struct changelist_filter_baton_t cb = {0};
+ int i;
+
+ if (changelists && changelists->nelts)
+ SVN_ERR(svn_hash_from_cstring_keys(&cb.changelist_hash,
+ changelists, scratch_pool));
+ cb.status_func = status_func;
+ cb.status_baton = status_baton;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ if (svn_path_is_url(path))
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("'%s' is not a local path"), path);
+ SVN_ERR(svn_dirent_get_absolute(&path, path, scratch_pool));
+
+ SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, path, depth,
+ FALSE /*get_all*/, FALSE /*no_ignore*/,
+ FALSE /*ignore_text_mods*/,
+ NULL /*ignore_patterns*/,
+ changelist_filter_func, &cb,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/** Write local changes to the shelf storage.
+ *
+ * @a paths, @a depth, @a changelists: The selection of local paths to diff.
+ *
+ * @a paths are relative to CWD (or absolute).
+ */
+static svn_error_t *
+shelf_write_changes(svn_boolean_t *any_shelved,
+ svn_client__shelf2_version_t *shelf_version,
+ const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_status_func_t shelved_func,
+ void *shelved_baton,
+ svn_client_status_func_t not_shelved_func,
+ void *not_shelved_baton,
+ const char *wc_root_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ write_changes_baton_t wb = { 0 };
+
+ wb.wc_root_abspath = wc_root_abspath;
+ wb.shelf_version = shelf_version;
+ wb.ctx = ctx;
+ wb.any_shelved = FALSE;
+ wb.was_shelved_func = shelved_func;
+ wb.was_shelved_baton = shelved_baton;
+ wb.was_not_shelved_func = not_shelved_func;
+ wb.was_not_shelved_baton = not_shelved_baton;
+ wb.pool = result_pool;
+
+ /* Walk the WC */
+ SVN_ERR(wc_walk_status_multi(paths, depth, changelists,
+ write_changes_visitor, &wb,
+ ctx, scratch_pool));
+
+ *any_shelved = wb.any_shelved;
+ return SVN_NO_ERROR;
+}
+
+/* Construct a shelf object representing an empty shelf: no versions,
+ * no revprops, no looking to see if such a shelf exists on disk.
+ */
+static svn_error_t *
+shelf_construct(svn_client__shelf2_t **shelf_p,
+ const char *name,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool)
+{
+ svn_client__shelf2_t *shelf = apr_palloc(result_pool, sizeof(*shelf));
+ char *shelves_dir;
+
+ SVN_ERR(svn_client_get_wc_root(&shelf->wc_root_abspath,
+ local_abspath, ctx,
+ result_pool, result_pool));
+ SVN_ERR(get_shelves_dir(&shelves_dir,
+ ctx->wc_ctx, local_abspath,
+ result_pool, result_pool));
+ shelf->shelves_dir = shelves_dir;
+ shelf->ctx = ctx;
+ shelf->pool = result_pool;
+
+ shelf->name = apr_pstrdup(result_pool, name);
+ shelf->revprops = apr_hash_make(result_pool);
+ shelf->max_version = 0;
+
+ *shelf_p = shelf;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_open_existing(svn_client__shelf2_t **shelf_p,
+ const char *name,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(shelf_construct(shelf_p, name,
+ local_abspath, ctx, result_pool));
+ SVN_ERR(shelf_read_revprops(*shelf_p, result_pool));
+ SVN_ERR(shelf_read_current(*shelf_p, result_pool));
+ if ((*shelf_p)->max_version < 0)
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Shelf '%s' not found"),
+ name);
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_open_or_create(svn_client__shelf2_t **shelf_p,
+ const char *name,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool)
+{
+ svn_client__shelf2_t *shelf;
+
+ SVN_ERR(shelf_construct(&shelf, name,
+ local_abspath, ctx, result_pool));
+ SVN_ERR(shelf_read_revprops(shelf, result_pool));
+ SVN_ERR(shelf_read_current(shelf, result_pool));
+ if (shelf->max_version < 0)
+ {
+ shelf->max_version = 0;
+ SVN_ERR(shelf_write_current(shelf, result_pool));
+ }
+ *shelf_p = shelf;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_close(svn_client__shelf2_t *shelf,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_delete(const char *name,
+ const char *local_abspath,
+ svn_boolean_t dry_run,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__shelf2_t *shelf;
+ int i;
+ char *abspath;
+
+ SVN_ERR(svn_client__shelf2_open_existing(&shelf, name,
+ local_abspath, ctx, scratch_pool));
+
+ /* Remove the versions. */
+ for (i = shelf->max_version; i > 0; i--)
+ {
+ SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
+ }
+
+ /* Remove the other files */
+ SVN_ERR(get_log_abspath(&abspath, shelf, scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
+ SVN_ERR(get_current_abspath(&abspath, shelf, scratch_pool));
+ SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool));
+
+ SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Baton for paths_changed_visitor(). */
+struct paths_changed_walk_baton_t
+{
+ apr_hash_t *paths_hash;
+ svn_boolean_t as_abspath;
+ const char *wc_root_abspath;
+ apr_pool_t *pool;
+};
+
+/* Add to the list(s) in BATON, the RELPATH of a shelved 'binary' file.
+ * Implements shelved_files_walk_func_t. */
+static svn_error_t *
+paths_changed_visitor(void *baton,
+ const char *relpath,
+ svn_wc_status3_t *s,
+ apr_pool_t *scratch_pool)
+{
+ struct paths_changed_walk_baton_t *b = baton;
+
+ relpath = (b->as_abspath
+ ? svn_dirent_join(b->wc_root_abspath, relpath, b->pool)
+ : apr_pstrdup(b->pool, relpath));
+ svn_hash_sets(b->paths_hash, relpath, relpath);
+ return SVN_NO_ERROR;
+}
+
+/* Get the paths changed, relative to WC root or as abspaths, as a hash
+ * and/or an array (in no particular order).
+ */
+static svn_error_t *
+shelf_paths_changed(apr_hash_t **paths_hash_p,
+ apr_array_header_t **paths_array_p,
+ svn_client__shelf2_version_t *shelf_version,
+ svn_boolean_t as_abspath,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__shelf2_t *shelf = shelf_version->shelf;
+ apr_hash_t *paths_hash = apr_hash_make(result_pool);
+ struct paths_changed_walk_baton_t baton;
+
+ baton.paths_hash = paths_hash;
+ baton.as_abspath = as_abspath;
+ baton.wc_root_abspath = shelf->wc_root_abspath;
+ baton.pool = result_pool;
+ SVN_ERR(shelf_status_walk(shelf_version, "",
+ paths_changed_visitor, &baton,
+ scratch_pool));
+
+ if (paths_hash_p)
+ *paths_hash_p = paths_hash;
+ if (paths_array_p)
+ SVN_ERR(svn_hash_keys(paths_array_p, paths_hash, result_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_paths_changed(apr_hash_t **affected_paths,
+ svn_client__shelf2_version_t *shelf_version,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(shelf_paths_changed(affected_paths, NULL, shelf_version,
+ FALSE /*as_abspath*/,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Send a notification */
+static svn_error_t *
+send_notification(const char *local_abspath,
+ svn_wc_notify_action_t action,
+ svn_node_kind_t kind,
+ svn_wc_notify_state_t content_state,
+ svn_wc_notify_state_t prop_state,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ apr_pool_t *scratch_pool)
+{
+ if (notify_func)
+ {
+ svn_wc_notify_t *notify
+ = svn_wc_create_notify(local_abspath, action, scratch_pool);
+
+ notify->kind = kind;
+ notify->content_state = content_state;
+ notify->prop_state = prop_state;
+ notify_func(notify_baton, notify, scratch_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Merge a shelved change into WC_ABSPATH.
+ */
+static svn_error_t *
+wc_file_merge(const char *wc_abspath,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_wc_notify_state_t property_state;
+ svn_boolean_t has_local_mods;
+ enum svn_wc_merge_outcome_t content_outcome;
+ const char *target_label, *left_label, *right_label;
+ apr_array_header_t *prop_changes;
+
+ /* xgettext: the '.working', '.merge-left' and '.merge-right' strings
+ are used to tag onto a file name in case of a merge conflict */
+ target_label = apr_psprintf(scratch_pool, _(".working"));
+ left_label = apr_psprintf(scratch_pool, _(".merge-left"));
+ right_label = apr_psprintf(scratch_pool, _(".merge-right"));
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool));
+ SVN_ERR(svn_wc_text_modified_p2(&has_local_mods, ctx->wc_ctx,
+ wc_abspath, FALSE, scratch_pool));
+
+ /* Do property merge and text merge in one step so that keyword expansion
+ takes into account the new property values. */
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ svn_wc_merge5(&content_outcome, &property_state, ctx->wc_ctx,
+ left_file, right_file, wc_abspath,
+ left_label, right_label, target_label,
+ NULL, NULL, /*left, right conflict-versions*/
+ FALSE /*dry_run*/, NULL /*diff3_cmd*/,
+ NULL /*merge_options*/,
+ left_props, prop_changes,
+ NULL, NULL,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool),
+ ctx->wc_ctx, wc_abspath,
+ FALSE /*lock_anchor*/, scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Merge a shelved change (of properties) into the dir at WC_ABSPATH.
+ */
+static svn_error_t *
+wc_dir_props_merge(const char *wc_abspath,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *prop_changes;
+ svn_wc_notify_state_t property_state;
+
+ SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool));
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ svn_wc_merge_props3(&property_state, ctx->wc_ctx,
+ wc_abspath,
+ NULL, NULL, /*left, right conflict-versions*/
+ left_props, prop_changes,
+ FALSE /*dry_run*/,
+ NULL, NULL,
+ ctx->cancel_func, ctx->cancel_baton,
+ scratch_pool),
+ ctx->wc_ctx, wc_abspath,
+ FALSE /*lock_anchor*/, scratch_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Apply a shelved "delete" to TO_WC_ABSPATH.
+ */
+static svn_error_t *
+wc_node_delete(const char *to_wc_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ svn_wc_delete4(ctx->wc_ctx,
+ to_wc_abspath,
+ FALSE /*keep_local*/,
+ TRUE /*delete_unversioned_target*/,
+ NULL, NULL, NULL, NULL, /*cancel, notify*/
+ scratch_pool),
+ ctx->wc_ctx, to_wc_abspath,
+ TRUE /*lock_anchor*/, scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Apply a shelved "add" to TO_WC_ABSPATH.
+ * The node must already exist on disk, in a versioned parent dir.
+ */
+static svn_error_t *
+wc_node_add(const char *to_wc_abspath,
+ apr_hash_t *work_props,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ /* If it was not already versioned, schedule the node for addition.
+ (Do not apply autoprops, because this isn't a user-facing "add" but
+ restoring a previously saved state.) */
+ SVN_WC__CALL_WITH_WRITE_LOCK(
+ svn_wc_add_from_disk3(ctx->wc_ctx,
+ to_wc_abspath, work_props,
+ FALSE /* skip checks */,
+ NULL, NULL, scratch_pool),
+ ctx->wc_ctx, to_wc_abspath,
+ TRUE /*lock_anchor*/, scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+/* Baton for apply_file_visitor(). */
+struct apply_files_baton_t
+{
+ svn_client__shelf2_version_t *shelf_version;
+ svn_boolean_t test_only; /* only check whether it would conflict */
+ svn_boolean_t conflict; /* would it conflict? */
+ svn_client_ctx_t *ctx;
+};
+
+/* Copy the file RELPATH from shelf binary file storage to the WC.
+ *
+ * If it is not already versioned, schedule the file for addition.
+ *
+ * Make any missing parent directories.
+ *
+ * In test mode (BATON->test_only): set BATON->conflict if we can't apply
+ * the change to WC at RELPATH without conflict. But in fact, just check
+ * if WC at RELPATH is locally modified.
+ *
+ * Implements shelved_files_walk_func_t. */
+static svn_error_t *
+apply_file_visitor(void *baton,
+ const char *relpath,
+ svn_wc_status3_t *s,
+ apr_pool_t *scratch_pool)
+{
+ struct apply_files_baton_t *b = baton;
+ const char *wc_root_abspath = b->shelf_version->shelf->wc_root_abspath;
+ char *stored_base_abspath, *stored_work_abspath;
+ apr_hash_t *base_props, *work_props;
+ const char *to_wc_abspath = svn_dirent_join(wc_root_abspath, relpath,
+ scratch_pool);
+ const char *to_dir_abspath = svn_dirent_dirname(to_wc_abspath, scratch_pool);
+
+ SVN_ERR(get_base_file_abspath(&stored_base_abspath,
+ b->shelf_version, relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(get_working_file_abspath(&stored_work_abspath,
+ b->shelf_version, relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(read_props_from_shelf(&base_props, &work_props,
+ s->node_status,
+ b->shelf_version, relpath,
+ scratch_pool, scratch_pool));
+
+ if (b->test_only)
+ {
+ svn_wc_status3_t *status;
+
+ SVN_ERR(svn_wc_status3(&status, b->ctx->wc_ctx, to_wc_abspath,
+ scratch_pool, scratch_pool));
+ switch (status->node_status)
+ {
+ case svn_wc_status_normal:
+ case svn_wc_status_none:
+ break;
+ default:
+ b->conflict = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Handle 'delete' and the delete half of 'replace' */
+ if (s->node_status == svn_wc_status_deleted
+ || s->node_status == svn_wc_status_replaced)
+ {
+ SVN_ERR(wc_node_delete(to_wc_abspath, b->ctx, scratch_pool));
+ if (s->node_status != svn_wc_status_replaced)
+ {
+ SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_delete,
+ s->kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable,
+ b->ctx->notify_func2, b->ctx->notify_baton2,
+ scratch_pool));
+ }
+ }
+
+ /* If we can merge a file, do so. */
+ if (s->node_status == svn_wc_status_modified)
+ {
+ if (s->kind == svn_node_dir)
+ {
+ SVN_ERR(wc_dir_props_merge(to_wc_abspath,
+ base_props, work_props,
+ b->ctx, scratch_pool, scratch_pool));
+ }
+ else if (s->kind == svn_node_file)
+ {
+ SVN_ERR(wc_file_merge(to_wc_abspath,
+ stored_base_abspath, stored_work_abspath,
+ base_props, work_props,
+ b->ctx, scratch_pool));
+ }
+ SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_update,
+ s->kind,
+ (s->kind == svn_node_dir)
+ ? svn_wc_notify_state_inapplicable
+ : svn_wc_notify_state_merged,
+ (s->kind == svn_node_dir)
+ ? svn_wc_notify_state_merged
+ : svn_wc_notify_state_unknown,
+ b->ctx->notify_func2, b->ctx->notify_baton2,
+ scratch_pool));
+ }
+
+ /* For an added file, copy it into the WC and ensure it's versioned. */
+ if (s->node_status == svn_wc_status_added
+ || s->node_status == svn_wc_status_replaced)
+ {
+ if (s->kind == svn_node_dir)
+ {
+ SVN_ERR(svn_io_make_dir_recursively(to_wc_abspath, scratch_pool));
+ }
+ else if (s->kind == svn_node_file)
+ {
+ SVN_ERR(svn_io_make_dir_recursively(to_dir_abspath, scratch_pool));
+ SVN_ERR(svn_io_copy_file(stored_work_abspath, to_wc_abspath,
+ TRUE /*copy_perms*/, scratch_pool));
+ }
+ SVN_ERR(wc_node_add(to_wc_abspath, work_props, b->ctx, scratch_pool));
+ SVN_ERR(send_notification(to_wc_abspath,
+ (s->node_status == svn_wc_status_replaced)
+ ? svn_wc_notify_update_replace
+ : svn_wc_notify_update_add,
+ s->kind,
+ svn_wc_notify_state_inapplicable,
+ svn_wc_notify_state_inapplicable,
+ b->ctx->notify_func2, b->ctx->notify_baton2,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*-------------------------------------------------------------------------*/
+/* Diff */
+
+/* */
+static svn_error_t *
+file_changed(svn_client__shelf2_version_t *shelf_version,
+ const char *relpath,
+ svn_wc_status3_t *s,
+ const svn_diff_tree_processor_t *diff_processor,
+ svn_diff_source_t *left_source,
+ svn_diff_source_t *right_source,
+ const char *left_stored_abspath,
+ const char *right_stored_abspath,
+ void *dir_baton,
+ apr_pool_t *scratch_pool)
+{
+ void *fb;
+ svn_boolean_t skip = FALSE;
+
+ SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath,
+ left_source, right_source,
+ NULL /*copyfrom*/,
+ dir_baton, diff_processor,
+ scratch_pool, scratch_pool));
+ if (!skip)
+ {
+ apr_hash_t *left_props, *right_props;
+ apr_array_header_t *prop_changes;
+
+ SVN_ERR(read_props_from_shelf(&left_props, &right_props,
+ s->node_status, shelf_version, relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props,
+ scratch_pool));
+ SVN_ERR(diff_processor->file_changed(
+ relpath,
+ left_source, right_source,
+ left_stored_abspath, right_stored_abspath,
+ left_props, right_props,
+ TRUE /*file_modified*/, prop_changes,
+ fb, diff_processor, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+file_deleted(svn_client__shelf2_version_t *shelf_version,
+ const char *relpath,
+ svn_wc_status3_t *s,
+ const svn_diff_tree_processor_t *diff_processor,
+ svn_diff_source_t *left_source,
+ const char *left_stored_abspath,
+ void *dir_baton,
+ apr_pool_t *scratch_pool)
+{
+ void *fb;
+ svn_boolean_t skip = FALSE;
+
+ SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath,
+ left_source, NULL, NULL /*copyfrom*/,
+ dir_baton, diff_processor,
+ scratch_pool, scratch_pool));
+ if (!skip)
+ {
+ apr_hash_t *left_props, *right_props;
+
+ SVN_ERR(read_props_from_shelf(&left_props, &right_props,
+ s->node_status, shelf_version, relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(diff_processor->file_deleted(relpath,
+ left_source,
+ left_stored_abspath,
+ left_props,
+ fb, diff_processor,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+file_added(svn_client__shelf2_version_t *shelf_version,
+ const char *relpath,
+ svn_wc_status3_t *s,
+ const svn_diff_tree_processor_t *diff_processor,
+ svn_diff_source_t *right_source,
+ const char *right_stored_abspath,
+ void *dir_baton,
+ apr_pool_t *scratch_pool)
+{
+ void *fb;
+ svn_boolean_t skip = FALSE;
+
+ SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath,
+ NULL, right_source, NULL /*copyfrom*/,
+ dir_baton, diff_processor,
+ scratch_pool, scratch_pool));
+ if (!skip)
+ {
+ apr_hash_t *left_props, *right_props;
+
+ SVN_ERR(read_props_from_shelf(&left_props, &right_props,
+ s->node_status, shelf_version, relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(diff_processor->file_added(
+ relpath,
+ NULL /*copyfrom_source*/, right_source,
+ NULL /*copyfrom_abspath*/, right_stored_abspath,
+ NULL /*copyfrom_props*/, right_props,
+ fb, diff_processor, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Baton for diff_visitor(). */
+struct diff_baton_t
+{
+ svn_client__shelf2_version_t *shelf_version;
+ const char *top_relpath; /* top of diff, relative to shelf */
+ const char *walk_root_abspath;
+ const svn_diff_tree_processor_t *diff_processor;
+};
+
+/* Drive BATON->diff_processor.
+ * Implements svn_io_walk_func_t. */
+static svn_error_t *
+diff_visitor(void *baton,
+ const char *abspath,
+ const apr_finfo_t *finfo,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_baton_t *b = baton;
+ const char *relpath;
+
+ relpath = svn_dirent_skip_ancestor(b->walk_root_abspath, abspath);
+ if (finfo->filetype == APR_REG
+ && (strlen(relpath) >= 5 && strcmp(relpath+strlen(relpath)-5, ".meta") == 0))
+ {
+ svn_wc_status3_t *s;
+ void *db = NULL;
+ svn_diff_source_t *left_source;
+ svn_diff_source_t *right_source;
+ char *left_stored_abspath, *right_stored_abspath;
+
+ relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5);
+ if (!svn_relpath_skip_ancestor(b->top_relpath, relpath))
+ return SVN_NO_ERROR;
+
+ SVN_ERR(status_read(&s, b->shelf_version, relpath,
+ scratch_pool, scratch_pool));
+
+ left_source = svn_diff__source_create(s->revision, scratch_pool);
+ right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool);
+ SVN_ERR(get_base_file_abspath(&left_stored_abspath,
+ b->shelf_version, relpath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(get_working_file_abspath(&right_stored_abspath,
+ b->shelf_version, relpath,
+ scratch_pool, scratch_pool));
+
+ switch (s->node_status)
+ {
+ case svn_wc_status_modified:
+ SVN_ERR(file_changed(b->shelf_version, relpath, s,
+ b->diff_processor,
+ left_source, right_source,
+ left_stored_abspath, right_stored_abspath,
+ db, scratch_pool));
+ break;
+ case svn_wc_status_added:
+ SVN_ERR(file_added(b->shelf_version, relpath, s,
+ b->diff_processor,
+ right_source, right_stored_abspath,
+ db, scratch_pool));
+ break;
+ case svn_wc_status_deleted:
+ SVN_ERR(file_deleted(b->shelf_version, relpath, s,
+ b->diff_processor,
+ left_source, left_stored_abspath,
+ db, scratch_pool));
+ break;
+ case svn_wc_status_replaced:
+ SVN_ERR(file_deleted(b->shelf_version, relpath, s,
+ b->diff_processor,
+ left_source, left_stored_abspath,
+ db, scratch_pool));
+ SVN_ERR(file_added(b->shelf_version, relpath, s,
+ b->diff_processor,
+ right_source, right_stored_abspath,
+ db, scratch_pool));
+ default:
+ break;
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_test_apply_file(svn_boolean_t *conflict_p,
+ svn_client__shelf2_version_t *shelf_version,
+ const char *file_relpath,
+ apr_pool_t *scratch_pool)
+{
+ struct apply_files_baton_t baton = {0};
+
+ baton.shelf_version = shelf_version;
+ baton.test_only = TRUE;
+ baton.conflict = FALSE;
+ baton.ctx = shelf_version->shelf->ctx;
+ SVN_ERR(shelf_status_visit_path(shelf_version, file_relpath,
+ apply_file_visitor, &baton,
+ scratch_pool));
+ *conflict_p = baton.conflict;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_apply(svn_client__shelf2_version_t *shelf_version,
+ svn_boolean_t dry_run,
+ apr_pool_t *scratch_pool)
+{
+ struct apply_files_baton_t baton = {0};
+
+ baton.shelf_version = shelf_version;
+ baton.ctx = shelf_version->shelf->ctx;
+ SVN_ERR(shelf_status_walk(shelf_version, "",
+ apply_file_visitor, &baton,
+ scratch_pool));
+
+ svn_io_sleep_for_timestamps(shelf_version->shelf->wc_root_abspath,
+ scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_unapply(svn_client__shelf2_version_t *shelf_version,
+ svn_boolean_t dry_run,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *targets;
+
+ SVN_ERR(shelf_paths_changed(NULL, &targets, shelf_version,
+ TRUE /*as_abspath*/,
+ scratch_pool, scratch_pool));
+ if (!dry_run)
+ {
+ SVN_ERR(svn_client_revert4(targets, svn_depth_empty,
+ NULL /*changelists*/,
+ FALSE /*clear_changelists*/,
+ FALSE /*metadata_only*/,
+ FALSE /*added_keep_local*/,
+ shelf_version->shelf->ctx, scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_delete_newer_versions(svn_client__shelf2_t *shelf,
+ svn_client__shelf2_version_t *shelf_version,
+ apr_pool_t *scratch_pool)
+{
+ int previous_version = shelf_version ? shelf_version->version_number : 0;
+ int i;
+
+ /* Delete any newer checkpoints */
+ for (i = shelf->max_version; i > previous_version; i--)
+ {
+ SVN_ERR(shelf_version_delete(shelf, i, scratch_pool));
+ }
+
+ shelf->max_version = previous_version;
+ SVN_ERR(shelf_write_current(shelf, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_diff(svn_client__shelf2_version_t *shelf_version,
+ const char *shelf_relpath,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const svn_diff_tree_processor_t *diff_processor,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_baton_t baton;
+
+ if (shelf_version->version_number == 0)
+ return SVN_NO_ERROR;
+
+ baton.shelf_version = shelf_version;
+ baton.top_relpath = shelf_relpath;
+ baton.walk_root_abspath = shelf_version->files_dir_abspath;
+ baton.diff_processor = diff_processor;
+ SVN_ERR(svn_io_dir_walk2(baton.walk_root_abspath, 0 /*wanted*/,
+ diff_visitor, &baton,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_save_new_version3(svn_client__shelf2_version_t **new_version_p,
+ svn_client__shelf2_t *shelf,
+ const apr_array_header_t *paths,
+ svn_depth_t depth,
+ const apr_array_header_t *changelists,
+ svn_client_status_func_t shelved_func,
+ void *shelved_baton,
+ svn_client_status_func_t not_shelved_func,
+ void *not_shelved_baton,
+ apr_pool_t *scratch_pool)
+{
+ int next_version = shelf->max_version + 1;
+ svn_client__shelf2_version_t *new_shelf_version;
+ svn_boolean_t any_shelved;
+
+ SVN_ERR(shelf_version_create(&new_shelf_version,
+ shelf, next_version, scratch_pool));
+ SVN_ERR(shelf_write_changes(&any_shelved,
+ new_shelf_version,
+ paths, depth, changelists,
+ shelved_func, shelved_baton,
+ not_shelved_func, not_shelved_baton,
+ shelf->wc_root_abspath,
+ shelf->ctx, scratch_pool, scratch_pool));
+
+ if (any_shelved)
+ {
+ shelf->max_version = next_version;
+ SVN_ERR(shelf_write_current(shelf, scratch_pool));
+
+ if (new_version_p)
+ SVN_ERR(svn_client__shelf2_version_open(new_version_p, shelf, next_version,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ if (new_version_p)
+ *new_version_p = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_get_log_message(char **log_message,
+ svn_client__shelf2_t *shelf,
+ apr_pool_t *result_pool)
+{
+ svn_string_t *propval = svn_hash_gets(shelf->revprops, SVN_PROP_REVISION_LOG);
+
+ if (propval)
+ *log_message = apr_pstrdup(result_pool, propval->data);
+ else
+ *log_message = NULL;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_set_log_message(svn_client__shelf2_t *shelf,
+ const char *message,
+ apr_pool_t *scratch_pool)
+{
+ svn_string_t *propval
+ = message ? svn_string_create(message, shelf->pool) : NULL;
+
+ SVN_ERR(svn_client__shelf2_revprop_set(shelf, SVN_PROP_REVISION_LOG, propval,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_list(apr_hash_t **shelf_infos,
+ const char *local_abspath,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *wc_root_abspath;
+ char *shelves_dir;
+ apr_hash_t *dirents;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(svn_wc__get_wcroot(&wc_root_abspath, ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/,
+ result_pool, scratch_pool));
+
+ *shelf_infos = apr_hash_make(result_pool);
+
+ /* Remove non-shelves */
+ for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const char *filename = apr_hash_this_key(hi);
+ svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
+ char *name = NULL;
+
+ svn_error_clear(shelf_name_from_filename(&name, filename, result_pool));
+ if (name && dirent->kind == svn_node_file)
+ {
+ svn_client__shelf2_info_t *info
+ = apr_palloc(result_pool, sizeof(*info));
+
+ info->mtime = dirent->mtime;
+ svn_hash_sets(*shelf_infos, name, info);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_version_open(svn_client__shelf2_version_t **shelf_version_p,
+ svn_client__shelf2_t *shelf,
+ int version_number,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__shelf2_version_t *shelf_version;
+ const svn_io_dirent2_t *dirent;
+
+ SVN_ERR(shelf_version_create(&shelf_version,
+ shelf, version_number, result_pool));
+ SVN_ERR(svn_io_stat_dirent2(&dirent,
+ shelf_version->files_dir_abspath,
+ FALSE /*verify_truename*/,
+ TRUE /*ignore_enoent*/,
+ result_pool, scratch_pool));
+ if (dirent->kind == svn_node_none)
+ {
+ return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+ _("Shelf '%s' version %d not found"),
+ shelf->name, version_number);
+ }
+ shelf_version->mtime = dirent->mtime;
+ *shelf_version_p = shelf_version;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_get_newest_version(svn_client__shelf2_version_t **shelf_version_p,
+ svn_client__shelf2_t *shelf,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ if (shelf->max_version == 0)
+ {
+ *shelf_version_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_client__shelf2_version_open(shelf_version_p,
+ shelf, shelf->max_version,
+ result_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__shelf2_get_all_versions(apr_array_header_t **versions_p,
+ svn_client__shelf2_t *shelf,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+
+ *versions_p = apr_array_make(result_pool, shelf->max_version - 1,
+ sizeof(svn_client__shelf2_version_t *));
+
+ for (i = 1; i <= shelf->max_version; i++)
+ {
+ svn_client__shelf2_version_t *shelf_version;
+
+ SVN_ERR(svn_client__shelf2_version_open(&shelf_version,
+ shelf, i,
+ result_pool, scratch_pool));
+ APR_ARRAY_PUSH(*versions_p, svn_client__shelf2_version_t *) = shelf_version;
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_client/shelve.c b/subversion/libsvn_client/shelve.c
deleted file mode 100644
index af8dd67bc605..000000000000
--- a/subversion/libsvn_client/shelve.c
+++ /dev/null
@@ -1,552 +0,0 @@
-/*
- * shelve.c: implementation of the 'shelve' commands
- *
- * ====================================================================
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- * ====================================================================
- */
-
-/* ==================================================================== */
-
-/* We define this here to remove any further warnings about the usage of
- experimental functions in this file. */
-#define SVN_EXPERIMENTAL
-
-#include "svn_client.h"
-#include "svn_wc.h"
-#include "svn_pools.h"
-#include "svn_dirent_uri.h"
-#include "svn_path.h"
-#include "svn_hash.h"
-#include "svn_utf.h"
-#include "svn_ctype.h"
-
-#include "client.h"
-#include "private/svn_client_private.h"
-#include "private/svn_wc_private.h"
-#include "svn_private_config.h"
-
-
-static svn_error_t *
-shelf_name_encode(char **encoded_name_p,
- const char *name,
- apr_pool_t *result_pool)
-{
- char *encoded_name
- = apr_palloc(result_pool, strlen(name) * 2 + 1);
- char *out_pos = encoded_name;
-
- if (name[0] == '\0')
- return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
- _("Shelf name cannot be the empty string"));
-
- while (*name)
- {
- apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++));
- out_pos += 2;
- }
- *encoded_name_p = encoded_name;
- return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-shelf_name_decode(char **decoded_name_p,
- const char *codename,
- apr_pool_t *result_pool)
-{
- svn_stringbuf_t *sb
- = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool);
- const char *input = codename;
-
- while (*input)
- {
- int c;
- int nchars;
- int nitems = sscanf(input, "%02x%n", &c, &nchars);
-
- if (nitems != 1 || nchars != 2)
- return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL,
- _("Shelve: Bad encoded name '%s'"), codename);
- svn_stringbuf_appendbyte(sb, c);
- input += 2;
- }
- *decoded_name_p = sb->data;
- return SVN_NO_ERROR;
-}
-
-/* Set *NAME to the shelf name from FILENAME. */
-static svn_error_t *
-shelf_name_from_filename(char **name,
- const char *filename,
- apr_pool_t *result_pool)
-{
- size_t len = strlen(filename);
-
- if (len > 6 && strcmp(filename + len - 6, ".patch") == 0)
- {
- char *codename = apr_pstrndup(result_pool, filename, len - 6);
- SVN_ERR(shelf_name_decode(name, codename, result_pool));
- }
- return SVN_NO_ERROR;
-}
-
-/* Set *PATCH_ABSPATH to the abspath of the patch file for shelved change
- * NAME, no matter whether it exists.
- */
-static svn_error_t *
-get_patch_abspath(char **patch_abspath,
- const char *name,
- const char *wc_root_abspath,
- svn_client_ctx_t *ctx,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- char *dir;
- char *filename;
-
- SVN_ERR(svn_wc__get_shelves_dir(&dir, ctx->wc_ctx, wc_root_abspath,
- scratch_pool, scratch_pool));
- SVN_ERR(shelf_name_encode(&filename, name, scratch_pool));
- filename = apr_pstrcat(scratch_pool, filename, ".patch", SVN_VA_NULL);
- *patch_abspath = svn_dirent_join(dir, filename, result_pool);
- return SVN_NO_ERROR;
-}
-
-/** Write local changes to a patch file for shelved change @a name.
- *
- * @a message: An optional log message.
- *
- * @a wc_root_abspath: The WC root dir.
- *
- * @a overwrite_existing: If a file at @a patch_abspath exists, overwrite it.
- *
- * @a paths, @a depth, @a changelists: The selection of local paths to diff.
- */
-static svn_error_t *
-shelf_write_patch(const char *name,
- const char *message,
- const char *wc_root_abspath,
- svn_boolean_t overwrite_existing,
- const apr_array_header_t *paths,
- svn_depth_t depth,
- const apr_array_header_t *changelists,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
-{
- char *patch_abspath;
- apr_int32_t flag;
- apr_file_t *outfile;
- svn_stream_t *outstream;
- svn_stream_t *errstream;
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- int i;
- svn_opt_revision_t peg_revision = {svn_opt_revision_unspecified, {0}};
- svn_opt_revision_t start_revision = {svn_opt_revision_base, {0}};
- svn_opt_revision_t end_revision = {svn_opt_revision_working, {0}};
-
- SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
- ctx, scratch_pool, scratch_pool));
-
- /* Get streams for the output and any error output of the diff. */
- /* ### svn_stream_open_writable() doesn't work here: the buffering
- goes wrong so that diff headers appear after their hunks.
- For now, fix by opening the file without APR_BUFFERED. */
- flag = APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE;
- if (! overwrite_existing)
- flag |= APR_FOPEN_EXCL;
- SVN_ERR(svn_io_file_open(&outfile, patch_abspath,
- flag, APR_FPROT_OS_DEFAULT, scratch_pool));
- outstream = svn_stream_from_aprfile2(outfile, FALSE /*disown*/, scratch_pool);
- SVN_ERR(svn_stream_for_stderr(&errstream, scratch_pool));
-
- /* Write the patch file header (log message, etc.) */
- if (message)
- {
- SVN_ERR(svn_stream_printf(outstream, scratch_pool, "%s\n",
- message));
- }
- SVN_ERR(svn_stream_printf(outstream, scratch_pool,
- "--This line, and those below, will be ignored--\n\n"));
- SVN_ERR(svn_stream_printf(outstream, scratch_pool,
- "--This patch was generated by 'svn shelve'--\n\n"));
-
- for (i = 0; i < paths->nelts; i++)
- {
- const char *path = APR_ARRAY_IDX(paths, i, const char *);
-
- if (svn_path_is_url(path))
- return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
- _("'%s' is not a local path"), path);
- SVN_ERR(svn_dirent_get_absolute(&path, path, scratch_pool));
-
- SVN_ERR(svn_client_diff_peg6(
- NULL /*options*/,
- path,
- &peg_revision,
- &start_revision,
- &end_revision,
- wc_root_abspath,
- depth,
- TRUE /*notice_ancestry*/,
- FALSE /*no_diff_added*/,
- FALSE /*no_diff_deleted*/,
- TRUE /*show_copies_as_adds*/,
- FALSE /*ignore_content_type: FALSE -> omit binary files*/,
- FALSE /*ignore_properties*/,
- FALSE /*properties_only*/,
- FALSE /*use_git_diff_format*/,
- SVN_APR_LOCALE_CHARSET,
- outstream,
- errstream,
- changelists,
- ctx, iterpool));
- }
- SVN_ERR(svn_stream_close(outstream));
- SVN_ERR(svn_stream_close(errstream));
-
- return SVN_NO_ERROR;
-}
-
-/** Apply the patch file for shelved change @a name to the WC.
- *
- * @a wc_root_abspath: The WC root dir.
- *
- * @a reverse: Apply the patch in reverse.
- *
- * @a dry_run: Don't really apply the changes, just notify what would be done.
- */
-static svn_error_t *
-shelf_apply_patch(const char *name,
- const char *wc_root_abspath,
- svn_boolean_t reverse,
- svn_boolean_t dry_run,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
-{
- char *patch_abspath;
-
- SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
- ctx, scratch_pool, scratch_pool));
- SVN_ERR(svn_client_patch(patch_abspath, wc_root_abspath,
- dry_run, 0 /*strip*/,
- reverse,
- FALSE /*ignore_whitespace*/,
- TRUE /*remove_tempfiles*/, NULL, NULL,
- ctx, scratch_pool));
-
- return SVN_NO_ERROR;
-}
-
-/** Delete the patch file for shelved change @a name.
- *
- * @a wc_root_abspath: The WC root dir.
- */
-static svn_error_t *
-shelf_delete_patch(const char *name,
- const char *wc_root_abspath,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
-{
- char *patch_abspath, *to_abspath;
-
- SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
- ctx, scratch_pool, scratch_pool));
- to_abspath = apr_pstrcat(scratch_pool, patch_abspath, ".bak", SVN_VA_NULL);
-
- /* remove any previous backup */
- SVN_ERR(svn_io_remove_file2(to_abspath, TRUE /*ignore_enoent*/,
- scratch_pool));
-
- /* move the patch to a backup file */
- SVN_ERR(svn_io_file_rename2(patch_abspath, to_abspath, FALSE /*flush_to_disk*/,
- scratch_pool));
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_client_shelve(const char *name,
- const apr_array_header_t *paths,
- svn_depth_t depth,
- const apr_array_header_t *changelists,
- svn_boolean_t keep_local,
- svn_boolean_t dry_run,
- svn_client_ctx_t *ctx,
- apr_pool_t *pool)
-{
- const char *local_abspath;
- const char *wc_root_abspath;
- const char *message = "";
- svn_error_t *err;
-
- /* ### TODO: check all paths are in same WC; for now use first path */
- SVN_ERR(svn_dirent_get_absolute(&local_abspath,
- APR_ARRAY_IDX(paths, 0, char *), pool));
- SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
- local_abspath, ctx, pool, pool));
-
- /* Fetch the log message and any other revprops */
- if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
- {
- const char *tmp_file;
- apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(void *));
-
- SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
- ctx, pool));
- if (! message)
- return SVN_NO_ERROR;
- }
-
- err = shelf_write_patch(name, message, wc_root_abspath,
- FALSE /*overwrite_existing*/,
- paths, depth, changelists,
- ctx, pool);
- if (err && APR_STATUS_IS_EEXIST(err->apr_err))
- {
- return svn_error_quick_wrapf(err,
- "Shelved change '%s' already exists",
- name);
- }
- else
- SVN_ERR(err);
-
- if (!keep_local)
- {
- /* Reverse-apply the patch. This should be a safer way to remove those
- changes from the WC than running a 'revert' operation. */
- SVN_ERR(shelf_apply_patch(name, wc_root_abspath,
- TRUE /*reverse*/, dry_run,
- ctx, pool));
- }
-
- if (dry_run)
- {
- SVN_ERR(shelf_delete_patch(name, wc_root_abspath,
- ctx, pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_client_unshelve(const char *name,
- const char *local_abspath,
- svn_boolean_t keep,
- svn_boolean_t dry_run,
- svn_client_ctx_t *ctx,
- apr_pool_t *pool)
-{
- const char *wc_root_abspath;
- svn_error_t *err;
-
- SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
- local_abspath, ctx, pool, pool));
-
- /* Apply the patch. */
- err = shelf_apply_patch(name, wc_root_abspath,
- FALSE /*reverse*/, dry_run,
- ctx, pool);
- if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
- {
- return svn_error_quick_wrapf(err,
- "Shelved change '%s' not found",
- name);
- }
- else
- SVN_ERR(err);
-
- /* Remove the patch. */
- if (! keep && ! dry_run)
- {
- SVN_ERR(shelf_delete_patch(name, wc_root_abspath,
- ctx, pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_client_shelves_delete(const char *name,
- const char *local_abspath,
- svn_boolean_t dry_run,
- svn_client_ctx_t *ctx,
- apr_pool_t *pool)
-{
- const char *wc_root_abspath;
-
- SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
- local_abspath, ctx, pool, pool));
-
- /* Remove the patch. */
- if (! dry_run)
- {
- svn_error_t *err;
-
- err = shelf_delete_patch(name, wc_root_abspath,
- ctx, pool);
- if (err && APR_STATUS_IS_ENOENT(err->apr_err))
- {
- return svn_error_quick_wrapf(err,
- "Shelved change '%s' not found",
- name);
- }
- else
- SVN_ERR(err);
- }
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_client_shelf_get_paths(apr_hash_t **affected_paths,
- const char *name,
- const char *local_abspath,
- svn_client_ctx_t *ctx,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- const char *wc_root_abspath;
- char *patch_abspath;
- svn_patch_file_t *patch_file;
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- apr_hash_t *paths = apr_hash_make(result_pool);
-
- SVN_ERR(svn_client_get_wc_root(&wc_root_abspath,
- local_abspath, ctx, scratch_pool, scratch_pool));
- SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath,
- ctx, scratch_pool, scratch_pool));
- SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, result_pool));
-
- while (1)
- {
- svn_patch_t *patch;
-
- svn_pool_clear(iterpool);
- SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file,
- FALSE /*reverse*/,
- FALSE /*ignore_whitespace*/,
- iterpool, iterpool));
- if (! patch)
- break;
- svn_hash_sets(paths,
- apr_pstrdup(result_pool, patch->old_filename),
- apr_pstrdup(result_pool, patch->new_filename));
- }
- SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool));
- svn_pool_destroy(iterpool);
-
- *affected_paths = paths;
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_client_shelf_has_changes(svn_boolean_t *has_changes,
- const char *name,
- const char *local_abspath,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
-{
- apr_hash_t *patch_paths;
-
- SVN_ERR(svn_client_shelf_get_paths(&patch_paths, name, local_abspath,
- ctx, scratch_pool, scratch_pool));
- *has_changes = (apr_hash_count(patch_paths) != 0);
- return SVN_NO_ERROR;
-}
-
-/* Set *LOGMSG to the log message stored in the file PATCH_ABSPATH.
- *
- * ### Currently just reads the first line.
- */
-static svn_error_t *
-read_logmsg_from_patch(const char **logmsg,
- const char *patch_abspath,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- apr_file_t *file;
- svn_stream_t *stream;
- svn_boolean_t eof;
- svn_stringbuf_t *line;
-
- SVN_ERR(svn_io_file_open(&file, patch_abspath,
- APR_FOPEN_READ, APR_FPROT_OS_DEFAULT, scratch_pool));
- stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool);
- SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool));
- SVN_ERR(svn_stream_close(stream));
- *logmsg = line->data;
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_client_shelves_list(apr_hash_t **shelved_patch_infos,
- const char *local_abspath,
- svn_client_ctx_t *ctx,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- char *shelves_dir;
- apr_hash_t *dirents;
- apr_hash_index_t *hi;
-
- SVN_ERR(svn_wc__get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath,
- scratch_pool, scratch_pool));
- SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/,
- result_pool, scratch_pool));
-
- *shelved_patch_infos = apr_hash_make(result_pool);
-
- /* Remove non-shelves */
- for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
- {
- const char *filename = apr_hash_this_key(hi);
- svn_io_dirent2_t *dirent = apr_hash_this_val(hi);
- char *name = NULL;
-
- svn_error_clear(shelf_name_from_filename(&name, filename, result_pool));
- if (name && dirent->kind == svn_node_file)
- {
- svn_client_shelved_patch_info_t *info
- = apr_palloc(result_pool, sizeof(*info));
-
- info->dirent = dirent;
- info->mtime = info->dirent->mtime;
- info->patch_path
- = svn_dirent_join(shelves_dir, filename, result_pool);
- SVN_ERR(read_logmsg_from_patch(&info->message, info->patch_path,
- result_pool, scratch_pool));
-
- svn_hash_sets(*shelved_patch_infos, name, info);
- }
- }
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_client_shelves_any(svn_boolean_t *any_shelved,
- const char *local_abspath,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
-{
- apr_hash_t *shelved_patch_infos;
-
- SVN_ERR(svn_client_shelves_list(&shelved_patch_infos, local_abspath,
- ctx, scratch_pool, scratch_pool));
- *any_shelved = apr_hash_count(shelved_patch_infos) != 0;
- return SVN_NO_ERROR;
-}
diff --git a/subversion/libsvn_client/status.c b/subversion/libsvn_client/status.c
index a70165867140..9e53713064d5 100644
--- a/subversion/libsvn_client/status.c
+++ b/subversion/libsvn_client/status.c
@@ -23,6 +23,9 @@
/* ==================================================================== */
+/* We define this here to remove any further warnings about the usage of
+ experimental functions in this file. */
+#define SVN_EXPERIMENTAL
/*** Includes. ***/
@@ -41,6 +44,7 @@
#include "svn_error.h"
#include "svn_hash.h"
+#include "private/svn_client_shelf.h"
#include "private/svn_client_private.h"
#include "private/svn_sorts_private.h"
#include "private/svn_wc_private.h"
@@ -329,6 +333,79 @@ do_external_status(svn_client_ctx_t *ctx,
return SVN_NO_ERROR;
}
+
+/* Run status on shelf SHELF_NAME, if it exists.
+ */
+static svn_error_t *
+shelf_status(const char *shelf_name,
+ const char *target_abspath,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+ svn_client__shelf_t *shelf;
+ svn_client__shelf_version_t *shelf_version;
+ const char *wc_relpath;
+
+ err = svn_client__shelf_open_existing(&shelf,
+ shelf_name, target_abspath,
+ ctx, scratch_pool);
+ if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ SVN_ERR(svn_client__shelf_version_open(&shelf_version,
+ shelf, shelf->max_version,
+ scratch_pool, scratch_pool));
+ wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath);
+ SVN_ERR(svn_client__shelf_version_status_walk(shelf_version, wc_relpath,
+ status_func, status_baton,
+ scratch_pool));
+ SVN_ERR(svn_client__shelf_close(shelf, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Run status on all shelves named in CHANGELISTS by a changelist name
+ * of the form "svn:shelf:SHELF_NAME", if they exist.
+ */
+static svn_error_t *
+shelves_status(const apr_array_header_t *changelists,
+ const char *target_abspath,
+ svn_wc_status_func4_t status_func,
+ void *status_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ static const char PREFIX[] = "svn:shelf:";
+ static const int PREFIX_LEN = 10;
+ int i;
+
+ if (! changelists)
+ return SVN_NO_ERROR;
+ for (i = 0; i < changelists->nelts; i++)
+ {
+ const char *cl = APR_ARRAY_IDX(changelists, i, const char *);
+
+ if (strncmp(cl, PREFIX, PREFIX_LEN) == 0)
+ {
+ const char *shelf_name = cl + PREFIX_LEN;
+
+ SVN_ERR(shelf_status(shelf_name, target_abspath,
+ status_func, status_baton,
+ ctx, scratch_pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
/*** Public Interface. ***/
@@ -586,6 +663,9 @@ svn_client_status6(svn_revnum_t *result_rev,
}
else
{
+ SVN_ERR(shelves_status(changelists, target_abspath,
+ tweak_status, &sb,
+ ctx, pool));
err = svn_wc_walk_status(ctx->wc_ctx, target_abspath,
depth, get_all, no_ignore, FALSE, ignores,
tweak_status, &sb,
diff --git a/subversion/libsvn_client/update.c b/subversion/libsvn_client/update.c
index 602f7e317b57..12469b74c15a 100644
--- a/subversion/libsvn_client/update.c
+++ b/subversion/libsvn_client/update.c
@@ -615,7 +615,7 @@ svn_client__update_internal(svn_revnum_t *result_rev,
{
const char *anchor_abspath, *lockroot_abspath;
svn_error_t *err;
- svn_opt_revision_t peg_revision = *revision;
+ svn_opt_revision_t opt_rev = *revision; /* operative revision */
apr_hash_t *conflicted_paths
= ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
@@ -668,7 +668,7 @@ svn_client__update_internal(svn_revnum_t *result_rev,
err = update_internal(result_rev, timestamp_sleep, conflicted_paths,
&ra_session, missing_parent,
- anchor_abspath, &peg_revision, svn_depth_empty,
+ anchor_abspath, &opt_rev, svn_depth_empty,
FALSE, ignore_externals,
allow_unver_obstructions, adds_as_modification,
FALSE, ctx, pool, iterpool);
@@ -679,8 +679,8 @@ svn_client__update_internal(svn_revnum_t *result_rev,
/* If we successfully updated a missing parent, let's re-use
the returned revision number for future updates for the
sake of consistency. */
- peg_revision.kind = svn_opt_revision_number;
- peg_revision.value.number = *result_rev;
+ opt_rev.kind = svn_opt_revision_number;
+ opt_rev.value.number = *result_rev;
}
svn_pool_destroy(iterpool);
@@ -696,7 +696,7 @@ svn_client__update_internal(svn_revnum_t *result_rev,
err = update_internal(result_rev, timestamp_sleep, conflicted_paths,
&ra_session,
local_abspath, anchor_abspath,
- &peg_revision, depth, depth_is_sticky,
+ &opt_rev, depth, depth_is_sticky,
ignore_externals, allow_unver_obstructions,
adds_as_modification,
TRUE, ctx, pool, pool);
diff --git a/subversion/libsvn_client/upgrade.c b/subversion/libsvn_client/upgrade.c
index 741443af2ddc..edba493d4a01 100644
--- a/subversion/libsvn_client/upgrade.c
+++ b/subversion/libsvn_client/upgrade.c
@@ -303,7 +303,7 @@ upgrade_externals_from_properties(svn_client_ctx_t *ctx,
{
apr_hash_index_t *hi;
apr_pool_t *iterpool;
- apr_pool_t *iterpool2;
+ apr_pool_t *inner_iterpool;
apr_hash_t *externals;
svn_opt_revision_t rev = {svn_opt_revision_unspecified, {0}};
@@ -317,7 +317,7 @@ upgrade_externals_from_properties(svn_client_ctx_t *ctx,
scratch_pool, scratch_pool));
iterpool = svn_pool_create(scratch_pool);
- iterpool2 = svn_pool_create(scratch_pool);
+ inner_iterpool = svn_pool_create(scratch_pool);
for (hi = apr_hash_first(scratch_pool, externals); hi;
hi = apr_hash_next(hi))
@@ -351,14 +351,12 @@ upgrade_externals_from_properties(svn_client_ctx_t *ctx,
iterpool, iterpool);
if (!err)
- externals_parent_url = svn_path_url_add_component2(
- externals_parent_repos_root_url,
- externals_parent_repos_relpath,
- iterpool);
- if (!err)
- err = svn_wc_parse_externals_description3(
- &externals_p, svn_dirent_dirname(local_abspath, iterpool),
- external_desc->data, FALSE, iterpool);
+ {
+ err = svn_wc_parse_externals_description3(
+ &externals_p, svn_dirent_dirname(local_abspath, iterpool),
+ external_desc->data, FALSE, iterpool);
+ }
+
if (err)
{
svn_wc_notify_t *notify =
@@ -376,24 +374,29 @@ upgrade_externals_from_properties(svn_client_ctx_t *ctx,
continue;
}
+ externals_parent_url = svn_path_url_add_component2(
+ externals_parent_repos_root_url,
+ externals_parent_repos_relpath,
+ iterpool);
+
for (i = 0; i < externals_p->nelts; i++)
{
svn_wc_external_item2_t *item;
item = APR_ARRAY_IDX(externals_p, i, svn_wc_external_item2_t*);
- svn_pool_clear(iterpool2);
+ svn_pool_clear(inner_iterpool);
err = upgrade_external_item(ctx, externals_parent_abspath,
externals_parent_url,
externals_parent_repos_root_url,
- item, info_baton, iterpool2);
+ item, info_baton, inner_iterpool);
if (err)
{
svn_wc_notify_t *notify =
svn_wc_create_notify(svn_dirent_join(externals_parent_abspath,
item->target_dir,
- iterpool2),
+ inner_iterpool),
svn_wc_notify_failed_external,
scratch_pool);
notify->err = err;
@@ -405,8 +408,8 @@ upgrade_externals_from_properties(svn_client_ctx_t *ctx,
}
}
+ svn_pool_destroy(inner_iterpool);
svn_pool_destroy(iterpool);
- svn_pool_destroy(iterpool2);
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_client/util.c b/subversion/libsvn_client/util.c
index 248412b04e8d..f6612b96e9bf 100644
--- a/subversion/libsvn_client/util.c
+++ b/subversion/libsvn_client/util.c
@@ -93,6 +93,7 @@ svn_client__pathrev_create_with_session(svn_client__pathrev_t **pathrev_p,
pathrev->rev = rev;
pathrev->url = apr_pstrdup(result_pool, url);
*pathrev_p = pathrev;
+ SVN_ERR_ASSERT(svn_uri__is_ancestor(pathrev->repos_root_url, url));
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_client/wc_editor.c b/subversion/libsvn_client/wc_editor.c
new file mode 100644
index 000000000000..145fce05650e
--- /dev/null
+++ b/subversion/libsvn_client/wc_editor.c
@@ -0,0 +1,655 @@
+/*
+ * wc_editor.c: editing the local modifications in the WC.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+/* ==================================================================== */
+
+/*** Includes. ***/
+
+#include <string.h>
+#include "svn_hash.h"
+#include "svn_client.h"
+#include "svn_delta.h"
+#include "svn_dirent_uri.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_wc.h"
+
+#include <apr_md5.h>
+
+#include "client.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_wc_private.h"
+#include "svn_private_config.h"
+
+
+/* ------------------------------------------------------------------ */
+
+/* WC Modifications Editor.
+ *
+ * This editor applies incoming modifications onto the current working state
+ * of the working copy, to produce a new working state.
+ *
+ * Currently, it assumes the working state matches what the edit driver
+ * expects to find, and may throw an error if not.
+ *
+ * For simplicity, we apply incoming edits as they arrive, rather than
+ * queueing them up to apply in a batch.
+ *
+ * TODO:
+ * - tests
+ * - use for all existing scenarios ('svn add', 'svn propset', etc.)
+ * - Instead of 'root_dir_add' option, probably the driver should anchor
+ * at the parent dir.
+ * - Instead of 'ignore_mergeinfo' option, implement that as a wrapper.
+ * - Option to quietly accept changes that seem to be already applied
+ * in the versioned state and/or on disk.
+ * Consider 'svn add' which assumes items to be added are found on disk.
+ * - Notification.
+ */
+
+/* Everything we need to know about the edit session.
+ */
+struct edit_baton_t
+{
+ const char *anchor_abspath;
+ svn_boolean_t manage_wc_write_lock;
+ const char *lock_root_abspath; /* the path locked, when locked */
+
+ /* True => 'open_root' method will act as 'add_directory' */
+ svn_boolean_t root_dir_add;
+ /* True => filter out any incoming svn:mergeinfo property changes */
+ svn_boolean_t ignore_mergeinfo_changes;
+
+ svn_ra_session_t *ra_session;
+
+ svn_wc_context_t *wc_ctx;
+ svn_client_ctx_t *ctx;
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+};
+
+/* Everything we need to know about a directory that's open for edits.
+ */
+struct dir_baton_t
+{
+ apr_pool_t *pool;
+
+ struct edit_baton_t *eb;
+
+ const char *local_abspath;
+};
+
+/* Join PATH onto ANCHOR_ABSPATH.
+ * Throw an error if the result is outside ANCHOR_ABSPATH.
+ */
+static svn_error_t *
+get_path(const char **local_abspath_p,
+ const char *anchor_abspath,
+ const char *path,
+ apr_pool_t *result_pool)
+{
+ svn_boolean_t under_root;
+
+ SVN_ERR(svn_dirent_is_under_root(&under_root, local_abspath_p,
+ anchor_abspath, path, result_pool));
+ if (! under_root)
+ {
+ return svn_error_createf(
+ SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
+ _("Path '%s' is not in the working copy"),
+ svn_dirent_local_style(path, result_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+/* Create a directory on disk and add it to version control,
+ * with no properties.
+ */
+static svn_error_t *
+mkdir(const char *abspath,
+ struct edit_baton_t *eb,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_io_make_dir_recursively(abspath, scratch_pool));
+ SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath,
+ NULL /*properties*/,
+ TRUE /* skip checks */,
+ eb->notify_func, eb->notify_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Prepare to open or add a directory: initialize a new dir baton.
+ *
+ * If PATH is "" and PB is null, it represents the root directory of
+ * the edit; otherwise PATH is not "" and PB is not null.
+ */
+static svn_error_t *
+dir_open_or_add(struct dir_baton_t **child_dir_baton,
+ const char *path,
+ struct dir_baton_t *pb,
+ struct edit_baton_t *eb,
+ apr_pool_t *dir_pool)
+{
+ struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db));
+
+ db->pool = dir_pool;
+ db->eb = eb;
+
+ SVN_ERR(get_path(&db->local_abspath,
+ eb->anchor_abspath, path, dir_pool));
+
+ *child_dir_baton = db;
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+release_write_lock(struct edit_baton_t *eb,
+ apr_pool_t *scratch_pool)
+{
+ if (eb->lock_root_abspath)
+ {
+ SVN_ERR(svn_wc__release_write_lock(
+ eb->ctx->wc_ctx, eb->lock_root_abspath, scratch_pool));
+ eb->lock_root_abspath = NULL;
+ }
+ return SVN_NO_ERROR;
+}
+
+/* */
+static apr_status_t
+pool_cleanup_handler(void *root_baton)
+{
+ struct dir_baton_t *db = root_baton;
+ struct edit_baton_t *eb = db->eb;
+
+ svn_error_clear(release_write_lock(eb, db->pool));
+ return APR_SUCCESS;
+}
+
+/* svn_delta_editor_t function */
+static svn_error_t *
+edit_open(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **root_baton)
+{
+ struct edit_baton_t *eb = edit_baton;
+ struct dir_baton_t *db;
+
+ SVN_ERR(dir_open_or_add(&db, "", NULL, eb, result_pool));
+
+ /* Acquire a WC write lock */
+ if (eb->manage_wc_write_lock)
+ {
+ apr_pool_cleanup_register(db->pool, db,
+ pool_cleanup_handler,
+ apr_pool_cleanup_null);
+ SVN_ERR(svn_wc__acquire_write_lock(&eb->lock_root_abspath,
+ eb->ctx->wc_ctx,
+ eb->anchor_abspath,
+ FALSE /*lock_anchor*/,
+ db->pool, db->pool));
+ }
+
+ if (eb->root_dir_add)
+ {
+ SVN_ERR(mkdir(db->local_abspath, eb, result_pool));
+ }
+
+ *root_baton = db;
+ return SVN_NO_ERROR;
+}
+
+/* svn_delta_editor_t function */
+static svn_error_t *
+edit_close_or_abort(void *edit_baton,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(release_write_lock(edit_baton, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ const char *local_abspath;
+
+ SVN_ERR(get_path(&local_abspath,
+ eb->anchor_abspath, path, scratch_pool));
+ SVN_ERR(svn_wc_delete4(eb->wc_ctx, local_abspath,
+ FALSE /*keep_local*/,
+ TRUE /*delete_unversioned*/,
+ NULL, NULL, /*cancellation*/
+ eb->notify_func, eb->notify_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t function. */
+static svn_error_t *
+dir_open(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **child_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct dir_baton_t *db;
+
+ SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool));
+
+ *child_baton = db;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dir_add(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *result_pool,
+ void **child_baton)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct dir_baton_t *db;
+ /* ### Our caller should be providing a scratch pool */
+ apr_pool_t *scratch_pool = svn_pool_create(result_pool);
+
+ SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool));
+
+ if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision))
+ {
+ SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/,
+ svn_node_dir,
+ copyfrom_path,
+ copyfrom_revision,
+ db->local_abspath,
+ db->eb->ra_session,
+ db->eb->ctx,
+ scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(mkdir(db->local_abspath, eb, result_pool));
+ }
+
+ *child_baton = db;
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dir_change_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct dir_baton_t *db = dir_baton;
+ struct edit_baton_t *eb = db->eb;
+
+ if (svn_property_kind2(name) != svn_prop_regular_kind
+ || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO)))
+ {
+ /* We can't handle DAV, ENTRY and merge specific props here */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value,
+ svn_depth_empty, FALSE, NULL,
+ NULL, NULL, /* Cancellation */
+ NULL, NULL, /* Notification */
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dir_close(void *dir_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* Everything we need to know about a file that's open for edits.
+ */
+struct file_baton_t
+{
+ apr_pool_t *pool;
+
+ struct edit_baton_t *eb;
+
+ const char *local_abspath;
+
+ /* fields for the transfer of text changes */
+ const char *writing_file;
+ unsigned char digest[APR_MD5_DIGESTSIZE]; /* MD5 digest of new fulltext */
+ svn_stream_t *wc_file_read_stream, *tmp_file_write_stream;
+ const char *tmp_path;
+};
+
+/* Create a new file on disk and add it to version control.
+ *
+ * The file is empty and has no properties.
+ */
+static svn_error_t *
+mkfile(const char *abspath,
+ struct edit_baton_t *eb,
+ apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_io_file_create_empty(abspath, scratch_pool));
+ SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath,
+ NULL /*properties*/,
+ TRUE /* skip checks */,
+ eb->notify_func, eb->notify_baton,
+ scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+file_open_or_add(const char *path,
+ void *parent_baton,
+ struct file_baton_t **file_baton,
+ apr_pool_t *file_pool)
+{
+ struct dir_baton_t *pb = parent_baton;
+ struct edit_baton_t *eb = pb->eb;
+ struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb));
+
+ fb->pool = file_pool;
+ fb->eb = eb;
+ SVN_ERR(get_path(&fb->local_abspath,
+ eb->anchor_abspath, path, fb->pool));
+
+ *file_baton = fb;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+file_open(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **file_baton)
+{
+ struct file_baton_t *fb;
+
+ SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool));
+
+ *file_baton = fb;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+file_add(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *result_pool,
+ void **file_baton)
+{
+ struct file_baton_t *fb;
+
+ SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool));
+
+ if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision))
+ {
+ SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/,
+ svn_node_file,
+ copyfrom_path,
+ copyfrom_revision,
+ fb->local_abspath,
+ fb->eb->ra_session,
+ fb->eb->ctx, fb->pool));
+ }
+ else
+ {
+ SVN_ERR(mkfile(fb->local_abspath, fb->eb, result_pool));
+ }
+
+ *file_baton = fb;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+file_change_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton_t *fb = file_baton;
+ struct edit_baton_t *eb = fb->eb;
+
+ if (svn_property_kind2(name) != svn_prop_regular_kind
+ || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO)))
+ {
+ /* We can't handle DAV, ENTRY and merge specific props here */
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, fb->local_abspath, name, value,
+ svn_depth_empty, FALSE, NULL,
+ NULL, NULL, /* Cancellation */
+ NULL, NULL, /* Notification */
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+file_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *result_pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct file_baton_t *fb = file_baton;
+ const char *target_dir = svn_dirent_dirname(fb->local_abspath, fb->pool);
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(! fb->writing_file);
+
+ err = svn_stream_open_readonly(&fb->wc_file_read_stream, fb->local_abspath,
+ fb->pool, fb->pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ fb->wc_file_read_stream = svn_stream_empty(fb->pool);
+ }
+ else
+ SVN_ERR(err);
+
+ SVN_ERR(svn_stream_open_unique(&fb->tmp_file_write_stream, &fb->writing_file,
+ target_dir, svn_io_file_del_none,
+ fb->pool, fb->pool));
+
+ svn_txdelta_apply(fb->wc_file_read_stream,
+ fb->tmp_file_write_stream,
+ fb->digest,
+ fb->local_abspath,
+ fb->pool,
+ /* Provide the handler directly */
+ handler, handler_baton);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+file_close(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *scratch_pool)
+{
+ struct file_baton_t *fb = file_baton;
+
+ /* If we have text changes, write them to disk */
+ if (fb->writing_file)
+ {
+ SVN_ERR(svn_stream_close(fb->wc_file_read_stream));
+ SVN_ERR(svn_io_file_rename2(fb->writing_file, fb->local_abspath,
+ FALSE /*flush*/, scratch_pool));
+ }
+
+ if (text_checksum)
+ {
+ svn_checksum_t *expected_checksum;
+ svn_checksum_t *actual_checksum;
+
+ SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
+ text_checksum, fb->pool));
+ actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool);
+
+ if (! svn_checksum_match(expected_checksum, actual_checksum))
+ return svn_error_trace(
+ svn_checksum_mismatch_err(expected_checksum,
+ actual_checksum,
+ fb->pool,
+ _("Checksum mismatch for '%s'"),
+ svn_dirent_local_style(
+ fb->local_abspath,
+ fb->pool)));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__wc_editor_internal(const svn_delta_editor_t **editor_p,
+ void **edit_baton_p,
+ const char *dst_abspath,
+ svn_boolean_t root_dir_add,
+ svn_boolean_t ignore_mergeinfo_changes,
+ svn_boolean_t manage_wc_write_lock,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool)
+{
+ svn_delta_editor_t *editor = svn_delta_default_editor(result_pool);
+ struct edit_baton_t *eb = apr_pcalloc(result_pool, sizeof(*eb));
+
+ eb->anchor_abspath = apr_pstrdup(result_pool, dst_abspath);
+ eb->manage_wc_write_lock = manage_wc_write_lock;
+ eb->lock_root_abspath = NULL;
+ eb->root_dir_add = root_dir_add;
+ eb->ignore_mergeinfo_changes = ignore_mergeinfo_changes;
+
+ eb->ra_session = ra_session;
+ eb->wc_ctx = ctx->wc_ctx;
+ eb->ctx = ctx;
+ eb->notify_func = notify_func;
+ eb->notify_baton = notify_baton;
+
+ editor->open_root = edit_open;
+ editor->close_edit = edit_close_or_abort;
+ editor->abort_edit = edit_close_or_abort;
+
+ editor->delete_entry = delete_entry;
+
+ editor->open_directory = dir_open;
+ editor->add_directory = dir_add;
+ editor->change_dir_prop = dir_change_prop;
+ editor->close_directory = dir_close;
+
+ editor->open_file = file_open;
+ editor->add_file = file_add;
+ editor->change_file_prop = file_change_prop;
+ editor->apply_textdelta = file_textdelta;
+ editor->close_file = file_close;
+
+ *editor_p = editor;
+ *edit_baton_p = eb;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__wc_editor(const svn_delta_editor_t **editor_p,
+ void **edit_baton_p,
+ const char *dst_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_ra_session_t *ra_session,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_client__wc_editor_internal(editor_p, edit_baton_p,
+ dst_abspath,
+ FALSE /*root_dir_add*/,
+ FALSE /*ignore_mergeinfo_changes*/,
+ TRUE /*manage_wc_write_lock*/,
+ notify_func, notify_baton,
+ ra_session,
+ ctx, result_pool));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__wc_copy_mods(const char *src_wc_abspath,
+ const char *dst_wc_abspath,
+ svn_wc_notify_func2_t notify_func,
+ void *notify_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_client__pathrev_t *base;
+ const char *dst_wc_url;
+ svn_ra_session_t *ra_session;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_array_header_t *src_targets = apr_array_make(scratch_pool, 1,
+ sizeof(char *));
+
+ /* We'll need an RA session to obtain the base of any copies */
+ SVN_ERR(svn_client__wc_node_get_base(&base,
+ src_wc_abspath, ctx->wc_ctx,
+ scratch_pool, scratch_pool));
+ dst_wc_url = base->url;
+ SVN_ERR(svn_client_open_ra_session2(&ra_session,
+ dst_wc_url, dst_wc_abspath,
+ ctx, scratch_pool, scratch_pool));
+ SVN_ERR(svn_client__wc_editor(&editor, &edit_baton,
+ dst_wc_abspath,
+ NULL, NULL, /*notification*/
+ ra_session, ctx, scratch_pool));
+
+ APR_ARRAY_PUSH(src_targets, const char *) = src_wc_abspath;
+ SVN_ERR(svn_client__wc_replay(src_wc_abspath,
+ src_targets, svn_depth_infinity, NULL,
+ editor, edit_baton,
+ notify_func, notify_baton,
+ ctx, scratch_pool));
+
+ return SVN_NO_ERROR;
+}