You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ri...@apache.org on 2024/11/24 14:10:54 UTC
svn commit: r1922048 [2/3] - in /subversion/branches/apply-processor/subversion/libsvn_client: client.h merge.c merge_processor.c
Modified: subversion/branches/apply-processor/subversion/libsvn_client/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/apply-processor/subversion/libsvn_client/merge.c?rev=1922048&r1=1922047&r2=1922048&view=diff
==============================================================================
--- subversion/branches/apply-processor/subversion/libsvn_client/merge.c (original)
+++ subversion/branches/apply-processor/subversion/libsvn_client/merge.c Sun Nov 24 14:10:54 2024
@@ -232,7 +232,6 @@ struct notify_begin_state_t
};
typedef struct merge_cmd_baton_t {
- svn_boolean_t force_delete; /* Delete a file/dir even if modified */
svn_boolean_t dry_run;
svn_boolean_t record_only; /* Whether to merge only mergeinfo
differences. */
@@ -311,16 +310,6 @@ typedef struct merge_cmd_baton_t {
/* A list of tree conflict victim absolute paths which may be NULL. */
apr_hash_t *tree_conflicted_abspaths;
- /* The diff3_cmd in ctx->config, if any, else null. We could just
- extract this as needed, but since more than one caller uses it,
- we just set it up when this baton is created. */
- const char *diff3_cmd;
- const apr_array_header_t *merge_options;
-
- /* Array of file extension patterns to preserve as extensions in
- generated conflict files. */
- const apr_array_header_t *ext_patterns;
-
/* RA sessions used throughout a merge operation. Opened/re-parented
as needed.
@@ -456,24 +445,6 @@ notify_pre_1_16_warning(svn_error_t *war
}
#endif
-/* Return SVN_ERR_UNSUPPORTED_FEATURE if URL is not inside the repository
- of LOCAL_ABSPATH. Use SCRATCH_POOL for temporary allocations. */
-static svn_error_t *
-check_repos_match(const svn_client__merge_target_t *target,
- const char *local_abspath,
- const char *url,
- apr_pool_t *scratch_pool)
-{
- if (!svn_uri__is_ancestor(target->loc.repos_root_url, url))
- return svn_error_createf(
- SVN_ERR_UNSUPPORTED_FEATURE, NULL,
- _("URL '%s' of '%s' is not in repository '%s'"),
- url, svn_dirent_local_style(local_abspath, scratch_pool),
- target->loc.repos_root_url);
-
- return SVN_NO_ERROR;
-}
-
/* Decide whether LOCATION1 and LOCATION2 point to the same repository
* (with the same root URL) or to two different repositories.
* - same repository root URL -> set *SAME_REPOS true
@@ -628,2916 +599,6 @@ perform_obstruction_check(svn_wc_notify_
return SVN_NO_ERROR;
}
-/* Create *LEFT and *RIGHT conflict versions for conflict victim
- * at VICTIM_ABSPATH, with merge-left node kind MERGE_LEFT_NODE_KIND
- * and merge-right node kind MERGE_RIGHT_NODE_KIND, using information
- * obtained from MERGE_SOURCE and TARGET.
- * 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,
- const char *victim_abspath,
- svn_node_kind_t merge_left_node_kind,
- svn_node_kind_t merge_right_node_kind,
- const svn_client__merge_source_t *merge_source,
- const svn_client__merge_target_t *target,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- const char *child = svn_dirent_skip_ancestor(target->abspath,
- victim_abspath);
- const char *left_relpath, *right_relpath;
-
- SVN_ERR_ASSERT(child != NULL);
- left_relpath = svn_client__pathrev_relpath(merge_source->loc1,
- scratch_pool);
- right_relpath = svn_client__pathrev_relpath(merge_source->loc2,
- 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, scratch_pool),
- merge_source->loc1->rev,
- merge_left_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, scratch_pool),
- merge_source->loc2->rev,
- merge_right_node_kind, result_pool);
-
- return SVN_NO_ERROR;
-}
-
-/* Helper for filter_self_referential_mergeinfo()
-
- *MERGEINFO is a non-empty, non-null collection of mergeinfo.
-
- Remove all mergeinfo from *MERGEINFO that describes revision ranges
- greater than REVISION. Put a copy of any removed mergeinfo, allocated
- in POOL, into *YOUNGER_MERGEINFO.
-
- If no mergeinfo is removed from *MERGEINFO then *YOUNGER_MERGEINFO is set
- to NULL. If all mergeinfo is removed from *MERGEINFO then *MERGEINFO is
- set to NULL.
- */
-static svn_error_t*
-split_mergeinfo_on_revision(svn_mergeinfo_t *younger_mergeinfo,
- svn_mergeinfo_t *mergeinfo,
- svn_revnum_t revision,
- apr_pool_t *pool)
-{
- apr_hash_index_t *hi;
- apr_pool_t *iterpool = svn_pool_create(pool);
-
- *younger_mergeinfo = NULL;
- for (hi = apr_hash_first(pool, *mergeinfo); hi; hi = apr_hash_next(hi))
- {
- int i;
- const char *merge_source_path = apr_hash_this_key(hi);
- svn_rangelist_t *rangelist = apr_hash_this_val(hi);
-
- svn_pool_clear(iterpool);
-
- for (i = 0; i < rangelist->nelts; i++)
- {
- svn_merge_range_t *range =
- APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
- if (range->end <= revision)
- {
- /* This entirely of this range is as old or older than
- REVISION, so leave it in *MERGEINFO. */
- continue;
- }
- else
- {
- /* Since the rangelists in svn_mergeinfo_t's are sorted in
- increasing order we know that part or all of *this* range
- and *all* of the remaining ranges in *RANGELIST are younger
- than REVISION. Remove the younger rangelists from
- *MERGEINFO and put them in *YOUNGER_MERGEINFO. */
- int j;
- svn_rangelist_t *younger_rangelist =
- apr_array_make(pool, 1, sizeof(svn_merge_range_t *));
-
- for (j = i; j < rangelist->nelts; j++)
- {
- svn_merge_range_t *younger_range = svn_merge_range_dup(
- APR_ARRAY_IDX(rangelist, j, svn_merge_range_t *), pool);
-
- /* REVISION might intersect with the first range where
- range->end > REVISION. If that is the case then split
- the current range into two, putting the younger half
- into *YOUNGER_MERGEINFO and leaving the older half in
- *MERGEINFO. */
- if (j == i && range->start + 1 <= revision)
- younger_range->start = range->end = revision;
-
- APR_ARRAY_PUSH(younger_rangelist, svn_merge_range_t *) =
- younger_range;
- }
-
- /* So far we've only been manipulating rangelists, now we
- actually create *YOUNGER_MERGEINFO and then remove the older
- ranges from *MERGEINFO */
- if (!(*younger_mergeinfo))
- *younger_mergeinfo = apr_hash_make(pool);
- 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++) */
- }
- }
- }
-
- svn_pool_destroy(iterpool);
-
- return SVN_NO_ERROR;
-}
-
-
-/* Make a copy of PROPCHANGES (array of svn_prop_t) into *TRIMMED_PROPCHANGES,
- omitting any svn:mergeinfo changes. */
-static svn_error_t *
-omit_mergeinfo_changes(apr_array_header_t **trimmed_propchanges,
- const apr_array_header_t *propchanges,
- apr_pool_t *result_pool)
-{
- int i;
-
- *trimmed_propchanges = apr_array_make(result_pool,
- propchanges->nelts,
- sizeof(svn_prop_t));
-
- for (i = 0; i < propchanges->nelts; ++i)
- {
- const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
-
- /* If this property is not svn:mergeinfo, then copy it. */
- if (strcmp(change->name, SVN_PROP_MERGEINFO) != 0)
- APR_ARRAY_PUSH(*trimmed_propchanges, svn_prop_t) = *change;
- }
-
- return SVN_NO_ERROR;
-}
-
-
-/* Helper for merge_props_changed().
-
- *PROPS is an array of svn_prop_t structures representing regular properties
- to be added to the working copy TARGET_ABSPATH.
-
- The merge source and target are assumed to be in the same repository.
-
- Filter out mergeinfo property additions to TARGET_ABSPATH when
- those additions refer to the same line of history as TARGET_ABSPATH as
- described below.
-
- Examine the added mergeinfo, looking at each range (or single rev)
- of each source path. If a source_path/range refers to the same line of
- history as TARGET_ABSPATH (pegged at its base revision), then filter out
- that range. If the entire rangelist for a given path is filtered then
- filter out the path as well.
-
- RA_SESSION is an open RA session to the repository
- in which both the source and target live, else RA_SESSION is not used. It
- may be temporarily reparented as needed by this function.
-
- Use CTX for any further client operations.
-
- If any filtering occurs, set outgoing *PROPS to a shallow copy (allocated
- in POOL) of incoming *PROPS minus the filtered mergeinfo. */
-static svn_error_t *
-filter_self_referential_mergeinfo(apr_array_header_t **props,
- const char *target_abspath,
- svn_ra_session_t *ra_session,
- svn_client_ctx_t *ctx,
- apr_pool_t *pool)
-{
- apr_array_header_t *adjusted_props;
- int i;
- apr_pool_t *iterpool;
- svn_boolean_t is_copy;
- const char *repos_relpath;
- svn_client__pathrev_t target_base;
-
- /* If PATH itself has been added there is no need to filter. */
- SVN_ERR(svn_wc__node_get_origin(&is_copy, &target_base.rev, &repos_relpath,
- &target_base.repos_root_url,
- &target_base.repos_uuid, NULL, NULL,
- ctx->wc_ctx, target_abspath, FALSE,
- pool, pool));
-
- if (is_copy || !repos_relpath)
- return SVN_NO_ERROR; /* A copy or a local addition */
-
- target_base.url = svn_path_url_add_component2(target_base.repos_root_url,
- repos_relpath, pool);
-
- adjusted_props = apr_array_make(pool, (*props)->nelts, sizeof(svn_prop_t));
- iterpool = svn_pool_create(pool);
- for (i = 0; i < (*props)->nelts; ++i)
- {
- svn_prop_t *prop = &APR_ARRAY_IDX((*props), i, svn_prop_t);
-
- svn_mergeinfo_t mergeinfo, younger_mergeinfo;
- svn_mergeinfo_t filtered_mergeinfo = NULL;
- svn_mergeinfo_t filtered_younger_mergeinfo = NULL;
- svn_error_t *err;
-
- /* If this property isn't mergeinfo or is NULL valued (i.e. prop removal)
- or empty mergeinfo it does not require any special handling. There
- is nothing to filter out of empty mergeinfo and the concept of
- filtering doesn't apply if we are trying to remove mergeinfo
- entirely. */
- if ((strcmp(prop->name, SVN_PROP_MERGEINFO) != 0)
- || (! prop->value) /* Removal of mergeinfo */
- || (! prop->value->len)) /* Empty mergeinfo */
- {
- APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *prop;
- continue;
- }
-
- svn_pool_clear(iterpool);
-
- /* Non-empty mergeinfo; filter self-referential mergeinfo out. */
-
- /* Parse the incoming mergeinfo to allow easier manipulation. */
- err = svn_mergeinfo_parse(&mergeinfo, prop->value->data, iterpool);
-
- if (err)
- {
- /* Issue #3896: If we can't parse it, we certainly can't
- filter it. */
- if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
- {
- svn_error_clear(err);
- APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *prop;
- continue;
- }
- else
- {
- return svn_error_trace(err);
- }
- }
-
- /* The working copy target PATH is at BASE_REVISION. Divide the
- incoming mergeinfo into two groups. One where all revision ranges
- are as old or older than BASE_REVISION and one where all revision
- ranges are younger.
-
- Note: You may be wondering why we do this.
-
- For the incoming mergeinfo "older" than target's base revision we
- can filter out self-referential mergeinfo efficiently using
- svn_client__get_history_as_mergeinfo(). We simply look at PATH's
- natural history as mergeinfo and remove that from any incoming
- mergeinfo.
-
- For mergeinfo "younger" than the base revision we can't use
- svn_ra_get_location_segments() to look into PATH's future
- history. Instead we must use svn_client__repos_locations() and
- look at each incoming source/range individually and see if PATH
- at its base revision and PATH at the start of the incoming range
- exist on the same line of history. If they do then we can filter
- out the incoming range. But since we have to do this for each
- range there is a substantial performance penalty to pay if the
- incoming ranges are not contiguous, i.e. we call
- svn_client__repos_locations for each discrete range and incur
- the cost of a roundtrip communication with the repository. */
- SVN_ERR(split_mergeinfo_on_revision(&younger_mergeinfo,
- &mergeinfo,
- target_base.rev,
- iterpool));
-
- /* Filter self-referential mergeinfo from younger_mergeinfo. */
- if (younger_mergeinfo)
- {
- apr_hash_index_t *hi;
- const char *merge_source_root_url;
-
- SVN_ERR(svn_ra_get_repos_root2(ra_session,
- &merge_source_root_url, iterpool));
-
- for (hi = apr_hash_first(iterpool, younger_mergeinfo);
- hi; hi = apr_hash_next(hi))
- {
- int j;
- const char *source_path = apr_hash_this_key(hi);
- svn_rangelist_t *rangelist = apr_hash_this_val(hi);
- const char *merge_source_url;
- svn_rangelist_t *adjusted_rangelist =
- apr_array_make(iterpool, 0, sizeof(svn_merge_range_t *));
-
- merge_source_url =
- svn_path_url_add_component2(merge_source_root_url,
- source_path + 1, iterpool);
-
- for (j = 0; j < rangelist->nelts; j++)
- {
- svn_error_t *err2;
- svn_client__pathrev_t *start_loc;
- svn_merge_range_t *range =
- APR_ARRAY_IDX(rangelist, j, svn_merge_range_t *);
-
- /* Because the merge source normalization code
- ensures mergeinfo refers to real locations on
- the same line of history, there's no need to
- look at the whole range, just the start. */
-
- /* Check if PATH@BASE_REVISION exists at
- RANGE->START on the same line of history.
- (start+1 because RANGE->start is not inclusive.) */
- err2 = svn_client__repos_location(&start_loc, ra_session,
- &target_base,
- range->start + 1,
- ctx, iterpool, iterpool);
- if (err2)
- {
- if (err2->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES
- || err2->apr_err == SVN_ERR_FS_NOT_FOUND
- || err2->apr_err == SVN_ERR_FS_NO_SUCH_REVISION)
- {
- /* PATH@BASE_REVISION didn't exist at
- RANGE->START + 1 or is unrelated to the
- resource PATH@RANGE->START. Some of the
- requested revisions may not even exist in
- the repository; a real possibility since
- mergeinfo is hand editable. In all of these
- cases clear and ignore the error and don't
- do any filtering.
-
- Note: In this last case it is possible that
- we will allow self-referential mergeinfo to
- be applied, but fixing it here is potentially
- very costly in terms of finding what part of
- a range is actually valid. Simply allowing
- the merge to proceed without filtering the
- offending range seems the least worst
- option. */
- svn_error_clear(err2);
- err2 = NULL;
- APR_ARRAY_PUSH(adjusted_rangelist,
- svn_merge_range_t *) = range;
- }
- else
- {
- return svn_error_trace(err2);
- }
- }
- else
- {
- /* PATH@BASE_REVISION exists on the same
- line of history at RANGE->START and RANGE->END.
- Now check that PATH@BASE_REVISION's path
- names at RANGE->START and RANGE->END are the same.
- If the names are not the same then the mergeinfo
- describing PATH@RANGE->START through
- PATH@RANGE->END actually belong to some other
- line of history and we want to record this
- mergeinfo, not filter it. */
- if (strcmp(start_loc->url, merge_source_url) != 0)
- {
- APR_ARRAY_PUSH(adjusted_rangelist,
- svn_merge_range_t *) = range;
- }
- }
- /* else no need to add, this mergeinfo is
- all on the same line of history. */
- } /* for (j = 0; j < rangelist->nelts; j++) */
-
- /* Add any rangelists for source_path that are not
- self-referential. */
- if (adjusted_rangelist->nelts)
- {
- if (!filtered_younger_mergeinfo)
- filtered_younger_mergeinfo = apr_hash_make(iterpool);
- svn_hash_sets(filtered_younger_mergeinfo, source_path,
- adjusted_rangelist);
- }
-
- } /* Iteration over each merge source in younger_mergeinfo. */
- } /* if (younger_mergeinfo) */
-
- /* Filter self-referential mergeinfo from "older" mergeinfo. */
- if (mergeinfo)
- {
- svn_mergeinfo_t implicit_mergeinfo;
-
- SVN_ERR(svn_client__get_history_as_mergeinfo(
- &implicit_mergeinfo, NULL,
- &target_base, target_base.rev, SVN_INVALID_REVNUM,
- ra_session, ctx, iterpool));
-
- /* Remove PATH's implicit mergeinfo from the incoming mergeinfo. */
- SVN_ERR(svn_mergeinfo_remove2(&filtered_mergeinfo,
- implicit_mergeinfo,
- mergeinfo, TRUE, iterpool, iterpool));
- }
-
- /* Combine whatever older and younger filtered mergeinfo exists
- into filtered_mergeinfo. */
- if (filtered_mergeinfo && filtered_younger_mergeinfo)
- SVN_ERR(svn_mergeinfo_merge2(filtered_mergeinfo,
- filtered_younger_mergeinfo, iterpool,
- iterpool));
- else if (filtered_younger_mergeinfo)
- filtered_mergeinfo = filtered_younger_mergeinfo;
-
- /* If there is any incoming mergeinfo remaining after filtering
- then put it in adjusted_props. */
- if (filtered_mergeinfo && apr_hash_count(filtered_mergeinfo))
- {
- /* Convert filtered_mergeinfo to a svn_prop_t and put it
- back in the array. */
- svn_string_t *filtered_mergeinfo_str;
- svn_prop_t *adjusted_prop = apr_pcalloc(pool,
- sizeof(*adjusted_prop));
- SVN_ERR(svn_mergeinfo_to_string(&filtered_mergeinfo_str,
- filtered_mergeinfo,
- pool));
- adjusted_prop->name = SVN_PROP_MERGEINFO;
- adjusted_prop->value = filtered_mergeinfo_str;
- APR_ARRAY_PUSH(adjusted_props, svn_prop_t) = *adjusted_prop;
- }
- }
- svn_pool_destroy(iterpool);
-
- *props = adjusted_props;
- return SVN_NO_ERROR;
-}
-
-/* Prepare a set of property changes PROPCHANGES to be used for a merge
- operation on LOCAL_ABSPATH.
-
- Remove all non-regular prop-changes (entry-props and WC-props).
- Remove all non-mergeinfo prop-changes if it's a record-only merge.
- Remove self-referential mergeinfo (### in some cases...)
- Remove foreign-repository mergeinfo (### in some cases...)
-
- Store the resulting property changes in *PROP_UPDATES.
- Store information on where mergeinfo is updated in MERGE_B.
-
- Used for both file and directory property merges. */
-static svn_error_t *
-prepare_merge_props_changed(const apr_array_header_t **prop_updates,
- const char *local_abspath,
- const apr_array_header_t *propchanges,
- merge_cmd_baton_t *merge_b,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- apr_array_header_t *props;
-
- SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
-
- /* We only want to merge "regular" version properties: by
- definition, 'svn merge' shouldn't touch any data within .svn/ */
- SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props,
- result_pool));
-
- /* If we are only applying mergeinfo changes then we need to do
- additional filtering of PROPS so it contains only mergeinfo changes. */
- if (merge_b->record_only && props->nelts)
- {
- apr_array_header_t *mergeinfo_props =
- apr_array_make(result_pool, 1, sizeof(svn_prop_t));
- int i;
-
- for (i = 0; i < props->nelts; i++)
- {
- svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
-
- if (strcmp(prop->name, SVN_PROP_MERGEINFO) == 0)
- {
- APR_ARRAY_PUSH(mergeinfo_props, svn_prop_t) = *prop;
- break;
- }
- }
- props = mergeinfo_props;
- }
-
- if (props->nelts)
- {
- /* Issue #3383: We don't want mergeinfo from a foreign repos.
-
- If this is a merge from a foreign repository we must strip all
- incoming mergeinfo (including mergeinfo deletions). */
- if (! merge_b->same_repos)
- SVN_ERR(omit_mergeinfo_changes(&props, props, result_pool));
-
- /* If this is a forward merge then don't add new mergeinfo to
- PATH that is already part of PATH's own history, see
- http://svn.haxx.se/dev/archive-2008-09/0006.shtml. If the
- merge sources are not ancestral then there is no concept of a
- 'forward' or 'reverse' merge and we filter unconditionally. */
- if (merge_b->merge_source.loc1->rev < merge_b->merge_source.loc2->rev
- || !merge_b->merge_source.ancestral)
- {
- if (HONOR_MERGEINFO(merge_b) || merge_b->reintegrate_merge)
- SVN_ERR(filter_self_referential_mergeinfo(&props,
- local_abspath,
- merge_b->ra_session2,
- merge_b->ctx,
- result_pool));
- }
- }
- *prop_updates = props;
-
- /* 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;
-
- for (i = 0; i < props->nelts; ++i)
- {
- svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
-
- if (strcmp(prop->name, SVN_PROP_MERGEINFO) == 0)
- {
- /* Does LOCAL_ABSPATH have any pristine mergeinfo? */
- svn_boolean_t has_pristine_mergeinfo = FALSE;
- apr_hash_t *pristine_props;
-
- SVN_ERR(svn_wc_get_pristine_props(&pristine_props,
- merge_b->ctx->wc_ctx,
- local_abspath,
- scratch_pool,
- scratch_pool));
-
- if (pristine_props
- && svn_hash_gets(pristine_props, SVN_PROP_MERGEINFO))
- has_pristine_mergeinfo = TRUE;
-
- if (!has_pristine_mergeinfo && prop->value)
- {
- alloc_and_store_path(&merge_b->paths_with_new_mergeinfo,
- local_abspath, merge_b->pool);
- }
- else if (has_pristine_mergeinfo && !prop->value)
- {
- alloc_and_store_path(&merge_b->paths_with_deleted_mergeinfo,
- local_abspath, merge_b->pool);
- }
- }
- }
- }
-
- return SVN_NO_ERROR;
-}
-
-#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;
-};
-
-/* 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;
-
- /* The pool containing this baton. Use for RESULT_POOL for storing in this
- baton */
- apr_pool_t *pool;
-
- /* 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.
-
- 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;
- svn_node_kind_t tree_conflict_local_node_kind;
- svn_node_kind_t tree_conflict_merge_left_node_kind;
- svn_node_kind_t tree_conflict_merge_right_node_kind;
-
- /* 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;
-};
-
-/* 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_dir_baton_t for an explanation. */
- svn_wc_conflict_reason_t tree_conflict_reason;
- svn_wc_conflict_action_t tree_conflict_action;
- svn_node_kind_t tree_conflict_local_node_kind;
- svn_node_kind_t tree_conflict_merge_left_node_kind;
- svn_node_kind_t tree_conflict_merge_right_node_kind;
-
- /* 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 */
-};
-
-/* Record the skip for future processing and (later) produce the
- skip notification */
-static svn_error_t *
-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,
- struct merge_dir_baton_t *pdb,
- 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)
- && !(pdb && pdb->shadowed))
- {
- store_path(merge_b->skipped_abspaths, local_abspath);
- }
-
- if (merge_b->notify_func)
- {
- svn_wc_notify_t *notify;
-
- notify = svn_wc_create_notify(local_abspath, action, scratch_pool);
- notify->kind = kind;
- notify->content_state = notify->prop_state = state;
-
- merge_b->notify_func(merge_b->notify_baton, notify,
- scratch_pool);
- }
- return SVN_NO_ERROR;
-}
-
-/* Forward declaration */
-static svn_client__merge_path_t *
-find_nearest_ancestor_with_intersecting_ranges(
- svn_revnum_t *start,
- svn_revnum_t *end,
- const apr_array_header_t *children_with_mergeinfo,
- svn_boolean_t path_is_own_ancestor,
- const char *local_abspath);
-
-/* 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.
- *
- * 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(merge_cmd_baton_t *merge_b,
- const char *local_abspath,
- struct merge_dir_baton_t *parent_baton,
- svn_node_kind_t local_node_kind,
- svn_node_kind_t merge_left_node_kind,
- svn_node_kind_t merge_right_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)
-{
- svn_wc_context_t *wc_ctx = merge_b->ctx->wc_ctx;
-
- if (merge_b->record_only)
- return SVN_NO_ERROR;
-
- 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->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;
- }
-
- if (HONOR_MERGEINFO(merge_b) && merge_b->merge_source.ancestral)
- {
- struct svn_client__merge_source_t *source;
- svn_client__pathrev_t *loc1;
- svn_client__pathrev_t *loc2;
- svn_merge_range_t range =
- {SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, TRUE};
-
- /* We are honoring mergeinfo so do not blindly record
- * a conflict describing the merge of
- * SOURCE->LOC1->URL@SOURCE->LOC1->REV through
- * SOURCE->LOC2->URL@SOURCE->LOC2->REV
- * but figure out the actual revision range merged. */
- (void)find_nearest_ancestor_with_intersecting_ranges(
- &(range.start), &(range.end),
- merge_b->children_with_mergeinfo,
- action != svn_wc_conflict_action_delete,
- local_abspath);
- loc1 = svn_client__pathrev_dup(merge_b->merge_source.loc1,
- scratch_pool);
- loc2 = svn_client__pathrev_dup(merge_b->merge_source.loc2,
- scratch_pool);
- loc1->rev = range.start;
- loc2->rev = range.end;
- source = svn_client__merge_source_create(loc1, loc2,
- merge_b->merge_source.ancestral,
- scratch_pool);
- SVN_ERR(make_conflict_versions(&left, &right, local_abspath,
- merge_left_node_kind,
- merge_right_node_kind,
- source, merge_b->target,
- result_pool, scratch_pool));
- }
- else
- SVN_ERR(make_conflict_versions(&left, &right, local_abspath,
- merge_left_node_kind,
- merge_right_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, local_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->notify_func && notify_tc)
- {
- svn_wc_notify_t *notify;
-
- notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict,
- scratch_pool);
- notify->kind = local_node_kind;
-
- merge_b->notify_func(merge_b->notify_baton, notify,
- scratch_pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Record the add for future processing and produce the
- update_add notification
- */
-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)
-{
- if (merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
- {
- store_path(merge_b->merged_abspaths, local_abspath);
- }
-
- if (merge_b->notify_func)
- {
- svn_wc_notify_t *notify;
- svn_wc_notify_action_t action = svn_wc_notify_update_add;
-
- if (notify_replaced)
- action = svn_wc_notify_update_replace;
-
- notify = svn_wc_create_notify(local_abspath, action, scratch_pool);
- notify->kind = kind;
-
- merge_b->notify_func(merge_b->notify_baton, notify,
- scratch_pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Record the update for future processing and produce the
- update_update notification */
-static svn_error_t *
-record_update_update(merge_cmd_baton_t *merge_b,
- const char *local_abspath,
- svn_node_kind_t kind,
- svn_wc_notify_state_t content_state,
- svn_wc_notify_state_t prop_state,
- apr_pool_t *scratch_pool)
-{
- if (merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
- {
- store_path(merge_b->merged_abspaths, local_abspath);
- }
-
- if (merge_b->notify_func)
- {
- svn_wc_notify_t *notify;
-
- 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->notify_func(merge_b->notify_baton, notify,
- scratch_pool);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Record the delete for future processing and for (later) producing the
- 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)
-{
- /* Update the lists of merged, skipped, tree-conflicted and added paths. */
- if (merge_b->merge_source.ancestral
- || merge_b->reintegrate_merge)
- {
- /* Issue #4166: If a previous merge added NOTIFY_ABSPATH, but we
- are now deleting it, then remove it from the list of added
- paths. */
- svn_hash_sets(merge_b->added_abspaths, local_abspath, NULL);
- store_path(merge_b->merged_abspaths, local_abspath);
- }
-
- if (parent_db)
- {
- const char *dup_abspath = apr_pstrdup(parent_db->pool, local_abspath);
-
- if (!parent_db->pending_deletes)
- parent_db->pending_deletes = apr_hash_make(parent_db->pool);
-
- svn_hash_sets(parent_db->pending_deletes, dup_abspath,
- svn_node_kind_to_word(kind));
- }
-
- /* Note in children_with_mergeinfo that all paths in this subtree are
- * being deleted, to avoid trying to set mergeinfo on them later. */
- if (merge_b->children_with_mergeinfo)
- {
- int i;
-
- for (i = 0; i < merge_b->children_with_mergeinfo->nelts; i++)
- {
- svn_client__merge_path_t *child
- = APR_ARRAY_IDX(merge_b->children_with_mergeinfo, i,
- svn_client__merge_path_t *);
-
- if (svn_dirent_is_ancestor(local_abspath, child->abspath))
- {
- SVN_ERR(svn_sort__array_delete2(merge_b->children_with_mergeinfo, i--, 1));
- }
- }
- }
-
- return SVN_NO_ERROR;
-}
-
-/* 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 *
-handle_pending_notifications(merge_cmd_baton_t *merge_b,
- struct merge_dir_baton_t *db,
- apr_pool_t *scratch_pool)
-{
- if (merge_b->notify_func && db->pending_deletes)
- {
- apr_hash_index_t *hi;
-
- for (hi = apr_hash_first(scratch_pool, db->pending_deletes);
- hi;
- hi = apr_hash_next(hi))
- {
- const char *del_abspath = apr_hash_this_key(hi);
- svn_wc_notify_t *notify;
-
- notify = svn_wc_create_notify(del_abspath,
- svn_wc_notify_update_delete,
- scratch_pool);
- notify->kind = svn_node_kind_from_word(
- apr_hash_this_val(hi));
-
- merge_b->notify_func(merge_b->notify_baton,
- notify, scratch_pool);
- }
-
- db->pending_deletes = NULL;
- }
- return SVN_NO_ERROR;
-}
-
-/* Helper function for the merge_dir_*() and merge_file_*() functions.
-
- 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)
- {
- const char *dir_abspath = svn_dirent_dirname(local_abspath,
- scratch_pool);
-
- SVN_ERR(mark_dir_edited(merge_b, db->parent_baton, dir_abspath,
- scratch_pool));
- }
-
- db->edited = TRUE;
-
- if (! db->shadowed)
- return SVN_NO_ERROR; /* Easy out */
-
- 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 */
-
- if (merge_b->notify_func)
- {
- svn_wc_notify_t *notify;
-
- notify = svn_wc_create_notify(
- local_abspath,
- (db->tree_conflict_reason == CONFLICT_REASON_SKIP)
- ? svn_wc_notify_skip
- : svn_wc_notify_update_skip_obstruction,
- scratch_pool);
- notify->kind = svn_node_dir;
- notify->content_state = notify->prop_state = db->skip_reason;
-
- merge_b->notify_func(merge_b->notify_baton,
- notify,
- scratch_pool);
- }
-
- if (merge_b->merge_source.ancestral
- || merge_b->reintegrate_merge)
- {
- store_path(merge_b->skipped_abspaths, local_abspath);
- }
- }
- else if (db->tree_conflict_reason != CONFLICT_REASON_NONE)
- {
- /* open_directory() decided that a tree conflict should be raised */
-
- SVN_ERR(record_tree_conflict(merge_b, local_abspath, db->parent_baton,
- db->tree_conflict_local_node_kind,
- db->tree_conflict_merge_left_node_kind,
- db->tree_conflict_merge_right_node_kind,
- db->tree_conflict_action,
- db->tree_conflict_reason,
- NULL, TRUE,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* 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 char *dir_abspath = svn_dirent_dirname(local_abspath,
- scratch_pool);
-
- SVN_ERR(mark_dir_edited(merge_b, fb->parent_baton, dir_abspath,
- 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 (merge_b->notify_func)
- {
- svn_wc_notify_t *notify;
-
- 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->notify_func(merge_b->notify_baton,
- notify,
- scratch_pool);
- }
-
- if (merge_b->merge_source.ancestral
- || merge_b->reintegrate_merge)
- {
- store_path(merge_b->skipped_abspaths, local_abspath);
- }
- }
- 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,
- fb->tree_conflict_local_node_kind,
- fb->tree_conflict_merge_left_node_kind,
- fb->tree_conflict_merge_right_node_kind,
- fb->tree_conflict_action,
- fb->tree_conflict_reason,
- NULL, TRUE,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* An svn_diff_tree_processor_t function.
-
- Called before either merge_file_changed(), merge_file_added(),
- merge_file_deleted() or merge_file_closed(), unless it sets *SKIP to TRUE.
-
- When *SKIP is TRUE, the diff driver avoids work on getting the details
- for the closing callbacks.
- */
-static svn_error_t *
-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)
-{
- 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);
-
- 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;
-
- if (left_source)
- fb->tree_conflict_merge_left_node_kind = svn_node_file;
- else
- fb->tree_conflict_merge_left_node_kind = svn_node_none;
-
- if (right_source)
- fb->tree_conflict_merge_right_node_kind = svn_node_file;
- else
- fb->tree_conflict_merge_right_node_kind = svn_node_none;
-
- *new_file_baton = fb;
-
- if (pdb)
- {
- fb->parent_baton = pdb;
- fb->shadowed = pdb->shadowed;
- fb->skip_reason = pdb->skip_reason;
- }
-
- if (fb->shadowed)
- {
- /* An ancestor is tree conflicted. Nothing to do here. */
- }
- else if (left_source != NULL)
- {
- /* Node is expected to be a file, which will be changed or deleted. */
- 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,
- &fb->tree_conflict_local_node_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)
- fb->tree_conflict_local_node_kind = svn_node_none;
- }
-
- if (fb->tree_conflict_local_node_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 (fb->tree_conflict_local_node_kind != svn_node_file)
- {
- svn_boolean_t added;
- fb->shadowed = TRUE;
-
- SVN_ERR(svn_wc__node_is_added(&added, merge_b->ctx->wc_ctx,
- local_abspath, scratch_pool));
-
- fb->tree_conflict_reason = added ? svn_wc_conflict_reason_added
- : 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;
-
- /* 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,
- old_tc->node_kind,
- old_tc->src_left_version->node_kind,
- 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_boolean_t is_deleted;
-
- SVN_ERR(perform_obstruction_check(&obstr_state, &is_deleted, NULL,
- &fb->tree_conflict_local_node_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 (fb->tree_conflict_local_node_kind != svn_node_none
- && !is_deleted)
- {
- /* Set a tree conflict */
- svn_boolean_t added;
-
- fb->shadowed = TRUE;
- SVN_ERR(svn_wc__node_is_added(&added, merge_b->ctx->wc_ctx,
- local_abspath, scratch_pool));
-
- fb->tree_conflict_reason = added ? svn_wc_conflict_reason_added
- : svn_wc_conflict_reason_obstructed;
- }
- }
-
- /* Handle pending conflicts */
- SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* 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(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 = 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,
- 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(!left_file || svn_dirent_is_absolute(left_file));
- SVN_ERR_ASSERT(!right_file || svn_dirent_is_absolute(right_file));
-
- SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
-
- if (fb->shadowed)
- {
- 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_update,
- fb->skip_reason, fb->parent_baton,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
- }
-
- /* This callback is essentially no more than a wrapper around
- svn_wc_merge6(). Thank goodness that all the
- diff-editor-mechanisms are doing the hard work of getting the
- fulltexts! */
-
- 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, 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 || !left_file) && prop_changes->nelts)
- {
- SVN_ERR(svn_wc_merge_props3(&property_state, ctx->wc_ctx, local_abspath,
- left, right,
- left_props, prop_changes,
- merge_b->dry_run,
- NULL, NULL,
- ctx->cancel_func, ctx->cancel_baton,
- scratch_pool));
- if (property_state == svn_wc_notify_state_conflicted)
- {
- alloc_and_store_path(&merge_b->conflicted_paths, local_abspath,
- merge_b->pool);
- }
- }
-
- /* Easy out: We are only applying mergeinfo differences. */
- if (merge_b->record_only)
- {
- /* NO-OP */
- }
- else if (left_file)
- {
- svn_boolean_t has_local_mods;
- enum svn_wc_merge_outcome_t content_outcome;
- const char *target_label;
- const char *left_label;
- const char *right_label;
- const char *path_ext = "";
-
- if (merge_b->ext_patterns && merge_b->ext_patterns->nelts)
- {
- svn_path_splitext(NULL, &path_ext, local_abspath, scratch_pool);
- if (! (*path_ext
- && svn_cstring_match_glob_list(path_ext,
- merge_b->ext_patterns)))
- {
- path_ext = "";
- }
- }
-
- /* xgettext: the '.working', '.merge-left.r%ld' and
- '.merge-right.r%ld' strings are used to tag onto a file
- name in case of a merge conflict */
-
- target_label = apr_psprintf(scratch_pool, _(".working%s%s"),
- *path_ext ? "." : "", path_ext);
- left_label = apr_psprintf(scratch_pool,
- _(".merge-left.r%ld%s%s"),
- left_source->revision,
- *path_ext ? "." : "", path_ext);
- right_label = apr_psprintf(scratch_pool,
- _(".merge-right.r%ld%s%s"),
- right_source->revision,
- *path_ext ? "." : "", path_ext);
-
- SVN_ERR(svn_wc_text_modified_p2(&has_local_mods, ctx->wc_ctx,
- local_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_ERR(svn_wc_merge6(&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,
- left_props, prop_changes,
- NULL, NULL,
- 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)
- text_state = svn_wc_notify_state_conflicted;
- else if (has_local_mods
- && content_outcome != svn_wc_merge_unchanged)
- text_state = svn_wc_notify_state_merged;
- else if (content_outcome == svn_wc_merge_merged)
- text_state = svn_wc_notify_state_changed;
- else if (content_outcome == svn_wc_merge_no_merge)
- text_state = svn_wc_notify_state_missing;
- else /* merge_outcome == svn_wc_merge_unchanged */
- text_state = svn_wc_notify_state_unchanged;
- }
-
- 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,
- text_state, property_state,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* 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(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 = processor->baton;
- struct merge_file_baton_t *fb = file_baton;
- const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
- relpath, scratch_pool);
- apr_hash_t *pristine_props;
- apr_hash_t *new_props;
-
- SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
-
- SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
-
- if (fb->shadowed)
- {
- 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, fb->parent_baton,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
- }
-
- /* Easy out: We are only applying mergeinfo differences. */
- if (merge_b->record_only)
- {
- return SVN_NO_ERROR;
- }
-
- if ((merge_b->merge_source.ancestral || merge_b->reintegrate_merge)
- && ( !fb->parent_baton || !fb->parent_baton->added))
- {
- /* 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, *pristine_contents;
-
- /* If this is a merge from the same repository as our
- working copy, we handle adds as add-with-history.
- Otherwise, we'll use a pure add. */
- if (merge_b->same_repos)
- {
- copyfrom_url = svn_path_url_add_component2(
- merge_b->merge_source.loc2->url,
- relpath, scratch_pool);
- copyfrom_rev = right_source->revision;
- SVN_ERR(check_repos_match(merge_b->target, local_abspath,
- copyfrom_url, scratch_pool));
- 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;
-
- pristine_contents = svn_stream_empty(scratch_pool);
- SVN_ERR(svn_stream_open_readonly(&new_contents, right_file,
- scratch_pool, scratch_pool));
-
- pristine_props = apr_hash_make(scratch_pool); /* Local addition */
-
- /* 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));
-
- 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);
- }
-
- /* 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));
-
- /* Caller must call svn_sleep_for_timestamps() */
- *merge_b->use_sleep = TRUE;
- }
-
- SVN_ERR(record_update_add(merge_b, local_abspath, svn_node_file,
- fb->add_is_replace, scratch_pool));
-
- return SVN_NO_ERROR;
-}
-
-/* Compare the two sets of properties PROPS1 and PROPS2, ignoring the
- * "svn:mergeinfo" property, and noticing only "normal" props. Set *SAME to
- * true if the rest of the properties are identical or false if they differ.
- */
-static svn_error_t *
-properties_same_p(svn_boolean_t *same,
- apr_hash_t *props1,
- apr_hash_t *props2,
- apr_pool_t *scratch_pool)
-{
- apr_array_header_t *prop_changes;
- int i, diffs;
-
- /* Examine the properties that differ */
- SVN_ERR(svn_prop_diffs(&prop_changes, props1, props2, scratch_pool));
- diffs = 0;
- for (i = 0; i < prop_changes->nelts; i++)
- {
- const char *pname = APR_ARRAY_IDX(prop_changes, i, svn_prop_t).name;
-
- /* Count the properties we're interested in; ignore the rest */
- if (svn_wc_is_normal_prop(pname)
- && strcmp(pname, SVN_PROP_MERGEINFO) != 0)
- diffs++;
- }
- *same = (diffs == 0);
- return SVN_NO_ERROR;
-}
-
-/* Compare the file OLDER_ABSPATH (together with its normal properties in
- * ORIGINAL_PROPS which may also contain WC props and entry props) with the
- * versioned file MINE_ABSPATH (together with its versioned properties).
- * Set *SAME to true if they are the same or false if they differ, ignoring
- * the "svn:mergeinfo" property, and ignoring differences in keyword
- * expansion and end-of-line style. */
-static svn_error_t *
-files_same_p(svn_boolean_t *same,
- const char *older_abspath,
- apr_hash_t *original_props,
- const char *mine_abspath,
- svn_wc_context_t *wc_ctx,
- apr_pool_t *scratch_pool)
-{
- apr_hash_t *working_props;
-
- SVN_ERR(svn_wc_prop_list2(&working_props, wc_ctx, mine_abspath,
- scratch_pool, scratch_pool));
-
- /* Compare the properties */
- SVN_ERR(properties_same_p(same, original_props, working_props,
- scratch_pool));
- if (*same)
- {
- svn_stream_t *mine_stream;
- svn_stream_t *older_stream;
- svn_string_t *special = svn_hash_gets(working_props, SVN_PROP_SPECIAL);
- svn_string_t *eol_style = svn_hash_gets(working_props, SVN_PROP_EOL_STYLE);
- svn_string_t *keywords = svn_hash_gets(working_props, SVN_PROP_KEYWORDS);
-
- /* Compare the file content, translating 'mine' to 'normal' form. */
- if (special != NULL)
- SVN_ERR(svn_subst_read_specialfile(&mine_stream, mine_abspath,
- scratch_pool, scratch_pool));
- else
- SVN_ERR(svn_stream_open_readonly(&mine_stream, mine_abspath,
- scratch_pool, scratch_pool));
-
- if (!special && (eol_style || keywords))
- {
- apr_hash_t *kw = NULL;
- const char *eol = NULL;
- svn_subst_eol_style_t style;
-
- /* We used to use svn_client__get_normalized_stream() here, but
- that doesn't work in 100% of the cases because it doesn't
- convert EOLs to the repository form; just to '\n'.
- */
-
- if (eol_style)
- {
- svn_subst_eol_style_from_value(&style, &eol, eol_style->data);
-
- if (style == svn_subst_eol_style_native)
- eol = SVN_SUBST_NATIVE_EOL_STR;
- else if (style != svn_subst_eol_style_fixed
- && style != svn_subst_eol_style_none)
- return svn_error_create(SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
- }
-
- if (keywords)
- SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data, "", "",
- "", 0, "", scratch_pool));
-
- mine_stream = svn_subst_stream_translated(
- mine_stream, eol, FALSE, kw, FALSE, scratch_pool);
- }
-
- SVN_ERR(svn_stream_open_readonly(&older_stream, older_abspath,
- scratch_pool, scratch_pool));
-
- SVN_ERR(svn_stream_contents_same2(same, mine_stream, older_stream,
- scratch_pool));
-
- }
-
- return SVN_NO_ERROR;
-}
-
-/* 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(const char *relpath,
- const svn_diff_source_t *left_source,
- const char *left_file,
- /*const*/ apr_hash_t *left_props,
- void *file_baton,
- const struct svn_diff_tree_processor_t *processor,
- apr_pool_t *scratch_pool)
-{
- 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,
- relpath, scratch_pool);
- svn_boolean_t same;
-
- SVN_ERR(mark_file_edited(merge_b, fb, local_abspath, scratch_pool));
-
- if (fb->shadowed)
- {
- 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_delete,
- fb->skip_reason, fb->parent_baton,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
- }
-
- /* Easy out: We are only applying mergeinfo differences. */
- if (merge_b->record_only)
- {
- return SVN_NO_ERROR;
- }
-
- /* If the files are identical, attempt deletion */
- if (merge_b->force_delete)
- same = TRUE;
- else
- SVN_ERR(files_same_p(&same, left_file, left_props,
- local_abspath, merge_b->ctx->wc_ctx,
- scratch_pool));
-
- if (fb->parent_baton
- && fb->parent_baton->delete_state)
- {
- if (same)
- {
- /* Note that we checked this file */
- store_path(fb->parent_baton->delete_state->compared_abspaths,
- local_abspath);
- }
- else
- {
- /* We found some modification. Parent should raise a tree conflict */
- fb->parent_baton->delete_state->found_edit = TRUE;
- }
-
- return SVN_NO_ERROR;
- }
- else if (same)
- {
- if (!merge_b->dry_run)
- SVN_ERR(svn_wc_delete4(merge_b->ctx->wc_ctx, local_abspath,
- FALSE /* keep_local */, FALSE /* unversioned */,
- merge_b->ctx->cancel_func,
- merge_b->ctx->cancel_baton,
- NULL, NULL /* no notify */,
- scratch_pool));
-
- /* Record that we might have deleted mergeinfo */
- alloc_and_store_path(&merge_b->paths_with_deleted_mergeinfo,
- local_abspath, merge_b->pool);
-
- /* And notify the deletion */
- SVN_ERR(record_update_delete(merge_b, fb->parent_baton, local_abspath,
- svn_node_file, scratch_pool));
- }
- else
- {
- /* The files differ, so raise a conflict instead of deleting */
-
- /* This is use case 5 described in the paper attached to issue
- * #2282. See also notes/tree-conflicts/detection.txt
- */
- SVN_ERR(record_tree_conflict(merge_b, local_abspath, fb->parent_baton,
- svn_node_file,
- svn_node_file,
- svn_node_none,
- svn_wc_conflict_action_delete,
- svn_wc_conflict_reason_edited,
- NULL, TRUE,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
-}
-
-/* An svn_diff_tree_processor_t function.
-
- Called before either merge_dir_changed(), merge_dir_added(),
- merge_dir_deleted() or merge_dir_closed(), unless it sets *SKIP to TRUE.
-
- After this call and before the close call, all descendants will receive
- their changes, unless *SKIP_CHILDREN is set to TRUE.
-
- When *SKIP is TRUE, the diff driver avoids work on getting the details
- for the closing callbacks.
-
- The SKIP and SKIP_DESCENDANTS work independently.
- */
-static svn_error_t *
-merge_dir_opened(void **new_dir_baton,
- svn_boolean_t *skip,
- svn_boolean_t *skip_children,
- 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 *parent_dir_baton,
- const struct svn_diff_tree_processor_t *processor,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- merge_cmd_baton_t *merge_b = processor->baton;
- struct merge_dir_baton_t *db;
- struct merge_dir_baton_t *pdb = parent_dir_baton;
-
- const char *local_abspath = svn_dirent_join(merge_b->target->abspath,
- relpath, scratch_pool);
-
- db = apr_pcalloc(result_pool, sizeof(*db));
- db->pool = result_pool;
- db->tree_conflict_reason = CONFLICT_REASON_NONE;
- db->tree_conflict_action = svn_wc_conflict_action_edit;
- db->skip_reason = svn_wc_notify_state_unknown;
-
- *new_dir_baton = db;
-
- if (left_source)
- db->tree_conflict_merge_left_node_kind = svn_node_dir;
- else
- db->tree_conflict_merge_left_node_kind = svn_node_none;
-
- if (right_source)
- db->tree_conflict_merge_right_node_kind = svn_node_dir;
- else
- db->tree_conflict_merge_right_node_kind = svn_node_none;
-
- if (pdb)
- {
- db->parent_baton = pdb;
- db->shadowed = pdb->shadowed;
- db->skip_reason = pdb->skip_reason;
- }
-
- if (db->shadowed)
- {
- /* An ancestor is tree conflicted. Nothing to do here. */
- if (! left_source)
- db->added = TRUE;
- }
- else if (left_source != NULL)
- {
- /* Node is expected to be a directory. */
- svn_boolean_t is_deleted;
- svn_boolean_t excluded;
- svn_depth_t parent_depth;
-
- if (! right_source)
- db->tree_conflict_action = svn_wc_conflict_action_delete;
-
- /* 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, &excluded,
- &db->tree_conflict_local_node_kind,
- &parent_depth, merge_b,
- local_abspath, scratch_pool));
-
- if (obstr_state != svn_wc_notify_state_inapplicable)
- {
- db->shadowed = TRUE;
-
- if (obstr_state == svn_wc_notify_state_obstructed)
- {
- svn_boolean_t is_wcroot;
-
- SVN_ERR(svn_wc_check_root(&is_wcroot, NULL, NULL,
- merge_b->ctx->wc_ctx,
- local_abspath, scratch_pool));
-
- if (is_wcroot)
- {
- db->tree_conflict_reason = CONFLICT_REASON_SKIP_WC;
- return SVN_NO_ERROR;
- }
- }
-
- db->tree_conflict_reason = CONFLICT_REASON_SKIP;
- db->skip_reason = obstr_state;
-
- if (! right_source)
- {
- *skip = *skip_children = TRUE;
- SVN_ERR(mark_dir_edited(merge_b, db, local_abspath,
- scratch_pool));
- }
-
- return SVN_NO_ERROR;
- }
-
- if (is_deleted)
- db->tree_conflict_local_node_kind = svn_node_none;
- }
-
- if (db->tree_conflict_local_node_kind == svn_node_none)
- {
- db->shadowed = TRUE;
-
- /* If this is not the merge target and the parent is too shallow to
- contain this directory, and the directory is not presen
- 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_immediates)))
- {
- db->shadowed = TRUE;
-
- db->tree_conflict_reason = CONFLICT_REASON_SKIP;
- db->skip_reason = svn_wc_notify_state_missing;
-
- return SVN_NO_ERROR;
- }
-
- if (is_deleted)
- db->tree_conflict_reason = svn_wc_conflict_reason_deleted;
- else
- db->tree_conflict_reason = svn_wc_conflict_reason_missing;
-
- /* ### To avoid breaking tests */
- *skip = TRUE;
- *skip_children = TRUE;
- SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
- return SVN_NO_ERROR;
- /* ### /avoid breaking tests */
- }
- else if (db->tree_conflict_local_node_kind != svn_node_dir)
- {
- svn_boolean_t added;
-
- db->shadowed = TRUE;
- SVN_ERR(svn_wc__node_is_added(&added, merge_b->ctx->wc_ctx,
- local_abspath, scratch_pool));
-
- db->tree_conflict_reason = added ? svn_wc_conflict_reason_added
- : svn_wc_conflict_reason_obstructed;
-
- /* ### To avoid breaking tests */
- *skip = TRUE;
- *skip_children = TRUE;
- SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
- return SVN_NO_ERROR;
- /* ### /avoid breaking tests */
- }
-
- if (! right_source)
- {
- /* We want to delete the directory */
- /* Mark PB edited now? */
- db->tree_conflict_action = svn_wc_conflict_action_delete;
- SVN_ERR(mark_dir_edited(merge_b, db, local_abspath, scratch_pool));
-
- if (db->shadowed)
- {
- *skip_children = TRUE;
- return SVN_NO_ERROR; /* Already set a tree conflict */
- }
-
- db->delete_state = (pdb != NULL) ? pdb->delete_state : NULL;
-
- if (db->delete_state && db->delete_state->found_edit)
- {
- /* A sibling found a conflict. Done. */
- *skip = TRUE;
- *skip_children = TRUE;
- }
- else if (merge_b->force_delete)
- {
- /* No comparison necessary */
- *skip_children = TRUE;
- }
- else if (! db->delete_state)
- {
- /* Start descendant comparison */
- db->delete_state = apr_pcalloc(db->pool,
- sizeof(*db->delete_state));
-
- db->delete_state->del_root = db;
- db->delete_state->compared_abspaths = apr_hash_make(db->pool);
- }
- }
- }
- else
- {
- const svn_wc_conflict_description2_t *old_tc = NULL;
-
- /* The node doesn't exist pre-merge: We have an addition */
- db->added = TRUE;
- db->tree_conflict_action = svn_wc_conflict_action_add;
-
- if (pdb && pdb->pending_deletes
- && svn_hash_gets(pdb->pending_deletes, local_abspath))
- {
- db->add_is_replace = TRUE;
- db->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)))
- {
- db->tree_conflict_action = svn_wc_conflict_action_replace;
- db->tree_conflict_reason = old_tc->reason;
-
- 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;
- *skip_children = TRUE;
-
- /* Update the tree conflict to store that this is a replace */
- SVN_ERR(record_tree_conflict(merge_b, local_abspath, pdb,
- old_tc->node_kind,
[... 754 lines stripped ...]