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 ...]
