diff options
Diffstat (limited to 'subversion/libsvn_client')
33 files changed, 9197 insertions, 3004 deletions
diff --git a/subversion/libsvn_client/add.c b/subversion/libsvn_client/add.c index ce7891afb27c0..3bb548ae8b45b 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 b9363e20dd3e7..f78b3041f5b27 100644 --- a/subversion/libsvn_client/blame.c +++ b/subversion/libsvn_client/blame.c @@ -456,7 +456,7 @@ file_rev_handler(void *baton, const char *path, svn_revnum_t revnum, SVN_ERR_CLIENT_IS_BINARY_FILE, NULL, _("Cannot calculate blame information for binary file '%s'"), (svn_path_is_url(frb->target) - ? frb->target + ? frb->target : svn_dirent_local_style(frb->target, pool))); } } @@ -553,7 +553,7 @@ file_rev_handler(void *baton, const char *path, svn_revnum_t revnum, || frb->include_merged_revisions); /* The file existed before start_rev; generate no blame info for - lines from this revision (or before). + lines from this revision (or before). This revision specifies the state as it was at the start revision */ @@ -656,14 +656,16 @@ normalize_blames(struct blame_chain *chain, } svn_error_t * -svn_client_blame5(const char *target, +svn_client_blame6(svn_revnum_t *start_revnum_p, + svn_revnum_t *end_revnum_p, + const char *target, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *start, const svn_opt_revision_t *end, const svn_diff_file_options_t *diff_options, svn_boolean_t ignore_mime_type, svn_boolean_t include_merged_revisions, - svn_client_blame_receiver3_t receiver, + svn_client_blame_receiver4_t receiver, void *receiver_baton, svn_client_ctx_t *ctx, apr_pool_t *pool) @@ -696,10 +698,13 @@ svn_client_blame5(const char *target, SVN_ERR(svn_client__get_revision_number(&start_revnum, NULL, ctx->wc_ctx, target_abspath_or_url, ra_session, start, pool)); - + if (start_revnum_p) + *start_revnum_p = start_revnum; SVN_ERR(svn_client__get_revision_number(&end_revnum, NULL, ctx->wc_ctx, target_abspath_or_url, ra_session, end, pool)); + if (end_revnum_p) + *end_revnum_p = end_revnum; { svn_client__pathrev_t *loc; @@ -734,7 +739,7 @@ svn_client_blame5(const char *target, mime_type = svn_prop_get_value(props, SVN_PROP_MIME_TYPE); } - else + else { const svn_string_t *value; @@ -941,18 +946,21 @@ svn_client_blame5(const char *target, SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); if (!eof || sb->len) { + svn_string_t line; + line.data = sb->data; + line.len = sb->len; if (walk->rev) - SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, + SVN_ERR(receiver(receiver_baton, line_no, walk->rev->revision, walk->rev->rev_props, merged_rev, merged_rev_props, merged_path, - sb->data, FALSE, iterpool)); + &line, FALSE, iterpool)); else - SVN_ERR(receiver(receiver_baton, start_revnum, end_revnum, + SVN_ERR(receiver(receiver_baton, line_no, SVN_INVALID_REVNUM, NULL, SVN_INVALID_REVNUM, NULL, NULL, - sb->data, TRUE, iterpool)); + &line, TRUE, iterpool)); } if (eof) break; } diff --git a/subversion/libsvn_client/client.h b/subversion/libsvn_client/client.h index c0a794712f3f3..97cd1e297c023 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 4a945c887aa94..df2f5f7c52244 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 1f3d87783436a..2be3ecd5ce399 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 0b342e0e0a1e9..9a58703c4238f 100644 --- a/subversion/libsvn_client/conflicts.c +++ b/subversion/libsvn_client/conflicts.c @@ -255,7 +255,7 @@ struct repos_move_info { /* The revision in which this move was committed. */ svn_revnum_t rev; - /* The author who commited the revision in which this move was committed. */ + /* The author who committed the revision in which this move was committed. */ const char *rev_author; /* The repository relpath the node was moved from in this revision. */ @@ -383,7 +383,7 @@ add_new_move(struct repos_move_info **new_move, const char *author, apr_hash_t *moved_paths, svn_ra_session_t *ra_session, - const char *repos_root_url, + const char *repos_root_url, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { @@ -661,7 +661,7 @@ match_copies_to_deletion(const char *deleted_repos_relpath, TRUE, iterpool)); if (!related) continue; - + /* Remember details of this move. */ SVN_ERR(add_new_move(&move, deleted_repos_relpath, copy->copyto_path, copy->copyfrom_rev, @@ -669,7 +669,7 @@ match_copies_to_deletion(const char *deleted_repos_relpath, moved_paths, ra_session, repos_root_url, result_pool, iterpool)); push_move(move, moves_table, result_pool); - } + } } else { @@ -782,7 +782,7 @@ map_deleted_path_to_move(const char *deleted_relpath, { const char *relpath; struct repos_move_info *move; - + move = APR_ARRAY_IDX(moves, i, struct repos_move_info *); if (strcmp(move->moved_from_repos_relpath, deleted_relpath) == 0) return move; @@ -806,18 +806,20 @@ map_deleted_path_to_move(const char *deleted_relpath, if (closest_move) { const char *relpath; - const char *moved_along_path; - struct repos_move_info *move; - + /* See if we can find an even closer move for this moved-along path. */ relpath = svn_relpath_skip_ancestor(closest_move->moved_to_repos_relpath, deleted_relpath); - moved_along_path = - svn_relpath_join(closest_move->moved_from_repos_relpath, relpath, - scratch_pool); - move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool); - if (move) - return move; + if (relpath && relpath[0] != '\0') + { + struct repos_move_info *move; + const char *moved_along_path = + svn_relpath_join(closest_move->moved_from_repos_relpath, relpath, + scratch_pool); + move = map_deleted_path_to_move(moved_along_path, moves, scratch_pool); + if (move) + return move; + } } return closest_move; @@ -965,7 +967,7 @@ cache_copied_item(apr_hash_t *copies, const char *changed_path, * This function answers the same question as svn_ra_get_deleted_rev() but * works in cases where we do not already know a revision in which the deleted * node once used to exist. - * + * * If the node was moved, rather than deleted, return move information * in BATON->MOVE. */ @@ -1096,7 +1098,7 @@ find_deleted_rev(void *baton, b->deleted_rev_author = apr_pstrdup(b->result_pool, author->data); else b->deleted_rev_author = _("unknown author"); - + b->replacing_node_kind = replacing_node_kind; /* We're done. Abort the log operation. */ @@ -1171,7 +1173,7 @@ describe_local_file_node_change(const char **description, const char *moved_to_abspath; svn_error_t *err; - err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, + err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, ctx->wc_ctx, conflict->local_abspath, scratch_pool, @@ -1255,7 +1257,7 @@ describe_local_file_node_change(const char **description, { const char *moved_from_abspath; - SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, + SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, ctx->wc_ctx, conflict->local_abspath, scratch_pool, @@ -1396,7 +1398,7 @@ describe_local_dir_node_change(const char **description, const char *moved_to_abspath; svn_error_t *err; - err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, + err = svn_wc__node_was_moved_away(&moved_to_abspath, NULL, ctx->wc_ctx, conflict->local_abspath, scratch_pool, @@ -1481,7 +1483,7 @@ describe_local_dir_node_change(const char **description, { const char *moved_from_abspath; - SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, + SVN_ERR(svn_wc__node_was_moved_here(&moved_from_abspath, NULL, ctx->wc_ctx, conflict->local_abspath, scratch_pool, @@ -1579,7 +1581,7 @@ struct find_moves_baton * rB: mv b->c * rC: mv c->d * we map each revision number to all the moves which happened in the - * revision, which looks as follows: + * revision, which looks as follows: * rA : [(x->z), (a->b)] * rB : [(b->c)] * rC : [(c->d)] @@ -2100,33 +2102,6 @@ trace_moved_node_backwards(apr_hash_t *moves_table, return SVN_NO_ERROR; } -static svn_error_t * -reparent_session_and_fetch_node_kind(svn_node_kind_t *node_kind, - svn_ra_session_t *ra_session, - const char *url, - svn_revnum_t peg_rev, - apr_pool_t *scratch_pool) -{ - svn_error_t *err; - - err = svn_ra_reparent(ra_session, url, scratch_pool); - if (err) - { - if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL) - { - svn_error_clear(err); - *node_kind = svn_node_unknown; - return SVN_NO_ERROR; - } - - return svn_error_trace(err); - } - - SVN_ERR(svn_ra_check_path(ra_session, "", peg_rev, node_kind, scratch_pool)); - - return SVN_NO_ERROR; -} - /* Scan MOVES_TABLE for moves which affect a particular deleted node, and * build a set of new move information for this node. * Return heads of all possible move chains in *MOVES. @@ -2173,22 +2148,29 @@ find_operative_moves(apr_array_header_t **moves, svn_pool_clear(iterpool); move = APR_ARRAY_IDX(moves_in_deleted_rev, i, struct repos_move_info *); - relpath = svn_relpath_skip_ancestor(move->moved_from_repos_relpath, + if (strcmp(move->moved_from_repos_relpath, deleted_repos_relpath) == 0) + { + APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move; + continue; + } + + /* Test for an operative nested move. */ + relpath = svn_relpath_skip_ancestor(move->moved_to_repos_relpath, deleted_repos_relpath); if (relpath && relpath[0] != '\0') { - svn_node_kind_t node_kind; - - url = svn_path_url_add_component2(repos_root_url, - deleted_repos_relpath, - iterpool); - SVN_ERR(reparent_session_and_fetch_node_kind(&node_kind, - ra_session, url, - rev_below(deleted_rev), - iterpool)); - move = new_path_adjusted_move(move, relpath, node_kind, result_pool); + struct repos_move_info *nested_move; + const char *actual_deleted_repos_relpath; + + actual_deleted_repos_relpath = + svn_relpath_join(move->moved_from_repos_relpath, relpath, + iterpool); + nested_move = map_deleted_path_to_move(actual_deleted_repos_relpath, + moves_in_deleted_rev, + iterpool); + if (nested_move) + APR_ARRAY_PUSH(*moves, struct repos_move_info *) = nested_move; } - APR_ARRAY_PUSH(*moves, struct repos_move_info *) = move; } if (url != NULL) @@ -2368,6 +2350,8 @@ struct conflict_tree_local_missing_details /* If not SVN_INVALID_REVNUM, the node was deleted in DELETED_REV. */ svn_revnum_t deleted_rev; + /* ### Add 'added_rev', like in conflict_tree_incoming_delete_details? */ + /* Author who committed DELETED_REV. */ const char *deleted_rev_author; @@ -2375,21 +2359,49 @@ struct conflict_tree_local_missing_details const char *deleted_repos_relpath; /* Move information about the conflict victim. If not NULL, this is an - * array of repos_move_info elements. Each element is the head of a - * move chain which starts in DELETED_REV. */ + * array of 'struct repos_move_info *' elements. Each element is the + * head of a move chain which starts in DELETED_REV. */ apr_array_header_t *moves; + /* If moves is not NULL, a map of repos_relpaths and working copy nodes. + * + * Each key is a "const char *" repository relpath corresponding to a + * possible repository-side move destination node in the revision which + * is the merge-right revision in case of a merge. + * + * Each value is an apr_array_header_t *. + * Each array consists of "const char *" absolute paths to working copy + * nodes which correspond to the repository node selected by the map key. + * Each such working copy node is a potential local move target which can + * be chosen to find a suitable merge target when resolving a tree conflict. + * + * This may be an empty hash map in case if there is no move target path + * in the working copy. */ + apr_hash_t *wc_move_targets; + + /* If not NULL, the preferred move target repository relpath. This is our key + * into the WC_MOVE_TARGETS map above (can be overridden by the user). */ + const char *move_target_repos_relpath; + + /* The current index into the list of working copy nodes corresponding to + * MOVE_TARGET_REPOS_REPLATH (can be overridden by the user). */ + int wc_move_target_idx; + /* Move information about siblings. Siblings are nodes which share * a youngest common ancestor with the conflict victim. E.g. in case * of a merge operation they are part of the merge source branch. - * If not NULL, this is an array of repos_move_info elements. + * If not NULL, this is an array of 'struct repos_move_info *' elements. * Each element is the head of a move chain, which starts at some * point in history after siblings and conflict victim forked off * their common ancestor. */ apr_array_header_t *sibling_moves; - /* If not NULL, this is the move target abspath. */ - const char *moved_to_abspath; + /* List of nodes in the WC which are suitable merge targets for changes + * merged from any moved sibling. Array elements are 'const char *' + * absolute paths of working copy nodes. This array contains multiple + * elements only if ambiguous matches were found in the WC. */ + apr_array_header_t *wc_siblings; + int preferred_sibling_idx; }; static svn_error_t * @@ -2608,6 +2620,168 @@ find_moves_in_natural_history(apr_array_header_t **moves, return SVN_NO_ERROR; } +static svn_error_t * +collect_sibling_move_candidates(apr_array_header_t *candidates, + const char *victim_abspath, + svn_node_kind_t victim_kind, + struct repos_move_info *move, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *basename; + apr_array_header_t *abspaths; + int i; + + basename = svn_relpath_basename(move->moved_from_repos_relpath, scratch_pool); + SVN_ERR(svn_wc__find_working_nodes_with_basename(&abspaths, victim_abspath, + basename, victim_kind, + ctx->wc_ctx, result_pool, + scratch_pool)); + apr_array_cat(candidates, abspaths); + + if (move->next) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < move->next->nelts; i++) + { + struct repos_move_info *next_move; + next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); + SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath, + victim_kind, next_move, ctx, + result_pool, iterpool)); + svn_pool_clear(iterpool); + } + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + +/* Follow each move chain starting a MOVE all the way to the end to find + * the possible working copy locations for VICTIM_ABSPATH which corresponds + * to VICTIM_REPOS_REPLATH@VICTIM_REVISION. + * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the + * repos_relpath which is the corresponding move destination in the repository. + * This function is recursive. */ +static svn_error_t * +follow_move_chains(apr_hash_t *wc_move_targets, + struct repos_move_info *move, + svn_client_ctx_t *ctx, + const char *victim_abspath, + svn_node_kind_t victim_node_kind, + const char *victim_repos_relpath, + svn_revnum_t victim_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *candidate_abspaths; + + /* Gather candidate nodes which represent this moved_to_repos_relpath. */ + SVN_ERR(svn_wc__guess_incoming_move_target_nodes( + &candidate_abspaths, ctx->wc_ctx, + victim_abspath, victim_node_kind, + move->moved_to_repos_relpath, + scratch_pool, scratch_pool)); + + if (candidate_abspaths->nelts > 0) + { + apr_array_header_t *moved_to_abspaths; + int i; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + moved_to_abspaths = apr_array_make(result_pool, 1, + sizeof (const char *)); + + for (i = 0; i < candidate_abspaths->nelts; i++) + { + const char *candidate_abspath; + const char *repos_root_url; + const char *repos_uuid; + const char *candidate_repos_relpath; + svn_revnum_t candidate_revision; + + svn_pool_clear(iterpool); + + candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i, + const char *); + SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, + &candidate_repos_relpath, + &repos_root_url, + &repos_uuid, + NULL, NULL, + ctx->wc_ctx, + candidate_abspath, + FALSE, + iterpool, iterpool)); + + if (candidate_revision == SVN_INVALID_REVNUM) + continue; + + /* If the conflict victim and the move target candidate + * are not from the same revision we must ensure that + * they are related. */ + if (candidate_revision != victim_revision) + { + svn_client__pathrev_t *yca_loc; + svn_error_t *err; + + err = find_yca(&yca_loc, victim_repos_relpath, + victim_revision, + candidate_repos_relpath, + candidate_revision, + repos_root_url, repos_uuid, + NULL, ctx, iterpool, iterpool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + yca_loc = NULL; + } + else + return svn_error_trace(err); + } + + if (yca_loc == NULL) + continue; + } + + APR_ARRAY_PUSH(moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, candidate_abspath); + } + svn_pool_destroy(iterpool); + + svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath, + moved_to_abspaths); + } + + if (move->next) + { + int i; + apr_pool_t *iterpool; + + /* Recurse into each of the possible move chains. */ + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < move->next->nelts; i++) + { + struct repos_move_info *next_move; + + svn_pool_clear(iterpool); + + next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); + SVN_ERR(follow_move_chains(wc_move_targets, next_move, + ctx, victim_abspath, victim_node_kind, + victim_repos_relpath, victim_revision, + result_pool, iterpool)); + + } + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + /* Implements tree_conflict_get_details_func_t. */ static svn_error_t * conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, @@ -2621,12 +2795,15 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, svn_revnum_t old_rev; svn_revnum_t new_rev; svn_revnum_t deleted_rev; + svn_node_kind_t old_kind; + svn_node_kind_t new_kind; const char *deleted_rev_author; svn_node_kind_t replacing_node_kind; const char *deleted_basename; struct conflict_tree_local_missing_details *details; apr_array_header_t *moves = NULL; apr_array_header_t *sibling_moves = NULL; + apr_array_header_t *wc_siblings = NULL; const char *related_repos_relpath; svn_revnum_t related_peg_rev; const char *repos_root_url; @@ -2637,10 +2814,10 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, svn_revnum_t end_rev; SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( - &old_repos_relpath, &old_rev, NULL, conflict, + &old_repos_relpath, &old_rev, &old_kind, conflict, scratch_pool, scratch_pool)); SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( - &new_repos_relpath, &new_rev, NULL, conflict, + &new_repos_relpath, &new_rev, &new_kind, conflict, scratch_pool, scratch_pool)); /* Scan the conflict victim's parent's log to find a revision which @@ -2656,9 +2833,14 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, scratch_pool, scratch_pool)); + /* If the parent is not part of the repository-side tree checked out + * into this working copy, then bail. We do not support this case yet. */ + if (parent_peg_rev == SVN_INVALID_REVNUM) + return SVN_NO_ERROR; + /* Pick the younger incoming node as our 'related node' which helps * pin-pointing the deleted conflict victim in history. */ - related_repos_relpath = + related_repos_relpath = (old_rev < new_rev ? new_repos_relpath : old_repos_relpath); related_peg_rev = (old_rev < new_rev ? new_rev : old_rev); @@ -2711,6 +2893,9 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, { const char *victim_abspath; svn_node_kind_t related_node_kind; + apr_array_header_t *candidates; + int i; + apr_pool_t *iterpool; /* ### The following describes all moves in terms of forward-merges, * should do we something else for reverse-merges? */ @@ -2744,7 +2929,81 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, if (sibling_moves == NULL) return SVN_NO_ERROR; - /* ## TODO: Find the missing node in the WC. */ + /* Find the missing node in the WC. In theory, this requires tracing + * back history of every node in the WC to check for a YCA with the + * conflict victim. This operation would obviously be quite expensive. + * + * However, assuming that the victim was not moved in the merge target, + * we can take a short-cut: The basename of the node cannot have changed, + * so we can limit history tracing to nodes with a matching basename. + * + * This approach solves the conflict case where an edit to a file which + * was moved on one branch is cherry-picked to another branch where the + * corresponding file has not been moved (yet). It does not solve move + * vs. move conflicts, but such conflicts are not yet supported by the + * resolver anyway and are hard to solve without server-side support. */ + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < sibling_moves->nelts; i++) + { + struct repos_move_info *move; + int j; + + svn_pool_clear(iterpool); + + move = APR_ARRAY_IDX(sibling_moves, i, struct repos_move_info *); + candidates = apr_array_make(iterpool, 1, sizeof(const char *)); + SVN_ERR(collect_sibling_move_candidates(candidates, victim_abspath, + old_rev < new_rev + ? new_kind : old_kind, + move, ctx, iterpool, + iterpool)); + + /* Determine whether a candidate node shares a YCA with the victim. */ + for (j = 0; j < candidates->nelts; j++) + { + const char *candidate_abspath; + const char *candidate_repos_relpath; + svn_revnum_t candidate_revision; + svn_error_t *err; + + candidate_abspath = APR_ARRAY_IDX(candidates, j, const char *); + SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, + &candidate_repos_relpath, + NULL, NULL, NULL, NULL, + ctx->wc_ctx, + candidate_abspath, + FALSE, + iterpool, iterpool)); + err = find_yca(&yca_loc, + old_rev < new_rev + ? new_repos_relpath : old_repos_relpath, + old_rev < new_rev ? new_rev : old_rev, + candidate_repos_relpath, + candidate_revision, + repos_root_url, repos_uuid, + NULL, ctx, iterpool, iterpool); + if (err) + { + if (err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + yca_loc = NULL; + } + else + return svn_error_trace(err); + } + + if (yca_loc) + { + if (wc_siblings == NULL) + wc_siblings = apr_array_make(conflict->pool, 1, + sizeof(const char *)); + APR_ARRAY_PUSH(wc_siblings, const char *) = + apr_pstrdup(conflict->pool, candidate_abspath); + } + } + } + svn_pool_destroy(iterpool); } details = apr_pcalloc(conflict->pool, sizeof(*details)); @@ -2753,10 +3012,85 @@ conflict_tree_get_details_local_missing(svn_client_conflict_t *conflict, if (deleted_rev != SVN_INVALID_REVNUM) details->deleted_repos_relpath = svn_relpath_join(parent_repos_relpath, deleted_basename, - conflict->pool); + conflict->pool); details->moves = moves; + if (details->moves != NULL) + { + apr_pool_t *iterpool; + int i; + + details->wc_move_targets = apr_hash_make(conflict->pool); + iterpool = svn_pool_create(scratch_pool); + for (i = 0; i < details->moves->nelts; i++) + { + struct repos_move_info *move; + + svn_pool_clear(iterpool); + move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *); + SVN_ERR(follow_move_chains(details->wc_move_targets, move, ctx, + conflict->local_abspath, + new_kind, + new_repos_relpath, + new_rev, + scratch_pool, iterpool)); + } + svn_pool_destroy(iterpool); + + if (apr_hash_count(details->wc_move_targets) > 0) + { + apr_array_header_t *move_target_repos_relpaths; + const svn_sort__item_t *item; + + /* Initialize to the first possible move target. Hopefully, + * in most cases there will only be one candidate anyway. */ + move_target_repos_relpaths = svn_sort__hash( + details->wc_move_targets, + svn_sort_compare_items_as_paths, + scratch_pool); + item = &APR_ARRAY_IDX(move_target_repos_relpaths, + 0, svn_sort__item_t); + details->move_target_repos_relpath = item->key; + details->wc_move_target_idx = 0; + } + else + { + details->move_target_repos_relpath = NULL; + details->wc_move_target_idx = 0; + } + } + details->sibling_moves = sibling_moves; - + details->wc_siblings = wc_siblings; + if (details->wc_move_targets && apr_hash_count(details->wc_move_targets) == 1) + { + apr_array_header_t *wc_abspaths; + + wc_abspaths = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + if (wc_abspaths->nelts == 1) + { + svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind; + + if (kind == svn_node_file) + conflict->recommended_option_id = + svn_client_conflict_option_local_move_file_text_merge; + else if (kind == svn_node_dir) + conflict->recommended_option_id = + svn_client_conflict_option_local_move_dir_merge; + } + } + else if (details->wc_siblings && details->wc_siblings->nelts == 1) + { + svn_node_kind_t kind = old_rev < new_rev ? new_kind : old_kind; + + if (kind == svn_node_file) + conflict->recommended_option_id = + svn_client_conflict_option_sibling_move_file_text_merge; + else if (kind == svn_node_dir) + conflict->recommended_option_id = + svn_client_conflict_option_sibling_move_dir_merge; + } + conflict->tree_conflict_local_details = details; return SVN_NO_ERROR; @@ -2910,7 +3244,7 @@ conflict_tree_get_description_local_missing(const char **description, if (details->moves || details->sibling_moves) { struct repos_move_info *move; - + *description = _("No such file or directory was found in the " "merge target working copy.\n"); @@ -3590,7 +3924,7 @@ describe_incoming_deletion_upon_update( struct repos_move_info *move; move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); - description = + description = apr_psprintf(result_pool, _("Item updated from r%ld to r%ld was moved " "to '^/%s' by %s in r%ld."), old_rev, new_rev, @@ -3728,7 +4062,7 @@ describe_incoming_deletion_upon_switch( result_pool, scratch_pool); } - return description; + return description; } else if (victim_node_kind == svn_node_file || victim_node_kind == svn_node_symlink) @@ -3881,7 +4215,7 @@ describe_incoming_deletion_upon_switch( { struct repos_move_info *move; const char *description; - + move = APR_ARRAY_IDX(details->moves, 0, struct repos_move_info *); description = apr_psprintf(result_pool, @@ -4512,7 +4846,7 @@ conflict_tree_get_description_incoming_delete( action = describe_incoming_reverse_addition_upon_switch( details, victim_node_kind, old_repos_relpath, old_rev, new_repos_relpath, new_rev, result_pool); - + } } else if (conflict_operation == svn_wc_operation_merge) @@ -4665,133 +4999,6 @@ get_incoming_delete_details_for_reverse_addition( return SVN_NO_ERROR; } -/* Follow each move chain starting a MOVE all the way to the end to find - * the possible working copy locations for VICTIM_ABSPATH which corresponds - * to VICTIM_REPOS_REPLATH@VICTIM_REVISION. - * Add each such location to the WC_MOVE_TARGETS hash table, keyed on the - * repos_relpath which is the corresponding move destination in the repository. - * This function is recursive. */ -static svn_error_t * -follow_move_chains(apr_hash_t *wc_move_targets, - struct repos_move_info *move, - svn_client_ctx_t *ctx, - const char *victim_abspath, - svn_node_kind_t victim_node_kind, - const char *victim_repos_relpath, - svn_revnum_t victim_revision, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - /* If this is the end of a move chain, look for matching paths in - * the working copy and add them to our collection if found. */ - if (move->next == NULL) - { - apr_array_header_t *candidate_abspaths; - - /* Gather candidate nodes which represent this moved_to_repos_relpath. */ - SVN_ERR(svn_wc__guess_incoming_move_target_nodes( - &candidate_abspaths, ctx->wc_ctx, - victim_abspath, victim_node_kind, - move->moved_to_repos_relpath, - scratch_pool, scratch_pool)); - if (candidate_abspaths->nelts > 0) - { - apr_array_header_t *moved_to_abspaths; - int i; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - - moved_to_abspaths = apr_array_make(result_pool, 1, - sizeof (const char *)); - - for (i = 0; i < candidate_abspaths->nelts; i++) - { - const char *candidate_abspath; - const char *repos_root_url; - const char *repos_uuid; - const char *candidate_repos_relpath; - svn_revnum_t candidate_revision; - - svn_pool_clear(iterpool); - - candidate_abspath = APR_ARRAY_IDX(candidate_abspaths, i, - const char *); - SVN_ERR(svn_wc__node_get_origin(NULL, &candidate_revision, - &candidate_repos_relpath, - &repos_root_url, - &repos_uuid, - NULL, NULL, - ctx->wc_ctx, - candidate_abspath, - FALSE, - iterpool, iterpool)); - - if (candidate_revision == SVN_INVALID_REVNUM) - continue; - - /* If the conflict victim and the move target candidate - * are not from the same revision we must ensure that - * they are related. */ - if (candidate_revision != victim_revision) - { - svn_client__pathrev_t *yca_loc; - svn_error_t *err; - - err = find_yca(&yca_loc, victim_repos_relpath, - victim_revision, - candidate_repos_relpath, - candidate_revision, - repos_root_url, repos_uuid, - NULL, ctx, iterpool, iterpool); - if (err) - { - if (err->apr_err == SVN_ERR_FS_NOT_FOUND) - { - svn_error_clear(err); - yca_loc = NULL; - } - else - return svn_error_trace(err); - } - - if (yca_loc == NULL) - continue; - } - - APR_ARRAY_PUSH(moved_to_abspaths, const char *) = - apr_pstrdup(result_pool, candidate_abspath); - } - svn_pool_destroy(iterpool); - - svn_hash_sets(wc_move_targets, move->moved_to_repos_relpath, - moved_to_abspaths); - } - } - else - { - int i; - apr_pool_t *iterpool; - - /* Recurse into each of the possible move chains. */ - iterpool = svn_pool_create(scratch_pool); - for (i = 0; i < move->next->nelts; i++) - { - struct repos_move_info *next_move; - - svn_pool_clear(iterpool); - - next_move = APR_ARRAY_IDX(move->next, i, struct repos_move_info *); - SVN_ERR(follow_move_chains(wc_move_targets, next_move, - ctx, victim_abspath, victim_node_kind, - victim_repos_relpath, victim_revision, - result_pool, iterpool)); - - } - svn_pool_destroy(iterpool); - } - - return SVN_NO_ERROR; -} - static svn_error_t * init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, svn_client_conflict_t *conflict, @@ -4803,11 +5010,9 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, svn_node_kind_t victim_node_kind; const char *incoming_new_repos_relpath; svn_revnum_t incoming_new_pegrev; - svn_wc_operation_t operation; victim_abspath = svn_client_conflict_get_local_abspath(conflict); victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); - operation = svn_client_conflict_get_operation(conflict); /* ### Should we get the old location in case of reverse-merges? */ SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( &incoming_new_repos_relpath, &incoming_new_pegrev, @@ -4833,11 +5038,8 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, get_moved_to_repos_relpath(details, scratch_pool); details->wc_move_target_idx = 0; - /* If only one move target exists after an update or switch, - * recommend a resolution option which follows the incoming move. */ - if (apr_hash_count(details->wc_move_targets) == 1 && - (operation == svn_wc_operation_update || - operation == svn_wc_operation_switch)) + /* If only one move target exists recommend a resolution option. */ + if (apr_hash_count(details->wc_move_targets) == 1) { apr_array_header_t *wc_abspaths; @@ -4850,7 +5052,10 @@ init_wc_move_targets(struct conflict_tree_incoming_delete_details *details, /* Only one of these will be present for any given conflict. */ svn_client_conflict_option_incoming_move_file_text_merge, svn_client_conflict_option_incoming_move_dir_merge, - svn_client_conflict_option_local_move_file_text_merge + svn_client_conflict_option_local_move_file_text_merge, + svn_client_conflict_option_local_move_dir_merge, + svn_client_conflict_option_sibling_move_file_text_merge, + svn_client_conflict_option_sibling_move_dir_merge, }; apr_array_header_t *options; @@ -4907,6 +5112,7 @@ conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict, const char *parent_repos_relpath; svn_revnum_t parent_peg_rev; svn_revnum_t deleted_rev; + svn_revnum_t end_rev; const char *deleted_rev_author; svn_node_kind_t replacing_node_kind; apr_array_header_t *moves; @@ -4939,12 +5145,15 @@ conflict_tree_get_details_incoming_delete(svn_client_conflict_t *conflict, related_peg_rev = SVN_INVALID_REVNUM; } + end_rev = (new_kind == svn_node_none ? 0 : old_rev); + if (end_rev >= parent_peg_rev) + end_rev = (parent_peg_rev > 0 ? parent_peg_rev - 1 : 0); + SVN_ERR(find_revision_for_suspected_deletion( &deleted_rev, &deleted_rev_author, &replacing_node_kind, &moves, conflict, svn_dirent_basename(conflict->local_abspath, scratch_pool), - parent_repos_relpath, parent_peg_rev, - new_kind == svn_node_none ? 0 : old_rev, + parent_repos_relpath, parent_peg_rev, end_rev, related_repos_relpath, related_peg_rev, ctx, conflict->pool, scratch_pool)); if (deleted_rev == SVN_INVALID_REVNUM) @@ -5077,7 +5286,7 @@ conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict, const char *repos_root_url; svn_revnum_t old_rev; svn_revnum_t new_rev; - struct conflict_tree_incoming_add_details *details; + struct conflict_tree_incoming_add_details *details = NULL; svn_wc_operation_t operation; SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( @@ -5170,7 +5379,8 @@ conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict, } } } - else if (operation == svn_wc_operation_merge) + else if (operation == svn_wc_operation_merge && + strcmp(old_repos_relpath, new_repos_relpath) == 0) { if (old_rev < new_rev) { @@ -5221,7 +5431,7 @@ conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict, details->deleted_rev = SVN_INVALID_REVNUM; details->deleted_rev_author = NULL; } - else + else if (old_rev > new_rev) { /* The merge operation was a reverse-merge. * This addition is in fact a deletion, applied in reverse, @@ -5261,10 +5471,6 @@ conflict_tree_get_details_incoming_add(svn_client_conflict_t *conflict, details->moves = moves; } } - else - { - details = NULL; - } conflict->tree_conflict_incoming_details = details; @@ -5727,7 +5933,10 @@ find_modified_rev(void *baton, if (log_item->copyfrom_path) b->repos_relpath = apr_pstrdup(b->scratch_pool, - log_item->copyfrom_path); + /* ### remove leading slash */ + svn_relpath_canonicalize( + log_item->copyfrom_path, + iterpool)); } else if (b->node_kind == svn_node_dir && svn_relpath_skip_ancestor(b->repos_relpath, path) != NULL) @@ -5931,6 +6140,9 @@ describe_incoming_edit_list_modified_revs(apr_array_header_t *edits, const char *s = ""; int i; + if (edits->nelts == 0) + return _(" (no revisions found)"); + if (edits->nelts <= max_revs_to_display) num_revs_to_skip = 0; else @@ -5967,15 +6179,18 @@ describe_incoming_edit_list_modified_revs(apr_array_header_t *edits, { if (i == edits->nelts - (max_revs_to_display / 2)) s = apr_psprintf(result_pool, - _("%s\n [%d revisions omitted for " - "brevity],\n"), + Q_("%s\n [%d revision omitted for " + "brevity],\n", + "%s\n [%d revisions omitted for " + "brevity],\n", + num_revs_to_skip), s, num_revs_to_skip); s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s, details->rev, details->author, i < edits->nelts - 1 ? "," : ""); } - } + } else s = apr_psprintf(result_pool, _("%s r%ld by %s%s"), s, details->rev, details->author, @@ -6124,7 +6339,7 @@ conflict_tree_get_description_incoming_edit( "during reverse-merge of\n" "'^/%s:%ld-%ld'"), new_repos_relpath, new_rev + 1, old_rev); - + else action = apr_psprintf(scratch_pool, _("Changes from the following revisions " @@ -6159,7 +6374,7 @@ svn_client_conflict_tree_get_description( SVN_ERR(conflict->tree_conflict_get_local_description_func( local_change_description, conflict, ctx, result_pool, scratch_pool)); - + return SVN_NO_ERROR; } @@ -6683,8 +6898,10 @@ resolve_merge_incoming_added_file_text_update( apr_hash_t *working_props; apr_array_header_t *propdiffs; svn_error_t *err; + svn_wc_conflict_reason_t local_change; local_abspath = svn_client_conflict_get_local_abspath(conflict); + local_change = svn_client_conflict_get_local_change(conflict); /* Set up tempory storage for the working version of file. */ SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath, @@ -6695,20 +6912,31 @@ resolve_merge_incoming_added_file_text_update( svn_io_file_del_none, scratch_pool, scratch_pool)); - /* Copy the detranslated working file to temporary storage. */ - SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx, - local_abspath, local_abspath, - SVN_WC_TRANSLATE_TO_NF, - scratch_pool, scratch_pool)); + if (local_change == svn_wc_conflict_reason_unversioned) + { + /* Copy the unversioned file to temporary storage. */ + SVN_ERR(svn_stream_open_readonly(&working_file_stream, local_abspath, + scratch_pool, scratch_pool)); + /* Unversioned files have no properties. */ + working_props = apr_hash_make(scratch_pool); + } + else + { + /* Copy the detranslated working file to temporary storage. */ + SVN_ERR(svn_wc__translated_stream(&working_file_stream, ctx->wc_ctx, + local_abspath, local_abspath, + SVN_WC_TRANSLATE_TO_NF, + scratch_pool, scratch_pool)); + /* Get a copy of the working file's properties. */ + SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + filter_props(working_props, scratch_pool); + } + SVN_ERR(svn_stream_copy3(working_file_stream, working_file_tmp_stream, ctx->cancel_func, ctx->cancel_baton, scratch_pool)); - /* Get a copy of the working file's properties. */ - SVN_ERR(svn_wc_prop_list2(&working_props, ctx->wc_ctx, local_abspath, - scratch_pool, scratch_pool)); - filter_props(working_props, scratch_pool); - /* Create an empty file as fake "merge-base" for the two added files. * The files are not ancestrally related so this is the best we can do. */ SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL, @@ -6727,8 +6955,9 @@ resolve_merge_incoming_added_file_text_update( /* Revert the path in order to restore the repository's line of * history, which is part of the BASE tree. This revert operation * is why are being careful about not losing the temporary copy. */ - err = svn_wc_revert5(ctx->wc_ctx, local_abspath, svn_depth_empty, + err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_empty, FALSE, NULL, TRUE, FALSE, + TRUE /*added_keep_local*/, NULL, NULL, /* no cancellation */ ctx->notify_func2, ctx->notify_baton2, scratch_pool); @@ -6760,7 +6989,7 @@ unlock_wc: scratch_pool)); svn_io_sleep_for_timestamps(local_abspath, scratch_pool); SVN_ERR(err); - + if (ctx->notify_func2) { svn_wc_notify_t *notify; @@ -7545,7 +7774,6 @@ merge_newly_added_dir(const char *added_repos_relpath, diff_processor = processor; if (reverse_merge) diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, - NULL, scratch_pool); /* Filter the first path component using a filter processor, until we fixed @@ -7707,20 +7935,47 @@ resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option, const char *local_abspath; const char *lock_abspath; svn_error_t *err; + svn_wc_conflict_reason_t local_change; local_abspath = svn_client_conflict_get_local_abspath(conflict); + local_change = svn_client_conflict_get_local_change(conflict); - SVN_ERR(svn_wc__acquire_write_lock_for_resolve( - &lock_abspath, ctx->wc_ctx, local_abspath, - scratch_pool, scratch_pool)); + if (local_change == svn_wc_conflict_reason_unversioned) + { + char *parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool); + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, parent_abspath, + scratch_pool, scratch_pool)); - err = svn_wc__conflict_tree_update_local_add(ctx->wc_ctx, - local_abspath, - ctx->cancel_func, - ctx->cancel_baton, - ctx->notify_func2, - ctx->notify_baton2, - scratch_pool); + /* The update/switch operation has added the incoming versioned + * directory as a deleted op-depth layer. We can revert this layer + * to make the incoming tree appear in the working copy. + * This meta-data-only revert operation effecively merges the + * versioned and unversioned trees but leaves all unversioned files as + * they were. This is the best we can do; 3-way merging of unversioned + * files with files from the repository is impossible because there is + * no known merge base. No unversioned data will be lost, and any + * differences to files in the repository will show up in 'svn diff'. */ + err = svn_wc_revert6(ctx->wc_ctx, local_abspath, svn_depth_infinity, + FALSE, NULL, TRUE, TRUE /* metadata_only */, + TRUE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + } + else + { + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + err = svn_wc__conflict_tree_update_local_add(ctx->wc_ctx, + local_abspath, + ctx->cancel_func, + ctx->cancel_baton, + ctx->notify_func2, + ctx->notify_baton2, + scratch_pool); + } err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, lock_abspath, @@ -7730,35 +7985,6 @@ resolve_update_incoming_added_dir_merge(svn_client_conflict_option_t *option, return SVN_NO_ERROR; } -/* A baton for notification_adjust_func(). */ -struct notification_adjust_baton -{ - svn_wc_notify_func2_t inner_func; - void *inner_baton; - const char *checkout_abspath; - const char *final_abspath; -}; - -/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose - * baton is BATON->inner_baton) and adjusts the notification paths that - * start with BATON->checkout_abspath to start instead with - * BATON->final_abspath. */ -static void -notification_adjust_func(void *baton, - const svn_wc_notify_t *notify, - apr_pool_t *pool) -{ - struct notification_adjust_baton *nb = baton; - svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool); - const char *relpath; - - relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path); - inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool); - - if (nb->inner_func) - nb->inner_func(nb->inner_baton, inner_notify, pool); -} - /* Resolve a dir/dir "incoming add vs local obstruction" tree conflict by * replacing the local directory with the incoming directory. * If MERGE_DIRS is set, also merge the directories after replacing. */ @@ -7777,14 +8003,8 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option, svn_revnum_t incoming_new_pegrev; const char *local_abspath; const char *lock_abspath; - const char *tmpdir_abspath, *tmp_abspath; svn_error_t *err; - svn_revnum_t copy_src_revnum; - svn_opt_revision_t copy_src_peg_revision; svn_boolean_t timestamp_sleep; - svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2; - void *old_notify_baton2 = ctx->notify_baton2; - struct notification_adjust_baton nb; local_abspath = svn_client_conflict_get_local_abspath(conflict); @@ -7805,46 +8025,6 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option, if (corrected_url) url = corrected_url; - - /* Find a temporary location in which to check out the copy source. */ - SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, local_abspath, - scratch_pool, scratch_pool)); - - SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath, - svn_io_file_del_on_close, - scratch_pool, scratch_pool)); - - /* Make a new checkout of the requested source. While doing so, - * resolve copy_src_revnum to an actual revision number in case it - * was until now 'invalid' meaning 'head'. Ask this function not to - * sleep for timestamps, by passing a sleep_needed output param. - * Send notifications for all nodes except the root node, and adjust - * them to refer to the destination rather than this temporary path. */ - - nb.inner_func = ctx->notify_func2; - nb.inner_baton = ctx->notify_baton2; - nb.checkout_abspath = tmp_abspath; - nb.final_abspath = local_abspath; - ctx->notify_func2 = notification_adjust_func; - ctx->notify_baton2 = &nb; - - copy_src_peg_revision.kind = svn_opt_revision_number; - copy_src_peg_revision.value.number = incoming_new_pegrev; - - err = svn_client__checkout_internal(©_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, @@ -7861,31 +8041,11 @@ merge_incoming_added_dir_replace(svn_client_conflict_option_t *option, if (err) goto unlock_wc; - /* Schedule dst_path for addition in parent, with copy history. - Don't send any notification here. - Then remove the temporary checkout's .svn dir in preparation for - moving the rest of it into the final destination. */ - err = svn_wc_copy3(ctx->wc_ctx, tmp_abspath, local_abspath, - TRUE /* metadata_only */, - NULL, NULL, /* don't allow user to cancel here */ - NULL, NULL, scratch_pool); - if (err) - goto unlock_wc; - - err = svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath, - FALSE, scratch_pool, scratch_pool); - if (err) - goto unlock_wc; - err = svn_wc_remove_from_revision_control2(ctx->wc_ctx, - tmp_abspath, - FALSE, FALSE, - NULL, NULL, /* don't cancel */ - scratch_pool); - if (err) - goto unlock_wc; - - /* Move the temporary disk tree into place. */ - err = svn_io_file_rename2(tmp_abspath, local_abspath, FALSE, scratch_pool); + err = svn_client__repos_to_wc_copy_by_editor(×tamp_sleep, + svn_node_dir, + url, incoming_new_pegrev, + local_abspath, + ra_session, ctx, scratch_pool); if (err) goto unlock_wc; @@ -8012,6 +8172,112 @@ resolve_merge_incoming_added_dir_replace_and_merge( scratch_pool)); } +/* Ensure the conflict victim is a copy of itself from before it was deleted. + * Update and switch are supposed to set this up when flagging the conflict. */ +static svn_error_t * +ensure_local_edit_vs_incoming_deletion_copied_state( + struct conflict_tree_incoming_delete_details *details, + svn_wc_operation_t operation, + const char *wcroot_abspath, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + + svn_boolean_t is_copy; + svn_revnum_t copyfrom_rev; + const char *copyfrom_repos_relpath; + + SVN_ERR_ASSERT(operation == svn_wc_operation_update || + operation == svn_wc_operation_switch); + + SVN_ERR(svn_wc__node_get_origin(&is_copy, ©from_rev, + ©from_repos_relpath, + NULL, NULL, NULL, NULL, + ctx->wc_ctx, conflict->local_abspath, + FALSE, scratch_pool, scratch_pool)); + if (!is_copy) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Cannot resolve tree conflict on '%s' " + "(expected a copied item, but the item " + "is not a copy)"), + svn_dirent_local_style( + svn_dirent_skip_ancestor( + wcroot_abspath, + conflict->local_abspath), + scratch_pool)); + else if (details->deleted_rev != SVN_INVALID_REVNUM && + copyfrom_rev >= details->deleted_rev) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Cannot resolve tree conflict on '%s' " + "(expected an item copied from a revision " + "smaller than r%ld, but the item was " + "copied from r%ld)"), + svn_dirent_local_style( + svn_dirent_skip_ancestor( + wcroot_abspath, conflict->local_abspath), + scratch_pool), + details->deleted_rev, copyfrom_rev); + else if (details->added_rev != SVN_INVALID_REVNUM && + copyfrom_rev < details->added_rev) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Cannot resolve tree conflict on '%s' " + "(expected an item copied from a revision " + "larger than r%ld, but the item was " + "copied from r%ld)"), + svn_dirent_local_style( + svn_dirent_skip_ancestor( + wcroot_abspath, conflict->local_abspath), + scratch_pool), + details->added_rev, copyfrom_rev); + else if (operation == svn_wc_operation_update) + { + const char *old_repos_relpath; + + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &old_repos_relpath, NULL, NULL, conflict, + scratch_pool, scratch_pool)); + if (strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0 && + strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Cannot resolve tree conflict on '%s' " + "(expected an item copied from '^/%s' " + "or from '^/%s' but the item was " + "copied from '^/%s@%ld')"), + svn_dirent_local_style( + svn_dirent_skip_ancestor( + wcroot_abspath, conflict->local_abspath), + scratch_pool), + details->repos_relpath, + old_repos_relpath, + copyfrom_repos_relpath, copyfrom_rev); + } + else if (operation == svn_wc_operation_switch) + { + const char *old_repos_relpath; + + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &old_repos_relpath, NULL, NULL, conflict, + scratch_pool, scratch_pool)); + + if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Cannot resolve tree conflict on '%s' " + "(expected an item copied from '^/%s', " + "but the item was copied from " + "'^/%s@%ld')"), + svn_dirent_local_style( + svn_dirent_skip_ancestor( + wcroot_abspath, + conflict->local_abspath), + scratch_pool), + old_repos_relpath, + copyfrom_repos_relpath, copyfrom_rev); + } + + return SVN_NO_ERROR; +} + /* Verify the local working copy state matches what we expect when an * incoming deletion tree conflict exists. * We assume update/merge/switch operations leave the working copy in a @@ -8022,26 +8288,25 @@ resolve_merge_incoming_added_dir_replace_and_merge( static svn_error_t * verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict, svn_client_conflict_option_t *option, - svn_client_ctx_t *ctx, + svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { const char *local_abspath; const char *wcroot_abspath; svn_wc_operation_t operation; + svn_wc_conflict_reason_t local_change; local_abspath = svn_client_conflict_get_local_abspath(conflict); SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath, scratch_pool, scratch_pool)); operation = svn_client_conflict_get_operation(conflict); + local_change = svn_client_conflict_get_local_change(conflict); if (operation == svn_wc_operation_update || operation == svn_wc_operation_switch) { struct conflict_tree_incoming_delete_details *details; - svn_boolean_t is_copy; - svn_revnum_t copyfrom_rev; - const char *copyfrom_repos_relpath; details = conflict->tree_conflict_incoming_details; if (details == NULL) @@ -8053,26 +8318,8 @@ verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict, svn_dirent_local_style(local_abspath, scratch_pool)); - /* Ensure that the item is a copy of itself from before it was deleted. - * Update and switch are supposed to set this up when flagging the - * conflict. */ - SVN_ERR(svn_wc__node_get_origin(&is_copy, ©from_rev, - ©from_repos_relpath, - NULL, NULL, NULL, NULL, - ctx->wc_ctx, local_abspath, FALSE, - scratch_pool, scratch_pool)); - if (!is_copy) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Cannot resolve tree conflict on '%s' " - "(expected a copied item, but the item " - "is not a copy)"), - svn_dirent_local_style( - svn_dirent_skip_ancestor( - wcroot_abspath, - conflict->local_abspath), - scratch_pool)); - else if (details->deleted_rev == SVN_INVALID_REVNUM && - details->added_rev == SVN_INVALID_REVNUM) + if (details->deleted_rev == SVN_INVALID_REVNUM && + details->added_rev == SVN_INVALID_REVNUM) return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, _("Could not find the revision in which '%s' " "was deleted from the repository"), @@ -8081,75 +8328,11 @@ verify_local_state_for_incoming_delete(svn_client_conflict_t *conflict, wcroot_abspath, conflict->local_abspath), scratch_pool)); - else if (details->deleted_rev != SVN_INVALID_REVNUM && - copyfrom_rev >= details->deleted_rev) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Cannot resolve tree conflict on '%s' " - "(expected an item copied from a revision " - "smaller than r%ld, but the item was " - "copied from r%ld)"), - svn_dirent_local_style( - svn_dirent_skip_ancestor( - wcroot_abspath, conflict->local_abspath), - scratch_pool), - details->deleted_rev, copyfrom_rev); - else if (details->added_rev != SVN_INVALID_REVNUM && - copyfrom_rev < details->added_rev) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Cannot resolve tree conflict on '%s' " - "(expected an item copied from a revision " - "larger than r%ld, but the item was " - "copied from r%ld)"), - svn_dirent_local_style( - svn_dirent_skip_ancestor( - wcroot_abspath, conflict->local_abspath), - scratch_pool), - details->added_rev, copyfrom_rev); - else if (operation == svn_wc_operation_update) - { - const char *old_repos_relpath; - - SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( - &old_repos_relpath, NULL, NULL, conflict, - scratch_pool, scratch_pool)); - if (strcmp(copyfrom_repos_relpath, details->repos_relpath) != 0 && - strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Cannot resolve tree conflict on '%s' " - "(expected an item copied from '^/%s' " - "or from '^/%s' but the item was " - "copied from '^/%s@%ld')"), - svn_dirent_local_style( - svn_dirent_skip_ancestor( - wcroot_abspath, conflict->local_abspath), - scratch_pool), - details->repos_relpath, - old_repos_relpath, - copyfrom_repos_relpath, copyfrom_rev); - } - else if (operation == svn_wc_operation_switch) - { - const char *old_repos_relpath; - - SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( - &old_repos_relpath, NULL, NULL, conflict, - scratch_pool, scratch_pool)); - - if (strcmp(copyfrom_repos_relpath, old_repos_relpath) != 0) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Cannot resolve tree conflict on '%s' " - "(expected an item copied from '^/%s', " - "but the item was copied from " - "'^/%s@%ld')"), - svn_dirent_local_style( - svn_dirent_skip_ancestor( - wcroot_abspath, - conflict->local_abspath), - scratch_pool), - old_repos_relpath, - copyfrom_repos_relpath, copyfrom_rev); - } + if (local_change == svn_wc_conflict_reason_edited) + SVN_ERR(ensure_local_edit_vs_incoming_deletion_copied_state( + details, operation, wcroot_abspath, conflict, ctx, + scratch_pool)); } else if (operation == svn_wc_operation_merge) { @@ -8299,7 +8482,9 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, apr_pool_t *scratch_pool) { svn_client_conflict_option_id_t option_id; - const char *local_abspath; + const char *victim_abspath; + const char *merge_source_abspath; + svn_wc_conflict_reason_t local_change; svn_wc_operation_t operation; const char *lock_abspath; svn_error_t *err; @@ -8325,7 +8510,8 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, const char *moved_to_abspath; const char *incoming_abspath = NULL; - local_abspath = svn_client_conflict_get_local_abspath(conflict); + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + local_change = svn_client_conflict_get_local_change(conflict); operation = svn_client_conflict_get_operation(conflict); details = conflict->tree_conflict_incoming_details; if (details == NULL || details->moves == NULL) @@ -8333,21 +8519,21 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, _("The specified conflict resolution option " "requires details for tree conflict at '%s' " "to be fetched from the repository first."), - svn_dirent_local_style(local_abspath, + svn_dirent_local_style(victim_abspath, scratch_pool)); if (operation == svn_wc_operation_none) return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, _("Invalid operation code '%d' recorded for " "conflict at '%s'"), operation, - svn_dirent_local_style(local_abspath, + svn_dirent_local_style(victim_abspath, scratch_pool)); option_id = svn_client_conflict_option_get_id(option); SVN_ERR_ASSERT(option_id == svn_client_conflict_option_incoming_move_file_text_merge || option_id == - svn_client_conflict_option_incoming_move_dir_merge); - + svn_client_conflict_option_both_moved_file_move_merge); + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, conflict, scratch_pool, scratch_pool)); @@ -8361,7 +8547,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, scratch_pool)); /* Set up temporary storage for the common ancestor version of the file. */ - SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, local_abspath, + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_stream_open_unique(&ancestor_stream, &ancestor_abspath, wc_tmpdir, @@ -8391,21 +8577,41 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, details->wc_move_target_idx, const char *); + if (local_change == svn_wc_conflict_reason_missing) + { + /* This is an incoming move vs local move conflict. + * Merge from the local move's target location to the + * incoming move's target location. */ + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *moves; + + local_details = conflict->tree_conflict_local_details; + moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + merge_source_abspath = + APR_ARRAY_IDX(moves, local_details->wc_move_target_idx, const char *); + } + else + merge_source_abspath = victim_abspath; + /* ### The following WC modifications should be atomic. */ SVN_ERR(svn_wc__acquire_write_lock_for_resolve( &lock_abspath, ctx->wc_ctx, - svn_dirent_get_longest_ancestor(local_abspath, + svn_dirent_get_longest_ancestor(victim_abspath, moved_to_abspath, scratch_pool), scratch_pool, scratch_pool)); - err = verify_local_state_for_incoming_delete(conflict, option, ctx, - scratch_pool); - if (err) - goto unlock_wc; + if (local_change != svn_wc_conflict_reason_missing) + { + err = verify_local_state_for_incoming_delete(conflict, option, ctx, + scratch_pool); + if (err) + goto unlock_wc; + } /* Get a copy of the conflict victim's properties. */ - err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, local_abspath, + err = svn_wc_prop_list2(&victim_props, ctx->wc_ctx, merge_source_abspath, scratch_pool, scratch_pool); if (err) goto unlock_wc; @@ -8426,10 +8632,10 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, if (operation == svn_wc_operation_update || operation == svn_wc_operation_switch) { - svn_stream_t *working_stream; + svn_stream_t *moved_to_stream; svn_stream_t *incoming_stream; - /* Create a temporary copy of the working file in repository-normal form. + /* Create a temporary copy of the moved file in repository-normal form. * Set up this temporary file to be automatically removed. */ err = svn_stream_open_unique(&incoming_stream, &incoming_abspath, wc_tmpdir, @@ -8438,18 +8644,31 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, if (err) goto unlock_wc; - err = svn_wc__translated_stream(&working_stream, ctx->wc_ctx, - local_abspath, local_abspath, + err = svn_wc__translated_stream(&moved_to_stream, ctx->wc_ctx, + moved_to_abspath, + moved_to_abspath, SVN_WC_TRANSLATE_TO_NF, scratch_pool, scratch_pool); if (err) goto unlock_wc; - err = svn_stream_copy3(working_stream, incoming_stream, + err = svn_stream_copy3(moved_to_stream, incoming_stream, NULL, NULL, /* no cancellation */ scratch_pool); if (err) goto unlock_wc; + + /* Overwrite the moved file with the conflict victim's content. + * Incoming changes will be merged in from the temporary file created + * above. This is required to correctly make local changes show up as + * 'mine' during the three-way text merge between the ancestor file, + * the conflict victim ('mine'), and the moved file ('theirs') which + * was brought in by the update/switch operation and occupies the path + * of the merge target. */ + err = svn_io_copy_file(merge_source_abspath, moved_to_abspath, FALSE, + scratch_pool); + if (err) + goto unlock_wc; } else if (operation == svn_wc_operation_merge) { @@ -8486,7 +8705,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, err = svn_io_remove_file2(moved_to_abspath, FALSE, scratch_pool); if (err) goto unlock_wc; - err = svn_wc__move2(ctx->wc_ctx, local_abspath, moved_to_abspath, + err = svn_wc__move2(ctx->wc_ctx, merge_source_abspath, moved_to_abspath, FALSE, /* ordinary (not meta-data only) move */ FALSE, /* mixed-revisions don't apply to files */ NULL, NULL, /* don't allow user to cancel here */ @@ -8522,7 +8741,7 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, goto unlock_wc; incoming_abspath = NULL; } - + if (ctx->notify_func2) { svn_wc_notify_t *notify; @@ -8544,19 +8763,27 @@ resolve_incoming_move_file_text_merge(svn_client_conflict_option_t *option, operation == svn_wc_operation_switch) { /* Delete the tree conflict victim (clears the tree conflict marker). */ - err = svn_wc_delete4(ctx->wc_ctx, local_abspath, FALSE, FALSE, + err = svn_wc_delete4(ctx->wc_ctx, victim_abspath, FALSE, FALSE, NULL, NULL, /* don't allow user to cancel here */ NULL, NULL, /* no extra notification */ scratch_pool); if (err) goto unlock_wc; } + else if (local_change == svn_wc_conflict_reason_missing) + { + /* Clear tree conflict marker. */ + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, + scratch_pool); + if (err) + goto unlock_wc; + } if (ctx->notify_func2) { svn_wc_notify_t *notify; - notify = svn_wc_create_notify(local_abspath, svn_wc_notify_resolved_tree, + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, scratch_pool); ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); } @@ -8577,6 +8804,521 @@ unlock_wc: return SVN_NO_ERROR; } +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by merging from the + * incoming move's target location to the local move's target location, + * overriding the incoming move. */ +static svn_error_t * +resolve_both_moved_file_text_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_option_id_t option_id; + const char *victim_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + const char *wc_tmpdir; + const char *ancestor_abspath; + svn_stream_t *ancestor_stream; + apr_hash_t *ancestor_props; + apr_hash_t *incoming_props; + apr_hash_t *local_props; + const char *ancestor_url; + const char *corrected_url; + svn_ra_session_t *ra_session; + svn_wc_merge_outcome_t merge_content_outcome; + svn_wc_notify_state_t merge_props_outcome; + apr_array_header_t *propdiffs; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *local_moves; + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first."), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + /* Set up temporary storage for the common ancestor version of the file. */ + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(&ancestor_stream, + &ancestor_abspath, wc_tmpdir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + /* Fetch the ancestor file's content. */ + ancestor_url = svn_path_url_add_component2(repos_root_url, + incoming_old_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, + ancestor_url, NULL, NULL, + FALSE, FALSE, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, + ancestor_stream, NULL, /* fetched_rev */ + &ancestor_props, scratch_pool)); + filter_props(ancestor_props, scratch_pool); + + /* Close stream to flush ancestor file to disk. */ + SVN_ERR(svn_stream_close(ancestor_stream)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Get a copy of the incoming moved item's properties. */ + err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx, + incoming_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Get a copy of the local move target's properties. */ + err = svn_wc_prop_list2(&local_props, ctx->wc_ctx, + local_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Create a property diff for the files. */ + err = svn_prop_diffs(&propdiffs, incoming_props, local_props, + scratch_pool); + if (err) + goto unlock_wc; + + /* Perform the file merge. */ + err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, + ctx->wc_ctx, ancestor_abspath, + incoming_moved_to_abspath, local_moved_to_abspath, + NULL, NULL, NULL, /* labels */ + NULL, NULL, /* conflict versions */ + FALSE, /* dry run */ + NULL, NULL, /* diff3_cmd, merge_options */ + apr_hash_count(ancestor_props) ? ancestor_props : NULL, + propdiffs, + NULL, NULL, /* conflict func/baton */ + NULL, NULL, /* don't allow user to cancel here */ + scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(local_moved_to_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (merge_content_outcome == svn_wc_merge_conflict) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->prop_state = merge_props_outcome; + notify->kind = svn_node_file; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + /* Revert local addition of the incoming move's target. */ + err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, + svn_depth_infinity, FALSE, NULL, TRUE, FALSE, + FALSE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by moving the locally moved + * directory to the incoming move target location, and then merging changes. */ +static svn_error_t * +resolve_both_moved_dir_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_option_id_t option_id; + const char *victim_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + const char *incoming_moved_repos_relpath; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *local_moves; + svn_client__conflict_report_t *conflict_report; + const char *incoming_old_url; + const char *incoming_moved_url; + svn_opt_revision_t incoming_old_opt_rev; + svn_opt_revision_t incoming_moved_opt_rev; + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + + "to be fetched from the repository first."), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_dir_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Perform the merge. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; + + incoming_moved_repos_relpath = + get_moved_to_repos_relpath(incoming_details, scratch_pool); + incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_moved_repos_relpath, SVN_VA_NULL); + incoming_moved_opt_rev.kind = svn_opt_revision_number; + incoming_moved_opt_rev.value.number = incoming_new_pegrev; + err = svn_client__merge_locked(&conflict_report, + incoming_old_url, &incoming_old_opt_rev, + incoming_moved_url, &incoming_moved_opt_rev, + local_moved_to_abspath, svn_depth_infinity, + TRUE, TRUE, /* do a no-ancestry merge */ + FALSE, FALSE, FALSE, + TRUE, /* Allow mixed-rev just in case, + * since conflict victims can't be + * updated to straighten out + * mixed-rev trees. */ + NULL, ctx, scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Revert local addition of the incoming move's target. */ + err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, + svn_depth_infinity, FALSE, NULL, TRUE, FALSE, + FALSE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by merging from the + * incoming move's target location to the local move's target location, + * overriding the incoming move. */ +static svn_error_t * +resolve_both_moved_dir_move_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_option_id_t option_id; + const char *victim_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *local_moves; + svn_client__conflict_report_t *conflict_report; + const char *incoming_old_url; + const char *incoming_moved_url; + svn_opt_revision_t incoming_old_opt_rev; + svn_opt_revision_t incoming_moved_opt_rev; + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + + "to be fetched from the repository first."), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == + svn_client_conflict_option_both_moved_dir_move_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Revert the incoming move target directory. */ + err = svn_wc_revert6(ctx->wc_ctx, incoming_moved_to_abspath, + svn_depth_infinity, + FALSE, NULL, TRUE, FALSE, + TRUE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + /* The move operation is not part of natural history. We must replicate + * this move in our history. Record a move in the working copy. */ + err = svn_wc__move2(ctx->wc_ctx, local_moved_to_abspath, + incoming_moved_to_abspath, + FALSE, /* this is not a meta-data only move */ + TRUE, /* allow mixed-revisions just in case */ + NULL, NULL, /* don't allow user to cancel here */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + /* Merge INCOMING_OLD_URL@MERGE_LEFT->INCOMING_MOVED_URL@MERGE_RIGHT + * into the locally moved merge target. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; + + incoming_moved_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_details->move_target_repos_relpath, + SVN_VA_NULL); + incoming_moved_opt_rev.kind = svn_opt_revision_number; + incoming_moved_opt_rev.value.number = incoming_new_pegrev; + err = svn_client__merge_locked(&conflict_report, + incoming_old_url, &incoming_old_opt_rev, + incoming_moved_url, &incoming_moved_opt_rev, + incoming_moved_to_abspath, svn_depth_infinity, + TRUE, TRUE, /* do a no-ancestry merge */ + FALSE, FALSE, FALSE, + TRUE, /* Allow mixed-rev just in case, + * since conflict victims can't be + * updated to straighten out + * mixed-rev trees. */ + NULL, ctx, scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + /* Implements conflict_option_resolve_func_t. */ static svn_error_t * resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, @@ -8602,8 +9344,8 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, struct conflict_tree_incoming_delete_details *details; apr_array_header_t *possible_moved_to_abspaths; const char *moved_to_abspath; - svn_client__pathrev_t *yca_loc; - svn_opt_revision_t yca_opt_rev; + const char *incoming_old_url; + svn_opt_revision_t incoming_old_opt_rev; svn_client__conflict_report_t *conflict_report; svn_boolean_t is_copy; svn_boolean_t is_modified; @@ -8622,7 +9364,7 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, option_id = svn_client_conflict_option_get_id(option); SVN_ERR_ASSERT(option_id == svn_client_conflict_option_incoming_move_dir_merge); - + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, &repos_uuid, conflict, scratch_pool, scratch_pool)); @@ -8696,30 +9438,6 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, goto unlock_wc; } - /* Now find the youngest common ancestor of these nodes. */ - err = find_yca(&yca_loc, victim_repos_relpath, victim_peg_rev, - moved_to_repos_relpath, moved_to_peg_rev, - repos_root_url, repos_uuid, - NULL, ctx, scratch_pool, scratch_pool); - if (err) - goto unlock_wc; - - if (yca_loc == NULL) - { - err = svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Cannot resolve tree conflict on '%s' " - "(could not find common ancestor of '^/%s@%ld' " - " and '^/%s@%ld')"), - svn_dirent_local_style(local_abspath, - scratch_pool), - victim_repos_relpath, victim_peg_rev, - moved_to_repos_relpath, moved_to_peg_rev); - goto unlock_wc; - } - - yca_opt_rev.kind = svn_opt_revision_number; - yca_opt_rev.value.number = yca_loc->rev; - err = verify_local_state_for_incoming_delete(conflict, option, ctx, scratch_pool); if (err) @@ -8731,11 +9449,14 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, svn_opt_revision_t incoming_new_opt_rev; /* Revert the incoming move target directory. */ - SVN_ERR(svn_wc_revert5(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity, - FALSE, NULL, TRUE, FALSE, - NULL, NULL, /* no cancellation */ - ctx->notify_func2, ctx->notify_baton2, - scratch_pool)); + err = svn_wc_revert6(ctx->wc_ctx, moved_to_abspath, svn_depth_infinity, + FALSE, NULL, TRUE, FALSE, + TRUE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; /* The move operation is not part of natural history. We must replicate * this move in our history. Record a move in the working copy. */ @@ -8748,7 +9469,12 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, if (err) goto unlock_wc; - /* Merge YCA_URL@YCA_REV->MOVE_TARGET_URL@MERGE_RIGHT into move target. */ + /* Merge INCOMING_OLD_URL@MERGE_LEFT->MOVE_TARGET_URL@MERGE_RIGHT + * into move target. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; move_target_url = apr_pstrcat(scratch_pool, repos_root_url, "/", get_moved_to_repos_relpath(details, scratch_pool), @@ -8756,7 +9482,7 @@ resolve_incoming_move_dir_merge(svn_client_conflict_option_t *option, incoming_new_opt_rev.kind = svn_opt_revision_number; incoming_new_opt_rev.value.number = incoming_new_pegrev; err = svn_client__merge_locked(&conflict_report, - yca_loc->url, &yca_opt_rev, + incoming_old_url, &incoming_old_opt_rev, move_target_url, &incoming_new_opt_rev, moved_to_abspath, svn_depth_infinity, TRUE, TRUE, /* do a no-ancestry merge */ @@ -8825,7 +9551,9 @@ unlock_wc: return SVN_NO_ERROR; } -/* Implements conflict_option_resolve_func_t. */ +/* Implements conflict_option_resolve_func_t. + * Handles svn_client_conflict_option_local_move_file_text_merge + * and svn_client_conflict_option_sibling_move_file_text_merge. */ static svn_error_t * resolve_local_move_file_merge(svn_client_conflict_option_t *option, svn_client_conflict_t *conflict, @@ -8853,6 +9581,12 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, svn_wc_notify_state_t merge_props_outcome; apr_array_header_t *propdiffs; struct conflict_tree_local_missing_details *details; + const char *merge_target_abspath; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); details = conflict->tree_conflict_local_details; @@ -8868,8 +9602,31 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, NULL, conflict, scratch_pool, scratch_pool)); + if (details->wc_siblings) + { + merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *); + } + else if (details->wc_move_targets && details->move_target_repos_relpath) + { + apr_array_header_t *moves; + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + merge_target_abspath = APR_ARRAY_IDX(moves, details->wc_move_target_idx, + const char *); + } + else + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Corresponding working copy node not found " + "for '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor( + wcroot_abspath, conflict->local_abspath), + scratch_pool)); + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, - details->moved_to_abspath, + merge_target_abspath, scratch_pool, scratch_pool)); /* Fetch the common ancestor file's content. */ @@ -8914,7 +9671,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, SVN_ERR(svn_wc__acquire_write_lock_for_resolve( &lock_abspath, ctx->wc_ctx, svn_dirent_get_longest_ancestor(conflict->local_abspath, - details->moved_to_abspath, + merge_target_abspath, scratch_pool), scratch_pool, scratch_pool)); @@ -8922,7 +9679,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, ctx->wc_ctx, ancestor_tmp_abspath, incoming_tmp_abspath, - details->moved_to_abspath, + merge_target_abspath, NULL, NULL, NULL, /* labels */ NULL, NULL, /* conflict versions */ FALSE, /* dry run */ @@ -8932,7 +9689,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, NULL, NULL, /* conflict func/baton */ NULL, NULL, /* don't allow user to cancel here */ scratch_pool); - svn_io_sleep_for_timestamps(details->moved_to_abspath, scratch_pool); + svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool); if (err) return svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, @@ -8953,7 +9710,7 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, svn_wc_notify_t *notify; /* Tell the world about the file merge that just happened. */ - notify = svn_wc_create_notify(details->moved_to_abspath, + notify = svn_wc_create_notify(merge_target_abspath, svn_wc_notify_update_update, scratch_pool); if (merge_content_outcome == svn_wc_merge_conflict) @@ -8976,6 +9733,127 @@ resolve_local_move_file_merge(svn_client_conflict_option_t *option, return SVN_NO_ERROR; } +/* Implements conflict_option_resolve_func_t. */ +static svn_error_t * +resolve_local_move_dir_merge(svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + struct conflict_tree_local_missing_details *details; + const char *merge_target_abspath; + const char *incoming_old_url; + const char *incoming_new_url; + svn_opt_revision_t incoming_old_opt_rev; + svn_opt_revision_t incoming_new_opt_rev; + svn_client__conflict_report_t *conflict_report; + + details = conflict->tree_conflict_local_details; + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + if (details->wc_move_targets) + { + apr_array_header_t *moves; + + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + merge_target_abspath = + APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *); + } + else + merge_target_abspath = APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(conflict->local_abspath, + merge_target_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Resolve to current working copy state. + * svn_client__merge_locked() requires this. */ + err = svn_wc__del_tree_conflict(ctx->wc_ctx, conflict->local_abspath, + scratch_pool); + if (err) + goto unlock_wc; + + /* Merge outstanding changes to the merge target. */ + incoming_old_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_old_repos_relpath, SVN_VA_NULL); + incoming_old_opt_rev.kind = svn_opt_revision_number; + incoming_old_opt_rev.value.number = incoming_old_pegrev; + incoming_new_url = apr_pstrcat(scratch_pool, repos_root_url, "/", + incoming_new_repos_relpath, SVN_VA_NULL); + incoming_new_opt_rev.kind = svn_opt_revision_number; + incoming_new_opt_rev.value.number = incoming_new_pegrev; + err = svn_client__merge_locked(&conflict_report, + incoming_old_url, &incoming_old_opt_rev, + incoming_new_url, &incoming_new_opt_rev, + merge_target_abspath, svn_depth_infinity, + TRUE, TRUE, /* do a no-ancestry merge */ + FALSE, FALSE, FALSE, + TRUE, /* Allow mixed-rev just in case, + * since conflict victims can't be + * updated to straighten out + * mixed-rev trees. */ + NULL, ctx, scratch_pool, scratch_pool); +unlock_wc: + svn_io_sleep_for_timestamps(merge_target_abspath, scratch_pool); + err = svn_error_compose_create(err, + svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + if (err) + return svn_error_trace(err); + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(merge_target_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (conflict_report) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->kind = svn_node_dir; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + + /* And also about the successfully resolved tree conflict. */ + notify = svn_wc_create_notify(conflict->local_abspath, + svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + conflict->resolution_tree = svn_client_conflict_option_get_id(option); + + return SVN_NO_ERROR; +} + static svn_error_t * assert_text_conflict(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) { @@ -9112,13 +9990,13 @@ svn_client_conflict_text_get_resolution_options(apr_array_header_t **options, add_resolution_option(*options, conflict, svn_client_conflict_option_incoming_text_where_conflicted, _("Accept incoming for conflicts"), - _("accept changes only where they conflict"), + _("accept incoming changes only where they conflict"), resolve_text_conflict); add_resolution_option(*options, conflict, svn_client_conflict_option_working_text_where_conflicted, _("Reject conflicts"), - _("reject changes which conflict and accept the rest"), + _("reject incoming changes which conflict and accept the rest"), resolve_text_conflict); add_resolution_option(*options, conflict, @@ -9392,6 +10270,7 @@ configure_option_incoming_added_file_text_merge(svn_client_conflict_t *conflict, incoming_new_kind == svn_node_file && incoming_change == svn_wc_conflict_action_add && (local_change == svn_wc_conflict_reason_obstructed || + local_change == svn_wc_conflict_reason_unversioned || local_change == svn_wc_conflict_reason_added)) { const char *description; @@ -9517,8 +10396,9 @@ configure_option_incoming_added_dir_merge(svn_client_conflict_t *conflict, incoming_change == svn_wc_conflict_action_add && (local_change == svn_wc_conflict_reason_added || (operation == svn_wc_operation_merge && - local_change == svn_wc_conflict_reason_obstructed))) - + local_change == svn_wc_conflict_reason_obstructed) || + (operation != svn_wc_operation_merge && + local_change == svn_wc_conflict_reason_unversioned))) { const char *description; const char *wcroot_abspath; @@ -9527,13 +10407,18 @@ configure_option_incoming_added_dir_merge(svn_client_conflict_t *conflict, conflict->local_abspath, scratch_pool, scratch_pool)); if (operation == svn_wc_operation_merge) - description = - apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"), - incoming_new_repos_relpath, incoming_new_pegrev, - svn_dirent_local_style( - svn_dirent_skip_ancestor(wcroot_abspath, - conflict->local_abspath), - scratch_pool)); + { + if (conflict->tree_conflict_incoming_details == NULL) + return SVN_NO_ERROR; + + description = + apr_psprintf(scratch_pool, _("merge '^/%s@%ld' into '%s'"), + incoming_new_repos_relpath, incoming_new_pegrev, + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, + conflict->local_abspath), + scratch_pool)); + } else description = apr_psprintf(scratch_pool, _("merge local '%s' and '^/%s@%ld'"), @@ -9723,7 +10608,7 @@ configure_option_incoming_delete_ignore(svn_client_conflict_t *conflict, is_local_move = (local_details != NULL && local_details->moves != NULL); - if (!is_incoming_move && !is_local_move) + if (is_incoming_move || is_local_move) return SVN_NO_ERROR; } @@ -9769,7 +10654,8 @@ configure_option_incoming_delete_accept(svn_client_conflict_t *conflict, incoming_details->moves != NULL); if (is_incoming_move && (local_change == svn_wc_conflict_reason_edited || - local_change == svn_wc_conflict_reason_moved_away)) + local_change == svn_wc_conflict_reason_moved_away || + local_change == svn_wc_conflict_reason_missing)) { /* An option which accepts the incoming deletion makes no sense * if we know there was a local move and/or an incoming move. */ @@ -9806,39 +10692,75 @@ describe_incoming_move_merge_conflict_option( const char **description, svn_client_conflict_t *conflict, svn_client_ctx_t *ctx, - struct conflict_tree_incoming_delete_details *details, + const char *moved_to_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - apr_array_header_t *move_target_wc_abspaths; svn_wc_operation_t operation; const char *victim_abspath; - const char *moved_to_abspath; + svn_node_kind_t victim_node_kind; const char *wcroot_abspath; - move_target_wc_abspaths = - svn_hash_gets(details->wc_move_targets, - get_moved_to_repos_relpath(details, scratch_pool)); - moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, - details->wc_move_target_idx, - const char *); - victim_abspath = svn_client_conflict_get_local_abspath(conflict); + victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, victim_abspath, scratch_pool, scratch_pool)); operation = svn_client_conflict_get_operation(conflict); if (operation == svn_wc_operation_merge) - *description = - apr_psprintf( - result_pool, _("move '%s' to '%s' and merge"), - svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, - victim_abspath), - scratch_pool), - svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, - moved_to_abspath), - scratch_pool)); + { + const char *incoming_moved_abspath = NULL; + + if (victim_node_kind == svn_node_none) + { + /* This is an incoming move vs local move conflict. */ + struct conflict_tree_incoming_delete_details *details; + + details = conflict->tree_conflict_incoming_details; + if (details->wc_move_targets) + { + apr_array_header_t *moves; + + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + incoming_moved_abspath = + APR_ARRAY_IDX(moves, details->wc_move_target_idx, + const char *); + } + } + + if (incoming_moved_abspath) + { + /* The 'move and merge' option follows the incoming move; note that + * moved_to_abspath points to the current location of an item which + * was moved in the history of our merge target branch. If the user + * chooses 'move and merge', that item will be moved again (i.e. it + * will be moved to and merged with incoming_moved_abspath's item). */ + *description = + apr_psprintf( + result_pool, _("move '%s' to '%s' and merge"), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + moved_to_abspath), + scratch_pool), + svn_dirent_local_style(svn_dirent_skip_ancestor( + wcroot_abspath, + incoming_moved_abspath), + scratch_pool)); + } + else + { + *description = + apr_psprintf( + result_pool, _("move '%s' to '%s' and merge"), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + victim_abspath), + scratch_pool), + svn_dirent_local_style(svn_dirent_skip_ancestor(wcroot_abspath, + moved_to_abspath), + scratch_pool)); + } + } else *description = apr_psprintf( @@ -9863,13 +10785,16 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict, { svn_node_kind_t victim_node_kind; svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; const char *incoming_old_repos_relpath; svn_revnum_t incoming_old_pegrev; svn_node_kind_t incoming_old_kind; const char *incoming_new_repos_relpath; svn_revnum_t incoming_new_pegrev; svn_node_kind_t incoming_new_kind; + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( &incoming_old_repos_relpath, &incoming_old_pegrev, @@ -9883,10 +10808,13 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict, if (victim_node_kind == svn_node_file && incoming_old_kind == svn_node_file && incoming_new_kind == svn_node_none && - incoming_change == svn_wc_conflict_action_delete) + incoming_change == svn_wc_conflict_action_delete && + local_change == svn_wc_conflict_reason_edited) { struct conflict_tree_incoming_delete_details *details; const char *description; + apr_array_header_t *move_target_wc_abspaths; + const char *moved_to_abspath; details = conflict->tree_conflict_incoming_details; if (details == NULL || details->moves == NULL) @@ -9895,9 +10823,15 @@ configure_option_incoming_move_file_merge(svn_client_conflict_t *conflict, if (apr_hash_count(details->wc_move_targets) == 0) return SVN_NO_ERROR; + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, + details->wc_move_target_idx, + const char *); SVN_ERR(describe_incoming_move_merge_conflict_option(&description, conflict, ctx, - details, + moved_to_abspath, scratch_pool, scratch_pool)); add_resolution_option( @@ -9948,6 +10882,8 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict, { struct conflict_tree_incoming_delete_details *details; const char *description; + apr_array_header_t *move_target_wc_abspaths; + const char *moved_to_abspath; details = conflict->tree_conflict_incoming_details; if (details == NULL || details->moves == NULL) @@ -9956,9 +10892,15 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict, if (apr_hash_count(details->wc_move_targets) == 0) return SVN_NO_ERROR; + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, + details->wc_move_target_idx, + const char *); SVN_ERR(describe_incoming_move_merge_conflict_option(&description, conflict, ctx, - details, + moved_to_abspath, scratch_pool, scratch_pool)); add_resolution_option(options, conflict, @@ -9973,23 +10915,32 @@ configure_option_incoming_dir_merge(svn_client_conflict_t *conflict, /* Configure 'local move file merge' resolution option for * a tree conflict. */ static svn_error_t * -configure_option_local_move_file_merge(svn_client_conflict_t *conflict, - svn_client_ctx_t *ctx, - apr_array_header_t *options, - apr_pool_t *scratch_pool) +configure_option_local_move_file_or_dir_merge( + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) { svn_wc_operation_t operation; svn_wc_conflict_action_t incoming_change; svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; const char *incoming_new_repos_relpath; svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; operation = svn_client_conflict_get_operation(conflict); incoming_change = svn_client_conflict_get_incoming_change(conflict); local_change = svn_client_conflict_get_local_change(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( &incoming_new_repos_relpath, &incoming_new_pegrev, - NULL, conflict, scratch_pool, + &incoming_new_kind, conflict, scratch_pool, scratch_pool)); if (operation == svn_wc_operation_merge && @@ -9997,62 +10948,37 @@ configure_option_local_move_file_merge(svn_client_conflict_t *conflict, local_change == svn_wc_conflict_reason_missing) { struct conflict_tree_local_missing_details *details; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, + scratch_pool, scratch_pool)); details = conflict->tree_conflict_local_details; - if (details != NULL && details->moves != NULL) + if (details != NULL && details->moves != NULL && + details->move_target_repos_relpath != NULL) { - apr_hash_t *wc_move_targets = apr_hash_make(scratch_pool); - apr_pool_t *iterpool; - int i; + apr_array_header_t *moves; + const char *moved_to_abspath; + const char *description; - iterpool = svn_pool_create(scratch_pool); - for (i = 0; i < details->moves->nelts; i++) - { - struct repos_move_info *move; + moves = svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + moved_to_abspath = + APR_ARRAY_IDX(moves, details->wc_move_target_idx, const char *); - svn_pool_clear(iterpool); - move = APR_ARRAY_IDX(details->moves, i, struct repos_move_info *); - SVN_ERR(follow_move_chains(wc_move_targets, move, ctx, - conflict->local_abspath, - svn_node_file, - incoming_new_repos_relpath, - incoming_new_pegrev, - scratch_pool, iterpool)); - } - svn_pool_destroy(iterpool); + description = + apr_psprintf( + scratch_pool, _("apply changes to move destination '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, moved_to_abspath), + scratch_pool)); - if (apr_hash_count(wc_move_targets) > 0) + if ((incoming_old_kind == svn_node_file || + incoming_old_kind == svn_node_none) && + (incoming_new_kind == svn_node_file || + incoming_new_kind == svn_node_none)) { - apr_array_header_t *move_target_repos_relpaths; - const svn_sort__item_t *item; - apr_array_header_t *moved_to_abspaths; - const char *description; - const char *wcroot_abspath; - - /* Initialize to the first possible move target. Hopefully, - * in most cases there will only be one candidate anyway. */ - move_target_repos_relpaths = svn_sort__hash( - wc_move_targets, - svn_sort_compare_items_as_paths, - scratch_pool); - item = &APR_ARRAY_IDX(move_target_repos_relpaths, - 0, svn_sort__item_t); - moved_to_abspaths = item->value; - details->moved_to_abspath = - apr_pstrdup(conflict->pool, - APR_ARRAY_IDX(moved_to_abspaths, 0, const char *)); - - SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, - conflict->local_abspath, - scratch_pool, scratch_pool)); - description = - apr_psprintf( - scratch_pool, _("apply changes to move destination '%s'"), - svn_dirent_local_style( - svn_dirent_skip_ancestor(wcroot_abspath, - details->moved_to_abspath), - scratch_pool)); - add_resolution_option( options, conflict, svn_client_conflict_option_local_move_file_text_merge, @@ -10060,50 +10986,943 @@ configure_option_local_move_file_merge(svn_client_conflict_t *conflict, description, resolve_local_move_file_merge); } else - details->moved_to_abspath = NULL; + { + add_resolution_option( + options, conflict, + svn_client_conflict_option_local_move_dir_merge, + _("Apply to move destination"), + description, resolve_local_move_dir_merge); + } } } return SVN_NO_ERROR; } -svn_error_t * -svn_client_conflict_option_get_moved_to_repos_relpath_candidates( - apr_array_header_t **possible_moved_to_repos_relpaths, +/* Configure 'sibling move file/dir merge' resolution option for + * a tree conflict. */ +static svn_error_t * +configure_option_sibling_move_merge(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; + + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + &incoming_new_kind, conflict, scratch_pool, + scratch_pool)); + + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, + scratch_pool, scratch_pool)); + + details = conflict->tree_conflict_local_details; + if (details != NULL && details->wc_siblings != NULL) + { + const char *description; + const char *sibling; + + sibling = + apr_pstrdup(conflict->pool, + APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *)); + description = + apr_psprintf( + scratch_pool, _("apply changes to '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, sibling), + scratch_pool)); + + if ((incoming_old_kind == svn_node_file || + incoming_old_kind == svn_node_none) && + (incoming_new_kind == svn_node_file || + incoming_new_kind == svn_node_none)) + { + add_resolution_option( + options, conflict, + svn_client_conflict_option_sibling_move_file_text_merge, + _("Apply to corresponding local location"), + description, resolve_local_move_file_merge); + } + else + { + add_resolution_option( + options, conflict, + svn_client_conflict_option_sibling_move_dir_merge, + _("Apply to corresponding local location"), + description, resolve_local_move_dir_merge); + } + } + } + + return SVN_NO_ERROR; +} + +struct conflict_tree_update_local_moved_away_details { + /* + * This array consists of "const char *" absolute paths to working copy + * nodes which are uncomitted copies and correspond to the repository path + * of the conflict victim. + * Each such working copy node is a potential local move target which can + * be chosen to find a suitable merge target when resolving a tree conflict. + * + * This may be an empty array in case if there is no move target path in + * the working copy. */ + apr_array_header_t *wc_move_targets; + + /* Current index into the list of working copy paths in WC_MOVE_TARGETS. */ + int preferred_move_target_idx; +}; + +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by merging from the + * incoming move's target location to the local move's target location, + * overriding the incoming move. The original local move was broken during + * update/switch, so overriding the incoming move involves recording a new + * move from the incoming move's target location to the local move's target + * location. */ +static svn_error_t * +resolve_both_moved_file_update_keep_local_move( svn_client_conflict_option_t *option, - apr_pool_t *result_pool, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { - svn_client_conflict_t *conflict = option->conflict; - struct conflict_tree_incoming_delete_details *details; + svn_client_conflict_option_id_t option_id; const char *victim_abspath; - apr_array_header_t *sorted_repos_relpaths; - int i; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + const char *wc_tmpdir; + const char *ancestor_abspath; + svn_stream_t *ancestor_stream; + apr_hash_t *ancestor_props; + apr_hash_t *incoming_props; + apr_hash_t *local_props; + const char *ancestor_url; + const char *corrected_url; + svn_ra_session_t *ra_session; + svn_wc_merge_outcome_t merge_content_outcome; + svn_wc_notify_state_t merge_props_outcome; + apr_array_header_t *propdiffs; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_update_local_moved_away_details *local_details; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge); + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("The specified conflict resolution option " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first."), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == svn_client_conflict_option_both_moved_file_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + /* Set up temporary storage for the common ancestor version of the file. */ + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(&ancestor_stream, + &ancestor_abspath, wc_tmpdir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + /* Fetch the ancestor file's content. */ + ancestor_url = svn_path_url_add_component2(repos_root_url, + incoming_old_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, + ancestor_url, NULL, NULL, + FALSE, FALSE, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, + ancestor_stream, NULL, /* fetched_rev */ + &ancestor_props, scratch_pool)); + filter_props(ancestor_props, scratch_pool); + + /* Close stream to flush ancestor file to disk. */ + SVN_ERR(svn_stream_close(ancestor_stream)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moved_to_abspath = + APR_ARRAY_IDX(local_details->wc_move_targets, + local_details->preferred_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Get a copy of the incoming moved item's properties. */ + err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx, + incoming_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Get a copy of the local move target's properties. */ + err = svn_wc_prop_list2(&local_props, ctx->wc_ctx, + local_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Create a property diff for the files. */ + err = svn_prop_diffs(&propdiffs, incoming_props, local_props, + scratch_pool); + if (err) + goto unlock_wc; + + /* Perform the file merge. */ + err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, + ctx->wc_ctx, ancestor_abspath, + incoming_moved_to_abspath, local_moved_to_abspath, + NULL, NULL, NULL, /* labels */ + NULL, NULL, /* conflict versions */ + FALSE, /* dry run */ + NULL, NULL, /* diff3_cmd, merge_options */ + apr_hash_count(ancestor_props) ? ancestor_props : NULL, + propdiffs, + NULL, NULL, /* conflict func/baton */ + NULL, NULL, /* don't allow user to cancel here */ + scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(local_moved_to_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (merge_content_outcome == svn_wc_merge_conflict) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->prop_state = merge_props_outcome; + notify->kind = svn_node_file; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + /* Record a new move which overrides the incoming move. */ + err = svn_wc__move2(ctx->wc_ctx, incoming_moved_to_abspath, + local_moved_to_abspath, + TRUE, /* meta-data only move */ + FALSE, /* mixed-revisions don't apply to files */ + NULL, NULL, /* don't allow user to cancel here */ + NULL, NULL, /* no extra notification */ + scratch_pool); + if (err) + goto unlock_wc; + + /* Remove moved-away file from disk. */ + err = svn_io_remove_file2(incoming_moved_to_abspath, TRUE, scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Implements conflict_option_resolve_func_t. + * Resolve an incoming move vs local move conflict by merging from the + * local move's target location to the incoming move's target location, + * and reverting the local move. */ +static svn_error_t * +resolve_both_moved_file_update_keep_incoming_move( + svn_client_conflict_option_t *option, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_option_id_t option_id; + const char *victim_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *lock_abspath; + svn_error_t *err; + const char *repos_root_url; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + const char *wc_tmpdir; + const char *ancestor_abspath; + svn_stream_t *ancestor_stream; + apr_hash_t *ancestor_props; + apr_hash_t *incoming_props; + apr_hash_t *local_props; + const char *ancestor_url; + const char *corrected_url; + svn_ra_session_t *ra_session; + svn_wc_merge_outcome_t merge_content_outcome; + svn_wc_notify_state_t merge_props_outcome; + apr_array_header_t *propdiffs; + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *possible_moved_to_abspaths; + const char *incoming_moved_to_abspath; + struct conflict_tree_update_local_moved_away_details *local_details; victim_abspath = svn_client_conflict_get_local_abspath(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->wc_move_targets == NULL) + operation = svn_client_conflict_get_operation(conflict); + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL) return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Getting a list of possible move targets " + _("The specified conflict resolution option " "requires details for tree conflict at '%s' " - "to be fetched from the repository first"), + "to be fetched from the repository first."), svn_dirent_local_style(victim_abspath, scratch_pool)); + if (operation == svn_wc_operation_none) + return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, + _("Invalid operation code '%d' recorded for " + "conflict at '%s'"), operation, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + option_id = svn_client_conflict_option_get_id(option); + SVN_ERR_ASSERT(option_id == + svn_client_conflict_option_both_moved_file_move_merge); + + SVN_ERR(svn_client_conflict_get_repos_info(&repos_root_url, NULL, + conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + NULL, conflict, scratch_pool, + scratch_pool)); + + /* Set up temporary storage for the common ancestor version of the file. */ + SVN_ERR(svn_wc__get_tmpdir(&wc_tmpdir, ctx->wc_ctx, victim_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_unique(&ancestor_stream, + &ancestor_abspath, wc_tmpdir, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + + /* Fetch the ancestor file's content. */ + ancestor_url = svn_path_url_add_component2(repos_root_url, + incoming_old_repos_relpath, + scratch_pool); + SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, + ancestor_url, NULL, NULL, + FALSE, FALSE, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(svn_ra_get_file(ra_session, "", incoming_old_pegrev, + ancestor_stream, NULL, /* fetched_rev */ + &ancestor_props, scratch_pool)); + filter_props(ancestor_props, scratch_pool); + + /* Close stream to flush ancestor file to disk. */ + SVN_ERR(svn_stream_close(ancestor_stream)); + + possible_moved_to_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(possible_moved_to_abspaths, + incoming_details->wc_move_target_idx, const char *); + + local_details = conflict->tree_conflict_local_details; + local_moved_to_abspath = + APR_ARRAY_IDX(local_details->wc_move_targets, + local_details->preferred_move_target_idx, const char *); + + /* ### The following WC modifications should be atomic. */ + SVN_ERR(svn_wc__acquire_write_lock_for_resolve( + &lock_abspath, ctx->wc_ctx, + svn_dirent_get_longest_ancestor(victim_abspath, + local_moved_to_abspath, + scratch_pool), + scratch_pool, scratch_pool)); + + /* Get a copy of the incoming moved item's properties. */ + err = svn_wc_prop_list2(&incoming_props, ctx->wc_ctx, + incoming_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Get a copy of the local move target's properties. */ + err = svn_wc_prop_list2(&local_props, ctx->wc_ctx, + local_moved_to_abspath, + scratch_pool, scratch_pool); + if (err) + goto unlock_wc; + + /* Create a property diff for the files. */ + err = svn_prop_diffs(&propdiffs, incoming_props, local_props, + scratch_pool); + if (err) + goto unlock_wc; + + /* Perform the file merge. */ + err = svn_wc_merge5(&merge_content_outcome, &merge_props_outcome, + ctx->wc_ctx, ancestor_abspath, + local_moved_to_abspath, incoming_moved_to_abspath, + NULL, NULL, NULL, /* labels */ + NULL, NULL, /* conflict versions */ + FALSE, /* dry run */ + NULL, NULL, /* diff3_cmd, merge_options */ + apr_hash_count(ancestor_props) ? ancestor_props : NULL, + propdiffs, + NULL, NULL, /* conflict func/baton */ + NULL, NULL, /* don't allow user to cancel here */ + scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + /* Tell the world about the file merge that just happened. */ + notify = svn_wc_create_notify(local_moved_to_abspath, + svn_wc_notify_update_update, + scratch_pool); + if (merge_content_outcome == svn_wc_merge_conflict) + notify->content_state = svn_wc_notify_state_conflicted; + else + notify->content_state = svn_wc_notify_state_merged; + notify->prop_state = merge_props_outcome; + notify->kind = svn_node_file; + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + /* Revert the copy-half of the local move. The delete-half of this move + * has already been deleted during the update/switch operation. */ + err = svn_wc_revert6(ctx->wc_ctx, local_moved_to_abspath, svn_depth_empty, + FALSE, NULL, TRUE, FALSE, + TRUE /*added_keep_local*/, + NULL, NULL, /* no cancellation */ + ctx->notify_func2, ctx->notify_baton2, + scratch_pool); + if (err) + goto unlock_wc; + + err = svn_wc__del_tree_conflict(ctx->wc_ctx, victim_abspath, scratch_pool); + if (err) + goto unlock_wc; + + if (ctx->notify_func2) + { + svn_wc_notify_t *notify; + + notify = svn_wc_create_notify(victim_abspath, svn_wc_notify_resolved_tree, + scratch_pool); + ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); + } + + svn_io_sleep_for_timestamps(local_moved_to_abspath, scratch_pool); + + conflict->resolution_tree = option_id; + +unlock_wc: + err = svn_error_compose_create(err, svn_wc__release_write_lock(ctx->wc_ctx, + lock_abspath, + scratch_pool)); + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* Implements tree_conflict_get_details_func_t. */ +static svn_error_t * +conflict_tree_get_details_update_local_moved_away( + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + struct conflict_tree_update_local_moved_away_details *details; + const char *incoming_old_repos_relpath; + svn_node_kind_t incoming_old_kind; + + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, NULL, &incoming_old_kind, + conflict, scratch_pool, scratch_pool)); + + details = apr_pcalloc(conflict->pool, sizeof(*details)); + + details->wc_move_targets = apr_array_make(conflict->pool, 1, + sizeof(const char *)); + + /* Search the WC for copies of the conflict victim. */ + SVN_ERR(svn_wc__find_copies_of_repos_path(&details->wc_move_targets, + conflict->local_abspath, + incoming_old_repos_relpath, + incoming_old_kind, + ctx->wc_ctx, + conflict->pool, + scratch_pool)); + + conflict->tree_conflict_local_details = details; + + return SVN_NO_ERROR; +} + +static svn_error_t * +get_both_moved_file_paths(const char **incoming_moved_to_abspath, + const char **local_moved_to_abspath, + svn_client_conflict_t *conflict, + apr_pool_t *scratch_pool) +{ + struct conflict_tree_incoming_delete_details *incoming_details; + apr_array_header_t *incoming_move_target_wc_abspaths; + svn_wc_operation_t operation; + + operation = svn_client_conflict_get_operation(conflict); + + *incoming_moved_to_abspath = NULL; + *local_moved_to_abspath = NULL; + + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL || + apr_hash_count(incoming_details->wc_move_targets) == 0) + return SVN_NO_ERROR; + + incoming_move_target_wc_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, + scratch_pool)); + *incoming_moved_to_abspath = + APR_ARRAY_IDX(incoming_move_target_wc_abspaths, + incoming_details->wc_move_target_idx, const char *); + + if (operation == svn_wc_operation_merge) + { + struct conflict_tree_local_missing_details *local_details; + apr_array_header_t *local_moves; + + local_details = conflict->tree_conflict_local_details; + if (local_details == NULL || + apr_hash_count(local_details->wc_move_targets) == 0) + return SVN_NO_ERROR; + + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + *local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, + const char *); + } + else + { + struct conflict_tree_update_local_moved_away_details *local_details; + + local_details = conflict->tree_conflict_local_details; + if (local_details == NULL || + local_details->wc_move_targets->nelts == 0) + return SVN_NO_ERROR; + + *local_moved_to_abspath = + APR_ARRAY_IDX(local_details->wc_move_targets, + local_details->preferred_move_target_idx, + const char *); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +conflict_tree_get_description_update_both_moved_file_merge( + const char **description, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *incoming_moved_to_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *wcroot_abspath; + + *description = NULL; + + SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath, + &local_moved_to_abspath, + conflict, scratch_pool)); + if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); + + operation = svn_client_conflict_get_operation(conflict); + + if (operation == svn_wc_operation_merge) + { + /* In case of a merge, the incoming move has A+ (copied) status... */ + *description = + apr_psprintf( + scratch_pool, + _("apply changes to '%s' and revert addition of '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), + scratch_pool), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), + scratch_pool)); + } + else + { + /* ...but in case of update/switch the local move has "A+" status. */ + *description = + apr_psprintf( + scratch_pool, + _("override incoming move and merge incoming changes from '%s' " + "to '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), + scratch_pool), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +conflict_tree_get_description_update_both_moved_file_move_merge( + const char **description, + svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *incoming_moved_to_abspath; + const char *local_moved_to_abspath; + svn_wc_operation_t operation; + const char *wcroot_abspath; + + *description = NULL; + + SVN_ERR(get_both_moved_file_paths(&incoming_moved_to_abspath, + &local_moved_to_abspath, + conflict, scratch_pool)); + if (incoming_moved_to_abspath == NULL || local_moved_to_abspath == NULL) + return SVN_NO_ERROR; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); + + operation = svn_client_conflict_get_operation(conflict); + + if (operation == svn_wc_operation_merge) + { + SVN_ERR(describe_incoming_move_merge_conflict_option( + description, conflict, ctx, local_moved_to_abspath, + scratch_pool, scratch_pool)); + } + else + { + *description = + apr_psprintf( + scratch_pool, + _("accept incoming move and merge local changes from " + "'%s' to '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), + scratch_pool), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Configure 'both moved file merge' resolution options for a tree conflict. */ +static svn_error_t * +configure_option_both_moved_file_merge(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + svn_wc_operation_t operation; + svn_node_kind_t victim_node_kind; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); + + operation = svn_client_conflict_get_operation(conflict); + victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + &incoming_new_kind, conflict, scratch_pool, + scratch_pool)); + + /* ### what about the switch operation? */ + if (((operation == svn_wc_operation_merge && + victim_node_kind == svn_node_none) || + (operation == svn_wc_operation_update && + victim_node_kind == svn_node_file)) && + incoming_old_kind == svn_node_file && + incoming_new_kind == svn_node_none && + ((operation == svn_wc_operation_merge && + local_change == svn_wc_conflict_reason_missing) || + (operation == svn_wc_operation_update && + local_change == svn_wc_conflict_reason_moved_away)) && + incoming_change == svn_wc_conflict_action_delete) + { + const char *description; + + SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge( + &description, conflict, ctx, conflict->pool, scratch_pool)); + + if (description == NULL) /* details not fetched yet */ + return SVN_NO_ERROR; + + add_resolution_option( + options, conflict, svn_client_conflict_option_both_moved_file_merge, + _("Merge to corresponding local location"), + description, + operation == svn_wc_operation_merge ? + resolve_both_moved_file_text_merge : + resolve_both_moved_file_update_keep_local_move); + + SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge( + &description, conflict, ctx, conflict->pool, scratch_pool)); + + add_resolution_option(options, conflict, + svn_client_conflict_option_both_moved_file_move_merge, + _("Move and merge"), description, + operation == svn_wc_operation_merge ? + resolve_incoming_move_file_text_merge : + resolve_both_moved_file_update_keep_incoming_move); + } + + return SVN_NO_ERROR; +} + +/* Configure 'both moved dir merge' resolution options for a tree conflict. */ +static svn_error_t * +configure_option_both_moved_dir_merge(svn_client_conflict_t *conflict, + svn_client_ctx_t *ctx, + apr_array_header_t *options, + apr_pool_t *scratch_pool) +{ + svn_wc_operation_t operation; + svn_node_kind_t victim_node_kind; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + const char *incoming_old_repos_relpath; + svn_revnum_t incoming_old_pegrev; + svn_node_kind_t incoming_old_kind; + const char *incoming_new_repos_relpath; + svn_revnum_t incoming_new_pegrev; + svn_node_kind_t incoming_new_kind; + const char *wcroot_abspath; + + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, + conflict->local_abspath, scratch_pool, + scratch_pool)); + + operation = svn_client_conflict_get_operation(conflict); + victim_node_kind = svn_client_conflict_tree_get_victim_node_kind(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + SVN_ERR(svn_client_conflict_get_incoming_old_repos_location( + &incoming_old_repos_relpath, &incoming_old_pegrev, + &incoming_old_kind, conflict, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client_conflict_get_incoming_new_repos_location( + &incoming_new_repos_relpath, &incoming_new_pegrev, + &incoming_new_kind, conflict, scratch_pool, + scratch_pool)); + + if (operation == svn_wc_operation_merge && + victim_node_kind == svn_node_none && + incoming_old_kind == svn_node_dir && + incoming_new_kind == svn_node_none && + local_change == svn_wc_conflict_reason_missing && + incoming_change == svn_wc_conflict_action_delete) + { + struct conflict_tree_incoming_delete_details *incoming_details; + struct conflict_tree_local_missing_details *local_details; + const char *description; + apr_array_header_t *local_moves; + const char *local_moved_to_abspath; + const char *incoming_moved_to_abspath; + apr_array_header_t *incoming_move_target_wc_abspaths; + + incoming_details = conflict->tree_conflict_incoming_details; + if (incoming_details == NULL || incoming_details->moves == NULL || + apr_hash_count(incoming_details->wc_move_targets) == 0) + return SVN_NO_ERROR; + + local_details = conflict->tree_conflict_local_details; + if (local_details == NULL || + apr_hash_count(local_details->wc_move_targets) == 0) + return SVN_NO_ERROR; + + local_moves = svn_hash_gets(local_details->wc_move_targets, + local_details->move_target_repos_relpath); + local_moved_to_abspath = + APR_ARRAY_IDX(local_moves, local_details->wc_move_target_idx, + const char *); + + incoming_move_target_wc_abspaths = + svn_hash_gets(incoming_details->wc_move_targets, + get_moved_to_repos_relpath(incoming_details, + scratch_pool)); + incoming_moved_to_abspath = + APR_ARRAY_IDX(incoming_move_target_wc_abspaths, + incoming_details->wc_move_target_idx, const char *); + + description = + apr_psprintf( + scratch_pool, _("apply changes to '%s' and revert addition of '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, local_moved_to_abspath), + scratch_pool), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, incoming_moved_to_abspath), + scratch_pool)); + add_resolution_option( + options, conflict, svn_client_conflict_option_both_moved_dir_merge, + _("Merge to corresponding local location"), + description, resolve_both_moved_dir_merge); + + SVN_ERR(describe_incoming_move_merge_conflict_option( + &description, conflict, ctx, local_moved_to_abspath, + scratch_pool, scratch_pool)); + add_resolution_option(options, conflict, + svn_client_conflict_option_both_moved_dir_move_merge, + _("Move and merge"), description, + resolve_both_moved_dir_move_merge); + } + + return SVN_NO_ERROR; +} + +/* Return a copy of the repos replath candidate list. */ +static svn_error_t * +get_repos_relpath_candidates( + apr_array_header_t **possible_moved_to_repos_relpaths, + apr_hash_t *wc_move_targets, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *sorted_repos_relpaths; + int i; - /* Return a copy of the repos replath candidate list. */ - sorted_repos_relpaths = svn_sort__hash(details->wc_move_targets, + sorted_repos_relpaths = svn_sort__hash(wc_move_targets, svn_sort_compare_items_as_paths, scratch_pool); - *possible_moved_to_repos_relpaths = apr_array_make( - result_pool, - sorted_repos_relpaths->nelts, - sizeof (const char *)); + *possible_moved_to_repos_relpaths = + apr_array_make(result_pool, sorted_repos_relpaths->nelts, + sizeof (const char *)); for (i = 0; i < sorted_repos_relpaths->nelts; i++) { svn_sort__item_t item; @@ -10119,37 +11938,115 @@ svn_client_conflict_option_get_moved_to_repos_relpath_candidates( } svn_error_t * -svn_client_conflict_option_set_moved_to_repos_relpath( +svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( + apr_array_header_t **possible_moved_to_repos_relpaths, svn_client_conflict_option_t *option, - int preferred_move_target_idx, - svn_client_ctx_t *ctx, + apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_client_conflict_t *conflict = option->conflict; - struct conflict_tree_incoming_delete_details *details; const char *victim_abspath; + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + svn_client_conflict_option_id_t id; + + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge && + id != svn_client_conflict_option_both_moved_file_merge && + id != svn_client_conflict_option_both_moved_file_move_merge && + id != svn_client_conflict_option_both_moved_dir_merge && + id != svn_client_conflict_option_both_moved_dir_move_merge) + { + /* We cannot operate on this option. */ + *possible_moved_to_repos_relpaths = NULL; + return SVN_NO_ERROR; + } + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; + + details = conflict->tree_conflict_local_details; + if (details == NULL || + (details->wc_move_targets == NULL && details->wc_siblings == NULL)) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move targets " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + if (details->wc_move_targets) + SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths, + details->wc_move_targets, + result_pool, scratch_pool)); + else + *possible_moved_to_repos_relpaths = NULL; + } + else + { + struct conflict_tree_incoming_delete_details *details; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move targets " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + SVN_ERR(get_repos_relpath_candidates(possible_moved_to_repos_relpaths, + details->wc_move_targets, + result_pool, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_conflict_option_get_moved_to_repos_relpath_candidates( + apr_array_header_t **possible_moved_to_repos_relpaths, + svn_client_conflict_option_t *option, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_get_moved_to_repos_relpath_candidates2( + possible_moved_to_repos_relpaths, option, result_pool, scratch_pool)); +} + +static svn_error_t * +set_wc_move_target(const char **new_hash_key, + apr_hash_t *wc_move_targets, + int preferred_move_target_idx, + const char *victim_abspath, + apr_pool_t *scratch_pool) +{ apr_array_header_t *move_target_repos_relpaths; svn_sort__item_t item; const char *move_target_repos_relpath; apr_hash_index_t *hi; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge); - - victim_abspath = svn_client_conflict_get_local_abspath(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->wc_move_targets == NULL) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Setting a move target requires details " - "for tree conflict at '%s' to be fetched " - "from the repository first"), - svn_dirent_local_style(victim_abspath, - scratch_pool)); - if (preferred_move_target_idx < 0 || - preferred_move_target_idx >= apr_hash_count(details->wc_move_targets)) + preferred_move_target_idx >= apr_hash_count(wc_move_targets)) return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, _("Index '%d' is out of bounds of the possible " "move target list for '%s'"), @@ -10158,15 +12055,14 @@ svn_client_conflict_option_set_moved_to_repos_relpath( scratch_pool)); /* Translate the index back into a hash table key. */ - move_target_repos_relpaths = - svn_sort__hash(details->wc_move_targets, - svn_sort_compare_items_as_paths, - scratch_pool); + move_target_repos_relpaths = svn_sort__hash(wc_move_targets, + svn_sort_compare_items_as_paths, + scratch_pool); item = APR_ARRAY_IDX(move_target_repos_relpaths, preferred_move_target_idx, svn_sort__item_t); move_target_repos_relpath = item.key; /* Find our copy of the hash key and remember the user's preference. */ - for (hi = apr_hash_first(scratch_pool, details->wc_move_targets); + for (hi = apr_hash_first(scratch_pool, wc_move_targets); hi != NULL; hi = apr_hash_next(hi)) { @@ -10174,15 +12070,7 @@ svn_client_conflict_option_set_moved_to_repos_relpath( if (strcmp(move_target_repos_relpath, repos_relpath) == 0) { - details->move_target_repos_relpath = repos_relpath; - /* Update option description. */ - SVN_ERR(describe_incoming_move_merge_conflict_option( - &option->description, - conflict, ctx, - details, - conflict->pool, - scratch_pool)); - + *new_hash_key = repos_relpath; return SVN_NO_ERROR; } } @@ -10196,107 +12084,501 @@ svn_client_conflict_option_set_moved_to_repos_relpath( } svn_error_t * -svn_client_conflict_option_get_moved_to_abspath_candidates( +svn_client_conflict_option_set_moved_to_repos_relpath2( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client_conflict_t *conflict = option->conflict; + const char *victim_abspath; + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + svn_client_conflict_option_id_t id; + + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge && + id != svn_client_conflict_option_both_moved_file_merge && + id != svn_client_conflict_option_both_moved_file_move_merge && + id != svn_client_conflict_option_both_moved_dir_merge && + id != svn_client_conflict_option_both_moved_dir_move_merge) + return SVN_NO_ERROR; /* We cannot operate on this option. Nothing to do. */ + + victim_abspath = svn_client_conflict_get_local_abspath(conflict); + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); + + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; + + details = conflict->tree_conflict_local_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath, + details->wc_move_targets, + preferred_move_target_idx, + victim_abspath, scratch_pool)); + details->wc_move_target_idx = 0; + + /* Update option description. */ + SVN_ERR(conflict_tree_get_description_local_missing( + &option->description, conflict, ctx, + conflict->pool, scratch_pool)); + } + else + { + struct conflict_tree_incoming_delete_details *details; + apr_array_header_t *move_target_wc_abspaths; + const char *moved_to_abspath; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + SVN_ERR(set_wc_move_target(&details->move_target_repos_relpath, + details->wc_move_targets, + preferred_move_target_idx, + victim_abspath, scratch_pool)); + details->wc_move_target_idx = 0; + + /* Update option description. */ + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, + details->wc_move_target_idx, + const char *); + SVN_ERR(describe_incoming_move_merge_conflict_option( + &option->description, + conflict, ctx, + moved_to_abspath, + conflict->pool, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_conflict_option_set_moved_to_repos_relpath( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_set_moved_to_repos_relpath2(option, + preferred_move_target_idx, ctx, scratch_pool)); +} + +svn_error_t * +svn_client_conflict_option_get_moved_to_abspath_candidates2( apr_array_header_t **possible_moved_to_abspaths, svn_client_conflict_option_t *option, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_client_conflict_t *conflict = option->conflict; - struct conflict_tree_incoming_delete_details *details; const char *victim_abspath; - apr_array_header_t *move_target_wc_abspaths; + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; int i; + svn_client_conflict_option_id_t id; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge); + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge && + id != svn_client_conflict_option_both_moved_file_merge && + id != svn_client_conflict_option_both_moved_file_move_merge && + id != svn_client_conflict_option_both_moved_dir_merge && + id != svn_client_conflict_option_both_moved_dir_move_merge) + { + /* We cannot operate on this option. */ + *possible_moved_to_abspaths = NULL; + return NULL; + } victim_abspath = svn_client_conflict_get_local_abspath(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->wc_move_targets == NULL) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Getting a list of possible move targets " - "requires details for tree conflict at '%s' " - "to be fetched from the repository first"), - svn_dirent_local_style(victim_abspath, - scratch_pool)); + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); - move_target_wc_abspaths = - svn_hash_gets(details->wc_move_targets, - get_moved_to_repos_relpath(details, scratch_pool)); + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; - /* Return a copy of the option's move target candidate list. */ - *possible_moved_to_abspaths = - apr_array_make(result_pool, move_target_wc_abspaths->nelts, - sizeof (const char *)); - for (i = 0; i < move_target_wc_abspaths->nelts; i++) + details = conflict->tree_conflict_local_details; + if (details == NULL || + (details->wc_move_targets == NULL && details->wc_siblings == NULL)) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move siblings " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + *possible_moved_to_abspaths = apr_array_make(result_pool, 1, + sizeof (const char *)); + if (details->wc_move_targets) + { + apr_array_header_t *move_target_wc_abspaths; + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + for (i = 0; i < move_target_wc_abspaths->nelts; i++) + { + const char *moved_to_abspath; + + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i, + const char *); + APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, moved_to_abspath); + } + } + + /* ### Siblings are actually 'corresponding nodes', not 'move targets'. + ### But we provide them here to avoid another API function. */ + if (details->wc_siblings) + { + for (i = 0; i < details->wc_siblings->nelts; i++) + { + const char *sibling_abspath; + + sibling_abspath = APR_ARRAY_IDX(details->wc_siblings, i, + const char *); + APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, sibling_abspath); + } + } + } + else if ((operation == svn_wc_operation_update || + operation == svn_wc_operation_switch) && + incoming_change == svn_wc_conflict_action_delete && + local_change == svn_wc_conflict_reason_moved_away) { - const char *moved_to_abspath; + struct conflict_tree_update_local_moved_away_details *details; - moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i, - const char *); - APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = - apr_pstrdup(result_pool, moved_to_abspath); + details = conflict->tree_conflict_local_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move targets " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + /* Return a copy of the option's move target candidate list. */ + *possible_moved_to_abspaths = + apr_array_make(result_pool, details->wc_move_targets->nelts, + sizeof (const char *)); + for (i = 0; i < details->wc_move_targets->nelts; i++) + { + const char *moved_to_abspath; + + moved_to_abspath = APR_ARRAY_IDX(details->wc_move_targets, i, + const char *); + APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, moved_to_abspath); + } + } + else + { + struct conflict_tree_incoming_delete_details *details; + apr_array_header_t *move_target_wc_abspaths; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Getting a list of possible move targets " + "requires details for tree conflict at '%s' " + "to be fetched from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + + /* Return a copy of the option's move target candidate list. */ + *possible_moved_to_abspaths = + apr_array_make(result_pool, move_target_wc_abspaths->nelts, + sizeof (const char *)); + for (i = 0; i < move_target_wc_abspaths->nelts; i++) + { + const char *moved_to_abspath; + + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, i, + const char *); + APR_ARRAY_PUSH(*possible_moved_to_abspaths, const char *) = + apr_pstrdup(result_pool, moved_to_abspath); + } } return SVN_NO_ERROR; } svn_error_t * -svn_client_conflict_option_set_moved_to_abspath( +svn_client_conflict_option_get_moved_to_abspath_candidates( + apr_array_header_t **possible_moved_to_abspaths, + svn_client_conflict_option_t *option, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_get_moved_to_abspath_candidates2( + possible_moved_to_abspaths, option, result_pool, scratch_pool)); +} + +svn_error_t * +svn_client_conflict_option_set_moved_to_abspath2( svn_client_conflict_option_t *option, int preferred_move_target_idx, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_client_conflict_t *conflict = option->conflict; - struct conflict_tree_incoming_delete_details *details; const char *victim_abspath; - apr_array_header_t *move_target_wc_abspaths; + svn_wc_operation_t operation; + svn_wc_conflict_action_t incoming_change; + svn_wc_conflict_reason_t local_change; + svn_client_conflict_option_id_t id; - SVN_ERR_ASSERT(svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_file_text_merge || - svn_client_conflict_option_get_id(option) == - svn_client_conflict_option_incoming_move_dir_merge); + id = svn_client_conflict_option_get_id(option); + if (id != svn_client_conflict_option_incoming_move_file_text_merge && + id != svn_client_conflict_option_incoming_move_dir_merge && + id != svn_client_conflict_option_local_move_file_text_merge && + id != svn_client_conflict_option_local_move_dir_merge && + id != svn_client_conflict_option_sibling_move_file_text_merge && + id != svn_client_conflict_option_sibling_move_dir_merge && + id != svn_client_conflict_option_both_moved_file_merge && + id != svn_client_conflict_option_both_moved_file_move_merge && + id != svn_client_conflict_option_both_moved_dir_merge && + id != svn_client_conflict_option_both_moved_dir_move_merge) + return NULL; /* We cannot operate on this option. Nothing to do. */ victim_abspath = svn_client_conflict_get_local_abspath(conflict); - details = conflict->tree_conflict_incoming_details; - if (details == NULL || details->wc_move_targets == NULL) - return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, - _("Setting a move target requires details " - "for tree conflict at '%s' to be fetched " - "from the repository first"), - svn_dirent_local_style(victim_abspath, - scratch_pool)); + operation = svn_client_conflict_get_operation(conflict); + incoming_change = svn_client_conflict_get_incoming_change(conflict); + local_change = svn_client_conflict_get_local_change(conflict); - move_target_wc_abspaths = - svn_hash_gets(details->wc_move_targets, - get_moved_to_repos_relpath(details, scratch_pool)); + if (operation == svn_wc_operation_merge && + incoming_change == svn_wc_conflict_action_edit && + local_change == svn_wc_conflict_reason_missing) + { + struct conflict_tree_local_missing_details *details; + const char *wcroot_abspath; + const char *preferred_sibling; - if (preferred_move_target_idx < 0 || - preferred_move_target_idx > move_target_wc_abspaths->nelts) - return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, - _("Index '%d' is out of bounds of the possible " - "move target list for '%s'"), - preferred_move_target_idx, - svn_dirent_local_style(victim_abspath, - scratch_pool)); + SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, + ctx->wc_ctx, + conflict->local_abspath, + scratch_pool, + scratch_pool)); - /* Record the user's preference. */ - details->wc_move_target_idx = preferred_move_target_idx; + details = conflict->tree_conflict_local_details; + if (details == NULL || (details->wc_siblings == NULL && + details->wc_move_targets == NULL)) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); - /* Update option description. */ - SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description, - conflict, ctx, - details, - conflict->pool, + if (details->wc_siblings) + { + if (preferred_move_target_idx < 0 || + preferred_move_target_idx > details->wc_siblings->nelts) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Index '%d' is out of bounds of the " + "possible move sibling list for '%s'"), + preferred_move_target_idx, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + /* Record the user's preference. */ + details->preferred_sibling_idx = preferred_move_target_idx; + + /* Update option description. */ + preferred_sibling = APR_ARRAY_IDX(details->wc_siblings, + details->preferred_sibling_idx, + const char *); + option->description = + apr_psprintf( + conflict->pool, _("apply changes to '%s'"), + svn_dirent_local_style( + svn_dirent_skip_ancestor(wcroot_abspath, preferred_sibling), + scratch_pool)); + } + else if (details->wc_move_targets) + { + apr_array_header_t *move_target_wc_abspaths; + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + details->move_target_repos_relpath); + + if (preferred_move_target_idx < 0 || + preferred_move_target_idx > move_target_wc_abspaths->nelts) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Index '%d' is out of bounds of the possible " + "move target list for '%s'"), + preferred_move_target_idx, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + /* Record the user's preference. */ + details->wc_move_target_idx = preferred_move_target_idx; + + /* Update option description. */ + SVN_ERR(conflict_tree_get_description_local_missing( + &option->description, conflict, ctx, + conflict->pool, scratch_pool)); + } + } + else if ((operation == svn_wc_operation_update || + operation == svn_wc_operation_switch) && + incoming_change == svn_wc_conflict_action_delete && + local_change == svn_wc_conflict_reason_moved_away) + { + struct conflict_tree_update_local_moved_away_details *details; + + details = conflict->tree_conflict_local_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + if (preferred_move_target_idx < 0 || + preferred_move_target_idx > details->wc_move_targets->nelts) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Index '%d' is out of bounds of the " + "possible move target list for '%s'"), + preferred_move_target_idx, + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + /* Record the user's preference. */ + details->preferred_move_target_idx = preferred_move_target_idx; + + /* Update option description. */ + if (id == svn_client_conflict_option_both_moved_file_merge) + SVN_ERR(conflict_tree_get_description_update_both_moved_file_merge( + &option->description, conflict, ctx, conflict->pool, + scratch_pool)); + else if (id == svn_client_conflict_option_both_moved_file_move_merge) + SVN_ERR(conflict_tree_get_description_update_both_moved_file_move_merge( + &option->description, conflict, ctx, conflict->pool, scratch_pool)); +#if 0 /* ### TODO: Also handle options for directories! */ + else if (id == svn_client_conflict_option_both_moved_dir_merge) + { + } + else if (id == svn_client_conflict_option_both_moved_dir_move_merge) + { + } +#endif + else + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Unexpected option id '%d'"), id); + } + else + { + struct conflict_tree_incoming_delete_details *details; + apr_array_header_t *move_target_wc_abspaths; + const char *moved_to_abspath; + + details = conflict->tree_conflict_incoming_details; + if (details == NULL || details->wc_move_targets == NULL) + return svn_error_createf(SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE, NULL, + _("Setting a move target requires details " + "for tree conflict at '%s' to be fetched " + "from the repository first"), + svn_dirent_local_style(victim_abspath, + scratch_pool)); + + move_target_wc_abspaths = + svn_hash_gets(details->wc_move_targets, + get_moved_to_repos_relpath(details, scratch_pool)); + + if (preferred_move_target_idx < 0 || + preferred_move_target_idx > move_target_wc_abspaths->nelts) + return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, + _("Index '%d' is out of bounds of the possible " + "move target list for '%s'"), + preferred_move_target_idx, + svn_dirent_local_style(victim_abspath, scratch_pool)); + + /* Record the user's preference. */ + details->wc_move_target_idx = preferred_move_target_idx; + + /* Update option description. */ + moved_to_abspath = APR_ARRAY_IDX(move_target_wc_abspaths, + details->wc_move_target_idx, + const char *); + SVN_ERR(describe_incoming_move_merge_conflict_option(&option->description, + conflict, ctx, + moved_to_abspath, + conflict->pool, + scratch_pool)); + } return SVN_NO_ERROR; } svn_error_t * +svn_client_conflict_option_set_moved_to_abspath( + svn_client_conflict_option_t *option, + int preferred_move_target_idx, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + /* The only difference to API version 2 is an assertion failure if + * an unexpected option is passed. + * We do not emulate this old behaviour since clients written against + * the previous API will just keep working. */ + return svn_error_trace( + svn_client_conflict_option_set_moved_to_abspath2(option, + preferred_move_target_idx, ctx, scratch_pool)); +} + +svn_error_t * svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options, svn_client_conflict_t *conflict, svn_client_ctx_t *ctx, @@ -10349,8 +12631,15 @@ svn_client_conflict_tree_get_resolution_options(apr_array_header_t **options, scratch_pool)); SVN_ERR(configure_option_incoming_dir_merge(conflict, ctx, *options, scratch_pool)); - SVN_ERR(configure_option_local_move_file_merge(conflict, ctx, *options, + SVN_ERR(configure_option_local_move_file_or_dir_merge(conflict, ctx, + *options, + scratch_pool)); + SVN_ERR(configure_option_sibling_move_merge(conflict, ctx, *options, + scratch_pool)); + SVN_ERR(configure_option_both_moved_file_merge(conflict, ctx, *options, scratch_pool)); + SVN_ERR(configure_option_both_moved_dir_merge(conflict, ctx, *options, + scratch_pool)); return SVN_NO_ERROR; } @@ -10443,7 +12732,7 @@ svn_client_conflict_get_recommended_option_id(svn_client_conflict_t *conflict) { return conflict->recommended_option_id; } - + svn_error_t * svn_client_conflict_text_resolve(svn_client_conflict_t *conflict, svn_client_conflict_option_t *option, @@ -10466,7 +12755,7 @@ svn_client_conflict_option_find_by_id(apr_array_header_t *options, { svn_client_conflict_option_t *this_option; svn_client_conflict_option_id_t this_option_id; - + this_option = APR_ARRAY_IDX(options, i, svn_client_conflict_option_t *); this_option_id = svn_client_conflict_option_get_id(this_option); @@ -10895,6 +13184,7 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict, apr_pool_t *scratch_pool) { svn_boolean_t tree_conflicted; + svn_wc_operation_t operation; svn_wc_conflict_action_t incoming_change; svn_wc_conflict_reason_t local_change; @@ -10911,6 +13201,7 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict, conflict->tree_conflict_get_local_description_func = conflict_tree_get_local_description_generic; + operation = svn_client_conflict_get_operation(conflict); incoming_change = svn_client_conflict_get_incoming_change(conflict); local_change = svn_client_conflict_get_local_change(conflict); @@ -10945,6 +13236,12 @@ conflict_type_specific_setup(svn_client_conflict_t *conflict, conflict->tree_conflict_get_local_details_func = conflict_tree_get_details_local_missing; } + else if (local_change == svn_wc_conflict_reason_moved_away && + operation == svn_wc_operation_update /* ### what about switch? */) + { + conflict->tree_conflict_get_local_details_func = + conflict_tree_get_details_update_local_moved_away; + } return SVN_NO_ERROR; } @@ -11019,7 +13316,7 @@ tree_conflict_collector(void *baton, { const char *tc_abspath; apr_pool_t *hash_pool; - + hash_pool = apr_hash_pool_get(cswb->unresolved_tree_conflicts); tc_abspath = apr_pstrdup(hash_pool, notify->path); svn_hash_sets(cswb->unresolved_tree_conflicts, tc_abspath, ""); @@ -11027,7 +13324,7 @@ tree_conflict_collector(void *baton, } } -/* +/* * Record a tree conflict resolution failure due to error condition ERR * in the RESOLVE_LATER hash table. If the hash table is not available * (meaning the caller does not wish to retry resolution later), or if @@ -11190,7 +13487,7 @@ svn_client_conflict_walk(const char *local_abspath, if (err) break; } - + if (!err && !cswb.resolved_a_tree_conflict && tc_abspath && apr_hash_count(cswb.unresolved_tree_conflicts)) { diff --git a/subversion/libsvn_client/copy.c b/subversion/libsvn_client/copy.c index b2e3a44d797b9..b3f2bacc7c8d2 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_abspath_or_url, - dst_abspath, - &pair->src_peg_revision, - &pair->src_op_revision, - svn_depth_infinity, - FALSE /* make_parents */, - TRUE /* already_locked */, - ctx, pool)); - - return SVN_NO_ERROR; - } - - if (pin_externals) - { - apr_hash_t *pinned_externals; - apr_hash_index_t *hi; - apr_pool_t *iterpool; const char *repos_root_url; apr_hash_t *new_externals; apr_hash_t *new_depths; SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool)); - SVN_ERR(resolve_pinned_externals(&pinned_externals, - externals_to_pin, pair, - ra_session, repos_root_url, - ctx, pool, pool)); - iterpool = svn_pool_create(pool); - for (hi = apr_hash_first(pool, pinned_externals); - hi; - hi = apr_hash_next(hi)) + if (pin_externals) { - const char *dst_relpath = apr_hash_this_key(hi); - svn_string_t *externals_propval = apr_hash_this_val(hi); - const char *local_abspath; + apr_hash_t *pinned_externals; + apr_hash_index_t *hi; + apr_pool_t *iterpool; + + SVN_ERR(resolve_pinned_externals(&pinned_externals, + externals_to_pin, pair, + ra_session, repos_root_url, + ctx, pool, pool)); + + iterpool = svn_pool_create(pool); + for (hi = apr_hash_first(pool, pinned_externals); + hi; + hi = apr_hash_next(hi)) + { + const char *dst_relpath = apr_hash_this_key(hi); + svn_string_t *externals_propval = apr_hash_this_val(hi); + const char *local_abspath; - svn_pool_clear(iterpool); + svn_pool_clear(iterpool); - local_abspath = svn_dirent_join(pair->dst_abspath_or_url, - dst_relpath, iterpool); - /* ### use a work queue? */ - SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, - SVN_PROP_EXTERNALS, externals_propval, - svn_depth_empty, TRUE /* skip_checks */, - NULL /* changelist_filter */, - ctx->cancel_func, ctx->cancel_baton, - NULL, NULL, /* no extra notification */ - iterpool)); + local_abspath = svn_dirent_join(pair->dst_abspath_or_url, + dst_relpath, iterpool); + /* ### use a work queue? */ + SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, + SVN_PROP_EXTERNALS, externals_propval, + svn_depth_empty, TRUE /* skip_checks */, + NULL /* changelist_filter */, + ctx->cancel_func, ctx->cancel_baton, + NULL, NULL, /* no extra notification */ + iterpool)); + } + svn_pool_destroy(iterpool); } /* Now update all externals in the newly created copy. */ @@ -2509,65 +2789,30 @@ repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep, ctx->wc_ctx, dst_abspath, svn_depth_infinity, - iterpool, iterpool)); + pool, pool)); SVN_ERR(svn_client__handle_externals(new_externals, new_depths, repos_root_url, dst_abspath, svn_depth_infinity, timestamp_sleep, ra_session, - ctx, iterpool)); - svn_pool_destroy(iterpool); + ctx, pool)); } - } /* end directory case */ - - else if (pair->src_kind == svn_node_file) - { - apr_hash_t *new_props; - const char *src_rel; - svn_stream_t *new_base_contents = svn_stream_buffered(pool); - - SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel, - pair->src_abspath_or_url, - pool)); - /* Fetch the file content. While doing so, resolve pair->src_revnum - * to an actual revision number if it's 'invalid' meaning 'head'. */ - SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum, - new_base_contents, - &pair->src_revnum, &new_props, pool)); - - if (new_props && ! same_repositories) - svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL); - - *timestamp_sleep = TRUE; - - SVN_ERR(svn_wc_add_repos_file4( - ctx->wc_ctx, dst_abspath, - new_base_contents, NULL, new_props, NULL, - same_repositories ? pair->src_abspath_or_url : NULL, - same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM, - ctx->cancel_func, ctx->cancel_baton, - pool)); } - /* Record the implied mergeinfo (before the notification callback - is invoked for the root node). */ - SVN_ERR(svn_client__get_repos_mergeinfo( - &src_mergeinfo, ra_session, - pair->src_abspath_or_url, pair->src_revnum, - svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool)); - SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool)); - - /* Do our own notification for the root node, even if we could possibly - have delegated it. See also issue #1552. - - ### Maybe this notification should mention the mergeinfo change. */ - if (ctx->notify_func2) + if (same_repositories) { - svn_wc_notify_t *notify = svn_wc_create_notify( - dst_abspath, svn_wc_notify_add, pool); - notify->kind = pair->src_kind; - ctx->notify_func2(ctx->notify_baton2, notify, pool); + /* Record the implied mergeinfo. */ + SVN_ERR(svn_client__get_repos_mergeinfo(&src_mergeinfo, ra_session, + pair->src_abspath_or_url, + pair->src_revnum, + svn_mergeinfo_inherited, + TRUE /*squelch_incapable*/, + pool)); + SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool)); + + /* ### Maybe the notification should mention this mergeinfo change. */ + /* ### Maybe we should do this during rather than after the copy. */ } return SVN_NO_ERROR; @@ -2585,38 +2830,8 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep, apr_pool_t *scratch_pool) { int i; - svn_boolean_t same_repositories; apr_pool_t *iterpool = svn_pool_create(scratch_pool); - /* We've already checked for physical obstruction by a working file. - But there could also be logical obstruction by an entry whose - working file happens to be missing.*/ - SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */, - ctx, scratch_pool, iterpool)); - - /* Decide whether the two repositories are the same or not. */ - { - const char *parent_abspath; - const char *src_uuid, *dst_uuid; - - /* Get the repository uuid of SRC_URL */ - SVN_ERR(svn_ra_get_uuid2(ra_session, &src_uuid, iterpool)); - - /* Get repository uuid of dst's parent directory, since dst may - not exist. ### TODO: we should probably walk up the wc here, - in case the parent dir has an imaginary URL. */ - if (copy_pairs->nelts == 1) - parent_abspath = svn_dirent_dirname(top_dst_abspath, scratch_pool); - else - parent_abspath = top_dst_abspath; - - SVN_ERR(svn_client_get_repos_root(NULL /* root_url */, &dst_uuid, - parent_abspath, ctx, - iterpool, iterpool)); - /* ### Also check repos_root_url? */ - same_repositories = (strcmp(src_uuid, dst_uuid) == 0); - } - /* Perform the move for each of the copy_pairs. */ for (i = 0; i < copy_pairs->nelts; i++) { @@ -2629,7 +2844,6 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep, SVN_ERR(repos_to_wc_copy_single(timestamp_sleep, APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *), - same_repositories, ignore_externals, pin_externals, externals_to_pin, ra_session, ctx, iterpool)); @@ -2642,7 +2856,6 @@ repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep, static svn_error_t * repos_to_wc_copy(svn_boolean_t *timestamp_sleep, const apr_array_header_t *copy_pairs, - svn_boolean_t make_parents, svn_boolean_t ignore_externals, svn_boolean_t pin_externals, const apr_hash_t *externals_to_pin, @@ -2696,8 +2909,6 @@ repos_to_wc_copy(svn_boolean_t *timestamp_sleep, { svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *); - svn_node_kind_t dst_parent_kind, dst_kind; - const char *dst_parent; const char *src_rel; svn_pool_clear(iterpool); @@ -2721,33 +2932,6 @@ repos_to_wc_copy(svn_boolean_t *timestamp_sleep, _("Path '%s' not found in head revision"), pair->src_abspath_or_url); } - - /* Figure out about dst. */ - SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind, - iterpool)); - if (dst_kind != svn_node_none) - { - return svn_error_createf( - SVN_ERR_ENTRY_EXISTS, NULL, - _("Path '%s' already exists"), - svn_dirent_local_style(pair->dst_abspath_or_url, pool)); - } - - /* Make sure the destination parent is a directory and produce a clear - error message if it is not. */ - dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool); - SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool)); - if (make_parents && dst_parent_kind == svn_node_none) - { - SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx, - iterpool)); - } - else if (dst_parent_kind != svn_node_dir) - { - return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, - _("Path '%s' is not a directory"), - svn_dirent_local_style(dst_parent, pool)); - } } svn_pool_destroy(iterpool); @@ -3070,8 +3254,9 @@ try_copy(svn_boolean_t *timestamp_sleep, /* Now, call the right handler for the operation. */ if ((! srcs_are_urls) && (! dst_is_url)) { - SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move, - metadata_only, ctx, pool, pool)); + SVN_ERR(verify_wc_srcs(copy_pairs, ctx, pool)); + SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, + ctx, pool, pool)); /* Copy or move all targets. */ if (is_move) @@ -3101,9 +3286,13 @@ try_copy(svn_boolean_t *timestamp_sleep, } else if ((srcs_are_urls) && (! dst_is_url)) { + SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, + FALSE, FALSE /* metadata_only */, + ctx, pool, pool)); + return svn_error_trace( repos_to_wc_copy(timestamp_sleep, - copy_pairs, make_parents, ignore_externals, + copy_pairs, ignore_externals, pin_externals, externals_to_pin, ctx, pool)); } else diff --git a/subversion/libsvn_client/copy_foreign.c b/subversion/libsvn_client/copy_foreign.c deleted file mode 100644 index cfe6aea058461..0000000000000 --- 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 943cdd9a44963..29a3395af2ea7 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 dc20b2772286e..b8e202609e746 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 ba6e91eb2e7bc..f019294f05c01 100644 --- a/subversion/libsvn_client/diff.c +++ b/subversion/libsvn_client/diff.c @@ -23,6 +23,9 @@ /* ==================================================================== */ +/* We define this here to remove any further warnings about the usage of + experimental functions in this file. */ +#define SVN_EXPERIMENTAL /*** Includes. ***/ @@ -48,6 +51,7 @@ #include "svn_subst.h" #include "client.h" +#include "private/svn_client_shelf.h" #include "private/svn_wc_private.h" #include "private/svn_diff_private.h" #include "private/svn_subr_private.h" @@ -66,6 +70,31 @@ _("Path '%s' must be an immediate child of " \ "the directory '%s'"), path, relative_to_dir) +/* State provided by the diff drivers; used by the diff writer */ +typedef struct diff_driver_info_t +{ + /* The anchor to prefix before wc paths */ + const char *anchor; + + /* Relative path of ra session from repos_root_url. + + Used only in printing git diff headers. The repository-root-relative + path of ... ### what user-visible property of the diff? */ + const char *session_relpath; + + /* Used only in printing git diff headers. Used to find the + repository-root-relative path of a WC path. */ + svn_wc_context_t *wc_ctx; + + /* The original targets passed to the diff command. We may need + these to construct distinctive diff labels when comparing the + same relative path in the same revision, under different anchors + (for example, when comparing a trunk against a branch). */ + const char *orig_path_1; + const char *orig_path_2; +} diff_driver_info_t; + + /* Calculate the repository relative path of DIFF_RELPATH, using * SESSION_RELPATH and WC_CTX, and return the result in *REPOS_RELPATH. * ORIG_TARGET is the related original target passed to the diff command, @@ -120,25 +149,47 @@ make_repos_relpath(const char **repos_relpath, return SVN_NO_ERROR; } -/* Adjust *INDEX_PATH, *ORIG_PATH_1 and *ORIG_PATH_2, representing the changed - * node and the two original targets passed to the diff command, to handle the - * case when we're dealing with different anchors. RELATIVE_TO_DIR is the - * directory the diff target should be considered relative to. - * ANCHOR is the local path where the diff editor is anchored. The resulting - * values are allocated in RESULT_POOL and temporary allocations are performed - * in SCRATCH_POOL. */ +/* Adjust paths to handle the case when we're dealing with different anchors. + * + * Set *INDEX_PATH to the new relative path. Set *LABEL_PATH1 and + * *LABEL_PATH2 to that path annotated with the unique parts of ORIG_PATH_1 + * and ORIG_PATH_2 respectively, like this: + * + * INDEX_PATH: "path" + * LABEL_PATH1: "path\t(.../branches/branch1)" + * LABEL_PATH2: "path\t(.../trunk)" + * + * Make the output paths relative to RELATIVE_TO_DIR (if not null) by + * removing it from the beginning of (ANCHOR + RELPATH). + * + * ANCHOR (if not null) is the local path where the diff editor is anchored. + * RELPATH is the path to the changed node within the diff editor, so + * relative to ANCHOR. + * + * RELATIVE_TO_DIR and ANCHOR are of the same form -- either absolute local + * paths or relative paths relative to the same base. + * + * ORIG_PATH_1 and ORIG_PATH_2 represent the two original target paths or + * URLs passed to the diff command. + * + * Allocate results in RESULT_POOL (or as a pointer to RELPATH) and + * temporary data in SCRATCH_POOL. + */ static svn_error_t * adjust_paths_for_diff_labels(const char **index_path, - const char **orig_path_1, - const char **orig_path_2, + const char **label_path1, + const char **label_path2, const char *relative_to_dir, const char *anchor, + const char *relpath, + const char *orig_path_1, + const char *orig_path_2, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { - const char *new_path = *index_path; - const char *new_path1 = *orig_path_1; - const char *new_path2 = *orig_path_2; + const char *new_path = relpath; + const char *new_path1 = orig_path_1; + const char *new_path2 = orig_path_2; if (anchor) new_path = svn_dirent_join(anchor, new_path, result_pool); @@ -177,6 +228,7 @@ adjust_paths_for_diff_labels(const char **index_path, /* ### BH: We can now just construct the repos_relpath, etc. as the anchor is available. See also make_repos_relpath() */ + /* Remove the common prefix of NEW_PATH1 and NEW_PATH2. */ is_url1 = svn_path_is_url(new_path1); is_url2 = svn_path_is_url(new_path2); @@ -220,8 +272,8 @@ adjust_paths_for_diff_labels(const char **index_path, new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2); *index_path = new_path; - *orig_path_1 = new_path1; - *orig_path_2 = new_path2; + *label_path1 = new_path1; + *label_path2 = new_path2; return SVN_NO_ERROR; } @@ -383,28 +435,33 @@ maybe_print_mode_change(svn_stream_t *os, } /* Print a git diff header showing the OPERATION to the stream OS using - * HEADER_ENCODING. Return suitable diff labels for the git diff in *LABEL1 - * and *LABEL2. REPOS_RELPATH1 and REPOS_RELPATH2 are relative to reposroot. - * are the paths passed to the original diff command. REV1 and REV2 are - * revisions being diffed. COPYFROM_PATH and COPYFROM_REV indicate where the + * HEADER_ENCODING. + * + * Return suitable diff labels for the git diff in *LABEL1 and *LABEL2. + * + * REV1 and REV2 are the revisions being diffed. + * COPYFROM_PATH and COPYFROM_REV indicate where the * diffed item was copied from. * Use SCRATCH_POOL for temporary allocations. */ static svn_error_t * print_git_diff_header(svn_stream_t *os, const char **label1, const char **label2, svn_diff_operation_kind_t operation, - const char *repos_relpath1, - const char *repos_relpath2, svn_revnum_t rev1, svn_revnum_t rev2, + const char *diff_relpath, const char *copyfrom_path, svn_revnum_t copyfrom_rev, apr_hash_t *left_props, apr_hash_t *right_props, const char *git_index_shas, const char *header_encoding, + const diff_driver_info_t *ddi, apr_pool_t *scratch_pool) { + const char *repos_relpath1; + const char *repos_relpath2; + const char *copyfrom_repos_relpath = NULL; svn_boolean_t exec_bit1 = (svn_prop_get_value(left_props, SVN_PROP_EXECUTABLE) != NULL); svn_boolean_t exec_bit2 = (svn_prop_get_value(right_props, @@ -414,6 +471,26 @@ print_git_diff_header(svn_stream_t *os, svn_boolean_t symlink_bit2 = (svn_prop_get_value(right_props, SVN_PROP_SPECIAL) != NULL); + SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, + ddi->orig_path_1, + ddi->session_relpath, + ddi->wc_ctx, + ddi->anchor, + scratch_pool, scratch_pool)); + SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, + ddi->orig_path_2, + ddi->session_relpath, + ddi->wc_ctx, + ddi->anchor, + scratch_pool, scratch_pool)); + if (copyfrom_path) + SVN_ERR(make_repos_relpath(©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 +564,45 @@ print_git_diff_header(svn_stream_t *os, return SVN_NO_ERROR; } +/* Print the "Index:" and "=====" lines. + * Show the paths in platform-independent format ('/' separators) + */ +static svn_error_t * +print_diff_index_header(svn_stream_t *outstream, + const char *header_encoding, + const char *index_path, + const char *suffix, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_stream_printf_from_utf8(outstream, + header_encoding, scratch_pool, + "Index: %s%s" APR_EOL_STR + SVN_DIFF__EQUAL_STRING APR_EOL_STR, + index_path, suffix)); + return SVN_NO_ERROR; +} + /* A helper func that writes out verbal descriptions of property diffs to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was - passed to svn_client_diff6(), which is probably stdout. + passed to svn_client_diff7(), which is probably stdout. ### FIXME needs proper docstring If USE_GIT_DIFF_FORMAT is TRUE, pring git diff headers, which always - show paths relative to the repository root. RA_SESSION and WC_CTX are - needed to normalize paths relative the repository root, and are ignored - if USE_GIT_DIFF_FORMAT is FALSE. - - ANCHOR is the local path where the diff editor is anchored. */ + show paths relative to the repository root. DDI->session_relpath and + DDI->wc_ctx are needed to normalize paths relative the repository root, + and are ignored if USE_GIT_DIFF_FORMAT is FALSE. + + If @a pretty_print_mergeinfo is true, then describe 'svn:mergeinfo' + property changes in a human-readable form that says what changes were + merged or reverse merged; otherwise (or if the mergeinfo property values + don't parse correctly) display them just like any other property. + */ static svn_error_t * display_prop_diffs(const apr_array_header_t *propchanges, apr_hash_t *left_props, apr_hash_t *right_props, const char *diff_relpath, - const char *anchor, - const char *orig_path1, - const char *orig_path2, svn_revnum_t rev1, svn_revnum_t rev2, const char *encoding, @@ -514,32 +610,29 @@ display_prop_diffs(const apr_array_header_t *propchanges, const char *relative_to_dir, svn_boolean_t show_diff_header, svn_boolean_t use_git_diff_format, - const char *ra_session_relpath, + svn_boolean_t pretty_print_mergeinfo, + const diff_driver_info_t *ddi, svn_cancel_func_t cancel_func, void *cancel_baton, - svn_wc_context_t *wc_ctx, apr_pool_t *scratch_pool) { const char *repos_relpath1 = NULL; - const char *repos_relpath2 = NULL; - const char *index_path = diff_relpath; - const char *adjusted_path1 = orig_path1; - const char *adjusted_path2 = orig_path2; + const char *index_path; + const char *label_path1, *label_path2; if (use_git_diff_format) { - SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, orig_path1, - ra_session_relpath, wc_ctx, anchor, - scratch_pool, scratch_pool)); - SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, orig_path2, - ra_session_relpath, wc_ctx, anchor, + SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, ddi->orig_path_1, + ddi->session_relpath, ddi->wc_ctx, ddi->anchor, scratch_pool, scratch_pool)); } /* If we're creating a diff on the wc root, path would be empty. */ - SVN_ERR(adjust_paths_for_diff_labels(&index_path, &adjusted_path1, - &adjusted_path2, - relative_to_dir, anchor, + SVN_ERR(adjust_paths_for_diff_labels(&index_path, + &label_path1, &label_path2, + relative_to_dir, ddi->anchor, + diff_relpath, + ddi->orig_path_1, ddi->orig_path_2, scratch_pool, scratch_pool)); if (show_diff_header) @@ -547,27 +640,21 @@ display_prop_diffs(const apr_array_header_t *propchanges, const char *label1; const char *label2; - label1 = diff_label(adjusted_path1, rev1, scratch_pool); - label2 = diff_label(adjusted_path2, rev2, scratch_pool); - - /* ### Should we show the paths in platform specific format, - * ### diff_content_changed() does not! */ + label1 = diff_label(label_path1, rev1, scratch_pool); + label2 = diff_label(label_path2, rev2, scratch_pool); - SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool, - "Index: %s" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); + SVN_ERR(print_diff_index_header(outstream, encoding, + index_path, "", scratch_pool)); if (use_git_diff_format) SVN_ERR(print_git_diff_header(outstream, &label1, &label2, svn_diff_op_modified, - repos_relpath1, repos_relpath2, - rev1, rev2, NULL, - SVN_INVALID_REVNUM, - left_props, - right_props, + rev1, rev2, + diff_relpath, + NULL, SVN_INVALID_REVNUM, + left_props, right_props, NULL, - encoding, scratch_pool)); + encoding, ddi, scratch_pool)); /* --- label1 * +++ label2 */ @@ -588,7 +675,7 @@ display_prop_diffs(const apr_array_header_t *propchanges, SVN_ERR(svn_diff__display_prop_diffs( outstream, encoding, propchanges, left_props, - TRUE /* pretty_print_mergeinfo */, + pretty_print_mergeinfo, -1 /* context_size */, cancel_func, cancel_baton, scratch_pool)); @@ -599,24 +686,6 @@ display_prop_diffs(const apr_array_header_t *propchanges, /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/ -/* State provided by the diff drivers; used by the diff writer */ -typedef struct diff_driver_info_t -{ - /* The anchor to prefix before wc paths */ - const char *anchor; - - /* Relative path of ra session from repos_root_url */ - const char *session_relpath; - - /* The original targets passed to the diff command. We may need - these to construct distinctive diff labels when comparing the - same relative path in the same revision, under different anchors - (for example, when comparing a trunk against a branch). */ - const char *orig_path_1; - const char *orig_path_2; -} diff_driver_info_t; - - /* Diff writer state */ typedef struct diff_writer_info_t { @@ -668,11 +737,12 @@ typedef struct diff_writer_info_t /* Whether to ignore copyfrom information when showing adds */ svn_boolean_t show_copies_as_adds; + /* Whether to show mergeinfo prop changes in human-readable form */ + svn_boolean_t pretty_print_mergeinfo; + /* Empty files for creating diffs or NULL if not used yet */ const char *empty_file; - svn_wc_context_t *wc_ctx; - svn_cancel_func_t cancel_func; void *cancel_baton; @@ -708,9 +778,6 @@ diff_props_changed(const char *diff_relpath, * dir_props_changed(). */ SVN_ERR(display_prop_diffs(props, left_props, right_props, diff_relpath, - dwi->ddi.anchor, - dwi->ddi.orig_path_1, - dwi->ddi.orig_path_2, rev1, rev2, dwi->header_encoding, @@ -718,10 +785,10 @@ diff_props_changed(const char *diff_relpath, dwi->relative_to_dir, show_diff_header, dwi->use_git_diff_format, - dwi->ddi.session_relpath, + dwi->pretty_print_mergeinfo, + &dwi->ddi, dwi->cancel_func, dwi->cancel_baton, - dwi->wc_ctx, scratch_pool)); } @@ -785,9 +852,12 @@ transform_link_to_git(const char **new_tmpfile, } /* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and - REV2 are used in the headers to indicate the file and revisions. If either - MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff, - but instead print a warning message. + REV2 are used in the headers to indicate the file and revisions. + + If either side has an svn:mime-type property that indicates 'binary' + content, then if DWI->force_binary is set, attempt to produce the + diff in the usual way, otherwise produce a 'GIT binary diff' in git mode + or print a warning message in non-git mode. If FORCE_DIFF is TRUE, always write a diff, even for empty diffs. @@ -812,9 +882,8 @@ diff_content_changed(svn_boolean_t *wrote_header, svn_stream_t *outstream = dwi->outstream; const char *label1, *label2; svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE; - const char *index_path = diff_relpath; - const char *path1 = dwi->ddi.orig_path_1; - const char *path2 = dwi->ddi.orig_path_2; + const char *index_path; + const char *label_path1, *label_path2; const char *mimetype1 = svn_prop_get_value(left_props, SVN_PROP_MIME_TYPE); const char *mimetype2 = svn_prop_get_value(right_props, SVN_PROP_MIME_TYPE); const char *index_shas = NULL; @@ -824,12 +893,15 @@ diff_content_changed(svn_boolean_t *wrote_header, return SVN_NO_ERROR; /* Generate the diff headers. */ - SVN_ERR(adjust_paths_for_diff_labels(&index_path, &path1, &path2, + SVN_ERR(adjust_paths_for_diff_labels(&index_path, + &label_path1, &label_path2, rel_to_dir, dwi->ddi.anchor, + diff_relpath, + dwi->ddi.orig_path_1, dwi->ddi.orig_path_2, scratch_pool, scratch_pool)); - label1 = diff_label(path1, rev1, scratch_pool); - label2 = diff_label(path2, rev2, scratch_pool); + label1 = diff_label(label_path1, rev1, scratch_pool); + label2 = diff_label(label_path2, rev2, scratch_pool); /* Possible easy-out: if either mime-type is binary and force was not specified, don't attempt to generate a viewable diff at all. @@ -869,12 +941,8 @@ diff_content_changed(svn_boolean_t *wrote_header, if (! dwi->force_binary && (mt1_binary || mt2_binary)) { /* Print out the diff header. */ - SVN_ERR(svn_stream_printf_from_utf8(outstream, - dwi->header_encoding, scratch_pool, - "Index: %s" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); - + SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, + index_path, "", scratch_pool)); *wrote_header = TRUE; /* ### Print git diff headers. */ @@ -883,40 +951,17 @@ diff_content_changed(svn_boolean_t *wrote_header, { svn_stream_t *left_stream; svn_stream_t *right_stream; - const char *repos_relpath1; - const char *repos_relpath2; - const char *copyfrom_repos_relpath = NULL; - - SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, - dwi->ddi.orig_path_1, - dwi->ddi.session_relpath, - dwi->wc_ctx, - dwi->ddi.anchor, - scratch_pool, scratch_pool)); - SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, - dwi->ddi.orig_path_2, - dwi->ddi.session_relpath, - dwi->wc_ctx, - dwi->ddi.anchor, - scratch_pool, scratch_pool)); - if (copyfrom_path) - SVN_ERR(make_repos_relpath(©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 +1018,9 @@ diff_content_changed(svn_boolean_t *wrote_header, int exitcode; /* Print out the diff header. */ - SVN_ERR(svn_stream_printf_from_utf8(outstream, - dwi->header_encoding, scratch_pool, - "Index: %s" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); + SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, + index_path, "", scratch_pool)); + *wrote_header = TRUE; /* ### Do we want to add git diff headers here too? I'd say no. The * ### 'Index' and '===' line is something subversion has added. The rest @@ -1030,10 +1073,6 @@ diff_content_changed(svn_boolean_t *wrote_header, scratch_pool), NULL, NULL, scratch_pool)); } - - /* If we have printed a diff for this path, mark it as visited. */ - if (exitcode == 1) - *wrote_header = TRUE; } else /* use libsvn_diff to generate the diff */ { @@ -1048,49 +1087,22 @@ diff_content_changed(svn_boolean_t *wrote_header, || svn_diff_contains_diffs(diff)) { /* Print out the diff header. */ - SVN_ERR(svn_stream_printf_from_utf8(outstream, - dwi->header_encoding, scratch_pool, - "Index: %s" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); + SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, + index_path, "", scratch_pool)); + *wrote_header = TRUE; if (dwi->use_git_diff_format) { - const char *repos_relpath1; - const char *repos_relpath2; - const char *copyfrom_repos_relpath = NULL; - - SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, - dwi->ddi.orig_path_1, - dwi->ddi.session_relpath, - dwi->wc_ctx, - dwi->ddi.anchor, - scratch_pool, scratch_pool)); - SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, - dwi->ddi.orig_path_2, - dwi->ddi.session_relpath, - dwi->wc_ctx, - dwi->ddi.anchor, - scratch_pool, scratch_pool)); - if (copyfrom_path) - SVN_ERR(make_repos_relpath(©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 +1114,6 @@ diff_content_changed(svn_boolean_t *wrote_header, dwi->options.for_internal->context_size, dwi->cancel_func, dwi->cancel_baton, scratch_pool)); - - /* If we have printed a diff for this path, mark it as visited. */ - if (dwi->use_git_diff_format || svn_diff_contains_diffs(diff)) - *wrote_header = TRUE; } } @@ -1180,11 +1188,9 @@ diff_file_added(const char *relpath, index_path = svn_dirent_join(dwi->ddi.anchor, relpath, scratch_pool); - SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream, - dwi->header_encoding, scratch_pool, - "Index: %s (added)" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); + SVN_ERR(print_diff_index_header(dwi->outstream, dwi->header_encoding, + index_path, " (added)", + scratch_pool)); wrote_header = TRUE; return SVN_NO_ERROR; } @@ -1270,11 +1276,9 @@ diff_file_deleted(const char *relpath, index_path = svn_dirent_join(dwi->ddi.anchor, relpath, scratch_pool); - SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream, - dwi->header_encoding, scratch_pool, - "Index: %s (deleted)" APR_EOL_STR - SVN_DIFF__EQUAL_STRING APR_EOL_STR, - index_path)); + SVN_ERR(print_diff_index_header(dwi->outstream, dwi->header_encoding, + index_path, " (deleted)", + scratch_pool)); } else { @@ -1537,11 +1541,22 @@ check_diff_target_exists(const char *url, /** Prepare a repos repos diff between PATH_OR_URL1 and * PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2. - * Return URLs and peg revisions in *URL1, *REV1 and in *URL2, *REV2. - * Return suitable anchors in *ANCHOR1 and *ANCHOR2, and targets in - * *TARGET1 and *TARGET2, based on *URL1 and *URL2. - * Indicate the corresponding node kinds in *KIND1 and *KIND2, and verify + * + * Return the resolved URL and peg revision pairs in *URL1, *REV1 and in + * *URL2, *REV2. + * + * Return suitable anchor URL and target pairs in *ANCHOR1, *TARGET1 and + * in *ANCHOR2, *TARGET2, corresponding to *URL1 and *URL2. + * + * (The choice of anchor URLs here appears to be: start with *URL1, *URL2; + * then take the parent dir on both sides, unless either of *URL1 or *URL2 + * is the repository root or the parent dir of *URL1 is unreadable.) + * + * Set *KIND1 and *KIND2 to the node kinds of *URL1 and *URL2, and verify * that at least one of the diff targets exists. + * + * Set *RA_SESSION to an RA session parented at the URL *ANCHOR1. + * * Use client context CTX. Do all allocations in POOL. */ static svn_error_t * diff_prepare_repos_repos(const char **url1, @@ -1766,8 +1781,8 @@ diff_prepare_repos_repos(const char **url1, /* A Theoretical Note From Ben, regarding do_diff(). - This function is really svn_client_diff6(). If you read the public - API description for svn_client_diff6(), it sounds quite Grand. It + This function is really svn_client_diff7(). If you read the public + API description for svn_client_diff7(), it sounds quite Grand. It sounds really generalized and abstract and beautiful: that it will diff any two paths, be they working-copy paths or URLs, at any two revisions. @@ -1791,7 +1806,7 @@ diff_prepare_repos_repos(const char **url1, pigeonholed into one of these use-cases, we currently bail with a friendly apology. - Perhaps someday a brave soul will truly make svn_client_diff6() + Perhaps someday a brave soul will truly make svn_client_diff7() perfectly general. For now, we live with the 90% case. Certainly, the commandline client only calls this function in legal ways. When there are other users of svn_client.h, maybe this will become @@ -1804,7 +1819,7 @@ static svn_error_t * unsupported_diff_error(svn_error_t *child_err) { return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err, - _("Sorry, svn_client_diff6 was called in a way " + _("Sorry, svn_client_diff7 was called in a way " "that is not yet supported")); } @@ -1813,12 +1828,14 @@ unsupported_diff_error(svn_error_t *child_err) PATH1 and PATH2 are both working copy paths. REVISION1 and REVISION2 are their respective revisions. - All other options are the same as those passed to svn_client_diff6(). */ + For now, require PATH1=PATH2, REVISION1='base', REVISION2='working', + otherwise return an error. + + Anchor DIFF_PROCESSOR at the requested diff targets. + + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * -diff_wc_wc(const char **root_relpath, - svn_boolean_t *root_is_dir, - struct diff_driver_info_t *ddi, - const char *path1, +diff_wc_wc(const char *path1, const svn_opt_revision_t *revision1, const char *path2, const svn_opt_revision_t *revision2, @@ -1844,24 +1861,12 @@ diff_wc_wc(const char **root_relpath, && (revision2->kind == svn_opt_revision_working)))) return unsupported_diff_error( svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, - _("Only diffs between a path's text-base " - "and its working files are supported at this time" + _("A non-URL diff at this time must be either from " + "a path's base to the same path's working version " + "or between the working versions of two paths" ))); - if (ddi) - { - svn_node_kind_t kind; - - SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1, - TRUE, FALSE, scratch_pool)); - - if (kind != svn_node_dir) - ddi->anchor = svn_dirent_dirname(path1, scratch_pool); - else - ddi->anchor = path1; - } - - SVN_ERR(svn_wc__diff7(root_relpath, root_is_dir, + SVN_ERR(svn_wc__diff7(TRUE, ctx->wc_ctx, abspath1, depth, ignore_ancestry, changelists, diff_processor, @@ -1878,11 +1883,31 @@ diff_wc_wc(const char **root_relpath, and the actual two paths compared are determined by following copy history from PATH_OR_URL2. - All other options are the same as those passed to svn_client_diff6(). */ + If DDI is null, anchor the DIFF_PROCESSOR at the requested diff + targets. (This case is used by diff-summarize.) + + If DDI is non-null: Set DDI->orig_path_* to the two diff target URLs as + resolved at the given revisions; set DDI->anchor to an anchor WC path + if either of PATH_OR_URL* is given as a WC path, else to null; set + DDI->session_relpath to the repository-relpath of the anchor URL for + DDI->orig_path_1. Anchor the DIFF_PROCESSOR at the anchor chosen + for the underlying diff implementation if the target on either side + is a file, else at the actual requested targets. + + (The choice of WC anchor implementated here for DDI->anchor appears to + be: choose PATH_OR_URL2 (if it's a WC path) or else PATH_OR_URL1 (if + it's a WC path); then take its parent dir unless both resolved URLs + refer to directories.) + + (For the choice of URL anchor for DDI->session_relpath, see + diff_prepare_repos_repos().) + + ### Bizarre anchoring. TODO: always anchor DIFF_PROCESSOR at the + requested targets. + + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * -diff_repos_repos(const char **root_relpath, - svn_boolean_t *root_is_dir, - struct diff_driver_info_t *ddi, +diff_repos_repos(struct diff_driver_info_t *ddi, const char *path_or_url1, const char *path_or_url2, const svn_opt_revision_t *revision1, @@ -1974,14 +1999,16 @@ diff_repos_repos(const char **root_relpath, target1 = str_tmp; diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, - NULL, scratch_pool); } /* Filter the first path component using a filter processor, until we fixed the diff processing to handle this directly */ - if (root_relpath) - *root_relpath = apr_pstrdup(result_pool, target1); + if (!ddi) + { + diff_processor = svn_diff__tree_processor_filter_create( + diff_processor, target1, scratch_pool); + } else if ((kind1 != svn_node_file && kind2 != svn_node_file) && target1[0] != '\0') { @@ -2048,11 +2075,17 @@ diff_repos_repos(const char **root_relpath, If REVERSE is TRUE, the diff will be reported in reverse. - All other options are the same as those passed to svn_client_diff6(). */ + If DDI is null, anchor the DIFF_PROCESSOR at the requested diff + targets. (This case is used by diff-summarize.) + + If DDI is non-null: Set DDI->orig_path_* to the URLs of the two diff + targets as resolved at the given revisions; set DDI->anchor to a WC path + anchor for PATH2; set DDI->session_relpath to the repository-relpath of + the URL of that same anchor WC path. + + All other options are the same as those passed to svn_client_diff7(). */ static svn_error_t * -diff_repos_wc(const char **root_relpath, - svn_boolean_t *root_is_dir, - struct diff_driver_info_t *ddi, +diff_repos_wc(struct diff_driver_info_t *ddi, const char *path_or_url1, const svn_opt_revision_t *revision1, const svn_opt_revision_t *peg_revision1, @@ -2130,11 +2163,6 @@ diff_repos_wc(const char **root_relpath, target = ""; } - if (root_relpath) - *root_relpath = apr_pstrdup(result_pool, target); - if (root_is_dir) - *root_is_dir = (*target == '\0'); - /* Fetch the URL of the anchor directory. */ SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, scratch_pool)); SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath, @@ -2143,7 +2171,7 @@ diff_repos_wc(const char **root_relpath, target_url = NULL; } - else /* is_copy && revision2->kind == svn_opt_revision_base */ + else /* is_copy && revision2->kind != svn_opt_revision_base */ { #if 0 svn_node_kind_t kind; @@ -2232,10 +2260,14 @@ diff_repos_wc(const char **root_relpath, anchor_url, result_pool); } + else + { + diff_processor = svn_diff__tree_processor_filter_create( + diff_processor, target, scratch_pool); + } if (reverse) - diff_processor = svn_diff__tree_processor_reverse_create( - diff_processor, NULL, scratch_pool); + diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, scratch_pool); /* Use the diff editor to generate the diff. */ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, @@ -2311,12 +2343,84 @@ diff_repos_wc(const char **root_relpath, return SVN_NO_ERROR; } +/* Run diff on shelf SHELF_NAME, if it exists. + */ +static svn_error_t * +diff_shelf(const char *shelf_name, + const char *target_abspath, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + const svn_diff_tree_processor_t *diff_processor, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_client__shelf_t *shelf; + svn_client__shelf_version_t *shelf_version; + const char *wc_relpath; + + err = svn_client__shelf_open_existing(&shelf, + shelf_name, target_abspath, + ctx, scratch_pool); + if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + SVN_ERR(svn_client__shelf_version_open(&shelf_version, + shelf, shelf->max_version, + scratch_pool, scratch_pool)); + wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath); + SVN_ERR(svn_client__shelf_diff(shelf_version, wc_relpath, + depth, ignore_ancestry, + diff_processor, scratch_pool)); + SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Run diff on all shelves named in CHANGELISTS by a changelist name + * of the form "svn:shelf:SHELF_NAME", if they exist. + */ +static svn_error_t * +diff_shelves(const apr_array_header_t *changelists, + const char *target_abspath, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + const svn_diff_tree_processor_t *diff_processor, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + static const char PREFIX[] = "svn:shelf:"; + static const int PREFIX_LEN = 10; + int i; + + if (! changelists) + return SVN_NO_ERROR; + for (i = 0; i < changelists->nelts; i++) + { + const char *cl = APR_ARRAY_IDX(changelists, i, const char *); + + if (strncmp(cl, PREFIX, PREFIX_LEN) == 0) + { + const char *shelf_name = cl + PREFIX_LEN; + + SVN_ERR(diff_shelf(shelf_name, target_abspath, + depth, ignore_ancestry, + diff_processor, ctx, scratch_pool)); + } + } + + return SVN_NO_ERROR; +} + /* This is basically just the guts of svn_client_diff[_summarize][_peg]6(). */ static svn_error_t * -do_diff(const char **root_relpath, - svn_boolean_t *root_is_dir, - diff_driver_info_t *ddi, +do_diff(diff_driver_info_t *ddi, const char *path_or_url1, const char *path_or_url2, const svn_opt_revision_t *revision1, @@ -2344,8 +2448,7 @@ do_diff(const char **root_relpath, if (is_repos2) { /* Ignores changelists. */ - SVN_ERR(diff_repos_repos(root_relpath, root_is_dir, - ddi, + SVN_ERR(diff_repos_repos(ddi, path_or_url1, path_or_url2, revision1, revision2, peg_revision, depth, ignore_ancestry, @@ -2355,7 +2458,7 @@ do_diff(const char **root_relpath, } else /* path_or_url2 is a working copy path */ { - SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi, + SVN_ERR(diff_repos_wc(ddi, path_or_url1, revision1, no_peg_revision ? revision1 : peg_revision, @@ -2370,7 +2473,7 @@ do_diff(const char **root_relpath, { if (is_repos2) { - SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi, + SVN_ERR(diff_repos_wc(ddi, path_or_url2, revision2, no_peg_revision ? revision2 : peg_revision, @@ -2394,19 +2497,51 @@ do_diff(const char **root_relpath, SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, scratch_pool)); - /* ### What about ddi? */ + if (ddi) + { + svn_node_kind_t kind1, kind2; + + SVN_ERR(svn_io_check_resolved_path(abspath1, &kind1, + scratch_pool)); + SVN_ERR(svn_io_check_resolved_path(abspath2, &kind2, + scratch_pool)); + if (kind1 == svn_node_dir && kind2 == svn_node_dir) + { + ddi->anchor = ""; + } + else + { + ddi->anchor = svn_dirent_basename(abspath1, NULL); + } + ddi->orig_path_1 = path_or_url1; + ddi->orig_path_2 = path_or_url2; + } + /* Ignores changelists, ignore_ancestry */ - SVN_ERR(svn_client__arbitrary_nodes_diff(root_relpath, root_is_dir, - abspath1, abspath2, + SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2, depth, diff_processor, - ctx, - result_pool, scratch_pool)); + ctx, scratch_pool)); } else { - SVN_ERR(diff_wc_wc(root_relpath, root_is_dir, ddi, - path_or_url1, revision1, + if (ddi) + { + ddi->anchor = path_or_url1; + ddi->orig_path_1 = path_or_url1; + ddi->orig_path_2 = path_or_url2; + } + + { + const char *abspath1; + + SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, + scratch_pool)); + SVN_ERR(diff_shelves(changelists, abspath1, + depth, ignore_ancestry, + diff_processor, ctx, scratch_pool)); + } + SVN_ERR(diff_wc_wc(path_or_url1, revision1, path_or_url2, revision2, depth, ignore_ancestry, changelists, diff_processor, ctx, @@ -2482,6 +2617,115 @@ create_diff_writer_info(diff_writer_info_t *dwi, return SVN_NO_ERROR; } +/* Set up *DIFF_PROCESSOR and *DDI for normal and git-style diffs (but not + * summary diffs). + */ +static svn_error_t * +get_diff_processor(svn_diff_tree_processor_t **diff_processor, + struct diff_driver_info_t **ddi, + const apr_array_header_t *options, + const char *relative_to_dir, + svn_boolean_t no_diff_added, + svn_boolean_t no_diff_deleted, + svn_boolean_t show_copies_as_adds, + svn_boolean_t ignore_content_type, + svn_boolean_t ignore_properties, + svn_boolean_t properties_only, + svn_boolean_t use_git_diff_format, + svn_boolean_t pretty_print_mergeinfo, + const char *header_encoding, + svn_stream_t *outstream, + svn_stream_t *errstream, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + diff_writer_info_t *dwi = apr_pcalloc(pool, sizeof(*dwi)); + svn_diff_tree_processor_t *processor; + + /* setup callback and baton */ + + SVN_ERR(create_diff_writer_info(dwi, options, + ctx->config, pool)); + dwi->pool = pool; + dwi->outstream = outstream; + dwi->errstream = errstream; + dwi->header_encoding = header_encoding; + + dwi->force_binary = ignore_content_type; + dwi->ignore_properties = ignore_properties; + dwi->properties_only = properties_only; + dwi->relative_to_dir = relative_to_dir; + dwi->use_git_diff_format = use_git_diff_format; + dwi->no_diff_added = no_diff_added; + dwi->no_diff_deleted = no_diff_deleted; + dwi->show_copies_as_adds = show_copies_as_adds; + dwi->pretty_print_mergeinfo = pretty_print_mergeinfo; + + dwi->cancel_func = ctx->cancel_func; + dwi->cancel_baton = ctx->cancel_baton; + + dwi->ddi.wc_ctx = ctx->wc_ctx; + dwi->ddi.session_relpath = NULL; + dwi->ddi.anchor = NULL; + + processor = svn_diff__tree_processor_create(dwi, pool); + + processor->dir_added = diff_dir_added; + processor->dir_changed = diff_dir_changed; + processor->dir_deleted = diff_dir_deleted; + + processor->file_added = diff_file_added; + processor->file_changed = diff_file_changed; + processor->file_deleted = diff_file_deleted; + + *diff_processor = processor; + *ddi = &dwi->ddi; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__get_diff_writer_svn( + svn_diff_tree_processor_t **diff_processor, + const char *anchor, + const char *orig_path_1, + const char *orig_path_2, + const apr_array_header_t *options, + const char *relative_to_dir, + svn_boolean_t no_diff_added, + svn_boolean_t no_diff_deleted, + svn_boolean_t show_copies_as_adds, + svn_boolean_t ignore_content_type, + svn_boolean_t ignore_properties, + svn_boolean_t properties_only, + svn_boolean_t pretty_print_mergeinfo, + const char *header_encoding, + svn_stream_t *outstream, + svn_stream_t *errstream, + svn_client_ctx_t *ctx, + apr_pool_t *pool) +{ + struct diff_driver_info_t *ddi; + + SVN_ERR(get_diff_processor(diff_processor, &ddi, + options, + relative_to_dir, + no_diff_added, + no_diff_deleted, + show_copies_as_adds, + ignore_content_type, + ignore_properties, + properties_only, + FALSE /*use_git_diff_format*/, + pretty_print_mergeinfo, + header_encoding, + outstream, errstream, + ctx, pool)); + ddi->anchor = anchor; + ddi->orig_path_1 = orig_path_1; + ddi->orig_path_2 = orig_path_2; + return SVN_NO_ERROR; +} + /*----------------------------------------------------------------------- */ /*** Public Interfaces. ***/ @@ -2519,7 +2763,7 @@ create_diff_writer_info(diff_writer_info_t *dwi, * These cases require server communication. */ svn_error_t * -svn_client_diff6(const apr_array_header_t *options, +svn_client_diff7(const apr_array_header_t *options, const char *path_or_url1, const svn_opt_revision_t *revision1, const char *path_or_url2, @@ -2534,6 +2778,7 @@ svn_client_diff6(const apr_array_header_t *options, svn_boolean_t ignore_properties, svn_boolean_t properties_only, svn_boolean_t use_git_diff_format, + svn_boolean_t pretty_print_mergeinfo, const char *header_encoding, svn_stream_t *outstream, svn_stream_t *errstream, @@ -2541,10 +2786,9 @@ svn_client_diff6(const apr_array_header_t *options, svn_client_ctx_t *ctx, apr_pool_t *pool) { - diff_writer_info_t dwi = { 0 }; svn_opt_revision_t peg_revision; - const svn_diff_tree_processor_t *diff_processor; - svn_diff_tree_processor_t *processor; + svn_diff_tree_processor_t *diff_processor; + struct diff_driver_info_t *ddi; if (ignore_properties && properties_only) return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, @@ -2554,50 +2798,26 @@ svn_client_diff6(const apr_array_header_t *options, /* We will never do a pegged diff from here. */ peg_revision.kind = svn_opt_revision_unspecified; - /* setup callback and baton */ - dwi.ddi.orig_path_1 = path_or_url1; - dwi.ddi.orig_path_2 = path_or_url2; - - SVN_ERR(create_diff_writer_info(&dwi, options, - ctx->config, pool)); - dwi.pool = pool; - dwi.outstream = outstream; - dwi.errstream = errstream; - dwi.header_encoding = header_encoding; - - dwi.force_binary = ignore_content_type; - dwi.ignore_properties = ignore_properties; - dwi.properties_only = properties_only; - dwi.relative_to_dir = relative_to_dir; - dwi.use_git_diff_format = use_git_diff_format; - dwi.no_diff_added = no_diff_added; - dwi.no_diff_deleted = no_diff_deleted; - dwi.show_copies_as_adds = show_copies_as_adds; - - dwi.cancel_func = ctx->cancel_func; - dwi.cancel_baton = ctx->cancel_baton; - - dwi.wc_ctx = ctx->wc_ctx; - dwi.ddi.session_relpath = NULL; - dwi.ddi.anchor = NULL; - - processor = svn_diff__tree_processor_create(&dwi, pool); - - processor->dir_added = diff_dir_added; - processor->dir_changed = diff_dir_changed; - processor->dir_deleted = diff_dir_deleted; - - processor->file_added = diff_file_added; - processor->file_changed = diff_file_changed; - processor->file_deleted = diff_file_deleted; - - diff_processor = processor; - /* --show-copies-as-adds and --git imply --notice-ancestry */ if (show_copies_as_adds || use_git_diff_format) ignore_ancestry = FALSE; - return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi, + SVN_ERR(get_diff_processor(&diff_processor, &ddi, + options, + relative_to_dir, + no_diff_added, + no_diff_deleted, + show_copies_as_adds, + ignore_content_type, + ignore_properties, + properties_only, + use_git_diff_format, + pretty_print_mergeinfo, + header_encoding, + outstream, errstream, + ctx, pool)); + + return svn_error_trace(do_diff(ddi, path_or_url1, path_or_url2, revision1, revision2, &peg_revision, TRUE /* no_peg_revision */, @@ -2607,7 +2827,7 @@ svn_client_diff6(const apr_array_header_t *options, } svn_error_t * -svn_client_diff_peg6(const apr_array_header_t *options, +svn_client_diff_peg7(const apr_array_header_t *options, const char *path_or_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *start_revision, @@ -2622,6 +2842,7 @@ svn_client_diff_peg6(const apr_array_header_t *options, svn_boolean_t ignore_properties, svn_boolean_t properties_only, svn_boolean_t use_git_diff_format, + svn_boolean_t pretty_print_mergeinfo, const char *header_encoding, svn_stream_t *outstream, svn_stream_t *errstream, @@ -2629,59 +2850,34 @@ svn_client_diff_peg6(const apr_array_header_t *options, svn_client_ctx_t *ctx, apr_pool_t *pool) { - diff_writer_info_t dwi = { 0 }; - const svn_diff_tree_processor_t *diff_processor; - svn_diff_tree_processor_t *processor; + svn_diff_tree_processor_t *diff_processor; + struct diff_driver_info_t *ddi; if (ignore_properties && properties_only) return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, _("Cannot ignore properties and show only " "properties at the same time")); - /* setup callback and baton */ - dwi.ddi.orig_path_1 = path_or_url; - dwi.ddi.orig_path_2 = path_or_url; - - SVN_ERR(create_diff_writer_info(&dwi, options, - ctx->config, pool)); - dwi.pool = pool; - dwi.outstream = outstream; - dwi.errstream = errstream; - dwi.header_encoding = header_encoding; - - dwi.force_binary = ignore_content_type; - dwi.ignore_properties = ignore_properties; - dwi.properties_only = properties_only; - dwi.relative_to_dir = relative_to_dir; - dwi.use_git_diff_format = use_git_diff_format; - dwi.no_diff_added = no_diff_added; - dwi.no_diff_deleted = no_diff_deleted; - dwi.show_copies_as_adds = show_copies_as_adds; - - dwi.cancel_func = ctx->cancel_func; - dwi.cancel_baton = ctx->cancel_baton; - - dwi.wc_ctx = ctx->wc_ctx; - dwi.ddi.session_relpath = NULL; - dwi.ddi.anchor = NULL; - - processor = svn_diff__tree_processor_create(&dwi, pool); - - processor->dir_added = diff_dir_added; - processor->dir_changed = diff_dir_changed; - processor->dir_deleted = diff_dir_deleted; - - processor->file_added = diff_file_added; - processor->file_changed = diff_file_changed; - processor->file_deleted = diff_file_deleted; - - diff_processor = processor; - /* --show-copies-as-adds and --git imply --notice-ancestry */ if (show_copies_as_adds || use_git_diff_format) ignore_ancestry = FALSE; - return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi, + SVN_ERR(get_diff_processor(&diff_processor, &ddi, + options, + relative_to_dir, + no_diff_added, + no_diff_deleted, + show_copies_as_adds, + ignore_content_type, + ignore_properties, + properties_only, + use_git_diff_format, + pretty_print_mergeinfo, + header_encoding, + outstream, errstream, + ctx, pool)); + + return svn_error_trace(do_diff(ddi, path_or_url, path_or_url, start_revision, end_revision, peg_revision, FALSE /* no_peg_revision */, @@ -2703,19 +2899,17 @@ svn_client_diff_summarize2(const char *path_or_url1, svn_client_ctx_t *ctx, apr_pool_t *pool) { - const svn_diff_tree_processor_t *diff_processor; + svn_diff_tree_processor_t *diff_processor; svn_opt_revision_t peg_revision; - const char **p_root_relpath; /* We will never do a pegged diff from here. */ peg_revision.kind = svn_opt_revision_unspecified; - SVN_ERR(svn_client__get_diff_summarize_callbacks( - &diff_processor, &p_root_relpath, + SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor, summarize_func, summarize_baton, - path_or_url1, pool, pool)); + pool, pool)); - return svn_error_trace(do_diff(p_root_relpath, NULL, NULL, + return svn_error_trace(do_diff(NULL, path_or_url1, path_or_url2, revision1, revision2, &peg_revision, TRUE /* no_peg_revision */, @@ -2737,15 +2931,13 @@ svn_client_diff_summarize_peg2(const char *path_or_url, svn_client_ctx_t *ctx, apr_pool_t *pool) { - const svn_diff_tree_processor_t *diff_processor; - const char **p_root_relpath; + svn_diff_tree_processor_t *diff_processor; - SVN_ERR(svn_client__get_diff_summarize_callbacks( - &diff_processor, &p_root_relpath, + SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor, summarize_func, summarize_baton, - path_or_url, pool, pool)); + pool, pool)); - return svn_error_trace(do_diff(p_root_relpath, NULL, NULL, + return svn_error_trace(do_diff(NULL, path_or_url, path_or_url, start_revision, end_revision, peg_revision, FALSE /* no_peg_revision */, diff --git a/subversion/libsvn_client/diff_local.c b/subversion/libsvn_client/diff_local.c index 056ee53e8f28b..feac90d788bdf 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 9e258cdfcbcb6..f4972a39e1947 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 2fc2dc8ffd536..b335f2a69eca1 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 3331647c9587e..2aa4c916cd8e2 100644 --- a/subversion/libsvn_client/info.c +++ b/subversion/libsvn_client/info.c @@ -167,7 +167,8 @@ build_info_from_dirent(svn_client_info2_t **info, #define DIRENT_FIELDS (SVN_DIRENT_KIND | \ SVN_DIRENT_CREATED_REV | \ SVN_DIRENT_TIME | \ - SVN_DIRENT_LAST_AUTHOR) + SVN_DIRENT_LAST_AUTHOR | \ + SVN_DIRENT_SIZE) /* Helper func for recursively fetching svn_dirent_t's from a remote @@ -267,6 +268,7 @@ same_resource_in_head(svn_boolean_t *same_p, ctx, pool); if (err && ((err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES) || + (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY) || (err->apr_err == SVN_ERR_FS_NOT_FOUND))) { svn_error_clear(err); diff --git a/subversion/libsvn_client/layout.c b/subversion/libsvn_client/layout.c new file mode 100644 index 0000000000000..bfa7ec1039f1e --- /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 7cc7865643a95..4e475d895adf2 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 78433c337e58b..d52de6d10ac37 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 21341b9b0a7b9..9d9a1c300a105 100644 --- a/subversion/libsvn_client/merge.c +++ b/subversion/libsvn_client/merge.c @@ -215,6 +215,21 @@ /*** Repos-Diff Editor Callbacks ***/ +struct merge_cmd_baton_t; + +struct notify_begin_state_t +{ + /* Cache of which abspath was last notified. */ + const char *last_abspath; + + /* Reference to the main merge baton */ + struct merge_cmd_baton_t *merge_b; + + /* the wrapped notification callback */ + svn_wc_notify_func2_t notify_func2; + void *notify_baton2; +}; + typedef struct merge_cmd_baton_t { svn_boolean_t force_delete; /* Delete a file/dir even if modified */ svn_boolean_t dry_run; @@ -242,11 +257,15 @@ typedef struct merge_cmd_baton_t { /* Rangelist containing single range which describes the gap, if any, in the natural history of the merge source currently being processed. - See http://subversion.tigris.org/issues/show_bug.cgi?id=3432. + See https://issues.apache.org/jira/browse/SVN-3432. Updated during each call to do_directory_merge(). May be NULL if there is no gap. */ svn_rangelist_t *implicit_src_gap; + /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global + comment) or a similar list for single-file-merges */ + const apr_array_header_t *children_with_mergeinfo; + svn_client_ctx_t *ctx; /* Client context for callbacks, etc. */ /* The list of any paths which remained in conflict after a @@ -319,17 +338,10 @@ typedef struct merge_cmd_baton_t { or do_file_merge() in do_merge(). */ apr_pool_t *pool; - - /* State for notify_merge_begin() */ - struct notify_begin_state_t - { - /* Cache of which abspath was last notified. */ - const char *last_abspath; - - /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global - comment) or a similar list for single-file-merges */ - const apr_array_header_t *nodes_with_mergeinfo; - } notify_begin; + /* Our notification callback, that adds a 'begin' notification */ + svn_wc_notify_func2_t notify_func; + void *notify_baton; + struct notify_begin_state_t notify_begin; } merge_cmd_baton_t; @@ -340,17 +352,25 @@ typedef struct merge_cmd_baton_t { merge source is an ancestor of the right-side (or vice-versa), the merge source is in the same repository as the merge target, and we are not ignoring mergeinfo. */ -#define HONOR_MERGEINFO(merge_b) ((merge_b)->mergeinfo_capable \ - && (merge_b)->merge_source.ancestral \ - && (merge_b)->same_repos \ - && (! (merge_b)->ignore_mergeinfo)) +static svn_boolean_t +HONOR_MERGEINFO(const merge_cmd_baton_t *merge_b) +{ + return (merge_b->mergeinfo_capable + && merge_b->merge_source.ancestral + && merge_b->same_repos + && (!merge_b->ignore_mergeinfo)); +} /* Return TRUE iff we should be recording mergeinfo for the merge described by MERGE_B. Specifically, that is if we are honoring mergeinfo and the merge is not a dry run. */ -#define RECORD_MERGEINFO(merge_b) (HONOR_MERGEINFO(merge_b) \ - && !(merge_b)->dry_run) +static svn_boolean_t +RECORD_MERGEINFO(const merge_cmd_baton_t *merge_b) +{ + return (HONOR_MERGEINFO(merge_b) + && !merge_b->dry_run); +} /*-----------------------------------------------------------------------*/ @@ -1226,13 +1246,6 @@ struct merge_file_baton_t svn_boolean_t add_is_replace; /* Add is second part of replace */ }; -/* Forward declaration */ -static svn_error_t * -notify_merge_begin(merge_cmd_baton_t *merge_b, - const char *local_abspath, - svn_boolean_t delete_action, - apr_pool_t *scratch_pool); - /* Record the skip for future processing and (later) produce the skip notification */ static svn_error_t * @@ -1253,18 +1266,16 @@ record_skip(merge_cmd_baton_t *merge_b, store_path(merge_b->skipped_abspaths, local_abspath); } - if (merge_b->ctx->notify_func2) + if (merge_b->notify_func) { svn_wc_notify_t *notify; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool)); - notify = svn_wc_create_notify(local_abspath, action, scratch_pool); notify->kind = kind; notify->content_state = notify->prop_state = state; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, notify, + scratch_pool); } return SVN_NO_ERROR; } @@ -1364,7 +1375,7 @@ record_tree_conflict(merge_cmd_baton_t *merge_b, * but figure out the actual revision range merged. */ (void)find_nearest_ancestor_with_intersecting_ranges( &(range.start), &(range.end), - merge_b->notify_begin.nodes_with_mergeinfo, + merge_b->children_with_mergeinfo, action != svn_wc_conflict_action_delete, local_abspath); loc1 = svn_client__pathrev_dup(merge_b->merge_source.loc1, @@ -1423,18 +1434,16 @@ record_tree_conflict(merge_cmd_baton_t *merge_b, } /* On a replacement we currently get two tree conflicts */ - if (merge_b->ctx->notify_func2 && notify_tc) + if (merge_b->notify_func && notify_tc) { svn_wc_notify_t *notify; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool)); - notify = svn_wc_create_notify(local_abspath, svn_wc_notify_tree_conflict, scratch_pool); notify->kind = local_node_kind; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, notify, + scratch_pool); } return SVN_NO_ERROR; @@ -1455,21 +1464,19 @@ record_update_add(merge_cmd_baton_t *merge_b, store_path(merge_b->merged_abspaths, local_abspath); } - if (merge_b->ctx->notify_func2) + if (merge_b->notify_func) { svn_wc_notify_t *notify; svn_wc_notify_action_t action = svn_wc_notify_update_add; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool)); - if (notify_replaced) action = svn_wc_notify_update_replace; notify = svn_wc_create_notify(local_abspath, action, scratch_pool); notify->kind = kind; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, notify, + scratch_pool); } return SVN_NO_ERROR; @@ -1490,20 +1497,18 @@ record_update_update(merge_cmd_baton_t *merge_b, store_path(merge_b->merged_abspaths, local_abspath); } - if (merge_b->ctx->notify_func2) + if (merge_b->notify_func) { svn_wc_notify_t *notify; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, scratch_pool)); - notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_update, scratch_pool); notify->kind = kind; notify->content_state = content_state; notify->prop_state = prop_state; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, notify, + scratch_pool); } return SVN_NO_ERROR; @@ -1529,8 +1534,6 @@ record_update_delete(merge_cmd_baton_t *merge_b, store_path(merge_b->merged_abspaths, local_abspath); } - SVN_ERR(notify_merge_begin(merge_b, local_abspath, TRUE, scratch_pool)); - if (parent_db) { const char *dup_abspath = apr_pstrdup(parent_db->pool, local_abspath); @@ -1552,7 +1555,7 @@ handle_pending_notifications(merge_cmd_baton_t *merge_b, struct merge_dir_baton_t *db, apr_pool_t *scratch_pool) { - if (merge_b->ctx->notify_func2 && db->pending_deletes) + if (merge_b->notify_func && db->pending_deletes) { apr_hash_index_t *hi; @@ -1569,8 +1572,8 @@ handle_pending_notifications(merge_cmd_baton_t *merge_b, notify->kind = svn_node_kind_from_word( apr_hash_this_val(hi)); - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, - notify, scratch_pool); + merge_b->notify_func(merge_b->notify_baton, + notify, scratch_pool); } db->pending_deletes = NULL; @@ -1620,13 +1623,10 @@ mark_dir_edited(merge_cmd_baton_t *merge_b, for clarity we produce a skip for this node that most likely isn't touched by the merge itself */ - if (merge_b->ctx->notify_func2) + if (merge_b->notify_func) { svn_wc_notify_t *notify; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, - scratch_pool)); - notify = svn_wc_create_notify( local_abspath, (db->tree_conflict_reason == CONFLICT_REASON_SKIP) @@ -1636,9 +1636,9 @@ mark_dir_edited(merge_cmd_baton_t *merge_b, notify->kind = svn_node_dir; notify->content_state = notify->prop_state = db->skip_reason; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, - notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, + notify, + scratch_pool); } if (merge_b->merge_source.ancestral @@ -1706,21 +1706,18 @@ mark_file_edited(merge_cmd_baton_t *merge_b, for clarity we produce a skip for this node that most likely isn't touched by the merge itself */ - if (merge_b->ctx->notify_func2) + if (merge_b->notify_func) { svn_wc_notify_t *notify; - SVN_ERR(notify_merge_begin(merge_b, local_abspath, FALSE, - scratch_pool)); - notify = svn_wc_create_notify(local_abspath, svn_wc_notify_skip, scratch_pool); notify->kind = svn_node_file; notify->content_state = notify->prop_state = fb->skip_reason; - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, - notify, - scratch_pool); + merge_b->notify_func(merge_b->notify_baton, + notify, + scratch_pool); } if (merge_b->merge_source.ancestral @@ -3413,6 +3410,49 @@ merge_node_absent(const char *relpath, return SVN_NO_ERROR; } +/* Return a diff processor that will apply the merge to the WC. + */ +static svn_diff_tree_processor_t * +merge_apply_processor(merge_cmd_baton_t *merge_cmd_baton, + apr_pool_t *result_pool) +{ + svn_diff_tree_processor_t *merge_processor; + + merge_processor = svn_diff__tree_processor_create(merge_cmd_baton, + result_pool); + + merge_processor->dir_opened = merge_dir_opened; + merge_processor->dir_changed = merge_dir_changed; + merge_processor->dir_added = merge_dir_added; + merge_processor->dir_deleted = merge_dir_deleted; + merge_processor->dir_closed = merge_dir_closed; + + merge_processor->file_opened = merge_file_opened; + merge_processor->file_changed = merge_file_changed; + merge_processor->file_added = merge_file_added; + merge_processor->file_deleted = merge_file_deleted; + /* Not interested in file_closed() */ + + merge_processor->node_absent = merge_node_absent; + + return merge_processor; +} + +/* Initialize minimal dir baton to allow calculating 'R'eplace + from 'D'elete + 'A'dd. */ +static void * +open_dir_for_replace_single_file(apr_pool_t *result_pool) +{ + struct merge_dir_baton_t *dir_baton = apr_pcalloc(result_pool, sizeof(*dir_baton)); + + dir_baton->pool = result_pool; + dir_baton->tree_conflict_reason = CONFLICT_REASON_NONE; + dir_baton->tree_conflict_action = svn_wc_conflict_action_edit; + dir_baton->skip_reason = svn_wc_notify_state_unknown; + + return dir_baton; +} + /*-----------------------------------------------------------------------*/ /*** Merge Notification ***/ @@ -3608,20 +3648,9 @@ notify_merge_completed(const char *target_abspath, } } -/* Is the notification the result of a real operative merge? */ -#define IS_OPERATIVE_NOTIFICATION(notify) \ - (notify->content_state == svn_wc_notify_state_conflicted \ - || notify->content_state == svn_wc_notify_state_merged \ - || notify->content_state == svn_wc_notify_state_changed \ - || notify->prop_state == svn_wc_notify_state_conflicted \ - || notify->prop_state == svn_wc_notify_state_merged \ - || notify->prop_state == svn_wc_notify_state_changed \ - || notify->action == svn_wc_notify_update_add \ - || notify->action == svn_wc_notify_tree_conflict) - /* Remove merge source gaps from range used for merge notifications. - See http://subversion.tigris.org/issues/show_bug.cgi?id=4138 + See https://issues.apache.org/jira/browse/SVN-4138 If IMPLICIT_SRC_GAP is not NULL then it is a rangelist containing a single range (see the implicit_src_gap member of merge_cmd_baton_t). @@ -3656,27 +3685,28 @@ remove_source_gap(svn_merge_range_t *range, * This calls the client's notification receiver (as found in the client * context), with a WC abspath. */ -static svn_error_t * -notify_merge_begin(merge_cmd_baton_t *merge_b, +static void +notify_merge_begin(struct notify_begin_state_t *notify_begin_state, const char *local_abspath, svn_boolean_t delete_action, apr_pool_t *scratch_pool) { + merge_cmd_baton_t *merge_b = notify_begin_state->merge_b; svn_wc_notify_t *notify; svn_merge_range_t n_range = {SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, TRUE}; const char *notify_abspath; - if (! merge_b->ctx->notify_func2) - return SVN_NO_ERROR; + if (! notify_begin_state->notify_func2) + return; /* If our merge sources are ancestors of one another... */ if (merge_b->merge_source.ancestral) { const svn_client__merge_path_t *child; - /* Find NOTIFY->PATH's nearest ancestor in - NOTIFY->CHILDREN_WITH_MERGEINFO. Normally we consider a child in - NOTIFY->CHILDREN_WITH_MERGEINFO representing PATH to be an + /* Find LOCAL_ABSPATH's nearest ancestor in + CHILDREN_WITH_MERGEINFO. Normally we consider a child in + CHILDREN_WITH_MERGEINFO representing PATH to be an ancestor of PATH, but if this is a deletion of PATH then the notification must be for a proper ancestor of PATH. This ensures we don't get notifications like: @@ -3692,47 +3722,47 @@ notify_merge_begin(merge_cmd_baton_t *merge_b, child = find_nearest_ancestor_with_intersecting_ranges( &(n_range.start), &(n_range.end), - merge_b->notify_begin.nodes_with_mergeinfo, + merge_b->children_with_mergeinfo, ! delete_action, local_abspath); if (!child && delete_action) { /* Triggered by file replace in single-file-merge */ - child = find_nearest_ancestor(merge_b->notify_begin.nodes_with_mergeinfo, + child = find_nearest_ancestor(merge_b->children_with_mergeinfo, TRUE, local_abspath); } assert(child != NULL); /* Should always find the merge anchor */ if (! child) - return SVN_NO_ERROR; + return; - if (merge_b->notify_begin.last_abspath != NULL - && strcmp(child->abspath, merge_b->notify_begin.last_abspath) == 0) + if (notify_begin_state->last_abspath != NULL + && strcmp(child->abspath, notify_begin_state->last_abspath) == 0) { /* Don't notify the same merge again */ - return SVN_NO_ERROR; + return; } - merge_b->notify_begin.last_abspath = child->abspath; + notify_begin_state->last_abspath = child->abspath; if (child->absent || child->remaining_ranges->nelts == 0 || !SVN_IS_VALID_REVNUM(n_range.start)) { /* No valid information for an header */ - return SVN_NO_ERROR; + return; } notify_abspath = child->abspath; } else { - if (merge_b->notify_begin.last_abspath) - return SVN_NO_ERROR; /* already notified */ + if (notify_begin_state->last_abspath) + return; /* already notified */ notify_abspath = merge_b->target->abspath; /* Store something in last_abspath. Any value would do */ - merge_b->notify_begin.last_abspath = merge_b->target->abspath; + notify_begin_state->last_abspath = merge_b->target->abspath; } notify = svn_wc_create_notify(notify_abspath, @@ -3753,10 +3783,23 @@ notify_merge_begin(merge_cmd_baton_t *merge_b, notify->merge_range = NULL; } - merge_b->ctx->notify_func2(merge_b->ctx->notify_baton2, notify, - scratch_pool); + notify_begin_state->notify_func2(notify_begin_state->notify_baton2, notify, + scratch_pool); +} + +/* Our notification callback, that adds a 'begin' notification */ +static void +notify_merging(void *baton, + const svn_wc_notify_t *notify, + apr_pool_t *pool) +{ + struct notify_begin_state_t *b = baton; + + notify_merge_begin(b, notify->path, + notify->action == svn_wc_notify_update_delete, + pool); - return SVN_NO_ERROR; + b->notify_func2(b->notify_baton2, notify, pool); } /* Set *OUT_RANGELIST to the intersection of IN_RANGELIST with the simple @@ -5445,7 +5488,7 @@ record_skips_in_mergeinfo(const char *mergeinfo_path, ### TODO: An empty range is fine if the skipped path doesn't ### inherit any mergeinfo from a parent, but if it does ### we need to account for that. See issue #3440 - ### http://subversion.tigris.org/issues/show_bug.cgi?id=3440. */ + ### https://issues.apache.org/jira/browse/SVN-3440. */ svn_hash_sets(merges, skipped_abspath, apr_array_make(scratch_pool, 0, sizeof(svn_merge_range_t *))); @@ -5554,7 +5597,7 @@ svn_client__make_merge_conflict_error(svn_client__conflict_report_t *report, defined in get_mergeinfo_paths(). Remove any paths absent from disk or scheduled for deletion from CHILDREN_WITH_MERGEINFO which are equal to or are descendants of TARGET_WCPATH by setting those children to NULL. */ -static void +static svn_error_t * remove_absent_children(const char *target_wcpath, apr_array_header_t *children_with_mergeinfo) { @@ -5569,9 +5612,10 @@ remove_absent_children(const char *target_wcpath, if ((child->absent || child->scheduled_for_deletion) && svn_dirent_is_ancestor(target_wcpath, child->abspath)) { - svn_sort__array_delete(children_with_mergeinfo, i--, 1); + SVN_ERR(svn_sort__array_delete2(children_with_mergeinfo, i--, 1)); } } + return SVN_NO_ERROR; } /* Helper for do_directory_merge() to handle the case where a merge editor @@ -5586,14 +5630,14 @@ remove_absent_children(const char *target_wcpath, MERGE_B->target->abspath, this must always be present in CHILDREN_WITH_MERGEINFO so this is never removed by this function. */ -static void +static svn_error_t * remove_children_with_deleted_mergeinfo(merge_cmd_baton_t *merge_b, apr_array_header_t *children_with_mergeinfo) { int i; if (!merge_b->paths_with_deleted_mergeinfo) - return; + return SVN_NO_ERROR; /* CHILDREN_WITH_MERGEINFO[0] is the always the merge target so start at the first child. */ @@ -5604,9 +5648,10 @@ remove_children_with_deleted_mergeinfo(merge_cmd_baton_t *merge_b, if (svn_hash_gets(merge_b->paths_with_deleted_mergeinfo, child->abspath)) { - svn_sort__array_delete(children_with_mergeinfo, i--, 1); + SVN_ERR(svn_sort__array_delete2(children_with_mergeinfo, i--, 1)); } } + return SVN_NO_ERROR; } /* Helper for do_directory_merge(). @@ -5932,7 +5977,7 @@ get_most_inclusive_rev(const apr_array_header_t *children_with_mergeinfo, remaining_ranges is inclusive of END_REV, Slice the first range in to two at END_REV. All the allocations are persistent and allocated from POOL. */ -static void +static svn_error_t * slice_remaining_ranges(apr_array_header_t *children_with_mergeinfo, svn_boolean_t is_rollback, svn_revnum_t end_rev, apr_pool_t *pool) @@ -5962,10 +6007,12 @@ slice_remaining_ranges(apr_array_header_t *children_with_mergeinfo, split_range2->start = end_rev; APR_ARRAY_IDX(child->remaining_ranges, 0, svn_merge_range_t *) = split_range1; - svn_sort__array_insert(child->remaining_ranges, &split_range2, 1); + SVN_ERR(svn_sort__array_insert2(child->remaining_ranges, + &split_range2, 1)); } } } + return SVN_NO_ERROR; } /* Helper for do_directory_merge(). @@ -5977,7 +6024,7 @@ slice_remaining_ranges(apr_array_header_t *children_with_mergeinfo, If a range is removed from a child's remaining_ranges array, allocate the new remaining_ranges array in POOL. */ -static void +static svn_error_t * remove_first_range_from_remaining_ranges(svn_revnum_t revision, apr_array_header_t *children_with_mergeinfo, @@ -5998,10 +6045,11 @@ remove_first_range_from_remaining_ranges(svn_revnum_t revision, APR_ARRAY_IDX(child->remaining_ranges, 0, svn_merge_range_t *); if (first_range->end == revision) { - svn_sort__array_delete(child->remaining_ranges, 0, 1); + SVN_ERR(svn_sort__array_delete2(child->remaining_ranges, 0, 1)); } } } + return SVN_NO_ERROR; } /* Get a file's content and properties from the repository. @@ -6087,7 +6135,7 @@ get_child_with_mergeinfo(const apr_array_header_t *children_with_mergeinfo, out of order and then sort afterwards. (One caller is doing a qsort after calling this anyway.) */ -static void +static svn_error_t * insert_child_to_merge(apr_array_header_t *children_with_mergeinfo, const svn_client__merge_path_t *insert_element, apr_pool_t *pool) @@ -6101,7 +6149,9 @@ insert_child_to_merge(apr_array_header_t *children_with_mergeinfo, compare_merge_path_t_as_paths); new_element = svn_client__merge_path_dup(insert_element, pool); - svn_sort__array_insert(children_with_mergeinfo, &new_element, insert_index); + SVN_ERR(svn_sort__array_insert2(children_with_mergeinfo, + &new_element, insert_index)); + return SVN_NO_ERROR; } /* Helper for get_mergeinfo_paths(). @@ -6162,7 +6212,7 @@ insert_parent_and_sibs_of_sw_absent_del_subtree( parent->missing_child = child->absent; parent->switched_child = child->switched; /* Insert PARENT into CHILDREN_WITH_MERGEINFO. */ - insert_child_to_merge(children_with_mergeinfo, parent, pool); + SVN_ERR(insert_child_to_merge(children_with_mergeinfo, parent, pool)); /* Increment for loop index so we don't process the inserted element. */ (*curr_index)++; } /*(parent == NULL) */ @@ -6199,8 +6249,8 @@ insert_parent_and_sibs_of_sw_absent_del_subtree( sibling_of_missing = svn_client__merge_path_create(child_abspath, pool); - insert_child_to_merge(children_with_mergeinfo, sibling_of_missing, - pool); + SVN_ERR(insert_child_to_merge(children_with_mergeinfo, + sibling_of_missing, pool)); } } @@ -6541,8 +6591,8 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo, svn_client__merge_path_t *switched_child = svn_client__merge_path_create(wc_path, result_pool); switched_child->switched = TRUE; - insert_child_to_merge(children_with_mergeinfo, switched_child, - result_pool); + SVN_ERR(insert_child_to_merge(children_with_mergeinfo, + switched_child, result_pool)); } } } @@ -6594,8 +6644,8 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo, } if (new_shallow_child) - insert_child_to_merge(children_with_mergeinfo, shallow_child, - result_pool); + SVN_ERR(insert_child_to_merge(children_with_mergeinfo, + shallow_child, result_pool)); } } @@ -6624,8 +6674,8 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo, svn_client__merge_path_t *absent_child = svn_client__merge_path_create(wc_path, result_pool); absent_child->absent = TRUE; - insert_child_to_merge(children_with_mergeinfo, absent_child, - result_pool); + SVN_ERR(insert_child_to_merge(children_with_mergeinfo, + absent_child, result_pool)); } } } @@ -6638,8 +6688,8 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo, svn_client__merge_path_t *target_child = svn_client__merge_path_create(target->abspath, result_pool); - insert_child_to_merge(children_with_mergeinfo, target_child, - result_pool); + SVN_ERR(insert_child_to_merge(children_with_mergeinfo, target_child, + result_pool)); } /* Case 8: Path is an immediate *directory* child of @@ -6682,8 +6732,8 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo, && depth == svn_depth_immediates) immediate_child->immediate_child_dir = TRUE; - insert_child_to_merge(children_with_mergeinfo, - immediate_child, result_pool); + SVN_ERR(insert_child_to_merge(children_with_mergeinfo, + immediate_child, result_pool)); } } } @@ -6775,9 +6825,9 @@ get_mergeinfo_paths(apr_array_header_t *children_with_mergeinfo, child_of_noninheritable = svn_client__merge_path_create(child_abspath, result_pool); child_of_noninheritable->child_of_noninheritable = TRUE; - insert_child_to_merge(children_with_mergeinfo, - child_of_noninheritable, - result_pool); + SVN_ERR(insert_child_to_merge(children_with_mergeinfo, + child_of_noninheritable, + result_pool)); if (!dry_run && same_repos) { svn_mergeinfo_t mergeinfo; @@ -7201,7 +7251,7 @@ normalize_merge_sources_internal(apr_array_header_t **merge_sources_p, new_segment->path = original_repos_relpath; new_segment->range_start = original_revision; new_segment->range_end = original_revision; - svn_sort__array_insert(segments, &new_segment, 0); + SVN_ERR(svn_sort__array_insert2(segments, &new_segment, 0)); } } } @@ -7596,16 +7646,15 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, /* ### Create a fake copy of merge_target as we don't keep remaining_ranges in sync (yet). */ - target_info = apr_pcalloc(scratch_pool, sizeof(*target_info)); - - target_info->abspath = merge_target->abspath; + target_info = svn_client__merge_path_create(merge_target->abspath, + scratch_pool); target_info->remaining_ranges = ranges_to_merge; APR_ARRAY_PUSH(child_with_mergeinfo, svn_client__merge_path_t *) = target_info; /* And store in baton to allow using it from notify_merge_begin() */ - merge_b->notify_begin.nodes_with_mergeinfo = child_with_mergeinfo; + merge_b->children_with_mergeinfo = child_with_mergeinfo; } while (ranges_to_merge->nelts > 0) @@ -7648,19 +7697,10 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, do a text-n-props merge; otherwise, do a delete-n-add merge. */ if (! (merge_b->diff_ignore_ancestry || sources_related)) { - struct merge_dir_baton_t dir_baton; + void *dir_baton = open_dir_for_replace_single_file(iterpool); void *file_baton; svn_boolean_t skip; - /* Initialize minimal dir baton to allow calculating 'R'eplace - from 'D'elete + 'A'dd. */ - - memset(&dir_baton, 0, sizeof(dir_baton)); - dir_baton.pool = iterpool; - dir_baton.tree_conflict_reason = CONFLICT_REASON_NONE; - dir_baton.tree_conflict_action = svn_wc_conflict_action_edit; - dir_baton.skip_reason = svn_wc_notify_state_unknown; - /* Delete... */ file_baton = NULL; skip = FALSE; @@ -7668,7 +7708,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, left_source, NULL /* right_source */, NULL /* copyfrom_source */, - &dir_baton, + dir_baton, processor, iterpool, iterpool)); if (! skip) @@ -7687,7 +7727,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, NULL /* left_source */, right_source, NULL /* copyfrom_source */, - &dir_baton, + dir_baton, processor, iterpool, iterpool)); if (! skip) @@ -7758,7 +7798,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, (This list is used from notify_merge_begin) Directory merges use remove_first_range_from_remaining_ranges() */ - svn_sort__array_delete(ranges_to_merge, 0, 1); + SVN_ERR(svn_sort__array_delete2(ranges_to_merge, 0, 1)); } merge_b->notify_begin.last_abspath = NULL; } /* !merge_b->record_only */ @@ -7819,7 +7859,7 @@ do_file_merge(svn_mergeinfo_catalog_t result_catalog, } } - merge_b->notify_begin.nodes_with_mergeinfo = NULL; + merge_b->children_with_mergeinfo = NULL; svn_pool_destroy(iterpool); @@ -7878,7 +7918,7 @@ process_children_with_new_mergeinfo(merge_cmd_baton_t *merge_b, was added (with preexisting mergeinfo) by the merge. That's actually more correct, since the inherited mergeinfo likely describes non-existent or unrelated merge history, but it's not quite so simple - as that, see http://subversion.tigris.org/issues/show_bug.cgi?id=4309 + as that, see https://issues.apache.org/jira/browse/SVN-4309 */ /* Get the path's new explicit mergeinfo... */ @@ -7945,7 +7985,8 @@ process_children_with_new_mergeinfo(merge_cmd_baton_t *merge_b, /* Set the path's remaining_ranges equal to its parent's. */ new_child->remaining_ranges = svn_rangelist_dup( parent->remaining_ranges, pool); - insert_child_to_merge(children_with_mergeinfo, new_child, pool); + SVN_ERR(insert_child_to_merge(children_with_mergeinfo, + new_child, pool)); } } } @@ -8269,7 +8310,7 @@ flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge, merge_b->target->abspath, depth, merge_b->ctx->wc_ctx, merge_b->ra_session1, scratch_pool, iterpool)); - /* Issue #4056: Walk NOTIFY_B->CHILDREN_WITH_MERGEINFO reverse depth-first + /* Issue #4056: Walk CHILDREN_WITH_MERGEINFO reverse depth-first order. This way each child knows if it has operative missing/switched children which necessitates non-inheritable mergeinfo. */ for (i = children_with_mergeinfo->nelts - 1; i >= 0; i--) @@ -8432,7 +8473,7 @@ flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge, } else /* child->record_mergeinfo */ { - /* If CHILD is in NOTIFY_B->CHILDREN_WITH_MERGEINFO simply + /* If CHILD is in CHILDREN_WITH_MERGEINFO simply because it had no explicit mergeinfo of its own at the start of the merge but is the child of of some path with non-inheritable mergeinfo, then the explicit mergeinfo it @@ -8457,7 +8498,7 @@ flag_subtrees_needing_mergeinfo(svn_boolean_t operative_merge, If RESULT_CATALOG is NULL then record mergeinfo describing a merge of MERGED_RANGE->START:MERGED_RANGE->END from the repository relative path MERGEINFO_FSPATH to the merge target (and possibly its subtrees) described - by NOTIFY_B->CHILDREN_WITH_MERGEINFO -- see the global comment + by CHILDREN_WITH_MERGEINFO -- see the global comment 'THE CHILDREN_WITH_MERGEINFO ARRAY'. Obviously this should only be called if recording mergeinfo -- see doc string for RECORD_MERGEINFO(). @@ -8508,10 +8549,10 @@ record_mergeinfo_for_dir_merge(svn_mergeinfo_catalog_t result_catalog, range.inheritable = TRUE; /* Remove absent children at or under MERGE_B->target->abspath from - NOTIFY_B->CHILDREN_WITH_MERGEINFO + CHILDREN_WITH_MERGEINFO before we calculate the merges performed. */ - remove_absent_children(merge_b->target->abspath, - children_with_mergeinfo); + SVN_ERR(remove_absent_children(merge_b->target->abspath, + children_with_mergeinfo)); /* Determine which subtrees of interest need mergeinfo recorded... */ SVN_ERR(flag_subtrees_needing_mergeinfo(operative_merge, &range, @@ -9334,7 +9375,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, /* Point our RA_SESSION to the URL of our youngest merge source side. */ ra_session = is_rollback ? merge_b->ra_session1 : merge_b->ra_session2; - /* Fill NOTIFY_B->CHILDREN_WITH_MERGEINFO with child paths (const + /* Fill CHILDREN_WITH_MERGEINFO with child paths (const svn_client__merge_path_t *) which might have intersecting merges because they meet one or more of the criteria described in get_mergeinfo_paths(). Here the paths are arranged in a depth @@ -9344,13 +9385,13 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, merge_b->dry_run, merge_b->same_repos, merge_b->ctx, scratch_pool, scratch_pool)); - /* The first item from the NOTIFY_B->CHILDREN_WITH_MERGEINFO is always + /* The first item from the CHILDREN_WITH_MERGEINFO is always the target thanks to depth-first ordering. */ target_merge_path = APR_ARRAY_IDX(children_with_mergeinfo, 0, svn_client__merge_path_t *); /* If we are honoring mergeinfo, then for each item in - NOTIFY_B->CHILDREN_WITH_MERGEINFO, we need to calculate what needs to be + CHILDREN_WITH_MERGEINFO, we need to calculate what needs to be merged, and then merge it. Otherwise, we just merge what we were asked to merge across the whole tree. */ SVN_ERR(populate_remaining_ranges(children_with_mergeinfo, @@ -9370,7 +9411,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, /* The merge target TARGET_ABSPATH and/or its subtrees may not need all of SOURCE->rev1:rev2 applied. So examine - NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the oldest starting + CHILDREN_WITH_MERGEINFO to find the oldest starting revision that actually needs to be merged (for reverse merges this is the youngest starting revision). @@ -9408,7 +9449,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, /* Is there anything to merge? */ if (SVN_IS_VALID_REVNUM(start_rev)) { - /* Now examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the oldest + /* Now examine CHILDREN_WITH_MERGEINFO to find the oldest ending revision that actually needs to be merged (for reverse merges this is the youngest ending revision). */ svn_revnum_t end_rev = @@ -9417,7 +9458,7 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, /* While END_REV is valid, do the following: - 1. Tweak each NOTIFY_B->CHILDREN_WITH_MERGEINFO element so that + 1. Tweak each CHILDREN_WITH_MERGEINFO element so that the element's remaining_ranges member has as its first element a range that ends with end_rev. @@ -9425,17 +9466,17 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, on MERGE_B->target->abspath for start_rev:end_rev. 3. Remove the first element from each - NOTIFY_B->CHILDREN_WITH_MERGEINFO element's remaining_ranges + CHILDREN_WITH_MERGEINFO element's remaining_ranges member. - 4. Again examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the most + 4. Again examine CHILDREN_WITH_MERGEINFO to find the most inclusive starting revision that actually needs to be merged and update start_rev. This prevents us from needlessly contacting the repository and doing a diff where we describe the entire target tree as *not* needing any of the requested range. This can happen whenever we have mergeinfo with gaps in it for the merge source. - 5. Again examine NOTIFY_B->CHILDREN_WITH_MERGEINFO to find the most + 5. Again examine CHILDREN_WITH_MERGEINFO to find the most inclusive ending revision that actually needs to be merged and update end_rev. @@ -9477,8 +9518,9 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, svn_pool_clear(iterpool); - slice_remaining_ranges(children_with_mergeinfo, - is_rollback, end_rev, scratch_pool); + SVN_ERR(slice_remaining_ranges(children_with_mergeinfo, + is_rollback, end_rev, + scratch_pool)); /* Reset variables that must be reset for every drive */ merge_b->notify_begin.last_abspath = NULL; @@ -9496,23 +9538,23 @@ do_mergeinfo_aware_dir_merge(svn_mergeinfo_catalog_t result_catalog, /* If any paths picked up explicit mergeinfo as a result of the merge we need to make sure any mergeinfo those paths inherited is recorded and then add these paths to - NOTIFY_B->CHILDREN_WITH_MERGEINFO.*/ + CHILDREN_WITH_MERGEINFO.*/ SVN_ERR(process_children_with_new_mergeinfo( merge_b, children_with_mergeinfo, scratch_pool)); /* If any subtrees had their explicit mergeinfo deleted as a result of the merge then remove these paths from - NOTIFY_B->CHILDREN_WITH_MERGEINFO since there is no need + CHILDREN_WITH_MERGEINFO since there is no need to consider these subtrees for subsequent editor drives nor do we want to record mergeinfo on them describing the merge itself. */ - remove_children_with_deleted_mergeinfo( - merge_b, children_with_mergeinfo); + SVN_ERR(remove_children_with_deleted_mergeinfo( + merge_b, children_with_mergeinfo)); /* Prepare for the next iteration (if any). */ - remove_first_range_from_remaining_ranges( - end_rev, children_with_mergeinfo, scratch_pool); + SVN_ERR(remove_first_range_from_remaining_ranges( + end_rev, children_with_mergeinfo, scratch_pool)); /* If we raised any conflicts, break out and report how much we have merged. */ @@ -9634,7 +9676,7 @@ do_directory_merge(svn_mergeinfo_catalog_t result_catalog, apr_array_make(scratch_pool, 16, sizeof(svn_client__merge_path_t *)); /* And make it read-only accessible from the baton */ - merge_b->notify_begin.nodes_with_mergeinfo = children_with_mergeinfo; + merge_b->children_with_mergeinfo = children_with_mergeinfo; /* If we are not honoring mergeinfo we can skip right to the business of merging changes! */ @@ -9652,7 +9694,7 @@ do_directory_merge(svn_mergeinfo_catalog_t result_catalog, processor, depth, merge_b, result_pool, scratch_pool)); - merge_b->notify_begin.nodes_with_mergeinfo = NULL; + merge_b->children_with_mergeinfo = NULL; return SVN_NO_ERROR; } @@ -9889,28 +9931,13 @@ do_merge(apr_hash_t **modified_subtrees, merge_cmd_baton.added_abspaths = apr_hash_make(result_pool); merge_cmd_baton.tree_conflicted_abspaths = apr_hash_make(result_pool); - { - svn_diff_tree_processor_t *merge_processor; - - merge_processor = svn_diff__tree_processor_create(&merge_cmd_baton, - scratch_pool); - - merge_processor->dir_opened = merge_dir_opened; - merge_processor->dir_changed = merge_dir_changed; - merge_processor->dir_added = merge_dir_added; - merge_processor->dir_deleted = merge_dir_deleted; - merge_processor->dir_closed = merge_dir_closed; - - merge_processor->file_opened = merge_file_opened; - merge_processor->file_changed = merge_file_changed; - merge_processor->file_added = merge_file_added; - merge_processor->file_deleted = merge_file_deleted; - /* Not interested in file_closed() */ - - merge_processor->node_absent = merge_node_absent; + merge_cmd_baton.notify_func = notify_merging; + merge_cmd_baton.notify_baton = &merge_cmd_baton.notify_begin; + merge_cmd_baton.notify_begin.merge_b = &merge_cmd_baton; + merge_cmd_baton.notify_begin.notify_func2 = ctx->notify_func2; + merge_cmd_baton.notify_begin.notify_baton2 = ctx->notify_baton2; - processor = merge_processor; - } + processor = merge_apply_processor(&merge_cmd_baton, scratch_pool); if (src_session) { diff --git a/subversion/libsvn_client/mtcc.c b/subversion/libsvn_client/mtcc.c index 75889cad2746f..48ddcccbc1648 100644 --- a/subversion/libsvn_client/mtcc.c +++ b/subversion/libsvn_client/mtcc.c @@ -604,7 +604,7 @@ mtcc_op_contains_non_delete(const mtcc_op_t *op) static svn_error_t * mtcc_add_delete(const char *relpath, svn_boolean_t for_move, - svn_client__mtcc_t *mtcc, + svn_client__mtcc_t *mtcc, apr_pool_t *scratch_pool) { mtcc_op_t *op; @@ -636,7 +636,7 @@ mtcc_add_delete(const char *relpath, { /* Allow deleting directories, that are unmodified except for one or more deleted descendants */ - + SVN_ERR(mtcc_op_find(&op, &created, relpath, mtcc->root_op, TRUE, FALSE, FALSE, mtcc->pool, scratch_pool)); diff --git a/subversion/libsvn_client/patch.c b/subversion/libsvn_client/patch.c index 1b2d86b1da947..549d20c114840 100644 --- a/subversion/libsvn_client/patch.c +++ b/subversion/libsvn_client/patch.c @@ -343,7 +343,9 @@ strip_path(const char **result, const char *path, int strip_count, components = svn_path_decompose(path, scratch_pool); if (strip_count > components->nelts) return svn_error_createf(SVN_ERR_CLIENT_PATCH_BAD_STRIP_COUNT, NULL, - _("Cannot strip %u components from '%s'"), + Q_("Cannot strip %u component from '%s'", + "Cannot strip %u components from '%s'", + strip_count), strip_count, svn_dirent_local_style(path, scratch_pool)); @@ -1018,6 +1020,7 @@ init_patch_target(patch_target_t **patch_target, target_content_t *content; svn_boolean_t has_text_changes = FALSE; svn_boolean_t follow_moves; + const char *tempdir_abspath; has_text_changes = ((patch->hunks && patch->hunks->nelts > 0) || patch->binary_patch); @@ -1223,8 +1226,10 @@ init_patch_target(patch_target_t **patch_target, } /* Open a temporary file to write the patched result to. */ + SVN_ERR(svn_wc__get_tmpdir(&tempdir_abspath, wc_ctx, + target->local_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_io_open_unique_file3(&target->patched_file, - &target->patched_path, NULL, + &target->patched_path, tempdir_abspath, remove_tempfiles ? svn_io_file_del_on_pool_cleanup : svn_io_file_del_none, @@ -1236,7 +1241,7 @@ init_patch_target(patch_target_t **patch_target, /* Open a temporary stream to write rejected hunks to. */ SVN_ERR(svn_stream_open_unique(&target->reject_stream, - &target->reject_path, NULL, + &target->reject_path, tempdir_abspath, remove_tempfiles ? svn_io_file_del_on_pool_cleanup : svn_io_file_del_none, @@ -2145,8 +2150,8 @@ reject_hunk(patch_target_t *target, target_content_t *content, if (prop_name) { /* ### Print 'Added', 'Deleted' or 'Modified' instead of 'Property'. */ - svn_stream_printf(target->reject_stream, - pool, "Property: %s" APR_EOL_STR, prop_name); + SVN_ERR(svn_stream_printf(target->reject_stream, + pool, "Property: %s" APR_EOL_STR, prop_name)); atat = prop_atat; } else diff --git a/subversion/libsvn_client/ra.c b/subversion/libsvn_client/ra.c index d50e720bd0f14..c1b71e65fc61d 100644 --- a/subversion/libsvn_client/ra.c +++ b/subversion/libsvn_client/ra.c @@ -402,8 +402,7 @@ svn_client__open_ra_session_internal(svn_ra_session_t **ra_session, } } - /* If the caller allows for auto-following redirections, and the - RA->open() call above reveals a CORRECTED_URL, try the new URL. + /* If the caller allows for auto-following redirections, try the new URL. We'll do this in a loop up to some maximum number follow-and-retry attempts. */ if (corrected_url) @@ -414,12 +413,14 @@ svn_client__open_ra_session_internal(svn_ra_session_t **ra_session, *corrected_url = NULL; while (attempts_left--) { - const char *corrected = NULL; + const char *corrected = NULL; /* canonicalized version */ + const char *redirect_url = NULL; /* non-canonicalized version */ /* Try to open the RA session. If this is our last attempt, don't accept corrected URLs from the RA provider. */ - SVN_ERR(svn_ra_open4(ra_session, + SVN_ERR(svn_ra_open5(ra_session, attempts_left == 0 ? NULL : &corrected, + attempts_left == 0 ? NULL : &redirect_url, base_url, uuid, cbtable, cb, ctx->config, result_pool)); @@ -441,19 +442,28 @@ svn_client__open_ra_session_internal(svn_ra_session_t **ra_session, *corrected_url = corrected; /* Make sure we've not attempted this URL before. */ - if (svn_hash_gets(attempted, corrected)) + if (svn_hash_gets(attempted, redirect_url)) return svn_error_createf(SVN_ERR_CLIENT_CYCLE_DETECTED, NULL, _("Redirect cycle detected for URL '%s'"), - corrected); + redirect_url); + + /* + * Remember this redirect URL so we don't wind up in a loop. + * + * Store the non-canonicalized version of the URL. The canonicalized + * version is insufficient for loop detection because we might not get + * an exact match against URLs used by the RA protocol-layer (the URL + * used by the protocol may contain trailing slashes, for example, + * which are stripped during canonicalization). + */ + svn_hash_sets(attempted, redirect_url, (void *)1); - /* Remember this CORRECTED_URL so we don't wind up in a loop. */ - svn_hash_sets(attempted, corrected, (void *)1); base_url = corrected; } } else { - SVN_ERR(svn_ra_open4(ra_session, NULL, base_url, + SVN_ERR(svn_ra_open5(ra_session, NULL, NULL, base_url, uuid, cbtable, cb, ctx->config, result_pool)); } diff --git a/subversion/libsvn_client/repos_diff.c b/subversion/libsvn_client/repos_diff.c index 58fe8aaa1dae6..885b5eba2bc08 100644 --- a/subversion/libsvn_client/repos_diff.c +++ b/subversion/libsvn_client/repos_diff.c @@ -51,6 +51,7 @@ #include "private/svn_subr_private.h" #include "private/svn_wc_private.h" #include "private/svn_editor.h" +#include "private/svn_sorts_private.h" /* Overall crawler editor baton. */ struct edit_baton { @@ -380,10 +381,10 @@ get_file_from_ra(struct file_baton *fb, way. Hence this little hack: We populate FILE_BATON->PROPCHANGES only with *actual* property changes. - See http://subversion.tigris.org/issues/show_bug.cgi?id=3657#desc9 and + See https://issues.apache.org/jira/browse/SVN-3657#desc9 and http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details. */ -static void +static svn_error_t * remove_non_prop_changes(apr_hash_t *pristine_props, apr_array_header_t *changes) { @@ -391,7 +392,7 @@ remove_non_prop_changes(apr_hash_t *pristine_props, /* For added nodes, there is nothing to filter. */ if (apr_hash_count(pristine_props) == 0) - return; + return SVN_NO_ERROR; for (i = 0; i < changes->nelts; i++) { @@ -404,18 +405,13 @@ remove_non_prop_changes(apr_hash_t *pristine_props, if (old_val && svn_string_compare(old_val, change->value)) { - int j; - - /* Remove the matching change by shifting the rest */ - for (j = i; j < changes->nelts - 1; j++) - { - APR_ARRAY_IDX(changes, j, svn_prop_t) - = APR_ARRAY_IDX(changes, j+1, svn_prop_t); - } - changes->nelts--; + /* Remove the matching change and re-check the current index */ + SVN_ERR(svn_sort__array_delete2(changes, i, 1)); + i--; } } } + return SVN_NO_ERROR; } /* Get the empty file associated with the edit baton. This is cached so @@ -1015,7 +1011,7 @@ close_file(void *file_baton, } if (fb->pristine_props) - remove_non_prop_changes(fb->pristine_props, fb->propchanges); + SVN_ERR(remove_non_prop_changes(fb->pristine_props, fb->propchanges)); right_props = svn_prop__patch(fb->pristine_props, fb->propchanges, fb->pool); @@ -1088,7 +1084,7 @@ close_directory(void *dir_baton, if (db->propchanges->nelts > 0) { - remove_non_prop_changes(pristine_props, db->propchanges); + SVN_ERR(remove_non_prop_changes(pristine_props, db->propchanges)); } if (db->propchanges->nelts > 0 || db->added) diff --git a/subversion/libsvn_client/revert.c b/subversion/libsvn_client/revert.c index d827014ebe1e6..96495f15d2576 100644 --- a/subversion/libsvn_client/revert.c +++ b/subversion/libsvn_client/revert.c @@ -51,42 +51,32 @@ struct revert_with_write_lock_baton { const apr_array_header_t *changelists; svn_boolean_t clear_changelists; svn_boolean_t metadata_only; + svn_boolean_t added_keep_local; svn_client_ctx_t *ctx; }; /* (Note: All arguments are in the baton above.) - Attempt to revert LOCAL_ABSPATH. + Attempt to revert LOCAL_ABSPATH by calling svn_wc_revert6(), which + see for further details. - If DEPTH is svn_depth_empty, revert just the properties on the - directory; else if svn_depth_files, revert the properties and any - files immediately under the directory; else if - svn_depth_immediates, revert all of the preceding plus properties - on immediate subdirectories; else if svn_depth_infinity, revert - path and everything under it fully recursively. - - CHANGELISTS is an array of const char * changelist names, used as a - restrictive filter on items reverted; that is, don't revert any - item unless it's a member of one of those changelists. If - CHANGELISTS is empty (or altogether NULL), no changelist filtering occurs. - - Consult CTX to determine whether or not to revert timestamp to the - time of last commit ('use-commit-times = yes'). - - If PATH is unversioned, return SVN_ERR_UNVERSIONED_RESOURCE. */ + If the target isn't versioned, send a 'skip' notification and return + no error. + */ static svn_error_t * revert(void *baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { struct revert_with_write_lock_baton *b = baton; svn_error_t *err; - err = svn_wc_revert5(b->ctx->wc_ctx, + err = svn_wc_revert6(b->ctx->wc_ctx, b->local_abspath, b->depth, b->use_commit_times, b->changelists, b->clear_changelists, b->metadata_only, + b->added_keep_local, b->ctx->cancel_func, b->ctx->cancel_baton, b->ctx->notify_func2, b->ctx->notify_baton2, scratch_pool); @@ -123,11 +113,12 @@ revert(void *baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) svn_error_t * -svn_client_revert3(const apr_array_header_t *paths, +svn_client_revert4(const apr_array_header_t *paths, svn_depth_t depth, const apr_array_header_t *changelists, svn_boolean_t clear_changelists, svn_boolean_t metadata_only, + svn_boolean_t added_keep_local, svn_client_ctx_t *ctx, apr_pool_t *pool) { @@ -183,6 +174,7 @@ svn_client_revert3(const apr_array_header_t *paths, baton.changelists = changelists; baton.clear_changelists = clear_changelists; baton.metadata_only = metadata_only; + baton.added_keep_local = added_keep_local; baton.ctx = ctx; err = svn_wc__is_wcroot(&wc_root, ctx->wc_ctx, local_abspath, iterpool); diff --git a/subversion/libsvn_client/revisions.c b/subversion/libsvn_client/revisions.c index 4bfbfc3fc32ad..c209c9ea385e6 100644 --- a/subversion/libsvn_client/revisions.c +++ b/subversion/libsvn_client/revisions.c @@ -146,7 +146,14 @@ svn_client__get_revision_number(svn_revnum_t *revnum, scratch_pool)); if (revision->kind == svn_opt_revision_previous) - (*revnum)--; + { + if (*revnum == 0) + return svn_error_createf( + SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Path '%s' has no previous revision"), + svn_dirent_local_style(local_abspath, scratch_pool)); + --(*revnum); + } } break; diff --git a/subversion/libsvn_client/shelf.c b/subversion/libsvn_client/shelf.c new file mode 100644 index 0000000000000..2bba1935384a4 --- /dev/null +++ b/subversion/libsvn_client/shelf.c @@ -0,0 +1,1274 @@ +/* + * shelf.c: implementation of shelving + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + +/* We define this here to remove any further warnings about the usage of + experimental functions in this file. */ +#define SVN_EXPERIMENTAL + +#include "svn_client.h" +#include "svn_wc.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_utf.h" +#include "svn_ctype.h" +#include "svn_props.h" + +#include "client.h" +#include "private/svn_client_shelf.h" +#include "private/svn_client_private.h" +#include "private/svn_wc_private.h" +#include "private/svn_sorts_private.h" +#include "svn_private_config.h" + + +static svn_error_t * +shelf_name_encode(char **encoded_name_p, + const char *name, + apr_pool_t *result_pool) +{ + char *encoded_name + = apr_palloc(result_pool, strlen(name) * 2 + 1); + char *out_pos = encoded_name; + + if (name[0] == '\0') + return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL, + _("Shelf name cannot be the empty string")); + + while (*name) + { + apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++)); + out_pos += 2; + } + *encoded_name_p = encoded_name; + return SVN_NO_ERROR; +} + +static svn_error_t * +shelf_name_decode(char **decoded_name_p, + const char *codename, + apr_pool_t *result_pool) +{ + svn_stringbuf_t *sb + = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool); + const char *input = codename; + + while (*input) + { + int c; + int nchars; + int nitems = sscanf(input, "%02x%n", &c, &nchars); + + if (nitems != 1 || nchars != 2) + return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL, + _("Shelve: Bad encoded name '%s'"), codename); + svn_stringbuf_appendbyte(sb, c); + input += 2; + } + *decoded_name_p = sb->data; + return SVN_NO_ERROR; +} + +/* Set *NAME to the shelf name from FILENAME, if FILENAME names a '.current' + * file, else to NULL. */ +static svn_error_t * +shelf_name_from_filename(char **name, + const char *filename, + apr_pool_t *result_pool) +{ + size_t len = strlen(filename); + static const char suffix[] = ".current"; + size_t suffix_len = sizeof(suffix) - 1; + + if (len > suffix_len && strcmp(filename + len - suffix_len, suffix) == 0) + { + char *codename = apr_pstrndup(result_pool, filename, len - suffix_len); + SVN_ERR(shelf_name_decode(name, codename, result_pool)); + } + else + { + *name = NULL; + } + return SVN_NO_ERROR; +} + +/* Set *DIR to the shelf storage directory inside the WC's administrative + * area. Ensure the directory exists. */ +static svn_error_t * +get_shelves_dir(char **dir, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *experimental_abspath; + + SVN_ERR(svn_wc__get_experimental_dir(&experimental_abspath, + wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + *dir = svn_dirent_join(experimental_abspath, "shelves/v3", result_pool); + + /* Ensure the directory exists. (Other versions of svn don't create it.) */ + SVN_ERR(svn_io_make_dir_recursively(*dir, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the file storage dir for SHELF + * version VERSION, no matter whether it exists. + */ +static svn_error_t * +shelf_version_files_dir_abspath(const char **abspath, + svn_client__shelf_t *shelf, + int version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *codename; + char *filename; + + SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); + filename = apr_psprintf(scratch_pool, "%s-%03d.wc", codename, version); + *abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + return SVN_NO_ERROR; +} + +/* Create a shelf-version object for a version that may or may not already + * exist on disk. + */ +static svn_error_t * +shelf_version_create(svn_client__shelf_version_t **new_version_p, + svn_client__shelf_t *shelf, + int version_number, + apr_pool_t *result_pool) +{ + svn_client__shelf_version_t *shelf_version + = apr_pcalloc(result_pool, sizeof(*shelf_version)); + + shelf_version->shelf = shelf; + shelf_version->version_number = version_number; + SVN_ERR(shelf_version_files_dir_abspath(&shelf_version->files_dir_abspath, + shelf, version_number, + result_pool, result_pool)); + *new_version_p = shelf_version; + return SVN_NO_ERROR; +} + +/* Delete the storage for SHELF:VERSION. */ +static svn_error_t * +shelf_version_delete(svn_client__shelf_t *shelf, + int version, + apr_pool_t *scratch_pool) +{ + const char *files_dir_abspath; + + SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath, + shelf, version, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/, + NULL, NULL, /*cancel*/ + scratch_pool)); + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +get_log_abspath(char **log_abspath, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *codename; + const char *filename; + + SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); + filename = apr_pstrcat(scratch_pool, codename, ".log", SVN_VA_NULL); + *log_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + return SVN_NO_ERROR; +} + +/* Set SHELF->revprops by reading from its storage (the '.log' file). + * Set SHELF->revprops to empty if the storage file does not exist; this + * is not an error. + */ +static svn_error_t * +shelf_read_revprops(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool) +{ + char *log_abspath; + svn_error_t *err; + svn_stream_t *stream; + + SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool)); + + shelf->revprops = apr_hash_make(shelf->pool); + err = svn_stream_open_readonly(&stream, log_abspath, + scratch_pool, scratch_pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + SVN_ERR(svn_hash_read2(shelf->revprops, stream, "PROPS-END", shelf->pool)); + SVN_ERR(svn_stream_close(stream)); + return SVN_NO_ERROR; +} + +/* Write SHELF's revprops to its file storage. + */ +static svn_error_t * +shelf_write_revprops(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool) +{ + char *log_abspath; + apr_file_t *file; + svn_stream_t *stream; + + SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool)); + + SVN_ERR(svn_io_file_open(&file, log_abspath, + APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, + APR_FPROT_OS_DEFAULT, scratch_pool)); + stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool); + + SVN_ERR(svn_hash_write2(shelf->revprops, stream, "PROPS-END", scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_revprop_set(svn_client__shelf_t *shelf, + const char *prop_name, + const svn_string_t *prop_val, + apr_pool_t *scratch_pool) +{ + svn_hash_sets(shelf->revprops, apr_pstrdup(shelf->pool, prop_name), + svn_string_dup(prop_val, shelf->pool)); + SVN_ERR(shelf_write_revprops(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_revprop_set_all(svn_client__shelf_t *shelf, + apr_hash_t *revprop_table, + apr_pool_t *scratch_pool) +{ + if (revprop_table) + shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool); + else + shelf->revprops = apr_hash_make(shelf->pool); + + SVN_ERR(shelf_write_revprops(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_revprop_get(svn_string_t **prop_val, + svn_client__shelf_t *shelf, + const char *prop_name, + apr_pool_t *result_pool) +{ + *prop_val = svn_hash_gets(shelf->revprops, prop_name); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf_revprop_list(apr_hash_t **props, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool) +{ + *props = shelf->revprops; + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +get_current_abspath(char **current_abspath, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool) +{ + char *codename; + char *filename; + + SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); + filename = apr_psprintf(result_pool, "%s.current", codename); + *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + return SVN_NO_ERROR; +} + +/* Read SHELF->max_version from its storage (the '.current' file). + * Set SHELF->max_version to -1 if that file does not exist. + */ +static svn_error_t * +shelf_read_current(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool) +{ + char *current_abspath; + svn_error_t *err; + + SVN_ERR(get_current_abspath(¤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/shelf2.c b/subversion/libsvn_client/shelf2.c new file mode 100644 index 0000000000000..533a961e260d7 --- /dev/null +++ b/subversion/libsvn_client/shelf2.c @@ -0,0 +1,2124 @@ +/* + * shelf2.c: implementation of shelving v2 + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + +/* We define this here to remove any further warnings about the usage of + experimental functions in this file. */ +#define SVN_EXPERIMENTAL + +#include "svn_client.h" +#include "svn_wc.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_hash.h" +#include "svn_utf.h" +#include "svn_ctype.h" +#include "svn_props.h" + +#include "client.h" +#include "private/svn_client_shelf2.h" +#include "private/svn_client_private.h" +#include "private/svn_wc_private.h" +#include "private/svn_sorts_private.h" +#include "svn_private_config.h" + + +static svn_error_t * +shelf_name_encode(char **encoded_name_p, + const char *name, + apr_pool_t *result_pool) +{ + char *encoded_name + = apr_palloc(result_pool, strlen(name) * 2 + 1); + char *out_pos = encoded_name; + + if (name[0] == '\0') + return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL, + _("Shelf name cannot be the empty string")); + + while (*name) + { + apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++)); + out_pos += 2; + } + *encoded_name_p = encoded_name; + return SVN_NO_ERROR; +} + +static svn_error_t * +shelf_name_decode(char **decoded_name_p, + const char *codename, + apr_pool_t *result_pool) +{ + svn_stringbuf_t *sb + = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool); + const char *input = codename; + + while (*input) + { + int c; + int nchars; + int nitems = sscanf(input, "%02x%n", &c, &nchars); + + if (nitems != 1 || nchars != 2) + return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL, + _("Shelve: Bad encoded name '%s'"), codename); + svn_stringbuf_appendbyte(sb, c); + input += 2; + } + *decoded_name_p = sb->data; + return SVN_NO_ERROR; +} + +/* Set *NAME to the shelf name from FILENAME, if FILENAME names a '.current' + * file, else to NULL. */ +static svn_error_t * +shelf_name_from_filename(char **name, + const char *filename, + apr_pool_t *result_pool) +{ + size_t len = strlen(filename); + static const char suffix[] = ".current"; + int suffix_len = sizeof(suffix) - 1; + + if (len > suffix_len && strcmp(filename + len - suffix_len, suffix) == 0) + { + char *codename = apr_pstrndup(result_pool, filename, len - suffix_len); + SVN_ERR(shelf_name_decode(name, codename, result_pool)); + } + else + { + *name = NULL; + } + return SVN_NO_ERROR; +} + +/* Set *DIR to the shelf storage directory inside the WC's administrative + * area. Ensure the directory exists. */ +static svn_error_t * +get_shelves_dir(char **dir, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *experimental_abspath; + + SVN_ERR(svn_wc__get_experimental_dir(&experimental_abspath, + wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + *dir = svn_dirent_join(experimental_abspath, "shelves/v2", result_pool); + + /* Ensure the directory exists. (Other versions of svn don't create it.) */ + SVN_ERR(svn_io_make_dir_recursively(*dir, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the file storage dir for SHELF + * version VERSION, no matter whether it exists. + */ +static svn_error_t * +shelf_version_files_dir_abspath(const char **abspath, + svn_client__shelf2_t *shelf, + int version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *codename; + char *filename; + + SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); + filename = apr_psprintf(scratch_pool, "%s-%03d.d", codename, version); + *abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + return SVN_NO_ERROR; +} + +/* Create a shelf-version object for a version that may or may not already + * exist on disk. + */ +static svn_error_t * +shelf_version_create(svn_client__shelf2_version_t **new_version_p, + svn_client__shelf2_t *shelf, + int version_number, + apr_pool_t *result_pool) +{ + svn_client__shelf2_version_t *shelf_version + = apr_pcalloc(result_pool, sizeof(*shelf_version)); + + shelf_version->shelf = shelf; + shelf_version->version_number = version_number; + SVN_ERR(shelf_version_files_dir_abspath(&shelf_version->files_dir_abspath, + shelf, version_number, + result_pool, result_pool)); + *new_version_p = shelf_version; + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the metadata file for SHELF_VERSION + * node at RELPATH, no matter whether it exists. + */ +static svn_error_t * +get_metadata_abspath(char **abspath, + svn_client__shelf2_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_relpath = apr_psprintf(scratch_pool, "%s.meta", wc_relpath); + *abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + result_pool); + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the base text file for SHELF_VERSION + * node at RELPATH, no matter whether it exists. + */ +static svn_error_t * +get_base_file_abspath(char **base_abspath, + svn_client__shelf2_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_relpath = apr_psprintf(scratch_pool, "%s.base", wc_relpath); + *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + result_pool); + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the working text file for SHELF_VERSION + * node at RELPATH, no matter whether it exists. + */ +static svn_error_t * +get_working_file_abspath(char **work_abspath, + svn_client__shelf2_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_relpath = apr_psprintf(scratch_pool, "%s.work", wc_relpath); + *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + result_pool); + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the base props file for SHELF_VERSION + * node at RELPATH, no matter whether it exists. + */ +static svn_error_t * +get_base_props_abspath(char **base_abspath, + svn_client__shelf2_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_relpath = apr_psprintf(scratch_pool, "%s.base-props", wc_relpath); + *base_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + result_pool); + return SVN_NO_ERROR; +} + +/* Set *ABSPATH to the abspath of the working props file for SHELF_VERSION + * node at RELPATH, no matter whether it exists. + */ +static svn_error_t * +get_working_props_abspath(char **work_abspath, + svn_client__shelf2_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + wc_relpath = apr_psprintf(scratch_pool, "%s.work-props", wc_relpath); + *work_abspath = svn_dirent_join(shelf_version->files_dir_abspath, wc_relpath, + result_pool); + return SVN_NO_ERROR; +} + +/* Delete the storage for SHELF:VERSION. */ +static svn_error_t * +shelf_version_delete(svn_client__shelf2_t *shelf, + int version, + apr_pool_t *scratch_pool) +{ + const char *files_dir_abspath; + + SVN_ERR(shelf_version_files_dir_abspath(&files_dir_abspath, + shelf, version, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_remove_dir2(files_dir_abspath, TRUE /*ignore_enoent*/, + NULL, NULL, /*cancel*/ + scratch_pool)); + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +get_log_abspath(char **log_abspath, + svn_client__shelf2_t *shelf, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *codename; + const char *filename; + + SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); + filename = apr_pstrcat(scratch_pool, codename, ".log", SVN_VA_NULL); + *log_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + return SVN_NO_ERROR; +} + +/* Set SHELF->revprops by reading from its storage (the '.log' file). + * Set SHELF->revprops to empty if the storage file does not exist; this + * is not an error. + */ +static svn_error_t * +shelf_read_revprops(svn_client__shelf2_t *shelf, + apr_pool_t *scratch_pool) +{ + char *log_abspath; + svn_error_t *err; + svn_stream_t *stream; + + SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool)); + + shelf->revprops = apr_hash_make(shelf->pool); + err = svn_stream_open_readonly(&stream, log_abspath, + scratch_pool, scratch_pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + SVN_ERR(svn_hash_read2(shelf->revprops, stream, "PROPS-END", shelf->pool)); + SVN_ERR(svn_stream_close(stream)); + return SVN_NO_ERROR; +} + +/* Write SHELF's revprops to its file storage. + */ +static svn_error_t * +shelf_write_revprops(svn_client__shelf2_t *shelf, + apr_pool_t *scratch_pool) +{ + char *log_abspath; + apr_file_t *file; + svn_stream_t *stream; + + SVN_ERR(get_log_abspath(&log_abspath, shelf, scratch_pool, scratch_pool)); + + SVN_ERR(svn_io_file_open(&file, log_abspath, + APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, + APR_FPROT_OS_DEFAULT, scratch_pool)); + stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool); + + SVN_ERR(svn_hash_write2(shelf->revprops, stream, "PROPS-END", scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_revprop_set(svn_client__shelf2_t *shelf, + const char *prop_name, + const svn_string_t *prop_val, + apr_pool_t *scratch_pool) +{ + svn_hash_sets(shelf->revprops, apr_pstrdup(shelf->pool, prop_name), + svn_string_dup(prop_val, shelf->pool)); + SVN_ERR(shelf_write_revprops(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_revprop_set_all(svn_client__shelf2_t *shelf, + apr_hash_t *revprop_table, + apr_pool_t *scratch_pool) +{ + if (revprop_table) + shelf->revprops = svn_prop_hash_dup(revprop_table, shelf->pool); + else + shelf->revprops = apr_hash_make(shelf->pool); + + SVN_ERR(shelf_write_revprops(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_revprop_get(svn_string_t **prop_val, + svn_client__shelf2_t *shelf, + const char *prop_name, + apr_pool_t *result_pool) +{ + *prop_val = svn_hash_gets(shelf->revprops, prop_name); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_revprop_list(apr_hash_t **props, + svn_client__shelf2_t *shelf, + apr_pool_t *result_pool) +{ + *props = shelf->revprops; + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +get_current_abspath(char **current_abspath, + svn_client__shelf2_t *shelf, + apr_pool_t *result_pool) +{ + char *codename; + char *filename; + + SVN_ERR(shelf_name_encode(&codename, shelf->name, result_pool)); + filename = apr_psprintf(result_pool, "%s.current", codename); + *current_abspath = svn_dirent_join(shelf->shelves_dir, filename, result_pool); + return SVN_NO_ERROR; +} + +/* Read SHELF->max_version from its storage (the '.current' file). + * Set SHELF->max_version to -1 if that file does not exist. + */ +static svn_error_t * +shelf_read_current(svn_client__shelf2_t *shelf, + apr_pool_t *scratch_pool) +{ + char *current_abspath; + svn_error_t *err; + + SVN_ERR(get_current_abspath(¤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__shelf2_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 */ + +/* Create a status struct with all fields initialized to valid values + * representing 'uninteresting' or 'unknown' status. + */ +static svn_wc_status3_t * +status_create(apr_pool_t *result_pool) +{ + svn_wc_status3_t *s = apr_pcalloc(result_pool, sizeof(*s)); + + s->filesize = SVN_INVALID_FILESIZE; + s->versioned = TRUE; + s->node_status = svn_wc_status_none; + s->text_status = svn_wc_status_none; + s->prop_status = svn_wc_status_none; + s->revision = SVN_INVALID_REVNUM; + s->changed_rev = SVN_INVALID_REVNUM; + s->repos_node_status = svn_wc_status_none; + s->repos_text_status = svn_wc_status_none; + s->repos_prop_status = svn_wc_status_none; + s->ood_changed_rev = SVN_INVALID_REVNUM; + return s; +} + +/* Convert from svn_node_kind_t to a single character representation. */ +static char +kind_to_char(svn_node_kind_t kind) +{ + return (kind == svn_node_dir ? 'd' + : kind == svn_node_file ? 'f' + : kind == svn_node_symlink ? 'l' + : '?'); +} + +/* Convert to svn_node_kind_t from a single character representation. */ +static svn_node_kind_t +char_to_kind(char kind) +{ + return (kind == 'd' ? svn_node_dir + : kind == 'f' ? svn_node_file + : kind == 'l' ? svn_node_symlink + : svn_node_unknown); +} + +/* Return the single character representation of STATUS. + * (Similar to subversion/svn/status.c:generate_status_code() + * and subversion/tests/libsvn_client/client-test.c:status_to_char().) */ +static char +status_to_char(enum svn_wc_status_kind status) +{ + switch (status) + { + case svn_wc_status_none: return '.'; + case svn_wc_status_unversioned: return '?'; + case svn_wc_status_normal: return ' '; + case svn_wc_status_added: return 'A'; + case svn_wc_status_missing: return '!'; + case svn_wc_status_deleted: return 'D'; + case svn_wc_status_replaced: return 'R'; + case svn_wc_status_modified: return 'M'; + case svn_wc_status_merged: return 'G'; + case svn_wc_status_conflicted: return 'C'; + case svn_wc_status_ignored: return 'I'; + case svn_wc_status_obstructed: return '~'; + case svn_wc_status_external: return 'X'; + case svn_wc_status_incomplete: return ':'; + default: return '*'; + } +} + +static enum svn_wc_status_kind +char_to_status(char status) +{ + switch (status) + { + case '.': return svn_wc_status_none; + case '?': return svn_wc_status_unversioned; + case ' ': return svn_wc_status_normal; + case 'A': return svn_wc_status_added; + case '!': return svn_wc_status_missing; + case 'D': return svn_wc_status_deleted; + case 'R': return svn_wc_status_replaced; + case 'M': return svn_wc_status_modified; + case 'G': return svn_wc_status_merged; + case 'C': return svn_wc_status_conflicted; + case 'I': return svn_wc_status_ignored; + case '~': return svn_wc_status_obstructed; + case 'X': return svn_wc_status_external; + case ':': return svn_wc_status_incomplete; + default: return (enum svn_wc_status_kind)0; + } +} + +/* Write a serial representation of (some fields of) STATUS to STREAM. + */ +static svn_error_t * +wc_status_serialize(svn_stream_t *stream, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + SVN_ERR(svn_stream_printf(stream, scratch_pool, "%c %c%c%c %ld", + kind_to_char(status->kind), + status_to_char(status->node_status), + status_to_char(status->text_status), + status_to_char(status->prop_status), + status->revision)); + return SVN_NO_ERROR; +} + +/* Read a serial representation of (some fields of) STATUS from STREAM. + */ +static svn_error_t * +wc_status_unserialize(svn_wc_status3_t *status, + svn_stream_t *stream, + apr_pool_t *result_pool) +{ + svn_stringbuf_t *sb; + char *string; + + SVN_ERR(svn_stringbuf_from_stream(&sb, stream, 100, result_pool)); + string = sb->data; + status->kind = char_to_kind(string[0]); + status->node_status = char_to_status(string[2]); + status->text_status = char_to_status(string[3]); + status->prop_status = char_to_status(string[4]); + sscanf(string + 6, "%ld", &status->revision); + return SVN_NO_ERROR; +} + +/* Write status to shelf storage. + */ +static svn_error_t * +status_write(svn_client__shelf2_version_t *shelf_version, + const char *relpath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + char *file_abspath; + svn_stream_t *stream; + + SVN_ERR(get_metadata_abspath(&file_abspath, shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_writable(&stream, file_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(wc_status_serialize(stream, status, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + return SVN_NO_ERROR; +} + +/* Read status from shelf storage. + */ +static svn_error_t * +status_read(svn_wc_status3_t **status, + svn_client__shelf2_version_t *shelf_version, + const char *relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_wc_status3_t *s = status_create(result_pool); + char *file_abspath; + svn_stream_t *stream; + + SVN_ERR(get_metadata_abspath(&file_abspath, shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_readonly(&stream, file_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(wc_status_unserialize(s, stream, result_pool)); + SVN_ERR(svn_stream_close(stream)); + + s->changelist = apr_psprintf(result_pool, "svn:shelf:%s", + shelf_version->shelf->name); + *status = s; + return SVN_NO_ERROR; +} + +/* A visitor function type for use with shelf_status_walk(). + * The same as svn_wc_status_func4_t except relpath instead of abspath. + * Only some fields in STATUS are available. + */ +typedef svn_error_t *(*shelf_status_visitor_t)(void *baton, + const char *relpath, + svn_wc_status3_t *status, + apr_pool_t *scratch_pool); + +/* Baton for shelved_files_walk_visitor(). */ +struct shelf_status_baton_t +{ + svn_client__shelf2_version_t *shelf_version; + const char *top_relpath; + const char *walk_root_abspath; + shelf_status_visitor_t walk_func; + void *walk_baton; +}; + +/* Call BATON->walk_func(BATON->walk_baton, relpath, ...) for the shelved + * 'binary' file stored at ABSPATH. + * Implements svn_io_walk_func_t. */ +static svn_error_t * +shelf_status_visitor(void *baton, + const char *abspath, + const apr_finfo_t *finfo, + apr_pool_t *scratch_pool) +{ + struct shelf_status_baton_t *b = baton; + const char *relpath; + + relpath = svn_dirent_skip_ancestor(b->walk_root_abspath, abspath); + if (finfo->filetype == APR_REG + && (strlen(relpath) >= 5 && strcmp(relpath+strlen(relpath)-5, ".meta") == 0)) + { + svn_wc_status3_t *s; + + relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5); + if (!svn_relpath_skip_ancestor(b->top_relpath, relpath)) + return SVN_NO_ERROR; + + SVN_ERR(status_read(&s, b->shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(b->walk_func(b->walk_baton, relpath, s, scratch_pool)); + } + return SVN_NO_ERROR; +} + +/* Report the shelved status of the path SHELF_VERSION:WC_RELPATH + * via WALK_FUNC(WALK_BATON, ...). + */ +static svn_error_t * +shelf_status_visit_path(svn_client__shelf2_version_t *shelf_version, + const char *wc_relpath, + shelf_status_visitor_t walk_func, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + struct shelf_status_baton_t baton; + char *abspath; + apr_finfo_t finfo; + + baton.shelf_version = shelf_version; + baton.top_relpath = wc_relpath; + baton.walk_root_abspath = shelf_version->files_dir_abspath; + baton.walk_func = walk_func; + baton.walk_baton = walk_baton; + SVN_ERR(get_metadata_abspath(&abspath, shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_stat(&finfo, abspath, APR_FINFO_TYPE, scratch_pool)); + SVN_ERR(shelf_status_visitor(&baton, abspath, &finfo, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Report the shelved status of all the shelved paths in SHELF_VERSION + * via WALK_FUNC(WALK_BATON, ...). + */ +static svn_error_t * +shelf_status_walk(svn_client__shelf2_version_t *shelf_version, + const char *wc_relpath, + shelf_status_visitor_t walk_func, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + struct shelf_status_baton_t baton; + svn_error_t *err; + + baton.shelf_version = shelf_version; + baton.top_relpath = wc_relpath; + baton.walk_root_abspath = shelf_version->files_dir_abspath; + baton.walk_func = walk_func; + baton.walk_baton = walk_baton; + err = svn_io_dir_walk2(baton.walk_root_abspath, 0 /*wanted*/, + shelf_status_visitor, &baton, + scratch_pool); + if (err && APR_STATUS_IS_ENOENT(err->apr_err)) + svn_error_clear(err); + else + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +typedef struct wc_status_baton_t +{ + svn_client__shelf2_version_t *shelf_version; + svn_wc_status_func4_t walk_func; + void *walk_baton; +} wc_status_baton_t; + +static svn_error_t * +wc_status_visitor(void *baton, + const char *relpath, + svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + struct wc_status_baton_t *b = baton; + svn_client__shelf2_t *shelf = b->shelf_version->shelf; + const char *abspath = svn_dirent_join(shelf->wc_root_abspath, relpath, + scratch_pool); + SVN_ERR(b->walk_func(b->walk_baton, abspath, status, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_version_status_walk(svn_client__shelf2_version_t *shelf_version, + const char *wc_relpath, + svn_wc_status_func4_t walk_func, + void *walk_baton, + apr_pool_t *scratch_pool) +{ + wc_status_baton_t baton; + + baton.shelf_version = shelf_version; + baton.walk_func = walk_func; + baton.walk_baton = walk_baton; + SVN_ERR(shelf_status_walk(shelf_version, wc_relpath, + wc_status_visitor, &baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/*-------------------------------------------------------------------------*/ +/* Shelf Storage */ + +/* A baton for use with write_changes_visitor(). */ +typedef struct write_changes_baton_t { + const char *wc_root_abspath; + svn_client__shelf2_version_t *shelf_version; + svn_client_ctx_t *ctx; + svn_boolean_t any_shelved; /* were any paths successfully shelved? */ + svn_client_status_func_t was_shelved_func; + void *was_shelved_baton; + svn_client_status_func_t was_not_shelved_func; + void *was_not_shelved_baton; + apr_pool_t *pool; /* pool for data in 'unshelvable', etc. */ +} write_changes_baton_t; + +/* */ +static svn_error_t * +notify_shelved(write_changes_baton_t *wb, + const char *wc_relpath, + const char *local_abspath, + const svn_wc_status3_t *wc_status, + apr_pool_t *scratch_pool) +{ + if (wb->was_shelved_func) + { + svn_client_status_t *cst; + + SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath, + wc_status, + scratch_pool, scratch_pool)); + SVN_ERR(wb->was_shelved_func(wb->was_shelved_baton, + wc_relpath, cst, scratch_pool)); + } + + wb->any_shelved = TRUE; + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +notify_not_shelved(write_changes_baton_t *wb, + const char *wc_relpath, + const char *local_abspath, + const svn_wc_status3_t *wc_status, + apr_pool_t *scratch_pool) +{ + if (wb->was_not_shelved_func) + { + svn_client_status_t *cst; + + SVN_ERR(svn_client__create_status(&cst, wb->ctx->wc_ctx, local_abspath, + wc_status, + scratch_pool, scratch_pool)); + SVN_ERR(wb->was_not_shelved_func(wb->was_not_shelved_baton, + wc_relpath, cst, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Read BASE_PROPS and WORK_PROPS from the WC, setting each to null if + * the node has no base or working version (respectively). + */ +static svn_error_t * +read_props_from_wc(apr_hash_t **base_props, + apr_hash_t **work_props, + enum svn_wc_status_kind node_status, + const char *from_wc_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (node_status != svn_wc_status_added) + SVN_ERR(svn_wc_get_pristine_props(base_props, ctx->wc_ctx, from_wc_abspath, + result_pool, scratch_pool)); + else + *base_props = NULL; + if (node_status != svn_wc_status_deleted) + SVN_ERR(svn_wc_prop_list2(work_props, ctx->wc_ctx, from_wc_abspath, + result_pool, scratch_pool)); + else + *work_props = NULL; + return SVN_NO_ERROR; +} + +/* Write BASE_PROPS and WORK_PROPS to storage in SHELF_VERSION:WC_RELPATH. + */ +static svn_error_t * +write_props_to_shelf(svn_client__shelf2_version_t *shelf_version, + const char *wc_relpath, + apr_hash_t *base_props, + apr_hash_t *work_props, + apr_pool_t *scratch_pool) +{ + char *stored_props_abspath; + svn_stream_t *stream; + + if (base_props) + { + SVN_ERR(get_base_props_abspath(&stored_props_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_writable(&stream, stored_props_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_hash_write2(base_props, stream, NULL, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + } + + if (work_props) + { + SVN_ERR(get_working_props_abspath(&stored_props_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_writable(&stream, stored_props_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_hash_write2(work_props, stream, NULL, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + } + + return SVN_NO_ERROR; +} + +/* Read BASE_PROPS and WORK_PROPS from storage in SHELF_VERSION:WC_RELPATH. + */ +static svn_error_t * +read_props_from_shelf(apr_hash_t **base_props, + apr_hash_t **work_props, + enum svn_wc_status_kind node_status, + svn_client__shelf2_version_t *shelf_version, + const char *wc_relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + char *stored_props_abspath; + svn_stream_t *stream; + + if (node_status != svn_wc_status_added) + { + *base_props = apr_hash_make(result_pool); + SVN_ERR(get_base_props_abspath(&stored_props_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_readonly(&stream, stored_props_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_hash_read2(*base_props, stream, NULL, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + } + else + *base_props = NULL; + + if (node_status != svn_wc_status_deleted) + { + *work_props = apr_hash_make(result_pool); + SVN_ERR(get_working_props_abspath(&stored_props_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_readonly(&stream, stored_props_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_hash_read2(*work_props, stream, NULL, scratch_pool)); + SVN_ERR(svn_stream_close(stream)); + } + else + *work_props = NULL; + + return SVN_NO_ERROR; +} + +/* Store metadata for any node, and base and working files if it's a file. + * + * Copy the WC base and working files at FROM_WC_ABSPATH to the storage + * area in SHELF_VERSION. + */ +static svn_error_t * +store_file(const char *from_wc_abspath, + const char *wc_relpath, + svn_client__shelf2_version_t *shelf_version, + const svn_wc_status3_t *status, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + char *stored_abspath; + apr_hash_t *base_props, *work_props; + + SVN_ERR(get_working_file_abspath(&stored_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(stored_abspath, + scratch_pool), + scratch_pool)); + SVN_ERR(status_write(shelf_version, wc_relpath, + status, scratch_pool)); + + /* properties */ + SVN_ERR(read_props_from_wc(&base_props, &work_props, + status->node_status, + from_wc_abspath, ctx, + scratch_pool, scratch_pool)); + SVN_ERR(write_props_to_shelf(shelf_version, wc_relpath, + base_props, work_props, + scratch_pool)); + + /* file text */ + if (status->kind == svn_node_file) + { + svn_stream_t *wc_base_stream; + svn_node_kind_t work_kind; + + /* Copy the base file (copy-from base, if copied/moved), if present */ + SVN_ERR(svn_wc_get_pristine_contents2(&wc_base_stream, + ctx->wc_ctx, from_wc_abspath, + scratch_pool, scratch_pool)); + if (wc_base_stream) + { + char *stored_base_abspath; + svn_stream_t *stored_base_stream; + + SVN_ERR(get_base_file_abspath(&stored_base_abspath, + shelf_version, wc_relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_open_writable(&stored_base_stream, + stored_base_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_copy3(wc_base_stream, stored_base_stream, + NULL, NULL, scratch_pool)); + } + + /* Copy the working file, if present */ + SVN_ERR(svn_io_check_path(from_wc_abspath, &work_kind, scratch_pool)); + if (work_kind == svn_node_file) + { + SVN_ERR(svn_io_copy_file(from_wc_abspath, stored_abspath, + TRUE /*copy_perms*/, scratch_pool)); + } + } + return SVN_NO_ERROR; +} + +/* An implementation of svn_wc_status_func4_t. */ +static svn_error_t * +write_changes_visitor(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + write_changes_baton_t *wb = baton; + const char *wc_relpath = svn_dirent_skip_ancestor(wb->wc_root_abspath, + local_abspath); + + /* Catch any conflict, even a tree conflict on a path that has + node-status 'unversioned'. */ + if (status->conflicted) + { + SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, + status, scratch_pool)); + } + else switch (status->node_status) + { + case svn_wc_status_deleted: + case svn_wc_status_added: + case svn_wc_status_replaced: + if (status->kind != svn_node_file + || status->copied) + { + SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, + status, scratch_pool)); + break; + } + /* fall through */ + case svn_wc_status_modified: + { + /* Store metadata, and base and working versions if it's a file */ + SVN_ERR(store_file(local_abspath, wc_relpath, wb->shelf_version, + status, wb->ctx, scratch_pool)); + SVN_ERR(notify_shelved(wb, wc_relpath, local_abspath, + status, scratch_pool)); + break; + } + + case svn_wc_status_incomplete: + if ((status->text_status != svn_wc_status_normal + && status->text_status != svn_wc_status_none) + || (status->prop_status != svn_wc_status_normal + && status->prop_status != svn_wc_status_none)) + { + /* Incomplete, but local modifications */ + SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, + status, scratch_pool)); + } + break; + + case svn_wc_status_conflicted: + case svn_wc_status_missing: + case svn_wc_status_obstructed: + SVN_ERR(notify_not_shelved(wb, wc_relpath, local_abspath, + status, scratch_pool)); + break; + + case svn_wc_status_normal: + case svn_wc_status_ignored: + case svn_wc_status_none: + case svn_wc_status_external: + case svn_wc_status_unversioned: + default: + break; + } + + return SVN_NO_ERROR; +} + +/* A baton for use with changelist_filter_func(). */ +struct changelist_filter_baton_t { + apr_hash_t *changelist_hash; + svn_wc_status_func4_t status_func; + void *status_baton; +}; + +/* Filter out paths that are not in the requested changelist(s). + * Implements svn_wc_status_func4_t. */ +static svn_error_t * +changelist_filter_func(void *baton, + const char *local_abspath, + const svn_wc_status3_t *status, + apr_pool_t *scratch_pool) +{ + struct changelist_filter_baton_t *b = baton; + + if (b->changelist_hash + && (! status->changelist + || ! svn_hash_gets(b->changelist_hash, status->changelist))) + { + return SVN_NO_ERROR; + } + + SVN_ERR(b->status_func(b->status_baton, local_abspath, status, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* + * Walk the WC tree(s) rooted at PATHS, to depth DEPTH, omitting paths that + * are not in one of the CHANGELISTS (if not null). + * + * Call STATUS_FUNC(STATUS_BATON, ...) for each visited path. + * + * PATHS are absolute, or relative to CWD. + */ +static svn_error_t * +wc_walk_status_multi(const apr_array_header_t *paths, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + struct changelist_filter_baton_t cb = {0}; + int i; + + if (changelists && changelists->nelts) + SVN_ERR(svn_hash_from_cstring_keys(&cb.changelist_hash, + changelists, scratch_pool)); + cb.status_func = status_func; + cb.status_baton = status_baton; + + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + + if (svn_path_is_url(path)) + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("'%s' is not a local path"), path); + SVN_ERR(svn_dirent_get_absolute(&path, path, scratch_pool)); + + SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, path, depth, + FALSE /*get_all*/, FALSE /*no_ignore*/, + FALSE /*ignore_text_mods*/, + NULL /*ignore_patterns*/, + changelist_filter_func, &cb, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/** Write local changes to the shelf storage. + * + * @a paths, @a depth, @a changelists: The selection of local paths to diff. + * + * @a paths are relative to CWD (or absolute). + */ +static svn_error_t * +shelf_write_changes(svn_boolean_t *any_shelved, + svn_client__shelf2_version_t *shelf_version, + const apr_array_header_t *paths, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_client_status_func_t shelved_func, + void *shelved_baton, + svn_client_status_func_t not_shelved_func, + void *not_shelved_baton, + const char *wc_root_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + write_changes_baton_t wb = { 0 }; + + wb.wc_root_abspath = wc_root_abspath; + wb.shelf_version = shelf_version; + wb.ctx = ctx; + wb.any_shelved = FALSE; + wb.was_shelved_func = shelved_func; + wb.was_shelved_baton = shelved_baton; + wb.was_not_shelved_func = not_shelved_func; + wb.was_not_shelved_baton = not_shelved_baton; + wb.pool = result_pool; + + /* Walk the WC */ + SVN_ERR(wc_walk_status_multi(paths, depth, changelists, + write_changes_visitor, &wb, + ctx, scratch_pool)); + + *any_shelved = wb.any_shelved; + return SVN_NO_ERROR; +} + +/* Construct a shelf object representing an empty shelf: no versions, + * no revprops, no looking to see if such a shelf exists on disk. + */ +static svn_error_t * +shelf_construct(svn_client__shelf2_t **shelf_p, + const char *name, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + svn_client__shelf2_t *shelf = apr_palloc(result_pool, sizeof(*shelf)); + char *shelves_dir; + + SVN_ERR(svn_client_get_wc_root(&shelf->wc_root_abspath, + local_abspath, ctx, + result_pool, result_pool)); + SVN_ERR(get_shelves_dir(&shelves_dir, + ctx->wc_ctx, local_abspath, + result_pool, result_pool)); + shelf->shelves_dir = shelves_dir; + shelf->ctx = ctx; + shelf->pool = result_pool; + + shelf->name = apr_pstrdup(result_pool, name); + shelf->revprops = apr_hash_make(result_pool); + shelf->max_version = 0; + + *shelf_p = shelf; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_open_existing(svn_client__shelf2_t **shelf_p, + const char *name, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + SVN_ERR(shelf_construct(shelf_p, name, + local_abspath, ctx, result_pool)); + SVN_ERR(shelf_read_revprops(*shelf_p, result_pool)); + SVN_ERR(shelf_read_current(*shelf_p, result_pool)); + if ((*shelf_p)->max_version < 0) + { + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Shelf '%s' not found"), + name); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_open_or_create(svn_client__shelf2_t **shelf_p, + const char *name, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool) +{ + svn_client__shelf2_t *shelf; + + SVN_ERR(shelf_construct(&shelf, name, + local_abspath, ctx, result_pool)); + SVN_ERR(shelf_read_revprops(shelf, result_pool)); + SVN_ERR(shelf_read_current(shelf, result_pool)); + if (shelf->max_version < 0) + { + shelf->max_version = 0; + SVN_ERR(shelf_write_current(shelf, result_pool)); + } + *shelf_p = shelf; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_close(svn_client__shelf2_t *shelf, + apr_pool_t *scratch_pool) +{ + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_delete(const char *name, + const char *local_abspath, + svn_boolean_t dry_run, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_client__shelf2_t *shelf; + int i; + char *abspath; + + SVN_ERR(svn_client__shelf2_open_existing(&shelf, name, + local_abspath, ctx, scratch_pool)); + + /* Remove the versions. */ + for (i = shelf->max_version; i > 0; i--) + { + SVN_ERR(shelf_version_delete(shelf, i, scratch_pool)); + } + + /* Remove the other files */ + SVN_ERR(get_log_abspath(&abspath, shelf, scratch_pool, scratch_pool)); + SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool)); + SVN_ERR(get_current_abspath(&abspath, shelf, scratch_pool)); + SVN_ERR(svn_io_remove_file2(abspath, TRUE /*ignore_enoent*/, scratch_pool)); + + SVN_ERR(svn_client__shelf2_close(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Baton for paths_changed_visitor(). */ +struct paths_changed_walk_baton_t +{ + apr_hash_t *paths_hash; + svn_boolean_t as_abspath; + const char *wc_root_abspath; + apr_pool_t *pool; +}; + +/* Add to the list(s) in BATON, the RELPATH of a shelved 'binary' file. + * Implements shelved_files_walk_func_t. */ +static svn_error_t * +paths_changed_visitor(void *baton, + const char *relpath, + svn_wc_status3_t *s, + apr_pool_t *scratch_pool) +{ + struct paths_changed_walk_baton_t *b = baton; + + relpath = (b->as_abspath + ? svn_dirent_join(b->wc_root_abspath, relpath, b->pool) + : apr_pstrdup(b->pool, relpath)); + svn_hash_sets(b->paths_hash, relpath, relpath); + return SVN_NO_ERROR; +} + +/* Get the paths changed, relative to WC root or as abspaths, as a hash + * and/or an array (in no particular order). + */ +static svn_error_t * +shelf_paths_changed(apr_hash_t **paths_hash_p, + apr_array_header_t **paths_array_p, + svn_client__shelf2_version_t *shelf_version, + svn_boolean_t as_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client__shelf2_t *shelf = shelf_version->shelf; + apr_hash_t *paths_hash = apr_hash_make(result_pool); + struct paths_changed_walk_baton_t baton; + + baton.paths_hash = paths_hash; + baton.as_abspath = as_abspath; + baton.wc_root_abspath = shelf->wc_root_abspath; + baton.pool = result_pool; + SVN_ERR(shelf_status_walk(shelf_version, "", + paths_changed_visitor, &baton, + scratch_pool)); + + if (paths_hash_p) + *paths_hash_p = paths_hash; + if (paths_array_p) + SVN_ERR(svn_hash_keys(paths_array_p, paths_hash, result_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_paths_changed(apr_hash_t **affected_paths, + svn_client__shelf2_version_t *shelf_version, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + SVN_ERR(shelf_paths_changed(affected_paths, NULL, shelf_version, + FALSE /*as_abspath*/, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +/* Send a notification */ +static svn_error_t * +send_notification(const char *local_abspath, + svn_wc_notify_action_t action, + svn_node_kind_t kind, + svn_wc_notify_state_t content_state, + svn_wc_notify_state_t prop_state, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool) +{ + if (notify_func) + { + svn_wc_notify_t *notify + = svn_wc_create_notify(local_abspath, action, scratch_pool); + + notify->kind = kind; + notify->content_state = content_state; + notify->prop_state = prop_state; + notify_func(notify_baton, notify, scratch_pool); + } + + return SVN_NO_ERROR; +} + +/* Merge a shelved change into WC_ABSPATH. + */ +static svn_error_t * +wc_file_merge(const char *wc_abspath, + const char *left_file, + const char *right_file, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_wc_notify_state_t property_state; + svn_boolean_t has_local_mods; + enum svn_wc_merge_outcome_t content_outcome; + const char *target_label, *left_label, *right_label; + apr_array_header_t *prop_changes; + + /* xgettext: the '.working', '.merge-left' and '.merge-right' strings + are used to tag onto a file name in case of a merge conflict */ + target_label = apr_psprintf(scratch_pool, _(".working")); + left_label = apr_psprintf(scratch_pool, _(".merge-left")); + right_label = apr_psprintf(scratch_pool, _(".merge-right")); + + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool)); + SVN_ERR(svn_wc_text_modified_p2(&has_local_mods, ctx->wc_ctx, + wc_abspath, FALSE, scratch_pool)); + + /* Do property merge and text merge in one step so that keyword expansion + takes into account the new property values. */ + SVN_WC__CALL_WITH_WRITE_LOCK( + svn_wc_merge5(&content_outcome, &property_state, ctx->wc_ctx, + left_file, right_file, wc_abspath, + left_label, right_label, target_label, + NULL, NULL, /*left, right conflict-versions*/ + FALSE /*dry_run*/, NULL /*diff3_cmd*/, + NULL /*merge_options*/, + left_props, prop_changes, + NULL, NULL, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool), + ctx->wc_ctx, wc_abspath, + FALSE /*lock_anchor*/, scratch_pool); + + return SVN_NO_ERROR; +} + +/* Merge a shelved change (of properties) into the dir at WC_ABSPATH. + */ +static svn_error_t * +wc_dir_props_merge(const char *wc_abspath, + /*const*/ apr_hash_t *left_props, + /*const*/ apr_hash_t *right_props, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *prop_changes; + svn_wc_notify_state_t property_state; + + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool)); + SVN_WC__CALL_WITH_WRITE_LOCK( + svn_wc_merge_props3(&property_state, ctx->wc_ctx, + wc_abspath, + NULL, NULL, /*left, right conflict-versions*/ + left_props, prop_changes, + FALSE /*dry_run*/, + NULL, NULL, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool), + ctx->wc_ctx, wc_abspath, + FALSE /*lock_anchor*/, scratch_pool); + + return SVN_NO_ERROR; +} + +/* Apply a shelved "delete" to TO_WC_ABSPATH. + */ +static svn_error_t * +wc_node_delete(const char *to_wc_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + SVN_WC__CALL_WITH_WRITE_LOCK( + svn_wc_delete4(ctx->wc_ctx, + to_wc_abspath, + FALSE /*keep_local*/, + TRUE /*delete_unversioned_target*/, + NULL, NULL, NULL, NULL, /*cancel, notify*/ + scratch_pool), + ctx->wc_ctx, to_wc_abspath, + TRUE /*lock_anchor*/, scratch_pool); + return SVN_NO_ERROR; +} + +/* Apply a shelved "add" to TO_WC_ABSPATH. + * The node must already exist on disk, in a versioned parent dir. + */ +static svn_error_t * +wc_node_add(const char *to_wc_abspath, + apr_hash_t *work_props, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + /* If it was not already versioned, schedule the node for addition. + (Do not apply autoprops, because this isn't a user-facing "add" but + restoring a previously saved state.) */ + SVN_WC__CALL_WITH_WRITE_LOCK( + svn_wc_add_from_disk3(ctx->wc_ctx, + to_wc_abspath, work_props, + FALSE /* skip checks */, + NULL, NULL, scratch_pool), + ctx->wc_ctx, to_wc_abspath, + TRUE /*lock_anchor*/, scratch_pool); + return SVN_NO_ERROR; +} + +/* Baton for apply_file_visitor(). */ +struct apply_files_baton_t +{ + svn_client__shelf2_version_t *shelf_version; + svn_boolean_t test_only; /* only check whether it would conflict */ + svn_boolean_t conflict; /* would it conflict? */ + svn_client_ctx_t *ctx; +}; + +/* Copy the file RELPATH from shelf binary file storage to the WC. + * + * If it is not already versioned, schedule the file for addition. + * + * Make any missing parent directories. + * + * In test mode (BATON->test_only): set BATON->conflict if we can't apply + * the change to WC at RELPATH without conflict. But in fact, just check + * if WC at RELPATH is locally modified. + * + * Implements shelved_files_walk_func_t. */ +static svn_error_t * +apply_file_visitor(void *baton, + const char *relpath, + svn_wc_status3_t *s, + apr_pool_t *scratch_pool) +{ + struct apply_files_baton_t *b = baton; + const char *wc_root_abspath = b->shelf_version->shelf->wc_root_abspath; + char *stored_base_abspath, *stored_work_abspath; + apr_hash_t *base_props, *work_props; + const char *to_wc_abspath = svn_dirent_join(wc_root_abspath, relpath, + scratch_pool); + const char *to_dir_abspath = svn_dirent_dirname(to_wc_abspath, scratch_pool); + + SVN_ERR(get_base_file_abspath(&stored_base_abspath, + b->shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(get_working_file_abspath(&stored_work_abspath, + b->shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(read_props_from_shelf(&base_props, &work_props, + s->node_status, + b->shelf_version, relpath, + scratch_pool, scratch_pool)); + + if (b->test_only) + { + svn_wc_status3_t *status; + + SVN_ERR(svn_wc_status3(&status, b->ctx->wc_ctx, to_wc_abspath, + scratch_pool, scratch_pool)); + switch (status->node_status) + { + case svn_wc_status_normal: + case svn_wc_status_none: + break; + default: + b->conflict = TRUE; + } + + return SVN_NO_ERROR; + } + + /* Handle 'delete' and the delete half of 'replace' */ + if (s->node_status == svn_wc_status_deleted + || s->node_status == svn_wc_status_replaced) + { + SVN_ERR(wc_node_delete(to_wc_abspath, b->ctx, scratch_pool)); + if (s->node_status != svn_wc_status_replaced) + { + SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_delete, + s->kind, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable, + b->ctx->notify_func2, b->ctx->notify_baton2, + scratch_pool)); + } + } + + /* If we can merge a file, do so. */ + if (s->node_status == svn_wc_status_modified) + { + if (s->kind == svn_node_dir) + { + SVN_ERR(wc_dir_props_merge(to_wc_abspath, + base_props, work_props, + b->ctx, scratch_pool, scratch_pool)); + } + else if (s->kind == svn_node_file) + { + SVN_ERR(wc_file_merge(to_wc_abspath, + stored_base_abspath, stored_work_abspath, + base_props, work_props, + b->ctx, scratch_pool)); + } + SVN_ERR(send_notification(to_wc_abspath, svn_wc_notify_update_update, + s->kind, + (s->kind == svn_node_dir) + ? svn_wc_notify_state_inapplicable + : svn_wc_notify_state_merged, + (s->kind == svn_node_dir) + ? svn_wc_notify_state_merged + : svn_wc_notify_state_unknown, + b->ctx->notify_func2, b->ctx->notify_baton2, + scratch_pool)); + } + + /* For an added file, copy it into the WC and ensure it's versioned. */ + if (s->node_status == svn_wc_status_added + || s->node_status == svn_wc_status_replaced) + { + if (s->kind == svn_node_dir) + { + SVN_ERR(svn_io_make_dir_recursively(to_wc_abspath, scratch_pool)); + } + else if (s->kind == svn_node_file) + { + SVN_ERR(svn_io_make_dir_recursively(to_dir_abspath, scratch_pool)); + SVN_ERR(svn_io_copy_file(stored_work_abspath, to_wc_abspath, + TRUE /*copy_perms*/, scratch_pool)); + } + SVN_ERR(wc_node_add(to_wc_abspath, work_props, b->ctx, scratch_pool)); + SVN_ERR(send_notification(to_wc_abspath, + (s->node_status == svn_wc_status_replaced) + ? svn_wc_notify_update_replace + : svn_wc_notify_update_add, + s->kind, + svn_wc_notify_state_inapplicable, + svn_wc_notify_state_inapplicable, + b->ctx->notify_func2, b->ctx->notify_baton2, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/*-------------------------------------------------------------------------*/ +/* Diff */ + +/* */ +static svn_error_t * +file_changed(svn_client__shelf2_version_t *shelf_version, + const char *relpath, + svn_wc_status3_t *s, + const svn_diff_tree_processor_t *diff_processor, + svn_diff_source_t *left_source, + svn_diff_source_t *right_source, + const char *left_stored_abspath, + const char *right_stored_abspath, + void *dir_baton, + apr_pool_t *scratch_pool) +{ + void *fb; + svn_boolean_t skip = FALSE; + + SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath, + left_source, right_source, + NULL /*copyfrom*/, + dir_baton, diff_processor, + scratch_pool, scratch_pool)); + if (!skip) + { + apr_hash_t *left_props, *right_props; + apr_array_header_t *prop_changes; + + SVN_ERR(read_props_from_shelf(&left_props, &right_props, + s->node_status, shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, + scratch_pool)); + SVN_ERR(diff_processor->file_changed( + relpath, + left_source, right_source, + left_stored_abspath, right_stored_abspath, + left_props, right_props, + TRUE /*file_modified*/, prop_changes, + fb, diff_processor, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +file_deleted(svn_client__shelf2_version_t *shelf_version, + const char *relpath, + svn_wc_status3_t *s, + const svn_diff_tree_processor_t *diff_processor, + svn_diff_source_t *left_source, + const char *left_stored_abspath, + void *dir_baton, + apr_pool_t *scratch_pool) +{ + void *fb; + svn_boolean_t skip = FALSE; + + SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath, + left_source, NULL, NULL /*copyfrom*/, + dir_baton, diff_processor, + scratch_pool, scratch_pool)); + if (!skip) + { + apr_hash_t *left_props, *right_props; + + SVN_ERR(read_props_from_shelf(&left_props, &right_props, + s->node_status, shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(diff_processor->file_deleted(relpath, + left_source, + left_stored_abspath, + left_props, + fb, diff_processor, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* */ +static svn_error_t * +file_added(svn_client__shelf2_version_t *shelf_version, + const char *relpath, + svn_wc_status3_t *s, + const svn_diff_tree_processor_t *diff_processor, + svn_diff_source_t *right_source, + const char *right_stored_abspath, + void *dir_baton, + apr_pool_t *scratch_pool) +{ + void *fb; + svn_boolean_t skip = FALSE; + + SVN_ERR(diff_processor->file_opened(&fb, &skip, relpath, + NULL, right_source, NULL /*copyfrom*/, + dir_baton, diff_processor, + scratch_pool, scratch_pool)); + if (!skip) + { + apr_hash_t *left_props, *right_props; + + SVN_ERR(read_props_from_shelf(&left_props, &right_props, + s->node_status, shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(diff_processor->file_added( + relpath, + NULL /*copyfrom_source*/, right_source, + NULL /*copyfrom_abspath*/, right_stored_abspath, + NULL /*copyfrom_props*/, right_props, + fb, diff_processor, scratch_pool)); + } + + return SVN_NO_ERROR; +} + +/* Baton for diff_visitor(). */ +struct diff_baton_t +{ + svn_client__shelf2_version_t *shelf_version; + const char *top_relpath; /* top of diff, relative to shelf */ + const char *walk_root_abspath; + const svn_diff_tree_processor_t *diff_processor; +}; + +/* Drive BATON->diff_processor. + * Implements svn_io_walk_func_t. */ +static svn_error_t * +diff_visitor(void *baton, + const char *abspath, + const apr_finfo_t *finfo, + apr_pool_t *scratch_pool) +{ + struct diff_baton_t *b = baton; + const char *relpath; + + relpath = svn_dirent_skip_ancestor(b->walk_root_abspath, abspath); + if (finfo->filetype == APR_REG + && (strlen(relpath) >= 5 && strcmp(relpath+strlen(relpath)-5, ".meta") == 0)) + { + svn_wc_status3_t *s; + void *db = NULL; + svn_diff_source_t *left_source; + svn_diff_source_t *right_source; + char *left_stored_abspath, *right_stored_abspath; + + relpath = apr_pstrndup(scratch_pool, relpath, strlen(relpath) - 5); + if (!svn_relpath_skip_ancestor(b->top_relpath, relpath)) + return SVN_NO_ERROR; + + SVN_ERR(status_read(&s, b->shelf_version, relpath, + scratch_pool, scratch_pool)); + + left_source = svn_diff__source_create(s->revision, scratch_pool); + right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); + SVN_ERR(get_base_file_abspath(&left_stored_abspath, + b->shelf_version, relpath, + scratch_pool, scratch_pool)); + SVN_ERR(get_working_file_abspath(&right_stored_abspath, + b->shelf_version, relpath, + scratch_pool, scratch_pool)); + + switch (s->node_status) + { + case svn_wc_status_modified: + SVN_ERR(file_changed(b->shelf_version, relpath, s, + b->diff_processor, + left_source, right_source, + left_stored_abspath, right_stored_abspath, + db, scratch_pool)); + break; + case svn_wc_status_added: + SVN_ERR(file_added(b->shelf_version, relpath, s, + b->diff_processor, + right_source, right_stored_abspath, + db, scratch_pool)); + break; + case svn_wc_status_deleted: + SVN_ERR(file_deleted(b->shelf_version, relpath, s, + b->diff_processor, + left_source, left_stored_abspath, + db, scratch_pool)); + break; + case svn_wc_status_replaced: + SVN_ERR(file_deleted(b->shelf_version, relpath, s, + b->diff_processor, + left_source, left_stored_abspath, + db, scratch_pool)); + SVN_ERR(file_added(b->shelf_version, relpath, s, + b->diff_processor, + right_source, right_stored_abspath, + db, scratch_pool)); + default: + break; + } + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_test_apply_file(svn_boolean_t *conflict_p, + svn_client__shelf2_version_t *shelf_version, + const char *file_relpath, + apr_pool_t *scratch_pool) +{ + struct apply_files_baton_t baton = {0}; + + baton.shelf_version = shelf_version; + baton.test_only = TRUE; + baton.conflict = FALSE; + baton.ctx = shelf_version->shelf->ctx; + SVN_ERR(shelf_status_visit_path(shelf_version, file_relpath, + apply_file_visitor, &baton, + scratch_pool)); + *conflict_p = baton.conflict; + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_apply(svn_client__shelf2_version_t *shelf_version, + svn_boolean_t dry_run, + apr_pool_t *scratch_pool) +{ + struct apply_files_baton_t baton = {0}; + + baton.shelf_version = shelf_version; + baton.ctx = shelf_version->shelf->ctx; + SVN_ERR(shelf_status_walk(shelf_version, "", + apply_file_visitor, &baton, + scratch_pool)); + + svn_io_sleep_for_timestamps(shelf_version->shelf->wc_root_abspath, + scratch_pool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_unapply(svn_client__shelf2_version_t *shelf_version, + svn_boolean_t dry_run, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *targets; + + SVN_ERR(shelf_paths_changed(NULL, &targets, shelf_version, + TRUE /*as_abspath*/, + scratch_pool, scratch_pool)); + if (!dry_run) + { + SVN_ERR(svn_client_revert4(targets, svn_depth_empty, + NULL /*changelists*/, + FALSE /*clear_changelists*/, + FALSE /*metadata_only*/, + FALSE /*added_keep_local*/, + shelf_version->shelf->ctx, scratch_pool)); + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_delete_newer_versions(svn_client__shelf2_t *shelf, + svn_client__shelf2_version_t *shelf_version, + apr_pool_t *scratch_pool) +{ + int previous_version = shelf_version ? shelf_version->version_number : 0; + int i; + + /* Delete any newer checkpoints */ + for (i = shelf->max_version; i > previous_version; i--) + { + SVN_ERR(shelf_version_delete(shelf, i, scratch_pool)); + } + + shelf->max_version = previous_version; + SVN_ERR(shelf_write_current(shelf, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_diff(svn_client__shelf2_version_t *shelf_version, + const char *shelf_relpath, + svn_depth_t depth, + svn_boolean_t ignore_ancestry, + const svn_diff_tree_processor_t *diff_processor, + apr_pool_t *scratch_pool) +{ + struct diff_baton_t baton; + + if (shelf_version->version_number == 0) + return SVN_NO_ERROR; + + baton.shelf_version = shelf_version; + baton.top_relpath = shelf_relpath; + baton.walk_root_abspath = shelf_version->files_dir_abspath; + baton.diff_processor = diff_processor; + SVN_ERR(svn_io_dir_walk2(baton.walk_root_abspath, 0 /*wanted*/, + diff_visitor, &baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_save_new_version3(svn_client__shelf2_version_t **new_version_p, + svn_client__shelf2_t *shelf, + const apr_array_header_t *paths, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_client_status_func_t shelved_func, + void *shelved_baton, + svn_client_status_func_t not_shelved_func, + void *not_shelved_baton, + apr_pool_t *scratch_pool) +{ + int next_version = shelf->max_version + 1; + svn_client__shelf2_version_t *new_shelf_version; + svn_boolean_t any_shelved; + + SVN_ERR(shelf_version_create(&new_shelf_version, + shelf, next_version, scratch_pool)); + SVN_ERR(shelf_write_changes(&any_shelved, + new_shelf_version, + paths, depth, changelists, + shelved_func, shelved_baton, + not_shelved_func, not_shelved_baton, + shelf->wc_root_abspath, + shelf->ctx, scratch_pool, scratch_pool)); + + if (any_shelved) + { + shelf->max_version = next_version; + SVN_ERR(shelf_write_current(shelf, scratch_pool)); + + if (new_version_p) + SVN_ERR(svn_client__shelf2_version_open(new_version_p, shelf, next_version, + scratch_pool, scratch_pool)); + } + else + { + if (new_version_p) + *new_version_p = NULL; + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_get_log_message(char **log_message, + svn_client__shelf2_t *shelf, + apr_pool_t *result_pool) +{ + svn_string_t *propval = svn_hash_gets(shelf->revprops, SVN_PROP_REVISION_LOG); + + if (propval) + *log_message = apr_pstrdup(result_pool, propval->data); + else + *log_message = NULL; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_set_log_message(svn_client__shelf2_t *shelf, + const char *message, + apr_pool_t *scratch_pool) +{ + svn_string_t *propval + = message ? svn_string_create(message, shelf->pool) : NULL; + + SVN_ERR(svn_client__shelf2_revprop_set(shelf, SVN_PROP_REVISION_LOG, propval, + scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_list(apr_hash_t **shelf_infos, + const char *local_abspath, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + const char *wc_root_abspath; + char *shelves_dir; + apr_hash_t *dirents; + apr_hash_index_t *hi; + + SVN_ERR(svn_wc__get_wcroot(&wc_root_abspath, ctx->wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/, + result_pool, scratch_pool)); + + *shelf_infos = apr_hash_make(result_pool); + + /* Remove non-shelves */ + for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi)) + { + const char *filename = apr_hash_this_key(hi); + svn_io_dirent2_t *dirent = apr_hash_this_val(hi); + char *name = NULL; + + svn_error_clear(shelf_name_from_filename(&name, filename, result_pool)); + if (name && dirent->kind == svn_node_file) + { + svn_client__shelf2_info_t *info + = apr_palloc(result_pool, sizeof(*info)); + + info->mtime = dirent->mtime; + svn_hash_sets(*shelf_infos, name, info); + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_version_open(svn_client__shelf2_version_t **shelf_version_p, + svn_client__shelf2_t *shelf, + int version_number, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_client__shelf2_version_t *shelf_version; + const svn_io_dirent2_t *dirent; + + SVN_ERR(shelf_version_create(&shelf_version, + shelf, version_number, result_pool)); + SVN_ERR(svn_io_stat_dirent2(&dirent, + shelf_version->files_dir_abspath, + FALSE /*verify_truename*/, + TRUE /*ignore_enoent*/, + result_pool, scratch_pool)); + if (dirent->kind == svn_node_none) + { + return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Shelf '%s' version %d not found"), + shelf->name, version_number); + } + shelf_version->mtime = dirent->mtime; + *shelf_version_p = shelf_version; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_get_newest_version(svn_client__shelf2_version_t **shelf_version_p, + svn_client__shelf2_t *shelf, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + if (shelf->max_version == 0) + { + *shelf_version_p = NULL; + return SVN_NO_ERROR; + } + + SVN_ERR(svn_client__shelf2_version_open(shelf_version_p, + shelf, shelf->max_version, + result_pool, scratch_pool)); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client__shelf2_get_all_versions(apr_array_header_t **versions_p, + svn_client__shelf2_t *shelf, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + int i; + + *versions_p = apr_array_make(result_pool, shelf->max_version - 1, + sizeof(svn_client__shelf2_version_t *)); + + for (i = 1; i <= shelf->max_version; i++) + { + svn_client__shelf2_version_t *shelf_version; + + SVN_ERR(svn_client__shelf2_version_open(&shelf_version, + shelf, i, + result_pool, scratch_pool)); + APR_ARRAY_PUSH(*versions_p, svn_client__shelf2_version_t *) = shelf_version; + } + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_client/shelve.c b/subversion/libsvn_client/shelve.c deleted file mode 100644 index af8dd67bc6053..0000000000000 --- a/subversion/libsvn_client/shelve.c +++ /dev/null @@ -1,552 +0,0 @@ -/* - * shelve.c: implementation of the 'shelve' commands - * - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - */ - -/* ==================================================================== */ - -/* We define this here to remove any further warnings about the usage of - experimental functions in this file. */ -#define SVN_EXPERIMENTAL - -#include "svn_client.h" -#include "svn_wc.h" -#include "svn_pools.h" -#include "svn_dirent_uri.h" -#include "svn_path.h" -#include "svn_hash.h" -#include "svn_utf.h" -#include "svn_ctype.h" - -#include "client.h" -#include "private/svn_client_private.h" -#include "private/svn_wc_private.h" -#include "svn_private_config.h" - - -static svn_error_t * -shelf_name_encode(char **encoded_name_p, - const char *name, - apr_pool_t *result_pool) -{ - char *encoded_name - = apr_palloc(result_pool, strlen(name) * 2 + 1); - char *out_pos = encoded_name; - - if (name[0] == '\0') - return svn_error_create(SVN_ERR_BAD_CHANGELIST_NAME, NULL, - _("Shelf name cannot be the empty string")); - - while (*name) - { - apr_snprintf(out_pos, 3, "%02x", (unsigned char)(*name++)); - out_pos += 2; - } - *encoded_name_p = encoded_name; - return SVN_NO_ERROR; -} - -static svn_error_t * -shelf_name_decode(char **decoded_name_p, - const char *codename, - apr_pool_t *result_pool) -{ - svn_stringbuf_t *sb - = svn_stringbuf_create_ensure(strlen(codename) / 2, result_pool); - const char *input = codename; - - while (*input) - { - int c; - int nchars; - int nitems = sscanf(input, "%02x%n", &c, &nchars); - - if (nitems != 1 || nchars != 2) - return svn_error_createf(SVN_ERR_BAD_CHANGELIST_NAME, NULL, - _("Shelve: Bad encoded name '%s'"), codename); - svn_stringbuf_appendbyte(sb, c); - input += 2; - } - *decoded_name_p = sb->data; - return SVN_NO_ERROR; -} - -/* Set *NAME to the shelf name from FILENAME. */ -static svn_error_t * -shelf_name_from_filename(char **name, - const char *filename, - apr_pool_t *result_pool) -{ - size_t len = strlen(filename); - - if (len > 6 && strcmp(filename + len - 6, ".patch") == 0) - { - char *codename = apr_pstrndup(result_pool, filename, len - 6); - SVN_ERR(shelf_name_decode(name, codename, result_pool)); - } - return SVN_NO_ERROR; -} - -/* Set *PATCH_ABSPATH to the abspath of the patch file for shelved change - * NAME, no matter whether it exists. - */ -static svn_error_t * -get_patch_abspath(char **patch_abspath, - const char *name, - const char *wc_root_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - char *dir; - char *filename; - - SVN_ERR(svn_wc__get_shelves_dir(&dir, ctx->wc_ctx, wc_root_abspath, - scratch_pool, scratch_pool)); - SVN_ERR(shelf_name_encode(&filename, name, scratch_pool)); - filename = apr_pstrcat(scratch_pool, filename, ".patch", SVN_VA_NULL); - *patch_abspath = svn_dirent_join(dir, filename, result_pool); - return SVN_NO_ERROR; -} - -/** Write local changes to a patch file for shelved change @a name. - * - * @a message: An optional log message. - * - * @a wc_root_abspath: The WC root dir. - * - * @a overwrite_existing: If a file at @a patch_abspath exists, overwrite it. - * - * @a paths, @a depth, @a changelists: The selection of local paths to diff. - */ -static svn_error_t * -shelf_write_patch(const char *name, - const char *message, - const char *wc_root_abspath, - svn_boolean_t overwrite_existing, - const apr_array_header_t *paths, - svn_depth_t depth, - const apr_array_header_t *changelists, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - char *patch_abspath; - apr_int32_t flag; - apr_file_t *outfile; - svn_stream_t *outstream; - svn_stream_t *errstream; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - int i; - svn_opt_revision_t peg_revision = {svn_opt_revision_unspecified, {0}}; - svn_opt_revision_t start_revision = {svn_opt_revision_base, {0}}; - svn_opt_revision_t end_revision = {svn_opt_revision_working, {0}}; - - SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, - ctx, scratch_pool, scratch_pool)); - - /* Get streams for the output and any error output of the diff. */ - /* ### svn_stream_open_writable() doesn't work here: the buffering - goes wrong so that diff headers appear after their hunks. - For now, fix by opening the file without APR_BUFFERED. */ - flag = APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE; - if (! overwrite_existing) - flag |= APR_FOPEN_EXCL; - SVN_ERR(svn_io_file_open(&outfile, patch_abspath, - flag, APR_FPROT_OS_DEFAULT, scratch_pool)); - outstream = svn_stream_from_aprfile2(outfile, FALSE /*disown*/, scratch_pool); - SVN_ERR(svn_stream_for_stderr(&errstream, scratch_pool)); - - /* Write the patch file header (log message, etc.) */ - if (message) - { - SVN_ERR(svn_stream_printf(outstream, scratch_pool, "%s\n", - message)); - } - SVN_ERR(svn_stream_printf(outstream, scratch_pool, - "--This line, and those below, will be ignored--\n\n")); - SVN_ERR(svn_stream_printf(outstream, scratch_pool, - "--This patch was generated by 'svn shelve'--\n\n")); - - for (i = 0; i < paths->nelts; i++) - { - const char *path = APR_ARRAY_IDX(paths, i, const char *); - - if (svn_path_is_url(path)) - return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, - _("'%s' is not a local path"), path); - SVN_ERR(svn_dirent_get_absolute(&path, path, scratch_pool)); - - SVN_ERR(svn_client_diff_peg6( - NULL /*options*/, - path, - &peg_revision, - &start_revision, - &end_revision, - wc_root_abspath, - depth, - TRUE /*notice_ancestry*/, - FALSE /*no_diff_added*/, - FALSE /*no_diff_deleted*/, - TRUE /*show_copies_as_adds*/, - FALSE /*ignore_content_type: FALSE -> omit binary files*/, - FALSE /*ignore_properties*/, - FALSE /*properties_only*/, - FALSE /*use_git_diff_format*/, - SVN_APR_LOCALE_CHARSET, - outstream, - errstream, - changelists, - ctx, iterpool)); - } - SVN_ERR(svn_stream_close(outstream)); - SVN_ERR(svn_stream_close(errstream)); - - return SVN_NO_ERROR; -} - -/** Apply the patch file for shelved change @a name to the WC. - * - * @a wc_root_abspath: The WC root dir. - * - * @a reverse: Apply the patch in reverse. - * - * @a dry_run: Don't really apply the changes, just notify what would be done. - */ -static svn_error_t * -shelf_apply_patch(const char *name, - const char *wc_root_abspath, - svn_boolean_t reverse, - svn_boolean_t dry_run, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - char *patch_abspath; - - SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, - ctx, scratch_pool, scratch_pool)); - SVN_ERR(svn_client_patch(patch_abspath, wc_root_abspath, - dry_run, 0 /*strip*/, - reverse, - FALSE /*ignore_whitespace*/, - TRUE /*remove_tempfiles*/, NULL, NULL, - ctx, scratch_pool)); - - return SVN_NO_ERROR; -} - -/** Delete the patch file for shelved change @a name. - * - * @a wc_root_abspath: The WC root dir. - */ -static svn_error_t * -shelf_delete_patch(const char *name, - const char *wc_root_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - char *patch_abspath, *to_abspath; - - SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, - ctx, scratch_pool, scratch_pool)); - to_abspath = apr_pstrcat(scratch_pool, patch_abspath, ".bak", SVN_VA_NULL); - - /* remove any previous backup */ - SVN_ERR(svn_io_remove_file2(to_abspath, TRUE /*ignore_enoent*/, - scratch_pool)); - - /* move the patch to a backup file */ - SVN_ERR(svn_io_file_rename2(patch_abspath, to_abspath, FALSE /*flush_to_disk*/, - scratch_pool)); - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelve(const char *name, - const apr_array_header_t *paths, - svn_depth_t depth, - const apr_array_header_t *changelists, - svn_boolean_t keep_local, - svn_boolean_t dry_run, - svn_client_ctx_t *ctx, - apr_pool_t *pool) -{ - const char *local_abspath; - const char *wc_root_abspath; - const char *message = ""; - svn_error_t *err; - - /* ### TODO: check all paths are in same WC; for now use first path */ - SVN_ERR(svn_dirent_get_absolute(&local_abspath, - APR_ARRAY_IDX(paths, 0, char *), pool)); - SVN_ERR(svn_client_get_wc_root(&wc_root_abspath, - local_abspath, ctx, pool, pool)); - - /* Fetch the log message and any other revprops */ - if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) - { - const char *tmp_file; - apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(void *)); - - SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, - ctx, pool)); - if (! message) - return SVN_NO_ERROR; - } - - err = shelf_write_patch(name, message, wc_root_abspath, - FALSE /*overwrite_existing*/, - paths, depth, changelists, - ctx, pool); - if (err && APR_STATUS_IS_EEXIST(err->apr_err)) - { - return svn_error_quick_wrapf(err, - "Shelved change '%s' already exists", - name); - } - else - SVN_ERR(err); - - if (!keep_local) - { - /* Reverse-apply the patch. This should be a safer way to remove those - changes from the WC than running a 'revert' operation. */ - SVN_ERR(shelf_apply_patch(name, wc_root_abspath, - TRUE /*reverse*/, dry_run, - ctx, pool)); - } - - if (dry_run) - { - SVN_ERR(shelf_delete_patch(name, wc_root_abspath, - ctx, pool)); - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_unshelve(const char *name, - const char *local_abspath, - svn_boolean_t keep, - svn_boolean_t dry_run, - svn_client_ctx_t *ctx, - apr_pool_t *pool) -{ - const char *wc_root_abspath; - svn_error_t *err; - - SVN_ERR(svn_client_get_wc_root(&wc_root_abspath, - local_abspath, ctx, pool, pool)); - - /* Apply the patch. */ - err = shelf_apply_patch(name, wc_root_abspath, - FALSE /*reverse*/, dry_run, - ctx, pool); - if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) - { - return svn_error_quick_wrapf(err, - "Shelved change '%s' not found", - name); - } - else - SVN_ERR(err); - - /* Remove the patch. */ - if (! keep && ! dry_run) - { - SVN_ERR(shelf_delete_patch(name, wc_root_abspath, - ctx, pool)); - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelves_delete(const char *name, - const char *local_abspath, - svn_boolean_t dry_run, - svn_client_ctx_t *ctx, - apr_pool_t *pool) -{ - const char *wc_root_abspath; - - SVN_ERR(svn_client_get_wc_root(&wc_root_abspath, - local_abspath, ctx, pool, pool)); - - /* Remove the patch. */ - if (! dry_run) - { - svn_error_t *err; - - err = shelf_delete_patch(name, wc_root_abspath, - ctx, pool); - if (err && APR_STATUS_IS_ENOENT(err->apr_err)) - { - return svn_error_quick_wrapf(err, - "Shelved change '%s' not found", - name); - } - else - SVN_ERR(err); - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelf_get_paths(apr_hash_t **affected_paths, - const char *name, - const char *local_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - const char *wc_root_abspath; - char *patch_abspath; - svn_patch_file_t *patch_file; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - apr_hash_t *paths = apr_hash_make(result_pool); - - SVN_ERR(svn_client_get_wc_root(&wc_root_abspath, - local_abspath, ctx, scratch_pool, scratch_pool)); - SVN_ERR(get_patch_abspath(&patch_abspath, name, wc_root_abspath, - ctx, scratch_pool, scratch_pool)); - SVN_ERR(svn_diff_open_patch_file(&patch_file, patch_abspath, result_pool)); - - while (1) - { - svn_patch_t *patch; - - svn_pool_clear(iterpool); - SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, - FALSE /*reverse*/, - FALSE /*ignore_whitespace*/, - iterpool, iterpool)); - if (! patch) - break; - svn_hash_sets(paths, - apr_pstrdup(result_pool, patch->old_filename), - apr_pstrdup(result_pool, patch->new_filename)); - } - SVN_ERR(svn_diff_close_patch_file(patch_file, iterpool)); - svn_pool_destroy(iterpool); - - *affected_paths = paths; - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelf_has_changes(svn_boolean_t *has_changes, - const char *name, - const char *local_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - apr_hash_t *patch_paths; - - SVN_ERR(svn_client_shelf_get_paths(&patch_paths, name, local_abspath, - ctx, scratch_pool, scratch_pool)); - *has_changes = (apr_hash_count(patch_paths) != 0); - return SVN_NO_ERROR; -} - -/* Set *LOGMSG to the log message stored in the file PATCH_ABSPATH. - * - * ### Currently just reads the first line. - */ -static svn_error_t * -read_logmsg_from_patch(const char **logmsg, - const char *patch_abspath, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - apr_file_t *file; - svn_stream_t *stream; - svn_boolean_t eof; - svn_stringbuf_t *line; - - SVN_ERR(svn_io_file_open(&file, patch_abspath, - APR_FOPEN_READ, APR_FPROT_OS_DEFAULT, scratch_pool)); - stream = svn_stream_from_aprfile2(file, FALSE /*disown*/, scratch_pool); - SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, result_pool)); - SVN_ERR(svn_stream_close(stream)); - *logmsg = line->data; - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelves_list(apr_hash_t **shelved_patch_infos, - const char *local_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) -{ - char *shelves_dir; - apr_hash_t *dirents; - apr_hash_index_t *hi; - - SVN_ERR(svn_wc__get_shelves_dir(&shelves_dir, ctx->wc_ctx, local_abspath, - scratch_pool, scratch_pool)); - SVN_ERR(svn_io_get_dirents3(&dirents, shelves_dir, FALSE /*only_check_type*/, - result_pool, scratch_pool)); - - *shelved_patch_infos = apr_hash_make(result_pool); - - /* Remove non-shelves */ - for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi)) - { - const char *filename = apr_hash_this_key(hi); - svn_io_dirent2_t *dirent = apr_hash_this_val(hi); - char *name = NULL; - - svn_error_clear(shelf_name_from_filename(&name, filename, result_pool)); - if (name && dirent->kind == svn_node_file) - { - svn_client_shelved_patch_info_t *info - = apr_palloc(result_pool, sizeof(*info)); - - info->dirent = dirent; - info->mtime = info->dirent->mtime; - info->patch_path - = svn_dirent_join(shelves_dir, filename, result_pool); - SVN_ERR(read_logmsg_from_patch(&info->message, info->patch_path, - result_pool, scratch_pool)); - - svn_hash_sets(*shelved_patch_infos, name, info); - } - } - - return SVN_NO_ERROR; -} - -svn_error_t * -svn_client_shelves_any(svn_boolean_t *any_shelved, - const char *local_abspath, - svn_client_ctx_t *ctx, - apr_pool_t *scratch_pool) -{ - apr_hash_t *shelved_patch_infos; - - SVN_ERR(svn_client_shelves_list(&shelved_patch_infos, local_abspath, - ctx, scratch_pool, scratch_pool)); - *any_shelved = apr_hash_count(shelved_patch_infos) != 0; - return SVN_NO_ERROR; -} diff --git a/subversion/libsvn_client/status.c b/subversion/libsvn_client/status.c index a701658671402..9e53713064d55 100644 --- a/subversion/libsvn_client/status.c +++ b/subversion/libsvn_client/status.c @@ -23,6 +23,9 @@ /* ==================================================================== */ +/* We define this here to remove any further warnings about the usage of + experimental functions in this file. */ +#define SVN_EXPERIMENTAL /*** Includes. ***/ @@ -41,6 +44,7 @@ #include "svn_error.h" #include "svn_hash.h" +#include "private/svn_client_shelf.h" #include "private/svn_client_private.h" #include "private/svn_sorts_private.h" #include "private/svn_wc_private.h" @@ -329,6 +333,79 @@ do_external_status(svn_client_ctx_t *ctx, return SVN_NO_ERROR; } + +/* Run status on shelf SHELF_NAME, if it exists. + */ +static svn_error_t * +shelf_status(const char *shelf_name, + const char *target_abspath, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_error_t *err; + svn_client__shelf_t *shelf; + svn_client__shelf_version_t *shelf_version; + const char *wc_relpath; + + err = svn_client__shelf_open_existing(&shelf, + shelf_name, target_abspath, + ctx, scratch_pool); + if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + else + SVN_ERR(err); + + SVN_ERR(svn_client__shelf_version_open(&shelf_version, + shelf, shelf->max_version, + scratch_pool, scratch_pool)); + wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath); + SVN_ERR(svn_client__shelf_version_status_walk(shelf_version, wc_relpath, + status_func, status_baton, + scratch_pool)); + SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Run status on all shelves named in CHANGELISTS by a changelist name + * of the form "svn:shelf:SHELF_NAME", if they exist. + */ +static svn_error_t * +shelves_status(const apr_array_header_t *changelists, + const char *target_abspath, + svn_wc_status_func4_t status_func, + void *status_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + static const char PREFIX[] = "svn:shelf:"; + static const int PREFIX_LEN = 10; + int i; + + if (! changelists) + return SVN_NO_ERROR; + for (i = 0; i < changelists->nelts; i++) + { + const char *cl = APR_ARRAY_IDX(changelists, i, const char *); + + if (strncmp(cl, PREFIX, PREFIX_LEN) == 0) + { + const char *shelf_name = cl + PREFIX_LEN; + + SVN_ERR(shelf_status(shelf_name, target_abspath, + status_func, status_baton, + ctx, scratch_pool)); + } + } + + return SVN_NO_ERROR; +} + /*** Public Interface. ***/ @@ -586,6 +663,9 @@ svn_client_status6(svn_revnum_t *result_rev, } else { + SVN_ERR(shelves_status(changelists, target_abspath, + tweak_status, &sb, + ctx, pool)); err = svn_wc_walk_status(ctx->wc_ctx, target_abspath, depth, get_all, no_ignore, FALSE, ignores, tweak_status, &sb, diff --git a/subversion/libsvn_client/update.c b/subversion/libsvn_client/update.c index 602f7e317b57d..12469b74c15a4 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 741443af2ddc1..edba493d4a01e 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 248412b04e8de..f6612b96e9bf9 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 0000000000000..145fce05650e3 --- /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; +} |