diff options
author | James McCoy <jamessan@debian.org> | 2019-11-16 13:05:42 -0500 |
---|---|---|
committer | James McCoy <jamessan@debian.org> | 2019-11-16 13:05:42 -0500 |
commit | ff0dcb36b05eb662b51423774a4502bc04378f69 (patch) | |
tree | 0839f9b4a052f41206aebfed0c08b2b832621638 /subversion/libsvn_client | |
parent | 569b75c031bd32d4ee16874ecdf225adac62514d (diff) |
New upstream version 1.13.0
Diffstat (limited to 'subversion/libsvn_client')
29 files changed, 6681 insertions, 2706 deletions
diff --git a/subversion/libsvn_client/add.c b/subversion/libsvn_client/add.c index ce7891a..3bb548a 100644 --- a/subversion/libsvn_client/add.c +++ b/subversion/libsvn_client/add.c @@ -983,12 +983,13 @@ svn_client_add5(const char *path, static svn_error_t * path_driver_cb_func(void **dir_baton, + const svn_delta_editor_t *editor, + void *edit_baton, void *parent_baton, void *callback_baton, const char *path, apr_pool_t *pool) { - const svn_delta_editor_t *editor = callback_baton; SVN_ERR(svn_path_check_valid(path, pool)); return editor->add_directory(path, parent_baton, NULL, SVN_INVALID_REVNUM, pool, dir_baton); @@ -1177,8 +1178,8 @@ mkdir_urls(const apr_array_header_t *urls, /* Call the path-based editor driver. */ err = svn_error_trace( - svn_delta_path_driver2(editor, edit_baton, targets, TRUE, - path_driver_cb_func, (void *)editor, pool)); + svn_delta_path_driver3(editor, edit_baton, targets, TRUE, + path_driver_cb_func, NULL, pool)); if (err) { diff --git a/subversion/libsvn_client/blame.c b/subversion/libsvn_client/blame.c index b9363e2..ab48d40 100644 --- a/subversion/libsvn_client/blame.c +++ b/subversion/libsvn_client/blame.c @@ -656,14 +656,16 @@ normalize_blames(struct blame_chain *chain, } svn_error_t * -svn_client_blame5(const char *target, +svn_client_blame6(svn_revnum_t *start_revnum_p, + svn_revnum_t *end_revnum_p, + const char *target, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *start, const svn_opt_revision_t *end, const svn_diff_file_options_t *diff_options, svn_boolean_t ignore_mime_type, svn_boolean_t include_merged_revisions, - svn_client_blame_receiver3_t receiver, + svn_client_blame_receiver4_t receiver, void *receiver_baton, svn_client_ctx_t *ctx, apr_pool_t *pool) @@ -696,10 +698,13 @@ svn_client_blame5(const char *target, SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx, target_abspath_or_url, ra_session, start, pool)); - + if (start_revnum_p) + *start_revnum_p = start_revnum; SVN_ERR(svn_client__get_revision_number(&end_revnum, NULL, ctx->wc_ctx, target_abspath_or_url, ra_session, end, pool)); + if (end_revnum_p) + *end_revnum_p = end_revnum; { svn_client__pathrev_t *loc; @@ -941,18 +946,21 @@ svn_client_blame5(const char *target, SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); if (!eof || sb->len) { + svn_string_t line; + line.data = sb->data; + line.len = sb->len; if (walk->rev) - SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, + SVN_ERR(receiver(receiver_baton, line_no, walk->rev->revision, walk->rev->rev_props, merged_rev, merged_rev_props, merged_path, - sb->data, FALSE, iterpool)); + &line, FALSE, iterpool)); else - SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, + SVN_ERR(receiver(receiver_baton, line_no, SVN_INVALID_REVNUM, NULL, SVN_INVALID_REVNUM, NULL, NULL, - sb->data, TRUE, iterpool)); + &line, TRUE, iterpool)); } if (eof) break; } diff --git a/subversion/libsvn_client/client.h b/subversion/libsvn_client/client.h index c0a7947..97cd1e2 100644 --- a/subversion/libsvn_client/client.h +++ b/subversion/libsvn_client/client.h @@ -682,34 +682,6 @@ svn_client__get_diff_editor2(const svn_delta_editor_t **editor, /* ---------------------------------------------------------------- */ -/*** Editor for diff summary ***/ - -/* Set *DIFF_PROCESSOR to a diff processor that will report a diff summary - to SUMMARIZE_FUNC. - - P_ROOT_RELPATH will return a pointer to a string that must be set to - the root of the operation before the processor is called. - - ORIGINAL_PATH specifies the original path and will be used with - **ANCHOR_PATH to create paths as the user originally provided them - to the diff function. - - SUMMARIZE_FUNC is called with SUMMARIZE_BATON as parameter by the - created callbacks for each changed item. -*/ -svn_error_t * -svn_client__get_diff_summarize_callbacks( - const svn_diff_tree_processor_t **diff_processor, - const char ***p_root_relpath, - svn_client_diff_summarize_func_t summarize_func, - void *summarize_baton, - const char *original_target, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - -/* ---------------------------------------------------------------- */ - - /*** Copy Stuff ***/ /* This structure is used to associate a specific copy or move SRC with a @@ -754,40 +726,23 @@ typedef struct svn_client__copy_pair_t /*** Commit Stuff ***/ -/* WARNING: This is all new, untested, un-peer-reviewed conceptual - stuff. +/* The "Harvest Committables" System - The day that 'svn switch' came into existence, our old commit - crawler (svn_wc_crawl_local_mods) became obsolete. It relied far - too heavily on the on-disk hierarchy of files and directories, and - simply had no way to support disjoint working copy trees or nest - working copies. The primary reason for this is that commit - process, in order to guarantee atomicity, is a single drive of a + The commit process requires, per repository, a single drive of a commit editor which is based not on working copy paths, but on - URLs. With the completion of 'svn switch', it became all too - likely that the on-disk working copy hierarchy would no longer be - guaranteed to map to a similar in-repository hierarchy. - - Aside from this new brokenness of the old system, an unrelated - feature request had cropped up -- the ability to know in advance of - your commit, exactly what would be committed (so that log messages - could be initially populated with this information). Since the old - crawler discovered commit candidates while in the process of - committing, it was impossible to harvest this information upfront. - As a workaround, svn_wc_statuses() was used to stat the whole - working copy for changes before the commit started...and then the - commit would again stat the whole tree for changes. - - Enter the new system. + URLs. The on-disk working copy hierarchy does not, in general, + map to a similar in-repository hierarchy, due to switched subtrees + and disjoint working copies. + + Also we wish to know exactly what would be committed, in advance of + the commit, so that a log message editor can be initially populated + with this information. The primary goal of this system is very straightforward: harvest all commit candidate information up front, and cache enough info in the process to use this to drive a URL-sorted commit. - *** END-OF-KNOWLEDGE *** - - The prototypes below are still in development. In general, the - idea is that commit-y processes ('svn mkdir URL', 'svn delete URL', + The idea is that commit-y processes ('svn mkdir URL', 'svn delete URL', 'svn commit', 'svn copy WC_PATH URL', 'svn copy URL1 URL2', 'svn move URL1 URL2', others?) generate the cached commit candidate information, and hand this information off to a consumer which is @@ -844,7 +799,7 @@ typedef svn_error_t *(*svn_client__check_url_kind_t)(void *baton, - if the candidate has a lock token, add it to the LOCK_TOKENS hash. - if the candidate is a directory scheduled for deletion, crawl - the directories children recursively for any lock tokens and + the directory's children recursively for any lock tokens and add them to the LOCK_TOKENS array. At the successful return of this function, COMMITTABLES will point @@ -915,6 +870,18 @@ svn_client__condense_commit_items(const char **base_url, apr_array_header_t *commit_items, apr_pool_t *pool); +/* Rewrite the COMMIT_ITEMS array to be sorted by URL. + Rewrite the items' URLs to be relative to BASE_URL. + + COMMIT_ITEMS is an array of (svn_client_commit_item3_t *) items. + + Afterwards, some of the items in COMMIT_ITEMS may contain data + allocated in POOL. */ +svn_error_t * +svn_client__condense_commit_items2(const char *base_url, + apr_array_header_t *commit_items, + apr_pool_t *pool); + /* Commit the items in the COMMIT_ITEMS array using EDITOR/EDIT_BATON to describe the committed local mods. Prior to this call, COMMIT_ITEMS should have been run through (and BASE_URL generated @@ -1129,24 +1096,26 @@ svn_client__resolve_conflicts(svn_boolean_t *conflicts_remain, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool); -/* Produce a diff with depth DEPTH between two files or two directories at - * LEFT_ABSPATH1 and RIGHT_ABSPATH, using the provided diff callbacks to - * show changes in files. The files and directories involved may be part of - * a working copy or they may be unversioned. For versioned files, show - * property changes, too. +/* Produce a diff with depth DEPTH between the file or directory at + * LEFT_ABSPATH and the file or directory at RIGHT_ABSPATH, reporting + * differences to DIFF_PROCESSOR. + * + * The files and directories involved may be part of a working copy or + * they may be unversioned. For versioned files, show property changes, + * too. * - * If ANCHOR_ABSPATH is not null, set it to the anchor of the diff before - * the first processor call. (The anchor is LEFT_ABSPATH or an ancestor of it) + * No copy or move information is reported to the diff processor. + * + * Anchor the DIFF_PROCESSOR at the requested diff targets (LEFT_ABSPATH, + * RIGHT_ABSPATH). As any children reached by recursion are matched by + * name, a diff processor relpath applies equally to both sides of the diff. */ svn_error_t * -svn_client__arbitrary_nodes_diff(const char **root_relpath, - svn_boolean_t *root_is_dir, - const char *left_abspath, +svn_client__arbitrary_nodes_diff(const char *left_abspath, const char *right_abspath, svn_depth_t depth, const svn_diff_tree_processor_t *diff_processor, svn_client_ctx_t *ctx, - apr_pool_t *result_pool, apr_pool_t *scratch_pool); diff --git a/subversion/libsvn_client/commit.c b/subversion/libsvn_client/commit.c index 4a945c8..df2f5f7 100644 --- a/subversion/libsvn_client/commit.c +++ b/subversion/libsvn_client/commit.c @@ -500,6 +500,129 @@ append_externals_as_explicit_targets(apr_array_header_t *rel_targets, return SVN_NO_ERROR; } +/* Crawl the working copy for commit items. + */ +static svn_error_t * +harvest_committables(apr_array_header_t **commit_items_p, + apr_hash_t **committables_by_path_p, + apr_hash_t **lock_tokens, + const char *base_dir_abspath, + const apr_array_header_t *targets, + int depth_empty_start, + svn_depth_t depth, + svn_boolean_t just_locked, + const apr_array_header_t *changelists, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct check_url_kind_baton cukb; + svn_client__committables_t *committables; + apr_hash_index_t *hi; + + /* Prepare for when we have a copy containing not-present nodes. */ + cukb.pool = scratch_pool; + cukb.session = NULL; /* ### Can we somehow reuse session? */ + cukb.repos_root_url = NULL; + cukb.ctx = ctx; + + SVN_ERR(svn_client__harvest_committables(&committables, lock_tokens, + base_dir_abspath, targets, + depth_empty_start, depth, + just_locked, + changelists, + check_url_kind, &cukb, + ctx, result_pool, scratch_pool)); + if (apr_hash_count(committables->by_repository) == 0) + { + *commit_items_p = NULL; + return SVN_NO_ERROR; /* Nothing to do */ + } + else if (apr_hash_count(committables->by_repository) > 1) + { + return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Commit can only commit to a single repository at a time.\n" + "Are all targets part of the same working copy?")); + } + + hi = apr_hash_first(scratch_pool, committables->by_repository); + *commit_items_p = apr_hash_this_val(hi); + if (committables_by_path_p) + *committables_by_path_p = committables->by_path; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__wc_replay(const char *src_wc_abspath, + const apr_array_header_t *targets, + svn_depth_t depth, + const apr_array_header_t *changelists, + const svn_delta_editor_t *editor, + void *edit_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + const char *base_abspath; + apr_array_header_t *rel_targets; + apr_hash_t *lock_tokens; + apr_array_header_t *commit_items; + svn_client__pathrev_t *base; + const char *base_url; + svn_wc_notify_func2_t saved_notify_func; + void *saved_notify_baton; + + /* Condense the target list. This makes all targets absolute. */ + SVN_ERR(svn_dirent_condense_targets(&base_abspath, &rel_targets, targets, + FALSE, pool, pool)); + + /* No targets means nothing to commit, so just return. */ + if (base_abspath == NULL) + return SVN_NO_ERROR; + + SVN_ERR_ASSERT(rel_targets != NULL); + + /* If we calculated only a base and no relative targets, this + must mean that we are being asked to commit (effectively) a + single path. */ + if (rel_targets->nelts == 0) + APR_ARRAY_PUSH(rel_targets, const char *) = ""; + + /* Crawl the working copy for commit items. */ + SVN_ERR(harvest_committables(&commit_items, NULL /*committables_by_path_p*/, + &lock_tokens, + base_abspath, rel_targets, + -1 /*depth_empty_start*/, + depth, + FALSE /*just_locked*/, + changelists, + ctx, pool, pool)); + if (!commit_items) + { + return SVN_NO_ERROR; + } + + SVN_ERR(svn_client__wc_node_get_base(&base, + src_wc_abspath, ctx->wc_ctx, pool, pool)); + base_url = base->url; + /* Sort our COMMIT_ITEMS by URL and find their relative URL-paths. */ + SVN_ERR(svn_client__condense_commit_items2(base_url, commit_items, pool)); + + saved_notify_func = ctx->notify_func2; + saved_notify_baton = ctx->notify_baton2; + ctx->notify_func2 = notify_func; + ctx->notify_baton2 = notify_baton; + /* BASE_URL is only used here in notifications & errors */ + SVN_ERR(svn_client__do_commit(base_url, commit_items, + editor, edit_baton, + NULL /*notify_prefix*/, NULL /*sha1_checksums*/, + ctx, pool, pool)); + ctx->notify_func2 = saved_notify_func; + ctx->notify_baton2 = saved_notify_baton; + return SVN_NO_ERROR; +} + svn_error_t * svn_client_commit6(const apr_array_header_t *targets, svn_depth_t depth, @@ -525,7 +648,7 @@ svn_client_commit6(const apr_array_header_t *targets, apr_array_header_t *rel_targets; apr_array_header_t *lock_targets; apr_array_header_t *locks_obtained; - svn_client__committables_t *committables; + apr_hash_t *committables_by_path; apr_hash_t *lock_tokens; apr_hash_t *sha1_checksums; apr_array_header_t *commit_items; @@ -615,55 +738,27 @@ svn_client_commit6(const apr_array_header_t *targets, pool); /* Crawl the working copy for commit items. */ - { - struct check_url_kind_baton cukb; - - /* Prepare for when we have a copy containing not-present nodes. */ - cukb.pool = iterpool; - cukb.session = NULL; /* ### Can we somehow reuse session? */ - cukb.repos_root_url = NULL; - cukb.ctx = ctx; - - cmt_err = svn_error_trace( - svn_client__harvest_committables(&committables, - &lock_tokens, - base_abspath, - rel_targets, - depth_empty_after, - depth, - ! keep_locks, - changelists, - check_url_kind, - &cukb, - ctx, - pool, - iterpool)); - - svn_pool_clear(iterpool); - } + cmt_err = svn_error_trace( + harvest_committables(&commit_items, &committables_by_path, + &lock_tokens, + base_abspath, + rel_targets, + depth_empty_after, + depth, + ! keep_locks, + changelists, + ctx, + pool, + iterpool)); + svn_pool_clear(iterpool); if (cmt_err) goto cleanup; - if (apr_hash_count(committables->by_repository) == 0) + if (!commit_items) { goto cleanup; /* Nothing to do */ } - else if (apr_hash_count(committables->by_repository) > 1) - { - cmt_err = svn_error_create( - SVN_ERR_UNSUPPORTED_FEATURE, NULL, - _("Commit can only commit to a single repository at a time.\n" - "Are all targets part of the same working copy?")); - goto cleanup; - } - - { - apr_hash_index_t *hi = apr_hash_first(iterpool, - committables->by_repository); - - commit_items = apr_hash_this_val(hi); - } /* If our array of targets contains only locks (and no actual file or prop modifications), then we return here to avoid committing a @@ -713,7 +808,7 @@ svn_client_commit6(const apr_array_header_t *targets, if (moved_from_abspath && delete_op_root_abspath) { svn_client_commit_item3_t *delete_half = - svn_hash_gets(committables->by_path, delete_op_root_abspath); + svn_hash_gets(committables_by_path, delete_op_root_abspath); if (!delete_half) { @@ -769,7 +864,7 @@ svn_client_commit6(const apr_array_header_t *targets, if (moved_to_abspath && copy_op_root_abspath && strcmp(moved_to_abspath, copy_op_root_abspath) == 0 && - svn_hash_gets(committables->by_path, copy_op_root_abspath) + svn_hash_gets(committables_by_path, copy_op_root_abspath) == NULL) { cmt_err = svn_error_createf( diff --git a/subversion/libsvn_client/commit_util.c b/subversion/libsvn_client/commit_util.c index 1f3d877..2be3ecd 100644 --- a/subversion/libsvn_client/commit_util.c +++ b/subversion/libsvn_client/commit_util.c @@ -1392,6 +1392,29 @@ sort_commit_item_urls(const void *a, const void *b) } +svn_error_t * +svn_client__condense_commit_items2(const char *base_url, + apr_array_header_t *commit_items, + apr_pool_t *pool) +{ + apr_array_header_t *ci = commit_items; /* convenience */ + int i; + + /* Sort our commit items by their URLs. */ + svn_sort__array(ci, sort_commit_item_urls); + + /* Hack BASE_URL off each URL; store the result as session_relpath. */ + for (i = 0; i < ci->nelts; i++) + { + svn_client_commit_item3_t *this_item + = APR_ARRAY_IDX(ci, i, svn_client_commit_item3_t *); + + this_item->session_relpath = svn_uri_skip_ancestor(base_url, + this_item->url, pool); + } + + return SVN_NO_ERROR; +} svn_error_t * svn_client__condense_commit_items(const char **base_url, @@ -1501,8 +1524,6 @@ struct file_mod_t /* A baton for use while driving a path-based editor driver for commit */ struct item_commit_baton { - const svn_delta_editor_t *editor; /* commit editor */ - void *edit_baton; /* commit editor's baton */ apr_hash_t *file_mods; /* hash: path->file_mod_t */ const char *notify_path_prefix; /* notification path prefix (NULL is okay, else abs path) */ @@ -1524,6 +1545,8 @@ struct item_commit_baton * This implements svn_delta_path_driver_cb_func_t. */ static svn_error_t * do_item_commit(void **dir_baton, + const svn_delta_editor_t *editor, + void *edit_baton, void *parent_baton, void *callback_baton, const char *path, @@ -1535,7 +1558,6 @@ do_item_commit(void **dir_baton, svn_node_kind_t kind = item->kind; void *file_baton = NULL; apr_pool_t *file_pool = NULL; - const svn_delta_editor_t *editor = icb->editor; apr_hash_t *file_mods = icb->file_mods; svn_client_ctx_t *ctx = icb->ctx; svn_error_t *err; @@ -1737,7 +1759,7 @@ do_item_commit(void **dir_baton, { if (! parent_baton) { - err = editor->open_root(icb->edit_baton, item->revision, + err = editor->open_root(edit_baton, item->revision, pool, dir_baton); } else @@ -1871,8 +1893,6 @@ svn_client__do_commit(const char *base_url, } /* Setup the callback baton. */ - cb_baton.editor = editor; - cb_baton.edit_baton = edit_baton; cb_baton.file_mods = file_mods; cb_baton.notify_path_prefix = notify_path_prefix; cb_baton.ctx = ctx; @@ -1880,7 +1900,7 @@ svn_client__do_commit(const char *base_url, cb_baton.base_url = base_url; /* Drive the commit editor! */ - SVN_ERR(svn_delta_path_driver2(editor, edit_baton, paths, TRUE, + SVN_ERR(svn_delta_path_driver3(editor, edit_baton, paths, TRUE, do_item_commit, &cb_baton, scratch_pool)); /* Transmit outstanding text deltas. */ diff --git a/subversion/libsvn_client/conflicts.c b/subversion/libsvn_client/conflicts.c index d1a2ae2..920c55e 100644 --- a/subversion/libsvn_client/conflicts.c +++ b/subversion/libsvn_client/conflicts.c @@ -255,7 +255,7 @@ struct repos_move_info { /* The revision in which this move was committed. */ svn_revnum_t rev; - /* The author who commited the revision in which this move was committed. */ + /* The author who committed the revision in which this move was committed. */ const char *rev_author; /* The repository relpath the node was moved from in this revision. */ @@ -2350,6 +2350,8 @@ struct conflict_tree_local_missing_details /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */ svn_revnum_t deleted_rev; + /* ### Add 'added_rev', like in conflict_tree_incoming_delete_details? */ + /* Author who committed DELETED_REV. */ const char *deleted_rev_author; @@ -2357,21 +2359,49 @@ struct conflict_tree_local_missing_details const char *deleted_repos_relpath; /* Move information about the conflict victim. If not NULL, this is an - * array of repos_move_info elements. Each element is the head of a - * move chain which starts in DELETED_REV. */ + * array of 'struct repos_move_info *' elements. Each element is the + * head of a move chain which starts in DELETED_REV. */ apr_array_header_t *moves; + /* If moves is not NULL, a map of repos_relpaths and working copy nodes. + * + * Each key is a "const char *" repository relpath corresponding to a + * possible repository-side move destination node in the revision which + * is the merge-right revision in case of a merge. + * + * Each value is an apr_array_header_t *. + * Each array consists of "const char *" absolute paths to working copy + * nodes which correspond to the repository node selected by the map key. + * Each such working copy node is a potential local move target which can + * be chosen to find a suitable merge target when resolving a tree conflict. + * + * This may be an empty hash map in case if there is no move target path + * in the working copy. */ + apr_hash_t *wc_move_targets; + + /* If not NULL, the preferred move target repository relpath. This is our key + * into the WC_MOVE_TARGETS map above (can be overridden by the user). */ + const char *move_target_repos_relpath; + + /* The current index into the list of working copy nodes corresponding to + * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */ + int wc_move_target_idx; + /* Move information about siblings. Siblings are nodes which share * a youngest common ancestor with the conflict victim. E.g. in case * of a merge operation they are part of the merge source branch. - * If not NULL, this is an array of repos_move_info elements. + * If not NULL, this is an array of 'struct repos_move_info *' elements. * Each element is the head of a move chain, which starts at some * point in history after siblings and conflict victim forked off * their common ancestor. */ apr_array_header_t *sibling_moves; - /* If not NULL, this is the move target abspath. */ - const char *moved_to_abspath; + /* List of nodes in the WC which are suitable merge targets for changes + * merged from any moved sibling. Array elements are 'const char *' + * absolute paths of working copy nodes. This array contains multiple + * elements only if ambiguous matches were found in the WC. */ + apr_array_header_t *wc_siblings; + int preferred_sibling_idx; }; static svn_error_t * @@ -2590,6 +2620,168 @@ find_moves_in_natural_history(apr_array_header_t **moves, return SVN_NO_ERROR; } +static svn_error_t * +collect_sibling_move_candidates(apr_array_header_t *candidates, + const char *victim_abspath, + svn_node_kind_t victim_kind, + struct repos_move_info *move, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *basename; + apr_array_header_t *abspaths; + int i; + + basename = svn_relpath_basename(move->moved_from_repos_relpath, scratch_pool); + SVN_ERR(svn_wc__find_working_nodes_with_basename(&abspaths, victim_abspath, + basename, victim_kind, + ctx->wc_ctx, result_pool, + scratch_pool)); + apr_array_cat(candidates, abspaths); + + if (move->next) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < move->next->nelts; i++) + { + struct repos_move_info *next_move; + next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); + SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath, + victim_kind, next_move, ctx, + result_pool, iterpool)); + svn_pool_clear(iterpool); + } + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + +/* Follow each move chain starting a MOVE all the way to the end to find + * the possible working copy locations for VICTIM_ABSPATH which corresponds + * to VICTIM_REPOS_REPLATH@VICTIM_REVISION. + * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the + * repos_relpath which is the corresponding move destination in the repository. + * This function is recursive. */ +static svn_error_t * +follow_move_chains(apr_hash_t *wc_move_targets, + struct repos_move_info *move, + svn_client_ctx_t *ctx, + const char *victim_abspath, + svn_node_kind_t victim_node_kind, + const char *victim_repos_relpath, + svn_revnum_t victim_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *candidate_abspaths; + + /* Gather candidate nodes which represent this moved_to_repos_relpath. */ + SVN_ERR(svn_wc__guess_incoming_move_target_nodes( + &candidate_abspaths, ctx->wc_ctx, + victim_abspath, victim_node_kind, + move->moved_to_repos_relpath, + scratch_pool, scratch_pool)); + + if (candidate_abspaths->nelts > 0) + { + apr_array_header_t *moved_to_abspaths; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + moved_to_abspaths = apr_array_make(result_pool, 1, + sizeof (const char *)); + + for (i = 0; i < candidate_abspaths->nelts; i++) + { + const char *candidate_abspath; + const char *repos_root_url; + const char *repos_uuid; + const char *candidate_repos_relpath; + svn_revnum_t candidate_revision; + + svn_pool_clear(iterpool); + + candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i, + const char *); + SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, + &candidate_repos_relpath, + &repos_root_url, + &repos_uuid, + NULL, NULL, + ctx->wc_ctx, + candidate_abspath, + FALSE, + iterpool, iterpool)); + + if (candidate_revision == SVN_INVALID_REVNUM) + continue; + + /* If the conflict victim and the move target candidate + * are not from the same revision we must ensure that + * they are related. */ + if (candidate_revision != victim_revision) + { + svn_client__pathrev_t *yca_loc; + svn_error_t *err; + + err = find_yca(&yca_loc, victim_repos_relpath, + victim_revision, + candidate_repos_relpath, + candidate_revision, + repos_root_url, repos_uuid, + NULL, ctx, iterpool, iterpool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + yca_loc = NULL; + } + else + return svn_error_trace(err); + } + + if (yca_loc == NULL) + continue; + } + + APR_ARRAY_PUSH(moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, candidate_abspath); + } + svn_pool_destroy(iterpool); + + svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath, + moved_to_abspaths); + } + + if (move->next) + { + int i; + apr_pool_t *iterpool; + + /* Recurse into each of the possible move chains. */ + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < move->next->nelts; i++) + { + struct repos_move_info *next_move; + + svn_pool_clear(iterpool); + + next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); + SVN_ERR(follow_move_chains(wc_move_targets, next_move, + ctx, victim_abspath, victim_node_kind, + victim_repos_relpath, victim_revision, + result_pool, iterpool)); + + } + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + /* Implements tree_conflict_get_details_func_t. */ static svn_error_t * conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, @@ -2603,12 +2795,15 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, svn_revnum_t old_rev; svn_revnum_t new_rev; svn_revnum_t deleted_rev; + svn_node_kind_t old_kind; + svn_node_kind_t new_kind; const char *deleted_rev_author; svn_node_kind_t replacing_node_kind; const char *deleted_basename; struct conflict_tree_local_missing_details *details; apr_array_header_t *moves = NULL; apr_array_header_t *sibling_moves = NULL; + apr_array_header_t *wc_siblings = NULL; const char *related_repos_relpath; svn_revnum_t related_peg_rev; const char *repos_root_url; @@ -2619,10 +2814,10 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, svn_revnum_t end_rev; SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( - &old_repos_relpath, &old_rev, NULL, conflict, + &old_repos_relpath, &old_rev, &old_kind, conflict, scratch_pool, scratch_pool)); SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( - &new_repos_relpath, &new_rev, NULL, conflict, + &new_repos_relpath, &new_rev, &new_kind, conflict, scratch_pool, scratch_pool)); /* Scan the conflict victim's parent's log to find a revision which @@ -2638,6 +2833,11 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, scratch_pool, scratch_pool)); + /* If the parent is not part of the repository-side tree checked out + * into this working copy, then bail. We do not support this case yet. */ + if (parent_peg_rev == SVN_INVALID_REVNUM) + return SVN_NO_ERROR; + /* Pick the younger incoming node as our 'related node' which helps * pin-pointing the deleted conflict victim in history. */ related_repos_relpath = @@ -2693,6 +2893,9 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, { const char *victim_abspath; svn_node_kind_t related_node_kind; + apr_array_header_t *candidates; + int i; + apr_pool_t *iterpool; /* ### The following describes all moves in terms of forward-merges, * should do we something else for reverse-merges? */ @@ -2726,7 +2929,81 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, if (sibling_moves == NULL) return SVN_NO_ERROR; - /* ## TODO: Find the missing node in the WC. */ + /* Find the missing node in the WC. In theory, this requires tracing + * back history of every node in the WC to check for a YCA with the + * conflict victim. This operation would obviously be quite expensive. + * + * However, assuming that the victim was not moved in the merge target, + * we can take a short-cut: The basename of the node cannot have changed, + * so we can limit history tracing to nodes with a matching basename. + * + * This approach solves the conflict case where an edit to a file which + * was moved on one branch is cherry-picked to another branch where the + * corresponding file has not been moved (yet). It does not solve move + * vs. move conflicts, but such conflicts are not yet supported by the + * resolver anyway and are hard to solve without server-side support. */ + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < sibling_moves->nelts; i++) + { + struct repos_move_info *move; + int j; + + svn_pool_clear(iterpool); + + move = APR_ARRAY_IDX(sibling_moves, i, struct repos_move_info *); + candidates = apr_array_make(iterpool, 1, sizeof(const char *)); + SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath, + old_rev < new_rev + ? new_kind : old_kind, + move, ctx, iterpool, + iterpool)); + + /* Determine whether a candidate node shares a YCA with the victim. */ + for (j = 0; j < candidates->nelts; j++) + { + const char *candidate_abspath; + const char *candidate_repos_relpath; + svn_revnum_t candidate_revision; + svn_error_t *err; + + candidate_abspath = APR_ARRAY_IDX(candidates, j, const char *); + SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, + &candidate_repos_relpath, + NULL, NULL, NULL, NULL, + ctx->wc_ctx, + candidate_abspath, + FALSE, + iterpool, iterpool)); + err = find_yca(&yca_loc, + old_rev < new_rev + ? new_repos_relpath : old_repos_relpath, + old_rev < new_rev ? new_rev : old_rev, + candidate_repos_relpath, + candidate_revision, + repos_root_url, repos_uuid, + NULL, ctx, iterpool, iterpool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + yca_loc = NULL; + } + else + return svn_error_trace(err); + } + + if (yca_loc) + { + if (wc_siblings == NULL) + wc_siblings = apr_array_make(conflict->pool, 1, + sizeof(const char *)); + APR_ARRAY_PUSH(wc_siblings, const char *) = + apr_pstrdup(conflict->pool, candidate_abspath); + } + } + } + svn_pool_destroy(iterpool); } details = apr_pcalloc(conflict->pool, sizeof(*details)); @@ -2737,8 +3014,83 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, deleted_basename, conflict->pool); details->moves = moves; - details->sibling_moves = sibling_moves; + if (details->moves != NULL) + { + apr_pool_t *iterpool; + int i; + + details->wc_move_targets = apr_hash_make(conflict->pool); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < details->moves->nelts; i++) + { + struct repos_move_info *move; + + svn_pool_clear(iterpool); + move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *); + SVN_ERR(follow_move_chains(details->wc_move_targets, move, ctx, + conflict->local_abspath, + new_kind, + new_repos_relpath, + new_rev, + scratch_pool, iterpool)); + } + svn_pool_destroy(iterpool); + + if (apr_hash_count(details->wc_move_targets) > 0) + { + apr_array_header_t *move_target_repos_relpaths; + const svn_sort__item_t *item; + + /* Initialize to the first possible move target. Hopefully, + * in most cases there will only be one candidate anyway. */ + move_target_repos_relpaths = svn_sort__hash( + details->wc_move_targets, + svn_sort_compare_items_as_paths, + scratch_pool); + item = &APR_ARRAY_IDX(move_target_repos_relpaths, + 0, svn_sort__item_t); + details->move_target_repos_relpath = item->key; + details->wc_move_target_idx = 0; + } + else + { + details->move_target_repos_relpath = NULL; + details->wc_move_target_idx = 0; + } + } + details->sibling_moves = sibling_moves; + details->wc_siblings = wc_siblings; + if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) == 1) + { + apr_array_header_t *wc_abspaths; + + wc_abspaths = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + if (wc_abspaths->nelts == 1) + { + svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind; + + if (kind == svn_node_file) + conflict->recommended_option_id = + svn_client_conflict_option_local_move_file_text_merge; + else if (kind == svn_node_dir) + conflict->recommended_option_id = + svn_client_conflict_option_local_move_dir_merge; + } + } + else if (details->wc_siblings && details->wc_siblings->nelts == 1) + { + svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind; + + if (kind == svn_node_file) + conflict->recommended_option_id = + svn_client_conflict_option_sibling_move_file_text_merge; + else if (kind == svn_node_dir) + conflict->recommended_option_id = + svn_client_conflict_option_sibling_move_dir_merge; + } + conflict->tree_conflict_local_details = details; return SVN_NO_ERROR; @@ -4647,133 +4999,6 @@ get_incoming_delete_details_for_reverse_addition( return SVN_NO_ERROR; } -/* Follow each move chain starting a MOVE all the way to the end to find - * the possible working copy locations for VICTIM_ABSPATH which corresponds - * to VICTIM_REPOS_REPLATH@VICTIM_REVISION. - * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the - * repos_relpath which is the corresponding move destination in the repository. - * This function is recursive. */ -static svn_error_t * -follow_move_chains(apr_hash_t *wc_move_targets, - struct repos_move_info *move, - svn_client_ctx_t *ctx, - const char *victim_abspath, - svn_node_kind_t victim_node_kind, - const char *victim_repos_relpath, - svn_revnum_t victim_revision, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - /* If this is the end of a move chain, look for matching paths in - * the working copy and add them to our collection if found. */ - if (move->next == NULL) - { - apr_array_header_t *candidate_abspaths; - - /* Gather candidate nodes which represent this moved_to_repos_relpath. */ - SVN_ERR(svn_wc__guess_incoming_move_target_nodes( - &candidate_abspaths, ctx->wc_ctx, - victim_abspath, victim_node_kind, - move->moved_to_repos_relpath, - scratch_pool, scratch_pool)); - if (candidate_abspaths->nelts > 0) - { - apr_array_header_t *moved_to_abspaths; - int i; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - - moved_to_abspaths = apr_array_make(result_pool, 1, - sizeof (const char *)); - - for (i = 0; i < candidate_abspaths->nelts; i++) - { - const char *candidate_abspath; - const char *repos_root_url; - const char *repos_uuid; - const char *candidate_repos_relpath; - svn_revnum_t candidate_revision; - - svn_pool_clear(iterpool); - - candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i, - const char *); - SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, - &candidate_repos_relpath, - &repos_root_url, - &repos_uuid, - NULL, NULL, - ctx->wc_ctx, - candidate_abspath, - FALSE, - iterpool, iterpool)); - - if (candidate_revision == SVN_INVALID_REVNUM) - continue; - - /* If the conflict victim and the move target candidate - * are not from the same revision we must ensure that - * they are related. */ - if (candidate_revision != victim_revision) - { - svn_client__pathrev_t *yca_loc; - svn_error_t *err; - - err = find_yca(&yca_loc, victim_repos_relpath, - victim_revision, - candidate_repos_relpath, - candidate_revision, - repos_root_url, repos_uuid, - NULL, ctx, iterpool, iterpool); - if (err) - { - if (err->apr_err == SVN_ERR_FS_NOT_FOUND) - { - svn_error_clear(err); - yca_loc = NULL; - } - else - return svn_error_trace(err); - } - - if (yca_loc == NULL) - continue; - } - - APR_ARRAY_PUSH(moved_to_abspaths, const char *) = - apr_pstrdup(result_pool, candidate_abspath); - } - svn_pool_destroy(iterpool); - - svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath, - moved_to_abspaths); - } - } - else - { - int i; - apr_pool_t *iterpool; - - /* Recurse into each of the possible move chains. */ - iterpool = svn_pool_create(scratch_pool); - for (i = 0; i < move->next->nelts; i++) - { - struct repos_move_info *next_move; - - svn_pool_clear(iterpool); - - next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); - SVN_ERR(follow_move_chains(wc_move_targets, next_move, - ctx, victim_abspath, victim_node_kind, - victim_repos_relpath, victim_revision, - result_pool, iterpool)); - - } - svn_pool_destroy(iterpool); - } - - return SVN_NO_ERROR; -} - static svn_error_t * init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, svn_client_conflict_t *conflict, @@ -4785,11 +5010,9 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, svn_node_kind_t victim_node_kind; const char *incoming_new_repos_relpath; svn_revnum_t incoming_new_pegrev; - svn_wc_operation_t operation; victim_abspath = svn_client_conflict_get_local_abspath(conflict); victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); - operation = svn_client_conflict_get_operation(conflict); /* ### Should we get the old location in case of reverse-merges? */ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( &incoming_new_repos_relpath, &incoming_new_pegrev, @@ -4815,11 +5038,8 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, get_moved_to_repos_relpath(details, scratch_pool); details->wc_move_target_idx = 0; - /* If only one move target exists after an update or switch, - * recommend a resolution option which follows the incoming move. */ - if (apr_hash_count(details->wc_move_targets) == 1 && - (operation == svn_wc_operation_update || - operation == svn_wc_operation_switch)) + /* If only one move target exists recommend a resolution option. */ + if (apr_hash_count(details->wc_move_targets) == 1) { apr_array_header_t *wc_abspaths; @@ -4832,7 +5052,10 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, /* Only one of these will be present for any given conflict. */ svn_client_conflict_option_incoming_move_file_text_merge, svn_client_conflict_option_incoming_move_dir_merge, - svn_client_conflict_option_local_move_file_text_merge + svn_client_conflict_option_local_move_file_text_merge, + svn_client_conflict_option_local_move_dir_merge, + svn_client_conflict_option_sibling_move_file_text_merge, + svn_client_conflict_option_sibling_move_dir_merge, }; apr_array_header_t *options; @@ -4889,6 +5112,7 @@ conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict, const char *parent_repos_relpath; svn_revnum_t parent_peg_rev; svn_revnum_t deleted_rev; + svn_revnum_t end_rev; const char *deleted_rev_author; svn_node_kind_t replacing_node_kind; apr_array_header_t *moves; @@ -4921,12 +5145,15 @@ conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict, related_peg_rev = SVN_INVALID_REVNUM; } + end_rev = (new_kind == svn_node_none ? 0 : old_rev); + if (end_rev >= parent_peg_rev) + end_rev = (parent_peg_rev > 0 ? parent_peg_rev - 1 : 0); + SVN_ERR(find_revision_for_suspected_deletion( &deleted_rev, &deleted_rev_author, &replacing_node_kind, &moves, conflict, svn_dirent_basename(conflict->local_abspath, scratch_pool), - parent_repos_relpath, parent_peg_rev, - new_kind == svn_node_none ? 0 : old_rev, + parent_repos_relpath, parent_peg_rev, end_rev, related_repos_relpath, related_peg_rev, ctx, conflict->pool, scratch_pool)); if (deleted_rev == SVN_INVALID_REVNUM) @@ -5952,8 +6179,11 @@ describe_incoming_edit_list_modified_revs(apr_array_header_t *edits, { if (i == edits->nelts - (max_revs_to_display / 2)) s = apr_psprintf(result_pool, - _("%s\n [%d revisions omitted for " - "brevity],\n"), + Q_("%s\n [%d revision omitted for " + "brevity],\n", + "%s\n [%d revisions omitted for " + "brevity],\n", + num_revs_to_skip), s, num_revs_to_skip); s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s, @@ -6725,8 +6955,9 @@ resolve_merge_incoming_added_file_text_update( /* Revert the path in order to restore the repository's line of * history, which is part of the BASE tree. This revert operation * is why are being careful about not losing the temporary copy. */ - err = svn_wc_revert5(ctx->wc_ctx, local_abspath, svn_depth_empty, + err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_empty, FALSE, NULL, TRUE, FALSE, + TRUE /*added_keep_local*/, NULL, NULL, /* no cancellation */ ctx->notify_func2, ctx->notify_baton2, scratch_pool); @@ -7543,7 +7774,6 @@ merge_newly_added_dir(const char *added_repos_relpath, diff_processor = processor; if (reverse_merge) diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, - NULL, scratch_pool); /* Filter the first path component using a filter processor, until we fixed @@ -7726,8 +7956,9 @@ resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option, * files with files from the repository is impossible because there is * no known merge base. No unversioned data will be lost, and any * differences to files in the repository will show up in 'svn diff'. */ - err = svn_wc_revert5(ctx->wc_ctx, local_abspath, svn_depth_infinity, + err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_infinity, FALSE, NULL, TRUE, TRUE /* metadata_only */, + TRUE /*added_keep_local*/, NULL, NULL, /* no cancellation */ ctx->notify_func2, ctx->notify_baton2, scratch_pool); @@ -7754,35 +7985,6 @@ resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option, return SVN_NO_ERROR; } -/* A baton for notification_adjust_func(). */ -struct notification_adjust_baton -{ - svn_wc_notify_func2_t inner_func; - void *inner_baton; - const char *checkout_abspath; - const char *final_abspath; -}; - -/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose - * baton is BATON->inner_baton) and adjusts the notification paths that - * start with BATON->checkout_abspath to start instead with - * BATON->final_abspath. */ -static void -notification_adjust_func(void *baton, - const svn_wc_notify_t *notify, - apr_pool_t *pool) -{ - struct notification_adjust_baton *nb = baton; - svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool); - const char *relpath; - - relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path); - inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool); - - if (nb->inner_func) - nb->inner_func(nb->inner_baton, inner_notify, pool); -} - /* Resolve a dir/dir "incoming add vs local obstruction" tree conflict by * replacing the local directory with the incoming directory. * If MERGE_DIRS is set, also merge the directories after replacing. */ @@ -7801,14 +8003,8 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option, svn_revnum_t incoming_new_pegrev; const char *local_abspath; const char *lock_abspath; - const char *tmpdir_abspath, *tmp_abspath; svn_error_t *err; - svn_revnum_t copy_src_revnum; - svn_opt_revision_t copy_src_peg_revision; svn_boolean_t timestamp_sleep; - svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2; - void *old_notify_baton2 = ctx->notify_baton2; - struct notification_adjust_baton nb; local_abspath = svn_client_conflict_get_local_abspath(conflict); @@ -7829,46 +8025,6 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option, if (corrected_url) url = corrected_url; - - /* Find a temporary location in which to check out the copy source. */ - SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, local_abspath, - scratch_pool, scratch_pool)); - - SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath, - svn_io_file_del_on_close, - scratch_pool, scratch_pool)); - - /* Make a new checkout of the requested source. While doing so, - * resolve copy_src_revnum to an actual revision number in case it - * was until now 'invalid' meaning 'head'. Ask this function not to - * sleep for timestamps, by passing a sleep_needed output param. - * Send notifications for all nodes except the root node, and adjust - * them to refer to the destination rather than this temporary path. */ - - nb.inner_func = ctx->notify_func2; - nb.inner_baton = ctx->notify_baton2; - nb.checkout_abspath = tmp_abspath; - nb.final_abspath = local_abspath; - ctx->notify_func2 = notification_adjust_func; - ctx->notify_baton2 = &nb; - - copy_src_peg_revision.kind = svn_opt_revision_number; - copy_src_peg_revision.value.number = incoming_new_pegrev; - - err = svn_client__checkout_internal(©_src_revnum, ×tamp_sleep, - url, tmp_abspath, - ©_src_peg_revision, - ©_src_peg_revision, - svn_depth_infinity, - TRUE, /* we want to ignore externals */ - FALSE, /* we don't allow obstructions */ - ra_session, ctx, scratch_pool); - - ctx->notify_func2 = old_notify_func2; - ctx->notify_baton2 = old_notify_baton2; - - SVN_ERR(err); - /* ### The following WC modifications should be atomic. */ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, ctx->wc_ctx, @@ -7885,31 +8041,11 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option, if (err) goto unlock_wc; - /* Schedule dst_path for addition in parent, with copy history. - Don't send any notification here. - Then remove the temporary checkout's .svn dir in preparation for - moving the rest of it into the final destination. */ - err = svn_wc_copy3(ctx->wc_ctx, tmp_abspath, local_abspath, - TRUE /* metadata_only */, - NULL, NULL, /* don't allow user to cancel here */ - NULL, NULL, scratch_pool); - if (err) - goto unlock_wc; - - err = svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath, - FALSE, scratch_pool, scratch_pool); - if (err) - goto unlock_wc; - err = svn_wc_remove_from_revision_control2(ctx->wc_ctx, - tmp_abspath, - FALSE, FALSE, - NULL, NULL, /* don't cancel */ - scratch_pool); - if (err) - goto unlock_wc; - - /* Move the temporary disk tree into place. */ - err = svn_io_file_rename2(tmp_abspath, local_abspath, FALSE, scratch_pool); + err = svn_client__repos_to_wc_copy_by_editor(×tamp_sleep, + svn_node_dir, + url, incoming_new_pegrev, + local_abspath, + ra_session, ctx, scratch_pool); if (err) goto unlock_wc; @@ -8346,7 +8482,9 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, apr_pool_t *scratch_pool) { svn_client_conflict_option_id_t option_id; - const char *local_abspath; + const char *victim_abspath; + const char *merge_source_abspath; + svn_wc_conflict_reason_t local_change; svn_wc_operation_t operation; const char *lock_abspath; svn_error_t *err; @@ -8372,7 +8510,8 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, const char *moved_to_abspath; const char *incoming_abspath = NULL; - local_abspath = svn_client_conflict_get_local_abspath(conflict); + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + local_change = svn_client_conflict_get_local_change(conflict); operation = svn_client_conflict_get_operation(conflict); details = conflict->tree_conflict_incoming_details; if (details == NULL || details->moves == NULL) @@ -8380,20 +8519,20 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, _("The specified conflict resolution option " "requires details for tree conflict at '%s' " "to be fetched from the repository first."), - svn_dirent_local_style(local_abspath, + svn_dirent_local_style(victim_abspath, scratch_pool)); if (operation == svn_wc_operation_none) return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, _("Invalid operation code '%d' recorded for " "conflict at '%s'"), operation, - svn_dirent_local_style(local_abspath, + svn_dirent_local_style(victim_abspath, scratch_pool)); option_id = svn_client_conflict_option_get_id(option); SVN_ERR_ASSERT(option_id == svn_client_conflict_option_incoming_move_file_text_merge || option_id == - svn_client_conflict_option_incoming_move_dir_merge); + svn_client_conflict_option_both_moved_file_move_merge); SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, conflict, scratch_pool, @@ -8408,7 +8547,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, scratch_pool)); /* Set up temporary storage for the common ancestor version of the file. */ - SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath, + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_stream_open_unique(&ancestor_stream, &ancestor_abspath, wc_tmpdir, @@ -8438,21 +8577,41 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, details->wc_move_target_idx, const char *); + if (local_change == svn_wc_conflict_reason_missing) + { + /* This is an incoming move vs local move conflict. + * Merge from the local move's target location to the + * incoming move's target location. */ + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *moves; + + local_details = conflict->tree_conflict_local_details; + moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + merge_source_abspath = + APR_ARRAY_IDX(moves, local_details->wc_move_target_idx, const char *); + } + else + merge_source_abspath = victim_abspath; + /* ### The following WC modifications should be atomic. */ SVN_ERR(svn_wc__acquire_write_lock_for_resolve( &lock_abspath, ctx->wc_ctx, - svn_dirent_get_longest_ancestor(local_abspath, + svn_dirent_get_longest_ancestor(victim_abspath, moved_to_abspath, scratch_pool), scratch_pool, scratch_pool)); - err = verify_local_state_for_incoming_delete(conflict, option, ctx, - scratch_pool); - if (err) - goto unlock_wc; + if (local_change != svn_wc_conflict_reason_missing) + { + err = verify_local_state_for_incoming_delete(conflict, option, ctx, + scratch_pool); + if (err) + goto unlock_wc; + } /* Get a copy of the conflict victim's properties. */ - err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, local_abspath, + err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, merge_source_abspath, scratch_pool, scratch_pool); if (err) goto unlock_wc; @@ -8506,7 +8665,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, * the conflict victim ('mine'), and the moved file ('theirs') which * was brought in by the update/switch operation and occupies the path * of the merge target. */ - err = svn_io_copy_file(local_abspath, moved_to_abspath, FALSE, + err = svn_io_copy_file(merge_source_abspath, moved_to_abspath, FALSE, scratch_pool); if (err) goto unlock_wc; @@ -8546,7 +8705,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool); if (err) goto unlock_wc; - err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath, + err = svn_wc__move2(ctx->wc_ctx, merge_source_abspath, moved_to_abspath, FALSE, /* ordinary (not meta-data only) move */ FALSE, /* mixed-revisions don't apply to files */ NULL, NULL, /* don't allow user to cancel here */ @@ -8604,19 +8763,27 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, operation == svn_wc_operation_switch) { /* Delete the tree conflict victim (clears the tree conflict marker). */ - err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE, + err = svn_wc_delete4(ctx->wc_ctx, victim_abspath, FALSE, FALSE, NULL, NULL, /* don't allow user to cancel here */ NULL, NULL, /* no extra notification */ scratch_pool); if (err) goto unlock_wc; } + else if (local_change == svn_wc_conflict_reason_missing) + { + /* Clear tree conflict marker. */ + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, + scratch_pool); + if (err) + goto unlock_wc; + } if (ctx->notify_func2) { svn_wc_notify_t *notify; - notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree, + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, scratch_pool); ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); } @@ -8637,6 +8804,521 @@ unlock_wc: return SVN_NO_ERROR; } +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by merging from the + * incoming move's target location to the local move's target location, + * overriding the incoming move. */ +static svn_error_t * +resolve_both_moved_file_text_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_option_id_t option_id; + const char *victim_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + const char *wc_tmpdir; + const char *ancestor_abspath; + svn_stream_t *ancestor_stream; + apr_hash_t *ancestor_props; + apr_hash_t *incoming_props; + apr_hash_t *local_props; + const char *ancestor_url; + const char *corrected_url; + svn_ra_session_t *ra_session; + svn_wc_merge_outcome_t merge_content_outcome; + svn_wc_notify_state_t merge_props_outcome; + apr_array_header_t *propdiffs; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *local_moves; + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first."), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + /* Set up temporary storage for the common ancestor version of the file. */ + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(&ancestor_stream, + &ancestor_abspath, wc_tmpdir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + /* Fetch the ancestor file's content. */ + ancestor_url = svn_path_url_add_component2(repos_root_url, + incoming_old_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, + ancestor_url, NULL, NULL, + FALSE, FALSE, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, + ancestor_stream, NULL, /* fetched_rev */ + &ancestor_props, scratch_pool)); + filter_props(ancestor_props, scratch_pool); + + /* Close stream to flush ancestor file to disk. */ + SVN_ERR(svn_stream_close(ancestor_stream)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Get a copy of the incoming moved item's properties. */ + err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx, + incoming_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Get a copy of the local move target's properties. */ + err = svn_wc_prop_list2(&local_props, ctx->wc_ctx, + local_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Create a property diff for the files. */ + err = svn_prop_diffs(&propdiffs, incoming_props, local_props, + scratch_pool); + if (err) + goto unlock_wc; + + /* Perform the file merge. */ + err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, + ctx->wc_ctx, ancestor_abspath, + incoming_moved_to_abspath, local_moved_to_abspath, + NULL, NULL, NULL, /* labels */ + NULL, NULL, /* conflict versions */ + FALSE, /* dry run */ + NULL, NULL, /* diff3_cmd, merge_options */ + apr_hash_count(ancestor_props) ? ancestor_props : NULL, + propdiffs, + NULL, NULL, /* conflict func/baton */ + NULL, NULL, /* don't allow user to cancel here */ + scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(local_moved_to_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (merge_content_outcome == svn_wc_merge_conflict) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->prop_state = merge_props_outcome; + notify->kind = svn_node_file; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + /* Revert local addition of the incoming move's target. */ + err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, + svn_depth_infinity, FALSE, NULL, TRUE, FALSE, + FALSE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by moving the locally moved + * directory to the incoming move target location, and then merging changes. */ +static svn_error_t * +resolve_both_moved_dir_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_option_id_t option_id; + const char *victim_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + const char *incoming_moved_repos_relpath; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *local_moves; + svn_client__conflict_report_t *conflict_report; + const char *incoming_old_url; + const char *incoming_moved_url; + svn_opt_revision_t incoming_old_opt_rev; + svn_opt_revision_t incoming_moved_opt_rev; + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + + "to be fetched from the repository first."), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_dir_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Perform the merge. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; + + incoming_moved_repos_relpath = + get_moved_to_repos_relpath(incoming_details, scratch_pool); + incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_moved_repos_relpath, SVN_VA_NULL); + incoming_moved_opt_rev.kind = svn_opt_revision_number; + incoming_moved_opt_rev.value.number = incoming_new_pegrev; + err = svn_client__merge_locked(&conflict_report, + incoming_old_url, &incoming_old_opt_rev, + incoming_moved_url, &incoming_moved_opt_rev, + local_moved_to_abspath, svn_depth_infinity, + TRUE, TRUE, /* do a no-ancestry merge */ + FALSE, FALSE, FALSE, + TRUE, /* Allow mixed-rev just in case, + * since conflict victims can't be + * updated to straighten out + * mixed-rev trees. */ + NULL, ctx, scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Revert local addition of the incoming move's target. */ + err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, + svn_depth_infinity, FALSE, NULL, TRUE, FALSE, + FALSE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by merging from the + * incoming move's target location to the local move's target location, + * overriding the incoming move. */ +static svn_error_t * +resolve_both_moved_dir_move_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_option_id_t option_id; + const char *victim_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *local_moves; + svn_client__conflict_report_t *conflict_report; + const char *incoming_old_url; + const char *incoming_moved_url; + svn_opt_revision_t incoming_old_opt_rev; + svn_opt_revision_t incoming_moved_opt_rev; + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + + "to be fetched from the repository first."), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == + svn_client_conflict_option_both_moved_dir_move_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Revert the incoming move target directory. */ + err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, + svn_depth_infinity, + FALSE, NULL, TRUE, FALSE, + TRUE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + /* The move operation is not part of natural history. We must replicate + * this move in our history. Record a move in the working copy. */ + err = svn_wc__move2(ctx->wc_ctx, local_moved_to_abspath, + incoming_moved_to_abspath, + FALSE, /* this is not a meta-data only move */ + TRUE, /* allow mixed-revisions just in case */ + NULL, NULL, /* don't allow user to cancel here */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + /* Merge INCOMING_OLD_URL@MERGE_LEFT->INCOMING_MOVED_URL@MERGE_RIGHT + * into the locally moved merge target. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; + + incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_details->move_target_repos_relpath, + SVN_VA_NULL); + incoming_moved_opt_rev.kind = svn_opt_revision_number; + incoming_moved_opt_rev.value.number = incoming_new_pegrev; + err = svn_client__merge_locked(&conflict_report, + incoming_old_url, &incoming_old_opt_rev, + incoming_moved_url, &incoming_moved_opt_rev, + incoming_moved_to_abspath, svn_depth_infinity, + TRUE, TRUE, /* do a no-ancestry merge */ + FALSE, FALSE, FALSE, + TRUE, /* Allow mixed-rev just in case, + * since conflict victims can't be + * updated to straighten out + * mixed-rev trees. */ + NULL, ctx, scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + /* Implements conflict_option_resolve_func_t. */ static svn_error_t * resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, @@ -8767,11 +9449,14 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, svn_opt_revision_t incoming_new_opt_rev; /* Revert the incoming move target directory. */ - SVN_ERR(svn_wc_revert5(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity, - FALSE, NULL, TRUE, FALSE, - NULL, NULL, /* no cancellation */ - ctx->notify_func2, ctx->notify_baton2, - scratch_pool)); + err = svn_wc_revert6(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity, + FALSE, NULL, TRUE, FALSE, + TRUE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; /* The move operation is not part of natural history. We must replicate * this move in our history. Record a move in the working copy. */ @@ -8866,7 +9551,9 @@ unlock_wc: return SVN_NO_ERROR; } -/* Implements conflict_option_resolve_func_t. */ +/* Implements conflict_option_resolve_func_t. + * Handles svn_client_conflict_option_local_move_file_text_merge + * and svn_client_conflict_option_sibling_move_file_text_merge. */ static svn_error_t * resolve_local_move_file_merge(svn_client_conflict_option_t *option, svn_client_conflict_t *conflict, @@ -8894,6 +9581,12 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, svn_wc_notify_state_t merge_props_outcome; apr_array_header_t *propdiffs; struct conflict_tree_local_missing_details *details; + const char *merge_target_abspath; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); details = conflict->tree_conflict_local_details; @@ -8909,8 +9602,31 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, NULL, conflict, scratch_pool, scratch_pool)); + if (details->wc_siblings) + { + merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *); + } + else if (details->wc_move_targets && details->move_target_repos_relpath) + { + apr_array_header_t *moves; + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + merge_target_abspath = APR_ARRAY_IDX(moves, details->wc_move_target_idx, + const char *); + } + else + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Corresponding working copy node not found " + "for '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor( + wcroot_abspath, conflict->local_abspath), + scratch_pool)); + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, - details->moved_to_abspath, + merge_target_abspath, scratch_pool, scratch_pool)); /* Fetch the common ancestor file's content. */ @@ -8955,7 +9671,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, SVN_ERR(svn_wc__acquire_write_lock_for_resolve( &lock_abspath, ctx->wc_ctx, svn_dirent_get_longest_ancestor(conflict->local_abspath, - details->moved_to_abspath, + merge_target_abspath, scratch_pool), scratch_pool, scratch_pool)); @@ -8963,7 +9679,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, ctx->wc_ctx, ancestor_tmp_abspath, incoming_tmp_abspath, - details->moved_to_abspath, + merge_target_abspath, NULL, NULL, NULL, /* labels */ NULL, NULL, /* conflict versions */ FALSE, /* dry run */ @@ -8973,7 +9689,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, NULL, NULL, /* conflict func/baton */ NULL, NULL, /* don't allow user to cancel here */ scratch_pool); - svn_io_sleep_for_timestamps(details->moved_to_abspath, scratch_pool); + svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool); if (err) return svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, @@ -8994,7 +9710,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, svn_wc_notify_t *notify; /* Tell the world about the file merge that just happened. */ - notify = svn_wc_create_notify(details->moved_to_abspath, + notify = svn_wc_create_notify(merge_target_abspath, svn_wc_notify_update_update, scratch_pool); if (merge_content_outcome == svn_wc_merge_conflict) @@ -9017,6 +9733,127 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, return SVN_NO_ERROR; } +/* Implements conflict_option_resolve_func_t. */ +static svn_error_t * +resolve_local_move_dir_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + struct conflict_tree_local_missing_details *details; + const char *merge_target_abspath; + const char *incoming_old_url; + const char *incoming_new_url; + svn_opt_revision_t incoming_old_opt_rev; + svn_opt_revision_t incoming_new_opt_rev; + svn_client__conflict_report_t *conflict_report; + + details = conflict->tree_conflict_local_details; + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + if (details->wc_move_targets) + { + apr_array_header_t *moves; + + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + merge_target_abspath = + APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *); + } + else + merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(conflict->local_abspath, + merge_target_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Resolve to current working copy state. + * svn_client__merge_locked() requires this. */ + err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath, + scratch_pool); + if (err) + goto unlock_wc; + + /* Merge outstanding changes to the merge target. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; + incoming_new_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_new_repos_relpath, SVN_VA_NULL); + incoming_new_opt_rev.kind = svn_opt_revision_number; + incoming_new_opt_rev.value.number = incoming_new_pegrev; + err = svn_client__merge_locked(&conflict_report, + incoming_old_url, &incoming_old_opt_rev, + incoming_new_url, &incoming_new_opt_rev, + merge_target_abspath, svn_depth_infinity, + TRUE, TRUE, /* do a no-ancestry merge */ + FALSE, FALSE, FALSE, + TRUE, /* Allow mixed-rev just in case, + * since conflict victims can't be + * updated to straighten out + * mixed-rev trees. */ + NULL, ctx, scratch_pool, scratch_pool); +unlock_wc: + svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool); + err = svn_error_compose_create(err, + svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + if (err) + return svn_error_trace(err); + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(merge_target_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (conflict_report) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->kind = svn_node_dir; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + + /* And also about the successfully resolved tree conflict. */ + notify = svn_wc_create_notify(conflict->local_abspath, + svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + conflict->resolution_tree = svn_client_conflict_option_get_id(option); + + return SVN_NO_ERROR; +} + static svn_error_t * assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) { @@ -9153,13 +9990,13 @@ svn_client_conflict_text_get_resolution_options(apr_array_header_t **options, add_resolution_option(*options, conflict, svn_client_conflict_option_incoming_text_where_conflicted, _("Accept incoming for conflicts"), - _("accept changes only where they conflict"), + _("accept incoming changes only where they conflict"), resolve_text_conflict); add_resolution_option(*options, conflict, svn_client_conflict_option_working_text_where_conflicted, _("Reject conflicts"), - _("reject changes which conflict and accept the rest"), + _("reject incoming changes which conflict and accept the rest"), resolve_text_conflict); add_resolution_option(*options, conflict, @@ -9771,7 +10608,7 @@ configure_option_incoming_delete_ignore(svn_client_conflict_t *conflict, is_local_move = (local_details != NULL && local_details->moves != NULL); - if (!is_incoming_move && !is_local_move) + if (is_incoming_move || is_local_move) return SVN_NO_ERROR; } @@ -9817,7 +10654,8 @@ configure_option_incoming_delete_accept(svn_client_conflict_t *conflict, incoming_details->moves != NULL); if (is_incoming_move && (local_change == svn_wc_conflict_reason_edited || - local_change == svn_wc_conflict_reason_moved_away)) + local_change == svn_wc_conflict_reason_moved_away || + local_change == svn_wc_conflict_reason_missing)) { /* An option which accepts the incoming deletion makes no sense * if we know there was a local move and/or an incoming move. */ @@ -9854,39 +10692,75 @@ describe_incoming_move_merge_conflict_option( const char **description, svn_client_conflict_t *conflict, svn_client_ctx_t *ctx, - struct conflict_tree_incoming_delete_details *details, + const char *moved_to_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - apr_array_header_t *move_target_wc_abspaths; svn_wc_operation_t operation; const char *victim_abspath; - const char *moved_to_abspath; + svn_node_kind_t victim_node_kind; const char *wcroot_abspath; - move_target_wc_abspaths = - svn_hash_gets(details->wc_move_targets, - get_moved_to_repos_relpath(details, scratch_pool)); - moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, - details->wc_move_target_idx, - const char *); - victim_abspath = svn_client_conflict_get_local_abspath(conflict); + victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, victim_abspath, scratch_pool, scratch_pool)); operation = svn_client_conflict_get_operation(conflict); if (operation == svn_wc_operation_merge) - *description = - apr_psprintf( - result_pool, _("move '%s' to '%s' and merge"), - svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, - victim_abspath), - scratch_pool), - svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, - moved_to_abspath), - scratch_pool)); + { + const char *incoming_moved_abspath = NULL; + + if (victim_node_kind == svn_node_none) + { + /* This is an incoming move vs local move conflict. */ + struct conflict_tree_incoming_delete_details *details; + + details = conflict->tree_conflict_incoming_details; + if (details->wc_move_targets) + { + apr_array_header_t *moves; + + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + incoming_moved_abspath = + APR_ARRAY_IDX(moves, details->wc_move_target_idx, + const char *); + } + } + + if (incoming_moved_abspath) + { + /* The 'move and merge' option follows the incoming move; note that + * moved_to_abspath points to the current location of an item which + * was moved in the history of our merge target branch. If the user + * chooses 'move and merge', that item will be moved again (i.e. it + * will be moved to and merged with incoming_moved_abspath's item). */ + *description = + apr_psprintf( + result_pool, _("move '%s' to '%s' and merge"), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + moved_to_abspath), + scratch_pool), + svn_dirent_local_style(svn_dirent_skip_ancestor( + wcroot_abspath, + incoming_moved_abspath), + scratch_pool)); + } + else + { + *description = + apr_psprintf( + result_pool, _("move '%s' to '%s' and merge"), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + victim_abspath), + scratch_pool), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + moved_to_abspath), + scratch_pool)); + } + } else *description = apr_psprintf( @@ -9918,6 +10792,7 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict, const char *incoming_new_repos_relpath; svn_revnum_t incoming_new_pegrev; svn_node_kind_t incoming_new_kind; + incoming_change = svn_client_conflict_get_incoming_change(conflict); local_change = svn_client_conflict_get_local_change(conflict); victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); @@ -9938,6 +10813,8 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict, { struct conflict_tree_incoming_delete_details *details; const char *description; + apr_array_header_t *move_target_wc_abspaths; + const char *moved_to_abspath; details = conflict->tree_conflict_incoming_details; if (details == NULL || details->moves == NULL) @@ -9946,9 +10823,15 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict, if (apr_hash_count(details->wc_move_targets) == 0) return SVN_NO_ERROR; + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, + details->wc_move_target_idx, + const char *); SVN_ERR(describe_incoming_move_merge_conflict_option(&description, conflict, ctx, - details, + moved_to_abspath, scratch_pool, scratch_pool)); add_resolution_option( @@ -9999,6 +10882,8 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict, { struct conflict_tree_incoming_delete_details *details; const char *description; + apr_array_header_t *move_target_wc_abspaths; + const char *moved_to_abspath; details = conflict->tree_conflict_incoming_details; if (details == NULL || details->moves == NULL) @@ -10007,9 +10892,15 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict, if (apr_hash_count(details->wc_move_targets) == 0) return SVN_NO_ERROR; + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, + details->wc_move_target_idx, + const char *); SVN_ERR(describe_incoming_move_merge_conflict_option(&description, conflict, ctx, - details, + moved_to_abspath, scratch_pool, scratch_pool)); add_resolution_option(options, conflict, @@ -10024,23 +10915,32 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict, /* Configure 'local move file merge' resolution option for * a tree conflict. */ static svn_error_t * -configure_option_local_move_file_merge(svn_client_conflict_t *conflict, - svn_client_ctx_t *ctx, - apr_array_header_t *options, - apr_pool_t *scratch_pool) +configure_option_local_move_file_or_dir_merge( + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) { svn_wc_operation_t operation; svn_wc_conflict_action_t incoming_change; svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; const char *incoming_new_repos_relpath; svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; operation = svn_client_conflict_get_operation(conflict); incoming_change = svn_client_conflict_get_incoming_change(conflict); local_change = svn_client_conflict_get_local_change(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( &incoming_new_repos_relpath, &incoming_new_pegrev, - NULL, conflict, scratch_pool, + &incoming_new_kind, conflict, scratch_pool, scratch_pool)); if (operation == svn_wc_operation_merge && @@ -10048,62 +10948,37 @@ configure_option_local_move_file_merge(svn_client_conflict_t *conflict, local_change == svn_wc_conflict_reason_missing) { struct conflict_tree_local_missing_details *details; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, + scratch_pool, scratch_pool)); details = conflict->tree_conflict_local_details; - if (details != NULL && details->moves != NULL) + if (details != NULL && details->moves != NULL && + details->move_target_repos_relpath != NULL) { - apr_hash_t *wc_move_targets = apr_hash_make(scratch_pool); - apr_pool_t *iterpool; - int i; + apr_array_header_t *moves; + const char *moved_to_abspath; + const char *description; - iterpool = svn_pool_create(scratch_pool); - for (i = 0; i < details->moves->nelts; i++) - { - struct repos_move_info *move; + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + moved_to_abspath = + APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *); - svn_pool_clear(iterpool); - move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *); - SVN_ERR(follow_move_chains(wc_move_targets, move, ctx, - conflict->local_abspath, - svn_node_file, - incoming_new_repos_relpath, - incoming_new_pegrev, - scratch_pool, iterpool)); - } - svn_pool_destroy(iterpool); + description = + apr_psprintf( + scratch_pool, _("apply changes to move destination '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, moved_to_abspath), + scratch_pool)); - if (apr_hash_count(wc_move_targets) > 0) + if ((incoming_old_kind == svn_node_file || + incoming_old_kind == svn_node_none) && + (incoming_new_kind == svn_node_file || + incoming_new_kind == svn_node_none)) { - apr_array_header_t *move_target_repos_relpaths; - const svn_sort__item_t *item; - apr_array_header_t *moved_to_abspaths; - const char *description; - const char *wcroot_abspath; - - /* Initialize to the first possible move target. Hopefully, - * in most cases there will only be one candidate anyway. */ - move_target_repos_relpaths = svn_sort__hash( - wc_move_targets, - svn_sort_compare_items_as_paths, - scratch_pool); - item = &APR_ARRAY_IDX(move_target_repos_relpaths, - 0, svn_sort__item_t); - moved_to_abspaths = item->value; - details->moved_to_abspath = - apr_pstrdup(conflict->pool, - APR_ARRAY_IDX(moved_to_abspaths, 0, const char *)); - - SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, - conflict->local_abspath, - scratch_pool, scratch_pool)); - description = - apr_psprintf( - scratch_pool, _("apply changes to move destination '%s'"), - svn_dirent_local_style( - svn_dirent_skip_ancestor(wcroot_abspath, - details->moved_to_abspath), - scratch_pool)); - add_resolution_option( options, conflict, svn_client_conflict_option_local_move_file_text_merge, @@ -10111,50 +10986,943 @@ configure_option_local_move_file_merge(svn_client_conflict_t *conflict, description, resolve_local_move_file_merge); } else - details->moved_to_abspath = NULL; + { + add_resolution_option( + options, conflict, + svn_client_conflict_option_local_move_dir_merge, + _("Apply to move destination"), + description, resolve_local_move_dir_merge); + } } } return SVN_NO_ERROR; } -svn_error_t * -svn_client_conflict_option_get_moved_to_repos_relpath_candidates( - apr_array_header_t **possible_moved_to_repos_relpaths, +/* Configure 'sibling move file/dir merge' resolution option for + * a tree conflict. */ +static svn_error_t * +configure_option_sibling_move_merge(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; + + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + &incoming_new_kind, conflict, scratch_pool, + scratch_pool)); + + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, + scratch_pool, scratch_pool)); + + details = conflict->tree_conflict_local_details; + if (details != NULL && details->wc_siblings != NULL) + { + const char *description; + const char *sibling; + + sibling = + apr_pstrdup(conflict->pool, + APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *)); + description = + apr_psprintf( + scratch_pool, _("apply changes to '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, sibling), + scratch_pool)); + + if ((incoming_old_kind == svn_node_file || + incoming_old_kind == svn_node_none) && + (incoming_new_kind == svn_node_file || + incoming_new_kind == svn_node_none)) + { + add_resolution_option( + options, conflict, + svn_client_conflict_option_sibling_move_file_text_merge, + _("Apply to corresponding local location"), + description, resolve_local_move_file_merge); + } + else + { + add_resolution_option( + options, conflict, + svn_client_conflict_option_sibling_move_dir_merge, + _("Apply to corresponding local location"), + description, resolve_local_move_dir_merge); + } + } + } + + return SVN_NO_ERROR; +} + +struct conflict_tree_update_local_moved_away_details { + /* + * This array consists of "const char *" absolute paths to working copy + * nodes which are uncomitted copies and correspond to the repository path + * of the conflict victim. + * Each such working copy node is a potential local move target which can + * be chosen to find a suitable merge target when resolving a tree conflict. + * + * This may be an empty array in case if there is no move target path in + * the working copy. */ + apr_array_header_t *wc_move_targets; + + /* Current index into the list of working copy paths in WC_MOVE_TARGETS. */ + int preferred_move_target_idx; +}; + +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by merging from the + * incoming move's target location to the local move's target location, + * overriding the incoming move. The original local move was broken during + * update/switch, so overriding the incoming move involves recording a new + * move from the incoming move's target location to the local move's target + * location. */ +static svn_error_t * +resolve_both_moved_file_update_keep_local_move( svn_client_conflict_option_t *option, - apr_pool_t *result_pool, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { - svn_client_conflict_t *conflict = option->conflict; - struct conflict_tree_incoming_delete_details *details; + svn_client_conflict_option_id_t option_id; const char *victim_abspath; - apr_array_header_t *sorted_repos_relpaths; - int i; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + const char *wc_tmpdir; + const char *ancestor_abspath; + svn_stream_t *ancestor_stream; + apr_hash_t *ancestor_props; + apr_hash_t *incoming_props; + apr_hash_t *local_props; + const char *ancestor_url; + const char *corrected_url; + svn_ra_session_t *ra_session; + svn_wc_merge_outcome_t merge_content_outcome; + svn_wc_notify_state_t merge_props_outcome; + apr_array_header_t *propdiffs; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_update_local_moved_away_details *local_details; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge); + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first."), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + /* Set up temporary storage for the common ancestor version of the file. */ + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(&ancestor_stream, + &ancestor_abspath, wc_tmpdir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + /* Fetch the ancestor file's content. */ + ancestor_url = svn_path_url_add_component2(repos_root_url, + incoming_old_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, + ancestor_url, NULL, NULL, + FALSE, FALSE, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, + ancestor_stream, NULL, /* fetched_rev */ + &ancestor_props, scratch_pool)); + filter_props(ancestor_props, scratch_pool); + + /* Close stream to flush ancestor file to disk. */ + SVN_ERR(svn_stream_close(ancestor_stream)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moved_to_abspath = + APR_ARRAY_IDX(local_details->wc_move_targets, + local_details->preferred_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Get a copy of the incoming moved item's properties. */ + err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx, + incoming_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Get a copy of the local move target's properties. */ + err = svn_wc_prop_list2(&local_props, ctx->wc_ctx, + local_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Create a property diff for the files. */ + err = svn_prop_diffs(&propdiffs, incoming_props, local_props, + scratch_pool); + if (err) + goto unlock_wc; + + /* Perform the file merge. */ + err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, + ctx->wc_ctx, ancestor_abspath, + incoming_moved_to_abspath, local_moved_to_abspath, + NULL, NULL, NULL, /* labels */ + NULL, NULL, /* conflict versions */ + FALSE, /* dry run */ + NULL, NULL, /* diff3_cmd, merge_options */ + apr_hash_count(ancestor_props) ? ancestor_props : NULL, + propdiffs, + NULL, NULL, /* conflict func/baton */ + NULL, NULL, /* don't allow user to cancel here */ + scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(local_moved_to_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (merge_content_outcome == svn_wc_merge_conflict) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->prop_state = merge_props_outcome; + notify->kind = svn_node_file; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + /* Record a new move which overrides the incoming move. */ + err = svn_wc__move2(ctx->wc_ctx, incoming_moved_to_abspath, + local_moved_to_abspath, + TRUE, /* meta-data only move */ + FALSE, /* mixed-revisions don't apply to files */ + NULL, NULL, /* don't allow user to cancel here */ + NULL, NULL, /* no extra notification */ + scratch_pool); + if (err) + goto unlock_wc; + + /* Remove moved-away file from disk. */ + err = svn_io_remove_file2(incoming_moved_to_abspath, TRUE, scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by merging from the + * local move's target location to the incoming move's target location, + * and reverting the local move. */ +static svn_error_t * +resolve_both_moved_file_update_keep_incoming_move( + svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_option_id_t option_id; + const char *victim_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + const char *wc_tmpdir; + const char *ancestor_abspath; + svn_stream_t *ancestor_stream; + apr_hash_t *ancestor_props; + apr_hash_t *incoming_props; + apr_hash_t *local_props; + const char *ancestor_url; + const char *corrected_url; + svn_ra_session_t *ra_session; + svn_wc_merge_outcome_t merge_content_outcome; + svn_wc_notify_state_t merge_props_outcome; + apr_array_header_t *propdiffs; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_update_local_moved_away_details *local_details; victim_abspath = svn_client_conflict_get_local_abspath(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->wc_move_targets == NULL) + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Getting a list of possible move targets " + _("The specified conflict resolution option " "requires details for tree conflict at '%s' " - "to be fetched from the repository first"), + "to be fetched from the repository first."), svn_dirent_local_style(victim_abspath, scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == + svn_client_conflict_option_both_moved_file_move_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + /* Set up temporary storage for the common ancestor version of the file. */ + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(&ancestor_stream, + &ancestor_abspath, wc_tmpdir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + /* Fetch the ancestor file's content. */ + ancestor_url = svn_path_url_add_component2(repos_root_url, + incoming_old_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, + ancestor_url, NULL, NULL, + FALSE, FALSE, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, + ancestor_stream, NULL, /* fetched_rev */ + &ancestor_props, scratch_pool)); + filter_props(ancestor_props, scratch_pool); + + /* Close stream to flush ancestor file to disk. */ + SVN_ERR(svn_stream_close(ancestor_stream)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moved_to_abspath = + APR_ARRAY_IDX(local_details->wc_move_targets, + local_details->preferred_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Get a copy of the incoming moved item's properties. */ + err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx, + incoming_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Get a copy of the local move target's properties. */ + err = svn_wc_prop_list2(&local_props, ctx->wc_ctx, + local_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Create a property diff for the files. */ + err = svn_prop_diffs(&propdiffs, incoming_props, local_props, + scratch_pool); + if (err) + goto unlock_wc; + + /* Perform the file merge. */ + err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, + ctx->wc_ctx, ancestor_abspath, + local_moved_to_abspath, incoming_moved_to_abspath, + NULL, NULL, NULL, /* labels */ + NULL, NULL, /* conflict versions */ + FALSE, /* dry run */ + NULL, NULL, /* diff3_cmd, merge_options */ + apr_hash_count(ancestor_props) ? ancestor_props : NULL, + propdiffs, + NULL, NULL, /* conflict func/baton */ + NULL, NULL, /* don't allow user to cancel here */ + scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(local_moved_to_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (merge_content_outcome == svn_wc_merge_conflict) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->prop_state = merge_props_outcome; + notify->kind = svn_node_file; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + /* Revert the copy-half of the local move. The delete-half of this move + * has already been deleted during the update/switch operation. */ + err = svn_wc_revert6(ctx->wc_ctx, local_moved_to_abspath, svn_depth_empty, + FALSE, NULL, TRUE, FALSE, + TRUE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Implements tree_conflict_get_details_func_t. */ +static svn_error_t * +conflict_tree_get_details_update_local_moved_away( + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + struct conflict_tree_update_local_moved_away_details *details; + const char *incoming_old_repos_relpath; + svn_node_kind_t incoming_old_kind; + + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, NULL, &incoming_old_kind, + conflict, scratch_pool, scratch_pool)); + + details = apr_pcalloc(conflict->pool, sizeof(*details)); + + details->wc_move_targets = apr_array_make(conflict->pool, 1, + sizeof(const char *)); + + /* Search the WC for copies of the conflict victim. */ + SVN_ERR(svn_wc__find_copies_of_repos_path(&details->wc_move_targets, + conflict->local_abspath, + incoming_old_repos_relpath, + incoming_old_kind, + ctx->wc_ctx, + conflict->pool, + scratch_pool)); + + conflict->tree_conflict_local_details = details; + + return SVN_NO_ERROR; +} + +static svn_error_t * +get_both_moved_file_paths(const char **incoming_moved_to_abspath, + const char **local_moved_to_abspath, + svn_client_conflict_t *conflict, + apr_pool_t *scratch_pool) +{ + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *incoming_move_target_wc_abspaths; + svn_wc_operation_t operation; + + operation = svn_client_conflict_get_operation(conflict); - /* Return a copy of the repos replath candidate list. */ - sorted_repos_relpaths = svn_sort__hash(details->wc_move_targets, + *incoming_moved_to_abspath = NULL; + *local_moved_to_abspath = NULL; + + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL || + apr_hash_count(incoming_details->wc_move_targets) == 0) + return SVN_NO_ERROR; + + incoming_move_target_wc_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, + scratch_pool)); + *incoming_moved_to_abspath = + APR_ARRAY_IDX(incoming_move_target_wc_abspaths, + incoming_details->wc_move_target_idx, const char *); + + if (operation == svn_wc_operation_merge) + { + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *local_moves; + + local_details = conflict->tree_conflict_local_details; + if (local_details == NULL || + apr_hash_count(local_details->wc_move_targets) == 0) + return SVN_NO_ERROR; + + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + *local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, + const char *); + } + else + { + struct conflict_tree_update_local_moved_away_details *local_details; + + local_details = conflict->tree_conflict_local_details; + if (local_details == NULL || + local_details->wc_move_targets->nelts == 0) + return SVN_NO_ERROR; + + *local_moved_to_abspath = + APR_ARRAY_IDX(local_details->wc_move_targets, + local_details->preferred_move_target_idx, + const char *); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +conflict_tree_get_description_update_both_moved_file_merge( + const char **description, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *incoming_moved_to_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *wcroot_abspath; + + *description = NULL; + + SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath, + &local_moved_to_abspath, + conflict, scratch_pool)); + if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); + + operation = svn_client_conflict_get_operation(conflict); + + if (operation == svn_wc_operation_merge) + { + /* In case of a merge, the incoming move has A+ (copied) status... */ + *description = + apr_psprintf( + scratch_pool, + _("apply changes to '%s' and revert addition of '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), + scratch_pool), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), + scratch_pool)); + } + else + { + /* ...but in case of update/switch the local move has "A+" status. */ + *description = + apr_psprintf( + scratch_pool, + _("override incoming move and merge incoming changes from '%s' " + "to '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), + scratch_pool), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +conflict_tree_get_description_update_both_moved_file_move_merge( + const char **description, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *incoming_moved_to_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *wcroot_abspath; + + *description = NULL; + + SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath, + &local_moved_to_abspath, + conflict, scratch_pool)); + if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); + + operation = svn_client_conflict_get_operation(conflict); + + if (operation == svn_wc_operation_merge) + { + SVN_ERR(describe_incoming_move_merge_conflict_option( + description, conflict, ctx, local_moved_to_abspath, + scratch_pool, scratch_pool)); + } + else + { + *description = + apr_psprintf( + scratch_pool, + _("accept incoming move and merge local changes from " + "'%s' to '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), + scratch_pool), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Configure 'both moved file merge' resolution options for a tree conflict. */ +static svn_error_t * +configure_option_both_moved_file_merge(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + svn_wc_operation_t operation; + svn_node_kind_t victim_node_kind; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); + + operation = svn_client_conflict_get_operation(conflict); + victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + &incoming_new_kind, conflict, scratch_pool, + scratch_pool)); + + /* ### what about the switch operation? */ + if (((operation == svn_wc_operation_merge && + victim_node_kind == svn_node_none) || + (operation == svn_wc_operation_update && + victim_node_kind == svn_node_file)) && + incoming_old_kind == svn_node_file && + incoming_new_kind == svn_node_none && + ((operation == svn_wc_operation_merge && + local_change == svn_wc_conflict_reason_missing) || + (operation == svn_wc_operation_update && + local_change == svn_wc_conflict_reason_moved_away)) && + incoming_change == svn_wc_conflict_action_delete) + { + const char *description; + + SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge( + &description, conflict, ctx, conflict->pool, scratch_pool)); + + if (description == NULL) /* details not fetched yet */ + return SVN_NO_ERROR; + + add_resolution_option( + options, conflict, svn_client_conflict_option_both_moved_file_merge, + _("Merge to corresponding local location"), + description, + operation == svn_wc_operation_merge ? + resolve_both_moved_file_text_merge : + resolve_both_moved_file_update_keep_local_move); + + SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge( + &description, conflict, ctx, conflict->pool, scratch_pool)); + + add_resolution_option(options, conflict, + svn_client_conflict_option_both_moved_file_move_merge, + _("Move and merge"), description, + operation == svn_wc_operation_merge ? + resolve_incoming_move_file_text_merge : + resolve_both_moved_file_update_keep_incoming_move); + } + + return SVN_NO_ERROR; +} + +/* Configure 'both moved dir merge' resolution options for a tree conflict. */ +static svn_error_t * +configure_option_both_moved_dir_merge(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + svn_wc_operation_t operation; + svn_node_kind_t victim_node_kind; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); + + operation = svn_client_conflict_get_operation(conflict); + victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + &incoming_new_kind, conflict, scratch_pool, + scratch_pool)); + + if (operation == svn_wc_operation_merge && + victim_node_kind == svn_node_none && + incoming_old_kind == svn_node_dir && + incoming_new_kind == svn_node_none && + local_change == svn_wc_conflict_reason_missing && + incoming_change == svn_wc_conflict_action_delete) + { + struct conflict_tree_incoming_delete_details *incoming_details; + struct conflict_tree_local_missing_details *local_details; + const char *description; + apr_array_header_t *local_moves; + const char *local_moved_to_abspath; + const char *incoming_moved_to_abspath; + apr_array_header_t *incoming_move_target_wc_abspaths; + + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL || + apr_hash_count(incoming_details->wc_move_targets) == 0) + return SVN_NO_ERROR; + + local_details = conflict->tree_conflict_local_details; + if (local_details == NULL || + apr_hash_count(local_details->wc_move_targets) == 0) + return SVN_NO_ERROR; + + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, + const char *); + + incoming_move_target_wc_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, + scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(incoming_move_target_wc_abspaths, + incoming_details->wc_move_target_idx, const char *); + + description = + apr_psprintf( + scratch_pool, _("apply changes to '%s' and revert addition of '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), + scratch_pool), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), + scratch_pool)); + add_resolution_option( + options, conflict, svn_client_conflict_option_both_moved_dir_merge, + _("Merge to corresponding local location"), + description, resolve_both_moved_dir_merge); + + SVN_ERR(describe_incoming_move_merge_conflict_option( + &description, conflict, ctx, local_moved_to_abspath, + scratch_pool, scratch_pool)); + add_resolution_option(options, conflict, + svn_client_conflict_option_both_moved_dir_move_merge, + _("Move and merge"), description, + resolve_both_moved_dir_move_merge); + } + + return SVN_NO_ERROR; +} + +/* Return a copy of the repos replath candidate list. */ +static svn_error_t * +get_repos_relpath_candidates( + apr_array_header_t **possible_moved_to_repos_relpaths, + apr_hash_t *wc_move_targets, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *sorted_repos_relpaths; + int i; + + sorted_repos_relpaths = svn_sort__hash(wc_move_targets, svn_sort_compare_items_as_paths, scratch_pool); - *possible_moved_to_repos_relpaths = apr_array_make( - result_pool, - sorted_repos_relpaths->nelts, - sizeof (const char *)); + *possible_moved_to_repos_relpaths = + apr_array_make(result_pool, sorted_repos_relpaths->nelts, + sizeof (const char *)); for (i = 0; i < sorted_repos_relpaths->nelts; i++) { svn_sort__item_t item; @@ -10170,37 +11938,115 @@ svn_client_conflict_option_get_moved_to_repos_relpath_candidates( } svn_error_t * -svn_client_conflict_option_set_moved_to_repos_relpath( +svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( + apr_array_header_t **possible_moved_to_repos_relpaths, svn_client_conflict_option_t *option, - int preferred_move_target_idx, - svn_client_ctx_t *ctx, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_client_conflict_t *conflict = option->conflict; - struct conflict_tree_incoming_delete_details *details; const char *victim_abspath; + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + svn_client_conflict_option_id_t id; + + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge && + id != svn_client_conflict_option_both_moved_file_merge && + id != svn_client_conflict_option_both_moved_file_move_merge && + id != svn_client_conflict_option_both_moved_dir_merge && + id != svn_client_conflict_option_both_moved_dir_move_merge) + { + /* We cannot operate on this option. */ + *possible_moved_to_repos_relpaths = NULL; + return SVN_NO_ERROR; + } + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; + + details = conflict->tree_conflict_local_details; + if (details == NULL || + (details->wc_move_targets == NULL && details->wc_siblings == NULL)) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move targets " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + if (details->wc_move_targets) + SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths, + details->wc_move_targets, + result_pool, scratch_pool)); + else + *possible_moved_to_repos_relpaths = NULL; + } + else + { + struct conflict_tree_incoming_delete_details *details; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move targets " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths, + details->wc_move_targets, + result_pool, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_conflict_option_get_moved_to_repos_relpath_candidates( + apr_array_header_t **possible_moved_to_repos_relpaths, + svn_client_conflict_option_t *option, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( + possible_moved_to_repos_relpaths, option, result_pool, scratch_pool)); +} + +static svn_error_t * +set_wc_move_target(const char **new_hash_key, + apr_hash_t *wc_move_targets, + int preferred_move_target_idx, + const char *victim_abspath, + apr_pool_t *scratch_pool) +{ apr_array_header_t *move_target_repos_relpaths; svn_sort__item_t item; const char *move_target_repos_relpath; apr_hash_index_t *hi; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge); - - victim_abspath = svn_client_conflict_get_local_abspath(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->wc_move_targets == NULL) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Setting a move target requires details " - "for tree conflict at '%s' to be fetched " - "from the repository first"), - svn_dirent_local_style(victim_abspath, - scratch_pool)); - if (preferred_move_target_idx < 0 || - preferred_move_target_idx >= apr_hash_count(details->wc_move_targets)) + preferred_move_target_idx >= apr_hash_count(wc_move_targets)) return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, _("Index '%d' is out of bounds of the possible " "move target list for '%s'"), @@ -10209,15 +12055,14 @@ svn_client_conflict_option_set_moved_to_repos_relpath( scratch_pool)); /* Translate the index back into a hash table key. */ - move_target_repos_relpaths = - svn_sort__hash(details->wc_move_targets, - svn_sort_compare_items_as_paths, - scratch_pool); + move_target_repos_relpaths = svn_sort__hash(wc_move_targets, + svn_sort_compare_items_as_paths, + scratch_pool); item = APR_ARRAY_IDX(move_target_repos_relpaths, preferred_move_target_idx, svn_sort__item_t); move_target_repos_relpath = item.key; /* Find our copy of the hash key and remember the user's preference. */ - for (hi = apr_hash_first(scratch_pool, details->wc_move_targets); + for (hi = apr_hash_first(scratch_pool, wc_move_targets); hi != NULL; hi = apr_hash_next(hi)) { @@ -10225,16 +12070,7 @@ svn_client_conflict_option_set_moved_to_repos_relpath( if (strcmp(move_target_repos_relpath, repos_relpath) == 0) { - details->move_target_repos_relpath = repos_relpath; - details->wc_move_target_idx = 0; - /* Update option description. */ - SVN_ERR(describe_incoming_move_merge_conflict_option( - &option->description, - conflict, ctx, - details, - conflict->pool, - scratch_pool)); - + *new_hash_key = repos_relpath; return SVN_NO_ERROR; } } @@ -10248,107 +12084,501 @@ svn_client_conflict_option_set_moved_to_repos_relpath( } svn_error_t * -svn_client_conflict_option_get_moved_to_abspath_candidates( +svn_client_conflict_option_set_moved_to_repos_relpath2( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_t *conflict = option->conflict; + const char *victim_abspath; + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + svn_client_conflict_option_id_t id; + + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge && + id != svn_client_conflict_option_both_moved_file_merge && + id != svn_client_conflict_option_both_moved_file_move_merge && + id != svn_client_conflict_option_both_moved_dir_merge && + id != svn_client_conflict_option_both_moved_dir_move_merge) + return SVN_NO_ERROR; /* We cannot operate on this option. Nothing to do. */ + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; + + details = conflict->tree_conflict_local_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath, + details->wc_move_targets, + preferred_move_target_idx, + victim_abspath, scratch_pool)); + details->wc_move_target_idx = 0; + + /* Update option description. */ + SVN_ERR(conflict_tree_get_description_local_missing( + &option->description, conflict, ctx, + conflict->pool, scratch_pool)); + } + else + { + struct conflict_tree_incoming_delete_details *details; + apr_array_header_t *move_target_wc_abspaths; + const char *moved_to_abspath; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath, + details->wc_move_targets, + preferred_move_target_idx, + victim_abspath, scratch_pool)); + details->wc_move_target_idx = 0; + + /* Update option description. */ + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, + details->wc_move_target_idx, + const char *); + SVN_ERR(describe_incoming_move_merge_conflict_option( + &option->description, + conflict, ctx, + moved_to_abspath, + conflict->pool, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_conflict_option_set_moved_to_repos_relpath( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_set_moved_to_repos_relpath2(option, + preferred_move_target_idx, ctx, scratch_pool)); +} + +svn_error_t * +svn_client_conflict_option_get_moved_to_abspath_candidates2( apr_array_header_t **possible_moved_to_abspaths, svn_client_conflict_option_t *option, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_client_conflict_t *conflict = option->conflict; - struct conflict_tree_incoming_delete_details *details; const char *victim_abspath; - apr_array_header_t *move_target_wc_abspaths; + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; int i; + svn_client_conflict_option_id_t id; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge); + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge && + id != svn_client_conflict_option_both_moved_file_merge && + id != svn_client_conflict_option_both_moved_file_move_merge && + id != svn_client_conflict_option_both_moved_dir_merge && + id != svn_client_conflict_option_both_moved_dir_move_merge) + { + /* We cannot operate on this option. */ + *possible_moved_to_abspaths = NULL; + return NULL; + } victim_abspath = svn_client_conflict_get_local_abspath(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->wc_move_targets == NULL) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Getting a list of possible move targets " - "requires details for tree conflict at '%s' " - "to be fetched from the repository first"), - svn_dirent_local_style(victim_abspath, - scratch_pool)); + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); - move_target_wc_abspaths = - svn_hash_gets(details->wc_move_targets, - get_moved_to_repos_relpath(details, scratch_pool)); + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; - /* Return a copy of the option's move target candidate list. */ - *possible_moved_to_abspaths = - apr_array_make(result_pool, move_target_wc_abspaths->nelts, - sizeof (const char *)); - for (i = 0; i < move_target_wc_abspaths->nelts; i++) + details = conflict->tree_conflict_local_details; + if (details == NULL || + (details->wc_move_targets == NULL && details->wc_siblings == NULL)) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move siblings " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + *possible_moved_to_abspaths = apr_array_make(result_pool, 1, + sizeof (const char *)); + if (details->wc_move_targets) + { + apr_array_header_t *move_target_wc_abspaths; + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + for (i = 0; i < move_target_wc_abspaths->nelts; i++) + { + const char *moved_to_abspath; + + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i, + const char *); + APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, moved_to_abspath); + } + } + + /* ### Siblings are actually 'corresponding nodes', not 'move targets'. + ### But we provide them here to avoid another API function. */ + if (details->wc_siblings) + { + for (i = 0; i < details->wc_siblings->nelts; i++) + { + const char *sibling_abspath; + + sibling_abspath = APR_ARRAY_IDX(details->wc_siblings, i, + const char *); + APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, sibling_abspath); + } + } + } + else if ((operation == svn_wc_operation_update || + operation == svn_wc_operation_switch) && + incoming_change == svn_wc_conflict_action_delete && + local_change == svn_wc_conflict_reason_moved_away) { - const char *moved_to_abspath; + struct conflict_tree_update_local_moved_away_details *details; - moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i, - const char *); - APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = - apr_pstrdup(result_pool, moved_to_abspath); + details = conflict->tree_conflict_local_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move targets " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + /* Return a copy of the option's move target candidate list. */ + *possible_moved_to_abspaths = + apr_array_make(result_pool, details->wc_move_targets->nelts, + sizeof (const char *)); + for (i = 0; i < details->wc_move_targets->nelts; i++) + { + const char *moved_to_abspath; + + moved_to_abspath = APR_ARRAY_IDX(details->wc_move_targets, i, + const char *); + APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, moved_to_abspath); + } + } + else + { + struct conflict_tree_incoming_delete_details *details; + apr_array_header_t *move_target_wc_abspaths; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move targets " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + + /* Return a copy of the option's move target candidate list. */ + *possible_moved_to_abspaths = + apr_array_make(result_pool, move_target_wc_abspaths->nelts, + sizeof (const char *)); + for (i = 0; i < move_target_wc_abspaths->nelts; i++) + { + const char *moved_to_abspath; + + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i, + const char *); + APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, moved_to_abspath); + } } return SVN_NO_ERROR; } svn_error_t * -svn_client_conflict_option_set_moved_to_abspath( +svn_client_conflict_option_get_moved_to_abspath_candidates( + apr_array_header_t **possible_moved_to_abspaths, + svn_client_conflict_option_t *option, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_get_moved_to_abspath_candidates2( + possible_moved_to_abspaths, option, result_pool, scratch_pool)); +} + +svn_error_t * +svn_client_conflict_option_set_moved_to_abspath2( svn_client_conflict_option_t *option, int preferred_move_target_idx, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_client_conflict_t *conflict = option->conflict; - struct conflict_tree_incoming_delete_details *details; const char *victim_abspath; - apr_array_header_t *move_target_wc_abspaths; + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + svn_client_conflict_option_id_t id; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge); + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge && + id != svn_client_conflict_option_both_moved_file_merge && + id != svn_client_conflict_option_both_moved_file_move_merge && + id != svn_client_conflict_option_both_moved_dir_merge && + id != svn_client_conflict_option_both_moved_dir_move_merge) + return NULL; /* We cannot operate on this option. Nothing to do. */ victim_abspath = svn_client_conflict_get_local_abspath(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->wc_move_targets == NULL) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Setting a move target requires details " - "for tree conflict at '%s' to be fetched " - "from the repository first"), - svn_dirent_local_style(victim_abspath, - scratch_pool)); + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); - move_target_wc_abspaths = - svn_hash_gets(details->wc_move_targets, - get_moved_to_repos_relpath(details, scratch_pool)); + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; + const char *wcroot_abspath; + const char *preferred_sibling; - if (preferred_move_target_idx < 0 || - preferred_move_target_idx > move_target_wc_abspaths->nelts) - return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, - _("Index '%d' is out of bounds of the possible " - "move target list for '%s'"), - preferred_move_target_idx, - svn_dirent_local_style(victim_abspath, - scratch_pool)); + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, + ctx->wc_ctx, + conflict->local_abspath, + scratch_pool, + scratch_pool)); + + details = conflict->tree_conflict_local_details; + if (details == NULL || (details->wc_siblings == NULL && + details->wc_move_targets == NULL)) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); - /* Record the user's preference. */ - details->wc_move_target_idx = preferred_move_target_idx; + if (details->wc_siblings) + { + if (preferred_move_target_idx < 0 || + preferred_move_target_idx > details->wc_siblings->nelts) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Index '%d' is out of bounds of the " + "possible move sibling list for '%s'"), + preferred_move_target_idx, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + /* Record the user's preference. */ + details->preferred_sibling_idx = preferred_move_target_idx; - /* Update option description. */ - SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description, - conflict, ctx, - details, - conflict->pool, + /* Update option description. */ + preferred_sibling = APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *); + option->description = + apr_psprintf( + conflict->pool, _("apply changes to '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling), + scratch_pool)); + } + else if (details->wc_move_targets) + { + apr_array_header_t *move_target_wc_abspaths; + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + + if (preferred_move_target_idx < 0 || + preferred_move_target_idx > move_target_wc_abspaths->nelts) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Index '%d' is out of bounds of the possible " + "move target list for '%s'"), + preferred_move_target_idx, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + /* Record the user's preference. */ + details->wc_move_target_idx = preferred_move_target_idx; + + /* Update option description. */ + SVN_ERR(conflict_tree_get_description_local_missing( + &option->description, conflict, ctx, + conflict->pool, scratch_pool)); + } + } + else if ((operation == svn_wc_operation_update || + operation == svn_wc_operation_switch) && + incoming_change == svn_wc_conflict_action_delete && + local_change == svn_wc_conflict_reason_moved_away) + { + struct conflict_tree_update_local_moved_away_details *details; + + details = conflict->tree_conflict_local_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + if (preferred_move_target_idx < 0 || + preferred_move_target_idx > details->wc_move_targets->nelts) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Index '%d' is out of bounds of the " + "possible move target list for '%s'"), + preferred_move_target_idx, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + /* Record the user's preference. */ + details->preferred_move_target_idx = preferred_move_target_idx; + + /* Update option description. */ + if (id == svn_client_conflict_option_both_moved_file_merge) + SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge( + &option->description, conflict, ctx, conflict->pool, + scratch_pool)); + else if (id == svn_client_conflict_option_both_moved_file_move_merge) + SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge( + &option->description, conflict, ctx, conflict->pool, scratch_pool)); +#if 0 /* ### TODO: Also handle options for directories! */ + else if (id == svn_client_conflict_option_both_moved_dir_merge) + { + } + else if (id == svn_client_conflict_option_both_moved_dir_move_merge) + { + } +#endif + else + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Unexpected option id '%d'"), id); + } + else + { + struct conflict_tree_incoming_delete_details *details; + apr_array_header_t *move_target_wc_abspaths; + const char *moved_to_abspath; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, scratch_pool)); + + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + + if (preferred_move_target_idx < 0 || + preferred_move_target_idx > move_target_wc_abspaths->nelts) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Index '%d' is out of bounds of the possible " + "move target list for '%s'"), + preferred_move_target_idx, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + /* Record the user's preference. */ + details->wc_move_target_idx = preferred_move_target_idx; + + /* Update option description. */ + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, + details->wc_move_target_idx, + const char *); + SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description, + conflict, ctx, + moved_to_abspath, + conflict->pool, + scratch_pool)); + } return SVN_NO_ERROR; } svn_error_t * +svn_client_conflict_option_set_moved_to_abspath( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_set_moved_to_abspath2(option, + preferred_move_target_idx, ctx, scratch_pool)); +} + +svn_error_t * svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options, svn_client_conflict_t *conflict, svn_client_ctx_t *ctx, @@ -10401,8 +12631,15 @@ svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options, scratch_pool)); SVN_ERR(configure_option_incoming_dir_merge(conflict, ctx, *options, scratch_pool)); - SVN_ERR(configure_option_local_move_file_merge(conflict, ctx, *options, + SVN_ERR(configure_option_local_move_file_or_dir_merge(conflict, ctx, + *options, + scratch_pool)); + SVN_ERR(configure_option_sibling_move_merge(conflict, ctx, *options, + scratch_pool)); + SVN_ERR(configure_option_both_moved_file_merge(conflict, ctx, *options, scratch_pool)); + SVN_ERR(configure_option_both_moved_dir_merge(conflict, ctx, *options, + scratch_pool)); return SVN_NO_ERROR; } @@ -10947,6 +13184,7 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) { svn_boolean_t tree_conflicted; + svn_wc_operation_t operation; svn_wc_conflict_action_t incoming_change; svn_wc_conflict_reason_t local_change; @@ -10963,6 +13201,7 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict, conflict->tree_conflict_get_local_description_func = conflict_tree_get_local_description_generic; + operation = svn_client_conflict_get_operation(conflict); incoming_change = svn_client_conflict_get_incoming_change(conflict); local_change = svn_client_conflict_get_local_change(conflict); @@ -10997,6 +13236,12 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict, conflict->tree_conflict_get_local_details_func = conflict_tree_get_details_local_missing; } + else if (local_change == svn_wc_conflict_reason_moved_away && + operation == svn_wc_operation_update /* ### what about switch? */) + { + conflict->tree_conflict_get_local_details_func = + conflict_tree_get_details_update_local_moved_away; + } return SVN_NO_ERROR; } diff --git a/subversion/libsvn_client/copy.c b/subversion/libsvn_client/copy.c index 8b63ee8..b3f2bac 100644 --- a/subversion/libsvn_client/copy.c +++ b/subversion/libsvn_client/copy.c @@ -578,7 +578,7 @@ pin_externals_prop(svn_string_t **pinned_externals, static svn_error_t * resolve_pinned_externals(apr_hash_t **pinned_externals, const apr_hash_t *externals_to_pin, - svn_client__copy_pair_t *pair, + const svn_client__copy_pair_t *pair, svn_ra_session_t *ra_session, const char *repos_root_url, svn_client_ctx_t *ctx, @@ -1099,14 +1099,13 @@ verify_wc_dsts(const apr_array_header_t *copy_pairs, return SVN_NO_ERROR; } +/* Verify that the WC sources in COPY_PAIRS exist, and set pair->src_kind + for each. + */ static svn_error_t * -verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs, - svn_boolean_t make_parents, - svn_boolean_t is_move, - svn_boolean_t metadata_only, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +verify_wc_srcs(const apr_array_header_t *copy_pairs, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) { int i; apr_pool_t *iterpool = svn_pool_create(scratch_pool); @@ -1133,10 +1132,6 @@ verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs, pair->src_abspath_or_url, scratch_pool)); } - - SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx, - result_pool, iterpool)); - svn_pool_destroy(iterpool); return SVN_NO_ERROR; @@ -1163,10 +1158,6 @@ typedef struct path_driver_info_t or move operation. */ struct path_driver_cb_baton { - /* The editor (and its state) used to perform the operation. */ - const svn_delta_editor_t *editor; - void *edit_baton; - /* A hash of path -> path_driver_info_t *'s. */ apr_hash_t *action_hash; @@ -1176,6 +1167,8 @@ struct path_driver_cb_baton static svn_error_t * path_driver_cb_func(void **dir_baton, + const svn_delta_editor_t *editor, + void *edit_baton, void *parent_baton, void *callback_baton, const char *path, @@ -1196,9 +1189,9 @@ path_driver_cb_func(void **dir_baton, /* Check to see if we need to add the path as a parent directory. */ if (path_info->dir_add) { - return cb_baton->editor->add_directory(path, parent_baton, NULL, - SVN_INVALID_REVNUM, pool, - dir_baton); + return editor->add_directory(path, parent_baton, NULL, + SVN_INVALID_REVNUM, pool, + dir_baton); } /* If this is a resurrection, we know the source and dest paths are @@ -1230,8 +1223,8 @@ path_driver_cb_func(void **dir_baton, if (do_delete) { - SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM, - parent_baton, pool)); + SVN_ERR(editor->delete_entry(path, SVN_INVALID_REVNUM, + parent_baton, pool)); } if (do_add) { @@ -1240,40 +1233,40 @@ path_driver_cb_func(void **dir_baton, if (path_info->src_kind == svn_node_file) { void *file_baton; - SVN_ERR(cb_baton->editor->add_file(path, parent_baton, - path_info->src_url, - path_info->src_revnum, - pool, &file_baton)); + SVN_ERR(editor->add_file(path, parent_baton, + path_info->src_url, + path_info->src_revnum, + pool, &file_baton)); if (path_info->mergeinfo) - SVN_ERR(cb_baton->editor->change_file_prop(file_baton, - SVN_PROP_MERGEINFO, - path_info->mergeinfo, - pool)); - SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool)); + SVN_ERR(editor->change_file_prop(file_baton, + SVN_PROP_MERGEINFO, + path_info->mergeinfo, + pool)); + SVN_ERR(editor->close_file(file_baton, NULL, pool)); } else { - SVN_ERR(cb_baton->editor->add_directory(path, parent_baton, - path_info->src_url, - path_info->src_revnum, - pool, dir_baton)); + SVN_ERR(editor->add_directory(path, parent_baton, + path_info->src_url, + path_info->src_revnum, + pool, dir_baton)); if (path_info->mergeinfo) - SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, - SVN_PROP_MERGEINFO, - path_info->mergeinfo, - pool)); + SVN_ERR(editor->change_dir_prop(*dir_baton, + SVN_PROP_MERGEINFO, + path_info->mergeinfo, + pool)); } } if (path_info->externals) { if (*dir_baton == NULL) - SVN_ERR(cb_baton->editor->open_directory(path, parent_baton, - SVN_INVALID_REVNUM, - pool, dir_baton)); + SVN_ERR(editor->open_directory(path, parent_baton, + SVN_INVALID_REVNUM, + pool, dir_baton)); - SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS, - path_info->externals, pool)); + SVN_ERR(editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS, + path_info->externals, pool)); } return SVN_NO_ERROR; @@ -1857,13 +1850,11 @@ repos_to_repos_copy(const apr_array_header_t *copy_pairs, pool)); /* Setup the callback baton. */ - cb_baton.editor = editor; - cb_baton.edit_baton = edit_baton; cb_baton.action_hash = action_hash; cb_baton.is_move = is_move; /* Call the path-based editor driver. */ - err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE, + err = svn_delta_path_driver3(editor, edit_baton, paths, TRUE, path_driver_cb_func, &cb_baton, pool); if (err) { @@ -2318,9 +2309,15 @@ struct notification_adjust_baton }; /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose - * baton is BATON->inner_baton) and adjusts the notification paths that - * start with BATON->checkout_abspath to start instead with - * BATON->final_abspath. */ + * baton is BATON->inner_baton) to turn the result of a 'checkout' into + * what we want to see for a 'copy to WC' operation. + * + * - Adjust the notification paths that start with BATON->checkout_abspath + * to start instead with BATON->final_abspath. + * - Change start-of-update notification into a plain WC 'add' for the root. + * - Change checkout 'add' notifications into a plain WC 'add'. + * - Discard 'update_completed' notifications. + */ static void notification_adjust_func(void *baton, const svn_wc_notify_t *notify, @@ -2333,18 +2330,372 @@ notification_adjust_func(void *baton, relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path); inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool); + /* Convert 'update' notifications to plain 'add' notifications; discard + notifications about checkout/update starting/finishing. */ + if (notify->action == svn_wc_notify_update_started /* root */ + || notify->action == svn_wc_notify_update_add) /* non-root */ + { + inner_notify->action = svn_wc_notify_add; + } + else if (notify->action == svn_wc_notify_update_update + || notify->action == svn_wc_notify_update_completed) + { + /* update_update happens only for a prop mod on root; the root was + already notified so discard this */ + return; + } + if (nb->inner_func) nb->inner_func(nb->inner_baton, inner_notify, pool); } +/** Copy a directory tree from a remote repository. + * + * Copy from RA_SESSION:LOCATION to WC_CTX:DST_ABSPATH. + * + * Create the directory DST_ABSPATH, if not present. Its parent should be + * already under version control in the WC and in a suitable state for + * scheduling the addition of a child. + * + * Ignore any incoming non-regular properties (entry-props, DAV/WC-props). + * Remove any incoming 'svn:mergeinfo' properties. + */ +static svn_error_t * +copy_foreign_dir(svn_ra_session_t *ra_session, + const svn_client__pathrev_t *location, + const char *dst_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const svn_delta_editor_t *editor; + void *eb; + const svn_delta_editor_t *wrapped_editor; + void *wrapped_baton; + const svn_ra_reporter3_t *reporter; + void *reporter_baton; + + /* Get a WC editor. It does not need an RA session because we will not + be sending it any 'copy from' requests, only 'add' requests. */ + SVN_ERR(svn_client__wc_editor_internal(&editor, &eb, + dst_abspath, + TRUE /*root_dir_add*/, + TRUE /*ignore_mergeinfo_changes*/, + FALSE /*manage_wc_write_lock*/, + notify_func, notify_baton, + NULL /*ra_session*/, + ctx, scratch_pool)); + + SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, + editor, eb, + &wrapped_editor, &wrapped_baton, + scratch_pool)); + + SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton, + location->rev, "", svn_depth_infinity, + FALSE, FALSE, wrapped_editor, wrapped_baton, + scratch_pool, scratch_pool)); + + SVN_ERR(reporter->set_path(reporter_baton, "", location->rev, + svn_depth_infinity /* irrelevant */, + TRUE /*start_empty*/, + NULL, scratch_pool)); + + SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Implementation of svn_client__repos_to_wc_copy() for a dir. + */ +static svn_error_t * +svn_client__repos_to_wc_copy_dir(svn_boolean_t *timestamp_sleep, + const char *src_url, + svn_revnum_t src_revnum, + const char *dst_abspath, + svn_boolean_t same_repositories, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const char *tmpdir_abspath, *tmp_abspath; + + SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + + if (!same_repositories) + { + svn_client__pathrev_t *location; + + *timestamp_sleep = TRUE; + + /* ### Reparenting "ra_session" can't be right, can it? As this is + a foreign repo, surely we need a new RA session? */ + SVN_ERR(svn_client__pathrev_create_with_session(&location, ra_session, + src_revnum, src_url, + scratch_pool)); + SVN_ERR(svn_ra_reparent(ra_session, src_url, scratch_pool)); + SVN_ERR(copy_foreign_dir(ra_session, location, + dst_abspath, + ctx->notify_func2, ctx->notify_baton2, + ctx->cancel_func, ctx->cancel_baton, + ctx, scratch_pool)); + + return SVN_NO_ERROR; + } + + /* Find a temporary location in which to check out the copy source. */ + SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath, + scratch_pool, scratch_pool)); + + /* Get a temporary path. The crude way we do this is to create a + temporary file, remember its name, and let it be deleted immediately. */ + SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath, + svn_io_file_del_on_close, + scratch_pool, scratch_pool)); + + /* Make a new checkout of the requested source. While doing so, + * resolve copy_src_revnum to an actual revision number in case it + * was until now 'invalid' meaning 'head'. Ask this function not to + * sleep for timestamps, by passing a sleep_needed output param. + * Send notifications for all nodes except the root node, and adjust + * them to refer to the destination rather than this temporary path. */ + { + svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2; + void *old_notify_baton2 = ctx->notify_baton2; + struct notification_adjust_baton nb; + svn_error_t *err; + svn_opt_revision_t copy_src_revision; + + copy_src_revision.kind = svn_opt_revision_number; + copy_src_revision.value.number = src_revnum; + + nb.inner_func = ctx->notify_func2; + nb.inner_baton = ctx->notify_baton2; + nb.checkout_abspath = tmp_abspath; + nb.final_abspath = dst_abspath; + ctx->notify_func2 = notification_adjust_func; + ctx->notify_baton2 = &nb; + + err = svn_client__checkout_internal(NULL /*result_rev*/, timestamp_sleep, + src_url, + tmp_abspath, + ©_src_revision, + ©_src_revision, + svn_depth_infinity, + TRUE /*ignore_externals*/, + FALSE, /* we don't allow obstructions */ + ra_session, ctx, scratch_pool); + + ctx->notify_func2 = old_notify_func2; + ctx->notify_baton2 = old_notify_baton2; + + SVN_ERR(err); + } + + /* Schedule dst_path for addition in parent, with copy history. + Don't send any notification here. + Then remove the temporary checkout's .svn dir in preparation for + moving the rest of it into the final destination. */ + SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath, + TRUE /* metadata_only */, + NULL, NULL, /* don't allow user to cancel here */ + NULL, NULL, scratch_pool)); + SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath, + FALSE, scratch_pool, scratch_pool)); + SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx, + tmp_abspath, + FALSE, FALSE, + NULL, NULL, /* don't cancel */ + scratch_pool)); + + /* Move the temporary disk tree into place. */ + SVN_ERR(svn_io_file_rename2(tmp_abspath, dst_abspath, FALSE, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Implementation of svn_client__repos_to_wc_copy() for a file. + * + * This has no 'ignore_externals' parameter because we don't support the + * 'svn:externals' property being set on a file. + */ +static svn_error_t * +svn_client__repos_to_wc_copy_file(svn_boolean_t *timestamp_sleep, + const char *src_url, + svn_revnum_t src_rev, + const char *dst_abspath, + svn_boolean_t same_repositories, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const char *src_rel; + apr_hash_t *new_props; + svn_stream_t *new_base_contents = svn_stream_buffered(scratch_pool); + + SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, src_url, + scratch_pool)); + /* Fetch the file content. */ + SVN_ERR(svn_ra_get_file(ra_session, src_rel, src_rev, + new_base_contents, NULL, &new_props, + scratch_pool)); + if (!same_repositories) + svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL); + + *timestamp_sleep = TRUE; + SVN_ERR(svn_wc_add_repos_file4( + ctx->wc_ctx, dst_abspath, + new_base_contents, NULL, new_props, NULL, + same_repositories ? src_url : NULL, + same_repositories ? src_rev : SVN_INVALID_REVNUM, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool)); + /* Do our own notification for the root node, even if we could possibly + have delegated it. See also issue #2198. */ + if (ctx->notify_func2) + { + svn_wc_notify_t *notify + = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, scratch_pool); + + notify->kind = svn_node_file; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + return SVN_NO_ERROR; +} + +/* Are RA_SESSION and the versioned *parent* dir of WC_TARGET_ABSPATH in + * the same repository? + */ +static svn_error_t * +is_same_repository(svn_boolean_t *same_repository, + svn_ra_session_t *ra_session, + const char *wc_target_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const char *src_uuid, *dst_uuid; + + /* Get the repository UUIDs of copy source URL and WC parent path */ + SVN_ERR(svn_ra_get_uuid2(ra_session, &src_uuid, scratch_pool)); + SVN_ERR(svn_client_get_repos_root(NULL /*root_url*/, &dst_uuid, + svn_dirent_dirname(wc_target_abspath, + scratch_pool), + ctx, scratch_pool, scratch_pool)); + *same_repository = (strcmp(src_uuid, dst_uuid) == 0); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__repos_to_wc_copy_internal(svn_boolean_t *timestamp_sleep, + svn_node_kind_t kind, + const char *src_url, + svn_revnum_t src_rev, + const char *dst_abspath, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const char *old_session_url; + svn_boolean_t timestamp_sleep_ignored; + svn_boolean_t same_repositories; + + SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, + src_url, scratch_pool)); + + SVN_ERR(is_same_repository(&same_repositories, + ra_session, dst_abspath, ctx, scratch_pool)); + + if (!timestamp_sleep) + timestamp_sleep = ×tamp_sleep_ignored; + + if (kind == svn_node_dir) + { + SVN_ERR(svn_client__repos_to_wc_copy_dir(timestamp_sleep, + src_url, src_rev, + dst_abspath, + same_repositories, + ra_session, + ctx, scratch_pool)); + } + else if (kind == svn_node_file) + { + SVN_ERR(svn_client__repos_to_wc_copy_file(timestamp_sleep, + src_url, src_rev, + dst_abspath, + same_repositories, + ra_session, + ctx, scratch_pool)); + } + + /* Reparent the session back to the original URL. */ + SVN_ERR(svn_ra_reparent(ra_session, old_session_url, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__repos_to_wc_copy_by_editor(svn_boolean_t *timestamp_sleep, + svn_node_kind_t kind, + const char *src_url, + svn_revnum_t src_rev, + const char *dst_abspath, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const svn_delta_editor_t *editor; + void *eb; + const char *src_anchor = svn_uri_dirname(src_url, scratch_pool); + const char *dst_target = svn_dirent_basename(dst_abspath, scratch_pool); + void *rb, *db; + + SVN_ERR(svn_ra_reparent(ra_session, src_anchor, scratch_pool)); + + SVN_ERR(svn_client__wc_editor_internal( + &editor, &eb, + svn_dirent_dirname(dst_abspath, scratch_pool), + FALSE /*root_dir_add*/, + FALSE /*ignore_mergeinfo_changes*/, + FALSE /*manage_wc_write_lock*/, + ctx->notify_func2, ctx->notify_baton2, + ra_session, + ctx, scratch_pool)); + + SVN_ERR(editor->open_root(eb, SVN_INVALID_REVNUM, scratch_pool, &rb)); + if (kind == svn_node_dir) + { + SVN_ERR(editor->add_directory(dst_target, rb, + src_url, src_rev, + scratch_pool, + &db)); + SVN_ERR(editor->close_directory(db, scratch_pool)); + } + else + { + SVN_ERR(editor->add_file(dst_target, rb, + src_url, src_rev, + scratch_pool, + &db)); + SVN_ERR(editor->close_file(db, NULL, scratch_pool)); + } + SVN_ERR(editor->close_edit(eb, scratch_pool)); + + if (timestamp_sleep) + *timestamp_sleep = TRUE; + return SVN_NO_ERROR; +} + /* Peform each individual copy operation for a repos -> wc copy. A helper for repos_to_wc_copy(). - Resolve PAIR->src_revnum to a real revision number if it isn't already. */ + PAIR->src_revnum PAIR->src_abspath_or_url should already have been + resolved to the operative revision number and operative URL. + */ static svn_error_t * repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep, - svn_client__copy_pair_t *pair, - svn_boolean_t same_repositories, + const svn_client__copy_pair_t *pair, svn_boolean_t ignore_externals, svn_boolean_t pin_externals, const apr_hash_t *externals_to_pin, @@ -2354,9 +2705,14 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep, { apr_hash_t *src_mergeinfo; const char *dst_abspath = pair->dst_abspath_or_url; + svn_boolean_t same_repositories; + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(pair->src_revnum)); + SVN_ERR_ASSERT(svn_path_is_url(pair->src_abspath_or_url)); SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); + SVN_ERR(is_same_repository(&same_repositories, + ra_session, dst_abspath, ctx, pool)); if (!same_repositories && ctx->notify_func2) { svn_wc_notify_t *notify; @@ -2372,135 +2728,59 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep, SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); } - if (pair->src_kind == svn_node_dir) + SVN_ERR(svn_client__repos_to_wc_copy_by_editor( + timestamp_sleep, + pair->src_kind, + pair->src_abspath_or_url, + pair->src_revnum, + dst_abspath, + ra_session, ctx, pool)); + + /* Fetch externals, pinning them if requested */ + if (!ignore_externals && pair->src_kind == svn_node_dir) { if (same_repositories) { - const char *tmpdir_abspath, *tmp_abspath; - - /* Find a temporary location in which to check out the copy source. */ - SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath, - pool, pool)); - - SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath, - svn_io_file_del_on_close, pool, pool)); - - /* Make a new checkout of the requested source. While doing so, - * resolve pair->src_revnum to an actual revision number in case it - * was until now 'invalid' meaning 'head'. Ask this function not to - * sleep for timestamps, by passing a sleep_needed output param. - * Send notifications for all nodes except the root node, and adjust - * them to refer to the destination rather than this temporary path. */ - { - svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2; - void *old_notify_baton2 = ctx->notify_baton2; - struct notification_adjust_baton nb; - svn_error_t *err; - - nb.inner_func = ctx->notify_func2; - nb.inner_baton = ctx->notify_baton2; - nb.checkout_abspath = tmp_abspath; - nb.final_abspath = dst_abspath; - ctx->notify_func2 = notification_adjust_func; - ctx->notify_baton2 = &nb; - - /* Avoid a chicken-and-egg problem: - * If pinning externals we'll need to adjust externals - * properties before checking out any externals. - * But copy needs to happen before pinning because else there - * are no svn:externals properties to pin. */ - if (pin_externals) - ignore_externals = TRUE; - - err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep, - pair->src_original, - tmp_abspath, - &pair->src_peg_revision, - &pair->src_op_revision, - svn_depth_infinity, - ignore_externals, FALSE, - ra_session, ctx, pool); - - ctx->notify_func2 = old_notify_func2; - ctx->notify_baton2 = old_notify_baton2; - - SVN_ERR(err); - } - - *timestamp_sleep = TRUE; - - /* Schedule dst_path for addition in parent, with copy history. - Don't send any notification here. - Then remove the temporary checkout's .svn dir in preparation for - moving the rest of it into the final destination. */ - SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath, - TRUE /* metadata_only */, - ctx->cancel_func, ctx->cancel_baton, - NULL, NULL, pool)); - SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath, - FALSE, pool, pool)); - SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx, - tmp_abspath, - FALSE, FALSE, - ctx->cancel_func, - ctx->cancel_baton, - pool)); - - /* Move the temporary disk tree into place. */ - SVN_ERR(svn_io_file_rename2(tmp_abspath, dst_abspath, FALSE, pool)); - } - else - { - *timestamp_sleep = TRUE; - - SVN_ERR(svn_client__copy_foreign(pair->src_original, - dst_abspath, - &pair->src_peg_revision, - &pair->src_op_revision, - svn_depth_infinity, - FALSE /* make_parents */, - TRUE /* already_locked */, - ctx, pool)); - - return SVN_NO_ERROR; - } - - if (pin_externals) - { - apr_hash_t *pinned_externals; - apr_hash_index_t *hi; - apr_pool_t *iterpool; const char *repos_root_url; apr_hash_t *new_externals; apr_hash_t *new_depths; SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool)); - SVN_ERR(resolve_pinned_externals(&pinned_externals, - externals_to_pin, pair, - ra_session, repos_root_url, - ctx, pool, pool)); - iterpool = svn_pool_create(pool); - for (hi = apr_hash_first(pool, pinned_externals); - hi; - hi = apr_hash_next(hi)) + if (pin_externals) { - const char *dst_relpath = apr_hash_this_key(hi); - svn_string_t *externals_propval = apr_hash_this_val(hi); - const char *local_abspath; + apr_hash_t *pinned_externals; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + SVN_ERR(resolve_pinned_externals(&pinned_externals, + externals_to_pin, pair, + ra_session, repos_root_url, + ctx, pool, pool)); + + iterpool = svn_pool_create(pool); + for (hi = apr_hash_first(pool, pinned_externals); + hi; + hi = apr_hash_next(hi)) + { + const char *dst_relpath = apr_hash_this_key(hi); + svn_string_t *externals_propval = apr_hash_this_val(hi); + const char *local_abspath; - svn_pool_clear(iterpool); + svn_pool_clear(iterpool); - local_abspath = svn_dirent_join(pair->dst_abspath_or_url, - dst_relpath, iterpool); - /* ### use a work queue? */ - SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, - SVN_PROP_EXTERNALS, externals_propval, - svn_depth_empty, TRUE /* skip_checks */, - NULL /* changelist_filter */, - ctx->cancel_func, ctx->cancel_baton, - NULL, NULL, /* no extra notification */ - iterpool)); + local_abspath = svn_dirent_join(pair->dst_abspath_or_url, + dst_relpath, iterpool); + /* ### use a work queue? */ + SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, + SVN_PROP_EXTERNALS, externals_propval, + svn_depth_empty, TRUE /* skip_checks */, + NULL /* changelist_filter */, + ctx->cancel_func, ctx->cancel_baton, + NULL, NULL, /* no extra notification */ + iterpool)); + } + svn_pool_destroy(iterpool); } /* Now update all externals in the newly created copy. */ @@ -2509,51 +2789,20 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep, ctx->wc_ctx, dst_abspath, svn_depth_infinity, - iterpool, iterpool)); + pool, pool)); SVN_ERR(svn_client__handle_externals(new_externals, new_depths, repos_root_url, dst_abspath, svn_depth_infinity, timestamp_sleep, ra_session, - ctx, iterpool)); - svn_pool_destroy(iterpool); + ctx, pool)); } - } /* end directory case */ - - else if (pair->src_kind == svn_node_file) - { - apr_hash_t *new_props; - const char *src_rel; - svn_stream_t *new_base_contents = svn_stream_buffered(pool); - - SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, - pair->src_abspath_or_url, - pool)); - /* Fetch the file content. While doing so, resolve pair->src_revnum - * to an actual revision number if it's 'invalid' meaning 'head'. */ - SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum, - new_base_contents, - &pair->src_revnum, &new_props, pool)); - - if (new_props && ! same_repositories) - svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL); - - *timestamp_sleep = TRUE; - - SVN_ERR(svn_wc_add_repos_file4( - ctx->wc_ctx, dst_abspath, - new_base_contents, NULL, new_props, NULL, - same_repositories ? pair->src_abspath_or_url : NULL, - same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM, - ctx->cancel_func, ctx->cancel_baton, - pool)); } if (same_repositories) { - /* Record the implied mergeinfo (before the notification callback - is invoked for the root node). */ + /* Record the implied mergeinfo. */ SVN_ERR(svn_client__get_repos_mergeinfo(&src_mergeinfo, ra_session, pair->src_abspath_or_url, pair->src_revnum, @@ -2561,18 +2810,9 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep, TRUE /*squelch_incapable*/, pool)); SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool)); - } - /* Do our own notification for the root node, even if we could possibly - have delegated it. See also issue #1552. - - ### Maybe this notification should mention the mergeinfo change. */ - if (ctx->notify_func2) - { - svn_wc_notify_t *notify = svn_wc_create_notify( - dst_abspath, svn_wc_notify_add, pool); - notify->kind = pair->src_kind; - ctx->notify_func2(ctx->notify_baton2, notify, pool); + /* ### Maybe the notification should mention this mergeinfo change. */ + /* ### Maybe we should do this during rather than after the copy. */ } return SVN_NO_ERROR; @@ -2590,38 +2830,8 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep, apr_pool_t *scratch_pool) { int i; - svn_boolean_t same_repositories; apr_pool_t *iterpool = svn_pool_create(scratch_pool); - /* We've already checked for physical obstruction by a working file. - But there could also be logical obstruction by an entry whose - working file happens to be missing.*/ - SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */, - ctx, scratch_pool, iterpool)); - - /* Decide whether the two repositories are the same or not. */ - { - const char *parent_abspath; - const char *src_uuid, *dst_uuid; - - /* Get the repository uuid of SRC_URL */ - SVN_ERR(svn_ra_get_uuid2(ra_session, &src_uuid, iterpool)); - - /* Get repository uuid of dst's parent directory, since dst may - not exist. ### TODO: we should probably walk up the wc here, - in case the parent dir has an imaginary URL. */ - if (copy_pairs->nelts == 1) - parent_abspath = svn_dirent_dirname(top_dst_abspath, scratch_pool); - else - parent_abspath = top_dst_abspath; - - SVN_ERR(svn_client_get_repos_root(NULL /* root_url */, &dst_uuid, - parent_abspath, ctx, - iterpool, iterpool)); - /* ### Also check repos_root_url? */ - same_repositories = (strcmp(src_uuid, dst_uuid) == 0); - } - /* Perform the move for each of the copy_pairs. */ for (i = 0; i < copy_pairs->nelts; i++) { @@ -2634,7 +2844,6 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep, SVN_ERR(repos_to_wc_copy_single(timestamp_sleep, APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *), - same_repositories, ignore_externals, pin_externals, externals_to_pin, ra_session, ctx, iterpool)); @@ -2647,7 +2856,6 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep, static svn_error_t * repos_to_wc_copy(svn_boolean_t *timestamp_sleep, const apr_array_header_t *copy_pairs, - svn_boolean_t make_parents, svn_boolean_t ignore_externals, svn_boolean_t pin_externals, const apr_hash_t *externals_to_pin, @@ -2701,8 +2909,6 @@ repos_to_wc_copy(svn_boolean_t *timestamp_sleep, { svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); - svn_node_kind_t dst_parent_kind, dst_kind; - const char *dst_parent; const char *src_rel; svn_pool_clear(iterpool); @@ -2726,43 +2932,6 @@ repos_to_wc_copy(svn_boolean_t *timestamp_sleep, _("Path '%s' not found in head revision"), pair->src_abspath_or_url); } - - /* Figure out about dst. */ - SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, - iterpool)); - if (dst_kind != svn_node_none) - { - return svn_error_createf( - SVN_ERR_ENTRY_EXISTS, NULL, - _("Path '%s' already exists"), - svn_dirent_local_style(pair->dst_abspath_or_url, pool)); - } - - /* Make sure the destination parent is a directory and produce a clear - error message if it is not. */ - dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool); - SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool)); - if (make_parents && dst_parent_kind == svn_node_none) - { - SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx, - iterpool)); - } - else if (make_parents && dst_parent_kind == svn_node_dir) - { - SVN_ERR(svn_wc_read_kind2(&dst_parent_kind, ctx->wc_ctx, dst_parent, - FALSE, TRUE, iterpool)); - if (dst_parent_kind == svn_node_none) - { - SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx, - iterpool)); - } - } - else if (dst_parent_kind != svn_node_dir) - { - return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, - _("Path '%s' is not a directory"), - svn_dirent_local_style(dst_parent, pool)); - } } svn_pool_destroy(iterpool); @@ -3085,8 +3254,9 @@ try_copy(svn_boolean_t *timestamp_sleep, /* Now, call the right handler for the operation. */ if ((! srcs_are_urls) && (! dst_is_url)) { - SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move, - metadata_only, ctx, pool, pool)); + SVN_ERR(verify_wc_srcs(copy_pairs, ctx, pool)); + SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, + ctx, pool, pool)); /* Copy or move all targets. */ if (is_move) @@ -3116,9 +3286,13 @@ try_copy(svn_boolean_t *timestamp_sleep, } else if ((srcs_are_urls) && (! dst_is_url)) { + SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, + FALSE, FALSE /* metadata_only */, + ctx, pool, pool)); + return svn_error_trace( repos_to_wc_copy(timestamp_sleep, - copy_pairs, make_parents, ignore_externals, + copy_pairs, ignore_externals, pin_externals, externals_to_pin, ctx, pool)); } else diff --git a/subversion/libsvn_client/copy_foreign.c b/subversion/libsvn_client/copy_foreign.c deleted file mode 100644 index cfe6aea..0000000 --- a/subversion/libsvn_client/copy_foreign.c +++ /dev/null @@ -1,575 +0,0 @@ -/* - * copy_foreign.c: copy from other repository support. - * - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - */ - -/* ==================================================================== */ - -/*** Includes. ***/ - -#include <string.h> -#include "svn_hash.h" -#include "svn_client.h" -#include "svn_delta.h" -#include "svn_dirent_uri.h" -#include "svn_error.h" -#include "svn_error_codes.h" -#include "svn_path.h" -#include "svn_pools.h" -#include "svn_props.h" -#include "svn_ra.h" -#include "svn_wc.h" - -#include <apr_md5.h> - -#include "client.h" -#include "private/svn_subr_private.h" -#include "private/svn_wc_private.h" -#include "svn_private_config.h" - -struct edit_baton_t -{ - apr_pool_t *pool; - const char *anchor_abspath; - - svn_wc_context_t *wc_ctx; - svn_wc_notify_func2_t notify_func; - void *notify_baton; -}; - -struct dir_baton_t -{ - apr_pool_t *pool; - - struct dir_baton_t *pb; - struct edit_baton_t *eb; - - const char *local_abspath; - - svn_boolean_t created; - apr_hash_t *properties; - - int users; -}; - -/* svn_delta_editor_t function */ -static svn_error_t * -edit_open(void *edit_baton, - svn_revnum_t base_revision, - apr_pool_t *result_pool, - void **root_baton) -{ - struct edit_baton_t *eb = edit_baton; - apr_pool_t *dir_pool = svn_pool_create(eb->pool); - struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db)); - - db->pool = dir_pool; - db->eb = eb; - db->users = 1; - db->local_abspath = eb->anchor_abspath; - - SVN_ERR(svn_io_make_dir_recursively(eb->anchor_abspath, dir_pool)); - - *root_baton = db; - - return SVN_NO_ERROR; -} - -/* svn_delta_editor_t function */ -static svn_error_t * -edit_close(void *edit_baton, - apr_pool_t *scratch_pool) -{ - return SVN_NO_ERROR; -} - -static svn_error_t * -dir_add(const char *path, - void *parent_baton, - const char *copyfrom_path, - svn_revnum_t copyfrom_revision, - apr_pool_t *result_pool, - void **child_baton) -{ - struct dir_baton_t *pb = parent_baton; - struct edit_baton_t *eb = pb->eb; - apr_pool_t *dir_pool = svn_pool_create(pb->pool); - struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db)); - svn_boolean_t under_root; - - pb->users++; - - db->pb = pb; - db->eb = pb->eb; - db->pool = dir_pool; - db->users = 1; - - SVN_ERR(svn_dirent_is_under_root(&under_root, &db->local_abspath, - eb->anchor_abspath, path, db->pool)); - if (! under_root) - { - return svn_error_createf( - SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, - _("Path '%s' is not in the working copy"), - svn_dirent_local_style(path, db->pool)); - } - - SVN_ERR(svn_io_make_dir_recursively(db->local_abspath, db->pool)); - - *child_baton = db; - return SVN_NO_ERROR; -} - -static svn_error_t * -dir_change_prop(void *dir_baton, - const char *name, - const svn_string_t *value, - apr_pool_t *scratch_pool) -{ - struct dir_baton_t *db = dir_baton; - struct edit_baton_t *eb = db->eb; - svn_prop_kind_t prop_kind; - - prop_kind = svn_property_kind2(name); - - if (prop_kind != svn_prop_regular_kind - || ! strcmp(name, SVN_PROP_MERGEINFO)) - { - /* We can't handle DAV, ENTRY and merge specific props here */ - return SVN_NO_ERROR; - } - - if (! db->created) - { - /* We can still store them in the hash for immediate addition - with the svn_wc_add_from_disk3() call */ - if (! db->properties) - db->properties = apr_hash_make(db->pool); - - if (value != NULL) - svn_hash_sets(db->properties, apr_pstrdup(db->pool, name), - svn_string_dup(value, db->pool)); - } - else - { - /* We have already notified for this directory, so don't do that again */ - SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value, - svn_depth_empty, FALSE, NULL, - NULL, NULL, /* Cancellation */ - NULL, NULL, /* Notification */ - scratch_pool)); - } - - return SVN_NO_ERROR; -} - -/* Releases the directory baton if there are no more users */ -static svn_error_t * -maybe_done(struct dir_baton_t *db) -{ - db->users--; - - if (db->users == 0) - { - struct dir_baton_t *pb = db->pb; - - svn_pool_clear(db->pool); - - if (pb) - SVN_ERR(maybe_done(pb)); - } - - return SVN_NO_ERROR; -} - -static svn_error_t * -ensure_added(struct dir_baton_t *db, - apr_pool_t *scratch_pool) -{ - if (db->created) - return SVN_NO_ERROR; - - if (db->pb) - SVN_ERR(ensure_added(db->pb, scratch_pool)); - - db->created = TRUE; - - /* Add the directory with all the already collected properties */ - SVN_ERR(svn_wc_add_from_disk3(db->eb->wc_ctx, - db->local_abspath, - db->properties, - TRUE /* skip checks */, - db->eb->notify_func, - db->eb->notify_baton, - scratch_pool)); - - return SVN_NO_ERROR; -} - -static svn_error_t * -dir_close(void *dir_baton, - apr_pool_t *scratch_pool) -{ - struct dir_baton_t *db = dir_baton; - /*struct edit_baton_t *eb = db->eb;*/ - - SVN_ERR(ensure_added(db, scratch_pool)); - - SVN_ERR(maybe_done(db)); - - return SVN_NO_ERROR; -} - -struct file_baton_t -{ - apr_pool_t *pool; - - struct dir_baton_t *pb; - struct edit_baton_t *eb; - - const char *local_abspath; - apr_hash_t *properties; - - svn_boolean_t writing; - unsigned char digest[APR_MD5_DIGESTSIZE]; - - const char *tmp_path; -}; - -static svn_error_t * -file_add(const char *path, - void *parent_baton, - const char *copyfrom_path, - svn_revnum_t copyfrom_revision, - apr_pool_t *result_pool, - void **file_baton) -{ - struct dir_baton_t *pb = parent_baton; - struct edit_baton_t *eb = pb->eb; - apr_pool_t *file_pool = svn_pool_create(pb->pool); - struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb)); - svn_boolean_t under_root; - - pb->users++; - - fb->pool = file_pool; - fb->eb = eb; - fb->pb = pb; - - SVN_ERR(svn_dirent_is_under_root(&under_root, &fb->local_abspath, - eb->anchor_abspath, path, fb->pool)); - if (! under_root) - { - return svn_error_createf( - SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, - _("Path '%s' is not in the working copy"), - svn_dirent_local_style(path, fb->pool)); - } - - *file_baton = fb; - return SVN_NO_ERROR; -} - -static svn_error_t * -file_change_prop(void *file_baton, - const char *name, - const svn_string_t *value, - apr_pool_t *scratch_pool) -{ - struct file_baton_t *fb = file_baton; - svn_prop_kind_t prop_kind; - - prop_kind = svn_property_kind2(name); - - if (prop_kind != svn_prop_regular_kind - || ! strcmp(name, SVN_PROP_MERGEINFO)) - { - /* We can't handle DAV, ENTRY and merge specific props here */ - return SVN_NO_ERROR; - } - - /* We store all properties in the hash for immediate addition - with the svn_wc_add_from_disk3() call */ - if (! fb->properties) - fb->properties = apr_hash_make(fb->pool); - - if (value != NULL) - svn_hash_sets(fb->properties, apr_pstrdup(fb->pool, name), - svn_string_dup(value, fb->pool)); - - return SVN_NO_ERROR; -} - -static svn_error_t * -file_textdelta(void *file_baton, - const char *base_checksum, - apr_pool_t *result_pool, - svn_txdelta_window_handler_t *handler, - void **handler_baton) -{ - struct file_baton_t *fb = file_baton; - svn_stream_t *target; - - SVN_ERR_ASSERT(! fb->writing); - - SVN_ERR(svn_stream_open_writable(&target, fb->local_abspath, fb->pool, - fb->pool)); - - fb->writing = TRUE; - svn_txdelta_apply(svn_stream_empty(fb->pool) /* source */, - target, - fb->digest, - fb->local_abspath, - fb->pool, - /* Provide the handler directly */ - handler, handler_baton); - - return SVN_NO_ERROR; -} - -static svn_error_t * -file_close(void *file_baton, - const char *text_checksum, - apr_pool_t *scratch_pool) -{ - struct file_baton_t *fb = file_baton; - struct edit_baton_t *eb = fb->eb; - struct dir_baton_t *pb = fb->pb; - - SVN_ERR(ensure_added(pb, fb->pool)); - - if (text_checksum) - { - svn_checksum_t *expected_checksum; - svn_checksum_t *actual_checksum; - - SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, - text_checksum, fb->pool)); - actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool); - - if (! svn_checksum_match(expected_checksum, actual_checksum)) - return svn_error_trace( - svn_checksum_mismatch_err(expected_checksum, - actual_checksum, - fb->pool, - _("Checksum mismatch for '%s'"), - svn_dirent_local_style( - fb->local_abspath, - fb->pool))); - } - - SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, fb->local_abspath, fb->properties, - TRUE /* skip checks */, - eb->notify_func, eb->notify_baton, - fb->pool)); - - svn_pool_destroy(fb->pool); - SVN_ERR(maybe_done(pb)); - - return SVN_NO_ERROR; -} - -static svn_error_t * -copy_foreign_dir(svn_ra_session_t *ra_session, - svn_client__pathrev_t *location, - svn_wc_context_t *wc_ctx, - const char *dst_abspath, - svn_depth_t depth, - svn_wc_notify_func2_t notify_func, - void *notify_baton, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool) -{ - struct edit_baton_t eb; - svn_delta_editor_t *editor = svn_delta_default_editor(scratch_pool); - const svn_delta_editor_t *wrapped_editor; - void *wrapped_baton; - const svn_ra_reporter3_t *reporter; - void *reporter_baton; - - eb.pool = scratch_pool; - eb.anchor_abspath = dst_abspath; - - eb.wc_ctx = wc_ctx; - eb.notify_func = notify_func; - eb.notify_baton = notify_baton; - - editor->open_root = edit_open; - editor->close_edit = edit_close; - - editor->add_directory = dir_add; - editor->change_dir_prop = dir_change_prop; - editor->close_directory = dir_close; - - editor->add_file = file_add; - editor->change_file_prop = file_change_prop; - editor->apply_textdelta = file_textdelta; - editor->close_file = file_close; - - SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, - editor, &eb, - &wrapped_editor, &wrapped_baton, - scratch_pool)); - - SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &reporter_baton, - location->rev, "", svn_depth_infinity, - FALSE, FALSE, wrapped_editor, wrapped_baton, - scratch_pool, scratch_pool)); - - SVN_ERR(reporter->set_path(reporter_baton, "", location->rev, depth, - TRUE /* incomplete */, - NULL, scratch_pool)); - - SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool)); - - return SVN_NO_ERROR; -} - - -svn_error_t * -svn_client__copy_foreign(const char *url, - const char *dst_abspath, - svn_opt_revision_t *peg_revision, - svn_opt_revision_t *revision, - svn_depth_t depth, - svn_boolean_t make_parents, - svn_boolean_t already_locked, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - svn_ra_session_t *ra_session; - svn_client__pathrev_t *loc; - svn_node_kind_t kind; - svn_node_kind_t wc_kind; - const char *dir_abspath; - - SVN_ERR_ASSERT(svn_path_is_url(url)); - SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath)); - - /* Do we need to validate/update revisions? */ - - SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc, - url, NULL, - peg_revision, - revision, ctx, - scratch_pool)); - - SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, scratch_pool)); - - if (kind != svn_node_file && kind != svn_node_dir) - return svn_error_createf( - SVN_ERR_ILLEGAL_TARGET, NULL, - _("'%s' is not a valid location inside a repository"), - url); - - SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dst_abspath, FALSE, TRUE, - scratch_pool)); - - if (wc_kind != svn_node_none) - { - return svn_error_createf( - SVN_ERR_ENTRY_EXISTS, NULL, - _("'%s' is already under version control"), - svn_dirent_local_style(dst_abspath, scratch_pool)); - } - - dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); - SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath, - FALSE, FALSE, scratch_pool)); - - if (wc_kind == svn_node_none) - { - if (make_parents) - SVN_ERR(svn_client__make_local_parents(dir_abspath, make_parents, ctx, - scratch_pool)); - - SVN_ERR(svn_wc_read_kind2(&wc_kind, ctx->wc_ctx, dir_abspath, - FALSE, FALSE, scratch_pool)); - } - - if (wc_kind != svn_node_dir) - return svn_error_createf( - SVN_ERR_ENTRY_NOT_FOUND, NULL, - _("Can't add '%s', because no parent directory is found"), - svn_dirent_local_style(dst_abspath, scratch_pool)); - - - if (kind == svn_node_file) - { - svn_stream_t *target; - apr_hash_t *props; - apr_hash_index_t *hi; - SVN_ERR(svn_stream_open_writable(&target, dst_abspath, scratch_pool, - scratch_pool)); - - SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, target, NULL, &props, - scratch_pool)); - - if (props != NULL) - for (hi = apr_hash_first(scratch_pool, props); hi; - hi = apr_hash_next(hi)) - { - const char *name = apr_hash_this_key(hi); - - if (svn_property_kind2(name) != svn_prop_regular_kind - || ! strcmp(name, SVN_PROP_MERGEINFO)) - { - /* We can't handle DAV, ENTRY and merge specific props here */ - svn_hash_sets(props, name, NULL); - } - } - - if (!already_locked) - SVN_WC__CALL_WITH_WRITE_LOCK( - svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props, - TRUE /* skip checks */, - ctx->notify_func2, ctx->notify_baton2, - scratch_pool), - ctx->wc_ctx, dir_abspath, FALSE, scratch_pool); - else - SVN_ERR(svn_wc_add_from_disk3(ctx->wc_ctx, dst_abspath, props, - TRUE /* skip checks */, - ctx->notify_func2, ctx->notify_baton2, - scratch_pool)); - } - else - { - if (!already_locked) - SVN_WC__CALL_WITH_WRITE_LOCK( - copy_foreign_dir(ra_session, loc, - ctx->wc_ctx, dst_abspath, - depth, - ctx->notify_func2, ctx->notify_baton2, - ctx->cancel_func, ctx->cancel_baton, - scratch_pool), - ctx->wc_ctx, dir_abspath, FALSE, scratch_pool); - else - SVN_ERR(copy_foreign_dir(ra_session, loc, - ctx->wc_ctx, dst_abspath, - depth, - ctx->notify_func2, ctx->notify_baton2, - ctx->cancel_func, ctx->cancel_baton, - scratch_pool)); - } - - return SVN_NO_ERROR; -} diff --git a/subversion/libsvn_client/delete.c b/subversion/libsvn_client/delete.c index 943cdd9..29a3395 100644 --- a/subversion/libsvn_client/delete.c +++ b/subversion/libsvn_client/delete.c @@ -181,12 +181,13 @@ can_delete_node(svn_boolean_t *target_missing, static svn_error_t * path_driver_cb_func(void **dir_baton, + const svn_delta_editor_t *editor, + void *edit_baton, void *parent_baton, void *callback_baton, const char *path, apr_pool_t *pool) { - const svn_delta_editor_t *editor = callback_baton; *dir_baton = NULL; return editor->delete_entry(path, SVN_INVALID_REVNUM, parent_baton, pool); } @@ -248,8 +249,8 @@ single_repos_delete(svn_ra_session_t *ra_session, pool)); /* Call the path-based editor driver. */ - err = svn_delta_path_driver2(editor, edit_baton, relpaths, TRUE, - path_driver_cb_func, (void *)editor, pool); + err = svn_delta_path_driver3(editor, edit_baton, relpaths, TRUE, + path_driver_cb_func, NULL, pool); if (err) { diff --git a/subversion/libsvn_client/deprecated.c b/subversion/libsvn_client/deprecated.c index dc20b27..b8e2026 100644 --- a/subversion/libsvn_client/deprecated.c +++ b/subversion/libsvn_client/deprecated.c @@ -166,6 +166,61 @@ svn_client_mkdir(svn_client_commit_info_t **commit_info_p, } /*** From blame.c ***/ +struct blame_receiver_wrapper_baton3 { + void *baton; + svn_client_blame_receiver3_t receiver; + svn_revnum_t start_revnum; + svn_revnum_t end_revnum; +}; + +static svn_error_t * +blame_wrapper_receiver3(void *baton, + apr_int64_t line_no, + svn_revnum_t revision, + apr_hash_t *rev_props, + svn_revnum_t merged_revision, + apr_hash_t *merged_rev_props, + const char *merged_path, + const svn_string_t *line, + svn_boolean_t local_change, + apr_pool_t *pool) +{ + struct blame_receiver_wrapper_baton3 *brwb = baton; + + if (brwb->receiver) + return brwb->receiver(brwb->baton, brwb->start_revnum, brwb->end_revnum, + line_no, + revision, rev_props, merged_revision, + merged_rev_props, merged_path, line->data, + local_change, pool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_blame5(const char *target, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *start, + const svn_opt_revision_t *end, + const svn_diff_file_options_t *diff_options, + svn_boolean_t ignore_mime_type, + svn_boolean_t include_merged_revisions, + svn_client_blame_receiver3_t receiver, + void *receiver_baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + struct blame_receiver_wrapper_baton3 baton; + + baton.receiver = receiver; + baton.baton = receiver_baton; + + return svn_client_blame6(&baton.start_revnum, &baton.end_revnum, + target, peg_revision, start, end, + diff_options, + ignore_mime_type, include_merged_revisions, + blame_wrapper_receiver3, &baton, ctx, pool); +} struct blame_receiver_wrapper_baton2 { void *baton; @@ -936,6 +991,42 @@ svn_client_delete(svn_client_commit_info_t **commit_info_p, /*** From diff.c ***/ svn_error_t * +svn_client_diff6(const apr_array_header_t *diff_options, + const char *path_or_url1, + const svn_opt_revision_t *revision1, + const char *path_or_url2, + const svn_opt_revision_t *revision2, + const char *relative_to_dir, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t no_diff_added, + svn_boolean_t no_diff_deleted, + svn_boolean_t show_copies_as_adds, + svn_boolean_t ignore_content_type, + svn_boolean_t ignore_properties, + svn_boolean_t properties_only, + svn_boolean_t use_git_diff_format, + const char *header_encoding, + svn_stream_t *outstream, + svn_stream_t *errstream, + const apr_array_header_t *changelists, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + return svn_client_diff7(diff_options, + path_or_url1, revision1, + path_or_url2, revision2, + relative_to_dir, depth, + ignore_ancestry, no_diff_added, + no_diff_deleted, show_copies_as_adds, + ignore_content_type, ignore_properties, + properties_only, use_git_diff_format, + TRUE /*pretty_print_mergeinfo*/, + header_encoding, + outstream, errstream, changelists, ctx, pool); +} + +svn_error_t * svn_client_diff5(const apr_array_header_t *diff_options, const char *path1, const svn_opt_revision_t *revision1, @@ -1058,6 +1149,53 @@ svn_client_diff(const apr_array_header_t *options, } svn_error_t * +svn_client_diff_peg6(const apr_array_header_t *options, + const char *path_or_url, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *start_revision, + const svn_opt_revision_t *end_revision, + const char *relative_to_dir, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + svn_boolean_t no_diff_added, + svn_boolean_t no_diff_deleted, + svn_boolean_t show_copies_as_adds, + svn_boolean_t ignore_content_type, + svn_boolean_t ignore_properties, + svn_boolean_t properties_only, + svn_boolean_t use_git_diff_format, + const char *header_encoding, + svn_stream_t *outstream, + svn_stream_t *errstream, + const apr_array_header_t *changelists, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + return svn_client_diff_peg7(options, + path_or_url, + peg_revision, + start_revision, + end_revision, + relative_to_dir, + depth, + ignore_ancestry, + no_diff_added, + no_diff_deleted, + show_copies_as_adds, + ignore_content_type, + ignore_properties, + properties_only, + use_git_diff_format, + TRUE /*pretty_print_mergeinfo*/, + header_encoding, + outstream, + errstream, + changelists, + ctx, + pool); +} + +svn_error_t * svn_client_diff_peg5(const apr_array_header_t *diff_options, const char *path, const svn_opt_revision_t *peg_revision, @@ -1643,7 +1781,7 @@ svn_client_log(const apr_array_header_t *targets, * we just invoke the receiver manually on a hand-constructed log * message for revision 0. * - * See also http://subversion.tigris.org/issues/show_bug.cgi?id=692. + * See also https://issues.apache.org/jira/browse/SVN-692. */ if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_REVISION) && (start->kind == svn_opt_revision_head) @@ -2853,6 +2991,22 @@ svn_client_resolved(const char *path, } /*** From revert.c ***/ svn_error_t * +svn_client_revert3(const apr_array_header_t *paths, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_boolean_t clear_changelists, + svn_boolean_t metadata_only, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + SVN_ERR(svn_client_revert4(paths, depth, changelists, + clear_changelists, metadata_only, + TRUE /*added_keep_local*/, + ctx, pool)); + return SVN_NO_ERROR; +} + +svn_error_t * svn_client_revert2(const apr_array_header_t *paths, svn_depth_t depth, const apr_array_header_t *changelists, diff --git a/subversion/libsvn_client/diff.c b/subversion/libsvn_client/diff.c index ba6e91e..30d00b0 100644 --- a/subversion/libsvn_client/diff.c +++ b/subversion/libsvn_client/diff.c @@ -23,6 +23,9 @@ /* ==================================================================== */ +/* We define this here to remove any further warnings about the usage of + experimental functions in this file. */ +#define SVN_EXPERIMENTAL /*** Includes. ***/ @@ -66,6 +69,31 @@ _("Path '%s' must be an immediate child of " \ "the directory '%s'"), path, relative_to_dir) +/* State provided by the diff drivers; used by the diff writer */ +typedef struct diff_driver_info_t +{ + /* The anchor to prefix before wc paths */ + const char *anchor; + + /* Relative path of ra session from repos_root_url. + + Used only in printing git diff headers. The repository-root-relative + path of ... ### what user-visible property of the diff? */ + const char *session_relpath; + + /* Used only in printing git diff headers. Used to find the + repository-root-relative path of a WC path. */ + svn_wc_context_t *wc_ctx; + + /* The original targets passed to the diff command. We may need + these to construct distinctive diff labels when comparing the + same relative path in the same revision, under different anchors + (for example, when comparing a trunk against a branch). */ + const char *orig_path_1; + const char *orig_path_2; +} diff_driver_info_t; + + /* Calculate the repository relative path of DIFF_RELPATH, using * SESSION_RELPATH and WC_CTX, and return the result in *REPOS_RELPATH. * ORIG_TARGET is the related original target passed to the diff command, @@ -120,25 +148,47 @@ make_repos_relpath(const char **repos_relpath, return SVN_NO_ERROR; } -/* Adjust *INDEX_PATH, *ORIG_PATH_1 and *ORIG_PATH_2, representing the changed - * node and the two original targets passed to the diff command, to handle the - * case when we're dealing with different anchors. RELATIVE_TO_DIR is the - * directory the diff target should be considered relative to. - * ANCHOR is the local path where the diff editor is anchored. The resulting - * values are allocated in RESULT_POOL and temporary allocations are performed - * in SCRATCH_POOL. */ +/* Adjust paths to handle the case when we're dealing with different anchors. + * + * Set *INDEX_PATH to the new relative path. Set *LABEL_PATH1 and + * *LABEL_PATH2 to that path annotated with the unique parts of ORIG_PATH_1 + * and ORIG_PATH_2 respectively, like this: + * + * INDEX_PATH: "path" + * LABEL_PATH1: "path\t(.../branches/branch1)" + * LABEL_PATH2: "path\t(.../trunk)" + * + * Make the output paths relative to RELATIVE_TO_DIR (if not null) by + * removing it from the beginning of (ANCHOR + RELPATH). + * + * ANCHOR (if not null) is the local path where the diff editor is anchored. + * RELPATH is the path to the changed node within the diff editor, so + * relative to ANCHOR. + * + * RELATIVE_TO_DIR and ANCHOR are of the same form -- either absolute local + * paths or relative paths relative to the same base. + * + * ORIG_PATH_1 and ORIG_PATH_2 represent the two original target paths or + * URLs passed to the diff command. + * + * Allocate results in RESULT_POOL (or as a pointer to RELPATH) and + * temporary data in SCRATCH_POOL. + */ static svn_error_t * adjust_paths_for_diff_labels(const char **index_path, - const char **orig_path_1, - const char **orig_path_2, + const char **label_path1, + const char **label_path2, const char *relative_to_dir, const char *anchor, + const char *relpath, + const char *orig_path_1, + const char *orig_path_2, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - const char *new_path = *index_path; - const char *new_path1 = *orig_path_1; - const char *new_path2 = *orig_path_2; + const char *new_path = relpath; + const char *new_path1 = orig_path_1; + const char *new_path2 = orig_path_2; if (anchor) new_path = svn_dirent_join(anchor, new_path, result_pool); @@ -177,6 +227,7 @@ adjust_paths_for_diff_labels(const char **index_path, /* ### BH: We can now just construct the repos_relpath, etc. as the anchor is available. See also make_repos_relpath() */ + /* Remove the common prefix of NEW_PATH1 and NEW_PATH2. */ is_url1 = svn_path_is_url(new_path1); is_url2 = svn_path_is_url(new_path2); @@ -220,8 +271,8 @@ adjust_paths_for_diff_labels(const char **index_path, new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2); *index_path = new_path; - *orig_path_1 = new_path1; - *orig_path_2 = new_path2; + *label_path1 = new_path1; + *label_path2 = new_path2; return SVN_NO_ERROR; } @@ -383,28 +434,33 @@ maybe_print_mode_change(svn_stream_t *os, } /* Print a git diff header showing the OPERATION to the stream OS using - * HEADER_ENCODING. Return suitable diff labels for the git diff in *LABEL1 - * and *LABEL2. REPOS_RELPATH1 and REPOS_RELPATH2 are relative to reposroot. - * are the paths passed to the original diff command. REV1 and REV2 are - * revisions being diffed. COPYFROM_PATH and COPYFROM_REV indicate where the + * HEADER_ENCODING. + * + * Return suitable diff labels for the git diff in *LABEL1 and *LABEL2. + * + * REV1 and REV2 are the revisions being diffed. + * COPYFROM_PATH and COPYFROM_REV indicate where the * diffed item was copied from. * Use SCRATCH_POOL for temporary allocations. */ static svn_error_t * print_git_diff_header(svn_stream_t *os, const char **label1, const char **label2, svn_diff_operation_kind_t operation, - const char *repos_relpath1, - const char *repos_relpath2, svn_revnum_t rev1, svn_revnum_t rev2, + const char *diff_relpath, const char *copyfrom_path, svn_revnum_t copyfrom_rev, apr_hash_t *left_props, apr_hash_t *right_props, const char *git_index_shas, const char *header_encoding, + const diff_driver_info_t *ddi, apr_pool_t *scratch_pool) { + const char *repos_relpath1; + const char *repos_relpath2; + const char *copyfrom_repos_relpath = NULL; svn_boolean_t exec_bit1 = (svn_prop_get_value(left_props, SVN_PROP_EXECUTABLE) != NULL); svn_boolean_t exec_bit2 = (svn_prop_get_value(right_props, @@ -414,6 +470,26 @@ print_git_diff_header(svn_stream_t *os, svn_boolean_t symlink_bit2 = (svn_prop_get_value(right_props, SVN_PROP_SPECIAL) != NULL); + SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, + ddi->orig_path_1, + ddi->session_relpath, + ddi->wc_ctx, + ddi->anchor, + scratch_pool, scratch_pool)); + SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, + ddi->orig_path_2, + ddi->session_relpath, + ddi->wc_ctx, + ddi->anchor, + scratch_pool, scratch_pool)); + if (copyfrom_path) + SVN_ERR(make_repos_relpath(©from_repos_relpath, copyfrom_path, + ddi->orig_path_2, + ddi->session_relpath, + ddi->wc_ctx, + ddi->anchor, + scratch_pool, scratch_pool)); + if (operation == svn_diff_op_deleted) { SVN_ERR(print_git_diff_header_deleted(os, header_encoding, @@ -487,26 +563,45 @@ print_git_diff_header(svn_stream_t *os, return SVN_NO_ERROR; } +/* Print the "Index:" and "=====" lines. + * Show the paths in platform-independent format ('/' separators) + */ +static svn_error_t * +print_diff_index_header(svn_stream_t *outstream, + const char *header_encoding, + const char *index_path, + const char *suffix, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_stream_printf_from_utf8(outstream, + header_encoding, scratch_pool, + "Index: %s%s" APR_EOL_STR + SVN_DIFF__EQUAL_STRING APR_EOL_STR, + index_path, suffix)); + return SVN_NO_ERROR; +} + /* A helper func that writes out verbal descriptions of property diffs to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was - passed to svn_client_diff6(), which is probably stdout. + passed to svn_client_diff7(), which is probably stdout. ### FIXME needs proper docstring If USE_GIT_DIFF_FORMAT is TRUE, pring git diff headers, which always - show paths relative to the repository root. RA_SESSION and WC_CTX are - needed to normalize paths relative the repository root, and are ignored - if USE_GIT_DIFF_FORMAT is FALSE. - - ANCHOR is the local path where the diff editor is anchored. */ + show paths relative to the repository root. DDI->session_relpath and + DDI->wc_ctx are needed to normalize paths relative the repository root, + and are ignored if USE_GIT_DIFF_FORMAT is FALSE. + + If @a pretty_print_mergeinfo is true, then describe 'svn:mergeinfo' + property changes in a human-readable form that says what changes were + merged or reverse merged; otherwise (or if the mergeinfo property values + don't parse correctly) display them just like any other property. + */ static svn_error_t * display_prop_diffs(const apr_array_header_t *propchanges, apr_hash_t *left_props, apr_hash_t *right_props, const char *diff_relpath, - const char *anchor, - const char *orig_path1, - const char *orig_path2, svn_revnum_t rev1, svn_revnum_t rev2, const char *encoding, @@ -514,32 +609,29 @@ display_prop_diffs(const apr_array_header_t *propchanges, const char *relative_to_dir, svn_boolean_t show_diff_header, svn_boolean_t use_git_diff_format, - const char *ra_session_relpath, + svn_boolean_t pretty_print_mergeinfo, + const diff_driver_info_t *ddi, svn_cancel_func_t cancel_func, void *cancel_baton, - svn_wc_context_t *wc_ctx, apr_pool_t *scratch_pool) { const char *repos_relpath1 = NULL; - const char *repos_relpath2 = NULL; - const char *index_path = diff_relpath; - const char *adjusted_path1 = orig_path1; - const char *adjusted_path2 = orig_path2; + const char *index_path; + const char *label_path1, *label_path2; if (use_git_diff_format) { - SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, orig_path1, - ra_session_relpath, wc_ctx, anchor, - scratch_pool, scratch_pool)); - SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, orig_path2, - ra_session_relpath, wc_ctx, anchor, + SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, ddi->orig_path_1, + ddi->session_relpath, ddi->wc_ctx, ddi->anchor, scratch_pool, scratch_pool)); } /* If we're creating a diff on the wc root, path would be empty. */ - SVN_ERR(adjust_paths_for_diff_labels(&index_path, &adjusted_path1, - &adjusted_path2, - relative_to_dir, anchor, + SVN_ERR(adjust_paths_for_diff_labels(&index_path, + &label_path1, &label_path2, + relative_to_dir, ddi->anchor, + diff_relpath, + ddi->orig_path_1, ddi->orig_path_2, scratch_pool, scratch_pool)); if (show_diff_header) @@ -547,27 +639,21 @@ display_prop_diffs(const apr_array_header_t *propchanges, const char *label1; const char *label2; - label1 = diff_label(adjusted_path1, rev1, scratch_pool); - label2 = diff_label(adjusted_path2, rev2, scratch_pool); - - /* ### Should we show the paths in platform specific format, - * ### diff_content_changed() does not! */ + label1 = diff_label(label_path1, rev1, scratch_pool); + label2 = diff_label(label_path2, rev2, scratch_pool); - SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool, - "Index: %s" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); + SVN_ERR(print_diff_index_header(outstream, encoding, + index_path, "", scratch_pool)); if (use_git_diff_format) SVN_ERR(print_git_diff_header(outstream, &label1, &label2, svn_diff_op_modified, - repos_relpath1, repos_relpath2, - rev1, rev2, NULL, - SVN_INVALID_REVNUM, - left_props, - right_props, + rev1, rev2, + diff_relpath, + NULL, SVN_INVALID_REVNUM, + left_props, right_props, NULL, - encoding, scratch_pool)); + encoding, ddi, scratch_pool)); /* --- label1 * +++ label2 */ @@ -588,7 +674,7 @@ display_prop_diffs(const apr_array_header_t *propchanges, SVN_ERR(svn_diff__display_prop_diffs( outstream, encoding, propchanges, left_props, - TRUE /* pretty_print_mergeinfo */, + pretty_print_mergeinfo, -1 /* context_size */, cancel_func, cancel_baton, scratch_pool)); @@ -599,24 +685,6 @@ display_prop_diffs(const apr_array_header_t *propchanges, /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/ -/* State provided by the diff drivers; used by the diff writer */ -typedef struct diff_driver_info_t -{ - /* The anchor to prefix before wc paths */ - const char *anchor; - - /* Relative path of ra session from repos_root_url */ - const char *session_relpath; - - /* The original targets passed to the diff command. We may need - these to construct distinctive diff labels when comparing the - same relative path in the same revision, under different anchors - (for example, when comparing a trunk against a branch). */ - const char *orig_path_1; - const char *orig_path_2; -} diff_driver_info_t; - - /* Diff writer state */ typedef struct diff_writer_info_t { @@ -668,11 +736,12 @@ typedef struct diff_writer_info_t /* Whether to ignore copyfrom information when showing adds */ svn_boolean_t show_copies_as_adds; + /* Whether to show mergeinfo prop changes in human-readable form */ + svn_boolean_t pretty_print_mergeinfo; + /* Empty files for creating diffs or NULL if not used yet */ const char *empty_file; - svn_wc_context_t *wc_ctx; - svn_cancel_func_t cancel_func; void *cancel_baton; @@ -708,9 +777,6 @@ diff_props_changed(const char *diff_relpath, * dir_props_changed(). */ SVN_ERR(display_prop_diffs(props, left_props, right_props, diff_relpath, - dwi->ddi.anchor, - dwi->ddi.orig_path_1, - dwi->ddi.orig_path_2, rev1, rev2, dwi->header_encoding, @@ -718,10 +784,10 @@ diff_props_changed(const char *diff_relpath, dwi->relative_to_dir, show_diff_header, dwi->use_git_diff_format, - dwi->ddi.session_relpath, + dwi->pretty_print_mergeinfo, + &dwi->ddi, dwi->cancel_func, dwi->cancel_baton, - dwi->wc_ctx, scratch_pool)); } @@ -785,9 +851,12 @@ transform_link_to_git(const char **new_tmpfile, } /* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and - REV2 are used in the headers to indicate the file and revisions. If either - MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff, - but instead print a warning message. + REV2 are used in the headers to indicate the file and revisions. + + If either side has an svn:mime-type property that indicates 'binary' + content, then if DWI->force_binary is set, attempt to produce the + diff in the usual way, otherwise produce a 'GIT binary diff' in git mode + or print a warning message in non-git mode. If FORCE_DIFF is TRUE, always write a diff, even for empty diffs. @@ -812,9 +881,8 @@ diff_content_changed(svn_boolean_t *wrote_header, svn_stream_t *outstream = dwi->outstream; const char *label1, *label2; svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE; - const char *index_path = diff_relpath; - const char *path1 = dwi->ddi.orig_path_1; - const char *path2 = dwi->ddi.orig_path_2; + const char *index_path; + const char *label_path1, *label_path2; const char *mimetype1 = svn_prop_get_value(left_props, SVN_PROP_MIME_TYPE); const char *mimetype2 = svn_prop_get_value(right_props, SVN_PROP_MIME_TYPE); const char *index_shas = NULL; @@ -824,12 +892,15 @@ diff_content_changed(svn_boolean_t *wrote_header, return SVN_NO_ERROR; /* Generate the diff headers. */ - SVN_ERR(adjust_paths_for_diff_labels(&index_path, &path1, &path2, + SVN_ERR(adjust_paths_for_diff_labels(&index_path, + &label_path1, &label_path2, rel_to_dir, dwi->ddi.anchor, + diff_relpath, + dwi->ddi.orig_path_1, dwi->ddi.orig_path_2, scratch_pool, scratch_pool)); - label1 = diff_label(path1, rev1, scratch_pool); - label2 = diff_label(path2, rev2, scratch_pool); + label1 = diff_label(label_path1, rev1, scratch_pool); + label2 = diff_label(label_path2, rev2, scratch_pool); /* Possible easy-out: if either mime-type is binary and force was not specified, don't attempt to generate a viewable diff at all. @@ -869,12 +940,8 @@ diff_content_changed(svn_boolean_t *wrote_header, if (! dwi->force_binary && (mt1_binary || mt2_binary)) { /* Print out the diff header. */ - SVN_ERR(svn_stream_printf_from_utf8(outstream, - dwi->header_encoding, scratch_pool, - "Index: %s" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); - + SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, + index_path, "", scratch_pool)); *wrote_header = TRUE; /* ### Print git diff headers. */ @@ -883,40 +950,17 @@ diff_content_changed(svn_boolean_t *wrote_header, { svn_stream_t *left_stream; svn_stream_t *right_stream; - const char *repos_relpath1; - const char *repos_relpath2; - const char *copyfrom_repos_relpath = NULL; - - SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, - dwi->ddi.orig_path_1, - dwi->ddi.session_relpath, - dwi->wc_ctx, - dwi->ddi.anchor, - scratch_pool, scratch_pool)); - SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, - dwi->ddi.orig_path_2, - dwi->ddi.session_relpath, - dwi->wc_ctx, - dwi->ddi.anchor, - scratch_pool, scratch_pool)); - if (copyfrom_path) - SVN_ERR(make_repos_relpath(©from_repos_relpath, copyfrom_path, - dwi->ddi.orig_path_2, - dwi->ddi.session_relpath, - dwi->wc_ctx, - dwi->ddi.anchor, - scratch_pool, scratch_pool)); - SVN_ERR(print_git_diff_header(outstream, &label1, &label2, + + SVN_ERR(print_git_diff_header(outstream, + &label1, &label2, operation, - repos_relpath1, repos_relpath2, rev1, rev2, - copyfrom_repos_relpath, - copyfrom_rev, - left_props, - right_props, + diff_relpath, + copyfrom_path, copyfrom_rev, + left_props, right_props, index_shas, dwi->header_encoding, - scratch_pool)); + &dwi->ddi, scratch_pool)); SVN_ERR(svn_stream_open_readonly(&left_stream, tmpfile1, scratch_pool, scratch_pool)); @@ -973,11 +1017,9 @@ diff_content_changed(svn_boolean_t *wrote_header, int exitcode; /* Print out the diff header. */ - SVN_ERR(svn_stream_printf_from_utf8(outstream, - dwi->header_encoding, scratch_pool, - "Index: %s" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); + SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, + index_path, "", scratch_pool)); + *wrote_header = TRUE; /* ### Do we want to add git diff headers here too? I'd say no. The * ### 'Index' and '===' line is something subversion has added. The rest @@ -1030,10 +1072,6 @@ diff_content_changed(svn_boolean_t *wrote_header, scratch_pool), NULL, NULL, scratch_pool)); } - - /* If we have printed a diff for this path, mark it as visited. */ - if (exitcode == 1) - *wrote_header = TRUE; } else /* use libsvn_diff to generate the diff */ { @@ -1048,49 +1086,22 @@ diff_content_changed(svn_boolean_t *wrote_header, || svn_diff_contains_diffs(diff)) { /* Print out the diff header. */ - SVN_ERR(svn_stream_printf_from_utf8(outstream, - dwi->header_encoding, scratch_pool, - "Index: %s" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); + SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, + index_path, "", scratch_pool)); + *wrote_header = TRUE; if (dwi->use_git_diff_format) { - const char *repos_relpath1; - const char *repos_relpath2; - const char *copyfrom_repos_relpath = NULL; - - SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, - dwi->ddi.orig_path_1, - dwi->ddi.session_relpath, - dwi->wc_ctx, - dwi->ddi.anchor, - scratch_pool, scratch_pool)); - SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, - dwi->ddi.orig_path_2, - dwi->ddi.session_relpath, - dwi->wc_ctx, - dwi->ddi.anchor, - scratch_pool, scratch_pool)); - if (copyfrom_path) - SVN_ERR(make_repos_relpath(©from_repos_relpath, - copyfrom_path, - dwi->ddi.orig_path_2, - dwi->ddi.session_relpath, - dwi->wc_ctx, - dwi->ddi.anchor, - scratch_pool, scratch_pool)); - SVN_ERR(print_git_diff_header(outstream, &label1, &label2, + SVN_ERR(print_git_diff_header(outstream, + &label1, &label2, operation, - repos_relpath1, repos_relpath2, rev1, rev2, - copyfrom_repos_relpath, - copyfrom_rev, - left_props, - right_props, + diff_relpath, + copyfrom_path, copyfrom_rev, + left_props, right_props, index_shas, dwi->header_encoding, - scratch_pool)); + &dwi->ddi, scratch_pool)); } /* Output the actual diff */ @@ -1102,10 +1113,6 @@ diff_content_changed(svn_boolean_t *wrote_header, dwi->options.for_internal->context_size, dwi->cancel_func, dwi->cancel_baton, scratch_pool)); - - /* If we have printed a diff for this path, mark it as visited. */ - if (dwi->use_git_diff_format || svn_diff_contains_diffs(diff)) - *wrote_header = TRUE; } } @@ -1180,11 +1187,9 @@ diff_file_added(const char *relpath, index_path = svn_dirent_join(dwi->ddi.anchor, relpath, scratch_pool); - SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream, - dwi->header_encoding, scratch_pool, - "Index: %s (added)" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); + SVN_ERR(print_diff_index_header(dwi->outstream, dwi->header_encoding, + index_path, " (added)", + scratch_pool)); wrote_header = TRUE; return SVN_NO_ERROR; } @@ -1270,11 +1275,9 @@ diff_file_deleted(const char *relpath, index_path = svn_dirent_join(dwi->ddi.anchor, relpath, scratch_pool); - SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream, - dwi->header_encoding, scratch_pool, - "Index: %s (deleted)" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); + SVN_ERR(print_diff_index_header(dwi->outstream, dwi->header_encoding, + index_path, " (deleted)", + scratch_pool)); } else { @@ -1537,11 +1540,22 @@ check_diff_target_exists(const char *url, /** Prepare a repos repos diff between PATH_OR_URL1 and * PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2. - * Return URLs and peg revisions in *URL1, *REV1 and in *URL2, *REV2. - * Return suitable anchors in *ANCHOR1 and *ANCHOR2, and targets in - * *TARGET1 and *TARGET2, based on *URL1 and *URL2. - * Indicate the corresponding node kinds in *KIND1 and *KIND2, and verify + * + * Return the resolved URL and peg revision pairs in *URL1, *REV1 and in + * *URL2, *REV2. + * + * Return suitable anchor URL and target pairs in *ANCHOR1, *TARGET1 and + * in *ANCHOR2, *TARGET2, corresponding to *URL1 and *URL2. + * + * (The choice of anchor URLs here appears to be: start with *URL1, *URL2; + * then take the parent dir on both sides, unless either of *URL1 or *URL2 + * is the repository root or the parent dir of *URL1 is unreadable.) + * + * Set *KIND1 and *KIND2 to the node kinds of *URL1 and *URL2, and verify * that at least one of the diff targets exists. + * + * Set *RA_SESSION to an RA session parented at the URL *ANCHOR1. + * * Use client context CTX. Do all allocations in POOL. */ static svn_error_t * diff_prepare_repos_repos(const char **url1, @@ -1766,8 +1780,8 @@ diff_prepare_repos_repos(const char **url1, /* A Theoretical Note From Ben, regarding do_diff(). - This function is really svn_client_diff6(). If you read the public - API description for svn_client_diff6(), it sounds quite Grand. It + This function is really svn_client_diff7(). If you read the public + API description for svn_client_diff7(), it sounds quite Grand. It sounds really generalized and abstract and beautiful: that it will diff any two paths, be they working-copy paths or URLs, at any two revisions. @@ -1791,7 +1805,7 @@ diff_prepare_repos_repos(const char **url1, pigeonholed into one of these use-cases, we currently bail with a friendly apology. - Perhaps someday a brave soul will truly make svn_client_diff6() + Perhaps someday a brave soul will truly make svn_client_diff7() perfectly general. For now, we live with the 90% case. Certainly, the commandline client only calls this function in legal ways. When there are other users of svn_client.h, maybe this will become @@ -1804,7 +1818,7 @@ static svn_error_t * unsupported_diff_error(svn_error_t *child_err) { return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err, - _("Sorry, svn_client_diff6 was called in a way " + _("Sorry, svn_client_diff7 was called in a way " "that is not yet supported")); } @@ -1813,12 +1827,14 @@ unsupported_diff_error(svn_error_t *child_err) PATH1 and PATH2 are both working copy paths. REVISION1 and REVISION2 are their respective revisions. - All other options are the same as those passed to svn_client_diff6(). */ + For now, require PATH1=PATH2, REVISION1='base', REVISION2='working', + otherwise return an error. + + Anchor DIFF_PROCESSOR at the requested diff targets. + + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * -diff_wc_wc(const char **root_relpath, - svn_boolean_t *root_is_dir, - struct diff_driver_info_t *ddi, - const char *path1, +diff_wc_wc(const char *path1, const svn_opt_revision_t *revision1, const char *path2, const svn_opt_revision_t *revision2, @@ -1844,24 +1860,12 @@ diff_wc_wc(const char **root_relpath, && (revision2->kind == svn_opt_revision_working)))) return unsupported_diff_error( svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, - _("Only diffs between a path's text-base " - "and its working files are supported at this time" + _("A non-URL diff at this time must be either from " + "a path's base to the same path's working version " + "or between the working versions of two paths" ))); - if (ddi) - { - svn_node_kind_t kind; - - SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1, - TRUE, FALSE, scratch_pool)); - - if (kind != svn_node_dir) - ddi->anchor = svn_dirent_dirname(path1, scratch_pool); - else - ddi->anchor = path1; - } - - SVN_ERR(svn_wc__diff7(root_relpath, root_is_dir, + SVN_ERR(svn_wc__diff7(TRUE, ctx->wc_ctx, abspath1, depth, ignore_ancestry, changelists, diff_processor, @@ -1878,11 +1882,31 @@ diff_wc_wc(const char **root_relpath, and the actual two paths compared are determined by following copy history from PATH_OR_URL2. - All other options are the same as those passed to svn_client_diff6(). */ + If DDI is null, anchor the DIFF_PROCESSOR at the requested diff + targets. (This case is used by diff-summarize.) + + If DDI is non-null: Set DDI->orig_path_* to the two diff target URLs as + resolved at the given revisions; set DDI->anchor to an anchor WC path + if either of PATH_OR_URL* is given as a WC path, else to null; set + DDI->session_relpath to the repository-relpath of the anchor URL for + DDI->orig_path_1. Anchor the DIFF_PROCESSOR at the anchor chosen + for the underlying diff implementation if the target on either side + is a file, else at the actual requested targets. + + (The choice of WC anchor implementated here for DDI->anchor appears to + be: choose PATH_OR_URL2 (if it's a WC path) or else PATH_OR_URL1 (if + it's a WC path); then take its parent dir unless both resolved URLs + refer to directories.) + + (For the choice of URL anchor for DDI->session_relpath, see + diff_prepare_repos_repos().) + + ### Bizarre anchoring. TODO: always anchor DIFF_PROCESSOR at the + requested targets. + + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * -diff_repos_repos(const char **root_relpath, - svn_boolean_t *root_is_dir, - struct diff_driver_info_t *ddi, +diff_repos_repos(struct diff_driver_info_t *ddi, const char *path_or_url1, const char *path_or_url2, const svn_opt_revision_t *revision1, @@ -1974,14 +1998,16 @@ diff_repos_repos(const char **root_relpath, target1 = str_tmp; diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, - NULL, scratch_pool); } /* Filter the first path component using a filter processor, until we fixed the diff processing to handle this directly */ - if (root_relpath) - *root_relpath = apr_pstrdup(result_pool, target1); + if (!ddi) + { + diff_processor = svn_diff__tree_processor_filter_create( + diff_processor, target1, scratch_pool); + } else if ((kind1 != svn_node_file && kind2 != svn_node_file) && target1[0] != '\0') { @@ -2048,11 +2074,17 @@ diff_repos_repos(const char **root_relpath, If REVERSE is TRUE, the diff will be reported in reverse. - All other options are the same as those passed to svn_client_diff6(). */ + If DDI is null, anchor the DIFF_PROCESSOR at the requested diff + targets. (This case is used by diff-summarize.) + + If DDI is non-null: Set DDI->orig_path_* to the URLs of the two diff + targets as resolved at the given revisions; set DDI->anchor to a WC path + anchor for PATH2; set DDI->session_relpath to the repository-relpath of + the URL of that same anchor WC path. + + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * -diff_repos_wc(const char **root_relpath, - svn_boolean_t *root_is_dir, - struct diff_driver_info_t *ddi, +diff_repos_wc(struct diff_driver_info_t *ddi, const char *path_or_url1, const svn_opt_revision_t *revision1, const svn_opt_revision_t *peg_revision1, @@ -2130,11 +2162,6 @@ diff_repos_wc(const char **root_relpath, target = ""; } - if (root_relpath) - *root_relpath = apr_pstrdup(result_pool, target); - if (root_is_dir) - *root_is_dir = (*target == '\0'); - /* Fetch the URL of the anchor directory. */ SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, scratch_pool)); SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath, @@ -2143,7 +2170,7 @@ diff_repos_wc(const char **root_relpath, target_url = NULL; } - else /* is_copy && revision2->kind == svn_opt_revision_base */ + else /* is_copy && revision2->kind != svn_opt_revision_base */ { #if 0 svn_node_kind_t kind; @@ -2232,10 +2259,14 @@ diff_repos_wc(const char **root_relpath, anchor_url, result_pool); } + else + { + diff_processor = svn_diff__tree_processor_filter_create( + diff_processor, target, scratch_pool); + } if (reverse) - diff_processor = svn_diff__tree_processor_reverse_create( - diff_processor, NULL, scratch_pool); + diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, scratch_pool); /* Use the diff editor to generate the diff. */ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, @@ -2311,12 +2342,84 @@ diff_repos_wc(const char **root_relpath, return SVN_NO_ERROR; } +/* Run diff on shelf SHELF_NAME, if it exists. + */ +static svn_error_t * +diff_shelf(const char *shelf_name, + const char *target_abspath, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + const svn_diff_tree_processor_t *diff_processor, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_client__shelf_t *shelf; + svn_client__shelf_version_t *shelf_version; + const char *wc_relpath; + + err = svn_client__shelf_open_existing(&shelf, + shelf_name, target_abspath, + ctx, scratch_pool); + if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + SVN_ERR(svn_client__shelf_version_open(&shelf_version, + shelf, shelf->max_version, + scratch_pool, scratch_pool)); + wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath); + SVN_ERR(svn_client__shelf_diff(shelf_version, wc_relpath, + depth, ignore_ancestry, + diff_processor, scratch_pool)); + SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Run diff on all shelves named in CHANGELISTS by a changelist name + * of the form "svn:shelf:SHELF_NAME", if they exist. + */ +static svn_error_t * +diff_shelves(const apr_array_header_t *changelists, + const char *target_abspath, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + const svn_diff_tree_processor_t *diff_processor, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + static const char PREFIX[] = "svn:shelf:"; + static const int PREFIX_LEN = 10; + int i; + + if (! changelists) + return SVN_NO_ERROR; + for (i = 0; i < changelists->nelts; i++) + { + const char *cl = APR_ARRAY_IDX(changelists, i, const char *); + + if (strncmp(cl, PREFIX, PREFIX_LEN) == 0) + { + const char *shelf_name = cl + PREFIX_LEN; + + SVN_ERR(diff_shelf(shelf_name, target_abspath, + depth, ignore_ancestry, + diff_processor, ctx, scratch_pool)); + } + } + + return SVN_NO_ERROR; +} + /* This is basically just the guts of svn_client_diff[_summarize][_peg]6(). */ static svn_error_t * -do_diff(const char **root_relpath, - svn_boolean_t *root_is_dir, - diff_driver_info_t *ddi, +do_diff(diff_driver_info_t *ddi, const char *path_or_url1, const char *path_or_url2, const svn_opt_revision_t *revision1, @@ -2344,8 +2447,7 @@ do_diff(const char **root_relpath, if (is_repos2) { /* Ignores changelists. */ - SVN_ERR(diff_repos_repos(root_relpath, root_is_dir, - ddi, + SVN_ERR(diff_repos_repos(ddi, path_or_url1, path_or_url2, revision1, revision2, peg_revision, depth, ignore_ancestry, @@ -2355,7 +2457,7 @@ do_diff(const char **root_relpath, } else /* path_or_url2 is a working copy path */ { - SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi, + SVN_ERR(diff_repos_wc(ddi, path_or_url1, revision1, no_peg_revision ? revision1 : peg_revision, @@ -2370,7 +2472,7 @@ do_diff(const char **root_relpath, { if (is_repos2) { - SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi, + SVN_ERR(diff_repos_wc(ddi, path_or_url2, revision2, no_peg_revision ? revision2 : peg_revision, @@ -2394,19 +2496,51 @@ do_diff(const char **root_relpath, SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, scratch_pool)); - /* ### What about ddi? */ + if (ddi) + { + svn_node_kind_t kind1, kind2; + + SVN_ERR(svn_io_check_resolved_path(abspath1, &kind1, + scratch_pool)); + SVN_ERR(svn_io_check_resolved_path(abspath2, &kind2, + scratch_pool)); + if (kind1 == svn_node_dir && kind2 == svn_node_dir) + { + ddi->anchor = ""; + } + else + { + ddi->anchor = svn_dirent_basename(abspath1, NULL); + } + ddi->orig_path_1 = path_or_url1; + ddi->orig_path_2 = path_or_url2; + } + /* Ignores changelists, ignore_ancestry */ - SVN_ERR(svn_client__arbitrary_nodes_diff(root_relpath, root_is_dir, - abspath1, abspath2, + SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2, depth, diff_processor, - ctx, - result_pool, scratch_pool)); + ctx, scratch_pool)); } else { - SVN_ERR(diff_wc_wc(root_relpath, root_is_dir, ddi, - path_or_url1, revision1, + if (ddi) + { + ddi->anchor = path_or_url1; + ddi->orig_path_1 = path_or_url1; + ddi->orig_path_2 = path_or_url2; + } + + { + const char *abspath1; + + SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, + scratch_pool)); + SVN_ERR(diff_shelves(changelists, abspath1, + depth, ignore_ancestry, + diff_processor, ctx, scratch_pool)); + } + SVN_ERR(diff_wc_wc(path_or_url1, revision1, path_or_url2, revision2, depth, ignore_ancestry, changelists, diff_processor, ctx, @@ -2482,6 +2616,115 @@ create_diff_writer_info(diff_writer_info_t *dwi, return SVN_NO_ERROR; } +/* Set up *DIFF_PROCESSOR and *DDI for normal and git-style diffs (but not + * summary diffs). + */ +static svn_error_t * +get_diff_processor(svn_diff_tree_processor_t **diff_processor, + struct diff_driver_info_t **ddi, + const apr_array_header_t *options, + const char *relative_to_dir, + svn_boolean_t no_diff_added, + svn_boolean_t no_diff_deleted, + svn_boolean_t show_copies_as_adds, + svn_boolean_t ignore_content_type, + svn_boolean_t ignore_properties, + svn_boolean_t properties_only, + svn_boolean_t use_git_diff_format, + svn_boolean_t pretty_print_mergeinfo, + const char *header_encoding, + svn_stream_t *outstream, + svn_stream_t *errstream, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + diff_writer_info_t *dwi = apr_pcalloc(pool, sizeof(*dwi)); + svn_diff_tree_processor_t *processor; + + /* setup callback and baton */ + + SVN_ERR(create_diff_writer_info(dwi, options, + ctx->config, pool)); + dwi->pool = pool; + dwi->outstream = outstream; + dwi->errstream = errstream; + dwi->header_encoding = header_encoding; + + dwi->force_binary = ignore_content_type; + dwi->ignore_properties = ignore_properties; + dwi->properties_only = properties_only; + dwi->relative_to_dir = relative_to_dir; + dwi->use_git_diff_format = use_git_diff_format; + dwi->no_diff_added = no_diff_added; + dwi->no_diff_deleted = no_diff_deleted; + dwi->show_copies_as_adds = show_copies_as_adds; + dwi->pretty_print_mergeinfo = pretty_print_mergeinfo; + + dwi->cancel_func = ctx->cancel_func; + dwi->cancel_baton = ctx->cancel_baton; + + dwi->ddi.wc_ctx = ctx->wc_ctx; + dwi->ddi.session_relpath = NULL; + dwi->ddi.anchor = NULL; + + processor = svn_diff__tree_processor_create(dwi, pool); + + processor->dir_added = diff_dir_added; + processor->dir_changed = diff_dir_changed; + processor->dir_deleted = diff_dir_deleted; + + processor->file_added = diff_file_added; + processor->file_changed = diff_file_changed; + processor->file_deleted = diff_file_deleted; + + *diff_processor = processor; + *ddi = &dwi->ddi; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__get_diff_writer_svn( + svn_diff_tree_processor_t **diff_processor, + const char *anchor, + const char *orig_path_1, + const char *orig_path_2, + const apr_array_header_t *options, + const char *relative_to_dir, + svn_boolean_t no_diff_added, + svn_boolean_t no_diff_deleted, + svn_boolean_t show_copies_as_adds, + svn_boolean_t ignore_content_type, + svn_boolean_t ignore_properties, + svn_boolean_t properties_only, + svn_boolean_t pretty_print_mergeinfo, + const char *header_encoding, + svn_stream_t *outstream, + svn_stream_t *errstream, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + struct diff_driver_info_t *ddi; + + SVN_ERR(get_diff_processor(diff_processor, &ddi, + options, + relative_to_dir, + no_diff_added, + no_diff_deleted, + show_copies_as_adds, + ignore_content_type, + ignore_properties, + properties_only, + FALSE /*use_git_diff_format*/, + pretty_print_mergeinfo, + header_encoding, + outstream, errstream, + ctx, pool)); + ddi->anchor = anchor; + ddi->orig_path_1 = orig_path_1; + ddi->orig_path_2 = orig_path_2; + return SVN_NO_ERROR; +} + /*----------------------------------------------------------------------- */ /*** Public Interfaces. ***/ @@ -2519,7 +2762,7 @@ create_diff_writer_info(diff_writer_info_t *dwi, * These cases require server communication. */ svn_error_t * -svn_client_diff6(const apr_array_header_t *options, +svn_client_diff7(const apr_array_header_t *options, const char *path_or_url1, const svn_opt_revision_t *revision1, const char *path_or_url2, @@ -2534,6 +2777,7 @@ svn_client_diff6(const apr_array_header_t *options, svn_boolean_t ignore_properties, svn_boolean_t properties_only, svn_boolean_t use_git_diff_format, + svn_boolean_t pretty_print_mergeinfo, const char *header_encoding, svn_stream_t *outstream, svn_stream_t *errstream, @@ -2541,10 +2785,9 @@ svn_client_diff6(const apr_array_header_t *options, svn_client_ctx_t *ctx, apr_pool_t *pool) { - diff_writer_info_t dwi = { 0 }; svn_opt_revision_t peg_revision; - const svn_diff_tree_processor_t *diff_processor; - svn_diff_tree_processor_t *processor; + svn_diff_tree_processor_t *diff_processor; + struct diff_driver_info_t *ddi; if (ignore_properties && properties_only) return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, @@ -2554,50 +2797,26 @@ svn_client_diff6(const apr_array_header_t *options, /* We will never do a pegged diff from here. */ peg_revision.kind = svn_opt_revision_unspecified; - /* setup callback and baton */ - dwi.ddi.orig_path_1 = path_or_url1; - dwi.ddi.orig_path_2 = path_or_url2; - - SVN_ERR(create_diff_writer_info(&dwi, options, - ctx->config, pool)); - dwi.pool = pool; - dwi.outstream = outstream; - dwi.errstream = errstream; - dwi.header_encoding = header_encoding; - - dwi.force_binary = ignore_content_type; - dwi.ignore_properties = ignore_properties; - dwi.properties_only = properties_only; - dwi.relative_to_dir = relative_to_dir; - dwi.use_git_diff_format = use_git_diff_format; - dwi.no_diff_added = no_diff_added; - dwi.no_diff_deleted = no_diff_deleted; - dwi.show_copies_as_adds = show_copies_as_adds; - - dwi.cancel_func = ctx->cancel_func; - dwi.cancel_baton = ctx->cancel_baton; - - dwi.wc_ctx = ctx->wc_ctx; - dwi.ddi.session_relpath = NULL; - dwi.ddi.anchor = NULL; - - processor = svn_diff__tree_processor_create(&dwi, pool); - - processor->dir_added = diff_dir_added; - processor->dir_changed = diff_dir_changed; - processor->dir_deleted = diff_dir_deleted; - - processor->file_added = diff_file_added; - processor->file_changed = diff_file_changed; - processor->file_deleted = diff_file_deleted; - - diff_processor = processor; - /* --show-copies-as-adds and --git imply --notice-ancestry */ if (show_copies_as_adds || use_git_diff_format) ignore_ancestry = FALSE; - return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi, + SVN_ERR(get_diff_processor(&diff_processor, &ddi, + options, + relative_to_dir, + no_diff_added, + no_diff_deleted, + show_copies_as_adds, + ignore_content_type, + ignore_properties, + properties_only, + use_git_diff_format, + pretty_print_mergeinfo, + header_encoding, + outstream, errstream, + ctx, pool)); + + return svn_error_trace(do_diff(ddi, path_or_url1, path_or_url2, revision1, revision2, &peg_revision, TRUE /* no_peg_revision */, @@ -2607,7 +2826,7 @@ svn_client_diff6(const apr_array_header_t *options, } svn_error_t * -svn_client_diff_peg6(const apr_array_header_t *options, +svn_client_diff_peg7(const apr_array_header_t *options, const char *path_or_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *start_revision, @@ -2622,6 +2841,7 @@ svn_client_diff_peg6(const apr_array_header_t *options, svn_boolean_t ignore_properties, svn_boolean_t properties_only, svn_boolean_t use_git_diff_format, + svn_boolean_t pretty_print_mergeinfo, const char *header_encoding, svn_stream_t *outstream, svn_stream_t *errstream, @@ -2629,59 +2849,34 @@ svn_client_diff_peg6(const apr_array_header_t *options, svn_client_ctx_t *ctx, apr_pool_t *pool) { - diff_writer_info_t dwi = { 0 }; - const svn_diff_tree_processor_t *diff_processor; - svn_diff_tree_processor_t *processor; + svn_diff_tree_processor_t *diff_processor; + struct diff_driver_info_t *ddi; if (ignore_properties && properties_only) return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, _("Cannot ignore properties and show only " "properties at the same time")); - /* setup callback and baton */ - dwi.ddi.orig_path_1 = path_or_url; - dwi.ddi.orig_path_2 = path_or_url; - - SVN_ERR(create_diff_writer_info(&dwi, options, - ctx->config, pool)); - dwi.pool = pool; - dwi.outstream = outstream; - dwi.errstream = errstream; - dwi.header_encoding = header_encoding; - - dwi.force_binary = ignore_content_type; - dwi.ignore_properties = ignore_properties; - dwi.properties_only = properties_only; - dwi.relative_to_dir = relative_to_dir; - dwi.use_git_diff_format = use_git_diff_format; - dwi.no_diff_added = no_diff_added; - dwi.no_diff_deleted = no_diff_deleted; - dwi.show_copies_as_adds = show_copies_as_adds; - - dwi.cancel_func = ctx->cancel_func; - dwi.cancel_baton = ctx->cancel_baton; - - dwi.wc_ctx = ctx->wc_ctx; - dwi.ddi.session_relpath = NULL; - dwi.ddi.anchor = NULL; - - processor = svn_diff__tree_processor_create(&dwi, pool); - - processor->dir_added = diff_dir_added; - processor->dir_changed = diff_dir_changed; - processor->dir_deleted = diff_dir_deleted; - - processor->file_added = diff_file_added; - processor->file_changed = diff_file_changed; - processor->file_deleted = diff_file_deleted; - - diff_processor = processor; - /* --show-copies-as-adds and --git imply --notice-ancestry */ if (show_copies_as_adds || use_git_diff_format) ignore_ancestry = FALSE; - return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi, + SVN_ERR(get_diff_processor(&diff_processor, &ddi, + options, + relative_to_dir, + no_diff_added, + no_diff_deleted, + show_copies_as_adds, + ignore_content_type, + ignore_properties, + properties_only, + use_git_diff_format, + pretty_print_mergeinfo, + header_encoding, + outstream, errstream, + ctx, pool)); + + return svn_error_trace(do_diff(ddi, path_or_url, path_or_url, start_revision, end_revision, peg_revision, FALSE /* no_peg_revision */, @@ -2703,19 +2898,17 @@ svn_client_diff_summarize2(const char *path_or_url1, svn_client_ctx_t *ctx, apr_pool_t *pool) { - const svn_diff_tree_processor_t *diff_processor; + svn_diff_tree_processor_t *diff_processor; svn_opt_revision_t peg_revision; - const char **p_root_relpath; /* We will never do a pegged diff from here. */ peg_revision.kind = svn_opt_revision_unspecified; - SVN_ERR(svn_client__get_diff_summarize_callbacks( - &diff_processor, &p_root_relpath, + SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor, summarize_func, summarize_baton, - path_or_url1, pool, pool)); + pool, pool)); - return svn_error_trace(do_diff(p_root_relpath, NULL, NULL, + return svn_error_trace(do_diff(NULL, path_or_url1, path_or_url2, revision1, revision2, &peg_revision, TRUE /* no_peg_revision */, @@ -2737,15 +2930,13 @@ svn_client_diff_summarize_peg2(const char *path_or_url, svn_client_ctx_t *ctx, apr_pool_t *pool) { - const svn_diff_tree_processor_t *diff_processor; - const char **p_root_relpath; + svn_diff_tree_processor_t *diff_processor; - SVN_ERR(svn_client__get_diff_summarize_callbacks( - &diff_processor, &p_root_relpath, + SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor, summarize_func, summarize_baton, - path_or_url, pool, pool)); + pool, pool)); - return svn_error_trace(do_diff(p_root_relpath, NULL, NULL, + return svn_error_trace(do_diff(NULL, path_or_url, path_or_url, start_revision, end_revision, peg_revision, FALSE /* no_peg_revision */, diff --git a/subversion/libsvn_client/diff_local.c b/subversion/libsvn_client/diff_local.c index 056ee53..feac90d 100644 --- a/subversion/libsvn_client/diff_local.c +++ b/subversion/libsvn_client/diff_local.c @@ -647,20 +647,17 @@ do_dir_diff(const char *left_abspath, } svn_error_t * -svn_client__arbitrary_nodes_diff(const char **root_relpath, - svn_boolean_t *root_is_dir, - const char *left_abspath, +svn_client__arbitrary_nodes_diff(const char *left_abspath, const char *right_abspath, svn_depth_t depth, const svn_diff_tree_processor_t *diff_processor, svn_client_ctx_t *ctx, - apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_node_kind_t left_kind; svn_node_kind_t right_kind; - const char *left_root_abspath; - const char *right_root_abspath; + const char *left_root_abspath = left_abspath; + const char *right_root_abspath = right_abspath; svn_boolean_t left_before_right = TRUE; /* Future argument? */ if (depth == svn_depth_unknown) @@ -671,28 +668,6 @@ svn_client__arbitrary_nodes_diff(const char **root_relpath, if (left_kind == svn_node_dir && right_kind == svn_node_dir) { - left_root_abspath = left_abspath; - right_root_abspath = right_abspath; - - if (root_relpath) - *root_relpath = ""; - if (root_is_dir) - *root_is_dir = TRUE; - } - else - { - svn_dirent_split(&left_root_abspath, root_relpath, left_abspath, - scratch_pool); - right_root_abspath = svn_dirent_dirname(right_abspath, scratch_pool); - - if (root_relpath) - *root_relpath = apr_pstrdup(result_pool, *root_relpath); - if (root_is_dir) - *root_is_dir = FALSE; - } - - if (left_kind == svn_node_dir && right_kind == svn_node_dir) - { SVN_ERR(do_dir_diff(left_abspath, right_abspath, left_root_abspath, right_root_abspath, FALSE, FALSE, left_before_right, @@ -710,79 +685,48 @@ svn_client__arbitrary_nodes_diff(const char **root_relpath, else if (left_kind == svn_node_file || left_kind == svn_node_dir || right_kind == svn_node_file || right_kind == svn_node_dir) { - void *dir_baton; - svn_boolean_t skip = FALSE; - svn_boolean_t skip_children = FALSE; - svn_diff_source_t *left_src; - svn_diff_source_t *right_src; - - left_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); - right_src = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); - - /* The root is replaced... */ - /* Report delete and/or add */ - - SVN_ERR(diff_processor->dir_opened(&dir_baton, &skip, &skip_children, "", - left_src, - right_src, - NULL /* copyfrom_src */, - NULL, - diff_processor, - scratch_pool, scratch_pool)); - - if (skip) - return SVN_NO_ERROR; - else if (!skip_children) + /* The root is added/deleted/replaced. Report delete and/or add. */ + if (left_before_right) { - if (left_before_right) - { - if (left_kind == svn_node_file) - SVN_ERR(do_file_diff(left_abspath, right_abspath, - left_root_abspath, right_root_abspath, - TRUE, FALSE, NULL /* parent_baton */, - diff_processor, ctx, scratch_pool)); - else if (left_kind == svn_node_dir) - SVN_ERR(do_dir_diff(left_abspath, right_abspath, - left_root_abspath, right_root_abspath, - TRUE, FALSE, left_before_right, - depth, NULL /* parent_baton */, - diff_processor, ctx, scratch_pool)); - } - - if (right_kind == svn_node_file) + if (left_kind == svn_node_file) SVN_ERR(do_file_diff(left_abspath, right_abspath, left_root_abspath, right_root_abspath, - FALSE, TRUE, NULL /* parent_baton */, + TRUE, FALSE, NULL /* parent_baton */, diff_processor, ctx, scratch_pool)); - else if (right_kind == svn_node_dir) + else if (left_kind == svn_node_dir) SVN_ERR(do_dir_diff(left_abspath, right_abspath, left_root_abspath, right_root_abspath, - FALSE, TRUE, left_before_right, + TRUE, FALSE, left_before_right, depth, NULL /* parent_baton */, diff_processor, ctx, scratch_pool)); - - if (! left_before_right) - { - if (left_kind == svn_node_file) - SVN_ERR(do_file_diff(left_abspath, right_abspath, - left_root_abspath, right_root_abspath, - TRUE, FALSE, NULL /* parent_baton */, - diff_processor, ctx, scratch_pool)); - else if (left_kind == svn_node_dir) - SVN_ERR(do_dir_diff(left_abspath, right_abspath, - left_root_abspath, right_root_abspath, - TRUE, FALSE, left_before_right, - depth, NULL /* parent_baton */, - diff_processor, ctx, scratch_pool)); - } } - SVN_ERR(diff_processor->dir_closed("", - left_src, - right_src, - dir_baton, - diff_processor, - scratch_pool)); + if (right_kind == svn_node_file) + SVN_ERR(do_file_diff(left_abspath, right_abspath, + left_root_abspath, right_root_abspath, + FALSE, TRUE, NULL /* parent_baton */, + diff_processor, ctx, scratch_pool)); + else if (right_kind == svn_node_dir) + SVN_ERR(do_dir_diff(left_abspath, right_abspath, + left_root_abspath, right_root_abspath, + FALSE, TRUE, left_before_right, + depth, NULL /* parent_baton */, + diff_processor, ctx, scratch_pool)); + + if (! left_before_right) + { + if (left_kind == svn_node_file) + SVN_ERR(do_file_diff(left_abspath, right_abspath, + left_root_abspath, right_root_abspath, + TRUE, FALSE, NULL /* parent_baton */, + diff_processor, ctx, scratch_pool)); + else if (left_kind == svn_node_dir) + SVN_ERR(do_dir_diff(left_abspath, right_abspath, + left_root_abspath, right_root_abspath, + TRUE, FALSE, left_before_right, + depth, NULL /* parent_baton */, + diff_processor, ctx, scratch_pool)); + } } else return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, diff --git a/subversion/libsvn_client/diff_summarize.c b/subversion/libsvn_client/diff_summarize.c index 9e258cd..f4972a3 100644 --- a/subversion/libsvn_client/diff_summarize.c +++ b/subversion/libsvn_client/diff_summarize.c @@ -36,11 +36,6 @@ /* Diff callbacks baton. */ struct summarize_baton_t { - apr_pool_t *baton_pool; /* For allocating skip_path */ - - /* The target path of the diff, relative to the anchor; "" if target == anchor. */ - const char *skip_relpath; - /* The summarize callback passed down from the API */ svn_client_diff_summarize_func_t summarize_func; @@ -49,9 +44,8 @@ struct summarize_baton_t { }; /* Call B->summarize_func with B->summarize_func_baton, passing it a - * summary object composed from PATH (but made to be relative to the target - * of the diff), SUMMARIZE_KIND, PROP_CHANGED (or FALSE if the action is an - * add or delete) and NODE_KIND. */ + * summary object composed from PATH, SUMMARIZE_KIND, PROP_CHANGED (or + * FALSE if the action is an add or delete) and NODE_KIND. */ static svn_error_t * send_summary(struct summarize_baton_t *b, const char *path, @@ -65,9 +59,7 @@ send_summary(struct summarize_baton_t *b, SVN_ERR_ASSERT(summarize_kind != svn_client_diff_summarize_kind_normal || prop_changed); - /* PATH is relative to the anchor of the diff, but SUM->path needs to be - relative to the target of the diff. */ - sum->path = svn_relpath_skip_ancestor(b->skip_relpath, path); + sum->path = path; sum->summarize_kind = summarize_kind; if (summarize_kind == svn_client_diff_summarize_kind_modified || summarize_kind == svn_client_diff_summarize_kind_normal) @@ -265,18 +257,15 @@ diff_file_deleted(const char *relpath, svn_error_t * svn_client__get_diff_summarize_callbacks( - const svn_diff_tree_processor_t **diff_processor, - const char ***p_root_relpath, + svn_diff_tree_processor_t **diff_processor, svn_client_diff_summarize_func_t summarize_func, void *summarize_baton, - const char *original_target, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_diff_tree_processor_t *dp; struct summarize_baton_t *b = apr_pcalloc(result_pool, sizeof(*b)); - b->baton_pool = result_pool; b->summarize_func = summarize_func; b->summarize_func_baton = summarize_baton; @@ -293,7 +282,6 @@ svn_client__get_diff_summarize_callbacks( dp->dir_added = diff_dir_added; *diff_processor = dp; - *p_root_relpath = &b->skip_relpath; return SVN_NO_ERROR; } diff --git a/subversion/libsvn_client/export.c b/subversion/libsvn_client/export.c index 2fc2dc8..b335f2a 100644 --- a/subversion/libsvn_client/export.c +++ b/subversion/libsvn_client/export.c @@ -453,12 +453,12 @@ export_node(void *baton, * If PATH exists but is a file, then error with SVN_ERR_WC_NOT_WORKING_COPY. * * If PATH is a already a directory, then error with - * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just + * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless OVERWRITE, in which case just * export into PATH with no error. */ static svn_error_t * open_root_internal(const char *path, - svn_boolean_t force, + svn_boolean_t overwrite, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *pool) @@ -472,7 +472,7 @@ open_root_internal(const char *path, return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, _("'%s' exists and is not a directory"), svn_dirent_local_style(path, pool)); - else if ((kind != svn_node_dir) || (! force)) + else if ((kind != svn_node_dir) || (! overwrite)) return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, _("'%s' already exists"), svn_dirent_local_style(path, pool)); @@ -501,7 +501,7 @@ struct edit_baton const char *repos_root_url; const char *root_path; const char *root_url; - svn_boolean_t force; + svn_boolean_t overwrite; svn_revnum_t *target_revision; apr_hash_t *externals; const char *native_eol; @@ -587,7 +587,7 @@ open_root(void *edit_baton, struct edit_baton *eb = edit_baton; struct dir_baton *db = apr_pcalloc(pool, sizeof(*db)); - SVN_ERR(open_root_internal(eb->root_path, eb->force, + SVN_ERR(open_root_internal(eb->root_path, eb->overwrite, eb->notify_func, eb->notify_baton, pool)); /* Build our dir baton. */ @@ -621,7 +621,7 @@ add_directory(const char *path, return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, _("'%s' exists and is not a directory"), svn_dirent_local_style(full_path, pool)); - else if (! (kind == svn_node_dir && eb->force)) + else if (! (kind == svn_node_dir && eb->overwrite)) return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, _("'%s' already exists"), svn_dirent_local_style(full_path, pool)); @@ -1077,7 +1077,7 @@ add_directory_ev2(void *baton, return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, _("'%s' exists and is not a directory"), svn_dirent_local_style(full_path, scratch_pool)); - else if (! (kind == svn_node_dir && eb->force)) + else if (! (kind == svn_node_dir && eb->overwrite)) return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, _("'%s' already exists"), svn_dirent_local_style(full_path, scratch_pool)); @@ -1141,7 +1141,7 @@ get_editor_ev2(const svn_delta_editor_t **export_editor, exb, result_pool)); /* Create the root of the export. */ - SVN_ERR(open_root_internal(eb->root_path, eb->force, eb->notify_func, + SVN_ERR(open_root_internal(eb->root_path, eb->overwrite, eb->notify_func, eb->notify_baton, scratch_pool)); return SVN_NO_ERROR; @@ -1153,7 +1153,6 @@ export_file_ev2(const char *from_url, struct edit_baton *eb, svn_client__pathrev_t *loc, svn_ra_session_t *ra_session, - svn_boolean_t overwrite, apr_pool_t *scratch_pool) { apr_hash_t *props; @@ -1177,7 +1176,7 @@ export_file_ev2(const char *from_url, SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool)); if ((to_kind == svn_node_file || to_kind == svn_node_unknown) && - ! overwrite) + ! eb->overwrite) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("Destination file '%s' exists, and " "will not be overwritten unless forced"), @@ -1207,7 +1206,6 @@ export_file(const char *from_url, struct edit_baton *eb, svn_client__pathrev_t *loc, svn_ra_session_t *ra_session, - svn_boolean_t overwrite, apr_pool_t *scratch_pool) { apr_hash_t *props; @@ -1232,7 +1230,7 @@ export_file(const char *from_url, SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool)); if ((to_kind == svn_node_file || to_kind == svn_node_unknown) && - ! overwrite) + ! eb->overwrite) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("Destination file '%s' exists, and " "will not be overwritten unless forced"), @@ -1289,7 +1287,6 @@ export_directory(const char *from_url, struct edit_baton *eb, svn_client__pathrev_t *loc, svn_ra_session_t *ra_session, - svn_boolean_t overwrite, svn_boolean_t ignore_externals, svn_boolean_t ignore_keywords, svn_depth_t depth, @@ -1344,7 +1341,7 @@ export_directory(const char *from_url, SVN_ERR(svn_io_check_path(to_path, &kind, scratch_pool)); if (kind == svn_node_none) SVN_ERR(open_root_internal - (to_path, overwrite, ctx->notify_func2, + (to_path, eb->overwrite, ctx->notify_func2, ctx->notify_baton2, scratch_pool)); if (! ignore_externals && depth == svn_depth_infinity) @@ -1415,7 +1412,7 @@ svn_client_export5(svn_revnum_t *result_rev, SVN_ERR(svn_ra_get_repos_root2(ra_session, &eb->repos_root_url, pool)); eb->root_path = to_path; eb->root_url = loc->url; - eb->force = overwrite; + eb->overwrite = overwrite; eb->target_revision = &edit_revision; eb->externals = apr_hash_make(pool); eb->native_eol = native_eol; @@ -1431,15 +1428,15 @@ svn_client_export5(svn_revnum_t *result_rev, { if (!ENABLE_EV2_IMPL) SVN_ERR(export_file(from_url, to_path, eb, loc, ra_session, - overwrite, pool)); + pool)); else SVN_ERR(export_file_ev2(from_url, to_path, eb, loc, - ra_session, overwrite, pool)); + ra_session, pool)); } else if (kind == svn_node_dir) { SVN_ERR(export_directory(from_url, to_path, - eb, loc, ra_session, overwrite, + eb, loc, ra_session, ignore_externals, ignore_keywords, depth, native_eol, ctx, pool)); } diff --git a/subversion/libsvn_client/info.c b/subversion/libsvn_client/info.c index 3331647..0f03c67 100644 --- a/subversion/libsvn_client/info.c +++ b/subversion/libsvn_client/info.c @@ -167,7 +167,8 @@ build_info_from_dirent(svn_client_info2_t **info, #define DIRENT_FIELDS (SVN_DIRENT_KIND | \ SVN_DIRENT_CREATED_REV | \ SVN_DIRENT_TIME | \ - SVN_DIRENT_LAST_AUTHOR) + SVN_DIRENT_LAST_AUTHOR | \ + SVN_DIRENT_SIZE) /* Helper func for recursively fetching svn_dirent_t's from a remote diff --git a/subversion/libsvn_client/layout.c b/subversion/libsvn_client/layout.c new file mode 100644 index 0000000..bfa7ec1 --- /dev/null +++ b/subversion/libsvn_client/layout.c @@ -0,0 +1,289 @@ +/* +* layout.c: code to list and update the working copy layout +* +* ==================================================================== +* Licensed to the Apache Software Foundation (ASF) under one +* or more contributor license agreements. See the NOTICE file +* distributed with this work for additional information +* regarding copyright ownership. The ASF licenses this file +* to you under the Apache License, Version 2.0 (the +* "License"); you may not use this file except in compliance +* with the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, +* software distributed under the License is distributed on an +* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +* KIND, either express or implied. See the License for the +* specific language governing permissions and limitations +* under the License. +* ==================================================================== +*/ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#include "svn_hash.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_wc.h" +#include "svn_client.h" +#include "svn_error.h" +#include "svn_pools.h" +#include "client.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" + +struct layout_item_t +{ + const char *local_abspath; + const char *url; + svn_revnum_t revision; + svn_depth_t depth; + struct layout_item_t *ancestor; + apr_pool_t *pool; +}; + +struct client_layout_baton_t +{ + const char *root_abspath; + svn_wc_context_t *wc_ctx; + const char *repos_root_url; + + struct layout_item_t *stack; + apr_pool_t *root_pool; + + svn_client__layout_func_t layout; + void *layout_baton; +}; + + +static svn_error_t * +layout_set_path(void *report_baton, + const char *path, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + struct client_layout_baton_t *lb = report_baton; + const char *local_abspath = svn_dirent_join(lb->root_abspath, path, pool); + struct layout_item_t *it; + apr_pool_t *item_pool; + svn_depth_t expected_depth; + + while (lb->stack + && !svn_dirent_is_ancestor(lb->stack->local_abspath, local_abspath)) + { + it = lb->stack; + lb->stack = it->ancestor; + svn_pool_destroy(it->pool); + } + + item_pool = svn_pool_create(lb->stack ? lb->stack->pool + : lb->root_pool); + + it = apr_pcalloc(item_pool, sizeof(*it)); + it->pool = item_pool; + it->local_abspath = apr_pstrdup(item_pool, local_abspath); + it->depth = depth; + it->revision = revision; + if (lb->stack) + { + it->url = svn_path_url_add_component2( + lb->stack->url, + svn_dirent_skip_ancestor(lb->stack->local_abspath, + local_abspath), + item_pool); + } + else + { + const char *repos_relpath, *repos_root_url; + + SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, + &repos_root_url, NULL, NULL, + lb->wc_ctx, local_abspath, + FALSE /* ignore_enoent */, + pool, pool)); + + lb->repos_root_url = apr_pstrdup(lb->root_pool, repos_root_url); + it->url = svn_path_url_add_component2(repos_root_url, repos_relpath, + item_pool); + } + it->ancestor = lb->stack; + lb->stack = it; + + if (!it->ancestor) + expected_depth = depth; + else if (it->ancestor->depth == svn_depth_infinity) + expected_depth = svn_depth_infinity; + else + expected_depth = svn_depth_empty; + + return svn_error_trace(lb->layout(lb->layout_baton, + it->local_abspath, + lb->repos_root_url, + FALSE /* not-present */, + FALSE /* url changed */, + it->url, + it->ancestor + ? it->ancestor->revision != it->revision + : FALSE, + it->revision, + (depth != expected_depth), + it->depth, + pool)); + } + +static svn_error_t * +layout_link_path(void *report_baton, + const char *path, + const char *url, + svn_revnum_t revision, + svn_depth_t depth, + svn_boolean_t start_empty, + const char *lock_token, + apr_pool_t *pool) +{ + struct client_layout_baton_t *lb = report_baton; + const char *local_abspath = svn_dirent_join(lb->root_abspath, path, pool); + struct layout_item_t *it; + apr_pool_t *item_pool; + svn_depth_t expected_depth; + + SVN_ERR_ASSERT(lb->stack); /* Always below root entry */ + + while (!svn_dirent_is_ancestor(lb->stack->local_abspath, local_abspath)) + { + it = lb->stack; + lb->stack = it->ancestor; + svn_pool_destroy(it->pool); + } + + item_pool = svn_pool_create(lb->stack ? lb->stack->pool + : lb->root_pool); + + it = apr_pcalloc(item_pool, sizeof(*it)); + it->pool = item_pool; + it->local_abspath = apr_pstrdup(item_pool, local_abspath); + it->depth = depth; + it->revision = revision; + it->url = apr_pstrdup(item_pool, url); + + it->ancestor = lb->stack; + lb->stack = it; + + if (it->ancestor->depth == svn_depth_infinity) + expected_depth = svn_depth_infinity; + else + expected_depth = svn_depth_empty; + + return svn_error_trace(lb->layout(lb->layout_baton, + it->local_abspath, + lb->repos_root_url, + FALSE /* not-present */, + TRUE /* url changed */, + it->url, + it->ancestor + ? it->ancestor->revision != it->revision + : FALSE, + it->revision, + (depth != expected_depth), + it->depth, + pool)); +} + +static svn_error_t * +layout_delete_path(void *report_baton, + const char *path, + apr_pool_t *pool) +{ + struct client_layout_baton_t *lb = report_baton; + const char *local_abspath = svn_dirent_join(lb->root_abspath, path, pool); + struct layout_item_t *it; + + SVN_ERR_ASSERT(lb->stack); /* Always below root entry */ + + while (!svn_dirent_is_ancestor(lb->stack->local_abspath, local_abspath)) + { + it = lb->stack; + lb->stack = it->ancestor; + svn_pool_destroy(it->pool); + } + + return svn_error_trace(lb->layout(lb->layout_baton, + local_abspath, + lb->repos_root_url, + TRUE /* not-present */, + FALSE /* url changed */, + NULL /* no-url */, + FALSE /* revision changed */, + SVN_INVALID_REVNUM, + FALSE /* depth changed */, + svn_depth_unknown, + pool)); +} + +static svn_error_t * +layout_finish_report(void *report_baton, + apr_pool_t *pool) +{ + /*struct client_layout_baton_t *lb = report_baton;*/ + return SVN_NO_ERROR; +} + +static svn_error_t * +layout_abort_report(void *report_baton, + apr_pool_t *pool) +{ + /*struct client_layout_baton_t *lb = report_baton;*/ + return SVN_NO_ERROR; +} + +static const svn_ra_reporter3_t layout_reporter = +{ + layout_set_path, + layout_delete_path, + layout_link_path, + layout_finish_report, + layout_abort_report +}; + +svn_error_t * +svn_client__layout_list(const char *local_abspath, + svn_client__layout_func_t layout, + void *layout_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + struct client_layout_baton_t lb; + + lb.root_abspath = local_abspath; + lb.root_pool = scratch_pool; + lb.wc_ctx = ctx->wc_ctx; + lb.repos_root_url = NULL; /* Filled in later */ + lb.stack = NULL; + + lb.layout = layout; + lb.layout_baton = layout_baton; + + /* Drive the reporter structure, describing the revisions within + LOCAL_ABSPATH. */ + SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, + &layout_reporter, &lb, + FALSE /* restore_files */, + svn_depth_infinity, + TRUE /* honor_depth_exclude */, + FALSE /* depth_compatibility_trick */, + FALSE /* use_commit_times */, + ctx->cancel_func, ctx->cancel_baton, + ctx->notify_func2, ctx->notify_baton2, + scratch_pool)); + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_client/libsvn_client.pc.in b/subversion/libsvn_client/libsvn_client.pc.in index 7cc7865..4e475d8 100644 --- a/subversion/libsvn_client/libsvn_client.pc.in +++ b/subversion/libsvn_client/libsvn_client.pc.in @@ -6,7 +6,7 @@ includedir=@includedir@ Name: libsvn_client Description: Subversion Client Library Version: @PACKAGE_VERSION@ -Requires: apr-@SVN_APR_MAJOR_VERSION@ -Requires.private: libsvn_wc libsvn_ra libsvn_delta libsvn_diff libsvn_subr -Libs: -L${libdir} -lsvn_client -Cflags: -I${includedir} +Requires: apr-@SVN_APR_MAJOR_VERSION@ +Requires.private: libsvn_wc, libsvn_ra, libsvn_delta, libsvn_diff, libsvn_subr +Libs: -L${libdir} -lsvn_client-1 +Cflags: -I${includedir}/subversion-1 diff --git a/subversion/libsvn_client/list.c b/subversion/libsvn_client/list.c index 78433c3..d52de6d 100644 --- a/subversion/libsvn_client/list.c +++ b/subversion/libsvn_client/list.c @@ -394,7 +394,7 @@ list_internal(const char *path_or_url, svn_membuf__create(&scratch_buffer, 256, pool); /* Report the dirent for the target. */ - if (match_patterns(svn_dirent_dirname(fs_path, pool), patterns, + if (match_patterns(svn_dirent_basename(fs_path, pool), patterns, &scratch_buffer)) SVN_ERR(list_func(baton, "", dirent, locks ? (svn_hash_gets(locks, fs_path)) diff --git a/subversion/libsvn_client/merge.c b/subversion/libsvn_client/merge.c index 21341b9..72e5c5a 100644 --- a/subversion/libsvn_client/merge.c +++ b/subversion/libsvn_client/merge.c @@ -215,6 +215,21 @@ /*** Repos-Diff Editor Callbacks ***/ +struct merge_cmd_baton_t; + +struct notify_begin_state_t +{ + /* Cache of which abspath was last notified. */ + const char *last_abspath; + + /* Reference to the main merge baton */ + struct merge_cmd_baton_t *merge_b; + + /* the wrapped notification callback */ + svn_wc_notify_func2_t notify_func2; + void *notify_baton2; +}; + typedef struct merge_cmd_baton_t { svn_boolean_t force_delete; /* Delete a file/dir even if modified */ svn_boolean_t dry_run; @@ -242,11 +257,15 @@ typedef struct merge_cmd_baton_t { /* Rangelist containing single range which describes the gap, if any, in the natural history of the merge source currently being processed. - See http://subversion.tigris.org/issues/show_bug.cgi?id=3432. + See https://issues.apache.org/jira/browse/SVN-3432. Updated during each call to do_directory_merge(). May be NULL if there is no gap. */ svn_rangelist_t *implicit_src_gap; + /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global + comment) or a similar list for single-file-merges */ + const apr_array_header_t *children_with_mergeinfo; + svn_client_ctx_t *ctx; /* Client context for callbacks, etc. */ /* The list of any paths which remained in conflict after a @@ -319,17 +338,10 @@ typedef struct merge_cmd_baton_t { or do_file_merge() in do_merge(). */ apr_pool_t *pool; - - /* State for notify_merge_begin() */ - struct notify_begin_state_t - { - /* Cache of which abspath was last notified. */ - const char *last_abspath; - - /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global - comment) or a similar list for single-file-merges */ - const apr_array_header_t *nodes_with_mergeinfo; - } notify_begin; + /* Our notification callback, that adds a 'begin' notification */ + svn_wc_notify_func2_t notify_func; + void *notify_baton; + struct notify_begin_state_t notify_begin; } merge_cmd_baton_t; @@ -340,17 +352,25 @@ typedef struct merge_cmd_baton_t { merge source is an ancestor of the right-side (or vice-versa), the merge source is in the same repository as the merge target, and we are not ignoring mergeinfo. */ -#define HONOR_MERGEINFO(merge_b) ((merge_b)->mergeinfo_capable \ - && (merge_b)->merge_source.ancestral \ - && (merge_b)->same_repos \ - && (! (merge_b)->ignore_mergeinfo)) +static svn_boolean_t +HONOR_MERGEINFO(const merge_cmd_baton_t *merge_b) +{ + return (merge_b->mergeinfo_capable + && merge_b->merge_source.ancestral + && merge_b->same_repos + && (!merge_b->ignore_mergeinfo)); +} /* Return TRUE iff we should be recording mergeinfo for the merge described by MERGE_B. Specifically, that is if we are honoring mergeinfo and the merge is not a dry run. */ -#define RECORD_MERGEINFO(merge_b) (HONOR_MERGEINFO(merge_b) \ - && !(merge_b)->dry_run) +static svn_boolean_t +RECORD_MERGEINFO(const merge_cmd_baton_t *merge_b) +{ + return (HONOR_MERGEINFO(merge_b) + && !merge_b->dry_run); +} /*-----------------------------------------------------------------------*/ @@ -1226,13 +1246,6 @@ struct merge_file_baton_t svn_boolean_t add_is_replace; /* Add is second part of replace */ }; -/* Forward declaration */ -static svn_error_t * -notify_merge_begin(merge_cmd_baton_t *merge_b, - const char *local_abspath, - svn_boolean_t delete_action, - apr_pool_t *scratch_pool); - /* Record the skip for future processing and (later) produce the skip notification */ static svn_error_t * @@ -1253,18 +1266,16 @@ record_skip(merge_cmd_baton_t *merge_b, store_path(merge_b->skipped_abspaths, local_abspath); } - if (merge_b->ctx->notify_func2) + if (merge_b->notify_func) { svn_wc_notify_t *notify; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool)); - notify = svn_wc_create_notify(local_abspath, action, scratch_pool); notify->kind = kind; notify->content_state = notify->prop_state = state; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, notify, + scratch_pool); } return SVN_NO_ERROR; } @@ -1364,7 +1375,7 @@ record_tree_conflict(merge_cmd_baton_t *merge_b, * but figure out the actual revision range merged. */ (void)find_nearest_ancestor_with_intersecting_ranges( &(range.start), &(range.end), - merge_b->notify_begin.nodes_with_mergeinfo, + merge_b->children_with_mergeinfo, action != svn_wc_conflict_action_delete, local_abspath); loc1 = svn_client__pathrev_dup(merge_b->merge_source.loc1, @@ -1423,18 +1434,16 @@ record_tree_conflict(merge_cmd_baton_t *merge_b, } /* On a replacement we currently get two tree conflicts */ - if (merge_b->ctx->notify_func2 && notify_tc) + if (merge_b->notify_func && notify_tc) { svn_wc_notify_t *notify; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool)); - notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict, scratch_pool); notify->kind = local_node_kind; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, notify, + scratch_pool); } return SVN_NO_ERROR; @@ -1455,21 +1464,19 @@ record_update_add(merge_cmd_baton_t *merge_b, store_path(merge_b->merged_abspaths, local_abspath); } - if (merge_b->ctx->notify_func2) + if (merge_b->notify_func) { svn_wc_notify_t *notify; svn_wc_notify_action_t action = svn_wc_notify_update_add; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool)); - if (notify_replaced) action = svn_wc_notify_update_replace; notify = svn_wc_create_notify(local_abspath, action, scratch_pool); notify->kind = kind; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, notify, + scratch_pool); } return SVN_NO_ERROR; @@ -1490,20 +1497,18 @@ record_update_update(merge_cmd_baton_t *merge_b, store_path(merge_b->merged_abspaths, local_abspath); } - if (merge_b->ctx->notify_func2) + if (merge_b->notify_func) { svn_wc_notify_t *notify; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool)); - notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_update, scratch_pool); notify->kind = kind; notify->content_state = content_state; notify->prop_state = prop_state; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, notify, + scratch_pool); } return SVN_NO_ERROR; @@ -1529,8 +1534,6 @@ record_update_delete(merge_cmd_baton_t *merge_b, store_path(merge_b->merged_abspaths, local_abspath); } - SVN_ERR(notify_merge_begin(merge_b, local_abspath, TRUE, scratch_pool)); - if (parent_db) { const char *dup_abspath = apr_pstrdup(parent_db->pool, local_abspath); @@ -1552,7 +1555,7 @@ handle_pending_notifications(merge_cmd_baton_t *merge_b, struct merge_dir_baton_t *db, apr_pool_t *scratch_pool) { - if (merge_b->ctx->notify_func2 && db->pending_deletes) + if (merge_b->notify_func && db->pending_deletes) { apr_hash_index_t *hi; @@ -1569,8 +1572,8 @@ handle_pending_notifications(merge_cmd_baton_t *merge_b, notify->kind = svn_node_kind_from_word( apr_hash_this_val(hi)); - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, - notify, scratch_pool); + merge_b->notify_func(merge_b->notify_baton, + notify, scratch_pool); } db->pending_deletes = NULL; @@ -1620,13 +1623,10 @@ mark_dir_edited(merge_cmd_baton_t *merge_b, for clarity we produce a skip for this node that most likely isn't touched by the merge itself */ - if (merge_b->ctx->notify_func2) + if (merge_b->notify_func) { svn_wc_notify_t *notify; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, - scratch_pool)); - notify = svn_wc_create_notify( local_abspath, (db->tree_conflict_reason == CONFLICT_REASON_SKIP) @@ -1636,9 +1636,9 @@ mark_dir_edited(merge_cmd_baton_t *merge_b, notify->kind = svn_node_dir; notify->content_state = notify->prop_state = db->skip_reason; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, - notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, + notify, + scratch_pool); } if (merge_b->merge_source.ancestral @@ -1706,21 +1706,18 @@ mark_file_edited(merge_cmd_baton_t *merge_b, for clarity we produce a skip for this node that most likely isn't touched by the merge itself */ - if (merge_b->ctx->notify_func2) + if (merge_b->notify_func) { svn_wc_notify_t *notify; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, - scratch_pool)); - notify = svn_wc_create_notify(local_abspath, svn_wc_notify_skip, scratch_pool); notify->kind = svn_node_file; notify->content_state = notify->prop_state = fb->skip_reason; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, - notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, + notify, + scratch_pool); } if (merge_b->merge_source.ancestral @@ -3413,6 +3410,49 @@ merge_node_absent(const char *relpath, return SVN_NO_ERROR; } +/* Return a diff processor that will apply the merge to the WC. + */ +static svn_diff_tree_processor_t * +merge_apply_processor(merge_cmd_baton_t *merge_cmd_baton, + apr_pool_t *result_pool) +{ + svn_diff_tree_processor_t *merge_processor; + + merge_processor = svn_diff__tree_processor_create(merge_cmd_baton, + result_pool); + + merge_processor->dir_opened = merge_dir_opened; + merge_processor->dir_changed = merge_dir_changed; + merge_processor->dir_added = merge_dir_added; + merge_processor->dir_deleted = merge_dir_deleted; + merge_processor->dir_closed = merge_dir_closed; + + merge_processor->file_opened = merge_file_opened; + merge_processor->file_changed = merge_file_changed; + merge_processor->file_added = merge_file_added; + merge_processor->file_deleted = merge_file_deleted; + /* Not interested in file_closed() */ + + merge_processor->node_absent = merge_node_absent; + + return merge_processor; +} + +/* Initialize minimal dir baton to allow calculating 'R'eplace + from 'D'elete + 'A'dd. */ +static void * +open_dir_for_replace_single_file(apr_pool_t *result_pool) +{ + struct merge_dir_baton_t *dir_baton = apr_pcalloc(result_pool, sizeof(*dir_baton)); + + dir_baton->pool = result_pool; + dir_baton->tree_conflict_reason = CONFLICT_REASON_NONE; + dir_baton->tree_conflict_action = svn_wc_conflict_action_edit; + dir_baton->skip_reason = svn_wc_notify_state_unknown; + + return dir_baton; +} + /*-----------------------------------------------------------------------*/ /*** Merge Notification ***/ @@ -3608,20 +3648,9 @@ notify_merge_completed(const char *target_abspath, } } -/* Is the notification the result of a real operative merge? */ -#define IS_OPERATIVE_NOTIFICATION(notify) \ - (notify->content_state == svn_wc_notify_state_conflicted \ - || notify->content_state == svn_wc_notify_state_merged \ - || notify->content_state == svn_wc_notify_state_changed \ - || notify->prop_state == svn_wc_notify_state_conflicted \ - || notify->prop_state == svn_wc_notify_state_merged \ - || notify->prop_state == svn_wc_notify_state_changed \ - || notify->action == svn_wc_notify_update_add \ - || notify->action == svn_wc_notify_tree_conflict) - /* Remove merge source gaps from range used for merge notifications. - See http://subversion.tigris.org/issues/show_bug.cgi?id=4138 + See https://issues.apache.org/jira/browse/SVN-4138 If IMPLICIT_SRC_GAP is not NULL then it is a rangelist containing a single range (see the implicit_src_gap member of merge_cmd_baton_t). @@ -3656,27 +3685,28 @@ remove_source_gap(svn_merge_range_t *range, * This calls the client's notification receiver (as found in the client * context), with a WC abspath. */ -static svn_error_t * -notify_merge_begin(merge_cmd_baton_t *merge_b, +static void +notify_merge_begin(struct notify_begin_state_t *notify_begin_state, const char *local_abspath, svn_boolean_t delete_action, apr_pool_t *scratch_pool) { + merge_cmd_baton_t *merge_b = notify_begin_state->merge_b; svn_wc_notify_t *notify; svn_merge_range_t n_range = {SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, TRUE}; const char *notify_abspath; - if (! merge_b->ctx->notify_func2) - return SVN_NO_ERROR; + if (! notify_begin_state->notify_func2) + return; /* If our merge sources are ancestors of one another... */ if (merge_b->merge_source.ancestral) { const svn_client__merge_path_t *child; - /* Find NOTIFY->PATH's nearest ancestor in - NOTIFY->CHILDREN_WITH_MERGEINFO. Normally we consider a child in - NOTIFY->CHILDREN_WITH_MERGEINFO representing PATH to be an + /* Find LOCAL_ABSPATH's nearest ancestor in + CHILDREN_WITH_MERGEINFO. Normally we consider a child in + CHILDREN_WITH_MERGEINFO representing PATH to be an ancestor of PATH, but if this is a deletion of PATH then the notification must be for a proper ancestor of PATH. This ensures we don't get notifications like: @@ -3692,47 +3722,47 @@ notify_merge_begin(merge_cmd_baton_t *merge_b, child = find_nearest_ancestor_with_intersecting_ranges( &(n_range.start), &(n_range.end), - merge_b->notify_begin.nodes_with_mergeinfo, + merge_b->children_with_mergeinfo, ! delete_action, local_abspath); if (!child && delete_action) { /* Triggered by file replace in single-file-merge */ - child = find_nearest_ancestor(merge_b->notify_begin.nodes_with_mergeinfo, + child = find_nearest_ancestor(merge_b->children_with_mergeinfo, TRUE, local_abspath); } assert(child != NULL); /* Should always find the merge anchor */ if (! child) - return SVN_NO_ERROR; + return; - if (merge_b->notify_begin.last_abspath != NULL - && strcmp(child->abspath, merge_b->notify_begin.last_abspath) == 0) + if (notify_begin_state->last_abspath != NULL + && strcmp(child->abspath, notify_begin_state->last_abspath) == 0) { /* Don't notify the same merge again */ - return SVN_NO_ERROR; + return; } - merge_b->notify_begin.last_abspath = child->abspath; + notify_begin_state->last_abspath = child->abspath; if (child->absent || child->remaining_ranges->nelts == 0 || !SVN_IS_VALID_REVNUM(n_range.start)) { /* No valid information for an header */ - return SVN_NO_ERROR; + return; } notify_abspath = child->abspath; } else { - if (merge_b->notify_begin.last_abspath) - return SVN_NO_ERROR; /* already notified */ + if (notify_begin_state->last_abspath) + return; /* already notified */ notify_abspath = merge_b->target->abspath; /* Store something in last_abspath. Any value would do */ - merge_b->notify_begin.last_abspath = merge_b->target->abspath; + notify_begin_state->last_abspath = merge_b->target->abspath; } notify = svn_wc_create_notify(notify_abspath, @@ -3753,10 +3783,23 @@ notify_merge_begin(merge_cmd_baton_t *merge_b, notify->merge_range = NULL; } - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify, - scratch_pool); + notify_begin_state->notify_func2(notify_begin_state->notify_baton2, notify, + scratch_pool); +} - return SVN_NO_ERROR; +/* Our notification callback, that adds a 'begin' notification */ +static void +notify_merging(void *baton, + const svn_wc_notify_t *notify, + apr_pool_t *pool) +{ + struct notify_begin_state_t *b = baton; + + notify_merge_begin(b, notify->path, + notify->action == svn_wc_notify_update_delete, + pool); + + b->notify_func2(b->notify_baton2, notify, pool); } /* Set *OUT_RANGELIST to the intersection of IN_RANGELIST with the simple @@ -5445,7 +5488,7 @@ record_skips_in_mergeinfo(const char *mergeinfo_path, ### TODO: An empty range is fine if the skipped path doesn't ### inherit any mergeinfo from a parent, but if it does ### we need to account for that. See issue #3440 - ### http://subversion.tigris.org/issues/show_bug.cgi?id=3440. */ + ### https://issues.apache.org/jira/browse/SVN-3440. */ svn_hash_sets(merges, skipped_abspath, apr_array_make(scratch_pool, 0, sizeof(svn_merge_range_t *))); @@ -7596,16 +7639,15 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, /* ### Create a fake copy of merge_target as we don't keep remaining_ranges in sync (yet). */ - target_info = apr_pcalloc(scratch_pool, sizeof(*target_info)); - - target_info->abspath = merge_target->abspath; + target_info = svn_client__merge_path_create(merge_target->abspath, + scratch_pool); target_info->remaining_ranges = ranges_to_merge; APR_ARRAY_PUSH(child_with_mergeinfo, svn_client__merge_path_t *) = target_info; /* And store in baton to allow using it from notify_merge_begin() */ - merge_b->notify_begin.nodes_with_mergeinfo = child_with_mergeinfo; + merge_b->children_with_mergeinfo = child_with_mergeinfo; } while (ranges_to_merge->nelts > 0) @@ -7648,19 +7690,10 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, do a text-n-props merge; otherwise, do a delete-n-add merge. */ if (! (merge_b->diff_ignore_ancestry || sources_related)) { - struct merge_dir_baton_t dir_baton; + void *dir_baton = open_dir_for_replace_single_file(iterpool); void *file_baton; svn_boolean_t skip; - /* Initialize minimal dir baton to allow calculating 'R'eplace - from 'D'elete + 'A'dd. */ - - memset(&dir_baton, 0, sizeof(dir_baton)); - dir_baton.pool = iterpool; - dir_baton.tree_conflict_reason = CONFLICT_REASON_NONE; - dir_baton.tree_conflict_action = svn_wc_conflict_action_edit; - dir_baton.skip_reason = svn_wc_notify_state_unknown; - /* Delete... */ file_baton = NULL; skip = FALSE; @@ -7668,7 +7701,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, left_source, NULL /* right_source */, NULL /* copyfrom_source */, - &dir_baton, + dir_baton, processor, iterpool, iterpool)); if (! skip) @@ -7687,7 +7720,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, NULL /* left_source */, right_source, NULL /* copyfrom_source */, - &dir_baton, + dir_baton, processor, iterpool, iterpool)); if (! skip) @@ -7819,7 +7852,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, } } - merge_b->notify_begin.nodes_with_mergeinfo = NULL; + merge_b->children_with_mergeinfo = NULL; svn_pool_destroy(iterpool); @@ -7878,7 +7911,7 @@ process_children_with_new_mergeinfo(merge_cmd_baton_t *merge_b, was added (with preexisting mergeinfo) by the merge. That's actually more correct, since the inherited mergeinfo likely describes non-existent or unrelated merge history, but it's not quite so simple - as that, see http://subversion.tigris.org/issues/show_bug.cgi?id=4309 + as that, see https://issues.apache.org/jira/browse/SVN-4309 */ /* Get the path's new explicit mergeinfo... */ @@ -8269,7 +8302,7 @@ flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge, merge_b->target->abspath, depth, merge_b->ctx->wc_ctx, merge_b->ra_session1, scratch_pool, iterpool)); - /* Issue #4056: Walk NOTIFY_B->CHILDREN_WITH_MERGEINFO reverse depth-first + /* Issue #4056: Walk CHILDREN_WITH_MERGEINFO reverse depth-first order. This way each child knows if it has operative missing/switched children which necessitates non-inheritable mergeinfo. */ for (i = children_with_mergeinfo->nelts - 1; i >= 0; i--) @@ -8432,7 +8465,7 @@ flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge, } else /* child->record_mergeinfo */ { - /* If CHILD is in NOTIFY_B->CHILDREN_WITH_MERGEINFO simply + /* If CHILD is in CHILDREN_WITH_MERGEINFO simply because it had no explicit mergeinfo of its own at the start of the merge but is the child of of some path with non-inheritable mergeinfo, then the explicit mergeinfo it @@ -8457,7 +8490,7 @@ flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge, If RESULT_CATALOG is NULL then record mergeinfo describing a merge of MERGED_RANGE->START:MERGED_RANGE->END from the repository relative path MERGEINFO_FSPATH to the merge target (and possibly its subtrees) described - by NOTIFY_B->CHILDREN_WITH_MERGEINFO -- see the global comment + by CHILDREN_WITH_MERGEINFO -- see the global comment 'THE CHILDREN_WITH_MERGEINFO ARRAY'. Obviously this should only be called if recording mergeinfo -- see doc string for RECORD_MERGEINFO(). @@ -8508,7 +8541,7 @@ record_mergeinfo_for_dir_merge(svn_mergeinfo_catalog_t result_catalog, range.inheritable = TRUE; /* Remove absent children at or under MERGE_B->target->abspath from - NOTIFY_B->CHILDREN_WITH_MERGEINFO + CHILDREN_WITH_MERGEINFO before we calculate the merges performed. */ remove_absent_children(merge_b->target->abspath, children_with_mergeinfo); @@ -9334,7 +9367,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, /* Point our RA_SESSION to the URL of our youngest merge source side. */ ra_session = is_rollback ? merge_b->ra_session1 : merge_b->ra_session2; - /* Fill NOTIFY_B->CHILDREN_WITH_MERGEINFO with child paths (const + /* Fill CHILDREN_WITH_MERGEINFO with child paths (const svn_client__merge_path_t *) which might have intersecting merges because they meet one or more of the criteria described in get_mergeinfo_paths(). Here the paths are arranged in a depth @@ -9344,13 +9377,13 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, merge_b->dry_run, merge_b->same_repos, merge_b->ctx, scratch_pool, scratch_pool)); - /* The first item from the NOTIFY_B->CHILDREN_WITH_MERGEINFO is always + /* The first item from the CHILDREN_WITH_MERGEINFO is always the target thanks to depth-first ordering. */ target_merge_path = APR_ARRAY_IDX(children_with_mergeinfo, 0, svn_client__merge_path_t *); /* If we are honoring mergeinfo, then for each item in - NOTIFY_B->CHILDREN_WITH_MERGEINFO, we need to calculate what needs to be + CHILDREN_WITH_MERGEINFO, we need to calculate what needs to be merged, and then merge it. Otherwise, we just merge what we were asked to merge across the whole tree. */ SVN_ERR(populate_remaining_ranges(children_with_mergeinfo, @@ -9370,7 +9403,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, /* The merge target TARGET_ABSPATH and/or its subtrees may not need all of SOURCE->rev1:rev2 applied. So examine - NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the oldest starting + CHILDREN_WITH_MERGEINFO to find the oldest starting revision that actually needs to be merged (for reverse merges this is the youngest starting revision). @@ -9408,7 +9441,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, /* Is there anything to merge? */ if (SVN_IS_VALID_REVNUM(start_rev)) { - /* Now examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the oldest + /* Now examine CHILDREN_WITH_MERGEINFO to find the oldest ending revision that actually needs to be merged (for reverse merges this is the youngest ending revision). */ svn_revnum_t end_rev = @@ -9417,7 +9450,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, /* While END_REV is valid, do the following: - 1. Tweak each NOTIFY_B->CHILDREN_WITH_MERGEINFO element so that + 1. Tweak each CHILDREN_WITH_MERGEINFO element so that the element's remaining_ranges member has as its first element a range that ends with end_rev. @@ -9425,17 +9458,17 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, on MERGE_B->target->abspath for start_rev:end_rev. 3. Remove the first element from each - NOTIFY_B->CHILDREN_WITH_MERGEINFO element's remaining_ranges + CHILDREN_WITH_MERGEINFO element's remaining_ranges member. - 4. Again examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the most + 4. Again examine CHILDREN_WITH_MERGEINFO to find the most inclusive starting revision that actually needs to be merged and update start_rev. This prevents us from needlessly contacting the repository and doing a diff where we describe the entire target tree as *not* needing any of the requested range. This can happen whenever we have mergeinfo with gaps in it for the merge source. - 5. Again examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the most + 5. Again examine CHILDREN_WITH_MERGEINFO to find the most inclusive ending revision that actually needs to be merged and update end_rev. @@ -9496,14 +9529,14 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, /* If any paths picked up explicit mergeinfo as a result of the merge we need to make sure any mergeinfo those paths inherited is recorded and then add these paths to - NOTIFY_B->CHILDREN_WITH_MERGEINFO.*/ + CHILDREN_WITH_MERGEINFO.*/ SVN_ERR(process_children_with_new_mergeinfo( merge_b, children_with_mergeinfo, scratch_pool)); /* If any subtrees had their explicit mergeinfo deleted as a result of the merge then remove these paths from - NOTIFY_B->CHILDREN_WITH_MERGEINFO since there is no need + CHILDREN_WITH_MERGEINFO since there is no need to consider these subtrees for subsequent editor drives nor do we want to record mergeinfo on them describing the merge itself. */ @@ -9634,7 +9667,7 @@ do_directory_merge(svn_mergeinfo_catalog_t result_catalog, apr_array_make(scratch_pool, 16, sizeof(svn_client__merge_path_t *)); /* And make it read-only accessible from the baton */ - merge_b->notify_begin.nodes_with_mergeinfo = children_with_mergeinfo; + merge_b->children_with_mergeinfo = children_with_mergeinfo; /* If we are not honoring mergeinfo we can skip right to the business of merging changes! */ @@ -9652,7 +9685,7 @@ do_directory_merge(svn_mergeinfo_catalog_t result_catalog, processor, depth, merge_b, result_pool, scratch_pool)); - merge_b->notify_begin.nodes_with_mergeinfo = NULL; + merge_b->children_with_mergeinfo = NULL; return SVN_NO_ERROR; } @@ -9889,28 +9922,13 @@ do_merge(apr_hash_t **modified_subtrees, merge_cmd_baton.added_abspaths = apr_hash_make(result_pool); merge_cmd_baton.tree_conflicted_abspaths = apr_hash_make(result_pool); - { - svn_diff_tree_processor_t *merge_processor; - - merge_processor = svn_diff__tree_processor_create(&merge_cmd_baton, - scratch_pool); - - merge_processor->dir_opened = merge_dir_opened; - merge_processor->dir_changed = merge_dir_changed; - merge_processor->dir_added = merge_dir_added; - merge_processor->dir_deleted = merge_dir_deleted; - merge_processor->dir_closed = merge_dir_closed; - - merge_processor->file_opened = merge_file_opened; - merge_processor->file_changed = merge_file_changed; - merge_processor->file_added = merge_file_added; - merge_processor->file_deleted = merge_file_deleted; - /* Not interested in file_closed() */ - - merge_processor->node_absent = merge_node_absent; + merge_cmd_baton.notify_func = notify_merging; + merge_cmd_baton.notify_baton = &merge_cmd_baton.notify_begin; + merge_cmd_baton.notify_begin.merge_b = &merge_cmd_baton; + merge_cmd_baton.notify_begin.notify_func2 = ctx->notify_func2; + merge_cmd_baton.notify_begin.notify_baton2 = ctx->notify_baton2; - processor = merge_processor; - } + processor = merge_apply_processor(&merge_cmd_baton, scratch_pool); if (src_session) { diff --git a/subversion/libsvn_client/patch.c b/subversion/libsvn_client/patch.c index 1b2d86b..549d20c 100644 --- a/subversion/libsvn_client/patch.c +++ b/subversion/libsvn_client/patch.c @@ -343,7 +343,9 @@ strip_path(const char **result, const char *path, int strip_count, components = svn_path_decompose(path, scratch_pool); if (strip_count > components->nelts) return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL, - _("Cannot strip %u components from '%s'"), + Q_("Cannot strip %u component from '%s'", + "Cannot strip %u components from '%s'", + strip_count), strip_count, svn_dirent_local_style(path, scratch_pool)); @@ -1018,6 +1020,7 @@ init_patch_target(patch_target_t **patch_target, target_content_t *content; svn_boolean_t has_text_changes = FALSE; svn_boolean_t follow_moves; + const char *tempdir_abspath; has_text_changes = ((patch->hunks && patch->hunks->nelts > 0) || patch->binary_patch); @@ -1223,8 +1226,10 @@ init_patch_target(patch_target_t **patch_target, } /* Open a temporary file to write the patched result to. */ + SVN_ERR(svn_wc__get_tmpdir(&tempdir_abspath, wc_ctx, + target->local_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_io_open_unique_file3(&target->patched_file, - &target->patched_path, NULL, + &target->patched_path, tempdir_abspath, remove_tempfiles ? svn_io_file_del_on_pool_cleanup : svn_io_file_del_none, @@ -1236,7 +1241,7 @@ init_patch_target(patch_target_t **patch_target, /* Open a temporary stream to write rejected hunks to. */ SVN_ERR(svn_stream_open_unique(&target->reject_stream, - &target->reject_path, NULL, + &target->reject_path, tempdir_abspath, remove_tempfiles ? svn_io_file_del_on_pool_cleanup : svn_io_file_del_none, @@ -2145,8 +2150,8 @@ reject_hunk(patch_target_t *target, target_content_t *content, if (prop_name) { /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. */ - svn_stream_printf(target->reject_stream, - pool, "Property: %s" APR_EOL_STR, prop_name); + SVN_ERR(svn_stream_printf(target->reject_stream, + pool, "Property: %s" APR_EOL_STR, prop_name)); atat = prop_atat; } else diff --git a/subversion/libsvn_client/repos_diff.c b/subversion/libsvn_client/repos_diff.c index 58fe8aa..17b537c 100644 --- a/subversion/libsvn_client/repos_diff.c +++ b/subversion/libsvn_client/repos_diff.c @@ -51,6 +51,7 @@ #include "private/svn_subr_private.h" #include "private/svn_wc_private.h" #include "private/svn_editor.h" +#include "private/svn_sorts_private.h" /* Overall crawler editor baton. */ struct edit_baton { @@ -380,7 +381,7 @@ get_file_from_ra(struct file_baton *fb, way. Hence this little hack: We populate FILE_BATON->PROPCHANGES only with *actual* property changes. - See http://subversion.tigris.org/issues/show_bug.cgi?id=3657#desc9 and + See https://issues.apache.org/jira/browse/SVN-3657#desc9 and http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details. */ static void @@ -404,15 +405,9 @@ remove_non_prop_changes(apr_hash_t *pristine_props, if (old_val && svn_string_compare(old_val, change->value)) { - int j; - - /* Remove the matching change by shifting the rest */ - for (j = i; j < changes->nelts - 1; j++) - { - APR_ARRAY_IDX(changes, j, svn_prop_t) - = APR_ARRAY_IDX(changes, j+1, svn_prop_t); - } - changes->nelts--; + /* Remove the matching change and re-check the current index */ + svn_sort__array_delete(changes, i, 1); + i--; } } } diff --git a/subversion/libsvn_client/revert.c b/subversion/libsvn_client/revert.c index d827014..cec28f2 100644 --- a/subversion/libsvn_client/revert.c +++ b/subversion/libsvn_client/revert.c @@ -51,6 +51,7 @@ struct revert_with_write_lock_baton { const apr_array_header_t *changelists; svn_boolean_t clear_changelists; svn_boolean_t metadata_only; + svn_boolean_t added_keep_local; svn_client_ctx_t *ctx; }; @@ -80,13 +81,14 @@ revert(void *baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) struct revert_with_write_lock_baton *b = baton; svn_error_t *err; - err = svn_wc_revert5(b->ctx->wc_ctx, + err = svn_wc_revert6(b->ctx->wc_ctx, b->local_abspath, b->depth, b->use_commit_times, b->changelists, b->clear_changelists, b->metadata_only, + b->added_keep_local, b->ctx->cancel_func, b->ctx->cancel_baton, b->ctx->notify_func2, b->ctx->notify_baton2, scratch_pool); @@ -123,11 +125,12 @@ revert(void *baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) svn_error_t * -svn_client_revert3(const apr_array_header_t *paths, +svn_client_revert4(const apr_array_header_t *paths, svn_depth_t depth, const apr_array_header_t *changelists, svn_boolean_t clear_changelists, svn_boolean_t metadata_only, + svn_boolean_t added_keep_local, svn_client_ctx_t *ctx, apr_pool_t *pool) { @@ -183,6 +186,7 @@ svn_client_revert3(const apr_array_header_t *paths, baton.changelists = changelists; baton.clear_changelists = clear_changelists; baton.metadata_only = metadata_only; + baton.added_keep_local = added_keep_local; baton.ctx = ctx; err = svn_wc__is_wcroot(&wc_root, ctx->wc_ctx, local_abspath, iterpool); diff --git a/subversion/libsvn_client/shelf.c b/subversion/libsvn_client/shelf.c new file mode 100644 index 0000000..a666efc --- /dev/null +++ b/subversion/libsvn_client/shelf.c @@ -0,0 +1,1273 @@ +/* + * shelf.c: implementation of shelving + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + +/* We define this here to remove any further warnings about the usage of + experimental functions in this file. */ +#define SVN_EXPERIMENTAL + +#include "svn_client.h" +#include "svn_wc.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_utf.h" +#include "svn_ctype.h" +#include "svn_props.h" + +#include "client.h" +#include "private/svn_client_private.h" +#include "private/svn_wc_private.h" +#include "private/svn_sorts_private.h" +#include "svn_private_config.h" + + +static svn_error_t * +shelf_name_encode(char **encoded_name_p, + const char *name, + apr_pool_t *result_pool) +{ + char *encoded_name + = apr_palloc(result_pool, strlen(name) * 2 + 1); + char *out_pos = encoded_name; + + if (name[0] == '\0') + return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL, + _("Shelf name cannot be the empty string")); + + while (*name) + { + apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++)); + out_pos += 2; + } + *encoded_name_p = encoded_name; + return SVN_NO_ERROR; +} + +static svn_error_t * +shelf_name_decode(char **decoded_name_p, + const char *codename, + apr_pool_t *result_pool) +{ + svn_stringbuf_t *sb + = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool); + const char *input = codename; + + while (*input) + { + int c; + int nchars; + int nitems = sscanf(input, "%02x%n", &c, &nchars); + + if (nitems != 1 || nchars != 2) + return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL, + _("Shelve: Bad encoded name '%s'"), codename); + svn_stringbuf_appendbyte(sb, c); + input += 2; + } + *decoded_name_p = sb->data; + return SVN_NO_ERROR; +} + +/* Set *NAME to the shelf name from FILENAME, if FILENAME names a '.current' + * file, else to NULL. */ +static svn_error_t * +shelf_name_from_filename(char **name, + const char *filename, + apr_pool_t *result_pool) +{ + size_t len = strlen(filename); + static const char suffix[] = ".current"; + size_t suffix_len = sizeof(suffix) - 1; + + if (len > suffix_len && strcmp(filename + len - suffix_len, suffix) == 0) + { + char *codename = apr_pstrndup(result_pool, filename, len - suffix_len); + SVN_ERR(shelf_name_decode(name, codename, result_pool)); + } + else + { + *name = NULL; + } + return SVN_NO_ERROR; +} + +/* Set *DIR to the shelf storage directory inside the WC's administrative + * area. Ensure the directory exists. */ +static svn_error_t * +get_shelves_dir(char **dir, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *experimental_abspath; + + SVN_ERR(svn_wc__get_experimental_dir(&experimental_abspath, + wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + *dir = svn_dirent_join(experimental_abspath, "shelves/v3", result_pool); + + /* Ensure the directory exists. (Other versions of svn don't create it.) */ + SVN_ERR(svn_io_make_dir_recursively(*dir, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the file storage dir for SHELF + * version VERSION, no matter whether it exists. + */ +static svn_error_t * +shelf_version_files_dir_abspath(const char **abspath, + svn_client__shelf_t *shelf, + int version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *codename; + char *filename; + + SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); + filename = apr_psprintf(scratch_pool, "%s-%03d.wc", codename, version); + *abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + return SVN_NO_ERROR; +} + +/* Create a shelf-version object for a version that may or may not already + * exist on disk. + */ +static svn_error_t * +shelf_version_create(svn_client__shelf_version_t **new_version_p, + svn_client__shelf_t *shelf, + int version_number, + apr_pool_t *result_pool) +{ + svn_client__shelf_version_t *shelf_version + = apr_pcalloc(result_pool, sizeof(*shelf_version)); + + shelf_version->shelf = shelf; + shelf_version->version_number = version_number; + SVN_ERR(shelf_version_files_dir_abspath(&shelf_version->files_dir_abspath, + shelf, version_number, + result_pool, result_pool)); + *new_version_p = shelf_version; + return SVN_NO_ERROR; +} + +/* Delete the storage for SHELF:VERSION. */ +static svn_error_t * +shelf_version_delete(svn_client__shelf_t *shelf, + int version, + apr_pool_t *scratch_pool) +{ + const char *files_dir_abspath; + + SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath, + shelf, version, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/, + NULL, NULL, /*cancel*/ + scratch_pool)); + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +get_log_abspath(char **log_abspath, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *codename; + const char *filename; + + SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); + filename = apr_pstrcat(scratch_pool, codename, ".log", SVN_VA_NULL); + *log_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + return SVN_NO_ERROR; +} + +/* Set SHELF->revprops by reading from its storage (the '.log' file). + * Set SHELF->revprops to empty if the storage file does not exist; this + * is not an error. + */ +static svn_error_t * +shelf_read_revprops(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool) +{ + char *log_abspath; + svn_error_t *err; + svn_stream_t *stream; + + SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool)); + + shelf->revprops = apr_hash_make(shelf->pool); + err = svn_stream_open_readonly(&stream, log_abspath, + scratch_pool, scratch_pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + SVN_ERR(svn_hash_read2(shelf->revprops, stream, "PROPS-END", shelf->pool)); + SVN_ERR(svn_stream_close(stream)); + return SVN_NO_ERROR; +} + +/* Write SHELF's revprops to its file storage. + */ +static svn_error_t * +shelf_write_revprops(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool) +{ + char *log_abspath; + apr_file_t *file; + svn_stream_t *stream; + + SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool)); + + SVN_ERR(svn_io_file_open(&file, log_abspath, + APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, + APR_FPROT_OS_DEFAULT, scratch_pool)); + stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool); + + SVN_ERR(svn_hash_write2(shelf->revprops, stream, "PROPS-END", scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_revprop_set(svn_client__shelf_t *shelf, + const char *prop_name, + const svn_string_t *prop_val, + apr_pool_t *scratch_pool) +{ + svn_hash_sets(shelf->revprops, apr_pstrdup(shelf->pool, prop_name), + svn_string_dup(prop_val, shelf->pool)); + SVN_ERR(shelf_write_revprops(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_revprop_set_all(svn_client__shelf_t *shelf, + apr_hash_t *revprop_table, + apr_pool_t *scratch_pool) +{ + if (revprop_table) + shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool); + else + shelf->revprops = apr_hash_make(shelf->pool); + + SVN_ERR(shelf_write_revprops(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_revprop_get(svn_string_t **prop_val, + svn_client__shelf_t *shelf, + const char *prop_name, + apr_pool_t *result_pool) +{ + *prop_val = svn_hash_gets(shelf->revprops, prop_name); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_revprop_list(apr_hash_t **props, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool) +{ + *props = shelf->revprops; + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +get_current_abspath(char **current_abspath, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool) +{ + char *codename; + char *filename; + + SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); + filename = apr_psprintf(result_pool, "%s.current", codename); + *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + return SVN_NO_ERROR; +} + +/* Read SHELF->max_version from its storage (the '.current' file). + * Set SHELF->max_version to -1 if that file does not exist. + */ +static svn_error_t * +shelf_read_current(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool) +{ + char *current_abspath; + svn_error_t *err; + + SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool)); + err = svn_io_read_version_file(&shelf->max_version, + current_abspath, scratch_pool); + if (err) + { + shelf->max_version = -1; + svn_error_clear(err); + return SVN_NO_ERROR; + } + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +shelf_write_current(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool) +{ + char *current_abspath; + + SVN_ERR(get_current_abspath(¤t_abspath, shelf, scratch_pool)); + SVN_ERR(svn_io_write_version_file(current_abspath, shelf->max_version, + scratch_pool)); + return SVN_NO_ERROR; +} + +/*-------------------------------------------------------------------------*/ +/* Status Reporting */ + +/* Adjust a status STATUS_IN obtained from the shelf storage WC, to add + * shelf-related metadata: + * - changelist: 'svn:shelf:SHELFNAME' + */ +static svn_error_t * +status_augment(svn_wc_status3_t **status_p, + const svn_wc_status3_t *status_in, + svn_client__shelf_version_t *shelf_version, + apr_pool_t *result_pool) +{ + *status_p = svn_wc_dup_status3(status_in, result_pool); + (*status_p)->changelist = apr_psprintf(result_pool, "svn:shelf:%s", + shelf_version->shelf->name); + return SVN_NO_ERROR; +} + +/* Read status from shelf storage. + */ +static svn_error_t * +status_read(svn_wc_status3_t **status, + svn_client__shelf_version_t *shelf_version, + const char *relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client_ctx_t *ctx = shelf_version->shelf->ctx; + char *abspath + = svn_dirent_join(shelf_version->files_dir_abspath, relpath, + scratch_pool); + + SVN_ERR(svn_wc_status3(status, ctx->wc_ctx, abspath, + result_pool, scratch_pool)); + SVN_ERR(status_augment(status, *status, shelf_version, result_pool)); + return SVN_NO_ERROR; +} + +/* A visitor function type for use with shelf_status_walk(). + * The same as svn_wc_status_func4_t except relpath instead of abspath. + */ +typedef svn_error_t *(*shelf_status_visitor_t)(void *baton, + const char *relpath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool); + +/* Baton for shelved_files_walk_visitor(). */ +struct shelf_status_baton_t +{ + svn_client__shelf_version_t *shelf_version; + shelf_status_visitor_t walk_func; + void *walk_baton; +}; + +/* Convert a svn_wc_status_func4_t callback invocation to call a + * shelf_status_visitor_t callback. + * + * Call BATON->walk_func(BATON->walk_baton, relpath, ...) for the shelved + * storage path ABSPATH, converting ABSPATH to a WC-relative path, and + * augmenting the STATUS. + * + * The opposite of wc_status_visitor(). + * + * Implements svn_wc_status_func4_t. */ +static svn_error_t * +shelf_status_visitor(void *baton, + const char *abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + struct shelf_status_baton_t *b = baton; + const char *relpath; + svn_wc_status3_t *new_status; + + relpath = svn_dirent_skip_ancestor(b->shelf_version->files_dir_abspath, + abspath); + SVN_ERR(status_augment(&new_status, status, b->shelf_version, scratch_pool)); + SVN_ERR(b->walk_func(b->walk_baton, relpath, new_status, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Report the shelved status of the path SHELF_VERSION:WC_RELPATH + * via WALK_FUNC(WALK_BATON, ...). + */ +static svn_error_t * +shelf_status_visit_path(svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + shelf_status_visitor_t walk_func, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + svn_wc_status3_t *status; + + SVN_ERR(status_read(&status, shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(walk_func(walk_baton, wc_relpath, status, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Report the shelved status of all the shelved paths in SHELF_VERSION + * at and under WC_RELPATH, via WALK_FUNC(WALK_BATON, ...). + */ +static svn_error_t * +shelf_status_walk(svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + shelf_status_visitor_t walk_func, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + svn_client_ctx_t *ctx = shelf_version->shelf->ctx; + char *walk_root_abspath + = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + scratch_pool); + struct shelf_status_baton_t baton; + svn_error_t *err; + + baton.shelf_version = shelf_version; + baton.walk_func = walk_func; + baton.walk_baton = walk_baton; + err = svn_wc_walk_status(ctx->wc_ctx, walk_root_abspath, + svn_depth_infinity, + FALSE /*get_all*/, + TRUE /*no_ignore*/, + FALSE /*ignore_text_mods*/, + NULL /*ignore_patterns: use the defaults*/, + shelf_status_visitor, &baton, + NULL, NULL, /*cancellation*/ + scratch_pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + svn_error_clear(err); + else + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Baton for wc_status_visitor(). */ +typedef struct wc_status_baton_t +{ + svn_client__shelf_version_t *shelf_version; + svn_wc_status_func4_t walk_func; + void *walk_baton; +} wc_status_baton_t; + +/* Convert a shelf_status_visitor_t callback invocation to call a + * svn_wc_status_func4_t callback. + * + * Call BATON->walk_func(BATON->walk_baton, abspath, ...) for the WC- + * relative path RELPATH, converting RELPATH to an abspath in the user's WC. + * + * The opposite of shelf_status_visitor(). + * + * Implements shelf_status_visitor_t. */ +static svn_error_t * +wc_status_visitor(void *baton, + const char *relpath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + struct wc_status_baton_t *b = baton; + svn_client__shelf_t *shelf = b->shelf_version->shelf; + const char *abspath = svn_dirent_join(shelf->wc_root_abspath, relpath, + scratch_pool); + SVN_ERR(b->walk_func(b->walk_baton, abspath, status, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_version_status_walk(svn_client__shelf_version_t *shelf_version, + const char *wc_relpath, + svn_wc_status_func4_t walk_func, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + wc_status_baton_t baton; + + baton.shelf_version = shelf_version; + baton.walk_func = walk_func; + baton.walk_baton = walk_baton; + SVN_ERR(shelf_status_walk(shelf_version, wc_relpath, + wc_status_visitor, &baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/*-------------------------------------------------------------------------*/ +/* Shelf Storage */ + +/* Construct a shelf object representing an empty shelf: no versions, + * no revprops, no looking to see if such a shelf exists on disk. + */ +static svn_error_t * +shelf_construct(svn_client__shelf_t **shelf_p, + const char *name, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + svn_client__shelf_t *shelf = apr_palloc(result_pool, sizeof(*shelf)); + char *shelves_dir; + + SVN_ERR(svn_client_get_wc_root(&shelf->wc_root_abspath, + local_abspath, ctx, + result_pool, result_pool)); + SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath, + result_pool, result_pool)); + shelf->shelves_dir = shelves_dir; + shelf->ctx = ctx; + shelf->pool = result_pool; + + shelf->name = apr_pstrdup(result_pool, name); + shelf->revprops = apr_hash_make(result_pool); + shelf->max_version = 0; + + *shelf_p = shelf; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_open_existing(svn_client__shelf_t **shelf_p, + const char *name, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + SVN_ERR(shelf_construct(shelf_p, name, + local_abspath, ctx, result_pool)); + SVN_ERR(shelf_read_revprops(*shelf_p, result_pool)); + SVN_ERR(shelf_read_current(*shelf_p, result_pool)); + if ((*shelf_p)->max_version < 0) + { + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Shelf '%s' not found"), + name); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_open_or_create(svn_client__shelf_t **shelf_p, + const char *name, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + svn_client__shelf_t *shelf; + + SVN_ERR(shelf_construct(&shelf, name, + local_abspath, ctx, result_pool)); + SVN_ERR(shelf_read_revprops(shelf, result_pool)); + SVN_ERR(shelf_read_current(shelf, result_pool)); + if (shelf->max_version < 0) + { + shelf->max_version = 0; + SVN_ERR(shelf_write_current(shelf, result_pool)); + } + *shelf_p = shelf; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_close(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool) +{ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_delete(const char *name, + const char *local_abspath, + svn_boolean_t dry_run, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client__shelf_t *shelf; + int i; + char *abspath; + + SVN_ERR(svn_client__shelf_open_existing(&shelf, name, + local_abspath, ctx, scratch_pool)); + + /* Remove the versions. */ + for (i = shelf->max_version; i > 0; i--) + { + SVN_ERR(shelf_version_delete(shelf, i, scratch_pool)); + } + + /* Remove the other files */ + SVN_ERR(get_log_abspath(&abspath, shelf, scratch_pool, scratch_pool)); + SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool)); + SVN_ERR(get_current_abspath(&abspath, shelf, scratch_pool)); + SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool)); + + SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Baton for paths_changed_visitor(). */ +struct paths_changed_walk_baton_t +{ + apr_hash_t *paths_hash; + const char *wc_root_abspath; + apr_pool_t *pool; +}; + +/* Add to the list(s) in BATON, the RELPATH of a shelved 'binary' file. + * Implements shelved_files_walk_func_t. */ +static svn_error_t * +paths_changed_visitor(void *baton, + const char *relpath, + const svn_wc_status3_t *s, + apr_pool_t *scratch_pool) +{ + struct paths_changed_walk_baton_t *b = baton; + + relpath = apr_pstrdup(b->pool, relpath); + svn_hash_sets(b->paths_hash, relpath, relpath); + return SVN_NO_ERROR; +} + +/* Get the paths changed, relative to WC root or as abspaths, as a hash + * and/or an array (in no particular order). + */ +static svn_error_t * +shelf_paths_changed(apr_hash_t **paths_hash_p, + apr_array_header_t **paths_array_p, + svn_client__shelf_version_t *shelf_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client__shelf_t *shelf = shelf_version->shelf; + apr_hash_t *paths_hash = apr_hash_make(result_pool); + struct paths_changed_walk_baton_t baton; + + baton.paths_hash = paths_hash; + baton.wc_root_abspath = shelf->wc_root_abspath; + baton.pool = result_pool; + SVN_ERR(shelf_status_walk(shelf_version, "", + paths_changed_visitor, &baton, + scratch_pool)); + + if (paths_hash_p) + *paths_hash_p = paths_hash; + if (paths_array_p) + SVN_ERR(svn_hash_keys(paths_array_p, paths_hash, result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_paths_changed(apr_hash_t **affected_paths, + svn_client__shelf_version_t *shelf_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR(shelf_paths_changed(affected_paths, NULL, shelf_version, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_replay(svn_client__shelf_version_t *shelf_version, + const char *top_relpath, + const svn_delta_editor_t *editor, + void *edit_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + svn_client_ctx_t *ctx = shelf_version->shelf->ctx; + apr_array_header_t *src_targets = apr_array_make(scratch_pool, 1, + sizeof(char *)); + const char *src_wc_abspath + = svn_dirent_join(shelf_version->files_dir_abspath, top_relpath, scratch_pool); + + APR_ARRAY_PUSH(src_targets, const char *) = src_wc_abspath; + SVN_ERR(svn_client__wc_replay(src_wc_abspath, + src_targets, svn_depth_infinity, NULL, + editor, edit_baton, + notify_func, notify_baton, + ctx, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Baton for test_apply_file_visitor(). */ +struct test_apply_files_baton_t +{ + svn_client__shelf_version_t *shelf_version; + svn_boolean_t conflict; /* would it conflict? */ + svn_client_ctx_t *ctx; +}; + +/* Ideally, set BATON->conflict if we can't apply a change to WC + * at RELPATH without conflict. But in fact, just check + * if WC at RELPATH is locally modified. + * + * Implements shelved_files_walk_func_t. */ +static svn_error_t * +test_apply_file_visitor(void *baton, + const char *relpath, + const svn_wc_status3_t *s, + apr_pool_t *scratch_pool) +{ + struct test_apply_files_baton_t *b = baton; + const char *wc_root_abspath = b->shelf_version->shelf->wc_root_abspath; + const char *to_wc_abspath = svn_dirent_join(wc_root_abspath, relpath, + scratch_pool); + svn_wc_status3_t *status; + + SVN_ERR(svn_wc_status3(&status, b->ctx->wc_ctx, to_wc_abspath, + scratch_pool, scratch_pool)); + switch (status->node_status) + { + case svn_wc_status_normal: + case svn_wc_status_none: + break; + default: + b->conflict = TRUE; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_test_apply_file(svn_boolean_t *conflict_p, + svn_client__shelf_version_t *shelf_version, + const char *file_relpath, + apr_pool_t *scratch_pool) +{ + struct test_apply_files_baton_t baton = {0}; + + baton.shelf_version = shelf_version; + baton.conflict = FALSE; + baton.ctx = shelf_version->shelf->ctx; + SVN_ERR(shelf_status_visit_path(shelf_version, file_relpath, + test_apply_file_visitor, &baton, + scratch_pool)); + *conflict_p = baton.conflict; + + return SVN_NO_ERROR; +} + +static svn_error_t * +wc_mods_editor(const svn_delta_editor_t **editor_p, + void **edit_baton_p, + const char *dst_wc_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client__pathrev_t *base; + const char *dst_wc_url; + svn_ra_session_t *ra_session; + + /* We'll need an RA session to obtain the base of any copies */ + SVN_ERR(svn_client__wc_node_get_base(&base, + dst_wc_abspath, ctx->wc_ctx, + scratch_pool, scratch_pool)); + dst_wc_url = base->url; + SVN_ERR(svn_client_open_ra_session2(&ra_session, + dst_wc_url, dst_wc_abspath, + ctx, result_pool, scratch_pool)); + SVN_ERR(svn_client__wc_editor(editor_p, edit_baton_p, + dst_wc_abspath, + notify_func, notify_baton, + ra_session, ctx, result_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_mods_editor(const svn_delta_editor_t **editor_p, + void **edit_baton_p, + svn_client__shelf_version_t *shelf_version, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + SVN_ERR(wc_mods_editor(editor_p, edit_baton_p, + shelf_version->files_dir_abspath, + notify_func, notify_baton, + ctx, result_pool, result_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_apply(svn_client__shelf_version_t *shelf_version, + svn_boolean_t dry_run, + apr_pool_t *scratch_pool) +{ + svn_client__shelf_t *shelf = shelf_version->shelf; + const svn_delta_editor_t *editor; + void *edit_baton; + + SVN_ERR(wc_mods_editor(&editor, &edit_baton, + shelf->wc_root_abspath, + NULL, NULL, /*notification*/ + shelf->ctx, scratch_pool, scratch_pool)); + + SVN_ERR(svn_client__shelf_replay(shelf_version, "", + editor, edit_baton, + shelf->ctx->notify_func2, shelf->ctx->notify_baton2, + scratch_pool)); + + svn_io_sleep_for_timestamps(shelf->wc_root_abspath, + scratch_pool); + return SVN_NO_ERROR; +} + +/* Baton for paths_changed_visitor(). */ +struct unapply_walk_baton_t +{ + const char *wc_root_abspath; + svn_boolean_t dry_run; + svn_boolean_t use_commit_times; + svn_client_ctx_t *ctx; + apr_pool_t *pool; +}; + +/* Revert the change at RELPATH in the user's WC. + * Implements shelved_files_walk_func_t. */ +static svn_error_t * +unapply_visitor(void *baton, + const char *relpath, + const svn_wc_status3_t *s, + apr_pool_t *scratch_pool) +{ + struct unapply_walk_baton_t *b = baton; + const char *abspath = svn_dirent_join(b->wc_root_abspath, relpath, + scratch_pool); + + if (!b->dry_run) + { + apr_array_header_t *targets + = apr_array_make(scratch_pool, 1, sizeof(char *)); + svn_depth_t depth; + + APR_ARRAY_PUSH(targets, const char *) = abspath; + + /* If the local modification is a "delete" then revert it all + (recursively). Otherwise we'd have to walk paths in + top-down order to revert a delete, whereas we need bottom-up + order to revert children of an added directory. */ + if (s->node_status == svn_wc_status_deleted + || s->node_status == svn_wc_status_replaced + || s->node_status == svn_wc_status_added) + depth = svn_depth_infinity; + else + depth = svn_depth_empty; + SVN_ERR(svn_wc_revert6(b->ctx->wc_ctx, + abspath, + depth, + b->use_commit_times, + NULL /*changelists*/, + FALSE /*clear_changelists*/, + FALSE /*metadata_only*/, + FALSE /*added_keep_local*/, + b->ctx->cancel_func, b->ctx->cancel_baton, + NULL, NULL, /*notification*/ + scratch_pool)); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_unapply(svn_client__shelf_version_t *shelf_version, + svn_boolean_t dry_run, + apr_pool_t *scratch_pool) +{ + svn_client_ctx_t *ctx = shelf_version->shelf->ctx; + svn_client__shelf_t *shelf = shelf_version->shelf; + struct unapply_walk_baton_t baton; + svn_config_t *cfg; + + baton.wc_root_abspath = shelf->wc_root_abspath; + baton.dry_run = dry_run; + baton.ctx = ctx; + baton.pool = scratch_pool; + + cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) + : NULL; + SVN_ERR(svn_config_get_bool(cfg, &baton.use_commit_times, + SVN_CONFIG_SECTION_MISCELLANY, + SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); + + SVN_WC__CALL_WITH_WRITE_LOCK( + shelf_status_walk(shelf_version, "", + unapply_visitor, &baton, + scratch_pool), + ctx->wc_ctx, shelf_version->shelf->wc_root_abspath, + FALSE /*lock_anchor*/, scratch_pool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_delete_newer_versions(svn_client__shelf_t *shelf, + svn_client__shelf_version_t *shelf_version, + apr_pool_t *scratch_pool) +{ + int previous_version = shelf_version ? shelf_version->version_number : 0; + int i; + + /* Delete any newer checkpoints */ + for (i = shelf->max_version; i > previous_version; i--) + { + SVN_ERR(shelf_version_delete(shelf, i, scratch_pool)); + } + + shelf->max_version = previous_version; + SVN_ERR(shelf_write_current(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_diff(svn_client__shelf_version_t *shelf_version, + const char *shelf_relpath, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + const svn_diff_tree_processor_t *diff_processor, + apr_pool_t *scratch_pool) +{ + svn_client_ctx_t *ctx = shelf_version->shelf->ctx; + char *local_abspath + = svn_dirent_join(shelf_version->files_dir_abspath, shelf_relpath, + scratch_pool); + + if (shelf_version->version_number == 0) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__diff7(FALSE /*anchor_at_given_paths*/, + ctx->wc_ctx, local_abspath, + depth, + ignore_ancestry, + NULL /*changelists*/, + diff_processor, + NULL, NULL, /*cancellation*/ + scratch_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Populate the storage a new shelf-version object NEW_SHELF_VERSION, + * by creating a shelf storage WC with its base state copied from the + * 'real' WC. + */ +static svn_error_t * +shelf_copy_base(svn_client__shelf_version_t *new_shelf_version, + apr_pool_t *scratch_pool) +{ + svn_client_ctx_t *ctx = new_shelf_version->shelf->ctx; + const char *users_wc_abspath = new_shelf_version->shelf->wc_root_abspath; + svn_client__pathrev_t *users_wc_root_base; + svn_opt_revision_t users_wc_root_rev; + svn_ra_session_t *ra_session = NULL; + svn_boolean_t sleep_here = FALSE; + + SVN_ERR(svn_client__wc_node_get_base(&users_wc_root_base, + users_wc_abspath, ctx->wc_ctx, + scratch_pool, scratch_pool)); + + /* ### We need to read and recreate the mixed-rev, switched-URL, + mixed-depth WC state; but for a rough start we'll just use + HEAD, unswitched, depth-infinity. */ + users_wc_root_rev.kind = svn_opt_revision_head; + + /* ### TODO: Create an RA session that reads from the user's WC. + For a rough start, we'll just let 'checkout' read from the repo. */ + + SVN_ERR(svn_client__checkout_internal(NULL /*result_rev*/, &sleep_here, + users_wc_root_base->url, + new_shelf_version->files_dir_abspath, + &users_wc_root_rev, &users_wc_root_rev, + svn_depth_infinity, + TRUE /*ignore_externals*/, + FALSE /*allow_unver_obstructions*/, + ra_session, + ctx, scratch_pool)); + /* ### hopefully we won't eventually need to sleep_here... */ + if (sleep_here) + svn_io_sleep_for_timestamps(new_shelf_version->files_dir_abspath, + scratch_pool); + return SVN_NO_ERROR; +} + +/* */ +struct shelf_save_notifer_baton_t +{ + svn_client__shelf_version_t *shelf_version; + svn_wc_notify_func2_t notify_func; + void *notify_baton; + svn_client_status_func_t shelved_func; + void *shelved_baton; + svn_boolean_t any_shelved; +}; + +/* */ +static void +shelf_save_notifier(void *baton, + const svn_wc_notify_t *notify, + apr_pool_t *pool) +{ + struct shelf_save_notifer_baton_t *nb = baton; + const char *wc_relpath + = svn_dirent_skip_ancestor(nb->shelf_version->shelf->wc_root_abspath, + notify->path); + svn_client_status_t *cst = NULL; +#if 0 + svn_wc_status3_t *wc_status; + + svn_error_clear(status_read(&wc_status, nb->shelf_version, wc_relpath, + pool, pool)); + svn_error_clear(svn_client__create_status( + &cst, nb->shelf_version->shelf->ctx->wc_ctx, + notify->path, wc_status, pool, pool)); +#endif + svn_error_clear(nb->shelved_func(nb->shelved_baton, wc_relpath, cst, pool)); + nb->any_shelved = TRUE; + + nb->notify_func(nb->notify_baton, notify, pool); +} + +svn_error_t * +svn_client__shelf_save_new_version3(svn_client__shelf_version_t **new_version_p, + svn_client__shelf_t *shelf, + const apr_array_header_t *paths, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_client_status_func_t shelved_func, + void *shelved_baton, + svn_client_status_func_t not_shelved_func, + void *not_shelved_baton, + apr_pool_t *scratch_pool) +{ + svn_client_ctx_t *ctx = shelf->ctx; + int next_version = shelf->max_version + 1; + svn_client__shelf_version_t *new_shelf_version; + struct shelf_save_notifer_baton_t nb; + const svn_delta_editor_t *editor; + void *edit_baton; + + SVN_ERR(shelf_version_create(&new_shelf_version, + shelf, next_version, scratch_pool)); + SVN_ERR(shelf_copy_base(new_shelf_version, scratch_pool)); + + nb.shelf_version = new_shelf_version; + nb.notify_func = ctx->notify_func2; + nb.notify_baton = ctx->notify_baton2; + nb.shelved_func = shelved_func; + nb.shelved_baton = shelved_baton; + nb.any_shelved = FALSE; + SVN_ERR(svn_client__shelf_mods_editor(&editor, &edit_baton, + new_shelf_version, + NULL, NULL, /*notification*/ + ctx, scratch_pool)); + SVN_ERR(svn_client__wc_replay(shelf->wc_root_abspath, + paths, depth, changelists, + editor, edit_baton, + shelf_save_notifier, &nb, + ctx, scratch_pool)); + + if (nb.any_shelved) + { + shelf->max_version = next_version; + SVN_ERR(shelf_write_current(shelf, scratch_pool)); + + if (new_version_p) + SVN_ERR(svn_client__shelf_version_open(new_version_p, shelf, next_version, + scratch_pool, scratch_pool)); + } + else + { + if (new_version_p) + *new_version_p = NULL; + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_get_log_message(char **log_message, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool) +{ + svn_string_t *propval = svn_hash_gets(shelf->revprops, SVN_PROP_REVISION_LOG); + + if (propval) + *log_message = apr_pstrdup(result_pool, propval->data); + else + *log_message = NULL; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_set_log_message(svn_client__shelf_t *shelf, + const char *message, + apr_pool_t *scratch_pool) +{ + svn_string_t *propval + = message ? svn_string_create(message, shelf->pool) : NULL; + + SVN_ERR(svn_client__shelf_revprop_set(shelf, SVN_PROP_REVISION_LOG, propval, + scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_list(apr_hash_t **shelf_infos, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *wc_root_abspath; + char *shelves_dir; + apr_hash_t *dirents; + apr_hash_index_t *hi; + + SVN_ERR(svn_wc__get_wcroot(&wc_root_abspath, ctx->wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/, + result_pool, scratch_pool)); + + *shelf_infos = apr_hash_make(result_pool); + + /* Remove non-shelves */ + for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi)) + { + const char *filename = apr_hash_this_key(hi); + svn_io_dirent2_t *dirent = apr_hash_this_val(hi); + char *name = NULL; + + svn_error_clear(shelf_name_from_filename(&name, filename, result_pool)); + if (name && dirent->kind == svn_node_file) + { + svn_client__shelf_info_t *info + = apr_palloc(result_pool, sizeof(*info)); + + info->mtime = dirent->mtime; + svn_hash_sets(*shelf_infos, name, info); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_version_open(svn_client__shelf_version_t **shelf_version_p, + svn_client__shelf_t *shelf, + int version_number, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client__shelf_version_t *shelf_version; + const svn_io_dirent2_t *dirent; + + SVN_ERR(shelf_version_create(&shelf_version, + shelf, version_number, result_pool)); + SVN_ERR(svn_io_stat_dirent2(&dirent, + shelf_version->files_dir_abspath, + FALSE /*verify_truename*/, + TRUE /*ignore_enoent*/, + result_pool, scratch_pool)); + if (dirent->kind == svn_node_none) + { + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Shelf '%s' version %d not found"), + shelf->name, version_number); + } + shelf_version->mtime = dirent->mtime; + *shelf_version_p = shelf_version; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_get_newest_version(svn_client__shelf_version_t **shelf_version_p, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (shelf->max_version == 0) + { + *shelf_version_p = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_client__shelf_version_open(shelf_version_p, + shelf, shelf->max_version, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_get_all_versions(apr_array_header_t **versions_p, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i; + + *versions_p = apr_array_make(result_pool, shelf->max_version - 1, + sizeof(svn_client__shelf_version_t *)); + + for (i = 1; i <= shelf->max_version; i++) + { + svn_client__shelf_version_t *shelf_version; + + SVN_ERR(svn_client__shelf_version_open(&shelf_version, + shelf, i, + result_pool, scratch_pool)); + APR_ARRAY_PUSH(*versions_p, svn_client__shelf_version_t *) = shelf_version; + } + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_client/shelve.c b/subversion/libsvn_client/shelve.c deleted file mode 100644 index 4eeb4dd..0000000 --- a/subversion/libsvn_client/shelve.c +++ /dev/null @@ -1,560 +0,0 @@ -/* - * shelve.c: implementation of the 'shelve' commands - * - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - */ - -/* ==================================================================== */ - -/* We define this here to remove any further warnings about the usage of - experimental functions in this file. */ -#define SVN_EXPERIMENTAL - -#include "svn_client.h" -#include "svn_wc.h" -#include "svn_pools.h" -#include "svn_dirent_uri.h" -#include "svn_path.h" -#include "svn_hash.h" -#include "svn_utf.h" -#include "svn_ctype.h" - -#include "client.h" -#include "private/svn_client_private.h" -#include "private/svn_wc_private.h" -#include "svn_private_config.h" - - -static svn_error_t * -shelf_name_encode(char **encoded_name_p, - const char *name, - apr_pool_t *result_pool) -{ - char *encoded_name - = apr_palloc(result_pool, strlen(name) * 2 + 1); - char *out_pos = encoded_name; - - if (name[0] == '\0') - return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL, - _("Shelf name cannot be the empty string")); - - while (*name) - { - apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++)); - out_pos += 2; - } - *encoded_name_p = encoded_name; - return SVN_NO_ERROR; -} - -static svn_error_t * -shelf_name_decode(char **decoded_name_p, - const char *codename, - apr_pool_t *result_pool) -{ - svn_stringbuf_t *sb - = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool); - const char *input = codename; - - while (*input) - { - int c; - int nchars; - int nitems = sscanf(input, "%02x%n", &c, &nchars); - - if (nitems != 1 || nchars != 2) - return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL, - _("Shelve: Bad encoded name '%s'"), codename); - svn_stringbuf_appendbyte(sb, c); - input += 2; - } - *decoded_name_p = sb->data; - return SVN_NO_ERROR; -} - -/* Set *NAME to the shelf name from FILENAME. */ -static svn_error_t * -shelf_name_from_filename(char **name, - const char *filename, - apr_pool_t *result_pool) -{ - size_t len = strlen(filename); - - if (len > 6 && strcmp(filename + len - 6, ".patch") == 0) - { - char *codename = apr_pstrndup(result_pool, filename, len - 6); - SVN_ERR(shelf_name_decode(name, codename, result_pool)); - } - return SVN_NO_ERROR; -} - -/* Set *PATCH_ABSPATH to the abspath of the patch file for shelved change - * NAME, no matter whether it exists. - */ -static svn_error_t * -get_patch_abspath(char **patch_abspath, - const char *name, - const char *wc_root_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - char *dir; - char *filename; - - SVN_ERR(svn_wc__get_shelves_dir(&dir, ctx->wc_ctx, wc_root_abspath, - scratch_pool, scratch_pool)); - SVN_ERR(shelf_name_encode(&filename, name, scratch_pool)); - filename = apr_pstrcat(scratch_pool, filename, ".patch", SVN_VA_NULL); - *patch_abspath = svn_dirent_join(dir, filename, result_pool); - return SVN_NO_ERROR; -} - -/** Write local changes to a patch file for shelved change @a name. - * - * @a message: An optional log message. - * - * @a wc_root_abspath: The WC root dir. - * - * @a overwrite_existing: If a file at @a patch_abspath exists, overwrite it. - * - * @a paths, @a depth, @a changelists: The selection of local paths to diff. - */ -static svn_error_t * -shelf_write_patch(const char *name, - const char *message, - const char *wc_root_abspath, - svn_boolean_t overwrite_existing, - const apr_array_header_t *paths, - svn_depth_t depth, - const apr_array_header_t *changelists, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - char *patch_abspath; - apr_int32_t flag; - apr_file_t *outfile; - svn_stream_t *outstream; - svn_stream_t *errstream; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - int i; - svn_opt_revision_t peg_revision = {svn_opt_revision_unspecified, {0}}; - svn_opt_revision_t start_revision = {svn_opt_revision_base, {0}}; - svn_opt_revision_t end_revision = {svn_opt_revision_working, {0}}; - - SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, - ctx, scratch_pool, scratch_pool)); - - /* Get streams for the output and any error output of the diff. */ - /* ### svn_stream_open_writable() doesn't work here: the buffering - goes wrong so that diff headers appear after their hunks. - For now, fix by opening the file without APR_BUFFERED. */ - flag = APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE; - if (! overwrite_existing) - flag |= APR_FOPEN_EXCL; - SVN_ERR(svn_io_file_open(&outfile, patch_abspath, - flag, APR_FPROT_OS_DEFAULT, scratch_pool)); - outstream = svn_stream_from_aprfile2(outfile, FALSE /*disown*/, scratch_pool); - SVN_ERR(svn_stream_for_stderr(&errstream, scratch_pool)); - - /* Write the patch file header (log message, etc.) */ - if (message) - { - SVN_ERR(svn_stream_printf(outstream, scratch_pool, "%s\n", - message)); - } - SVN_ERR(svn_stream_printf(outstream, scratch_pool, - "--This line, and those below, will be ignored--\n\n")); - SVN_ERR(svn_stream_printf(outstream, scratch_pool, - "--This patch was generated by 'svn shelve'--\n\n")); - - for (i = 0; i < paths->nelts; i++) - { - const char *path = APR_ARRAY_IDX(paths, i, const char *); - apr_hash_t *old_config; - svn_error_t *err; - - if (svn_path_is_url(path)) - return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, - _("'%s' is not a local path"), path); - SVN_ERR(svn_dirent_get_absolute(&path, path, scratch_pool)); - - /* Ensure we use internal diff, not any configured external diff-cmd. */ - old_config = ctx->config; - ctx->config = NULL; - err = svn_client_diff_peg6( - NULL /*options*/, - path, - &peg_revision, - &start_revision, - &end_revision, - wc_root_abspath, - depth, - TRUE /*notice_ancestry*/, - FALSE /*no_diff_added*/, - FALSE /*no_diff_deleted*/, - TRUE /*show_copies_as_adds*/, - FALSE /*ignore_content_type: FALSE -> omit binary files*/, - FALSE /*ignore_properties*/, - FALSE /*properties_only*/, - FALSE /*use_git_diff_format*/, - SVN_APR_LOCALE_CHARSET, - outstream, - errstream, - changelists, - ctx, iterpool); - ctx->config = old_config; - SVN_ERR(err); - } - - SVN_ERR(svn_stream_close(outstream)); - SVN_ERR(svn_stream_close(errstream)); - - return SVN_NO_ERROR; -} - -/** Apply the patch file for shelved change @a name to the WC. - * - * @a wc_root_abspath: The WC root dir. - * - * @a reverse: Apply the patch in reverse. - * - * @a dry_run: Don't really apply the changes, just notify what would be done. - */ -static svn_error_t * -shelf_apply_patch(const char *name, - const char *wc_root_abspath, - svn_boolean_t reverse, - svn_boolean_t dry_run, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - char *patch_abspath; - - SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, - ctx, scratch_pool, scratch_pool)); - SVN_ERR(svn_client_patch(patch_abspath, wc_root_abspath, - dry_run, 0 /*strip*/, - reverse, - FALSE /*ignore_whitespace*/, - TRUE /*remove_tempfiles*/, NULL, NULL, - ctx, scratch_pool)); - - return SVN_NO_ERROR; -} - -/** Delete the patch file for shelved change @a name. - * - * @a wc_root_abspath: The WC root dir. - */ -static svn_error_t * -shelf_delete_patch(const char *name, - const char *wc_root_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - char *patch_abspath, *to_abspath; - - SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, - ctx, scratch_pool, scratch_pool)); - to_abspath = apr_pstrcat(scratch_pool, patch_abspath, ".bak", SVN_VA_NULL); - - /* remove any previous backup */ - SVN_ERR(svn_io_remove_file2(to_abspath, TRUE /*ignore_enoent*/, - scratch_pool)); - - /* move the patch to a backup file */ - SVN_ERR(svn_io_file_rename2(patch_abspath, to_abspath, FALSE /*flush_to_disk*/, - scratch_pool)); - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelve(const char *name, - const apr_array_header_t *paths, - svn_depth_t depth, - const apr_array_header_t *changelists, - svn_boolean_t keep_local, - svn_boolean_t dry_run, - svn_client_ctx_t *ctx, - apr_pool_t *pool) -{ - const char *local_abspath; - const char *wc_root_abspath; - const char *message = ""; - svn_error_t *err; - - /* ### TODO: check all paths are in same WC; for now use first path */ - SVN_ERR(svn_dirent_get_absolute(&local_abspath, - APR_ARRAY_IDX(paths, 0, char *), pool)); - SVN_ERR(svn_client_get_wc_root(&wc_root_abspath, - local_abspath, ctx, pool, pool)); - - /* Fetch the log message and any other revprops */ - if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) - { - const char *tmp_file; - apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(void *)); - - SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, - ctx, pool)); - if (! message) - return SVN_NO_ERROR; - } - - err = shelf_write_patch(name, message, wc_root_abspath, - FALSE /*overwrite_existing*/, - paths, depth, changelists, - ctx, pool); - if (err && APR_STATUS_IS_EEXIST(err->apr_err)) - { - return svn_error_quick_wrapf(err, - "Shelved change '%s' already exists", - name); - } - else - SVN_ERR(err); - - if (!keep_local) - { - /* Reverse-apply the patch. This should be a safer way to remove those - changes from the WC than running a 'revert' operation. */ - SVN_ERR(shelf_apply_patch(name, wc_root_abspath, - TRUE /*reverse*/, dry_run, - ctx, pool)); - } - - if (dry_run) - { - SVN_ERR(shelf_delete_patch(name, wc_root_abspath, - ctx, pool)); - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_unshelve(const char *name, - const char *local_abspath, - svn_boolean_t keep, - svn_boolean_t dry_run, - svn_client_ctx_t *ctx, - apr_pool_t *pool) -{ - const char *wc_root_abspath; - svn_error_t *err; - - SVN_ERR(svn_client_get_wc_root(&wc_root_abspath, - local_abspath, ctx, pool, pool)); - - /* Apply the patch. */ - err = shelf_apply_patch(name, wc_root_abspath, - FALSE /*reverse*/, dry_run, - ctx, pool); - if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) - { - return svn_error_quick_wrapf(err, - "Shelved change '%s' not found", - name); - } - else - SVN_ERR(err); - - /* Remove the patch. */ - if (! keep && ! dry_run) - { - SVN_ERR(shelf_delete_patch(name, wc_root_abspath, - ctx, pool)); - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelves_delete(const char *name, - const char *local_abspath, - svn_boolean_t dry_run, - svn_client_ctx_t *ctx, - apr_pool_t *pool) -{ - const char *wc_root_abspath; - - SVN_ERR(svn_client_get_wc_root(&wc_root_abspath, - local_abspath, ctx, pool, pool)); - - /* Remove the patch. */ - if (! dry_run) - { - svn_error_t *err; - - err = shelf_delete_patch(name, wc_root_abspath, - ctx, pool); - if (err && APR_STATUS_IS_ENOENT(err->apr_err)) - { - return svn_error_quick_wrapf(err, - "Shelved change '%s' not found", - name); - } - else - SVN_ERR(err); - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelf_get_paths(apr_hash_t **affected_paths, - const char *name, - const char *local_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const char *wc_root_abspath; - char *patch_abspath; - svn_patch_file_t *patch_file; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - apr_hash_t *paths = apr_hash_make(result_pool); - - SVN_ERR(svn_client_get_wc_root(&wc_root_abspath, - local_abspath, ctx, scratch_pool, scratch_pool)); - SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, - ctx, scratch_pool, scratch_pool)); - SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, result_pool)); - - while (1) - { - svn_patch_t *patch; - - svn_pool_clear(iterpool); - SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, - FALSE /*reverse*/, - FALSE /*ignore_whitespace*/, - iterpool, iterpool)); - if (! patch) - break; - svn_hash_sets(paths, - apr_pstrdup(result_pool, patch->old_filename), - apr_pstrdup(result_pool, patch->new_filename)); - } - SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool)); - svn_pool_destroy(iterpool); - - *affected_paths = paths; - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelf_has_changes(svn_boolean_t *has_changes, - const char *name, - const char *local_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - apr_hash_t *patch_paths; - - SVN_ERR(svn_client_shelf_get_paths(&patch_paths, name, local_abspath, - ctx, scratch_pool, scratch_pool)); - *has_changes = (apr_hash_count(patch_paths) != 0); - return SVN_NO_ERROR; -} - -/* Set *LOGMSG to the log message stored in the file PATCH_ABSPATH. - * - * ### Currently just reads the first line. - */ -static svn_error_t * -read_logmsg_from_patch(const char **logmsg, - const char *patch_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - apr_file_t *file; - svn_stream_t *stream; - svn_boolean_t eof; - svn_stringbuf_t *line; - - SVN_ERR(svn_io_file_open(&file, patch_abspath, - APR_FOPEN_READ, APR_FPROT_OS_DEFAULT, scratch_pool)); - stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool); - SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool)); - SVN_ERR(svn_stream_close(stream)); - *logmsg = line->data; - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelves_list(apr_hash_t **shelved_patch_infos, - const char *local_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - char *shelves_dir; - apr_hash_t *dirents; - apr_hash_index_t *hi; - - SVN_ERR(svn_wc__get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath, - scratch_pool, scratch_pool)); - SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/, - result_pool, scratch_pool)); - - *shelved_patch_infos = apr_hash_make(result_pool); - - /* Remove non-shelves */ - for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi)) - { - const char *filename = apr_hash_this_key(hi); - svn_io_dirent2_t *dirent = apr_hash_this_val(hi); - char *name = NULL; - - svn_error_clear(shelf_name_from_filename(&name, filename, result_pool)); - if (name && dirent->kind == svn_node_file) - { - svn_client_shelved_patch_info_t *info - = apr_palloc(result_pool, sizeof(*info)); - - info->dirent = dirent; - info->mtime = info->dirent->mtime; - info->patch_path - = svn_dirent_join(shelves_dir, filename, result_pool); - SVN_ERR(read_logmsg_from_patch(&info->message, info->patch_path, - result_pool, scratch_pool)); - - svn_hash_sets(*shelved_patch_infos, name, info); - } - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelves_any(svn_boolean_t *any_shelved, - const char *local_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - apr_hash_t *shelved_patch_infos; - - SVN_ERR(svn_client_shelves_list(&shelved_patch_infos, local_abspath, - ctx, scratch_pool, scratch_pool)); - *any_shelved = apr_hash_count(shelved_patch_infos) != 0; - return SVN_NO_ERROR; -} diff --git a/subversion/libsvn_client/status.c b/subversion/libsvn_client/status.c index a701658..5ab5234 100644 --- a/subversion/libsvn_client/status.c +++ b/subversion/libsvn_client/status.c @@ -23,6 +23,9 @@ /* ==================================================================== */ +/* We define this here to remove any further warnings about the usage of + experimental functions in this file. */ +#define SVN_EXPERIMENTAL /*** Includes. ***/ @@ -329,6 +332,79 @@ do_external_status(svn_client_ctx_t *ctx, return SVN_NO_ERROR; } + +/* Run status on shelf SHELF_NAME, if it exists. + */ +static svn_error_t * +shelf_status(const char *shelf_name, + const char *target_abspath, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_client__shelf_t *shelf; + svn_client__shelf_version_t *shelf_version; + const char *wc_relpath; + + err = svn_client__shelf_open_existing(&shelf, + shelf_name, target_abspath, + ctx, scratch_pool); + if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + SVN_ERR(svn_client__shelf_version_open(&shelf_version, + shelf, shelf->max_version, + scratch_pool, scratch_pool)); + wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath); + SVN_ERR(svn_client__shelf_version_status_walk(shelf_version, wc_relpath, + status_func, status_baton, + scratch_pool)); + SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Run status on all shelves named in CHANGELISTS by a changelist name + * of the form "svn:shelf:SHELF_NAME", if they exist. + */ +static svn_error_t * +shelves_status(const apr_array_header_t *changelists, + const char *target_abspath, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + static const char PREFIX[] = "svn:shelf:"; + static const int PREFIX_LEN = 10; + int i; + + if (! changelists) + return SVN_NO_ERROR; + for (i = 0; i < changelists->nelts; i++) + { + const char *cl = APR_ARRAY_IDX(changelists, i, const char *); + + if (strncmp(cl, PREFIX, PREFIX_LEN) == 0) + { + const char *shelf_name = cl + PREFIX_LEN; + + SVN_ERR(shelf_status(shelf_name, target_abspath, + status_func, status_baton, + ctx, scratch_pool)); + } + } + + return SVN_NO_ERROR; +} + /*** Public Interface. ***/ @@ -586,6 +662,9 @@ svn_client_status6(svn_revnum_t *result_rev, } else { + SVN_ERR(shelves_status(changelists, target_abspath, + tweak_status, &sb, + ctx, pool)); err = svn_wc_walk_status(ctx->wc_ctx, target_abspath, depth, get_all, no_ignore, FALSE, ignores, tweak_status, &sb, diff --git a/subversion/libsvn_client/update.c b/subversion/libsvn_client/update.c index 602f7e3..12469b7 100644 --- a/subversion/libsvn_client/update.c +++ b/subversion/libsvn_client/update.c @@ -615,7 +615,7 @@ svn_client__update_internal(svn_revnum_t *result_rev, { const char *anchor_abspath, *lockroot_abspath; svn_error_t *err; - svn_opt_revision_t peg_revision = *revision; + svn_opt_revision_t opt_rev = *revision; /* operative revision */ apr_hash_t *conflicted_paths = ctx->conflict_func2 ? apr_hash_make(pool) : NULL; @@ -668,7 +668,7 @@ svn_client__update_internal(svn_revnum_t *result_rev, err = update_internal(result_rev, timestamp_sleep, conflicted_paths, &ra_session, missing_parent, - anchor_abspath, &peg_revision, svn_depth_empty, + anchor_abspath, &opt_rev, svn_depth_empty, FALSE, ignore_externals, allow_unver_obstructions, adds_as_modification, FALSE, ctx, pool, iterpool); @@ -679,8 +679,8 @@ svn_client__update_internal(svn_revnum_t *result_rev, /* If we successfully updated a missing parent, let's re-use the returned revision number for future updates for the sake of consistency. */ - peg_revision.kind = svn_opt_revision_number; - peg_revision.value.number = *result_rev; + opt_rev.kind = svn_opt_revision_number; + opt_rev.value.number = *result_rev; } svn_pool_destroy(iterpool); @@ -696,7 +696,7 @@ svn_client__update_internal(svn_revnum_t *result_rev, err = update_internal(result_rev, timestamp_sleep, conflicted_paths, &ra_session, local_abspath, anchor_abspath, - &peg_revision, depth, depth_is_sticky, + &opt_rev, depth, depth_is_sticky, ignore_externals, allow_unver_obstructions, adds_as_modification, TRUE, ctx, pool, pool); diff --git a/subversion/libsvn_client/upgrade.c b/subversion/libsvn_client/upgrade.c index 741443a..edba493 100644 --- a/subversion/libsvn_client/upgrade.c +++ b/subversion/libsvn_client/upgrade.c @@ -303,7 +303,7 @@ upgrade_externals_from_properties(svn_client_ctx_t *ctx, { apr_hash_index_t *hi; apr_pool_t *iterpool; - apr_pool_t *iterpool2; + apr_pool_t *inner_iterpool; apr_hash_t *externals; svn_opt_revision_t rev = {svn_opt_revision_unspecified, {0}}; @@ -317,7 +317,7 @@ upgrade_externals_from_properties(svn_client_ctx_t *ctx, scratch_pool, scratch_pool)); iterpool = svn_pool_create(scratch_pool); - iterpool2 = svn_pool_create(scratch_pool); + inner_iterpool = svn_pool_create(scratch_pool); for (hi = apr_hash_first(scratch_pool, externals); hi; hi = apr_hash_next(hi)) @@ -351,14 +351,12 @@ upgrade_externals_from_properties(svn_client_ctx_t *ctx, iterpool, iterpool); if (!err) - externals_parent_url = svn_path_url_add_component2( - externals_parent_repos_root_url, - externals_parent_repos_relpath, - iterpool); - if (!err) - err = svn_wc_parse_externals_description3( - &externals_p, svn_dirent_dirname(local_abspath, iterpool), - external_desc->data, FALSE, iterpool); + { + err = svn_wc_parse_externals_description3( + &externals_p, svn_dirent_dirname(local_abspath, iterpool), + external_desc->data, FALSE, iterpool); + } + if (err) { svn_wc_notify_t *notify = @@ -376,24 +374,29 @@ upgrade_externals_from_properties(svn_client_ctx_t *ctx, continue; } + externals_parent_url = svn_path_url_add_component2( + externals_parent_repos_root_url, + externals_parent_repos_relpath, + iterpool); + for (i = 0; i < externals_p->nelts; i++) { svn_wc_external_item2_t *item; item = APR_ARRAY_IDX(externals_p, i, svn_wc_external_item2_t*); - svn_pool_clear(iterpool2); + svn_pool_clear(inner_iterpool); err = upgrade_external_item(ctx, externals_parent_abspath, externals_parent_url, externals_parent_repos_root_url, - item, info_baton, iterpool2); + item, info_baton, inner_iterpool); if (err) { svn_wc_notify_t *notify = svn_wc_create_notify(svn_dirent_join(externals_parent_abspath, item->target_dir, - iterpool2), + inner_iterpool), svn_wc_notify_failed_external, scratch_pool); notify->err = err; @@ -405,8 +408,8 @@ upgrade_externals_from_properties(svn_client_ctx_t *ctx, } } + svn_pool_destroy(inner_iterpool); svn_pool_destroy(iterpool); - svn_pool_destroy(iterpool2); return SVN_NO_ERROR; } diff --git a/subversion/libsvn_client/util.c b/subversion/libsvn_client/util.c index 248412b..f6612b9 100644 --- a/subversion/libsvn_client/util.c +++ b/subversion/libsvn_client/util.c @@ -93,6 +93,7 @@ svn_client__pathrev_create_with_session(svn_client__pathrev_t **pathrev_p, pathrev->rev = rev; pathrev->url = apr_pstrdup(result_pool, url); *pathrev_p = pathrev; + SVN_ERR_ASSERT(svn_uri__is_ancestor(pathrev->repos_root_url, url)); return SVN_NO_ERROR; } diff --git a/subversion/libsvn_client/wc_editor.c b/subversion/libsvn_client/wc_editor.c new file mode 100644 index 0000000..145fce0 --- /dev/null +++ b/subversion/libsvn_client/wc_editor.c @@ -0,0 +1,655 @@ +/* + * wc_editor.c: editing the local modifications in the WC. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + +/*** Includes. ***/ + +#include <string.h> +#include "svn_hash.h" +#include "svn_client.h" +#include "svn_delta.h" +#include "svn_dirent_uri.h" +#include "svn_error.h" +#include "svn_error_codes.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_wc.h" + +#include <apr_md5.h> + +#include "client.h" +#include "private/svn_subr_private.h" +#include "private/svn_wc_private.h" +#include "svn_private_config.h" + + +/* ------------------------------------------------------------------ */ + +/* WC Modifications Editor. + * + * This editor applies incoming modifications onto the current working state + * of the working copy, to produce a new working state. + * + * Currently, it assumes the working state matches what the edit driver + * expects to find, and may throw an error if not. + * + * For simplicity, we apply incoming edits as they arrive, rather than + * queueing them up to apply in a batch. + * + * TODO: + * - tests + * - use for all existing scenarios ('svn add', 'svn propset', etc.) + * - Instead of 'root_dir_add' option, probably the driver should anchor + * at the parent dir. + * - Instead of 'ignore_mergeinfo' option, implement that as a wrapper. + * - Option to quietly accept changes that seem to be already applied + * in the versioned state and/or on disk. + * Consider 'svn add' which assumes items to be added are found on disk. + * - Notification. + */ + +/* Everything we need to know about the edit session. + */ +struct edit_baton_t +{ + const char *anchor_abspath; + svn_boolean_t manage_wc_write_lock; + const char *lock_root_abspath; /* the path locked, when locked */ + + /* True => 'open_root' method will act as 'add_directory' */ + svn_boolean_t root_dir_add; + /* True => filter out any incoming svn:mergeinfo property changes */ + svn_boolean_t ignore_mergeinfo_changes; + + svn_ra_session_t *ra_session; + + svn_wc_context_t *wc_ctx; + svn_client_ctx_t *ctx; + svn_wc_notify_func2_t notify_func; + void *notify_baton; +}; + +/* Everything we need to know about a directory that's open for edits. + */ +struct dir_baton_t +{ + apr_pool_t *pool; + + struct edit_baton_t *eb; + + const char *local_abspath; +}; + +/* Join PATH onto ANCHOR_ABSPATH. + * Throw an error if the result is outside ANCHOR_ABSPATH. + */ +static svn_error_t * +get_path(const char **local_abspath_p, + const char *anchor_abspath, + const char *path, + apr_pool_t *result_pool) +{ + svn_boolean_t under_root; + + SVN_ERR(svn_dirent_is_under_root(&under_root, local_abspath_p, + anchor_abspath, path, result_pool)); + if (! under_root) + { + return svn_error_createf( + SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, + _("Path '%s' is not in the working copy"), + svn_dirent_local_style(path, result_pool)); + } + return SVN_NO_ERROR; +} + +/* Create a directory on disk and add it to version control, + * with no properties. + */ +static svn_error_t * +mkdir(const char *abspath, + struct edit_baton_t *eb, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_io_make_dir_recursively(abspath, scratch_pool)); + SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath, + NULL /*properties*/, + TRUE /* skip checks */, + eb->notify_func, eb->notify_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* Prepare to open or add a directory: initialize a new dir baton. + * + * If PATH is "" and PB is null, it represents the root directory of + * the edit; otherwise PATH is not "" and PB is not null. + */ +static svn_error_t * +dir_open_or_add(struct dir_baton_t **child_dir_baton, + const char *path, + struct dir_baton_t *pb, + struct edit_baton_t *eb, + apr_pool_t *dir_pool) +{ + struct dir_baton_t *db = apr_pcalloc(dir_pool, sizeof(*db)); + + db->pool = dir_pool; + db->eb = eb; + + SVN_ERR(get_path(&db->local_abspath, + eb->anchor_abspath, path, dir_pool)); + + *child_dir_baton = db; + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +release_write_lock(struct edit_baton_t *eb, + apr_pool_t *scratch_pool) +{ + if (eb->lock_root_abspath) + { + SVN_ERR(svn_wc__release_write_lock( + eb->ctx->wc_ctx, eb->lock_root_abspath, scratch_pool)); + eb->lock_root_abspath = NULL; + } + return SVN_NO_ERROR; +} + +/* */ +static apr_status_t +pool_cleanup_handler(void *root_baton) +{ + struct dir_baton_t *db = root_baton; + struct edit_baton_t *eb = db->eb; + + svn_error_clear(release_write_lock(eb, db->pool)); + return APR_SUCCESS; +} + +/* svn_delta_editor_t function */ +static svn_error_t * +edit_open(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + void **root_baton) +{ + struct edit_baton_t *eb = edit_baton; + struct dir_baton_t *db; + + SVN_ERR(dir_open_or_add(&db, "", NULL, eb, result_pool)); + + /* Acquire a WC write lock */ + if (eb->manage_wc_write_lock) + { + apr_pool_cleanup_register(db->pool, db, + pool_cleanup_handler, + apr_pool_cleanup_null); + SVN_ERR(svn_wc__acquire_write_lock(&eb->lock_root_abspath, + eb->ctx->wc_ctx, + eb->anchor_abspath, + FALSE /*lock_anchor*/, + db->pool, db->pool)); + } + + if (eb->root_dir_add) + { + SVN_ERR(mkdir(db->local_abspath, eb, result_pool)); + } + + *root_baton = db; + return SVN_NO_ERROR; +} + +/* svn_delta_editor_t function */ +static svn_error_t * +edit_close_or_abort(void *edit_baton, + apr_pool_t *scratch_pool) +{ + SVN_ERR(release_write_lock(edit_baton, scratch_pool)); + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t revision, + void *parent_baton, + apr_pool_t *scratch_pool) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + const char *local_abspath; + + SVN_ERR(get_path(&local_abspath, + eb->anchor_abspath, path, scratch_pool)); + SVN_ERR(svn_wc_delete4(eb->wc_ctx, local_abspath, + FALSE /*keep_local*/, + TRUE /*delete_unversioned*/, + NULL, NULL, /*cancellation*/ + eb->notify_func, eb->notify_baton, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* An svn_delta_editor_t function. */ +static svn_error_t * +dir_open(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + void **child_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct dir_baton_t *db; + + SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool)); + + *child_baton = db; + return SVN_NO_ERROR; +} + +static svn_error_t * +dir_add(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *result_pool, + void **child_baton) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct dir_baton_t *db; + /* ### Our caller should be providing a scratch pool */ + apr_pool_t *scratch_pool = svn_pool_create(result_pool); + + SVN_ERR(dir_open_or_add(&db, path, pb, eb, result_pool)); + + if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision)) + { + SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/, + svn_node_dir, + copyfrom_path, + copyfrom_revision, + db->local_abspath, + db->eb->ra_session, + db->eb->ctx, + scratch_pool)); + } + else + { + SVN_ERR(mkdir(db->local_abspath, eb, result_pool)); + } + + *child_baton = db; + svn_pool_destroy(scratch_pool); + return SVN_NO_ERROR; +} + +static svn_error_t * +dir_change_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + struct dir_baton_t *db = dir_baton; + struct edit_baton_t *eb = db->eb; + + if (svn_property_kind2(name) != svn_prop_regular_kind + || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO))) + { + /* We can't handle DAV, ENTRY and merge specific props here */ + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, db->local_abspath, name, value, + svn_depth_empty, FALSE, NULL, + NULL, NULL, /* Cancellation */ + NULL, NULL, /* Notification */ + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +dir_close(void *dir_baton, + apr_pool_t *scratch_pool) +{ + return SVN_NO_ERROR; +} + +/* Everything we need to know about a file that's open for edits. + */ +struct file_baton_t +{ + apr_pool_t *pool; + + struct edit_baton_t *eb; + + const char *local_abspath; + + /* fields for the transfer of text changes */ + const char *writing_file; + unsigned char digest[APR_MD5_DIGESTSIZE]; /* MD5 digest of new fulltext */ + svn_stream_t *wc_file_read_stream, *tmp_file_write_stream; + const char *tmp_path; +}; + +/* Create a new file on disk and add it to version control. + * + * The file is empty and has no properties. + */ +static svn_error_t * +mkfile(const char *abspath, + struct edit_baton_t *eb, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_io_file_create_empty(abspath, scratch_pool)); + SVN_ERR(svn_wc_add_from_disk3(eb->wc_ctx, abspath, + NULL /*properties*/, + TRUE /* skip checks */, + eb->notify_func, eb->notify_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +file_open_or_add(const char *path, + void *parent_baton, + struct file_baton_t **file_baton, + apr_pool_t *file_pool) +{ + struct dir_baton_t *pb = parent_baton; + struct edit_baton_t *eb = pb->eb; + struct file_baton_t *fb = apr_pcalloc(file_pool, sizeof(*fb)); + + fb->pool = file_pool; + fb->eb = eb; + SVN_ERR(get_path(&fb->local_abspath, + eb->anchor_abspath, path, fb->pool)); + + *file_baton = fb; + return SVN_NO_ERROR; +} + +static svn_error_t * +file_open(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + void **file_baton) +{ + struct file_baton_t *fb; + + SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool)); + + *file_baton = fb; + return SVN_NO_ERROR; +} + +static svn_error_t * +file_add(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *result_pool, + void **file_baton) +{ + struct file_baton_t *fb; + + SVN_ERR(file_open_or_add(path, parent_baton, &fb, result_pool)); + + if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_revision)) + { + SVN_ERR(svn_client__repos_to_wc_copy_internal(NULL /*timestamp_sleep*/, + svn_node_file, + copyfrom_path, + copyfrom_revision, + fb->local_abspath, + fb->eb->ra_session, + fb->eb->ctx, fb->pool)); + } + else + { + SVN_ERR(mkfile(fb->local_abspath, fb->eb, result_pool)); + } + + *file_baton = fb; + return SVN_NO_ERROR; +} + +static svn_error_t * +file_change_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *scratch_pool) +{ + struct file_baton_t *fb = file_baton; + struct edit_baton_t *eb = fb->eb; + + if (svn_property_kind2(name) != svn_prop_regular_kind + || (eb->ignore_mergeinfo_changes && ! strcmp(name, SVN_PROP_MERGEINFO))) + { + /* We can't handle DAV, ENTRY and merge specific props here */ + return SVN_NO_ERROR; + } + + SVN_ERR(svn_wc_prop_set4(eb->wc_ctx, fb->local_abspath, name, value, + svn_depth_empty, FALSE, NULL, + NULL, NULL, /* Cancellation */ + NULL, NULL, /* Notification */ + scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +file_textdelta(void *file_baton, + const char *base_checksum, + apr_pool_t *result_pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton_t *fb = file_baton; + const char *target_dir = svn_dirent_dirname(fb->local_abspath, fb->pool); + svn_error_t *err; + + SVN_ERR_ASSERT(! fb->writing_file); + + err = svn_stream_open_readonly(&fb->wc_file_read_stream, fb->local_abspath, + fb->pool, fb->pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + fb->wc_file_read_stream = svn_stream_empty(fb->pool); + } + else + SVN_ERR(err); + + SVN_ERR(svn_stream_open_unique(&fb->tmp_file_write_stream, &fb->writing_file, + target_dir, svn_io_file_del_none, + fb->pool, fb->pool)); + + svn_txdelta_apply(fb->wc_file_read_stream, + fb->tmp_file_write_stream, + fb->digest, + fb->local_abspath, + fb->pool, + /* Provide the handler directly */ + handler, handler_baton); + + return SVN_NO_ERROR; +} + +static svn_error_t * +file_close(void *file_baton, + const char *text_checksum, + apr_pool_t *scratch_pool) +{ + struct file_baton_t *fb = file_baton; + + /* If we have text changes, write them to disk */ + if (fb->writing_file) + { + SVN_ERR(svn_stream_close(fb->wc_file_read_stream)); + SVN_ERR(svn_io_file_rename2(fb->writing_file, fb->local_abspath, + FALSE /*flush*/, scratch_pool)); + } + + if (text_checksum) + { + svn_checksum_t *expected_checksum; + svn_checksum_t *actual_checksum; + + SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5, + text_checksum, fb->pool)); + actual_checksum = svn_checksum__from_digest_md5(fb->digest, fb->pool); + + if (! svn_checksum_match(expected_checksum, actual_checksum)) + return svn_error_trace( + svn_checksum_mismatch_err(expected_checksum, + actual_checksum, + fb->pool, + _("Checksum mismatch for '%s'"), + svn_dirent_local_style( + fb->local_abspath, + fb->pool))); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__wc_editor_internal(const svn_delta_editor_t **editor_p, + void **edit_baton_p, + const char *dst_abspath, + svn_boolean_t root_dir_add, + svn_boolean_t ignore_mergeinfo_changes, + svn_boolean_t manage_wc_write_lock, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + svn_delta_editor_t *editor = svn_delta_default_editor(result_pool); + struct edit_baton_t *eb = apr_pcalloc(result_pool, sizeof(*eb)); + + eb->anchor_abspath = apr_pstrdup(result_pool, dst_abspath); + eb->manage_wc_write_lock = manage_wc_write_lock; + eb->lock_root_abspath = NULL; + eb->root_dir_add = root_dir_add; + eb->ignore_mergeinfo_changes = ignore_mergeinfo_changes; + + eb->ra_session = ra_session; + eb->wc_ctx = ctx->wc_ctx; + eb->ctx = ctx; + eb->notify_func = notify_func; + eb->notify_baton = notify_baton; + + editor->open_root = edit_open; + editor->close_edit = edit_close_or_abort; + editor->abort_edit = edit_close_or_abort; + + editor->delete_entry = delete_entry; + + editor->open_directory = dir_open; + editor->add_directory = dir_add; + editor->change_dir_prop = dir_change_prop; + editor->close_directory = dir_close; + + editor->open_file = file_open; + editor->add_file = file_add; + editor->change_file_prop = file_change_prop; + editor->apply_textdelta = file_textdelta; + editor->close_file = file_close; + + *editor_p = editor; + *edit_baton_p = eb; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__wc_editor(const svn_delta_editor_t **editor_p, + void **edit_baton_p, + const char *dst_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_ra_session_t *ra_session, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + SVN_ERR(svn_client__wc_editor_internal(editor_p, edit_baton_p, + dst_abspath, + FALSE /*root_dir_add*/, + FALSE /*ignore_mergeinfo_changes*/, + TRUE /*manage_wc_write_lock*/, + notify_func, notify_baton, + ra_session, + ctx, result_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__wc_copy_mods(const char *src_wc_abspath, + const char *dst_wc_abspath, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client__pathrev_t *base; + const char *dst_wc_url; + svn_ra_session_t *ra_session; + const svn_delta_editor_t *editor; + void *edit_baton; + apr_array_header_t *src_targets = apr_array_make(scratch_pool, 1, + sizeof(char *)); + + /* We'll need an RA session to obtain the base of any copies */ + SVN_ERR(svn_client__wc_node_get_base(&base, + src_wc_abspath, ctx->wc_ctx, + scratch_pool, scratch_pool)); + dst_wc_url = base->url; + SVN_ERR(svn_client_open_ra_session2(&ra_session, + dst_wc_url, dst_wc_abspath, + ctx, scratch_pool, scratch_pool)); + SVN_ERR(svn_client__wc_editor(&editor, &edit_baton, + dst_wc_abspath, + NULL, NULL, /*notification*/ + ra_session, ctx, scratch_pool)); + + APR_ARRAY_PUSH(src_targets, const char *) = src_wc_abspath; + SVN_ERR(svn_client__wc_replay(src_wc_abspath, + src_targets, svn_depth_infinity, NULL, + editor, edit_baton, + notify_func, notify_baton, + ctx, scratch_pool)); + + return SVN_NO_ERROR; +} |