aboutsummaryrefslogtreecommitdiffstats
path: root/subversion
diff options
context:
space:
mode:
Diffstat (limited to 'subversion')
-rw-r--r--subversion/include/private/svn_branch.h2
-rw-r--r--subversion/include/private/svn_client_mtcc.h11
-rw-r--r--subversion/include/private/svn_client_private.h234
-rw-r--r--subversion/include/private/svn_client_shelf.h498
-rw-r--r--subversion/include/private/svn_client_shelf2.h467
-rw-r--r--subversion/include/private/svn_dep_compat.h6
-rw-r--r--subversion/include/private/svn_diff_tree.h1
-rw-r--r--subversion/include/private/svn_dirent_uri_private.h53
-rw-r--r--subversion/include/private/svn_element.h2
-rw-r--r--subversion/include/private/svn_fs_fs_private.h97
-rw-r--r--subversion/include/private/svn_repos_private.h47
-rw-r--r--subversion/include/private/svn_sorts_private.h31
-rw-r--r--subversion/include/private/svn_subr_private.h14
-rw-r--r--subversion/include/private/svn_wc_private.h82
-rw-r--r--subversion/include/svn_client.h610
-rw-r--r--subversion/include/svn_config.h4
-rw-r--r--subversion/include/svn_delta.h164
-rw-r--r--subversion/include/svn_diff.h2
-rw-r--r--subversion/include/svn_dirent_uri.h145
-rw-r--r--subversion/include/svn_error_codes.h22
-rw-r--r--subversion/include/svn_fs.h58
-rw-r--r--subversion/include/svn_opt.h172
-rw-r--r--subversion/include/svn_opt_impl.h86
-rw-r--r--subversion/include/svn_props.h19
-rw-r--r--subversion/include/svn_ra.h44
-rw-r--r--subversion/include/svn_ra_svn.h2
-rw-r--r--subversion/include/svn_repos.h76
-rw-r--r--subversion/include/svn_time.h11
-rw-r--r--subversion/include/svn_types.h128
-rw-r--r--subversion/include/svn_types_impl.h157
-rw-r--r--subversion/include/svn_utf.h2
-rw-r--r--subversion/include/svn_version.h8
-rw-r--r--subversion/include/svn_wc.h33
-rw-r--r--subversion/libsvn_auth_gnome_keyring/gnome_keyring.c10
-rw-r--r--subversion/libsvn_auth_gnome_keyring/libsvn_auth_gnome_keyring.pc.in8
-rw-r--r--subversion/libsvn_auth_kwallet/kwallet.cpp8
-rw-r--r--subversion/libsvn_auth_kwallet/libsvn_auth_kwallet.pc.in8
-rw-r--r--subversion/libsvn_client/add.c7
-rw-r--r--subversion/libsvn_client/blame.c28
-rw-r--r--subversion/libsvn_client/client.h103
-rw-r--r--subversion/libsvn_client/commit.c185
-rw-r--r--subversion/libsvn_client/commit_util.c34
-rw-r--r--subversion/libsvn_client/conflicts.c3729
-rw-r--r--subversion/libsvn_client/copy.c753
-rw-r--r--subversion/libsvn_client/copy_foreign.c575
-rw-r--r--subversion/libsvn_client/delete.c7
-rw-r--r--subversion/libsvn_client/deprecated.c156
-rw-r--r--subversion/libsvn_client/diff.c892
-rw-r--r--subversion/libsvn_client/diff_local.c126
-rw-r--r--subversion/libsvn_client/diff_summarize.c20
-rw-r--r--subversion/libsvn_client/export.c33
-rw-r--r--subversion/libsvn_client/info.c4
-rw-r--r--subversion/libsvn_client/layout.c289
-rw-r--r--subversion/libsvn_client/libsvn_client.pc.in8
-rw-r--r--subversion/libsvn_client/list.c2
-rw-r--r--subversion/libsvn_client/merge.c413
-rw-r--r--subversion/libsvn_client/mtcc.c4
-rw-r--r--subversion/libsvn_client/patch.c15
-rw-r--r--subversion/libsvn_client/ra.c28
-rw-r--r--subversion/libsvn_client/repos_diff.c24
-rw-r--r--subversion/libsvn_client/revert.c30
-rw-r--r--subversion/libsvn_client/revisions.c9
-rw-r--r--subversion/libsvn_client/shelf.c1274
-rw-r--r--subversion/libsvn_client/shelf2.c2124
-rw-r--r--subversion/libsvn_client/shelve.c552
-rw-r--r--subversion/libsvn_client/status.c80
-rw-r--r--subversion/libsvn_client/update.c10
-rw-r--r--subversion/libsvn_client/upgrade.c31
-rw-r--r--subversion/libsvn_client/util.c1
-rw-r--r--subversion/libsvn_client/wc_editor.c655
-rw-r--r--subversion/libsvn_delta/branch.c2
-rw-r--r--subversion/libsvn_delta/branch_compat.c54
-rw-r--r--subversion/libsvn_delta/compat.c50
-rw-r--r--subversion/libsvn_delta/debug_editor.c159
-rw-r--r--subversion/libsvn_delta/deprecated.c73
-rw-r--r--subversion/libsvn_delta/element.c4
-rw-r--r--subversion/libsvn_delta/libsvn_delta.pc.in8
-rw-r--r--subversion/libsvn_delta/path_driver.c313
-rw-r--r--subversion/libsvn_diff/diff_file.c2
-rw-r--r--subversion/libsvn_diff/diff_tree.c75
-rw-r--r--subversion/libsvn_diff/libsvn_diff.pc.in8
-rw-r--r--subversion/libsvn_diff/parse-diff.c104
-rw-r--r--subversion/libsvn_fs/fs-loader.c39
-rw-r--r--subversion/libsvn_fs/fs-loader.h17
-rw-r--r--subversion/libsvn_fs/libsvn_fs.pc.in8
-rw-r--r--subversion/libsvn_fs_base/fs.c4
-rw-r--r--subversion/libsvn_fs_base/libsvn_fs_base.pc.in8
-rw-r--r--subversion/libsvn_fs_fs/cached_data.c2
-rw-r--r--subversion/libsvn_fs_fs/cached_data.h2
-rw-r--r--subversion/libsvn_fs_fs/dag.c2
-rw-r--r--subversion/libsvn_fs_fs/dump-index.c2
-rw-r--r--subversion/libsvn_fs_fs/fs.c84
-rw-r--r--subversion/libsvn_fs_fs/fs_fs.c170
-rw-r--r--subversion/libsvn_fs_fs/fs_fs.h71
-rw-r--r--subversion/libsvn_fs_fs/id.c3
-rw-r--r--subversion/libsvn_fs_fs/index.c4
-rw-r--r--subversion/libsvn_fs_fs/libsvn_fs_fs.pc.in8
-rw-r--r--subversion/libsvn_fs_fs/load-index.c6
-rw-r--r--subversion/libsvn_fs_fs/low_level.c8
-rw-r--r--subversion/libsvn_fs_fs/pack.c18
-rw-r--r--subversion/libsvn_fs_fs/recovery.c12
-rw-r--r--subversion/libsvn_fs_fs/rep-cache-db.h4
-rw-r--r--subversion/libsvn_fs_fs/rep-cache-db.sql2
-rw-r--r--subversion/libsvn_fs_fs/rep-cache.c24
-rw-r--r--subversion/libsvn_fs_fs/revprops.c60
-rw-r--r--subversion/libsvn_fs_fs/revprops.h9
-rw-r--r--subversion/libsvn_fs_fs/stats.c97
-rw-r--r--subversion/libsvn_fs_fs/temp_serializer.c4
-rw-r--r--subversion/libsvn_fs_fs/temp_serializer.h6
-rw-r--r--subversion/libsvn_fs_fs/transaction.c2
-rw-r--r--subversion/libsvn_fs_fs/tree.c49
-rw-r--r--subversion/libsvn_fs_fs/verify.c11
-rw-r--r--subversion/libsvn_fs_util/libsvn_fs_util.pc.in8
-rw-r--r--subversion/libsvn_fs_x/cached_data.c2
-rw-r--r--subversion/libsvn_fs_x/changes.c2
-rw-r--r--subversion/libsvn_fs_x/dag_cache.c6
-rw-r--r--subversion/libsvn_fs_x/dag_cache.h2
-rw-r--r--subversion/libsvn_fs_x/fs.c6
-rw-r--r--subversion/libsvn_fs_x/fs_x.c2
-rw-r--r--subversion/libsvn_fs_x/libsvn_fs_x.pc.in8
-rw-r--r--subversion/libsvn_fs_x/low_level.c8
-rw-r--r--subversion/libsvn_fs_x/pack.c12
-rw-r--r--subversion/libsvn_fs_x/rep-cache-db.h2
-rw-r--r--subversion/libsvn_fs_x/revprops.c6
-rw-r--r--subversion/libsvn_fs_x/temp_serializer.c4
-rw-r--r--subversion/libsvn_fs_x/transaction.c4
-rw-r--r--subversion/libsvn_fs_x/tree.c2
-rw-r--r--subversion/libsvn_fs_x/verify.c2
-rw-r--r--subversion/libsvn_ra/compat.c2
-rw-r--r--subversion/libsvn_ra/deprecated.c13
-rw-r--r--subversion/libsvn_ra/libsvn_ra.pc.in8
-rw-r--r--subversion/libsvn_ra/ra_loader.c11
-rw-r--r--subversion/libsvn_ra/ra_loader.h4
-rw-r--r--subversion/libsvn_ra/wrapper_template.h2
-rw-r--r--subversion/libsvn_ra_local/libsvn_ra_local.pc.in8
-rw-r--r--subversion/libsvn_ra_local/ra_plugin.c3
-rw-r--r--subversion/libsvn_ra_serf/commit.c4
-rw-r--r--subversion/libsvn_ra_serf/inherited_props.c2
-rw-r--r--subversion/libsvn_ra_serf/libsvn_ra_serf.pc.in8
-rw-r--r--subversion/libsvn_ra_serf/list.c13
-rw-r--r--subversion/libsvn_ra_serf/lock.c2
-rw-r--r--subversion/libsvn_ra_serf/options.c40
-rw-r--r--subversion/libsvn_ra_serf/ra_serf.h1
-rw-r--r--subversion/libsvn_ra_serf/replay.c4
-rw-r--r--subversion/libsvn_ra_serf/serf.c8
-rw-r--r--subversion/libsvn_ra_serf/update.c2
-rw-r--r--subversion/libsvn_ra_serf/util.c28
-rw-r--r--subversion/libsvn_ra_svn/client.c22
-rw-r--r--subversion/libsvn_ra_svn/editorp.c2
-rw-r--r--subversion/libsvn_ra_svn/libsvn_ra_svn.pc.in8
-rw-r--r--subversion/libsvn_ra_svn/protocol2
-rw-r--r--subversion/libsvn_repos/authz.c55
-rw-r--r--subversion/libsvn_repos/authz.h15
-rw-r--r--subversion/libsvn_repos/authz_info.c53
-rw-r--r--subversion/libsvn_repos/authz_parse.c95
-rw-r--r--subversion/libsvn_repos/commit.c30
-rw-r--r--subversion/libsvn_repos/config_file.c2
-rw-r--r--subversion/libsvn_repos/config_file.h2
-rw-r--r--subversion/libsvn_repos/delta.c14
-rw-r--r--subversion/libsvn_repos/deprecated.c29
-rw-r--r--subversion/libsvn_repos/dump.c90
-rw-r--r--subversion/libsvn_repos/dump_editor.c1040
-rw-r--r--subversion/libsvn_repos/fs-wrap.c4
-rw-r--r--subversion/libsvn_repos/libsvn_repos.pc.in8
-rw-r--r--subversion/libsvn_repos/list.c2
-rw-r--r--subversion/libsvn_repos/load-fs-vtable.c55
-rw-r--r--subversion/libsvn_repos/load.c58
-rw-r--r--subversion/libsvn_repos/log.c6
-rw-r--r--subversion/libsvn_repos/replay.c13
-rw-r--r--subversion/libsvn_repos/repos.c6
-rw-r--r--subversion/libsvn_subr/cmdline.c104
-rw-r--r--subversion/libsvn_subr/compress_lz4.c2
-rw-r--r--subversion/libsvn_subr/config_file.c38
-rw-r--r--subversion/libsvn_subr/config_keys.inc2
-rw-r--r--subversion/libsvn_subr/config_win.c2
-rw-r--r--subversion/libsvn_subr/deprecated.c412
-rw-r--r--subversion/libsvn_subr/dirent_uri.c175
-rw-r--r--subversion/libsvn_subr/error.c2
-rw-r--r--subversion/libsvn_subr/gpg_agent.c2
-rw-r--r--subversion/libsvn_subr/internal_statements.h2
-rw-r--r--subversion/libsvn_subr/io.c442
-rw-r--r--subversion/libsvn_subr/iter.c66
-rw-r--r--subversion/libsvn_subr/libsvn_subr.pc.in8
-rw-r--r--subversion/libsvn_subr/lz4/lz4.c2
-rw-r--r--subversion/libsvn_subr/lz4/lz4internal.h2
-rw-r--r--subversion/libsvn_subr/mergeinfo.c803
-rw-r--r--subversion/libsvn_subr/object_pool.c2
-rw-r--r--subversion/libsvn_subr/opt.c222
-rw-r--r--subversion/libsvn_subr/pool.c12
-rw-r--r--subversion/libsvn_subr/sorts.c65
-rw-r--r--subversion/libsvn_subr/sqlite3wrapper.c8
-rw-r--r--subversion/libsvn_subr/ssl_client_cert_pw_providers.c14
-rw-r--r--subversion/libsvn_subr/stream.c8
-rw-r--r--subversion/libsvn_subr/sysinfo.c289
-rw-r--r--subversion/libsvn_subr/utf.c2
-rw-r--r--subversion/libsvn_subr/utf8proc/utf8proc_data.c15088
-rw-r--r--subversion/libsvn_subr/version.c2
-rw-r--r--subversion/libsvn_subr/win32_crashrpt.c5
-rw-r--r--subversion/libsvn_subr/win32_crypto.c17
-rw-r--r--subversion/libsvn_subr/x509info.c2
-rw-r--r--subversion/libsvn_subr/x509parse.c2
-rw-r--r--subversion/libsvn_wc/README15
-rw-r--r--subversion/libsvn_wc/conflicts.c95
-rw-r--r--subversion/libsvn_wc/conflicts.h7
-rw-r--r--subversion/libsvn_wc/deprecated.c30
-rw-r--r--subversion/libsvn_wc/diff_local.c29
-rw-r--r--subversion/libsvn_wc/entries.c4
-rw-r--r--subversion/libsvn_wc/libsvn_wc.pc.in8
-rw-r--r--subversion/libsvn_wc/node.c28
-rw-r--r--subversion/libsvn_wc/props.c4
-rw-r--r--subversion/libsvn_wc/questions.c2
-rw-r--r--subversion/libsvn_wc/revert.c54
-rw-r--r--subversion/libsvn_wc/tree_conflicts.c2
-rw-r--r--subversion/libsvn_wc/update_editor.c33
-rw-r--r--subversion/libsvn_wc/upgrade.c4
-rw-r--r--subversion/libsvn_wc/wc-checks.h2
-rw-r--r--subversion/libsvn_wc/wc-metadata.h2
-rw-r--r--subversion/libsvn_wc/wc-queries.h1268
-rw-r--r--subversion/libsvn_wc/wc-queries.sql24
-rw-r--r--subversion/libsvn_wc/wc.h1
-rw-r--r--subversion/libsvn_wc/wc_db.c119
-rw-r--r--subversion/libsvn_wc/wc_db.h48
-rw-r--r--subversion/libsvn_wc/wc_db_update_move.c27
-rw-r--r--subversion/libsvn_wc/wc_db_wcroot.c16
-rw-r--r--subversion/libsvn_wc/wcroot_anchor.c16
-rw-r--r--subversion/svn/auth-cmd.c17
-rw-r--r--subversion/svn/blame-cmd.c42
-rw-r--r--subversion/svn/cl.h158
-rw-r--r--subversion/svn/conflict-callbacks.c162
-rw-r--r--subversion/svn/diff-cmd.c48
-rw-r--r--subversion/svn/filesize.c221
-rw-r--r--subversion/svn/help-cmd.c2
-rw-r--r--subversion/svn/info-cmd.c389
-rw-r--r--subversion/svn/list-cmd.c89
-rw-r--r--subversion/svn/log-cmd.c9
-rw-r--r--subversion/svn/merge-cmd.c40
-rw-r--r--subversion/svn/notify.c2
-rw-r--r--subversion/svn/propset-cmd.c2
-rw-r--r--subversion/svn/resolve-cmd.c2
-rw-r--r--subversion/svn/revert-cmd.c3
-rw-r--r--subversion/svn/shelf-cmd.c1405
-rw-r--r--subversion/svn/shelf-cmd.h49
-rw-r--r--subversion/svn/shelf2-cmd.c1369
-rw-r--r--subversion/svn/shelf2-cmd.h49
-rw-r--r--subversion/svn/shelve-cmd.c369
-rw-r--r--subversion/svn/svn.c1163
-rw-r--r--subversion/svn/util.c2
-rw-r--r--subversion/svn_private_config.h.in15
-rw-r--r--subversion/svnadmin/svnadmin.c473
-rw-r--r--subversion/svnbench/cl.h2
-rw-r--r--subversion/svnbench/help-cmd.c2
-rw-r--r--subversion/svnbench/svnbench.c92
-rw-r--r--subversion/svndumpfilter/svndumpfilter.c58
-rw-r--r--subversion/svnfsfs/dump-index-cmd.c7
-rw-r--r--subversion/svnfsfs/load-index-cmd.c8
-rw-r--r--subversion/svnfsfs/stats-cmd.c10
-rw-r--r--subversion/svnfsfs/svnfsfs.c64
-rw-r--r--subversion/svnlook/svnlook.c160
-rw-r--r--subversion/svnmucc/svnmucc.c4
-rw-r--r--subversion/svnrdump/dump_editor.c967
-rw-r--r--subversion/svnrdump/load_editor.c993
-rw-r--r--subversion/svnrdump/svnrdump.c72
-rw-r--r--subversion/svnrdump/svnrdump.h21
-rw-r--r--subversion/svnrdump/util.c4
-rw-r--r--subversion/svnserve/logger.c47
-rw-r--r--subversion/svnserve/logger.h11
-rw-r--r--subversion/svnserve/serve.c325
-rw-r--r--subversion/svnserve/svnserve.c2
-rw-r--r--subversion/svnsync/svnsync.c89
-rw-r--r--subversion/svnversion/svnversion.c2
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(&copy_src_revnum, &timestamp_sleep,
- url, tmp_abspath,
- &copy_src_peg_revision,
- &copy_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(&timestamp_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, &copyfrom_rev,
+ &copyfrom_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, &copyfrom_rev,
- &copyfrom_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,
+ &copy_src_revision,
+ &copy_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 = &timestamp_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(&copyfrom_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(&copyfrom_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(&copyfrom_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 list of any paths which remained in conflict after a
@@ -319,17 +338,10 @@ typedef struct merge_cmd_baton_t {
or do_file_merge() in do_merge(). */
apr_pool_t *pool;
-
- /* State for notify_merge_begin() */
- struct notify_begin_state_t
- {
- /* Cache of which abspath was last notified. */
- const char *last_abspath;
-
- /* Reference to the one-and-only CHILDREN_WITH_MERGEINFO (see global
- comment) or a similar list for single-file-merges */
- const apr_array_header_t *nodes_with_mergeinfo;
- } notify_begin;
+ /* Our notification callback, that adds a 'begin' notification */
+ svn_wc_notify_func2_t notify_func;
+ void *notify_baton;
+ struct notify_begin_state_t notify_begin;
} merge_cmd_baton_t;
@@ -340,17 +352,25 @@ typedef struct merge_cmd_baton_t {
merge source is an ancestor of the right-side (or vice-versa), the merge
source is in the same repository as the merge target, and we are not
ignoring mergeinfo. */
-#define HONOR_MERGEINFO(merge_b) ((merge_b)->mergeinfo_capable \
- && (merge_b)->merge_source.ancestral \
- && (merge_b)->same_repos \
- && (! (merge_b)->ignore_mergeinfo))
+static svn_boolean_t
+HONOR_MERGEINFO(const merge_cmd_baton_t *merge_b)
+{
+ return (merge_b->mergeinfo_capable