diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2020-05-31 20:58:28 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2020-05-31 20:58:28 +0000 |
commit | bbee6e0814d5875b85b81f26fd4ca7a28b6f9570 (patch) | |
tree | 726fcf32b39ca8976d7aa51b67c7236509f1bde4 /subversion | |
parent | 38cef28c88864beaadac7a7cffdec6da952c3eb2 (diff) | |
download | src-bbee6e0814d5875b85b81f26fd4ca7a28b6f9570.tar.gz src-bbee6e0814d5875b85b81f26fd4ca7a28b6f9570.zip |
Vendor import svn-1.14.0.vendor/subversion/subversion-1.14.0vendor/subversion
Notes
Notes:
svn path=/vendor/subversion/dist/; revision=361669
svn path=/vendor/subversion/subversion-1.14.0/; revision=361670; tag=vendor/subversion/subversion-1.14.0
Diffstat (limited to 'subversion')
270 files changed, 31324 insertions, 16297 deletions
diff --git a/subversion/include/private/svn_branch.h b/subversion/include/private/svn_branch.h index 3fbaeb7ef77c..df3a91c9a488 100644 --- a/subversion/include/private/svn_branch.h +++ b/subversion/include/private/svn_branch.h @@ -98,7 +98,7 @@ extern "C" { * * An element may appear in any or all branches, and its EID is the same in * each branch in which the element appears. - * + * * By definition, an element keeps the same EID for its whole lifetime, even * if deleted from all branches and later 'resurrected'. * diff --git a/subversion/include/private/svn_client_mtcc.h b/subversion/include/private/svn_client_mtcc.h index fe670b04e530..d8bc9029cfaf 100644 --- a/subversion/include/private/svn_client_mtcc.h +++ b/subversion/include/private/svn_client_mtcc.h @@ -207,6 +207,17 @@ svn_client__mtcc_check_path(svn_node_kind_t *kind, /** Commits all operations stored in @a mtcc as a new revision and destroys * @a mtcc. * + * A log message is obtained from the log message callback in the client + * context in @a mtcc. + * + * @a revprop_table (if non-NULL) supplies additional revision properties; + * it may not supply any "svn:*" revision properties. + * + * As with svn_ra_get_commit_editor3(), after the commit has succeeded, + * it will invoke @a commit_callback (if non-NULL) with filled-in + * #svn_commit_info_t *, @a commit_baton, and @a scratch_pool or some subpool + * thereof as arguments. + * * @since New in 1.9. */ svn_error_t * diff --git a/subversion/include/private/svn_client_private.h b/subversion/include/private/svn_client_private.h index 892fc4b0b574..614405ac7f35 100644 --- a/subversion/include/private/svn_client_private.h +++ b/subversion/include/private/svn_client_private.h @@ -281,26 +281,6 @@ svn_client__wc_node_get_origin(svn_client__pathrev_t **origin_p, apr_pool_t *result_pool, apr_pool_t *scratch_pool); -/* Copy the file or directory on URL in some repository to DST_ABSPATH, - * copying node information and properties. Resolve URL using PEG_REV and - * REVISION. - * - * If URL specifies a directory, create the copy using depth DEPTH. - * - * If MAKE_PARENTS is TRUE and DST_ABSPATH doesn't have an added parent - * create missing parent directories - */ -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); - /* Same as the public svn_client_mergeinfo_log2 API, except for the addition * of the TARGET_MERGEINFO_CATALOG and RESULT_POOL parameters. * @@ -341,6 +321,220 @@ svn_client__mergeinfo_log(svn_boolean_t finding_merged, apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/** Return a diff processor that will print a Subversion-style + * (not git-style) diff. + * + * @a anchor is optional (may be null), and is the 'anchor' path to prefix + * to the diff-processor paths before displaying. + * + * @a orig_path_1 and @a orig_path_2 are the two main root paths to be + * diffed; each may be a URL, a local WC path or a local unversioned path. + * + * Other arguments are as for svn_client_diff7() etc. + */ +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); + +/*** Editor for diff summary ***/ + +/* Set *DIFF_PROCESSOR to a diff processor that will report a diff summary + to SUMMARIZE_FUNC. + + 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( + svn_diff_tree_processor_t **diff_processor, + svn_client_diff_summarize_func_t summarize_func, + void *summarize_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/** Copy a directory tree or a file (according to @a kind) from @a src_url at + * @a src_rev, to @a dst_abspath in a WC. + * + * The caller should be holding a WC write lock that allows @a dst_abspath to + * be created, such as on the parent of @a dst_abspath. + * + * If not same repositories, then remove any svn:mergeinfo property. + * + * Use @a ra_session to fetch the data. The session may point to any URL + * within the source repository. + * + * This API does not process any externals definitions that may be present + * on copied directories. + */ +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); + +/** Copy a directory tree or a file (according to @a kind) from @a src_url at + * @a src_rev, to @a dst_abspath in a WC. + * + * The caller should be holding a WC write lock that allows @a dst_abspath to + * be created, such as on the parent of @a dst_abspath. + * + * If not same repositories, then remove any svn:mergeinfo property. + * + * Use @a ra_session to fetch the data. The session may point to a different + * URL after returning. + * + * This API does not process any externals definitions that may be present + * on copied directories. + */ +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); + +/** Return an editor for applying local modifications to a WC. + * + * Return an editor in @a *editor_p, @a *edit_baton_p that will apply + * local modifications to the WC subdirectory at @a dst_abspath. + * + * The @a path arguments to the editor methods shall be local WC paths, + * relative to @a dst_abspath. The @a copyfrom_path arguments to the + * editor methods shall be URLs. + * + * Send notifications via @a notify_func / @a notify_baton. + * ### INCOMPLETE + * + * @a ra_session is used to fetch the original content for copies. + * + * Ignore changes to non-regular property (entry-props, DAV/WC-props). + * + * Acquire the WC write lock in 'open_root' and release it in + * 'close_edit', in 'abort_edit', or when @a result_pool is cleared. + */ +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); + +/* Return an editor for applying local modifications to a WC. + * + * Like svn_client__wc_editor() but with additional options. + * + * If @a root_dir_add is true, then create and schedule for addition + * the root directory of this edit, else assume it is already a versioned, + * existing directory. + * + * If @a ignore_mergeinfo_changes is true, ignore any incoming changes + * to the 'svn:mergeinfo' property. + * + * If @a manage_wc_write_lock is true, acquire the WC write lock in + * 'open_root' and release it in 'close_edit', in 'abort_edit', or + * when @a result_pool is cleared. + */ +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); + +/** Send committable changes found in the WC to a delta-editor. + * + * Committable changes are found in TARGETS:DEPTH:CHANGELISTS. + * + * Send the changes to @a editor:@a edit_baton. The @a path arguments + * to the editor methods are URL-paths relative to the URL of + * @a src_wc_abspath. + * + * ### We will presumably need to change this so that the @a path + * arguments to the editor will be local WC relpaths, in order + * to handle switched paths. + * + * The @a copyfrom_path arguments to the editor methods are URLs. As the + * WC does not store copied-from-foreign-repository metadata, the URL will + * be in the same repository as the URL of its parent path. + * + * Compared with svn_client__do_commit(), this (like svn_client_commit6) + * handles: + * - condense targets and find committable paths + * - checking only one repository is involved + * + * Compared with svn_client_commit6(), this does not handle: + * - externals + * - log message + * - revprops + * - checking the commit includes both halves of each local move + * - changing the copy revision of each local move to ~HEAD + * - WC write locks + * - bumping revisions in WC + * - removing locks and changelists in WC + */ +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 *scratch_pool); + +/** Copy local modifications from one WC subtree to another. + * + * Find local modifications under @a src_wc_abspath, in the same way as + * for a commit. + * + * Edit the WC at @a dst_wc_abspath, applying those modifications to the + * current working state to produce a new working state. + * + * The source and destination may be in the same WC or in different WCs. + */ +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); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subversion/include/private/svn_client_shelf.h b/subversion/include/private/svn_client_shelf.h new file mode 100644 index 000000000000..0d747cdcd36f --- /dev/null +++ b/subversion/include/private/svn_client_shelf.h @@ -0,0 +1,498 @@ +/** + * @copyright + * ==================================================================== + * 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. + * ==================================================================== + * @endcopyright + * + * @file svn_client_shelf.h + * @brief Subversion's client library: experimental shelving v3 + */ + +#ifndef SVN_CLIENT_SHELF_H +#define SVN_CLIENT_SHELF_H + +#include <apr.h> +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_time.h> + +#include "svn_client.h" +#include "svn_types.h" +#include "svn_string.h" +#include "svn_wc.h" +#include "svn_diff.h" +#include "private/svn_diff_tree.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/** Shelving v3, with checkpoints + * + * @defgroup svn_client_shelves_checkpoints Shelves and checkpoints + * @{ + */ + +/** A shelf. + * + * @warning EXPERIMENTAL. + */ +typedef struct svn_client__shelf_t +{ + /* Public fields (read-only for public use) */ + const char *name; + int max_version; /**< @deprecated */ + + /* Private fields */ + const char *wc_root_abspath; + const char *shelves_dir; + apr_hash_t *revprops; /**< non-null; allocated in POOL */ + svn_client_ctx_t *ctx; + apr_pool_t *pool; +} svn_client__shelf_t; + +/** One version of a shelved change-set. + * + * @warning EXPERIMENTAL. + */ +typedef struct svn_client__shelf_version_t +{ + /* Public fields (read-only for public use) */ + svn_client__shelf_t *shelf; + apr_time_t mtime; /**< time-stamp of this version */ + + /* Private fields */ + const char *files_dir_abspath; /**< abspath of the storage area */ + int version_number; /**< version number starting from 1 */ +} svn_client__shelf_version_t; + +/** Open an existing shelf or create a new shelf. + * + * Create a new shelf (containing no versions) if a shelf named @a name + * is not found. + * + * The shelf should be closed after use by calling svn_client_shelf_close(). + * + * @a local_abspath is any path in the WC and is used to find the WC root. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Open an existing shelf named @a name, or error if it doesn't exist. + * + * The shelf should be closed after use by calling svn_client_shelf_close(). + * + * @a local_abspath is any path in the WC and is used to find the WC root. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Close @a shelf. + * + * If @a shelf is NULL, do nothing; otherwise @a shelf must be an open shelf. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf_close(svn_client__shelf_t *shelf, + apr_pool_t *scratch_pool); + +/** Delete the shelf named @a name, or error if it doesn't exist. + * + * @a local_abspath is any path in the WC and is used to find the WC root. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Get an editor that, when driven, will store changes in @a shelf_version. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Save the local modifications found by @a paths, @a depth, + * @a changelists as a new version of @a shelf. + * + * If any paths are shelved, create a new shelf-version and return the new + * shelf-version in @a *new_version_p, else set @a *new_version_p to null. + * @a new_version_p may be null if that output is not wanted; a new shelf- + * version is still saved and may be found through @a shelf. + * + * @a paths are relative to the CWD, or absolute. + * + * For each successfully shelved path: call @a shelved_func (if not null) + * with @a shelved_baton. + * + * If any paths cannot be shelved: if @a not_shelved_func is given, call + * it with @a not_shelved_baton for each such path, and still create a new + * shelf-version if any paths are shelved. + * + * This function does not revert the changes from the WC; use + * svn_client_shelf_unapply() for that. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Delete all newer versions of @a shelf newer than @a shelf_version. + * + * If @a shelf_version is null, delete all versions of @a shelf. (The + * shelf will still exist, with any log message and other revprops, but + * with no versions in it.) + * + * Leave the shelf's log message and other revprops unchanged. + * + * Any #svn_client__shelf_version_t object that refers to a deleted version + * will become invalid: attempting to use it will give undefined behaviour. + * The given @a shelf_version will remain valid. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Return in @a shelf_version an existing version of @a shelf, given its + * @a version_number (starting from 1). Error if that version doesn't exist. + * + * There is no need to "close" it after use. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Return in @a shelf_version the newest version of @a shelf. + * + * Set @a shelf_version to null if no versions exist. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Return in @a versions_p an array of (#svn_client__shelf_version_t *) + * containing all versions of @a shelf. + * + * The versions will be in chronological order, oldest to newest. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Apply @a shelf_version to the WC. + * + * If @a dry_run is true, try applying the shelf-version to the WC and + * report the full set of notifications about successes and conflicts, + * but leave the WC untouched. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf_apply(svn_client__shelf_version_t *shelf_version, + svn_boolean_t dry_run, + apr_pool_t *scratch_pool); + +/** Test whether we can successfully apply the changes for @a file_relpath + * in @a shelf_version to the WC. + * + * Set @a *conflict_p to true if the changes conflict with the WC state, + * else to false. + * + * If @a file_relpath is not found in @a shelf_version, set @a *conflict_p + * to FALSE. + * + * @a file_relpath is relative to the WC root. + * + * A conflict means the shelf cannot be applied successfully to the WC + * because the change to be applied is not compatible with the current + * working state of the WC file. Examples are a text conflict, or the + * file does not exist or is a directory, or the shelf is trying to add + * the file but it already exists, or trying to delete it but it does not + * exist. + * + * Return an error only if something is broken, e.g. unable to read data + * from the specified shelf-version. + * + * Leave the WC untouched. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Reverse-apply @a shelf_version to the WC. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf_unapply(svn_client__shelf_version_t *shelf_version, + svn_boolean_t dry_run, + apr_pool_t *scratch_pool); + +/** Send committable changes found in a shelf to a delta-editor. + * + * Push changes from the @a shelf_version subtree at @a top_relpath + * to @a editor : @a edit_baton. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Set @a *affected_paths to a hash with one entry for each path affected + * by the @a shelf_version. + * + * The hash key is the path of the affected file, relative to the WC root. + * + * (Future possibility: When moves and copies are supported, the hash key + * is the old path and value is the new path.) + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Set @a shelf's revprop @a prop_name to @a prop_val. + * + * This can be used to set or change the shelf's log message + * (property name "svn:log" or #SVN_PROP_REVISION_LOG). + * + * If @a prop_val is NULL, delete the property (if present). + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Set @a shelf's revprops to @a revprop_table. + * + * This deletes all previous revprops. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf_revprop_set_all(svn_client__shelf_t *shelf, + apr_hash_t *revprop_table, + apr_pool_t *scratch_pool); + +/** Get @a shelf's revprop @a prop_name into @a *prop_val. + * + * If the property is not present, set @a *prop_val to NULL. + * + * This can be used to get the shelf's log message + * (property name "svn:log" or #SVN_PROP_REVISION_LOG). + * + * The lifetime of the result is limited to that of @a shelf and/or + * of @a result_pool. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Get @a shelf's revprops into @a props. + * + * The lifetime of the result is limited to that of @a shelf and/or + * of @a result_pool. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf_revprop_list(apr_hash_t **props, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool); + +/** Set the log message in @a shelf to @a log_message. + * + * If @a log_message is null, delete the log message. + * + * Similar to svn_client_shelf_revprop_set(... SVN_PROP_REVISION_LOG ...). + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf_set_log_message(svn_client__shelf_t *shelf, + const char *log_message, + apr_pool_t *scratch_pool); + +/** Get the log message in @a shelf into @a *log_message. + * + * Set @a *log_message to NULL if there is no log message. + * + * Similar to svn_client_shelf_revprop_get(... SVN_PROP_REVISION_LOG ...). + * + * The result is allocated in @a result_pool. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf_get_log_message(char **log_message, + svn_client__shelf_t *shelf, + apr_pool_t *result_pool); + +/** Information about a shelf. + * + * @warning EXPERIMENTAL. + */ +typedef struct svn_client__shelf_info_t +{ + apr_time_t mtime; /**< mtime of the latest change */ +} svn_client__shelf_info_t; + +/** Set @a *shelf_infos to a hash, keyed by shelf name, of pointers to + * @c svn_client_shelf_info_t structures, one for each shelf in the + * given WC. + * + * @a local_abspath is any path in the WC and is used to find the WC root. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Report the shelved status of all the shelved paths in @a shelf_version + * via @a walk_func(@a walk_baton, ...). + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Output the subtree of @a shelf_version rooted at @a shelf_relpath + * as a diff to @a diff_processor. + * + * ### depth and ignore_ancestry are currently ignored. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_CLIENT_SHELF_H */ diff --git a/subversion/include/private/svn_client_shelf2.h b/subversion/include/private/svn_client_shelf2.h new file mode 100644 index 000000000000..e26537506976 --- /dev/null +++ b/subversion/include/private/svn_client_shelf2.h @@ -0,0 +1,467 @@ +/** + * @copyright + * ==================================================================== + * 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. + * ==================================================================== + * @endcopyright + * + * @file svn_client_shelf2.h + * @brief Subversion's client library: experimental shelving v2 + */ + +#ifndef SVN_CLIENT_SHELF2_H +#define SVN_CLIENT_SHELF2_H + +#include <apr.h> +#include <apr_pools.h> +#include <apr_hash.h> +#include <apr_time.h> + +#include "svn_client.h" +#include "svn_types.h" +#include "svn_string.h" +#include "svn_wc.h" +#include "svn_diff.h" +#include "private/svn_diff_tree.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + + +/** Shelving v2, with checkpoints + * + * @defgroup svn_client_shelves_checkpoints Shelves and checkpoints + * @{ + */ + +/** A shelf. + * + * @warning EXPERIMENTAL. + */ +typedef struct svn_client__shelf2_t +{ + /* Public fields (read-only for public use) */ + const char *name; + int max_version; /** @deprecated */ + + /* Private fields */ + const char *wc_root_abspath; + const char *shelves_dir; + apr_hash_t *revprops; /* non-null; allocated in POOL */ + svn_client_ctx_t *ctx; + apr_pool_t *pool; +} svn_client__shelf2_t; + +/** One version of a shelved change-set. + * + * @warning EXPERIMENTAL. + */ +typedef struct svn_client__shelf2_version_t +{ + /* Public fields (read-only for public use) */ + svn_client__shelf2_t *shelf; + apr_time_t mtime; /** time-stamp of this version */ + + /* Private fields */ + const char *files_dir_abspath; /** abspath of the storage area */ + int version_number; /** version number starting from 1 */ +} svn_client__shelf2_version_t; + +/** Open an existing shelf or create a new shelf. + * + * Create a new shelf (containing no versions) if a shelf named @a name + * is not found. + * + * The shelf should be closed after use by calling svn_client_shelf_close(). + * + * @a local_abspath is any path in the WC and is used to find the WC root. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Open an existing shelf named @a name, or error if it doesn't exist. + * + * The shelf should be closed after use by calling svn_client_shelf_close(). + * + * @a local_abspath is any path in the WC and is used to find the WC root. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Close @a shelf. + * + * If @a shelf is NULL, do nothing; otherwise @a shelf must be an open shelf. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf2_close(svn_client__shelf2_t *shelf, + apr_pool_t *scratch_pool); + +/** Delete the shelf named @a name, or error if it doesn't exist. + * + * @a local_abspath is any path in the WC and is used to find the WC root. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Save the local modifications found by @a paths, @a depth, + * @a changelists as a new version of @a shelf. + * + * If any paths are shelved, create a new shelf-version and return the new + * shelf-version in @a *new_version_p, else set @a *new_version_p to null. + * @a new_version_p may be null if that output is not wanted; a new shelf- + * version is still saved and may be found through @a shelf. + * + * @a paths are relative to the CWD, or absolute. + * + * For each successfully shelved path: call @a shelved_func (if not null) + * with @a shelved_baton. + * + * If any paths cannot be shelved: if @a not_shelved_func is given, call + * it with @a not_shelved_baton for each such path, and still create a new + * shelf-version if any paths are shelved. + * + * This function does not revert the changes from the WC; use + * svn_client_shelf_unapply() for that. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Delete all newer versions of @a shelf newer than @a shelf_version. + * + * If @a shelf_version is null, delete all versions of @a shelf. (The + * shelf will still exist, with any log message and other revprops, but + * with no versions in it.) + * + * Leave the shelf's log message and other revprops unchanged. + * + * Any #svn_client_shelf_version_t object that refers to a deleted version + * will become invalid: attempting to use it will give undefined behaviour. + * The given @a shelf_version will remain valid. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Return in @a shelf_version an existing version of @a shelf, given its + * @a version_number. Error if that version doesn't exist. + * + * There is no need to "close" it after use. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Return in @a shelf_version the newest version of @a shelf. + * + * Set @a shelf_version to null if no versions exist. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Return in @a versions_p an array of (#svn_client_shelf_version_t *) + * containing all versions of @a shelf. + * + * The versions will be in chronological order, oldest to newest. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Apply @a shelf_version to the WC. + * + * If @a dry_run is true, try applying the shelf-version to the WC and + * report the full set of notifications about successes and conflicts, + * but leave the WC untouched. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf2_apply(svn_client__shelf2_version_t *shelf_version, + svn_boolean_t dry_run, + apr_pool_t *scratch_pool); + +/** Test whether we can successfully apply the changes for @a file_relpath + * in @a shelf_version to the WC. + * + * Set @a *conflict_p to true if the changes conflict with the WC state, + * else to false. + * + * If @a file_relpath is not found in @a shelf_version, set @a *conflict_p + * to FALSE. + * + * @a file_relpath is relative to the WC root. + * + * A conflict means the shelf cannot be applied successfully to the WC + * because the change to be applied is not compatible with the current + * working state of the WC file. Examples are a text conflict, or the + * file does not exist or is a directory, or the shelf is trying to add + * the file but it already exists, or trying to delete it but it does not + * exist. + * + * Return an error only if something is broken, e.g. unable to read data + * from the specified shelf-version. + * + * Leave the WC untouched. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Reverse-apply @a shelf_version to the WC. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf2_unapply(svn_client__shelf2_version_t *shelf_version, + svn_boolean_t dry_run, + apr_pool_t *scratch_pool); + +/** Set @a *affected_paths to a hash with one entry for each path affected + * by the @a shelf_version. + * + * The hash key is the path of the affected file, relative to the WC root. + * + * (Future possibility: When moves and copies are supported, the hash key + * is the old path and value is the new path.) + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Set @a shelf's revprop @a prop_name to @a prop_val. + * + * This can be used to set or change the shelf's log message + * (property name "svn:log" or #SVN_PROP_REVISION_LOG). + * + * If @a prop_val is NULL, delete the property (if present). + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Set @a shelf's revprops to @a revprop_table. + * + * This deletes all previous revprops. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf2_revprop_set_all(svn_client__shelf2_t *shelf, + apr_hash_t *revprop_table, + apr_pool_t *scratch_pool); + +/** Get @a shelf's revprop @a prop_name into @a *prop_val. + * + * If the property is not present, set @a *prop_val to NULL. + * + * This can be used to get the shelf's log message + * (property name "svn:log" or #SVN_PROP_REVISION_LOG). + * + * The lifetime of the result is limited to that of @a shelf and/or + * of @a result_pool. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Get @a shelf's revprops into @a props. + * + * The lifetime of the result is limited to that of @a shelf and/or + * of @a result_pool. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf2_revprop_list(apr_hash_t **props, + svn_client__shelf2_t *shelf, + apr_pool_t *result_pool); + +/** Set the log message in @a shelf to @a log_message. + * + * If @a log_message is null, delete the log message. + * + * Similar to svn_client_shelf_revprop_set(... SVN_PROP_REVISION_LOG ...). + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf2_set_log_message(svn_client__shelf2_t *shelf, + const char *log_message, + apr_pool_t *scratch_pool); + +/** Get the log message in @a shelf into @a *log_message. + * + * Set @a *log_message to NULL if there is no log message. + * + * Similar to svn_client_shelf_revprop_get(... SVN_PROP_REVISION_LOG ...). + * + * The result is allocated in @a result_pool. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +svn_error_t * +svn_client__shelf2_get_log_message(char **log_message, + svn_client__shelf2_t *shelf, + apr_pool_t *result_pool); + +/** Information about a shelf. + * + * @warning EXPERIMENTAL. + */ +typedef struct svn_client__shelf2_info_t +{ + apr_time_t mtime; /* mtime of the latest change */ +} svn_client__shelf2_info_t; + +/** Set @a *shelf_infos to a hash, keyed by shelf name, of pointers to + * @c svn_client_shelf_info_t structures, one for each shelf in the + * given WC. + * + * @a local_abspath is any path in the WC and is used to find the WC root. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/* Report the shelved status of all the shelved paths in SHELF_VERSION + * via WALK_FUNC(WALK_BATON, ...). + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** Output the subtree of @a shelf_version rooted at @a shelf_relpath + * as a diff to @a diff_processor. + * + * ### depth and ignore_ancestry are currently ignored. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + +/** @} */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_CLIENT_SHELF2_H */ diff --git a/subversion/include/private/svn_dep_compat.h b/subversion/include/private/svn_dep_compat.h index 6b381d2ce81c..7e6603826135 100644 --- a/subversion/include/private/svn_dep_compat.h +++ b/subversion/include/private/svn_dep_compat.h @@ -108,12 +108,6 @@ extern "C" { #define APR_OPENINFO 0x00100000 #endif -#if !APR_VERSION_AT_LEAST(1,4,0) -#ifndef apr_time_from_msec -#define apr_time_from_msec(msec) ((apr_time_t)(msec) * 1000) -#endif -#endif - /** * APR 1 has volatile qualifier bugs in some atomic prototypes that * are fixed in APR 2: diff --git a/subversion/include/private/svn_diff_tree.h b/subversion/include/private/svn_diff_tree.h index 713644d08c4e..9c675d916aa1 100644 --- a/subversion/include/private/svn_diff_tree.h +++ b/subversion/include/private/svn_diff_tree.h @@ -321,7 +321,6 @@ svn_diff__tree_processor_create(void *baton, */ /* Used by libsvn clients repository diff */ const svn_diff_tree_processor_t * svn_diff__tree_processor_reverse_create(const svn_diff_tree_processor_t * processor, - const char *prefix_relpath, apr_pool_t *result_pool); /** diff --git a/subversion/include/private/svn_dirent_uri_private.h b/subversion/include/private/svn_dirent_uri_private.h new file mode 100644 index 000000000000..0da5f4722884 --- /dev/null +++ b/subversion/include/private/svn_dirent_uri_private.h @@ -0,0 +1,53 @@ +/* + * svn_dirent_uri_private.h : private definitions for dirents and URIs + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +#ifndef SVN_DIRENT_URI_PRIVATE_H +#define SVN_DIRENT_URI_PRIVATE_H + +#include <apr_pools.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Convert @a relpath from the local style to the canonical internal style. + * "Local style" means native path separators and "." for the empty path. + * + * Allocates the results in @a result_pool. Uses @a scratch_pool for + * temporary allocations. + * + * @since New in 1.7 (as svn_relpath__internal_style()). + * @since Name and signature changed in 1.12. + */ +svn_error_t * +svn_relpath__make_internal(const char **internal_style_relpath, + const char *relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_DIRENT_URI_PRIVATE_H */ diff --git a/subversion/include/private/svn_element.h b/subversion/include/private/svn_element.h index c467175c748e..9c7129a4a98a 100644 --- a/subversion/include/private/svn_element.h +++ b/subversion/include/private/svn_element.h @@ -348,7 +348,7 @@ svn_element__content_t * svn_element__tree_get(const svn_element__tree_t *tree, int eid); -svn_error_t * +void svn_element__tree_set(svn_element__tree_t *tree, int eid, const svn_element__content_t *element); diff --git a/subversion/include/private/svn_fs_fs_private.h b/subversion/include/private/svn_fs_fs_private.h index d2573d447c9d..2ca61e4cf8c1 100644 --- a/subversion/include/private/svn_fs_fs_private.h +++ b/subversion/include/private/svn_fs_fs_private.h @@ -255,22 +255,6 @@ typedef struct svn_fs_fs__stats_t apr_hash_t *by_extension; } svn_fs_fs__stats_t; - -/* Scan all contents of the repository FS and return statistics in *STATS, - * allocated in RESULT_POOL. Report progress through PROGRESS_FUNC with - * PROGRESS_BATON, if PROGRESS_FUNC is not NULL. - * Use SCRATCH_POOL for temporary allocations. - */ -svn_error_t * -svn_fs_fs__get_stats(svn_fs_fs__stats_t **stats, - svn_fs_t *fs, - svn_fs_progress_notify_func_t progress_func, - void *progress_baton, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool); - /* A node-revision ID in FSFS consists of 3 sub-IDs ("parts") that consist * of a creation REVISION number and some revision- / transaction-local * counter value (NUMBER). Old-style ID parts use global counter values. @@ -325,33 +309,60 @@ typedef svn_error_t * void *baton, apr_pool_t *scratch_pool); -/* Read the P2L index for the rev / pack file containing REVISION in FS. - * For each index entry, invoke CALLBACK_FUNC with CALLBACK_BATON. - * If not NULL, call CANCEL_FUNC with CANCEL_BATON from time to time. - * Use SCRATCH_POOL for temporary allocations. - */ -svn_error_t * -svn_fs_fs__dump_index(svn_fs_t *fs, - svn_revnum_t revision, - svn_fs_fs__dump_index_func_t callback_func, - void *callback_baton, - svn_cancel_func_t cancel_func, - void *cancel_baton, - apr_pool_t *scratch_pool); - - -/* Rewrite the respective index information of the rev / pack file in FS - * containing REVISION and use the svn_fs_fs__p2l_entry_t * array ENTRIES - * as the new index contents. Allocate temporaries from SCRATCH_POOL. - * - * Note that this becomes a no-op if ENTRIES is empty. You may use a zero- - * sized empty entry instead. - */ -svn_error_t * -svn_fs_fs__load_index(svn_fs_t *fs, - svn_revnum_t revision, - apr_array_header_t *entries, - apr_pool_t *scratch_pool); +typedef struct svn_fs_fs__ioctl_get_stats_input_t +{ + svn_fs_progress_notify_func_t progress_func; + void *progress_baton; +} svn_fs_fs__ioctl_get_stats_input_t; + +typedef struct svn_fs_fs__ioctl_get_stats_output_t +{ + svn_fs_fs__stats_t *stats; +} svn_fs_fs__ioctl_get_stats_output_t; + +SVN_FS_DECLARE_IOCTL_CODE(SVN_FS_FS__IOCTL_GET_STATS, SVN_FS_TYPE_FSFS, 1000); + +typedef struct svn_fs_fs__ioctl_dump_index_input_t +{ + svn_revnum_t revision; + svn_fs_fs__dump_index_func_t callback_func; + void *callback_baton; +} svn_fs_fs__ioctl_dump_index_input_t; + +SVN_FS_DECLARE_IOCTL_CODE(SVN_FS_FS__IOCTL_DUMP_INDEX, SVN_FS_TYPE_FSFS, 1001); + +typedef struct svn_fs_fs__ioctl_load_index_input_t +{ + svn_revnum_t revision; + /* Array of svn_fs_fs__p2l_entry_t * entries. */ + apr_array_header_t *entries; +} svn_fs_fs__ioctl_load_index_input_t; + +SVN_FS_DECLARE_IOCTL_CODE(SVN_FS_FS__IOCTL_LOAD_INDEX, SVN_FS_TYPE_FSFS, 1002); + +typedef struct svn_fs_fs__ioctl_revision_size_input_t +{ + svn_revnum_t revision; +} svn_fs_fs__ioctl_revision_size_input_t; + +typedef struct svn_fs_fs__ioctl_revision_size_output_t +{ + apr_off_t rev_size; +} svn_fs_fs__ioctl_revision_size_output_t; + +/* See svn_fs_fs__revision_size(). */ +SVN_FS_DECLARE_IOCTL_CODE(SVN_FS_FS__IOCTL_REVISION_SIZE, SVN_FS_TYPE_FSFS, 1003); + +typedef struct svn_fs_fs__ioctl_build_rep_cache_input_t +{ + svn_revnum_t start_rev; + svn_revnum_t end_rev; + svn_fs_progress_notify_func_t progress_func; + void *progress_baton; +} svn_fs_fs__ioctl_build_rep_cache_input_t; + +/* See svn_fs_fs__build_rep_cache(). */ +SVN_FS_DECLARE_IOCTL_CODE(SVN_FS_FS__IOCTL_BUILD_REP_CACHE, SVN_FS_TYPE_FSFS, 1004); #ifdef __cplusplus } diff --git a/subversion/include/private/svn_repos_private.h b/subversion/include/private/svn_repos_private.h index c65b73fc1e69..1fd34e8053c7 100644 --- a/subversion/include/private/svn_repos_private.h +++ b/subversion/include/private/svn_repos_private.h @@ -31,6 +31,7 @@ #include "svn_types.h" #include "svn_repos.h" +#include "svn_delta.h" #include "svn_editor.h" #include "svn_config.h" @@ -85,9 +86,11 @@ svn_repos__validate_prop(const char *name, * * NAME is used to check that VALUE should be normalized, and if this * is the case, VALUE is then normalized, allocated from RESULT_POOL. - * If no normalization is required, VALUE will be copied to RESULT_POOL - * unchanged. If NORMALIZED_P is not NULL, and the normalization - * happened, set *NORMALIZED_P to non-zero. If the property is returned + * If no normalization happened, *RESULT_P will be set to VALUE, and + * no copying of the value will occur. + * + * If NORMALIZED_P is not NULL, and the normalization happened, + * set *NORMALIZED_P to non-zero. If the property is returned * unchanged and NORMALIZED_P is not NULL, then *NORMALIZED_P will be * set to zero. SCRATCH_POOL will be used for temporary allocations. */ @@ -296,6 +299,23 @@ svn_repos__dump_headers(svn_stream_t *stream, svn_repos__dumpfile_headers_t *headers, apr_pool_t *scratch_pool); +/* Write a magic header record to DUMP_STREAM specifying format version + * VERSION. + */ +svn_error_t * +svn_repos__dump_magic_header_record(svn_stream_t *dump_stream, + int version, + apr_pool_t *pool); + +/* Write a UUID record to DUMP_STREAM. + * + * If UUID is NULL then write nothing at all. + */ +svn_error_t * +svn_repos__dump_uuid_header_record(svn_stream_t *dump_stream, + const char *uuid, + apr_pool_t *pool); + /* Write a revision record to DUMP_STREAM for revision REVISION with revision * properies REVPROPS, creating appropriate headers. * @@ -349,6 +369,27 @@ svn_repos__dump_node_record(svn_stream_t *dump_stream, svn_boolean_t content_length_always, apr_pool_t *scratch_pool); +/** + * Get a dump editor @a editor along with a @a edit_baton allocated in + * @a pool. The editor will write output to @a stream. + * + * @a update_anchor_relpath is the repository relative path of the + * anchor of the update-style drive which will happen on @a *editor; + * if a replay-style drive will instead be used, it should be passed + * as @c NULL. + * + * In contrast to the dump editor used inside svn_repos_dump_fs4(), this + * one supports only deltas mode. + * + * ### TODO: Unify with the dump editor inside svn_repos_dump_fs4(). + */ +svn_error_t * +svn_repos__get_dump_editor(const svn_delta_editor_t **editor, + void **edit_baton, + svn_stream_t *stream, + const char *update_anchor_relpath, + apr_pool_t *pool); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/subversion/include/private/svn_sorts_private.h b/subversion/include/private/svn_sorts_private.h index 2d5f1887f1b7..d0fddc0c6488 100644 --- a/subversion/include/private/svn_sorts_private.h +++ b/subversion/include/private/svn_sorts_private.h @@ -120,26 +120,29 @@ svn_sort__array_lookup(const apr_array_header_t *array, * @a insert_index, growing the array and shuffling existing elements along to * make room. * + * Raise an error if @a insert_index is less than 0 or greater than the length + * of the array. + * * @note Private. For use by Subversion's own code only. */ -void -svn_sort__array_insert(apr_array_header_t *array, - const void *new_element, - int insert_index); +svn_error_t * +svn_sort__array_insert2(apr_array_header_t *array, + const void *new_element, + int insert_index); /* Remove @a elements_to_delete elements starting at @a delete_index from the - * array @a arr. If @a delete_index is not a valid element of @a arr, - * @a elements_to_delete is not greater than zero, or - * @a delete_index + @a elements_to_delete is greater than @a arr->nelts, - * then do nothing. + * array @a arr. + * + * Raise an error if the indexes to delete extends outside the array bounds + * or if @a elements_to_delete is not greater than zero. * * @note Private. For use by Subversion's own code only. */ -void -svn_sort__array_delete(apr_array_header_t *arr, - int delete_index, - int elements_to_delete); +svn_error_t * +svn_sort__array_delete2(apr_array_header_t *arr, + int delete_index, + int elements_to_delete); /* Reverse the order of elements in @a array, in place. * @@ -190,7 +193,7 @@ svn_priority_queue__size(svn_priority_queue__t *queue); /** * Returns a reference to the first element in the @a queue. The queue - * contents remains unchanged. If the @a queue is empty, #NULL will be + * contents remains unchanged. If the @a queue is empty, NULL will be * returned. */ void * @@ -212,7 +215,7 @@ svn_priority_queue__pop(svn_priority_queue__t *queue); /** * Append the new @a element to the @a queue. @a element must neither be - * #NULL nor the first element as returned by #svn_priority_queue__peek. + * NULL nor the first element as returned by #svn_priority_queue__peek. */ void svn_priority_queue__push(svn_priority_queue__t *queue, const void *element); diff --git a/subversion/include/private/svn_subr_private.h b/subversion/include/private/svn_subr_private.h index d18c564748d0..468b7cc459f8 100644 --- a/subversion/include/private/svn_subr_private.h +++ b/subversion/include/private/svn_subr_private.h @@ -389,27 +389,27 @@ svn_hash__make(apr_pool_t *pool); /** Struct that represents a key value pair read from a serialized hash * representation. There are special cases that can also be represented: - * a #NULL @a key signifies the end of the hash, a #NULL @a val for non- + * a NULL @a key signifies the end of the hash, a NULL @a val for non- * NULL keys is only possible in incremental mode describes a deletion. * * @since New in 1.9. */ typedef struct svn_hash__entry_t { - /** 0-terminated Key. #NULL if this contains no data at all because we + /** 0-terminated Key. NULL if this contains no data at all because we * encountered the end of the hash. */ char *key; - /** Length of @a key. Must be 0 if @a key is #NULL. */ + /** Length of @a key. Must be 0 if @a key is NULL. */ apr_size_t keylen; - /** 0-terminated value stored with the key. If this is #NULL for a + /** 0-terminated value stored with the key. If this is NULL for a * non-NULL @a key, then this means that the key shall be removed from - * the hash (only used in incremental mode). Must be #NULL if @a key is - * #NULL. */ + * the hash (only used in incremental mode). Must be NULL if @a key is + * NULL. */ char *val; - /** Length of @a val. Must be 0 if @a val is #NULL. */ + /** Length of @a val. Must be 0 if @a val is NULL. */ apr_size_t vallen; } svn_hash__entry_t; diff --git a/subversion/include/private/svn_wc_private.h b/subversion/include/private/svn_wc_private.h index 521d092be692..9e316cda3b8c 100644 --- a/subversion/include/private/svn_wc_private.h +++ b/subversion/include/private/svn_wc_private.h @@ -348,19 +348,21 @@ svn_wc__get_wcroot(const char **wcroot_abspath, apr_pool_t *result_pool, apr_pool_t *scratch_pool); -/** Set @a *dir to the abspath of the directory in which shelved patches - * are stored, which is inside the WC's administrative directory, and ensure - * the directory exists. +/** Set @a *dir to the abspath of the directory in which administrative + * data for experimental features may be stored. This directory is inside + * the WC's administrative directory. Ensure the directory exists. * * @a local_abspath is any path in the WC, and is used to find the WC root. + * + * @warning EXPERIMENTAL. */ SVN_EXPERIMENTAL svn_error_t * -svn_wc__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); +svn_wc__get_experimental_dir(char **dir, + svn_wc_context_t *wc_ctx, + const char *local_abspath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); /** * The following are temporary APIs to aid in the transition from wc-1 to @@ -537,7 +539,7 @@ svn_wc__node_get_origin(svn_boolean_t *is_copy, * If @a base_only is TRUE then only the base node will be examined, * otherwise the current base or working node will be examined. * - * If a value is not interesting you can pass #NULL. + * If a value is not interesting you can pass NULL. * * If @a local_abspath is not in the working copy, return * @c SVN_ERR_WC_PATH_NOT_FOUND. Use @a scratch_pool for all temporary @@ -615,6 +617,42 @@ svn_wc__node_get_base(svn_node_kind_t *kind, apr_pool_t *scratch_pool); +/* Return an array of const char * elements, which represent local absolute + * paths for nodes, within the working copy indicated by WRI_ABSPATH, which + * have a basename matching BASENAME and have node kind KIND. + * If no such nodes exist, return an empty array. + * + * This function returns only paths to nodes which are present in the highest + * layer of the WC. In other words, paths to deleted and/or excluded nodes are + * never returned. + */ +svn_error_t * +svn_wc__find_working_nodes_with_basename(apr_array_header_t **abspaths, + const char *wri_abspath, + const char *basename, + svn_node_kind_t kind, + svn_wc_context_t *wc_ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/* Return an array of const char * elements, which represent local absolute + * paths for nodes, within the working copy indicated by WRI_ABSPATH, which + * are copies of REPOS_RELPATH and have node kind KIND. + * If no such nodes exist, return an empty array. + * + * This function returns only paths to nodes which are present in the highest + * layer of the WC. In other words, paths to deleted and/or excluded nodes are + * never returned. + */ +svn_error_t * +svn_wc__find_copies_of_repos_path(apr_array_header_t **abspaths, + const char *wri_abspath, + const char *repos_relpath, + svn_node_kind_t kind, + svn_wc_context_t *wc_ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /* Get the working revision of @a local_abspath using @a wc_ctx. If @a * local_abspath is not in the working copy, return @c * SVN_ERR_WC_PATH_NOT_FOUND. @@ -1762,7 +1800,7 @@ svn_wc__resolve_conflicts(svn_wc_context_t *wc_ctx, void *notify_baton, apr_pool_t *scratch_pool); -/** +/** * Resolve the text conflict at LOCAL_ABSPATH as per CHOICE, and then * mark the conflict resolved. * The working copy must already be locked for resolving, e.g. by calling @@ -1779,7 +1817,7 @@ svn_wc__conflict_text_mark_resolved(svn_wc_context_t *wc_ctx, void *notify_baton, apr_pool_t *scratch_pool); -/** +/** * Resolve the conflicted property PROPNAME at LOCAL_ABSPATH as per CHOICE, * and then mark the conflict resolved. If MERGED_VALUE is not NULL, this is * the new merged property, used when choosing #svn_wc_conflict_choose_merged. @@ -1810,7 +1848,7 @@ svn_wc__conflict_prop_mark_resolved(svn_wc_context_t *wc_ctx, * * The tree conflict at LOCAL_ABSPATH must have the following properties or * SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE will be returned: - * + * * operation: svn_wc_operation_update or svn_wc_operation_switch * local change: svn_wc_conflict_reason_deleted or * svn_wc_conflict_reason_replaced or @@ -1847,7 +1885,7 @@ svn_wc__conflict_tree_update_break_moved_away(svn_wc_context_t *wc_ctx, * * The tree conflict at LOCAL_ABSPATH must have the following properties or * SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE will be returned: - * + * * operation: svn_wc_operation_update or svn_wc_operation_switch * local change: svn_wc_conflict_reason_deleted or * svn_wc_conflict_reason_replaced @@ -1883,7 +1921,7 @@ svn_wc__conflict_tree_update_raise_moved_away(svn_wc_context_t *wc_ctx, * * The tree conflict at LOCAL_ABSPATH must have the following properties or * SVN_ERR_WC_CONFLICT_RESOLVER_FAILURE will be returned: - * + * * operation: svn_wc_operation_update or svn_wc_operation_switch * local change: svn_wc_conflict_reason_moved_away * incoming change: svn_wc_conflict_action_edit @@ -2044,15 +2082,19 @@ svn_wc__acquire_write_lock_for_resolve(const char **lock_root_abspath, /* The implemementation of svn_wc_diff6(), but reporting to a diff processor * - * If ROOT_RELPATH is not NULL, set *ROOT_RELPATH to the target of the diff - * within the diff namespace. ("" or a single path component). + * New mode, when ANCHOR_AT_GIVEN_PATHS is true: + * + * Anchor the DIFF_PROCESSOR at LOCAL_ABSPATH. + * + * Backward compatibility mode for svn_wc_diff6(), + * when ANCHOR_AT_GIVEN_PATHS is false: * - * If ROOT_IS_FILE is NOT NULL set it - * the first processor call. (The anchor is LOCAL_ABSPATH or an ancestor of it) + * Send diff processor relpaths relative to LOCAL_ABSPATH if it is a + * directory; otherwise, relative to the parent of LOCAL_ABSPATH. + * This matches the "anchor and target" semantics of svn_wc_diff6(). */ svn_error_t * -svn_wc__diff7(const char **root_relpath, - svn_boolean_t *root_is_dir, +svn_wc__diff7(svn_boolean_t anchor_at_given_paths, svn_wc_context_t *wc_ctx, const char *local_abspath, svn_depth_t depth, diff --git a/subversion/include/svn_client.h b/subversion/include/svn_client.h index 06dbbc35a7b3..c8bc74b3cbca 100644 --- a/subversion/include/svn_client.h +++ b/subversion/include/svn_client.h @@ -500,7 +500,7 @@ typedef struct svn_client_commit_item3_t * contents in @c incoming_prop_changes->pool, so that it has the * same lifetime as this data structure. * - * See http://subversion.tigris.org/issues/show_bug.cgi?id=806 for a + * See https://issues.apache.org/jira/browse/SVN-806 for a * description of what would happen if the post-commit process * didn't group these changes together with all other changes to the * item. @@ -520,7 +520,7 @@ typedef struct svn_client_commit_item3_t /** * When processing the commit this contains the relative path for - * the commit session. #NULL until the commit item is preprocessed. + * the commit session. NULL until the commit item is preprocessed. * @since New in 1.7. */ const char *session_relpath; @@ -736,16 +736,11 @@ typedef svn_error_t *(*svn_client_get_commit_log_t)( * @{ */ -/** Callback type used by svn_client_blame5() to notify the caller +/** Callback type used by svn_client_blame6() to notify the caller * that line @a line_no of the blamed file was last changed in @a revision * which has the revision properties @a rev_props, and that the contents were * @a line. * - * @a start_revnum and @a end_revnum contain the start and end revision - * number of the entire blame operation, as determined from the repository - * inside svn_client_blame5(). This can be useful for the blame receiver - * to format the blame output. - * * If svn_client_blame5() was called with @a include_merged_revisions set to * TRUE, @a merged_revision, @a merged_rev_props and @a merged_path will be * set, otherwise they will be NULL. @a merged_path will be set to the @@ -758,6 +753,49 @@ typedef svn_error_t *(*svn_client_get_commit_log_t)( * will be true if the reason there is no blame information is that the line * was modified locally. In all other cases @a local_change will be false. * + * Character Encoding and Line Splitting: + * + * It is up to the client to determine the character encoding. The @a line + * content is delivered without any encoding conversion. The line splitting + * is designed to work with ASCII-compatible encodings including UTF-8. Any + * of the byte sequences LF ("\n"), CR ("\n"), CR LF ("\r\n") ends a line + * and is not included in @a line. The @a line content can include all other + * byte values including zero (ASCII NUL). + * + * @note That is how line splitting is done on the final file content, from + * which this callback is driven. It is not entirely clear whether the line + * splitting used to calculate diffs between each revision and assign a + * revision number to each line is exactly compatible with this in all cases. + * + * Blaming files that have <tt>svn:mime-type</tt> set to something other + * than <tt>text/...</tt> requires the @a ignore_mime_type flag to be set to + * true when calling the svn_client_blame6 function. + * + * @since New in 1.12. + */ +typedef svn_error_t *(*svn_client_blame_receiver4_t)( + 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); + +/** + * Similar to #svn_client_blame_receiver4_t, but with the @a line parameter + * as a (const char*) instead of an svn_string_t, and the parameters + * @a start_revnum and @a end_revnum contain the start and end revision + * number of the entire blame operation, as resolved from the repository + * inside svn_client_blame6(). + * + * @deprecated Provided for backward compatibility with the 1.11 API. + * To replace @a start_revnum and @a end_revnum, see the corresponding + * output parameters in svn_client_blame6(). + * * @since New in 1.7. */ typedef svn_error_t *(*svn_client_blame_receiver3_t)( @@ -1547,6 +1585,38 @@ svn_client_switch(svn_revnum_t *result_rev, /** @} */ +/** Callback for svn_client__layout_list() + * + * @warning EXPERIMENTAL. + */ +typedef svn_error_t * (*svn_client__layout_func_t)( + void *layout_baton, + const char *local_abspath, + const char *repos_root_url, + svn_boolean_t not_present, + svn_boolean_t url_changed, + const char *url, + svn_boolean_t revision_changed, + svn_revnum_t revision, + svn_boolean_t depth_changed, + svn_depth_t depth, + apr_pool_t *scratch_pool); + +/** + * Describe the layout of the working copy below @a local_abspath to + * the callback @a layout. + * + * @warning EXPERIMENTAL. + */ +SVN_EXPERIMENTAL +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); + + /** * @defgroup Add Begin versioning files/directories in a working copy. * @@ -2888,6 +2958,12 @@ svn_client_log(const apr_array_header_t *targets, * #SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) and the client is 1.9.0 or * newer. * + * Before the first call to @a receiver, set @a *start_revnum_p and + * @a *end_revnum_p to the start and end revision number of the entire + * blame operation, as resolved from the repository. This can be useful + * for the blame receiver to format the blame output. Any or both of these + * arguments may be @c NULL. + * * Use @a diff_options to determine how to compare different revisions of the * target. * @@ -2896,8 +2972,33 @@ svn_client_log(const apr_array_header_t *targets, * * Use @a pool for any temporary allocation. * + * @since New in 1.12. + */ +svn_error_t * +svn_client_blame6(svn_revnum_t *start_revnum_p, + svn_revnum_t *end_revnum_p, + const char *path_or_url, + 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_receiver4_t receiver, + void *receiver_baton, + svn_client_ctx_t *ctx, + apr_pool_t *pool); + + +/** + * Similar to svn_client_blame6(), but with #svn_client_blame_receiver3_t + * as the receiver. + * + * @deprecated Provided for backwards compatibility with the 1.11 API. + * * @since New in 1.7. */ +SVN_DEPRECATED svn_error_t * svn_client_blame5(const char *path_or_url, const svn_opt_revision_t *peg_revision, @@ -2911,9 +3012,8 @@ svn_client_blame5(const char *path_or_url, svn_client_ctx_t *ctx, apr_pool_t *pool); - /** - * Similar to svn_client_blame5(), but with #svn_client_blame_receiver3_t + * Similar to svn_client_blame5(), but with #svn_client_blame_receiver2_t * as the receiver. * * @deprecated Provided for backwards compatibility with the 1.6 API. @@ -3060,11 +3160,17 @@ svn_client_blame(const char *path_or_url, * The above two options are mutually exclusive. It is an error to set * both to TRUE. * + * 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. + * * Generated headers are encoded using @a header_encoding. * - * Diff output will not be generated for binary files, unless @a - * ignore_content_type is TRUE, in which case diffs will be shown - * regardless of the content types. + * If either side has an svn:mime-type property that indicates 'binary' + * content, then if @a ignore_content_type 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. * * @a diff_options (an array of <tt>const char *</tt>) is used to pass * additional command line options to the diff processes invoked to compare @@ -3090,8 +3196,39 @@ svn_client_blame(const char *path_or_url, * @note @a relative_to_dir doesn't affect the path index generated by * external diff programs. * + * @since New in 1.11. + */ +svn_error_t * +svn_client_diff7(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, + svn_boolean_t pretty_print_mergeinfo, + 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); + +/** Similar to svn_client_diff7(), but with @a pretty_print_mergeinfo + * always passed as @c TRUE. + * + * @deprecated Provided for backward compatibility with the 1.10 API. * @since New in 1.8. */ +SVN_DEPRECATED svn_error_t * svn_client_diff6(const apr_array_header_t *diff_options, const char *path_or_url1, @@ -3249,14 +3386,45 @@ svn_client_diff(const apr_array_header_t *diff_options, * be either a working-copy path or URL. * * If @a peg_revision is #svn_opt_revision_unspecified, behave - * identically to svn_client_diff6(), using @a path_or_url for both of that + * identically to svn_client_diff7(), using @a path_or_url for both of that * function's @a path_or_url1 and @a path_or_url2 arguments. * - * All other options are handled identically to svn_client_diff6(). + * All other options are handled identically to svn_client_diff7(). * * @since New in 1.8. */ svn_error_t * +svn_client_diff_peg7(const apr_array_header_t *diff_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, + svn_boolean_t pretty_print_mergeinfo, + 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); + +/** Similar to svn_client_diff_peg7(), but with @a pretty_print_mergeinfo + * always passed as @c TRUE. + * + * @deprecated Provided for backward compatibility with the 1.7 API. + * @since New in 1.7. + */ +SVN_DEPRECATED +svn_error_t * svn_client_diff_peg6(const apr_array_header_t *diff_options, const char *path_or_url, const svn_opt_revision_t *peg_revision, @@ -3419,7 +3587,7 @@ svn_client_diff_peg(const apr_array_header_t *diff_options, * Calls @a summarize_func with @a summarize_baton for each difference * with a #svn_client_diff_summarize_t structure describing the difference. * - * See svn_client_diff6() for a description of the other parameters. + * See svn_client_diff7() for a description of the other parameters. * * @since New in 1.5. */ @@ -4274,17 +4442,23 @@ svn_client_relocate(const char *dir, /** * Restore the pristine version of working copy @a paths, - * effectively undoing any local mods. For each path in @a paths, - * revert it if it is a file. Else if it is a directory, revert - * according to @a depth: + * effectively undoing any local mods. This means returning each + * path's versioned status to 'unmodified' and changing its on-disk + * state to match that. + * + * If an item was in a state of conflict, reverting also marks the + * conflict as resolved. If there are conflict marker files attached + * to the item, these are removed. * * @a paths is an array of (const char *) local WC paths. * - * If @a depth is #svn_depth_empty, revert just the properties on - * the directory; else if #svn_depth_files, revert the properties + * For each path in @a paths, revert it if it is a file. Else if it is + * a directory, revert according to @a depth: + * If @a depth is #svn_depth_empty, revert just + * the directory; else if #svn_depth_files, revert the directory * 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, + * immediate subdirectories; else if #svn_depth_infinity, * revert path and everything under it fully recursively. * * @a changelists is an array of <tt>const char *</tt> changelist @@ -4296,9 +4470,18 @@ svn_client_relocate(const char *dir, * If @a clear_changelists is TRUE, then changelist information for the * paths is cleared while reverting. * - * If @a metadata_only is TRUE, the files and directories aren't changed - * by the operation. If there are conflict marker files attached to the - * targets these are removed. + * The @a metadata_only and @a added_keep_local options control the + * extent of reverting. If @a metadata_only is TRUE, the working copy + * files are untouched, but if there are conflict marker files attached + * to these files these markers are removed. Otherwise, if + * @a added_keep_local is TRUE, then all items are reverted except an + * item that was scheduled as plain 'add' (not a copy) will not be + * removed from the working copy. Otherwise, all items are reverted and + * their on-disk state changed to match. + * + * Consult the @c SVN_CONFIG_OPTION_USE_COMMIT_TIMES option in @a ctx to + * determine whether or not to revert timestamp to the time of last + * commit ('use-commit-times = yes'). * * If @a ctx->notify_func2 is non-NULL, then for each item reverted, * call @a ctx->notify_func2 with @a ctx->notify_baton2 and the path of @@ -4308,8 +4491,28 @@ svn_client_relocate(const char *dir, * then do not error, just invoke @a ctx->notify_func2 with @a * ctx->notify_baton2, using notification code #svn_wc_notify_skip. * + * @warning The 'revert' command intentionally and permanently loses + * local modifications. + * + * @since New in 1.11. + */ +svn_error_t * +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 *scratch_pool); + +/** Similar to svn_client_revert4(), but with @a added_keep_local set to + * TRUE. + * * @since New in 1.9. + * @deprecated Provided for backwards compatibility with the 1.10 API. */ +SVN_DEPRECATED svn_error_t * svn_client_revert3(const apr_array_header_t *paths, svn_depth_t depth, @@ -4427,12 +4630,23 @@ typedef enum svn_client_conflict_option_id_t { svn_client_conflict_option_incoming_move_dir_merge, /* Options for local move vs incoming edit on 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, /**< @since New in 1.11. */ + + /* Options for local missing vs incoming edit on merge. */ + svn_client_conflict_option_sibling_move_file_text_merge, /**< @since New in 1.11. */ + svn_client_conflict_option_sibling_move_dir_merge, /**< @since New in 1.11. */ + + /* Options for local move vs incoming move on merge. */ + svn_client_conflict_option_both_moved_file_merge, /*< @since New in 1.12 */ + svn_client_conflict_option_both_moved_file_move_merge, /*< @since New in 1.12 */ + svn_client_conflict_option_both_moved_dir_merge, /*< @since New in 1.12 */ + svn_client_conflict_option_both_moved_dir_move_merge, /*< @since New in 1.12 */ } svn_client_conflict_option_id_t; /** * Set a merged property value on @a option to @a merged_propval. - * + * * Setting the merged value is required before resolving the property * conflict using an option with ID svn_client_conflict_option_merged_text. * @@ -4448,26 +4662,45 @@ svn_client_conflict_option_set_merged_propval( const svn_string_t *merged_propval); /** - * Get a list of possible repository paths which can be applied to the - * svn_client_conflict_option_incoming_move_file_text_merge or - * svn_client_conflict_option_incoming_move_dir_merge resolution - * @a option. (If a different option is passed in, this function will - * raise an assertion failure.) - * - * In some situations, there can be multiple possible destinations for an - * incoming move. One such situation is where a file was copied and moved - * in the same revision: svn cp a b; svn mv a c; svn commit + * Get a list of possible repository paths which can be applied to @a option. + * + * In some situations, there can be multiple possible destinations for a move. + * One such situation is where a file was copied and moved in the same revision: + * svn cp a b; svn mv a c; svn commit * When this move is merged elsewhere, both b and c will appear as valid move * destinations to the conflict resolver. To resolve such ambiguity, the client * may call this function to obtain a list of possible destinations the user * may choose from. * + * @a *possible_moved_to_repos_relpaths is set to NULL if the @a option does + * not support multiple move targets. API users may assume that only one option + * among those which can be applied to a conflict supports move targets. + * * The array is allocated in @a result_pool and will have "const char *" * elements pointing to strings also allocated in @a result_pool. * All paths are relpaths, and relative to the repository root. * - * @see svn_client_conflict_option_set_moved_to_repos_relpath() + * @see svn_client_conflict_option_set_moved_to_repos_relpath2() + * @since New in 1.11. + */ +svn_error_t * +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, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/** + * Get a list of possible repository paths which can be applied to the + * svn_client_conflict_option_incoming_move_file_text_merge, or the + * svn_client_conflict_option_incoming_move_dir_merge resolution @a option. + * + * In SVN 1.10, if a different option is passed in, this function will + * raise an assertion failure. Otherwise this function behaves just like + * svn_client_conflict_option_get_moved_to_repos_relpath_candidates2(). + * * @since New in 1.10. + * @deprecated use svn_client_conflict_option_get_moved_to_repos_relpath_candidates2() */ svn_error_t * svn_client_conflict_option_get_moved_to_repos_relpath_candidates( @@ -4477,19 +4710,34 @@ svn_client_conflict_option_get_moved_to_repos_relpath_candidates( apr_pool_t *scratch_pool); /** - * Set the preferred moved target repository path for the - * svn_client_conflict_option_incoming_move_file_text_merge or - * svn_client_conflict_option_incoming_move_dir_merge resolution option. - * + * Set the preferred moved target repository path. If @a option is not + * applicable to a moved target repository path, do nothing. + * * @a preferred_move_target_idx must be a valid index into the list returned * by svn_client_conflict_option_get_moved_to_repos_relpath_candidates(). - * + * * This function can be called multiple times. * It affects the output of svn_client_conflict_tree_get_description() and * svn_client_conflict_option_get_description(). Call these functions again * to get updated descriptions containing the newly selected move target. * + * @since New in 1.11. + */ +svn_error_t * +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); + +/** + * Like svn_client_conflict_option_set_moved_to_repos_relpath2(), except + * that in SVN 1.10 it raises an assertion failure if an option other + * than svn_client_conflict_option_incoming_move_file_text_merge or + * svn_client_conflict_option_incoming_move_dir_merge is passed. + * * @since New in 1.10. + * @deprecated use svn_client_conflict_option_set_moved_to_repos_relpath2() */ svn_error_t * svn_client_conflict_option_set_moved_to_repos_relpath( @@ -4500,24 +4748,45 @@ svn_client_conflict_option_set_moved_to_repos_relpath( /** * Get a list of possible moved-to abspaths in the working copy which can be - * applied to the svn_client_conflict_option_incoming_move_file_text_merge - * or svn_client_conflict_option_incoming_move_dir_merge resolution @a option. - * (If a different option is passed in, this function will raise an assertion - * failure.) - * - * All paths in the returned list correspond to the repository path which - * is assumed to be the destination of the incoming move operation. - * To support cases where this destination path is ambiguous, the client may - * call svn_client_conflict_option_get_moved_to_repos_relpath_candidates() - * before calling this function to let the user select a repository path first. - * + * applied to @a option. + * + * All working copy paths in the returned list correspond to one repository + * path which is be one of the possible destinations of a move operation. + * More than one repository-side move target candidate may exist; call + * svn_client_conflict_option_get_moved_to_repos_relpath_candidates() before + * calling this function to let the user select a repository path first. + * Otherwise, one of the repository-side paths will be selected internally. + * + * @a *possible_moved_to_abspaths is set to NULL if the @a option does not + * support multiple move targets. API users may assume that only one option + * among those which can be applied to a conflict supports move targets. + * * If no possible moved-to paths can be found, return an empty array. * This doesn't mean that no move happened in the repository. It is possible * that the move destination is outside the scope of the current working copy, * for example, in which case the conflict must be resolved in some other way. * - * @see svn_client_conflict_option_set_moved_to_abspath() + * @see svn_client_conflict_option_set_moved_to_abspath2() + * @since New in 1.11. + */ +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); + +/** + * Get a list of possible moved-to abspaths in the working copy which can be + * svn_client_conflict_option_incoming_move_file_text_merge, or the + * svn_client_conflict_option_incoming_move_dir_merge resolution @a option. + * + * In SVN 1.10, if a different option is passed in, this function will + * raise an assertion failure. Otherwise this function behaves just like + * svn_client_conflict_option_get_moved_to_abspath_candidates2(). + * * @since New in 1.10. + * @deprecated use svn_client_conflict_option_get_moved_to_abspath_candidates2() */ svn_error_t * svn_client_conflict_option_get_moved_to_abspath_candidates( @@ -4527,14 +4796,34 @@ svn_client_conflict_option_get_moved_to_abspath_candidates( apr_pool_t *scratch_pool); /** - * Set the preferred moved target abspath for the - * svn_client_conflict_option_incoming_move_file_text_merge or - * svn_client_conflict_option_incoming_move_dir_merge resolution option. - * + * Set the preferred moved target working copy path. If @a option is not + * applicable to a moved target working copy path, do nothing. + * * @a preferred_move_target_idx must be a valid index into the list - * returned by svn_client_conflict_option_get_moved_to_abspath_candidates(). - * + * returned by svn_client_conflict_option_get_moved_to_abspath_candidates2(). + * + * This function can be called multiple times. + * It affects the output of svn_client_conflict_tree_get_description() and + * svn_client_conflict_option_get_description(). Call these functions again + * to get updated descriptions containing the newly selected move target. + * + * @since New in 1.11. + */ +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); + +/** + * Like svn_client_conflict_option_set_moved_to_abspath2(), except that + * in SVN 1.10 this function raises an assertion failure if an option + * other than svn_client_conflict_option_incoming_move_file_text_merge or + * svn_client_conflict_option_incoming_move_dir_merge is passed. + * * @since New in 1.10. + * @deprecated use svn_client_conflict_option_set_moved_to_abspath2() */ svn_error_t * svn_client_conflict_option_set_moved_to_abspath( @@ -4558,7 +4847,7 @@ svn_client_conflict_option_find_by_id( /** * Return a conflict for the conflicted path @a local_abspath. - * + * * @since New in 1.10. */ svn_error_t * @@ -4573,7 +4862,7 @@ svn_client_conflict_get(svn_client_conflict_t **conflict, * * The lifetime of @a conflict is limited. Its allocation in * memory will not persist beyond this callback's execution. - * + * * @since New in 1.10. */ typedef svn_error_t *(*svn_client_conflict_walk_func_t)( @@ -4583,15 +4872,15 @@ typedef svn_error_t *(*svn_client_conflict_walk_func_t)( /** * Walk all conflicts within the specified @a depth of @a local_abspath. - * Pass each conflict found during the walk to the @conflict_walk_func + * Pass each conflict found during the walk to the @a conflict_walk_func * callback, along with @a conflict_walk_func_baton. * Use cancellation and notification support provided by client context @a ctx. - * + * * This callback may choose to resolve the conflict. If the act of resolving * a conflict creates new conflicts within the walked working copy (as might * be the case for some tree conflicts), the callback will be invoked for each * such new conflict as well. - * + * * @since New in 1.10. */ svn_error_t * @@ -4611,7 +4900,7 @@ svn_client_conflict_walk(const char *local_abspath, * It contains the names of conflicted properties. If no property conflict * exists, the array will contain no elements. * -* @since New in 1.10. +* @since New in 1.10. */ svn_error_t * svn_client_conflict_get_conflicted(svn_boolean_t *text_conflicted, @@ -4794,7 +5083,7 @@ svn_client_conflict_option_get_description(svn_client_conflict_option_t *option, * Client implementations which aim to avoid excessive interactive prompting * may wish to try a recommended resolution option before falling back to * asking the user which option to use. - * + * * Conflict resolution with a recommended option is not guaranteed to succeed. * Clients should check for errors when trying to resolve a conflict and fall * back to other options and/or interactive prompting when the recommended @@ -4812,7 +5101,7 @@ svn_client_conflict_get_recommended_option_id(svn_client_conflict_t *conflict); * Return the absolute path to the conflicted working copy node described * by @a conflict. * - * @since New in 1.10. + * @since New in 1.10. */ const char * svn_client_conflict_get_local_abspath(svn_client_conflict_t *conflict); @@ -4821,7 +5110,7 @@ svn_client_conflict_get_local_abspath(svn_client_conflict_t *conflict); * Return the operation during which the conflict described by @a * conflict was recorded. * - * @since New in 1.10. + * @since New in 1.10. */ svn_wc_operation_t svn_client_conflict_get_operation(svn_client_conflict_t *conflict); @@ -4829,8 +5118,8 @@ svn_client_conflict_get_operation(svn_client_conflict_t *conflict); /** * Return the action an update, switch, or merge operation attempted to * perform on the working copy node described by @a conflict. - * - * @since New in 1.10. + * + * @since New in 1.10. */ svn_wc_conflict_action_t svn_client_conflict_get_incoming_change(svn_client_conflict_t *conflict); @@ -4843,14 +5132,14 @@ svn_client_conflict_get_incoming_change(svn_client_conflict_t *conflict); * modifications in the working copy. During merge operations it may * additionally be part of the history of the merge target branch, anywhere * between the common ancestor revision and the working copy revision. - * - * @since New in 1.10. + * + * @since New in 1.10. */ svn_wc_conflict_reason_t svn_client_conflict_get_local_change(svn_client_conflict_t *conflict); /** - * Return information about the repository associated with @a conflict. + * Return information about the repository associated with @a conflict. * In case of a foreign-repository merge this will differ from the * repository information associated with the merge target working copy. * @@ -4868,11 +5157,11 @@ svn_client_conflict_get_repos_info(const char **repos_root_url, * old version of the conflicted node described by @a conflict. * * If the repository-relative path is not available, the @a - * *incoming_old_repos_relpath will be set to @c NULL, + * *incoming_old_repos_relpath will be set to @c NULL, * * If the peg revision is not available, @a *incoming_old_regrev will be * set to SVN_INVALID_REVNUM. - * + * * If the node kind is not available or if the node does not exist at the * specified path and revision, @a *incoming_old_node_kind will be set to * svn_node_none. @@ -4881,7 +5170,7 @@ svn_client_conflict_get_repos_info(const char **repos_root_url, * Any output parameter may be set to @c NULL by the caller to indicate that * a particular piece of information should not be returned. * - * In case of tree conflicts, this path@revision does not necessarily exist + * In case of tree conflicts, this "path@revision" does not necessarily exist * in the repository, and it does not necessarily represent the incoming * change which is responsible for the occurance of the tree conflict. * The responsible incoming change is generally located somewhere between @@ -4987,7 +5276,7 @@ svn_client_conflict_prop_get_reject_abspath(svn_client_conflict_t *conflict); * Return the set of property values involved in the conflict of property * PROPNAME described by @a conflict. If a property value is unavailable the * corresponding output argument is set to @c NULL. - * + * * A 3-way diff of these property values can be generated with * svn_diff_mem_string_diff3(). A merged version with conflict * markers can be generated with svn_diff_mem_string_output_merge3(). @@ -5055,11 +5344,11 @@ const char * svn_client_conflict_text_get_mime_type(svn_client_conflict_t *conflict); /** - * Return absolute paths to the versions of the text-conflicted file + * Return absolute paths to the versions of the text-conflicted file * described by @a conflict. * * If a particular content is not available, it is set to @c NULL. - * + * * ### Should this be returning svn_stream_t instead of paths? * @since: New in 1.10. */ @@ -5330,7 +5619,7 @@ svn_client_copy7(const apr_array_header_t *sources, /** * Similar to svn_client_copy7(), but doesn't support meta_data_only * and cannot pin externals. - * + * * * @since New in 1.7. * @deprecated Provided for backward compatibility with the 1.8 API. @@ -6549,7 +6838,7 @@ svn_client_list2(const char *path_or_url, /** * Similar to svn_client_list2(), but with @a recurse instead of @a depth. - * If @a recurse is TRUE, pass #svn_depth_files for @a depth; else + * If @a recurse is FALSE, pass #svn_depth_immediates for @a depth; else * pass #svn_depth_infinity. * * @since New in 1.4. @@ -6714,169 +7003,12 @@ svn_client_cat(svn_stream_t *out, /** @} end group: cat */ - -/** Shelving commands - * - * @defgroup svn_client_shelve_funcs Client Shelving Functions - * @{ - */ - -/** Shelve a change. - * - * Shelve as @a name the local modifications found by @a paths, @a depth, - * @a changelists. Revert the shelved change from the WC unless @a keep_local - * is true. - * - * If @a dry_run is true, don't actually do it. - * - * @since New in 1.10. - * @warning EXPERIMENTAL. - */ -SVN_EXPERIMENTAL -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); - -/** Unshelve the shelved change @a name. - * - * @a local_abspath is any path in the WC and is used to find the WC root. - * Rename the shelved patch to add a '.bak' extension unless @a keep is true. - * - * If @a dry_run is true, don't actually do it. - * - * @since New in 1.10. - * @warning EXPERIMENTAL. - */ -SVN_EXPERIMENTAL -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); - -/** Delete the shelved patch @a name. - * - * @a local_abspath is any path in the WC and is used to find the WC root. - * - * If @a dry_run is true, don't actually do it. - * - * @since New in 1.10. - * @warning EXPERIMENTAL. - */ -SVN_EXPERIMENTAL -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); - -/** Information about a shelved patch. - * - * @since New in 1.10. - * @warning EXPERIMENTAL. - */ -typedef struct svn_client_shelved_patch_info_t -{ - const char *message; /* first line of log message */ - const char *patch_path; /* abspath of the patch file */ - svn_io_dirent2_t *dirent; /* info about the patch file */ - apr_time_t mtime; /* a copy of dirent->mtime */ -} svn_client_shelved_patch_info_t; - -/** Set @a *shelved_patch_infos to a hash, keyed by patch name, of pointers to - * @c svn_client_shelved_patch_info_t structures. - * - * @a local_abspath is any path in the WC and is used to find the WC root. - * - * @since New in 1.10. - * @warning EXPERIMENTAL. - */ -SVN_EXPERIMENTAL -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); - -/** Set @a *any_shelved to indicate if there are any shelved changes in this WC. - * - * This shall provide the answer fast, regardless of how many changes - * are stored, unlike svn_client_shelves_list(). - * - * ### Initial implementation isn't O(1) fast -- it just calls - * svn_client_shelves_list(). - * - * @a local_abspath is any path in the WC and is used to find the WC root. - * - * @since New in 1.10. - * @warning EXPERIMENTAL. - */ -SVN_EXPERIMENTAL -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); - -/** Set @a *affected_paths to a hash with one entry for each path affected - * by the shelf @a name. The hash key is the old path and value is - * the new path, both relative to the WC root. The key and value are the - * same except when a path is moved or copied. - * - * @since New in 1.10. - * @warning EXPERIMENTAL. - */ -SVN_EXPERIMENTAL -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); - -/** Set @a *has_changes to indicate whether the shelf @a name - * contains any modifications, in other words if svn_client_shelf_get_paths() - * would return a non-empty set of paths. - * - * @since New in 1.10. - * @warning EXPERIMENTAL. - */ -SVN_EXPERIMENTAL -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); - -/** @} */ - /** Changelist commands * * @defgroup svn_client_changelist_funcs Client Changelist Functions * @{ */ -/** Implementation note: - * - * For now, changelists are implemented by scattering the - * associations across multiple .svn/entries files in a working copy. - * However, this client API was written so that we have the option of - * changing the underlying implementation -- we may someday want to - * store changelist definitions in a centralized database. - */ - /** * Add each path in @a paths (recursing to @a depth as necessary) to * @a changelist. If a path is already a member of another diff --git a/subversion/include/svn_config.h b/subversion/include/svn_config.h index d194af09efbf..5e2ae1790fa6 100644 --- a/subversion/include/svn_config.h +++ b/subversion/include/svn_config.h @@ -44,8 +44,8 @@ extern "C" { /************************************************************************** *** *** *** For a description of the SVN configuration file syntax, see *** - *** your ~/.subversion/README, which is written out automatically by *** - *** svn_config_ensure(). *** + *** your ~/.subversion/README.txt, which is written out automatically *** + *** by svn_config_ensure(). *** *** *** **************************************************************************/ diff --git a/subversion/include/svn_delta.h b/subversion/include/svn_delta.h index c15788ee4d00..ee9e11fed6b5 100644 --- a/subversion/include/svn_delta.h +++ b/subversion/include/svn_delta.h @@ -495,6 +495,10 @@ svn_txdelta_send_contents(const unsigned char *contents, * since there's nothing else in the delta application's context to * supply a path for error messages.) * + * The @a source stream will NOT be closed. The @a target stream will be + * closed when the window handler is given a null window to signal the + * end of the delta. + * * @note To avoid lifetime issues, @a error_info is copied into * @a pool or a subpool thereof. */ @@ -859,7 +863,7 @@ svn_txdelta_skip_svndiff_window(apr_file_t *file, * @c apply_textdelta / @c apply_textdelta_stream and @c close_file * should not refer to a parent directory baton UNLESS the editor has * taken precautions to allocate it in a pool of the appropriate - * lifetime (the @a dir_pool passed to @c open_directory and + * lifetime (the @a result_pool passed to @c open_directory and * @c add_directory definitely does not have the proper lifetime). * In general, it is recommended to simply avoid keeping a parent * directory baton in a file baton. @@ -1285,24 +1289,47 @@ svn_delta_depth_filter_editor(const svn_delta_editor_t **editor, /** Callback function type for svn_delta_path_driver(). * * The handler of this callback is given the callback baton @a - * callback_baton, @a path which is a relpath relative to the + * callback_baton, @a editor and @a edit_baton which represent the + * editor being driven, @a relpath which is a relpath relative to the * root of the edit, and the @a parent_baton which represents - * path's parent directory as created by the editor passed to - * svn_delta_path_driver(). + * @a relpath's parent directory as created by the editor. + * + * If the handler deletes the node at @a relpath (and does not replace it + * with an added directory) it must set @a *dir_baton to null or leave + * it unchanged. * - * If @a path represents a directory, the handler must return a @a - * *dir_baton for @a path, generated from the same editor (so that the - * driver can later close that directory). + * If the handler opens (or adds) a directory at @a relpath, it must set + * @a *dir_baton to the directory baton for @a relpath, generated from + * the same editor. The driver will close the directory later. * - * If, however, @a path represents a file, the handler should NOT - * return any file batons. It can close any opened or added files - * immediately, or delay that close until the end of the edit when - * svn_delta_path_driver() returns. + * If the handler opens (or adds) a file at @a relpath, the handler must + * set @a *dir_baton to null or leave it unchanged. The handler must + * either close the file immediately, or delay that close until the end + * of the edit when svn_delta_path_driver() returns. * * Finally, if @a parent_baton is @c NULL, then the root of the edit * is also one of the paths passed to svn_delta_path_driver(). The * handler of this callback must call the editor's open_root() * function and return the top-level root dir baton in @a *dir_baton. + * + * @since New in 1.12. + */ +typedef svn_error_t *(*svn_delta_path_driver_cb_func2_t)( + void **dir_baton, + const svn_delta_editor_t *editor, + void *edit_baton, + void *parent_baton, + void *callback_baton, + const char *relpath, + apr_pool_t *pool); + +/** Like #svn_delta_path_driver_cb_func2_t but without the @a editor and + * @a edit_baton parameters. The user must arrange for the editor to be + * passed through @a callback_baton (if required, which it usually is). + * And @a path could possibly have a '/' prefix instead of being a relpath; + * see the note on svn_delta_path_driver2(). + * + * @deprecated Provided for backward compatibility with the 1.11 API. */ typedef svn_error_t *(*svn_delta_path_driver_cb_func_t)( void **dir_baton, @@ -1312,24 +1339,51 @@ typedef svn_error_t *(*svn_delta_path_driver_cb_func_t)( apr_pool_t *pool); -/** Drive @a editor (with its @a edit_baton) to visit each path in @a paths. +/** Drive @a editor (with its @a edit_baton) to visit each path in @a relpaths. + * * As each path is hit as part of the editor drive, use * @a callback_func and @a callback_baton to allow the caller to handle * the portion of the editor drive related to that path. * - * Each path in @a paths is a (const char *) relpath, relative - * to the root path of the @a edit. The editor drive will be - * performed in the same order as @a paths. The paths should be sorted - * using something like svn_sort_compare_paths to ensure that a depth-first - * pattern is observed for directory/file baton creation. If @a sort_paths + * Each path in @a relpaths is a (const char *) relpath, relative + * to the root path of the edit. The editor drive will be + * performed in the same order as @a relpaths. The paths should be sorted + * using something like svn_sort_compare_paths() to ensure that each + * directory in the depth-first walk is visited only once. If @a sort_paths * is set, the function will sort the paths for you. Some callers may need * further customization of the order (ie. libsvn_delta/compat.c). * + * If the first target path (after any requested sorting) is @c "" (the + * root of the edit), the callback function will be responsible for + * calling the editor's @c open_root method; otherwise, this function + * will call @c open_root. + * * Use @a scratch_pool for all necessary allocations. * - * @since New in 1.8. + * @since New in 1.12. */ svn_error_t * +svn_delta_path_driver3(const svn_delta_editor_t *editor, + void *edit_baton, + const apr_array_header_t *relpaths, + svn_boolean_t sort_paths, + svn_delta_path_driver_cb_func2_t callback_func, + void *callback_baton, + apr_pool_t *pool); + +/** Like svn_delta_path_driver3() but with a different callback function + * signature. + * + * Optionally, paths in @a paths could have a '/' prefix instead of being + * relpaths. If any of them do, then (since 1.12) ALL paths sent to the + * callback will have a '/' prefix. + * + * @deprecated Provided for backward compatibility with the 1.11 API. + * @since New in 1.8. Before 1.12, paths sent to the callback were the + * exact paths passed in @a paths. + */ +SVN_DEPRECATED +svn_error_t * svn_delta_path_driver2(const svn_delta_editor_t *editor, void *edit_baton, const apr_array_header_t *paths, @@ -1358,6 +1412,80 @@ svn_delta_path_driver(const svn_delta_editor_t *editor, void *callback_baton, apr_pool_t *scratch_pool); + +/** A state object for the path driver that is obtained from + * svn_delta_path_driver_start() and driven by + * svn_delta_path_driver_step() and svn_delta_path_driver_finish(). + * + * @since New in 1.12. + */ +typedef struct svn_delta_path_driver_state_t svn_delta_path_driver_state_t; + +/** Return a path driver object that can drive @a editor (with its + * @a edit_baton) to visit a series of paths. + * + * As each path is hit as part of the editor drive, the path driver will + * call @a callback_func and @a callback_baton to allow the caller to handle + * the portion of the editor drive related to that path. + * + * This will not call the editor's open_root method; for that, see + * svn_delta_path_driver_step(). + * + * @since New in 1.12. + */ +svn_error_t * +svn_delta_path_driver_start(svn_delta_path_driver_state_t **state_p, + const svn_delta_editor_t *editor, + void *edit_baton, + svn_delta_path_driver_cb_func2_t callback_func, + void *callback_baton, + apr_pool_t *result_pool); + +/** Visit @a relpath. + * + * @a state is the object returned by svn_delta_path_driver_start(). + * + * @a relpath is a relpath relative to the root path of the edit. + * + * This function uses the editor and the callback that were originally + * supplied to svn_delta_path_driver_start(). + * + * This drives the editor in a depth-first order, closing and then opening + * directories if necessary to move from the last visited path to the new + * path, as required by the editor driving rules. + * + * This then calls the callback to allow the caller to handle + * the portion of the editor drive related to that path. + * + * If the first path to visit is @c "" (the root of the edit), the + * callback function will be responsible for calling the editor's + * @c open_root method; otherwise, this function will call @c open_root. + * + * The order of paths to visit should in general be sorted using something + * like svn_sort_compare_paths() to ensure that each directory in the + * depth-first walk is visited only once. Some editors may rely on such a + * restriction. + * + * @since New in 1.12. + */ +svn_error_t * +svn_delta_path_driver_step(svn_delta_path_driver_state_t *state, + const char *relpath, + apr_pool_t *scratch_pool); + +/** Finish driving the editor. + * + * @a state is the object returned by svn_delta_path_driver_start(). + * + * This drives the editor to close any open directories and then calls + * the editor's @c close_edit method. + * + * @since New in 1.12. + */ +svn_error_t * +svn_delta_path_driver_finish(svn_delta_path_driver_state_t *state, + apr_pool_t *scratch_pool); + /** @} */ diff --git a/subversion/include/svn_diff.h b/subversion/include/svn_diff.h index bd2c97084bfb..61f4b77f1eb3 100644 --- a/subversion/include/svn_diff.h +++ b/subversion/include/svn_diff.h @@ -711,7 +711,7 @@ svn_diff_file_output_unified(svn_stream_t *output_stream, * @a conflict_latest to be displayed as conflict markers in the output. * If @a conflict_original, @a conflict_modified, @a conflict_latest and/or * @a conflict_separator is @c NULL, a default marker will be displayed. - * @a conflict_style dictates how conflicts are displayed. + * @a conflict_style dictates how conflicts are displayed. * Uses @a scratch_pool for temporary allocations. * * If not @c NULL, call @a cancel_func with @a cancel_baton once or multiple diff --git a/subversion/include/svn_dirent_uri.h b/subversion/include/svn_dirent_uri.h index 94856f2295e6..cf8152bcfb7c 100644 --- a/subversion/include/svn_dirent_uri.h +++ b/subversion/include/svn_dirent_uri.h @@ -60,12 +60,14 @@ * form, except: * * - @c svn_dirent_canonicalize() + * - @c svn_dirent_canonicalize_safe() * - @c svn_dirent_is_canonical() * - @c svn_dirent_internal_style() * - @c svn_relpath_canonicalize() + * - @c svn_relpath_canonicalize_safe() * - @c svn_relpath_is_canonical() - * - @c svn_relpath__internal_style() * - @c svn_uri_canonicalize() + * - @c svn_uri_canonicalize_safe() * - @c svn_uri_is_canonical() * * The Subversion codebase also recognizes some other classes of path: @@ -144,17 +146,47 @@ extern "C" { #endif /* __cplusplus */ -/** Convert @a dirent from the local style to the canonical internal style. +/** + * Convert @a dirent from the local style to the canonical internal style. * "Local style" means native path separators and "." for the empty path. * * Allocate the result in @a result_pool. * + * @warning This function may call @c abort() if the @a dirent parameter + * is not a valid local-style path. + * Use svn_dirent_internal_style_safe() for tainted input. + * * @since New in 1.6. */ const char * svn_dirent_internal_style(const char *dirent, apr_pool_t *result_pool); +/** + * Convert @a dirent from the local style to the canonical internal style + * and return it in @a *internal_style_dirent. "Local style" means native + * path separators and "." for the empty path. + * + * Similar to svn_dirent_internal_style() (which see), but returns an error + * if the @a dirent can not be canonicalized or of the result does not pass + * the svn_dirent_is_canonical() test. + * + * If the function fails and @a non_canonical_result is not @c NULL, the + * result of the failed canonicalization attempt (which may be @c NULL) + * will be returned in @a *non_canonical_result. + * + * Allocates the results in @a result_pool. Uses @a scratch_pool for + * temporary allocations. + * + * @since New in 1.12. + */ +svn_error_t * +svn_dirent_internal_style_safe(const char **internal_style_dirent, + const char **non_canonical_result, + const char *dirent, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /** Convert @a dirent from the internal style to the local style. * "Local style" means native path separators and "." for the empty path. * If the input is not canonical, the output may not be canonical. @@ -167,18 +199,6 @@ const char * svn_dirent_local_style(const char *dirent, apr_pool_t *result_pool); -/** Convert @a relpath from the local style to the canonical internal style. - * "Local style" means native path separators and "." for the empty path. - * - * Allocate the result in @a result_pool. - * - * @since New in 1.7. - */ -const char * -svn_relpath__internal_style(const char *relpath, - apr_pool_t *result_pool); - - /** Join a base dirent (@a base) with a component (@a component). * * If either @a base or @a component is the empty string, then the other @@ -453,7 +473,8 @@ svn_boolean_t svn_uri_is_root(const char *uri, apr_size_t len); -/** Return a new dirent like @a dirent, but transformed such that some types +/** + * Return a new dirent like @a dirent, but transformed such that some types * of dirent specification redundancies are removed. * * This involves: @@ -467,14 +488,43 @@ svn_uri_is_root(const char *uri, * * Allocate the result in @a result_pool. * + * @warning This function may call @c abort() if @a dirent can not be + * canonicalized. + * Use svn_dirent_canonicalize_safe() for tainted input. + * * @since New in 1.6. */ const char * svn_dirent_canonicalize(const char *dirent, apr_pool_t *result_pool); +/** + * Return a new @a *canonical_dirent like @a dirent, but transformed such + * that some types of dirent specification redundancies are removed. + * + * Similar to svn_dirent_canonicalize() (which see), but returns an error + * if the @a dirent can not be canonicalized or of the result does not pass + * the svn_dirent_is_canonical() test. + * + * If the function fails and @a non_canonical_result is not @c NULL, the + * result of the failed canonicalization attempt (which may be @c NULL) + * will be returned in @a *non_canonical_result. + * + * Allocates the results in @a result_pool. Uses @a scratch_pool for + * temporary allocations. + * + * @since New in 1.12. + */ +svn_error_t * +svn_dirent_canonicalize_safe(const char **canonical_dirent, + const char **non_canonical_result, + const char *dirent, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + -/** Return a new relpath like @a relpath, but transformed such that some types +/** + * Return a new relpath like @a relpath, but transformed such that some types * of relpath specification redundancies are removed. * * This involves: @@ -486,14 +536,44 @@ svn_dirent_canonicalize(const char *dirent, * * Allocate the result in @a result_pool. * + * @warning This function may call @c abort() if @a relpath can not be + * canonicalized. + * Use svn_relpath_canonicalize_safe() for tainted input. + * * @since New in 1.7. */ const char * svn_relpath_canonicalize(const char *relpath, apr_pool_t *result_pool); +/** + * Return a new @a *canonical_relpath like @a relpath, but transformed such + * that some types of relpath specification redundancies are removed. + * + * Similar to svn_relpath_canonicalize() (which see), but returns an error + * if the @a relpath can not be canonicalized or of the result does not + * pass the svn_relpath_is_canonical() test. + * + * If the function fails and @a non_canonical_result is not @c NULL, the + * result of the failed canonicalization attempt (which may be @c NULL) + * will be returned in @a *non_canonical_result. + * + * Allocates the results in @a result_pool. Uses @a scratch_pool for + * temporary allocations. + * + * @since New in 1.12. + */ + +svn_error_t * +svn_relpath_canonicalize_safe(const char **canonical_relpath, + const char **non_canonical_result, + const char *relpath, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + -/** Return a new uri like @a uri, but transformed such that some types +/** + * Return a new uri like @a uri, but transformed such that some types * of uri specification redundancies are removed. * * This involves: @@ -510,12 +590,41 @@ svn_relpath_canonicalize(const char *relpath, * * Allocate the result in @a result_pool. * - * @since New in 1.7. + * @warning This function may call @c abort() if @a uri can not be + * canonicalized. + * Use svn_uri_canonicalize_safe() for tainted input. + * + * @since New in 1.7. */ const char * svn_uri_canonicalize(const char *uri, apr_pool_t *result_pool); +/** + * Return a new @a *canonical_uri like @a uri, but transformed such that + * some types of uri specification redundancies are removed. + * + * Similar to svn_uri_canonicalize() (which see), but returns an error if + * the @a uri can not be canonicalized or of the result does not pass the + * svn_uri_is_canonical() test. + * + * If the function fails and @a non_canonical_result is not @c NULL, the + * result of the failed canonicalization attempt (which may be @c NULL) + * will be returned in @a *non_canonical_result. + * + * Allocates the results in @a result_pool. Uses @a scratch_pool for + * temporary allocations. + * + * @since New in 1.12. + */ +svn_error_t * +svn_uri_canonicalize_safe(const char **canonical_uri, + const char **non_canonical_result, + const char *uri, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + + /** Return @c TRUE iff @a dirent is canonical. * * Use @a scratch_pool for temporary allocations. diff --git a/subversion/include/svn_error_codes.h b/subversion/include/svn_error_codes.h index f95f0a0f0e84..c00c0f5b2f85 100644 --- a/subversion/include/svn_error_codes.h +++ b/subversion/include/svn_error_codes.h @@ -888,6 +888,21 @@ SVN_ERROR_START SVN_ERR_FS_CATEGORY_START + 67, "Content checksums supposedly match but content does not.") + /** @since New in 1.13. */ + SVN_ERRDEF(SVN_ERR_FS_UNRECOGNIZED_IOCTL_CODE, + SVN_ERR_FS_CATEGORY_START + 68, + "Unrecognized filesystem I/O control code") + + /** @since New in 1.14. */ + SVN_ERRDEF(SVN_ERR_FS_REP_SHARING_NOT_ALLOWED, + SVN_ERR_FS_CATEGORY_START + 69, + "Rep-sharing is not allowed.") + + /** @since New in 1.14. */ + SVN_ERRDEF(SVN_ERR_FS_REP_SHARING_NOT_SUPPORTED, + SVN_ERR_FS_CATEGORY_START + 70, + "Rep-sharing is not supported.") + /* repos errors */ SVN_ERRDEF(SVN_ERR_REPOS_LOCKED, @@ -1482,6 +1497,11 @@ SVN_ERROR_START SVN_ERR_MISC_CATEGORY_START + 46, "LZ4 decompression failed") + /** @since New in 1.12. */ + SVN_ERRDEF(SVN_ERR_CANONICALIZATION_FAILED, + SVN_ERR_MISC_CATEGORY_START + 47, + "Could not canonicalize path or URI") + /* command-line client errors */ SVN_ERRDEF(SVN_ERR_CL_ARG_PARSING_ERROR, @@ -1769,7 +1789,7 @@ SVN_ERROR_START SVN_ERRDEF(SVN_ERR_X509_CERT_VERIFY_FAILED, SVN_ERR_X509_CATEGORY_START + 19, - "Certficate verification failed") + "Certificate verification failed") SVN_ERROR_END diff --git a/subversion/include/svn_fs.h b/subversion/include/svn_fs.h index 179774e16a1e..198757c66555 100644 --- a/subversion/include/svn_fs.h +++ b/subversion/include/svn_fs.h @@ -1744,7 +1744,7 @@ svn_fs_paths_changed3(svn_fs_path_change_iterator_t **iterator, * * Use @a pool for all allocations, including the hash and its values. * - * @note Retrieving the #node_rev_id element of #svn_fs_path_change2_t may + * @note Retrieving the #svn_fs_path_change2_t.node_rev_id element may * be expensive in some FS backends. * * @since New in 1.6. @@ -1828,9 +1828,9 @@ svn_fs_node_history(svn_fs_history_t **history_p, * the same as the original. This will happen if the original * location was an interesting one (where the node was modified, or * took place in a copy event). This behavior allows looping callers - * to avoid the calling svn_fs_history_location() on the object - * returned by svn_fs_node_history(), and instead go ahead and begin - * calling svn_fs_history_prev(). + * to avoid calling svn_fs_history_location() on the object returned + * by svn_fs_node_history(), and instead go ahead and begin calling + * svn_fs_history_prev(). * * @note This function uses node-id ancestry alone to determine * modifiedness, and therefore does NOT claim that in any of the @@ -2492,7 +2492,7 @@ svn_fs_file_md5_checksum(unsigned char digest[], * svn_fs_file_contents(). In that case, the result of reading from * @a *contents is undefined. * - * ### @todo kff: I am worried about lifetime issues with this pool vs + * @todo kff: I am worried about lifetime issues with this pool vs * the trail created farther down the call stack. Trace this function * to investigate... */ @@ -3503,6 +3503,54 @@ svn_fs_info_dup(const void *info, apr_pool_t *result_pool, apr_pool_t *scratch_pool); +/** + * A structure specifying the filesystem-specific input/output operation. + * + * @see svn_fs_ioctl() + * + * @since New in 1.13. + */ +typedef struct svn_fs_ioctl_code_t +{ + const char *fs_type; + int code; +} svn_fs_ioctl_code_t; + +/** + * A convenience macro to declare #svn_fs_ioctl_code_t codes. + * + * @since New in 1.13. + */ +#define SVN_FS_DECLARE_IOCTL_CODE(name, fs_type, code) \ + static const svn_fs_ioctl_code_t name = { fs_type, code } + +/** + * Issue a filesystem-specific input/output operation defined by @a ctlcode + * (usually, a low-level operation which cannot be expressed by other + * filesystem APIs). If @a fs is @c NULL, issue a global operation. + * If @a fs is not @c NULL, issue an operation that is specific to this + * filesystem instance. + * + * If the filesystem cannot handle this ioctl code, return the + * #SVN_ERR_FS_UNRECOGNIZED_IOCTL_CODE error. + * + * Allocate the result in @a result_pool, use @a scratch_pool for temporary + * allocations. + * + * @see #svn_fs_ioctl_code_t + * + * @since New in 1.13. + */ +svn_error_t * +svn_fs_ioctl(svn_fs_t *fs, + svn_fs_ioctl_code_t ctlcode, + void *input, + void **output_p, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + /** @} */ #ifdef __cplusplus diff --git a/subversion/include/svn_opt.h b/subversion/include/svn_opt.h index 1c85b61682d3..6644d5659b1c 100644 --- a/subversion/include/svn_opt.h +++ b/subversion/include/svn_opt.h @@ -27,6 +27,8 @@ #ifndef SVN_OPT_H #define SVN_OPT_H +#include "svn_opt_impl.h" + #include <apr.h> #include <apr_pools.h> #include <apr_getopt.h> @@ -69,6 +71,10 @@ typedef svn_error_t *(svn_opt_subcommand_t)( /** The maximum number of options that can be accepted by a subcommand. */ #define SVN_OPT_MAX_OPTIONS 50 +/** The maximum number of paragraphs of help text a subcommand can have. + * @since New in 1.11. */ +#define SVN_OPT_MAX_PARAGRAPHS 100 + /** Options that have no short option char should use an identifying * integer equal to or greater than this. */ @@ -77,7 +83,39 @@ typedef svn_error_t *(svn_opt_subcommand_t)( /** One element of a subcommand dispatch table. * + * @since New in 1.11. + */ +typedef struct svn_opt_subcommand_desc3_t +{ + /** The full name of this command. */ + const char *name; + + /** The function this command invokes. */ + svn_opt_subcommand_t *cmd_func; + + /** A list of alias names for this command (e.g., 'up' for 'update'). */ + const char *aliases[SVN_OPT_MAX_ALIASES]; + + /** A multi-paragraph string describing this command. */ + const char *help[SVN_OPT_MAX_PARAGRAPHS]; + + /** A list of options accepted by this command. Each value in the + * array is a unique enum (the 2nd field in apr_getopt_option_t) + */ + int valid_options[SVN_OPT_MAX_OPTIONS]; + + /** A list of option help descriptions, keyed by the option unique enum + * (the 2nd field in apr_getopt_option_t), which override the generic + * descriptions given in an apr_getopt_option_t on a per-subcommand basis. + */ + struct { int optch; const char *desc; } desc_overrides[SVN_OPT_MAX_OPTIONS]; +} svn_opt_subcommand_desc3_t; + + +/** One element of a subcommand dispatch table. + * * @since New in 1.4. + * @deprecated Provided for backward compatibility with the 1.10 API. */ typedef struct svn_opt_subcommand_desc2_t { @@ -139,8 +177,21 @@ typedef struct svn_opt_subcommand_desc_t * Return the entry in @a table whose name matches @a cmd_name, or @c NULL if * none. @a cmd_name may be an alias. * + * @since New in 1.11. + */ +const svn_opt_subcommand_desc3_t * +svn_opt_get_canonical_subcommand3(const svn_opt_subcommand_desc3_t *table, + const char *cmd_name); + + +/** + * Same as svn_opt_get_canonical_subcommand3(), but with a different + * version of the subcommand description table. + * * @since New in 1.4. + * @deprecated Provided for backward compatibility with the 1.10 API. */ +SVN_DEPRECATED const svn_opt_subcommand_desc2_t * svn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table, const char *cmd_name); @@ -170,8 +221,22 @@ svn_opt_get_canonical_subcommand(const svn_opt_subcommand_desc_t *table, * * The returned value may be statically allocated, or allocated in @a pool. * + * @since New in 1.11. + */ +const apr_getopt_option_t * +svn_opt_get_option_from_code3(int code, + const apr_getopt_option_t *option_table, + const svn_opt_subcommand_desc3_t *command, + apr_pool_t *pool); + +/** + * Same as svn_opt_get_option_from_code3(), but with a different + * version of the subcommand description table. + * * @since New in 1.4. + * @deprecated Provided for backward compatibility with the 1.10 API. */ +SVN_DEPRECATED const apr_getopt_option_t * svn_opt_get_option_from_code2(int code, const apr_getopt_option_t *option_table, @@ -198,8 +263,21 @@ svn_opt_get_option_from_code(int code, * non-NULL, it is a zero-terminated array, and all subcommands take * the options listed in it. * + * @since New in 1.11. + */ +svn_boolean_t +svn_opt_subcommand_takes_option4(const svn_opt_subcommand_desc3_t *command, + int option_code, + const int *global_options); + +/** + * Same as svn_opt_subcommand_takes_option4(), but with a different + * version of the subcommand description table. + * * @since New in 1.5. + * @deprecated Provided for backward compatibility with the 1.10 API. */ +SVN_DEPRECATED svn_boolean_t svn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command, int option_code, @@ -235,7 +313,7 @@ svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command, /** * Print a generic (not command-specific) usage message to @a stream. * - * ### @todo Why is @a stream a stdio file instead of an svn stream? + * @todo Why is @a stream a stdio file instead of an svn stream? * * If @a header is non-NULL, print @a header followed by a newline. Then * loop over @a cmd_table printing the usage for each command (getting @@ -244,8 +322,24 @@ svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command, * * Use @a pool for temporary allocation. * + * @since New in 1.11. + */ +void +svn_opt_print_generic_help3(const char *header, + const svn_opt_subcommand_desc3_t *cmd_table, + const apr_getopt_option_t *opt_table, + const char *footer, + apr_pool_t *pool, + FILE *stream); + +/** + * Same as svn_opt_print_generic_help3(), but with a different + * version of the subcommand description table. + * * @since New in 1.4. + * @deprecated Provided for backward compatibility with the 1.10 API. */ +SVN_DEPRECATED void svn_opt_print_generic_help2(const char *header, const svn_opt_subcommand_desc2_t *cmd_table, @@ -297,8 +391,23 @@ svn_opt_format_option(const char **string, * use that second name as an alias for the first name. This additional * behaviour is new in 1.7. * + * @since New in 1.11. + */ +void +svn_opt_subcommand_help4(const char *subcommand, + const svn_opt_subcommand_desc3_t *table, + const apr_getopt_option_t *options_table, + const int *global_options, + apr_pool_t *pool); + +/** + * Same as svn_opt_subcommand_help4(), but with a different + * version of the subcommand description table. + * * @since New in 1.5. + * @deprecated Provided for backward compatibility with the 1.10 API. */ +SVN_DEPRECATED void svn_opt_subcommand_help3(const char *subcommand, const svn_opt_subcommand_desc2_t *table, @@ -336,43 +445,7 @@ svn_opt_subcommand_help(const char *subcommand, /* Parsing revision and date options. */ - -/** - * Various ways of specifying revisions. - * - * @note - * In contexts where local mods are relevant, the `working' kind - * refers to the uncommitted "working" revision, which may be modified - * with respect to its base revision. In other contexts, `working' - * should behave the same as `committed' or `current'. - */ -enum svn_opt_revision_kind { - /** No revision information given. */ - svn_opt_revision_unspecified, - - /** revision given as number */ - svn_opt_revision_number, - - /** revision given as date */ - svn_opt_revision_date, - - /** rev of most recent change */ - svn_opt_revision_committed, - - /** (rev of most recent change) - 1 */ - svn_opt_revision_previous, - - /** .svn/entries current revision */ - svn_opt_revision_base, - - /** current, plus local mods */ - svn_opt_revision_working, - - /** repository youngest */ - svn_opt_revision_head - - /* please update svn_opt__revision_to_string() when extending this enum */ -}; +/* NOTE: svn_opt_revision_kind is defined in svn_opt_impl.h */ /** * A revision value, which can be specified as a number or a date. @@ -700,9 +773,30 @@ svn_opt_parse_path(svn_opt_revision_t *rev, * --version flag *and* subcommand arguments on a help command line. * The logic for handling such a situation should be in one place. * - * @since New in 1.8. + * @since New in 1.11. */ +svn_error_t * +svn_opt_print_help5(apr_getopt_t *os, + const char *pgm_name, + svn_boolean_t print_version, + svn_boolean_t quiet, + svn_boolean_t verbose, + const char *version_footer, + const char *header, + const svn_opt_subcommand_desc3_t *cmd_table, + const apr_getopt_option_t *option_table, + const int *global_options, + const char *footer, + apr_pool_t *pool); +/** + * Same as svn_opt_print_help5(), but with a different + * version of the subcommand description table. + * + * @since New in 1.8. + * @deprecated Provided for backward compatibility with the 1.10 API. + */ +SVN_DEPRECATED svn_error_t * svn_opt_print_help4(apr_getopt_t *os, const char *pgm_name, diff --git a/subversion/include/svn_opt_impl.h b/subversion/include/svn_opt_impl.h new file mode 100644 index 000000000000..73fd2f36e5c6 --- /dev/null +++ b/subversion/include/svn_opt_impl.h @@ -0,0 +1,86 @@ +/** + * @copyright + * ==================================================================== + * 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. + * ==================================================================== + * @endcopyright + * + * @file svn_opt_impl.h + * @brief Option and argument parsing for Subversion command lines + * (common implementation) + * + * @warning This is a @b private implementation-specific header file. + * User code should include @ref svn_opt.h instead. + */ + +/* NOTE: + * This file *must not* include or depend on any other header except + * the C standard library headers. + */ + +#ifndef SVN_OPT_IMPL_H +#define SVN_OPT_IMPL_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/** + * Various ways of specifying revisions. + * + * @note + * In contexts where local mods are relevant, the `working' kind + * refers to the uncommitted "working" revision, which may be modified + * with respect to its base revision. In other contexts, `working' + * should behave the same as `committed' or `current'. + */ +/* NOTE: Update svnxx/revision.hpp when changing this enum. */ +enum svn_opt_revision_kind { + /** No revision information given. */ + svn_opt_revision_unspecified, + + /** revision given as number */ + svn_opt_revision_number, + + /** revision given as date */ + svn_opt_revision_date, + + /** rev of most recent change */ + svn_opt_revision_committed, + + /** (rev of most recent change) - 1 */ + svn_opt_revision_previous, + + /** .svn/entries current revision */ + svn_opt_revision_base, + + /** current, plus local mods */ + svn_opt_revision_working, + + /** repository youngest */ + svn_opt_revision_head + + /* please update svn_opt__revision_to_string() when extending this enum */ +}; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_OPT_IMPL_H */ diff --git a/subversion/include/svn_props.h b/subversion/include/svn_props.h index 7ea8bba0c048..8b044b059089 100644 --- a/subversion/include/svn_props.h +++ b/subversion/include/svn_props.h @@ -415,19 +415,26 @@ svn_prop_name_is_valid(const char *prop_name); /** Describes external items to check out into this directory. * * The format is a series of lines, each in the following format: - * [-r REV] URL[@PEG] LOCALPATH + * + * [-r REV] URL[@PEG] LOCALPATH + * * LOCALPATH is relative to the directory having this property. * REV pins the external to revision REV. * URL may be a full URL or a relative URL starting with one of: - * ../ to the parent directory of the extracted external - * ^/ to the repository root - * / to the server root - * // to the URL scheme + * + * ../ to the parent directory of the extracted external + * ^/ to the repository root + * / to the server root + * // to the URL scheme + * * The following format is supported for interoperability with * Subversion 1.4 and earlier clients: - * LOCALPATH [-r PEG] URL + * + * LOCALPATH [-r PEG] URL + * * The ambiguous format 'relative_path relative_path' is taken as * 'relative_url relative_path' with peg revision support. + * * Lines starting with a '#' character are ignored. */ #define SVN_PROP_EXTERNALS SVN_PROP_PREFIX "externals" diff --git a/subversion/include/svn_ra.h b/subversion/include/svn_ra.h index 030458d0e16a..4c71520dd1ff 100644 --- a/subversion/include/svn_ra.h +++ b/subversion/include/svn_ra.h @@ -65,7 +65,7 @@ svn_ra_version(void); * @a close_baton as appropriate. * * @a path is relative to the "root" of the session, defined by the - * @a repos_URL passed to svn_ra_open4() vtable call. + * @a repos_URL passed to svn_ra_open5() vtable call. * * @a name is the name of the property to fetch. If the property is present, * then it is returned in @a value. Otherwise, @a *value is set to @c NULL. @@ -229,7 +229,7 @@ typedef void (*svn_ra_progress_notify_func_t)(apr_off_t progress, * * @a revision is the target revision number of the received replay report. * - * @a editor and @a edit_baton should provided by the callback implementation. + * @a *editor and @a *edit_baton should provided by the callback implementation. * * @a replay_baton is the baton as originally passed to replay_range. * @@ -253,7 +253,7 @@ typedef svn_error_t *(*svn_ra_replay_revstart_callback_t)( * * @a revision is the target revision number of the received replay report. * - * @a editor and @a edit_baton should provided by the callback implementation. + * @a editor and @a edit_baton are the values provided by the REVSTART callback. * * @a replay_baton is the baton as originally passed to replay_range. * @@ -369,7 +369,7 @@ typedef struct svn_ra_reporter3_t * implementor should assume the directory has no entries or props. * * This will *override* any previous set_path() calls made on parent - * paths. @a path is relative to the URL specified in svn_ra_open4(). + * paths. @a path is relative to the URL specified in svn_ra_open5(). * * If @a lock_token is non-NULL, it is the lock token for @a path in the WC. * @@ -520,7 +520,7 @@ typedef struct svn_ra_reporter_t /** A collection of callbacks implemented by libsvn_client which allows * an RA layer to "pull" information from the client application, or * possibly store information. libsvn_client passes this vtable to - * svn_ra_open4(). + * svn_ra_open5(). * * Each routine takes a @a callback_baton originally provided with the * vtable. @@ -555,9 +555,9 @@ typedef struct svn_ra_callbacks2_t /** Fetch working copy properties. * - *<pre> ### we might have a problem if the RA layer ever wants a property - * ### that corresponds to a different revision of the file than - * ### what is in the WC. we'll cross that bridge one day...</pre> + * @note we might have a problem if the RA layer ever wants a property + * that corresponds to a different revision of the file than + * what is in the WC. we'll cross that bridge one day... */ svn_ra_get_wc_prop_func_t get_wc_prop; @@ -710,6 +710,14 @@ typedef struct svn_ra_session_t svn_ra_session_t; * within the new repository root URL that @a repos_URL pointed to within * the old repository root URL. * + * If @a redirect_url is not NULL and a @corrected_url is returned, then + * @a redirect_url contains a non-canonicalized version of @a corrected_url, + * as communicated in the network protocol used by the RA provider. + * THe @a redirect_url should be used for to detect redirection loops. + * Canonicalization may change the protocol-level URL in a way that + * makes detection of redirect loops impossible in some cases since URLs which + * are different at the protocol layer could map to the same canonicalized URL. + * * Return @c SVN_ERR_RA_UUID_MISMATCH if @a uuid is non-NULL and not equal * to the UUID of the repository at @c repos_URL. * @@ -728,8 +736,26 @@ typedef struct svn_ra_session_t svn_ra_session_t; * * @see svn_client_open_ra_session(). * + * @since New in 1.14. + */ +svn_error_t * +svn_ra_open5(svn_ra_session_t **session_p, + const char **corrected_url, + const char **redirect_url, + const char *repos_URL, + const char *uuid, + const svn_ra_callbacks2_t *callbacks, + void *callback_baton, + apr_hash_t *config, + apr_pool_t *pool); + +/** Similar to svn_ra_open5(), but with @a redirect_url always passed + * as @c NULL. + * * @since New in 1.7. + * @deprecated Provided for backward compatibility with the 1.13 API. */ +SVN_DEPRECATED svn_error_t * svn_ra_open4(svn_ra_session_t **session_p, const char **corrected_url, @@ -1857,7 +1883,7 @@ svn_ra_get_location_segments(svn_ra_session_t *session, * @note Prior to Subversion 1.9, this function may request delta handlers * from @a handler even for empty text deltas. Starting with 1.9, the * delta handler / baton return arguments passed to @a handler will be - * #NULL unless there is an actual difference in the file contents between + * NULL unless there is an actual difference in the file contents between * the current and the previous call. * * @since New in 1.5. diff --git a/subversion/include/svn_ra_svn.h b/subversion/include/svn_ra_svn.h index 6293255daba9..0bcd40b7aee0 100644 --- a/subversion/include/svn_ra_svn.h +++ b/subversion/include/svn_ra_svn.h @@ -201,7 +201,7 @@ typedef svn_error_t *(*svn_ra_svn_edit_callback)(void *baton); * If @a max_out is not 0, error out and close the connection whenever more * than @a max_out bytes have been send as response to some command. * - * @note The limits enforced may vary slightly by +/- the I/O buffer size. + * @note The limits enforced may vary slightly by +/- the I/O buffer size. * * @note If @a out_stream is an wrapped apr_file_t* the backing file will be * used for some operations. diff --git a/subversion/include/svn_repos.h b/subversion/include/svn_repos.h index 9bb462abbb3c..35ff00045c22 100644 --- a/subversion/include/svn_repos.h +++ b/subversion/include/svn_repos.h @@ -679,7 +679,7 @@ svn_repos_fs_type(svn_repos_t *repos, * The optional @a cancel_func callback will be invoked with * @a cancel_baton as usual to allow the user to preempt this potentially * lengthy operation. - * + * * Use @a scratch_pool for temporary allocations. * * @since New in 1.9. @@ -861,7 +861,7 @@ typedef svn_error_t *(*svn_repos_freeze_func_t)(void *baton, apr_pool_t *pool); * @since New in 1.8. */ svn_error_t * -svn_repos_freeze(apr_array_header_t *paths, +svn_repos_freeze(const apr_array_header_t *paths, svn_repos_freeze_func_t freeze_func, void *freeze_baton, apr_pool_t *pool); @@ -1036,7 +1036,10 @@ svn_repos_hooks_setenv(svn_repos_t *repos, * * @a send_copyfrom_args instructs the driver to send 'copyfrom' * arguments to the editor's add_file() and add_directory() methods, - * whenever it deems feasible. + * and therefore to send their content as deltas against the copy source, + * whenever it deems feasible. The implementation only does so for + * add_file(), and only when the file itself is the copy root (not when + * the file is part of a copied subtree). * * Use @a authz_read_func and @a authz_read_baton (if not @c NULL) to * avoid sending data through @a editor/@a edit_baton which is not @@ -2404,7 +2407,7 @@ svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog, * @note Prior to Subversion 1.9, this function may request delta handlers * from @a handler even for empty text deltas. Starting with 1.9, the * delta handler / baton return arguments passed to @a handler will be - * #NULL unless there is an actual difference in the file contents between + * NULL unless there is an actual difference in the file contents between * the current and the previous call. * * @since New in 1.5. @@ -3357,7 +3360,7 @@ svn_repos_dump_fs4(svn_repos_t *repos, apr_pool_t *pool); /** - * Similar to svn_repos_dump_fs4(), but with @a include_revprops and + * Similar to svn_repos_dump_fs4(), but with @a include_revprops and * @a include_changes both set to @c TRUE and @a filter_func and * @a filter_baton set to @c NULL. * @@ -3803,7 +3806,7 @@ typedef struct svn_repos_parse_fns3_t * * @since New in 1.8. - * @since Starting in 1.10, @a parse_fns may contain #NULL pointers for + * @since Starting in 1.10, @a parse_fns may contain NULL pointers for * those callbacks that the caller is not interested in. */ svn_error_t * @@ -4144,6 +4147,19 @@ svn_error_t * svn_repos_authz_initialize(apr_pool_t *pool); /** + * Callback for reporting authz file parsing warnings. + * + * The implementation may use @a scratch_pool for temporary + * allocations but should not assume that the lifetime of that pool + * persists past the callback invocation. + * + * The implementation @e must @e not clear @a error. + */ +typedef void (*svn_repos_authz_warning_func_t)(void *baton, + const svn_error_t *error, + apr_pool_t *scratch_pool); + +/** * Read authz configuration data from @a path (a dirent, an absolute file url * or a registry path) into @a *authz_p, allocated in @a pool. * @@ -4161,8 +4177,31 @@ svn_repos_authz_initialize(apr_pool_t *pool); * repository instance. Otherwise, set it to NULL and the repositories will * be opened as needed. * + * If the @a warning_func callback is not @c NULL, it is called + * (with @a warning_baton) to report non-fatal warnings emitted by + * the parser. + * + * @since New in 1.12. + */ +svn_error_t * +svn_repos_authz_read4(svn_authz_t **authz_p, + const char *path, + const char *groups_path, + svn_boolean_t must_exist, + svn_repos_t *repos_hint, + svn_repos_authz_warning_func_t warning_func, + void *warning_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/** + * Similar to svn_repos_authz_read3(), but with @a warning_func and + * @a warning_baton set to @c NULL. + * * @since New in 1.10. + * @deprecated Provided for backward compatibility with the 1.11 API. */ +SVN_DEPRECATED svn_error_t * svn_repos_authz_read3(svn_authz_t **authz_p, const char *path, @@ -4203,12 +4242,35 @@ svn_repos_authz_read(svn_authz_t **authz_p, /** * Read authz configuration data from @a stream into @a *authz_p, - * allocated in @a pool. + * allocated in @a result_pool. * * If @a groups_stream is set, use the global groups parsed from it. * + * If the @a warning_func callback is not @c NULL, it is called + * (with @a warning_baton) to report non-fatal warnings emitted by + * the parser. + * + * Uses @a scratch_pool for temporary aloocations. + * + * @since New in 1.12. + */ +svn_error_t * +svn_repos_authz_parse2(svn_authz_t **authz_p, + svn_stream_t *stream, + svn_stream_t *groups_stream, + svn_repos_authz_warning_func_t warning_func, + void *warning_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool); + +/** + * Similar to svn_repos_authz_parse2(), but with @a warning_func and + * @a warning_baton set to @c NULL. + * * @since New in 1.8. + * @deprecated Provided for backward compatibility with the 1.11 API. */ +SVN_DEPRECATED svn_error_t * svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream, diff --git a/subversion/include/svn_time.h b/subversion/include/svn_time.h index 76517ca79c37..ac857fb2b94e 100644 --- a/subversion/include/svn_time.h +++ b/subversion/include/svn_time.h @@ -38,14 +38,21 @@ extern "C" { /** Convert @a when to a <tt>const char *</tt> representation allocated - * in @a pool. Use svn_time_from_cstring() for the reverse - * conversion. + * in @a pool. + * + * @see svn_time_from_cstring() for the reverse conversion. */ const char * svn_time_to_cstring(apr_time_t when, apr_pool_t *pool); /** Convert @a data to an @c apr_time_t @a when. + * + * @see svn_time_to_cstring() for the reverse conversion. + * + * @deprecated Also accepts a format that was used before Subversion 0.14. + * See implementation for details. Use of this format is deprecated. + * * Use @a pool for temporary memory allocation. */ svn_error_t * diff --git a/subversion/include/svn_types.h b/subversion/include/svn_types.h index 394eda866937..418d6ac9cc84 100644 --- a/subversion/include/svn_types.h +++ b/subversion/include/svn_types.h @@ -27,6 +27,8 @@ #ifndef SVN_TYPES_H #define SVN_TYPES_H +#include "svn_types_impl.h" + /* ### this should go away, but it causes too much breakage right now */ #include <stdlib.h> #include <limits.h> /* for ULONG_MAX */ @@ -247,35 +249,6 @@ typedef struct svn_version_t svn_version_t; -/** @defgroup apr_hash_utilities APR Hash Table Helpers - * These functions enable the caller to dereference an APR hash table index - * without type casts or temporary variables. - * - * These functions are provided by APR itself from version 1.5. - * Definitions are provided here for when using older versions of APR. - * @{ - */ - -#if !APR_VERSION_AT_LEAST(1, 5, 0) - -/** Return the key of the hash table entry indexed by @a hi. */ -const void * -apr_hash_this_key(apr_hash_index_t *hi); - -/** Return the key length of the hash table entry indexed by @a hi. */ -apr_ssize_t -apr_hash_this_key_len(apr_hash_index_t *hi); - -/** Return the value of the hash table entry indexed by @a hi. */ -void * -apr_hash_this_val(apr_hash_index_t *hi); - -#endif - -/** @} */ - - - /** On Windows, APR_STATUS_IS_ENOTDIR includes several kinds of * invalid-pathname error but not ERROR_INVALID_NAME, so we include it. * We also include ERROR_DIRECTORY as that was not included in apr versions @@ -303,28 +276,7 @@ apr_hash_this_val(apr_hash_index_t *hi); -/** The various types of nodes in the Subversion filesystem. */ -typedef enum svn_node_kind_t -{ - /** absent */ - svn_node_none, - - /** regular file */ - svn_node_file, - - /** directory */ - svn_node_dir, - - /** something's here, but we don't know what */ - svn_node_unknown, - - /** - * symbolic link - * @note This value is not currently used by the public API. - * @since New in 1.8. - */ - svn_node_symlink -} svn_node_kind_t; +/* NOTE: svn_node_kind_t is defined in svn_types_impl.h */ /** Return a constant string expressing @a kind as an English word, e.g., * "file", "dir", etc. The string is not localized, as it may be used for @@ -346,23 +298,7 @@ svn_node_kind_t svn_node_kind_from_word(const char *word); -/** Generic three-state property to represent an unknown value for values - * that are just like booleans. The values have been set deliberately to - * make tristates disjoint from #svn_boolean_t. - * - * @note It is unsafe to use apr_pcalloc() to allocate these, since '0' is - * not a valid value. - * - * @since New in 1.7. */ -typedef enum svn_tristate_t -{ - /** state known to be false (the constant does not evaulate to false) */ - svn_tristate_false = 2, - /** state known to be true */ - svn_tristate_true, - /** state could be true or false */ - svn_tristate_unknown -} svn_tristate_t; +/* NOTE: svn_tristate_t is defined in svn_types_impl.h */ /** Return a constant string "true", "false" or NULL representing the value of * @a tristate. @@ -422,15 +358,11 @@ svn_tristate__from_word(const char * word); -/** A revision number. */ -typedef long int svn_revnum_t; +/* NOTE: svn_revnum_t and SVN_INVALID_REVNUM are defined in svn_types_impl.h */ /** Valid revision numbers begin at 0 */ #define SVN_IS_VALID_REVNUM(n) ((n) >= 0) -/** The 'official' invalid revision num */ -#define SVN_INVALID_REVNUM ((svn_revnum_t) -1) - /** Not really invalid...just unimportant -- one day, this can be its * own unique value, for now, just make it the same as * #SVN_INVALID_REVNUM. @@ -494,55 +426,7 @@ enum svn_recurse_kind svn_recursive }; -/** The concept of depth for directories. - * - * @note This is similar to, but not exactly the same as, the WebDAV - * and LDAP concepts of depth. - * - * @since New in 1.5. - */ -typedef enum svn_depth_t -{ - /* The order of these depths is important: the higher the number, - the deeper it descends. This allows us to compare two depths - numerically to decide which should govern. */ - - /** Depth undetermined or ignored. In some contexts, this means the - client should choose an appropriate default depth. The server - will generally treat it as #svn_depth_infinity. */ - svn_depth_unknown = -2, - - /** Exclude (i.e., don't descend into) directory D. - @note In Subversion 1.5, svn_depth_exclude is *not* supported - anywhere in the client-side (libsvn_wc/libsvn_client/etc) code; - it is only supported as an argument to set_path functions in the - ra and repos reporters. (This will enable future versions of - Subversion to run updates, etc, against 1.5 servers with proper - svn_depth_exclude behavior, once we get a chance to implement - client-side support for svn_depth_exclude.) - */ - svn_depth_exclude = -1, - - /** Just the named directory D, no entries. Updates will not pull in - any files or subdirectories not already present. */ - svn_depth_empty = 0, - - /** D + its file children, but not subdirs. Updates will pull in any - files not already present, but not subdirectories. */ - svn_depth_files = 1, - - /** D + immediate children (D and its entries). Updates will pull in - any files or subdirectories not already present; those - subdirectories' this_dir entries will have depth-empty. */ - svn_depth_immediates = 2, - - /** D + all descendants (full recursion from D). Updates will pull - in any files or subdirectories not already present; those - subdirectories' this_dir entries will have depth-infinity. - Equivalent to the pre-1.5 default update behavior. */ - svn_depth_infinity = 3 - -} svn_depth_t; +/* NOTE: svn_depth_t is defined in svn_types_impl.h */ /** Return a constant string expressing @a depth as an English word, * e.g., "infinity", "immediates", etc. The string is not localized, diff --git a/subversion/include/svn_types_impl.h b/subversion/include/svn_types_impl.h new file mode 100644 index 000000000000..625597f339eb --- /dev/null +++ b/subversion/include/svn_types_impl.h @@ -0,0 +1,157 @@ +/** + * @copyright + * ==================================================================== + * 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. + * ==================================================================== + * @endcopyright + * + * @file svn_types_impl.h + * @brief Subversion's data types (common implementation) + * + * @warning This is a @b private implementation-specific header file. + * User code should include @ref svn_types.h instead. + */ + +/* NOTE: + * This file *must not* include or depend on any other header except + * the C standard library headers. + */ + +#ifndef SVN_TYPES_IMPL_H +#define SVN_TYPES_IMPL_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#ifndef DOXYGEN +/* Forward declaration of the error object. */ +struct svn_error_t; +#endif + + +/** The various types of nodes in the Subversion filesystem. */ +typedef enum svn_node_kind_t +{ + /** absent */ + svn_node_none, + + /** regular file */ + svn_node_file, + + /** directory */ + svn_node_dir, + + /** something's here, but we don't know what */ + svn_node_unknown, + + /** + * symbolic link + * @note This value is not currently used by the public API. + * @since New in 1.8. + */ + svn_node_symlink +} svn_node_kind_t; + + +/** Generic three-state property to represent an unknown value for values + * that are just like booleans. The values have been set deliberately to + * make tristates disjoint from #svn_boolean_t. + * + * @note It is unsafe to use apr_pcalloc() to allocate these, since '0' is + * not a valid value. + * + * @since New in 1.7. */ +/* NOTE: Update svnxx/tristate.hpp when changing this enum. */ +typedef enum svn_tristate_t +{ + /** state known to be false (the constant does not evaulate to false) */ + svn_tristate_false = 2, + /** state known to be true */ + svn_tristate_true, + /** state could be true or false */ + svn_tristate_unknown +} svn_tristate_t; + + +/** A revision number. */ +/* NOTE: Update svnxx/revision.hpp when changing this typedef. */ +typedef long int svn_revnum_t; + +/** The 'official' invalid revision number. */ +/* NOTE: Update svnxx/revision.hpp when changing this definition. */ +#define SVN_INVALID_REVNUM ((svn_revnum_t) -1) + + +/** The concept of depth for directories. + * + * @note This is similar to, but not exactly the same as, the WebDAV + * and LDAP concepts of depth. + * + * @since New in 1.5. + */ +/* NOTE: Update svnxx/depth.hpp when changing this enum. */ +typedef enum svn_depth_t +{ + /* The order of these depths is important: the higher the number, + the deeper it descends. This allows us to compare two depths + numerically to decide which should govern. */ + + /** Depth undetermined or ignored. In some contexts, this means the + client should choose an appropriate default depth. The server + will generally treat it as #svn_depth_infinity. */ + svn_depth_unknown = -2, + + /** Exclude (i.e., don't descend into) directory D. + @note In Subversion 1.5, svn_depth_exclude is *not* supported + anywhere in the client-side (libsvn_wc/libsvn_client/etc) code; + it is only supported as an argument to set_path functions in the + ra and repos reporters. (This will enable future versions of + Subversion to run updates, etc, against 1.5 servers with proper + svn_depth_exclude behavior, once we get a chance to implement + client-side support for svn_depth_exclude.) + */ + svn_depth_exclude = -1, + + /** Just the named directory D, no entries. Updates will not pull in + any files or subdirectories not already present. */ + svn_depth_empty = 0, + + /** D + its file children, but not subdirs. Updates will pull in any + files not already present, but not subdirectories. */ + svn_depth_files = 1, + + /** D + immediate children (D and its entries). Updates will pull in + any files or subdirectories not already present; those + subdirectories' this_dir entries will have depth-empty. */ + svn_depth_immediates = 2, + + /** D + all descendants (full recursion from D). Updates will pull + in any files or subdirectories not already present; those + subdirectories' this_dir entries will have depth-infinity. + Equivalent to the pre-1.5 default update behavior. */ + svn_depth_infinity = 3 + +} svn_depth_t; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_TYPES_IMPL_H */ diff --git a/subversion/include/svn_utf.h b/subversion/include/svn_utf.h index 4a2c137b8e64..c65afc705a1c 100644 --- a/subversion/include/svn_utf.h +++ b/subversion/include/svn_utf.h @@ -212,7 +212,7 @@ svn_utf_cstring_from_utf8_ex(const char **dest, * so when we can detect that at configure time, things will change. * Also, this should (?) be moved to apr/apu eventually. * - * See http://subversion.tigris.org/issues/show_bug.cgi?id=807 for + * See https://issues.apache.org/jira/browse/SVN-807 for * details. */ const char * diff --git a/subversion/include/svn_version.h b/subversion/include/svn_version.h index 7d95271dfa4f..25bbe542df5b 100644 --- a/subversion/include/svn_version.h +++ b/subversion/include/svn_version.h @@ -61,7 +61,7 @@ extern "C" { * Modify when new functionality is added or new interfaces are * defined, but all changes are backward compatible. */ -#define SVN_VER_MINOR 10 +#define SVN_VER_MINOR 14 /** * Patch number. @@ -70,7 +70,7 @@ extern "C" { * * @since New in 1.1. */ -#define SVN_VER_PATCH 2 +#define SVN_VER_PATCH 0 /** @deprecated Provided for backward compatibility with the 1.0 API. */ @@ -93,7 +93,7 @@ extern "C" { * * Always change this at the same time as SVN_VER_NUMTAG. */ -#define SVN_VER_TAG " (r1835932)" +#define SVN_VER_TAG " (r1876290)" /** Number tag: a string describing the version. @@ -117,7 +117,7 @@ extern "C" { * file version. Its value remains 0 in the repository except in release * tags where it is the revision from which the tag was created. */ -#define SVN_VER_REVISION 1835932 +#define SVN_VER_REVISION 1876290 /* Version strings composed from the above definitions. */ diff --git a/subversion/include/svn_wc.h b/subversion/include/svn_wc.h index e632673e0ae4..cd018ae1ee8e 100644 --- a/subversion/include/svn_wc.h +++ b/subversion/include/svn_wc.h @@ -7602,9 +7602,14 @@ svn_wc_relocate(const char *path, * If @a clear_changelists is TRUE, then changelist information for the * paths is cleared. * - * If @a metadata_only is TRUE, the working copy files are untouched, but - * if there are conflict marker files attached to these files these - * markers are removed. + * The @a metadata_only and @a added_keep_local options control the + * extent of reverting. If @a metadata_only is TRUE, the working copy + * files are untouched, but if there are conflict marker files attached + * to these files these markers are removed. Otherwise, if + * @a added_keep_local is TRUE, then all items are reverted except an + * item that was scheduled as plain 'add' (not a copy) will not be + * removed from the working copy. Otherwise, all items are reverted and + * their on-disk state changed to match. * * If @a cancel_func is non-NULL, call it with @a cancel_baton at * various points during the reversion process. If it returns an @@ -7622,8 +7627,30 @@ svn_wc_relocate(const char *path, * If @a path is not under version control, return the error * #SVN_ERR_UNVERSIONED_RESOURCE. * + * @since New in 1.11. + */ +svn_error_t * +svn_wc_revert6(svn_wc_context_t *wc_ctx, + const char *local_abspath, + svn_depth_t depth, + svn_boolean_t use_commit_times, + const apr_array_header_t *changelist_filter, + svn_boolean_t clear_changelists, + svn_boolean_t metadata_only, + svn_boolean_t added_keep_local, + svn_cancel_func_t cancel_func, + void *cancel_baton, + svn_wc_notify_func2_t notify_func, + void *notify_baton, + apr_pool_t *scratch_pool); + +/** Similar to svn_wc_revert6() but with @a added_keep_local always + * set to TRUE. + * * @since New in 1.9. + * @deprecated Provided for backward compatibility with the 1.10 API. */ +SVN_DEPRECATED svn_error_t * svn_wc_revert5(svn_wc_context_t *wc_ctx, const char *local_abspath, diff --git a/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c b/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c index 7926960ca446..a871a9390ed9 100644 --- a/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c +++ b/subversion/libsvn_auth_gnome_keyring/gnome_keyring.c @@ -120,10 +120,10 @@ password_get_gnome_keyring(svn_boolean_t *done, gchar *gpassword; *done = FALSE; - + if (!available_collection(non_interactive, pool)) return SVN_NO_ERROR; - + gpassword = secret_password_lookup_sync(SECRET_SCHEMA_COMPAT_NETWORK, NULL, &gerror, "domain", realmstring, @@ -140,7 +140,7 @@ password_get_gnome_keyring(svn_boolean_t *done, g_free(gpassword); *done = TRUE; } - + return SVN_NO_ERROR; } @@ -158,7 +158,7 @@ password_set_gnome_keyring(svn_boolean_t *done, { GError *gerror = NULL; gboolean gstatus; - + *done = FALSE; if (!available_collection(non_interactive, pool)) @@ -182,7 +182,7 @@ password_set_gnome_keyring(svn_boolean_t *done, { *done = TRUE; } - + return SVN_NO_ERROR; } diff --git a/subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring.pc.in b/subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring.pc.in index a787178f4ea7..114c8bb3d2ed 100644 --- a/subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring.pc.in +++ b/subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring.pc.in @@ -6,7 +6,7 @@ includedir=@includedir@ Name: libsvn_auth_gnome_keyring Description: Subversion GNOME Keyring Library Version: @PACKAGE_VERSION@ -Requires: apr-@SVN_APR_MAJOR_VERSION@ -Requires.private: libsvn_subr @SVN_GNOME_KEYRING_PCLIBS@ -Libs: -L${libdir} -lsvn_auth_gnome_keyring -Cflags: -I${includedir} +Requires: apr-@SVN_APR_MAJOR_VERSION@ +Requires.private: libsvn_subr, @SVN_GNOME_KEYRING_PCLIBS@ +Libs: -L${libdir} -lsvn_auth_gnome_keyring-1 +Cflags: -I${includedir}/subversion-1 diff --git a/subversion/libsvn_auth_kwallet/kwallet.cpp b/subversion/libsvn_auth_kwallet/kwallet.cpp index f59e04778b3c..c64caabe14bd 100644 --- a/subversion/libsvn_auth_kwallet/kwallet.cpp +++ b/subversion/libsvn_auth_kwallet/kwallet.cpp @@ -227,10 +227,10 @@ kwallet_password_get(svn_boolean_t *done, KLocalizedString::setApplicationDomain("subversion"); /* translation domain */ /* componentName appears in KDE GUI prompts */ - KAboutData aboutData(QStringLiteral("subversion"), /* componentName */ + KAboutData aboutData(QString("subversion"), /* componentName */ i18n(get_application_name(parameters, pool)), /* displayName */ - QStringLiteral(SVN_VER_NUMBER)); + QString(SVN_VER_NUMBER)); KAboutData::setApplicationData(aboutData); #else KCmdLineArgs::init(q_argc, q_argv, @@ -309,10 +309,10 @@ kwallet_password_set(svn_boolean_t *done, KLocalizedString::setApplicationDomain("subversion"); /* translation domain */ /* componentName appears in KDE GUI prompts */ - KAboutData aboutData(QStringLiteral("subversion"), /* componentName */ + KAboutData aboutData(QString("subversion"), /* componentName */ i18n(get_application_name(parameters, pool)), /* displayName */ - QStringLiteral(SVN_VER_NUMBER)); + QString(SVN_VER_NUMBER)); KAboutData::setApplicationData(aboutData); #else KCmdLineArgs::init(q_argc, q_argv, diff --git a/subversion/libsvn_auth_kwallet/libsvn_auth_kwallet.pc.in b/subversion/libsvn_auth_kwallet/libsvn_auth_kwallet.pc.in index fa65cfa476b2..8d4d72bea0df 100644 --- a/subversion/libsvn_auth_kwallet/libsvn_auth_kwallet.pc.in +++ b/subversion/libsvn_auth_kwallet/libsvn_auth_kwallet.pc.in @@ -6,7 +6,7 @@ includedir=@includedir@ Name: libsvn_auth_kwallet Description: Subversion KWallet Library Version: @PACKAGE_VERSION@ -Requires: apr-@SVN_APR_MAJOR_VERSION@ -Requires.private: libsvn_subr -Libs: -L${libdir} -lsvn_auth_kwallet @SVN_KWALLET_LIBS@ -Cflags: -I${includedir} +Requires: apr-@SVN_APR_MAJOR_VERSION@ +Requires.private: libsvn_subr +Libs: -L${libdir} -lsvn_auth_kwallet-1 @SVN_KWALLET_LIBS@ +Cflags: -I${includedir}/subversion-1 diff --git a/subversion/libsvn_client/add.c b/subversion/libsvn_client/add.c index ce7891afb27c..3bb548ae8b45 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 b9363e20dd3e..f78b3041f5b2 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 c0a794712f3f..97cd1e297c02 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 4a945c887aa9..df2f5f7c5224 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 1f3d87783436..2be3ecd5ce39 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 0b342e0e0a1e..9a58703c4238 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 b2e3a44d797b..b3f2bacc7c8d 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 cfe6aea05846..000000000000 --- 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 943cdd9a4496..29a3395af2ea 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 dc20b2772286..b8e202609e74 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 ba6e91eb2e7b..f019294f05c0 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 056ee53e8f28..feac90d788bd 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 9e258cdfcbcb..f4972a39e194 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 2fc2dc8ffd53..b335f2a69eca 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 3331647c9587..2aa4c916cd8e 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 000000000000..bfa7ec1039f1 --- /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 7cc7865643a9..4e475d895adf 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 78433c337e58..d52de6d10ac3 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 21341b9b0a7b..9d9a1c300a10 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 l |