You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by pr...@apache.org on 2013/02/13 11:21:36 UTC
svn commit: r1445542 [4/16] - in /subversion/branches/verify-keep-going: ./
build/generator/ build/generator/swig/ build/generator/templates/
build/win32/ contrib/server-side/fsfsfixer/fixer/
contrib/server-side/svncutter/ notes/ notes/api-errata/1.7/ ...
Modified: subversion/branches/verify-keep-going/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/verify-keep-going/subversion/libsvn_client/merge.c?rev=1445542&r1=1445541&r2=1445542&view=diff
==============================================================================
--- subversion/branches/verify-keep-going/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/verify-keep-going/subversion/libsvn_client/merge.c Wed Feb 13 10:21:33 2013
@@ -64,6 +64,7 @@
#include "svn_private_config.h"
+
/*-----------------------------------------------------------------------*/
/* MERGEINFO MERGE SOURCE NORMALIZATION
@@ -197,9 +198,9 @@
* merge target and any of its subtrees which have explicit mergeinfo
* or otherwise need special attention during a merge.
*
- * During mergeinfo unaware merges, CHILDREN_WITH_MERGEINFO is created by
- * do_mergeinfo_unaware_dir_merge and contains only one element describing
- * a contiguous range to be merged to the WC merge target.
+ * During mergeinfo unaware merges, CHILDREN_WITH_MERGEINFO contains
+ * contains only one element (added by do_mergeinfo_unaware_dir_merge)
+ * describing a contiguous range to be merged to the WC merge target.
*
* During mergeinfo aware merges CHILDREN_WITH_MERGEINFO is created
* by get_mergeinfo_paths() and outside of that function and its helpers
@@ -234,9 +235,6 @@ typedef struct merge_target_t
/* Absolute path to the WC node */
const char *abspath;
- /* Node kind of the WC node (at the start of the merge) */
- svn_node_kind_t kind;
-
/* The repository location of the base node of the target WC. If the node
* is locally added, then URL & REV are NULL & SVN_INVALID_REVNUM.
* REPOS_ROOT_URL and REPOS_UUID are always valid. */
@@ -278,14 +276,6 @@ typedef struct merge_cmd_baton_t {
svn_client_ctx_t *ctx; /* Client context for callbacks, etc. */
- /* The list of paths for nodes we've deleted, used only when in
- dry_run mode. */
- apr_hash_t *dry_run_deletions;
-
- /* The list of paths for nodes we've added, used only when in
- dry_run mode. */
- apr_hash_t *dry_run_added;
-
/* The list of any paths which remained in conflict after a
resolution attempt was made. We track this in-memory, rather
than just using WC entry state, since the latter doesn't help us
@@ -352,36 +342,17 @@ typedef struct merge_cmd_baton_t {
or do_file_merge() in do_merge(). */
apr_pool_t *pool;
- /* Contains any state collected while receiving path notifications. */
- struct notification_receiver_baton_t
+
+ /* State for notify_merge_begin() */
+ struct notify_begin_state_t
{
- /* The wrapped callback and baton. */
- svn_wc_notify_func2_t wrapped_func;
- void *wrapped_baton;
-
- /* The number of operative notifications received. */
- apr_uint32_t nbr_operative_notifications;
-
- /* Flag indicating whether it is a single file merge or not. */
- svn_boolean_t is_single_file_merge;
-
- /* Depth first ordered list of paths that needs special care while merging.
- ### And ...? This is not just a list of paths. See the global comment
- 'THE CHILDREN_WITH_MERGEINFO ARRAY'.
- This defaults to NULL. For 'same_url' merge alone we set it to
- proper array. This is used by notification_receiver to put a
- merge notification begin lines. */
- apr_array_header_t *children_with_mergeinfo;
-
- /* The path in CHILDREN_WITH_MERGEINFO where we found the nearest ancestor
- for merged path. Default value is null. */
- const char *cur_ancestor_abspath;
-
- /* Pool with a sufficient lifetime to be used for output members such as
- * MERGED_ABSPATHS. */
- apr_pool_t *pool;
+ /* Cache of which abspath was last notified. */
+ const char *last_abspath;
- } nrb;
+ /* 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;
} merge_cmd_baton_t;
@@ -515,7 +486,7 @@ store_path(apr_hash_t *path_hash, const
const char *dup_path = apr_pstrdup(apr_hash_pool_get(path_hash),
local_abspath);
- apr_hash_set(path_hash, dup_path, APR_HASH_KEY_STRING, dup_path);
+ svn_hash_sets(path_hash, dup_path, dup_path);
}
/* Store LOCAL_ABSPATH in *PATH_HASH_P after duplicating it into the pool
@@ -531,41 +502,6 @@ alloc_and_store_path(apr_hash_t **path_h
store_path(*path_hash_p, local_abspath);
}
-/* Helper function to easy in checking if a path is in a path hash */
-static APR_INLINE svn_boolean_t
-contains_path(apr_hash_t *path_hash, const char *local_abspath)
-{
- return apr_hash_get(path_hash, local_abspath, APR_HASH_KEY_STRING) != NULL;
-}
-
-/* Return true iff we're in dry-run mode and LOCAL_ABSPATH would have been
- deleted by now if we weren't in dry-run mode.
- Used to avoid spurious notifications (e.g. conflicts) from a merge
- attempt into an existing target which would have been deleted if we
- weren't in dry_run mode (issue #2584). */
-static APR_INLINE svn_boolean_t
-dry_run_deleted_p(const merge_cmd_baton_t *merge_b,
- const char *local_abspath)
-{
- return (merge_b->dry_run &&
- apr_hash_get(merge_b->dry_run_deletions, local_abspath,
- APR_HASH_KEY_STRING) != NULL);
-}
-
-/* Return true iff we're in dry-run mode and LOCAL_ABSPATH would have been
- added by now if we weren't in dry-run mode.
- Used to avoid spurious notifications (e.g. conflicts) from a merge
- attempt into an existing target which would have been deleted if we
- weren't in dry_run mode (issue #2584). */
-static APR_INLINE svn_boolean_t
-dry_run_added_p(const merge_cmd_baton_t *merge_b,
- const char *local_abspath)
-{
- return (merge_b->dry_run &&
- apr_hash_get(merge_b->dry_run_added, local_abspath,
- APR_HASH_KEY_STRING) != NULL);
-}
-
/* Return whether any WC path was put in conflict by the merge
operation corresponding to MERGE_B. */
static APR_INLINE svn_boolean_t
@@ -593,10 +529,11 @@ is_path_conflicted_by_merge(merge_cmd_ba
static svn_error_t *
perform_obstruction_check(svn_wc_notify_state_t *obstruction_state,
svn_boolean_t *deleted,
+ svn_boolean_t *excluded,
svn_node_kind_t *kind,
+ svn_depth_t *parent_depth,
const merge_cmd_baton_t *merge_b,
const char *local_abspath,
- svn_node_kind_t expected_kind,
apr_pool_t *scratch_pool)
{
svn_wc_context_t *wc_ctx = merge_b->ctx->wc_ctx;
@@ -612,42 +549,6 @@ perform_obstruction_check(svn_wc_notify_
if (kind)
*kind = svn_node_none;
- /* In a dry run, make as if nodes "deleted" by the dry run appear so. */
- if (merge_b->dry_run)
- {
- if (dry_run_deleted_p(merge_b, local_abspath))
- {
- /* svn_wc_notify_state_inapplicable */
- /* svn_node_none */
-
- if (deleted)
- *deleted = TRUE;
-
- if (expected_kind != svn_node_unknown
- && expected_kind != svn_node_none)
- *obstruction_state = svn_wc_notify_state_obstructed;
- return SVN_NO_ERROR;
- }
- else if (dry_run_added_p(merge_b, local_abspath))
- {
- /* svn_wc_notify_state_inapplicable */
-
- if (kind)
- *kind = svn_node_dir; /* Currently only used for dirs */
-
- return SVN_NO_ERROR;
- }
- else if (dry_run_added_p(merge_b,
- svn_dirent_dirname(local_abspath,
- scratch_pool)))
- {
- /* svn_wc_notify_state_inapplicable */
- /* svn_node_none */
-
- return SVN_NO_ERROR;
- }
- }
-
if (kind == NULL)
kind = &wc_kind;
@@ -656,24 +557,18 @@ perform_obstruction_check(svn_wc_notify_
SVN_ERR(svn_wc__check_for_obstructions(obstruction_state,
kind,
deleted,
+ excluded,
+ parent_depth,
wc_ctx, local_abspath,
check_root,
scratch_pool));
-
- if (*obstruction_state == svn_wc_notify_state_inapplicable
- && expected_kind != svn_node_unknown
- && *kind != expected_kind)
- {
- *obstruction_state = svn_wc_notify_state_obstructed;
- }
-
return SVN_NO_ERROR;
}
/* Create *LEFT and *RIGHT conflict versions for conflict victim
* at VICTIM_ABSPATH, with kind NODE_KIND, using information obtained
* from MERGE_SOURCE and TARGET.
- * Allocate returned conflict versions in POOL. */
+ * Allocate returned conflict versions in RESULT_POOL. */
static svn_error_t *
make_conflict_versions(const svn_wc_conflict_version_t **left,
const svn_wc_conflict_version_t **right,
@@ -681,7 +576,8 @@ make_conflict_versions(const svn_wc_conf
svn_node_kind_t node_kind,
const merge_source_t *merge_source,
const merge_target_t *target,
- apr_pool_t *pool)
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
const char *child = svn_dirent_skip_ancestor(target->abspath,
victim_abspath);
@@ -689,182 +585,25 @@ make_conflict_versions(const svn_wc_conf
SVN_ERR_ASSERT(child != NULL);
left_relpath = svn_client__pathrev_relpath(merge_source->loc1,
- pool);
+ scratch_pool);
right_relpath = svn_client__pathrev_relpath(merge_source->loc2,
- pool);
+ scratch_pool);
*left = svn_wc_conflict_version_create2(
merge_source->loc1->repos_root_url,
merge_source->loc1->repos_uuid,
- svn_relpath_join(left_relpath, child, pool),
- merge_source->loc1->rev, node_kind, pool);
+ svn_relpath_join(left_relpath, child, scratch_pool),
+ merge_source->loc1->rev, node_kind, result_pool);
*right = svn_wc_conflict_version_create2(
merge_source->loc2->repos_root_url,
merge_source->loc2->repos_uuid,
- svn_relpath_join(right_relpath, child, pool),
- merge_source->loc2->rev, node_kind, pool);
-
- return SVN_NO_ERROR;
-}
-
-/* Set *CONFLICT to a new tree-conflict description allocated in POOL,
- * populated with information from the other parameters.
- * See tree_conflict() for the other parameters.
- */
-static svn_error_t*
-make_tree_conflict(svn_wc_conflict_description2_t **conflict,
- const char *victim_abspath,
- svn_node_kind_t node_kind,
- svn_wc_conflict_action_t action,
- svn_wc_conflict_reason_t reason,
- const merge_source_t *merge_source,
- const merge_target_t *target,
- apr_pool_t *pool)
-{
- const svn_wc_conflict_version_t *left;
- const svn_wc_conflict_version_t *right;
-
- SVN_ERR(make_conflict_versions(&left, &right, victim_abspath, node_kind,
- merge_source, target, pool));
-
- *conflict = svn_wc_conflict_description_create_tree2(
- victim_abspath, node_kind, svn_wc_operation_merge,
- left, right, pool);
-
- (*conflict)->action = action;
- (*conflict)->reason = reason;
-
- return SVN_NO_ERROR;
-}
-
-/* Record a tree conflict in the WC, unless this is a dry run or a record-
- * only merge, or if a tree conflict is already flagged for the VICTIM_PATH.
- * (The latter can happen if a merge-tracking-aware merge is doing multiple
- * editor drives because of a gap in the range of eligible revisions.)
- *
- * The tree conflict, with its victim specified by VICTIM_PATH, is
- * assumed to have happened during a merge using merge baton MERGE_B.
- *
- * NODE_KIND must be the node kind of "old" and "theirs" and "mine";
- * this function cannot cope with node kind clashes.
- * ACTION and REASON correspond to the fields
- * of the same names in svn_wc_tree_conflict_description_t.
- */
-static svn_error_t*
-tree_conflict(merge_cmd_baton_t *merge_b,
- const char *victim_abspath,
- svn_node_kind_t node_kind,
- svn_wc_conflict_action_t action,
- svn_wc_conflict_reason_t reason)
-{
- const svn_wc_conflict_description2_t *existing_conflict;
- svn_error_t *err;
-
- if (merge_b->record_only || merge_b->dry_run)
- return SVN_NO_ERROR;
-
- err = svn_wc__get_tree_conflict(&existing_conflict, merge_b->ctx->wc_ctx,
- victim_abspath, merge_b->pool,
- merge_b->pool);
-
- if (err)
- {
- if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
- return svn_error_trace(err);
-
- svn_error_clear(err);
- existing_conflict = FALSE;
- }
-
- if (existing_conflict == NULL)
- {
- svn_wc_conflict_description2_t *conflict;
-
- /* There is no existing tree conflict so it is safe to add one. */
- SVN_ERR(make_tree_conflict(&conflict, victim_abspath,
- node_kind, action, reason,
- &merge_b->merge_source, merge_b->target,
- merge_b->pool));
- SVN_ERR(svn_wc__add_tree_conflict(merge_b->ctx->wc_ctx, conflict,
- merge_b->pool));
-
- alloc_and_store_path(&merge_b->conflicted_paths, victim_abspath,
- merge_b->pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Similar to tree_conflict(), but if this is an "add" action and there
- is an existing tree conflict on the victim with a "delete" action, then
- combine the two conflicts into a single conflict with a "replace" action. */
-static svn_error_t*
-tree_conflict_on_add(merge_cmd_baton_t *merge_b,
- const char *victim_abspath,
- svn_node_kind_t node_kind,
- svn_wc_conflict_action_t action,
- svn_wc_conflict_reason_t reason)
-{
- const svn_wc_conflict_description2_t *existing_conflict;
- svn_wc_conflict_description2_t *conflict;
+ svn_relpath_join(right_relpath, child, scratch_pool),
+ merge_source->loc2->rev, node_kind, result_pool);
- if (merge_b->record_only || merge_b->dry_run)
- return SVN_NO_ERROR;
-
- /* Construct the new conflict first compare the new conflict with
- a possibly existing one. */
- SVN_ERR(make_tree_conflict(&conflict, victim_abspath,
- node_kind, action, reason,
- &merge_b->merge_source, merge_b->target,
- merge_b->pool));
-
- SVN_ERR(svn_wc__get_tree_conflict(&existing_conflict, merge_b->ctx->wc_ctx,
- victim_abspath, merge_b->pool,
- merge_b->pool));
-
- if (existing_conflict == NULL)
- {
- /* There is no existing tree conflict so it is safe to add one. */
- SVN_ERR(svn_wc__add_tree_conflict(merge_b->ctx->wc_ctx, conflict,
- merge_b->pool));
-
- alloc_and_store_path(&merge_b->conflicted_paths, victim_abspath,
- merge_b->pool);
- }
- else if (existing_conflict->action == svn_wc_conflict_action_delete &&
- conflict->action == svn_wc_conflict_action_add)
- {
- /* There is already a tree conflict raised by a previous incoming
- * change that attempted to delete the item (whether in this same
- * merge operation or not). Change the existing conflict to note
- * that the incoming change is replacement. */
-
- /* Remove the existing tree-conflict so we can add a new one.*/
- SVN_ERR(svn_wc__del_tree_conflict(merge_b->ctx->wc_ctx,
- victim_abspath, merge_b->pool));
-
- /* Preserve the reason which caused the first conflict,
- * re-label the incoming change as 'replacement', and update
- * version info for the left version of the conflict. */
- conflict->reason = existing_conflict->reason;
- conflict->action = svn_wc_conflict_action_replace;
- conflict->src_left_version = svn_wc_conflict_version_dup(
- existing_conflict->src_left_version,
- merge_b->pool);
-
- SVN_ERR(svn_wc__add_tree_conflict(merge_b->ctx->wc_ctx, conflict,
- merge_b->pool));
-
- alloc_and_store_path(&merge_b->conflicted_paths, victim_abspath,
- merge_b->pool);
- }
-
- /* In any other cases, we don't touch the existing conflict. */
return SVN_NO_ERROR;
}
-
/* Helper for filter_self_referential_mergeinfo()
*MERGEINFO is a non-empty, non-null collection of mergeinfo.
@@ -938,8 +677,8 @@ split_mergeinfo_on_revision(svn_mergeinf
ranges from *MERGEINFO */
if (!(*younger_mergeinfo))
*younger_mergeinfo = apr_hash_make(pool);
- apr_hash_set(*younger_mergeinfo, merge_source_path,
- APR_HASH_KEY_STRING, younger_rangelist);
+ svn_hash_sets(*younger_mergeinfo, merge_source_path,
+ younger_rangelist);
SVN_ERR(svn_mergeinfo_remove2(mergeinfo, *younger_mergeinfo,
*mergeinfo, TRUE, pool, iterpool));
break; /* ...out of for (i = 0; i < rangelist->nelts; i++) */
@@ -1209,8 +948,8 @@ filter_self_referential_mergeinfo(apr_ar
{
if (!filtered_younger_mergeinfo)
filtered_younger_mergeinfo = apr_hash_make(iterpool);
- apr_hash_set(filtered_younger_mergeinfo, source_path,
- APR_HASH_KEY_STRING, adjusted_rangelist);
+ svn_hash_sets(filtered_younger_mergeinfo, source_path,
+ adjusted_rangelist);
}
} /* Iteration over each merge source in younger_mergeinfo. */
@@ -1341,10 +1080,10 @@ prepare_merge_props_changed(const apr_ar
}
*prop_updates = props;
- /* If this is not a dry run then make a record in BATON if we find a
- PATH where mergeinfo is added where none existed previously or PATH
- is having its existing mergeinfo deleted. */
- if (!merge_b->dry_run && props->nelts)
+ /* Make a record in BATON if we find a PATH where mergeinfo is added
+ where none existed previously or PATH is having its existing
+ mergeinfo deleted. */
+ if (props->nelts)
{
int i;
@@ -1365,8 +1104,7 @@ prepare_merge_props_changed(const apr_ar
scratch_pool));
if (pristine_props
- && apr_hash_get(pristine_props, SVN_PROP_MERGEINFO,
- APR_HASH_KEY_STRING))
+ && svn_hash_gets(pristine_props, SVN_PROP_MERGEINFO))
has_pristine_mergeinfo = TRUE;
if (!has_pristine_mergeinfo && prop->value)
@@ -1386,132 +1124,286 @@ prepare_merge_props_changed(const apr_ar
return SVN_NO_ERROR;
}
-/* Indicate in *MOVED_AWAY whether the node at LOCAL_ABSPATH was
- * moved away locally. Do not raise an error if the node at LOCAL_ABSPATH
- * does not exist. */
-static svn_error_t *
-check_moved_away(svn_boolean_t *moved_away,
- svn_wc_context_t *wc_ctx,
- const char *local_abspath,
- apr_pool_t *scratch_pool)
-{
- const char *moved_to_abspath;
+#define CONFLICT_REASON_NONE ((svn_wc_conflict_reason_t)-1)
+#define CONFLICT_REASON_SKIP ((svn_wc_conflict_reason_t)-2)
+#define CONFLICT_REASON_SKIP_WC ((svn_wc_conflict_reason_t)-3)
+
+/* Baton used for testing trees for being editted while performing tree
+ conflict detection for incoming deletes */
+struct dir_delete_baton_t
+{
+ /* Reference to dir baton of directory that is the root of the deletion */
+ struct merge_dir_baton_t *del_root;
+
+ /* Boolean indicating that some edit is found. Allows avoiding more work */
+ svn_boolean_t found_edit;
+
+ /* A list of paths that are compared. Kept up to date until FOUND_EDIT is
+ set to TRUE */
+ apr_hash_t *compared_abspaths;
+};
- SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
- wc_ctx, local_abspath,
- scratch_pool, scratch_pool));
-
- *moved_away = (moved_to_abspath != NULL);
+/* Baton for the merge_dir_*() functions. Initialized in merge_dir_opened() */
+struct merge_dir_baton_t
+{
+ /* Reference to the parent baton, unless the parent is the anchor, in which
+ case PARENT_BATON is NULL */
+ struct merge_dir_baton_t *parent_baton;
- return SVN_NO_ERROR;
-}
+ /* The pool containing this baton. Use for RESULT_POOL for storing in this
+ baton */
+ apr_pool_t *pool;
-/* Indicate in *MOVED_HERE whether the node at LOCAL_ABSPATH was
- * moved here locally. Do not raise an error if the node at LOCAL_ABSPATH
- * does not exist. */
-static svn_error_t *
-check_moved_here(svn_boolean_t *moved_here,
- svn_wc_context_t *wc_ctx,
- const char *local_abspath,
- apr_pool_t *scratch_pool)
-{
- const char *moved_from_abspath;
- svn_error_t *err;
+ /* This directory doesn't have a representation in the working copy, so any
+ operation on it will be skipped and possibly cause a tree conflict on the
+ shadow root */
+ svn_boolean_t shadowed;
+
+ /* This node or one of its descendants received operational changes from the
+ merge. If this node is the shadow root its tree conflict status has been
+ applied */
+ svn_boolean_t edited;
+
+ /* If a tree conflict will be installed once edited, it's reason. If a skip
+ should be produced its reason. Otherwise CONFLICT_REASON_NONE for no tree
+ conflict.
+
+ Special values:
+ CONFLICT_REASON_SKIP:
+ The node will be skipped with content and property state as stored in
+ SKIP_REASON.
- *moved_here = FALSE;
+ CONFLICT_REASON_SKIP_WC:
+ The node will be skipped as an obstructing working copy.
+ */
+ svn_wc_conflict_reason_t tree_conflict_reason;
+ svn_wc_conflict_action_t tree_conflict_action;
- err = svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
- wc_ctx, local_abspath,
- scratch_pool, scratch_pool);
- if (err)
- {
- if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
- svn_error_clear(err);
- else
- return svn_error_trace(err);
- }
- else if (moved_from_abspath)
- *moved_here = TRUE;
+ /* When TREE_CONFLICT_REASON is CONFLICT_REASON_SKIP, the skip state to
+ add to the notification */
+ svn_wc_notify_state_t skip_reason;
+
+ /* TRUE if the node was added by this merge. Otherwise FALSE */
+ svn_boolean_t added;
+ svn_boolean_t add_is_replace; /* Add is second part of replace */
+
+ /* TRUE if we are taking over an existing directory as addition, otherwise
+ FALSE. */
+ svn_boolean_t add_existing;
+
+ /* NULL, or an hashtable mapping const char * local_abspaths to
+ const char *kind mapping, containing deleted nodes that still need a delete
+ notification (which may be a replaced notification if the node is not just
+ deleted) */
+ apr_hash_t *pending_deletes;
+
+ /* NULL, or an hashtable mapping const char * LOCAL_ABSPATHs to
+ a const svn_wc_conflict_description2_t * instance, describing the just
+ installed conflict */
+ apr_hash_t *new_tree_conflicts;
+
+ /* If not NULL, a reference to the information of the delete test that is
+ currently in progress. Allocated in the root-directory baton, referenced
+ from all descendants */
+ struct dir_delete_baton_t *delete_state;
+};
- return SVN_NO_ERROR;
-}
+/* Baton for the merge_dir_*() functions. Initialized in merge_file_opened() */
+struct merge_file_baton_t
+{
+ /* Reference to the parent baton, unless the parent is the anchor, in which
+ case PARENT_BATON is NULL */
+ struct merge_dir_baton_t *parent_baton;
+
+ /* This file doesn't have a representation in the working copy, so any
+ operation on it will be skipped and possibly cause a tree conflict
+ on the shadow root */
+ svn_boolean_t shadowed;
+
+ /* This node received operational changes from the merge. If this node
+ is the shadow root its tree conflict status has been applied */
+ svn_boolean_t edited;
+
+ /* If a tree conflict will be installed once edited, it's reason. If a skip
+ should be produced its reason. Some special values are defined. See the
+ merge_tree_baton_t for an explanation. */
+ svn_wc_conflict_reason_t tree_conflict_reason;
+ svn_wc_conflict_action_t tree_conflict_action;
+
+ /* When TREE_CONFLICT_REASON is CONFLICT_REASON_SKIP, the skip state to
+ add to the notification */
+ svn_wc_notify_state_t skip_reason;
+
+ /* TRUE if the node was added by this merge. Otherwise FALSE */
+ svn_boolean_t added;
+ svn_boolean_t add_is_replace; /* Add is second part of replace */
+};
-/* #defined HANDLE_NOTIFY_FROM_MERGE */
+/* 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 *
-record_skip(const merge_cmd_baton_t *merge_b,
+record_skip(merge_cmd_baton_t *merge_b,
const char *local_abspath,
svn_node_kind_t kind,
+ svn_wc_notify_action_t action,
svn_wc_notify_state_t state,
apr_pool_t *scratch_pool)
{
+ if (merge_b->record_only)
+ return SVN_NO_ERROR; /* ### Why? - Legacy compatibility */
+
if (merge_b->merge_source.ancestral
|| merge_b->reintegrate_merge)
{
store_path(merge_b->skipped_abspaths, local_abspath);
}
-#ifdef HANDLE_NOTIFY_FROM_MERGE
if (merge_b->ctx->notify_func2)
{
svn_wc_notify_t *notify;
- notify = svn_wc_create_notify(local_abspath, svn_wc_notify_skip,
- scratch_pool);
+ 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);
}
-#endif
return SVN_NO_ERROR;
}
-/* Record the skip for future processing and (later) produce the
- tree conflict notification */
+/* Record a tree conflict in the WC, unless this is a dry run or a record-
+ * only merge, or if a tree conflict is already flagged for the VICTIM_PATH.
+ * (The latter can happen if a merge-tracking-aware merge is doing multiple
+ * editor drives because of a gap in the range of eligible revisions.)
+ *
+ * The tree conflict, with its victim specified by VICTIM_PATH, is
+ * assumed to have happened during a merge using merge baton MERGE_B.
+ *
+ * NODE_KIND must be the node kind of "old" and "theirs" and "mine";
+ * this function cannot cope with node kind clashes.
+ * ACTION and REASON correspond to the fields
+ * of the same names in svn_wc_tree_conflict_description_t.
+ */
static svn_error_t *
-record_tree_conflict(const merge_cmd_baton_t *merge_b,
+record_tree_conflict(merge_cmd_baton_t *merge_b,
const char *local_abspath,
- svn_node_kind_t kind,
+ struct merge_dir_baton_t *parent_baton,
+ svn_node_kind_t node_kind,
+ svn_wc_conflict_action_t action,
+ svn_wc_conflict_reason_t reason,
+ const svn_wc_conflict_description2_t *existing_conflict,
+ svn_boolean_t notify_tc,
apr_pool_t *scratch_pool)
{
-#ifdef HANDLE_NOTIFY_FROM_MERGE
+ svn_wc_context_t *wc_ctx = merge_b->ctx->wc_ctx;
+
+ if (merge_b->merge_source.ancestral
+ || merge_b->reintegrate_merge)
+ {
+ store_path(merge_b->tree_conflicted_abspaths, local_abspath);
+ }
+
+ alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
+ merge_b->pool);
+
+
+ if (!merge_b->record_only && !merge_b->dry_run)
+ {
+ svn_wc_conflict_description2_t *conflict;
+ const svn_wc_conflict_version_t *left;
+ const svn_wc_conflict_version_t *right;
+ apr_pool_t *result_pool = parent_baton ? parent_baton->pool
+ : scratch_pool;
+
+ if (reason == svn_wc_conflict_reason_deleted)
+ {
+ const char *moved_to_abspath;
+
+ SVN_ERR(svn_wc__node_was_moved_away(&moved_to_abspath, NULL,
+ wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+
+ if (moved_to_abspath)
+ {
+ /* Local abspath itself has been moved away. If only a
+ descendant is moved away, we call the node itself deleted */
+ reason = svn_wc_conflict_reason_moved_away;
+ }
+ }
+ else if (reason == svn_wc_conflict_reason_added)
+ {
+ const char *moved_from_abspath;
+ SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL,
+ wc_ctx, local_abspath,
+ scratch_pool, scratch_pool));
+ if (moved_from_abspath)
+ reason = svn_wc_conflict_reason_moved_here;
+ }
+
+ SVN_ERR(make_conflict_versions(&left, &right, local_abspath, node_kind,
+ &merge_b->merge_source, merge_b->target,
+ result_pool, scratch_pool));
+
+ /* Fix up delete of file, add of dir replacement (or other way around) */
+ if (existing_conflict != NULL && existing_conflict->src_left_version)
+ left = existing_conflict->src_left_version;
+
+ conflict = svn_wc_conflict_description_create_tree2(
+ local_abspath, node_kind, svn_wc_operation_merge,
+ left, right, result_pool);
+
+ conflict->action = action;
+ conflict->reason = reason;
+
+ /* May return SVN_ERR_WC_PATH_UNEXPECTED_STATUS */
+ if (existing_conflict)
+ SVN_ERR(svn_wc__del_tree_conflict(wc_ctx, local_abspath,
+ scratch_pool));
+
+ SVN_ERR(svn_wc__add_tree_conflict(merge_b->ctx->wc_ctx, conflict,
+ scratch_pool));
+
+ if (parent_baton)
+ {
+ if (! parent_baton->new_tree_conflicts)
+ parent_baton->new_tree_conflicts = apr_hash_make(result_pool);
+
+ svn_hash_sets(parent_baton->new_tree_conflicts,
+ apr_pstrdup(result_pool, local_abspath),
+ conflict);
+ }
+
+ /* ### TODO: Store in parent baton */
+ }
+
/* On a replacement we currently get two tree conflicts */
- if (merge_b->ctx->notify_func2
- && !contains_path(merge_b->tree_conflicted_abspaths,
- local_abspath))
+ if (merge_b->ctx->notify_func2 && 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 = kind;
+ notify->kind = node_kind;
(*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
scratch_pool);
}
-#endif
-
- if (merge_b->merge_source.ancestral
- || merge_b->reintegrate_merge)
- {
- store_path(merge_b->tree_conflicted_abspaths, local_abspath);
- }
return SVN_NO_ERROR;
}
-/* Forward declaration */
-static svn_error_t *
-record_operative_merge_action(merge_cmd_baton_t *merge_b,
- const char *local_abspath,
- svn_boolean_t delete_action,
- apr_pool_t *scratch_pool);
-
-
/* Record the add for future processing and (later) produce the
update_add notification
@@ -1522,63 +1414,30 @@ static svn_error_t *
record_update_add(merge_cmd_baton_t *merge_b,
const char *local_abspath,
svn_node_kind_t kind,
+ svn_boolean_t notify_replaced,
apr_pool_t *scratch_pool)
{
- svn_boolean_t root_of_added_subtree = TRUE;
-
if (merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
{
- /* Stash the root path of any added subtrees. */
- if (merge_b->added_abspaths == NULL)
- {
- /* The first added path is always a root. */
- merge_b->added_abspaths = apr_hash_make(merge_b->pool);
- }
- else
- {
- const char *added_path_dir = svn_dirent_dirname(local_abspath,
- scratch_pool);
-
- /* Is NOTIFY->PATH the root of an added subtree? */
- while (strcmp(merge_b->target->abspath, added_path_dir))
- {
- if (contains_path(merge_b->added_abspaths, added_path_dir))
- {
- root_of_added_subtree = FALSE;
- break;
- }
-
- if (svn_dirent_is_root(added_path_dir, strlen(added_path_dir)))
- break;
- added_path_dir = svn_dirent_dirname(added_path_dir,
- scratch_pool);
- }
- }
-
- if (root_of_added_subtree)
- {
- store_path(merge_b->added_abspaths, local_abspath);
- }
-
store_path(merge_b->merged_abspaths, local_abspath);
}
- SVN_ERR(record_operative_merge_action(merge_b, local_abspath, FALSE,
- scratch_pool));
-
-#ifdef HANDLE_NOTIFY_FROM_MERGE
if (merge_b->ctx->notify_func2)
{
svn_wc_notify_t *notify;
+ svn_wc_notify_action_t action = svn_wc_notify_update_add;
- notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_add,
- scratch_pool);
+ 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);
}
-#endif
return SVN_NO_ERROR;
}
@@ -1598,14 +1457,12 @@ record_update_update(merge_cmd_baton_t *
store_path(merge_b->merged_abspaths, local_abspath);
}
- SVN_ERR(record_operative_merge_action(merge_b, local_abspath,
- FALSE, scratch_pool));
-
-#ifdef HANDLE_NOTIFY_FROM_MERGE
if (merge_b->ctx->notify_func2)
{
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;
@@ -1615,7 +1472,6 @@ record_update_update(merge_cmd_baton_t *
(*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
scratch_pool);
}
-#endif
return SVN_NO_ERROR;
}
@@ -1624,6 +1480,7 @@ record_update_update(merge_cmd_baton_t *
update_delete notification */
static svn_error_t *
record_update_delete(merge_cmd_baton_t *merge_b,
+ struct merge_dir_baton_t *parent_db,
const char *local_abspath,
svn_node_kind_t kind,
apr_pool_t *scratch_pool)
@@ -1635,416 +1492,518 @@ record_update_delete(merge_cmd_baton_t *
/* Issue #4166: If a previous merge added NOTIFY_ABSPATH, but we
are now deleting it, then remove it from the list of added
paths. */
- apr_hash_set(merge_b->added_abspaths, local_abspath,
- APR_HASH_KEY_STRING, NULL);
+ svn_hash_sets(merge_b->added_abspaths, local_abspath, NULL);
store_path(merge_b->merged_abspaths, local_abspath);
}
- SVN_ERR(record_operative_merge_action(merge_b, local_abspath,
- TRUE, scratch_pool));
+ SVN_ERR(notify_merge_begin(merge_b, local_abspath, TRUE, scratch_pool));
-#ifdef HANDLE_NOTIFY_FROM_MERGE
- if (merge_b->ctx->notify_func2)
+ if (parent_db)
{
- svn_wc_notify_t *notify;
+ const char *dup_abspath = apr_pstrdup(parent_db->pool, local_abspath);
- notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_delete,
- scratch_pool);
- notify->kind = kind;
+ if (!parent_db->pending_deletes)
+ parent_db->pending_deletes = apr_hash_make(parent_db->pool);
- (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2, notify,
- scratch_pool);
+ svn_hash_sets(parent_db->pending_deletes, dup_abspath,
+ svn_node_kind_to_word(kind));
}
-#endif
return SVN_NO_ERROR;
}
-/* An svn_wc_diff_callbacks4_t function. */
+/* Notify the pending 'D'eletes, that were waiting to see if a matching 'A'dd
+ might make them a 'R'eplace. */
static svn_error_t *
-merge_dir_props_changed(svn_wc_notify_state_t *state,
- svn_boolean_t *tree_conflicted,
- const char *local_relpath,
- svn_boolean_t dir_was_added,
- const apr_array_header_t *propchanges,
- apr_hash_t *original_props,
- void *diff_baton,
- apr_pool_t *scratch_pool)
+handle_pending_notifications(merge_cmd_baton_t *merge_b,
+ struct merge_dir_baton_t *db,
+ apr_pool_t *scratch_pool)
{
- merge_cmd_baton_t *merge_b = diff_baton;
- const apr_array_header_t *props;
- const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
- local_relpath, scratch_pool);
- svn_wc_notify_state_t obstr_state;
- svn_boolean_t is_deleted;
- svn_node_kind_t kind;
-
- SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
- merge_b, local_abspath, svn_node_dir,
- scratch_pool));
-
- if (obstr_state != svn_wc_notify_state_inapplicable)
+ if (merge_b->ctx->notify_func2 && db->pending_deletes)
{
- *state = obstr_state;
- SVN_ERR(record_skip(merge_b, local_abspath, svn_node_dir,
- obstr_state, scratch_pool));
- return SVN_NO_ERROR;
- }
-
- if (kind != svn_node_dir || is_deleted)
- {
- svn_wc_conflict_reason_t reason;
+ apr_hash_index_t *hi;
- if (is_deleted)
+ for (hi = apr_hash_first(scratch_pool, db->pending_deletes);
+ hi;
+ hi = apr_hash_next(hi))
{
- svn_boolean_t moved_away;
+ const char *del_abspath = svn__apr_hash_index_key(hi);
+ svn_wc_notify_t *notify;
- SVN_ERR(check_moved_away(&moved_away, merge_b->ctx->wc_ctx,
- local_abspath, scratch_pool));
+ notify = svn_wc_create_notify(del_abspath,
+ svn_wc_notify_update_delete,
+ scratch_pool);
+ notify->kind = svn_node_kind_from_word(
+ svn__apr_hash_index_val(hi));
- if (moved_away)
- reason = svn_wc_conflict_reason_moved_away;
- else
- reason = svn_wc_conflict_reason_deleted;
+ (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2,
+ notify, scratch_pool);
}
- else
- reason = svn_wc_conflict_reason_missing;
- SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_file,
- svn_wc_conflict_action_edit, reason));
-
- *tree_conflicted = TRUE;
- *state = svn_wc_notify_state_missing;
- SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_dir,
- scratch_pool));
-
- return SVN_NO_ERROR;
+ db->pending_deletes = NULL;
}
+ return SVN_NO_ERROR;
+}
- if (dir_was_added
- && merge_b->dry_run
- && dry_run_added_p(merge_b, local_abspath))
- {
- return SVN_NO_ERROR; /* We can't do a real prop merge for added dirs */
- }
+/* Helper function for the merge_dir_*() and merge_file_*() functions.
- if (dir_was_added && merge_b->same_repos)
+ Installs and notifies pre-recorded tree conflicts and skips for
+ ancestors of operational merges
+ */
+static svn_error_t *
+mark_dir_edited(merge_cmd_baton_t *merge_b,
+ struct merge_dir_baton_t *db,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ /* ### Too much common code with mark_file_edited */
+ if (db->edited)
+ return SVN_NO_ERROR;
+
+ if (db->parent_baton && !db->parent_baton->edited)
{
- /* When the directory was added in merge_dir_added() we didn't update its
- pristine properties. Instead we receive the property changes later and
- apply them in this function.
+ const char *dir_abspath = svn_dirent_dirname(local_abspath,
+ scratch_pool);
- If we would apply them as changes (such as before fixing issue #3405),
- we would see the unmodified properties as local changes, and the
- pristine properties would be out of sync with what the repository
- expects for this directory.
+ SVN_ERR(mark_dir_edited(merge_b, db->parent_baton, dir_abspath,
+ scratch_pool));
+ }
- Instead of doing that we now simply set the properties as the pristine
- properties via a private libsvn_wc api.
- */
+ db->edited = TRUE;
- const char *copyfrom_url;
- svn_revnum_t copyfrom_rev;
- const char *parent_abspath;
- const char *child;
+ if (! db->shadowed)
+ return SVN_NO_ERROR; /* Easy out */
- /* Creating a hash containing regular and entry props */
- apr_hash_t *new_pristine_props = svn_prop__patch(original_props, propchanges,
- scratch_pool);
+ if (db->parent_baton
+ && db->parent_baton->delete_state
+ && db->tree_conflict_reason != CONFLICT_REASON_NONE)
+ {
+ db->parent_baton->delete_state->found_edit = TRUE;
+ }
+ else if (db->tree_conflict_reason == CONFLICT_REASON_SKIP
+ || db->tree_conflict_reason == CONFLICT_REASON_SKIP_WC)
+ {
+ /* open_directory() decided not to flag a tree conflict, but
+ for clarity we produce a skip for this node that
+ most likely isn't touched by the merge itself */
- parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
- child = svn_dirent_is_child(merge_b->target->abspath, local_abspath, NULL);
- SVN_ERR_ASSERT(child != NULL);
+ if (merge_b->ctx->notify_func2)
+ {
+ svn_wc_notify_t *notify;
- copyfrom_url = svn_path_url_add_component2(merge_b->merge_source.loc2->url,
- child, scratch_pool);
- copyfrom_rev = merge_b->merge_source.loc2->rev;
+ SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE,
+ scratch_pool));
- SVN_ERR(check_repos_match(merge_b->target, parent_abspath, copyfrom_url,
- scratch_pool));
+ notify = svn_wc_create_notify(
+ local_abspath,
+ (db->tree_conflict_reason == CONFLICT_REASON_SKIP)
+ ? svn_wc_notify_skip
+ : svn_wc_notify_state_obstructed,
+ scratch_pool);
+ notify->kind = svn_node_dir;
+ notify->content_state = notify->prop_state = db->skip_reason;
- if (!merge_b->dry_run)
- {
- SVN_ERR(svn_wc__complete_directory_add(merge_b->ctx->wc_ctx,
- local_abspath,
- new_pristine_props,
- copyfrom_url, copyfrom_rev,
- scratch_pool));
+ (*merge_b->ctx->notify_func2)(merge_b->ctx->notify_baton2,
+ notify,
+ scratch_pool);
}
- *state = svn_wc_notify_state_changed; /* same as merge_props did */
- /* Until issue #3405 was fixed the code for changed directories was
- used for directory deletions, which made use apply svn:mergeinfo as if
- additional changes were applied by the merge, which really weren't.
-
- ### I wouldn't be surprised if we somehow relied on these for correct
- ### merges, but in this case the fix added here should also be applied
- ### for added files! */
-
- /* ### The old code performed (via prepare_merge_props_changed):
- if (apr_hash_get(new_pristine_props, SVN_PROP_MERGEINFO,
- APR_HASH_KEY_STRING))
+ if (merge_b->merge_source.ancestral
+ || merge_b->reintegrate_merge)
{
- alloc_and_store_path(&merge_b->paths_with_new_mergeinfo,
- local_abspath, merge_b->pool);
+ store_path(merge_b->skipped_abspaths, local_abspath);
}
- ### which is something merge_add_file() doesn't do, but
- ### merge_tests.py 95: no self referential filtering on added path
- ### fails if this block is not enabled.
- */
+ }
+ else if (db->tree_conflict_reason != CONFLICT_REASON_NONE)
+ {
+ /* open_directory() decided that a tree conflict should be raised */
- return SVN_NO_ERROR;
+ SVN_ERR(record_tree_conflict(merge_b, local_abspath, db->parent_baton,
+ svn_node_dir, db->tree_conflict_action,
+ db->tree_conflict_reason,
+ NULL, TRUE,
+ scratch_pool));
}
- SVN_ERR(prepare_merge_props_changed(&props, local_abspath, propchanges,
- merge_b, scratch_pool, scratch_pool));
+ return SVN_NO_ERROR;
+}
- if (props->nelts)
+/* Helper function for the merge_file_*() functions.
+
+ Installs and notifies pre-recorded tree conflicts and skips for
+ ancestors of operational merges
+ */
+static svn_error_t *
+mark_file_edited(merge_cmd_baton_t *merge_b,
+ struct merge_file_baton_t *fb,
+ const char *local_abspath,
+ apr_pool_t *scratch_pool)
+{
+ /* ### Too much common code with mark_dir_edited */
+ if (fb->edited)
+ return SVN_NO_ERROR;
+
+ if (fb->parent_baton && !fb->parent_baton->edited)
{
- const svn_wc_conflict_version_t *left;
- const svn_wc_conflict_version_t *right;
- svn_client_ctx_t *ctx = merge_b->ctx;
+ const char *dir_abspath = svn_dirent_dirname(local_abspath,
+ scratch_pool);
- SVN_ERR(make_conflict_versions(&left, &right, local_abspath,
- svn_node_dir, &merge_b->merge_source,
- merge_b->target, merge_b->pool));
+ SVN_ERR(mark_dir_edited(merge_b, fb->parent_baton, dir_abspath,
+ scratch_pool));
+ }
- SVN_ERR(svn_wc_merge_props3(state, ctx->wc_ctx, local_abspath,
- left, right,
- original_props, props,
- merge_b->dry_run,
- ctx->conflict_func2, ctx->conflict_baton2,
- ctx->cancel_func, ctx->cancel_baton,
- scratch_pool));
+ fb->edited = TRUE;
+
+ if (! fb->shadowed)
+ return SVN_NO_ERROR; /* Easy out */
+
+ if (fb->parent_baton
+ && fb->parent_baton->delete_state
+ && fb->tree_conflict_reason != CONFLICT_REASON_NONE)
+ {
+ fb->parent_baton->delete_state->found_edit = TRUE;
+ }
+ else if (fb->tree_conflict_reason == CONFLICT_REASON_SKIP
+ || fb->tree_conflict_reason == CONFLICT_REASON_SKIP_WC)
+ {
+ /* open_directory() decided not to flag a tree conflict, but
+ for clarity we produce a skip for this node that
+ most likely isn't touched by the merge itself */
- if (*state == svn_wc_notify_state_conflicted
- || *state == svn_wc_notify_state_merged
- || *state == svn_wc_notify_state_changed)
+ if (merge_b->ctx->notify_func2)
{
- SVN_ERR(record_update_update(merge_b, local_abspath, svn_node_file,
- svn_wc_notify_state_inapplicable,
- *state, scratch_pool));
+ 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);
+ }
+
+ if (merge_b->merge_source.ancestral
+ || merge_b->reintegrate_merge)
+ {
+ store_path(merge_b->skipped_abspaths, local_abspath);
}
}
- else
- *state = svn_wc_notify_state_unchanged;
+ else if (fb->tree_conflict_reason != CONFLICT_REASON_NONE)
+ {
+ /* open_file() decided that a tree conflict should be raised */
+
+ SVN_ERR(record_tree_conflict(merge_b, local_abspath, fb->parent_baton,
+ svn_node_file, fb->tree_conflict_action,
+ fb->tree_conflict_reason,
+ NULL, TRUE,
+ scratch_pool));
+ }
return SVN_NO_ERROR;
}
-/* Contains any state collected while resolving conflicts. */
-typedef struct conflict_resolver_baton_t
-{
- /* The wrapped callback and baton. If func is null, postpone conflicts. */
- svn_wc_conflict_resolver_func2_t wrapped_func;
- void *wrapped_baton;
-
- /* The list of any paths which remained in conflict after a
- resolution attempt was made. Non-null ptr to possibly-null ptr to hash. */
- apr_hash_t **conflicted_paths;
-
- /* Pool with a sufficient lifetime to be used for output members such as
- * *CONFLICTED_PATHS. */
- apr_pool_t *pool;
-} conflict_resolver_baton_t;
-
-/* An implementation of the svn_wc_conflict_resolver_func_t interface.
+/* An svn_diff_tree_processor_t function.
- This is called by the WC layer when a file merge conflicts. We call
- the wrapped conflict callback, BATON->wrapped_func, or, if that is
- null, we postpone the conflict.
+ Called before either merge_file_changed(), merge_file_added(),
+ merge_file_deleted() or merge_file_closed(), unless it sets *SKIP to TRUE.
- We keep a record of paths which remain in conflict.
-*/
+ When *SKIP is TRUE, the diff driver avoids work on getting the details
+ for the closing callbacks.
+ */
static svn_error_t *
-conflict_resolver(svn_wc_conflict_result_t **result,
- const svn_wc_conflict_description2_t *description,
- void *baton,
+merge_file_opened(void **new_file_baton,
+ svn_boolean_t *skip,
+ const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const svn_diff_source_t *copyfrom_source,
+ void *dir_baton,
+ const struct svn_diff_tree_processor_t *processor,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- svn_error_t *err;
- conflict_resolver_baton_t *conflict_b = baton;
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_dir_baton_t *pdb = dir_baton;
+ struct merge_file_baton_t *fb;
+ const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
+ relpath, scratch_pool);
- if (conflict_b->wrapped_func)
- err = (*conflict_b->wrapped_func)(result, description,
- conflict_b->wrapped_baton,
- result_pool,
- scratch_pool);
- else
+ fb = apr_pcalloc(result_pool, sizeof(*fb));
+ fb->tree_conflict_reason = CONFLICT_REASON_NONE;
+ fb->tree_conflict_action = svn_wc_conflict_action_edit;
+ fb->skip_reason = svn_wc_notify_state_unknown;
+
+ *new_file_baton = fb;
+
+ if (pdb)
{
- /* If we have no wrapped callback to invoke, then we still need
- to behave like a proper conflict-callback ourselves. */
- *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
- NULL, result_pool);
- err = SVN_NO_ERROR;
+ fb->parent_baton = pdb;
+ fb->shadowed = pdb->shadowed;
+ fb->skip_reason = pdb->skip_reason;
}
- /* Keep a record of paths still in conflict after the resolution attempt. */
- if ((*result)->choice == svn_wc_conflict_choose_postpone)
+ if (fb->shadowed)
+ {
+ /* An ancestor is tree conflicted. Nothing to do here. */
+ }
+ else if (left_source != NULL)
{
- alloc_and_store_path(conflict_b->conflicted_paths,
- description->local_abspath, conflict_b->pool);
+ /* Node is expected to be a file, which will be changed or deleted. */
+ svn_node_kind_t kind;
+ svn_boolean_t is_deleted;
+ svn_boolean_t excluded;
+ svn_depth_t parent_depth;
+
+ if (! right_source)
+ fb->tree_conflict_action = svn_wc_conflict_action_delete;
+
+ {
+ svn_wc_notify_state_t obstr_state;
+
+ SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &excluded,
+ &kind, &parent_depth,
+ merge_b, local_abspath,
+ scratch_pool));
+
+ if (obstr_state != svn_wc_notify_state_inapplicable)
+ {
+ fb->shadowed = TRUE;
+ fb->tree_conflict_reason = CONFLICT_REASON_SKIP;
+ fb->skip_reason = obstr_state;
+ return SVN_NO_ERROR;
+ }
+
+ if (is_deleted)
+ kind = svn_node_none;
+ }
+
+ if (kind == svn_node_none)
+ {
+ fb->shadowed = TRUE;
+
+ /* If this is not the merge target and the parent is too shallow to
+ contain this directory, and the directory is not present
+ via exclusion or depth filtering, skip it instead of recording
+ a tree conflict.
+
+ Non-inheritable mergeinfo will be recorded, allowing
+ future merges into non-shallow working copies to merge
+ changes we missed this time around. */
+ if (pdb && (excluded
+ || (parent_depth != svn_depth_unknown &&
+ parent_depth < svn_depth_files)))
+ {
+ fb->shadowed = TRUE;
+
+ fb->tree_conflict_reason = CONFLICT_REASON_SKIP;
+ fb->skip_reason = svn_wc_notify_state_missing;
+ return SVN_NO_ERROR;
+ }
+
+ if (is_deleted)
+ fb->tree_conflict_reason = svn_wc_conflict_reason_deleted;
+ else
+ fb->tree_conflict_reason = svn_wc_conflict_reason_missing;
+
+ /* ### Similar to directory */
+ *skip = TRUE;
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+ /* ### /Similar */
+ }
+ else if (kind != svn_node_file)
+ {
+ fb->shadowed = TRUE;
+
+ fb->tree_conflict_reason = svn_wc_conflict_reason_obstructed;
+
+ /* ### Similar to directory */
+ *skip = TRUE;
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+ return SVN_NO_ERROR;
+ /* ### /Similar */
+ }
+
+ if (! right_source)
+ {
+ /* We want to delete the directory */
+ fb->tree_conflict_action = svn_wc_conflict_action_delete;
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+
+ if (fb->shadowed)
+ {
+ return SVN_NO_ERROR; /* Already set a tree conflict */
+ }
+
+ /* Comparison mode to verify for delete tree conflicts? */
+ if (pdb && pdb->delete_state
+ && pdb->delete_state->found_edit)
+ {
+ /* Earlier nodes found a conflict. Done. */
+ *skip = TRUE;
+ }
+ }
}
+ else
+ {
+ const svn_wc_conflict_description2_t *old_tc = NULL;
- return svn_error_trace(err);
-}
+ /* The node doesn't exist pre-merge: We have an addition */
+ fb->added = TRUE;
+ fb->tree_conflict_action = svn_wc_conflict_action_add;
+ if (pdb && pdb->pending_deletes
+ && svn_hash_gets(pdb->pending_deletes, local_abspath))
+ {
+ fb->add_is_replace = TRUE;
+ fb->tree_conflict_action = svn_wc_conflict_action_replace;
+
+ svn_hash_sets(pdb->pending_deletes, local_abspath, NULL);
+ }
+
+ if (pdb
+ && pdb->new_tree_conflicts
+ && (old_tc = svn_hash_gets(pdb->new_tree_conflicts, local_abspath)))
+ {
+ fb->tree_conflict_action = svn_wc_conflict_action_replace;
+ fb->tree_conflict_reason = old_tc->reason;
+
+ /* Update the tree conflict to store that this is a replace */
+ SVN_ERR(record_tree_conflict(merge_b, local_abspath, pdb,
+ svn_node_file,
+ fb->tree_conflict_action,
+ fb->tree_conflict_reason,
+ old_tc, FALSE,
+ scratch_pool));
+
+ if (old_tc->reason == svn_wc_conflict_reason_deleted
+ || old_tc->reason == svn_wc_conflict_reason_moved_away)
+ {
+ /* Issue #3806: Incoming replacements on local deletes produce
+ inconsistent result.
+
+ In this specific case we can continue applying the add part
+ of the replacement. */
+ }
+ else
+ {
+ *skip = TRUE;
+
+ return SVN_NO_ERROR;
+ }
+ }
+ else if (! (merge_b->dry_run
+ && ((pdb && pdb->added) || fb->add_is_replace)))
+ {
+ svn_wc_notify_state_t obstr_state;
+ svn_node_kind_t kind;
+ svn_boolean_t is_deleted;
+
+ SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, NULL,
+ &kind, NULL,
+ merge_b, local_abspath,
+ scratch_pool));
+
+ if (obstr_state != svn_wc_notify_state_inapplicable)
+ {
+ /* Skip the obstruction */
+ fb->shadowed = TRUE;
+ fb->tree_conflict_reason = CONFLICT_REASON_SKIP;
+ fb->skip_reason = obstr_state;
+ }
+ else if (kind != svn_node_none && !is_deleted)
+ {
+ /* Set a tree conflict */
+ fb->shadowed = TRUE;
+ fb->tree_conflict_reason = svn_wc_conflict_reason_obstructed;
+ }
+ }
+
+ /* Handle pending conflicts */
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+ }
-/* An svn_wc_diff_callbacks4_t function. */
-static svn_error_t *
-merge_file_opened(svn_boolean_t *tree_conflicted,
- svn_boolean_t *skip,
- const char *path,
- svn_revnum_t rev,
- void *diff_baton,
- apr_pool_t *scratch_pool)
-{
return SVN_NO_ERROR;
}
-/* An svn_wc_diff_callbacks4_t function. */
+/* An svn_diff_tree_processor_t function.
+ *
+ * Called after merge_file_opened() when a node receives only text and/or
+ * property changes between LEFT_SOURCE and RIGHT_SOURCE.
+ *
+ * left_file and right_file can be NULL when the file is not modified.
+ * left_props and right_props are always available.
+ */
static svn_error_t *
-merge_file_changed(svn_wc_notify_state_t *content_state,
- svn_wc_notify_state_t *prop_state,
- svn_boolean_t *tree_conflicted,
- const char *mine_relpath,
- const char *older_abspath,
- const char *yours_abspath,
- svn_revnum_t older_rev,
- svn_revnum_t yours_rev,
- const char *mimetype1,
- const char *mimetype2,
- const apr_array_header_t *prop_changes,
- apr_hash_t *original_props,
- void *baton,
- apr_pool_t *scratch_pool)
+merge_file_changed(const char *relpath,
+ const svn_diff_source_t *left_source,
+ const svn_diff_source_t *right_source,
+ const char *left_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *left_props,
+ /*const*/ apr_hash_t *right_props,
+ svn_boolean_t file_modified,
+ const apr_array_header_t *prop_changes,
+ void *file_baton,
+ const struct svn_diff_tree_processor_t *processor,
+ apr_pool_t *scratch_pool)
{
- merge_cmd_baton_t *merge_b = baton;
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_file_baton_t *fb = file_baton;
svn_client_ctx_t *ctx = merge_b->ctx;
const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
- mine_relpath, scratch_pool);
- svn_node_kind_t wc_kind;
- svn_boolean_t is_deleted;
+ relpath, scratch_pool);
const svn_wc_conflict_version_t *left;
const svn_wc_conflict_version_t *right;
+ svn_wc_notify_state_t text_state;
+ svn_wc_notify_state_t property_state;
SVN_ERR_ASSERT(local_abspath && svn_dirent_is_absolute(local_abspath));
- SVN_ERR_ASSERT(!older_abspath || svn_dirent_is_absolute(older_abspath));
- SVN_ERR_ASSERT(!yours_abspath || svn_dirent_is_absolute(yours_abspath));
-
- /* Check for an obstructed or missing node on disk. */
- {
- svn_wc_notify_state_t obstr_state;
-
- SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &wc_kind,
- merge_b, local_abspath, svn_node_unknown,
- scratch_pool));
- if (obstr_state != svn_wc_notify_state_inapplicable)
- {
- /* ### a future thought: if the file is under version control,
- * but the working file is missing, maybe we can 'restore' the
- * working file from the text-base, and then allow the merge to run? */
-
- *content_state = obstr_state;
- if (obstr_state == svn_wc_notify_state_missing)
- *prop_state = svn_wc_notify_state_missing;
- SVN_ERR(record_skip(merge_b, local_abspath, svn_node_file,
- obstr_state, scratch_pool));
- return SVN_NO_ERROR;
- }
- }
+ SVN_ERR_ASSERT(!left_file || svn_dirent_is_absolute(left_file));
+ SVN_ERR_ASSERT(!right_file || svn_dirent_is_absolute(right_file));
- /* Other easy outs: if the merge target isn't under version
- control, or is just missing from disk, fogettaboutit. There's no
- way svn_wc_merge5() can do the merge. */
- if (wc_kind != svn_node_file || is_deleted)
- {
- svn_boolean_t moved_away;
- svn_wc_conflict_reason_t reason;
-
- /* Maybe the node is excluded via depth filtering? */
-
- if (wc_kind == svn_node_none)
- {
- svn_depth_t parent_depth;
-
- /* If the file isn't there due to depth restrictions, do not flag
- * a conflict. Non-inheritable mergeinfo will be recorded, allowing
- * future merges into non-shallow working copies to merge changes
- * we missed this time around. */
- SVN_ERR(svn_wc__node_get_depth(&parent_depth, ctx->wc_ctx,
- svn_dirent_dirname(local_abspath,
- scratch_pool),
- scratch_pool));
- if (parent_depth < svn_depth_files
- && parent_depth != svn_depth_unknown)
- {
- *content_state = svn_wc_notify_state_missing;
- *prop_state = svn_wc_notify_state_missing;
- SVN_ERR(record_skip(merge_b, local_abspath, svn_node_file,
- svn_wc_notify_state_missing, scratch_pool));
- return SVN_NO_ERROR;
- }
- }
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
- /* This is use case 4 described in the paper attached to issue
- * #2282. See also notes/tree-conflicts/detection.txt
- */
- if (is_deleted)
+ if (fb->shadowed)
+ {
+ if (fb->tree_conflict_reason == CONFLICT_REASON_NONE)
{
- SVN_ERR(check_moved_away(&moved_away, ctx->wc_ctx,
- local_abspath, scratch_pool));
- if (moved_away)
- reason = svn_wc_conflict_reason_moved_away;
- else
- reason = svn_wc_conflict_reason_deleted;
+ /* We haven't notified for this node yet: report a skip */
+ SVN_ERR(record_skip(merge_b, local_abspath, svn_node_file,
+ svn_wc_notify_update_shadowed_update,
+ fb->skip_reason, scratch_pool));
}
- else
- reason = svn_wc_conflict_reason_missing;
- SVN_ERR(tree_conflict(merge_b, local_abspath, svn_node_file,
- svn_wc_conflict_action_edit, reason));
- *tree_conflicted = TRUE;
- *content_state = svn_wc_notify_state_missing;
- *prop_state = svn_wc_notify_state_missing;
- SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_file,
- scratch_pool));
- return SVN_NO_ERROR;
- }
- /* ### TODO: Thwart attempts to merge into a path that has
- ### unresolved conflicts. This needs to be smart enough to deal
- ### with tree conflicts!
- if (is_path_conflicted_by_merge(merge_b, mine))
- {
- *content_state = svn_wc_notify_state_conflicted;
- return svn_error_createf(SVN_ERR_WC_FOUND_CONFLICT, NULL,
- _("Path '%s' is in conflict, and must be "
- "resolved before the remainder of the "
- "requested merge can be applied"), mine);
+ return SVN_NO_ERROR;
}
- */
/* This callback is essentially no more than a wrapper around
svn_wc_merge5(). Thank goodness that all the
diff-editor-mechanisms are doing the hard work of getting the
fulltexts! */
- *prop_state = svn_wc_notify_state_unchanged;
+ property_state = svn_wc_notify_state_unchanged;
+ text_state = svn_wc_notify_state_unchanged;
SVN_ERR(prepare_merge_props_changed(&prop_changes, local_abspath,
prop_changes, merge_b,
scratch_pool, scratch_pool));
SVN_ERR(make_conflict_versions(&left, &right, local_abspath,
- svn_node_file, &merge_b->merge_source, merge_b->target, merge_b->pool));
+ svn_node_file, &merge_b->merge_source, merge_b->target,
+ scratch_pool, scratch_pool));
/* Do property merge now, if we are not going to perform a text merge */
- if ((merge_b->record_only || !older_abspath) && prop_changes->nelts)
+ if ((merge_b->record_only || !left_file) && prop_changes->nelts)
{
- SVN_ERR(svn_wc_merge_props3(prop_state, ctx->wc_ctx, local_abspath,
+ SVN_ERR(svn_wc_merge_props3(&property_state, ctx->wc_ctx, local_abspath,
left, right,
- original_props, prop_changes,
+ left_props, prop_changes,
merge_b->dry_run,
ctx->conflict_func2, ctx->conflict_baton2,
ctx->cancel_func, ctx->cancel_baton,
@@ -2054,11 +2013,9 @@ merge_file_changed(svn_wc_notify_state_t
/* Easy out: We are only applying mergeinfo differences. */
if (merge_b->record_only)
{
- *content_state = svn_wc_notify_state_unchanged;
- return SVN_NO_ERROR;
+ /* NO-OP */
}
-
- if (older_abspath)
+ else if (left_file)
{
svn_boolean_t has_local_mods;
enum svn_wc_merge_outcome_t content_outcome;
@@ -2069,206 +2026,125 @@ merge_file_changed(svn_wc_notify_state_t
const char *target_label = _(".working");
const char *left_label = apr_psprintf(scratch_pool,
_(".merge-left.r%ld"),
- older_rev);
+ left_source->revision);
const char *right_label = apr_psprintf(scratch_pool,
_(".merge-right.r%ld"),
- yours_rev);
- conflict_resolver_baton_t conflict_baton = { 0 };
+ right_source->revision);
SVN_ERR(svn_wc_text_modified_p2(&has_local_mods, ctx->wc_ctx,
local_abspath, FALSE, scratch_pool));
- /* Wrap the resolver so as to keep a note of all postponed conflicts. */
- conflict_baton.wrapped_func = ctx->conflict_func2;
- conflict_baton.wrapped_baton = ctx->conflict_baton2;
-
- conflict_baton.conflicted_paths = &merge_b->conflicted_paths;
- conflict_baton.pool = merge_b->pool;
-
/* Do property merge and text merge in one step so that keyword expansion
takes into account the new property values. */
- SVN_ERR(svn_wc_merge5(&content_outcome, prop_state, ctx->wc_ctx,
- older_abspath, yours_abspath, local_abspath,
+ SVN_ERR(svn_wc_merge5(&content_outcome, &property_state, ctx->wc_ctx,
+ left_file, right_file, local_abspath,
left_label, right_label, target_label,
left, right,
merge_b->dry_run, merge_b->diff3_cmd,
merge_b->merge_options,
- original_props, prop_changes,
- conflict_resolver, &conflict_baton,
+ left_props, prop_changes,
+ ctx->conflict_func2, ctx->conflict_baton2,
ctx->cancel_func,
ctx->cancel_baton,
scratch_pool));
+ if (content_outcome == svn_wc_merge_conflict
+ || property_state == svn_wc_notify_state_conflicted)
+ {
+ alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
+ merge_b->pool);
+ }
+
if (content_outcome == svn_wc_merge_conflict)
- *content_state = svn_wc_notify_state_conflicted;
+ text_state = svn_wc_notify_state_conflicted;
else if (has_local_mods
&& content_outcome != svn_wc_merge_unchanged)
- *content_state = svn_wc_notify_state_merged;
+ text_state = svn_wc_notify_state_merged;
else if (content_outcome == svn_wc_merge_merged)
- *content_state = svn_wc_notify_state_changed;
+ text_state = svn_wc_notify_state_changed;
else if (content_outcome == svn_wc_merge_no_merge)
- *content_state = svn_wc_notify_state_missing;
+ text_state = svn_wc_notify_state_missing;
else /* merge_outcome == svn_wc_merge_unchanged */
- *content_state = svn_wc_notify_state_unchanged;
+ text_state = svn_wc_notify_state_unchanged;
}
- if (*content_state == svn_wc_notify_state_conflicted
- || *content_state == svn_wc_notify_state_merged
- || *content_state == svn_wc_notify_state_changed
- || *prop_state == svn_wc_notify_state_conflicted
- || *prop_state == svn_wc_notify_state_merged
- || *prop_state == svn_wc_notify_state_changed)
+ if (text_state == svn_wc_notify_state_conflicted
+ || text_state == svn_wc_notify_state_merged
+ || text_state == svn_wc_notify_state_changed
+ || property_state == svn_wc_notify_state_conflicted
+ || property_state == svn_wc_notify_state_merged
+ || property_state == svn_wc_notify_state_changed)
{
SVN_ERR(record_update_update(merge_b, local_abspath, svn_node_file,
- *content_state, *prop_state,
+ text_state, property_state,
scratch_pool));
}
return SVN_NO_ERROR;
}
-/* An svn_wc_diff_callbacks4_t function. */
+/* An svn_diff_tree_processor_t function.
+ *
+ * Called after merge_file_opened() when a node doesn't exist in LEFT_SOURCE,
+ * but does in RIGHT_SOURCE.
+ *
+ * When a node is replaced instead of just added a separate opened+deleted will
+ * be invoked before the current open+added.
+ */
static svn_error_t *
-merge_file_added(svn_wc_notify_state_t *content_state,
- svn_wc_notify_state_t *prop_state,
- svn_boolean_t *tree_conflicted,
- const char *mine_relpath,
- const char *older_abspath,
- const char *yours_abspath,
- svn_revnum_t rev1,
- svn_revnum_t rev2,
- const char *mimetype1,
- const char *mimetype2,
- const char *copyfrom_path,
- svn_revnum_t copyfrom_revision,
- const apr_array_header_t *prop_changes,
- apr_hash_t *original_props,
- void *baton,
+merge_file_added(const char *relpath,
+ const svn_diff_source_t *copyfrom_source,
+ const svn_diff_source_t *right_source,
+ const char *copyfrom_file,
+ const char *right_file,
+ /*const*/ apr_hash_t *copyfrom_props,
+ /*const*/ apr_hash_t *right_props,
+ void *file_baton,
+ const struct svn_diff_tree_processor_t *processor,
apr_pool_t *scratch_pool)
{
- merge_cmd_baton_t *merge_b = baton;
+ merge_cmd_baton_t *merge_b = processor->baton;
+ struct merge_file_baton_t *fb = file_baton;
const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
- mine_relpath, scratch_pool);
- svn_node_kind_t kind;
- svn_boolean_t is_deleted;
- int i;
- apr_hash_t *file_props;
+ relpath, scratch_pool);
+ apr_hash_t *pristine_props;
+ apr_hash_t *new_props;
SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
- /* Easy out: We are only applying mergeinfo differences. */
- if (merge_b->record_only)
+ SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
+
+ if (fb->shadowed)
{
- *content_state = svn_wc_notify_state_unchanged;
- *prop_state = svn_wc_notify_state_unchanged;
+ if (fb->tree_conflict_reason == CONFLICT_REASON_NONE)
+ {
+ /* We haven't notified for this node yet: report a skip */
+ SVN_ERR(record_skip(merge_b, local_abspath, svn_node_file,
+ svn_wc_notify_update_shadowed_add,
+ fb->skip_reason, scratch_pool));
+ }
+
return SVN_NO_ERROR;
}
- /* In most cases, we just leave prop_state as unknown, and let the
- content_state what happened, so we set prop_state here to avoid that
- below. */
- *prop_state = svn_wc_notify_state_unknown;
-
- /* Apply the prop changes to a new hash table. */
- file_props = apr_hash_copy(scratch_pool, original_props);
- for (i = 0; i < prop_changes->nelts; ++i)
- {
- const svn_prop_t *prop = &APR_ARRAY_IDX(prop_changes, i, svn_prop_t);
-
- /* We don't want any DAV wcprops related to this file because
- they'll point to the wrong repository (in the
- merge-from-foreign-repository scenario) or wrong place in the
- right repository (in the same-repos scenario). So we'll
- strip them. (Is this a layering violation?) */
- if (svn_property_kind2(prop->name) == svn_prop_wc_kind)
- continue;
-
- /* And in the foreign repository merge case, we only want
- regular properties. */
- if ((! merge_b->same_repos)
- && (svn_property_kind2(prop->name) != svn_prop_regular_kind))
- continue;
-
- /* Issue #3383: We don't want mergeinfo from a foreign repository. */
- if ((! merge_b->same_repos)
- && strcmp(prop->name, SVN_PROP_MERGEINFO) == 0)
- continue;
-
- apr_hash_set(file_props, prop->name, APR_HASH_KEY_STRING, prop->value);
+ /* Easy out: We are only applying mergeinfo differences. */
+ if (merge_b->record_only)
+ {
+ return SVN_NO_ERROR;
}
- /* Check for an obstructed or missing node on disk. */
- {
- svn_wc_notify_state_t obstr_state;
-
- SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, &kind,
- merge_b, local_abspath, svn_node_unknown,
- scratch_pool));
-
- if (obstr_state != svn_wc_notify_state_inapplicable)
- {
- *content_state = obstr_state;
-
- SVN_ERR(record_skip(merge_b, local_abspath, svn_node_file,
- obstr_state, scratch_pool));
-
- return SVN_NO_ERROR;
- }
-
- if (is_deleted)
- kind = svn_node_none;
- }
-
- if (kind != svn_node_none)
+ if ((merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
+ && ( !fb->parent_baton || !fb->parent_baton->added))
{
- /* The file add the merge wants to carry out is obstructed, so the
- * file the merge wants to add is a tree conflict victim.
- * See notes about obstructions in notes/tree-conflicts/detection.txt.
- */
- SVN_ERR(tree_conflict_on_add(merge_b, local_abspath, svn_node_file,
- svn_wc_conflict_action_add,
- svn_wc_conflict_reason_obstructed));
- *tree_conflicted = TRUE;
-
- /* directory already exists, is it under version control? */
- if ((kind != svn_node_none)
- && dry_run_deleted_p(merge_b, local_abspath))
- *content_state = svn_wc_notify_state_changed;
- else
- /* this will make the repos_editor send a 'skipped' message */
- *content_state = svn_wc_notify_state_obstructed;
-
- SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_file,
- scratch_pool));
-
- return SVN_NO_ERROR;
+ /* Store the roots of added subtrees */
+ store_path(merge_b->added_abspaths, local_abspath);
}
if (!merge_b->dry_run)
{
const char *copyfrom_url;
svn_revnum_t copyfrom_rev;
- svn_stream_t *new_contents, *new_base_contents;
- apr_hash_t *new_base_props, *new_props;
- svn_boolean_t existing_tree_conflict;
- svn_error_t *err;
- svn_node_kind_t parent_kind;
-
-
- /* Does the parent exist on disk (vs missing). If no we should
- report an obstruction. Or svn_wc_add_repos_file4() will just
- do its work and the workqueue will create the missing dirs */
- SVN_ERR(svn_io_check_path(
- svn_dirent_dirname(local_abspath, scratch_pool),
- &parent_kind, scratch_pool));
-
- if (parent_kind != svn_node_dir)
- {
- *content_state = svn_wc_notify_state_obstructed;
- SVN_ERR(record_skip(merge_b, local_abspath, svn_node_file,
- svn_wc_notify_state_obstructed, scratch_pool));
- return SVN_NO_ERROR;
- }
+ svn_stream_t *new_contents, *pristine_contents;
/* If this is a merge from the same repository as our
working copy, we handle adds as add-with-history.
@@ -2282,92 +2158,66 @@ merge_file_added(svn_wc_notify_state_t *
copyfrom_url = svn_path_url_add_component2(
merge_b->merge_source.loc2->url,
child, scratch_pool);
- copyfrom_rev = rev2;
+ copyfrom_rev = right_source->revision;
SVN_ERR(check_repos_match(merge_b->target, local_abspath,
copyfrom_url, scratch_pool));
- new_base_props = file_props;
- new_props = NULL; /* inherit from new_base_props */
- SVN_ERR(svn_stream_open_readonly(&new_base_contents,
- yours_abspath,
+ SVN_ERR(svn_stream_open_readonly(&pristine_contents,
+ right_file,
scratch_pool,
scratch_pool));
new_contents = NULL; /* inherit from new_base_contents */
+
+ pristine_props = right_props; /* Includes last_* information */
+ new_props = NULL; /* No local changes */
+
+ if (svn_hash_gets(pristine_props, SVN_PROP_MERGEINFO))
+ {
+ alloc_and_store_path(&merge_b->paths_with_new_mergeinfo,
+ local_abspath, merge_b->pool);
+ }
}
else
{
+ apr_array_header_t *regular_props;
+
copyfrom_url = NULL;
copyfrom_rev = SVN_INVALID_REVNUM;
- new_base_props = apr_hash_make(scratch_pool);
- new_props = file_props;
- new_base_contents = svn_stream_empty(scratch_pool);
- SVN_ERR(svn_stream_open_readonly(&new_contents, yours_abspath,
+
+ pristine_contents = svn_stream_empty(scratch_pool);
+ SVN_ERR(svn_stream_open_readonly(&new_contents, right_file,
scratch_pool, scratch_pool));
- }
- err = svn_wc_conflicted_p3(NULL, NULL, &existing_tree_conflict,
- merge_b->ctx->wc_ctx, local_abspath,
- merge_b->pool);
+ pristine_props = apr_hash_make(scratch_pool); /* Local addition */
- if (err)
- {
- if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
- return svn_error_trace(err);
+ /* We don't want any foreign properties */
+ SVN_ERR(svn_categorize_props(svn_prop_hash_to_array(right_props,
+ scratch_pool),
+ NULL, NULL, ®ular_props,
+ scratch_pool));
- svn_error_clear(err);
- existing_tree_conflict = FALSE;
+ new_props = svn_prop_array_to_hash(regular_props, scratch_pool);
+
+ /* Issue #3383: We don't want mergeinfo from a foreign repository. */
+ svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
}
- if (existing_tree_conflict)
- {
- svn_boolean_t moved_here;
- svn_wc_conflict_reason_t reason;
+ /* Do everything like if we had called 'svn cp PATH1 PATH2'. */
+ SVN_ERR(svn_wc_add_repos_file4(merge_b->ctx->wc_ctx,
+ local_abspath,
+ pristine_contents,
+ new_contents,
+ pristine_props, new_props,
+ copyfrom_url, copyfrom_rev,
+ merge_b->ctx->cancel_func,
+ merge_b->ctx->cancel_baton,
+ scratch_pool));
- /* Possibly collapse the existing conflict into a 'replace'
- * tree conflict. The conflict reason is 'added' because
- * the now-deleted tree conflict victim must have been
- * added in the history of the merge target. */
- SVN_ERR(check_moved_here(&moved_here, merge_b->ctx->wc_ctx,
- local_abspath, scratch_pool));
- reason = moved_here ? svn_wc_conflict_reason_moved_here
- : svn_wc_conflict_reason_added;
- SVN_ERR(tree_conflict_on_add(merge_b, local_abspath,
- svn_node_file,
- svn_wc_conflict_action_add,
- reason));
- *tree_conflicted = TRUE;
- SVN_ERR(record_tree_conflict(merge_b, local_abspath, svn_node_file,
- scratch_pool));
- }
- else
- {
- /* Since 'mine' doesn't exist, and this is
- 'merge_file_added', I hope it's safe to assume that
- 'older' is empty, and 'yours' is the full file. Merely
- copying 'yours' to 'mine', isn't enough; we need to get
- the whole text-base and props installed too, just as if
- we had called 'svn cp wc wc'. */
- SVN_ERR(svn_wc_add_repos_file4(merge_b->ctx->wc_ctx,
- local_abspath,
- new_base_contents,
- new_contents,
- new_base_props, new_props,
- copyfrom_url, copyfrom_rev,
- merge_b->ctx->cancel_func,
- merge_b->ctx->cancel_baton,
- scratch_pool));
- }
- }
- else /* dry_run */
- {
- /* ### Should we do this?
- store_path(merge_b->dry_run_added, local_abspath); */
+ /* Caller must call svn_sleep_for_timestamps() */
+ *merge_b->use_sleep = TRUE;
}
SVN_ERR(record_update_add(merge_b, local_abspath, svn_node_file,
- scratch_pool));
-
- *content_state = svn_wc_notify_state_changed;
- *prop_state = svn_wc_notify_state_changed;
+ fb->add_is_replace, scratch_pool));
return SVN_NO_ERROR;
}
@@ -2450,566 +2300,961 @@ files_same_p(svn_boolean_t *same,
return SVN_NO_ERROR;
}
-/* An svn_wc_diff_callbacks4_t function. */
+/* An svn_diff_tree_processor_t function.
+ *
+ * Called after merge_file_opened() when a node does exist in LEFT_SOURCE, but
+ * no longer exists (or is replaced) in RIGHT_SOURCE.
+ *
+ * When a node is replaced instead of just added a separate opened+added will
+ * be invoked after the current open+deleted.
+ */
static svn_error_t *
-merge_file_deleted(svn_wc_notify_state_t *state,
[... 3754 lines stripped ...]