summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_client/diff.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/diff.c')
-rw-r--r--subversion/libsvn_client/diff.c2723
1 files changed, 2723 insertions, 0 deletions
diff --git a/subversion/libsvn_client/diff.c b/subversion/libsvn_client/diff.c
new file mode 100644
index 0000000..a5a36bd
--- /dev/null
+++ b/subversion/libsvn_client/diff.c
@@ -0,0 +1,2723 @@
+/*
+ * diff.c: comparing
+ *
+ * ====================================================================
+ * 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 <apr_strings.h>
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include "svn_types.h"
+#include "svn_hash.h"
+#include "svn_wc.h"
+#include "svn_diff.h"
+#include "svn_mergeinfo.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_io.h"
+#include "svn_utf.h"
+#include "svn_pools.h"
+#include "svn_config.h"
+#include "svn_props.h"
+#include "svn_subst.h"
+#include "client.h"
+
+#include "private/svn_wc_private.h"
+#include "private/svn_diff_private.h"
+#include "private/svn_subr_private.h"
+
+#include "svn_private_config.h"
+
+
+/* Utilities */
+
+
+#define MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir) \
+ svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL, \
+ _("Path '%s' must be an immediate child of " \
+ "the directory '%s'"), path, relative_to_dir)
+
+/* Calculate the repository relative path of DIFF_RELPATH, using RA_SESSION
+ * and WC_CTX, and return the result in *REPOS_RELPATH.
+ * ORIG_TARGET is the related original target passed to the diff command,
+ * and may be used to derive leading path components missing from PATH.
+ * ANCHOR is the local path where the diff editor is anchored.
+ * Do all allocations in POOL. */
+static svn_error_t *
+make_repos_relpath(const char **repos_relpath,
+ const char *diff_relpath,
+ const char *orig_target,
+ svn_ra_session_t *ra_session,
+ svn_wc_context_t *wc_ctx,
+ const char *anchor,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *local_abspath;
+ const char *orig_repos_relpath = NULL;
+
+ if (! ra_session
+ || (anchor && !svn_path_is_url(orig_target)))
+ {
+ svn_error_t *err;
+ /* We're doing a WC-WC diff, so we can retrieve all information we
+ * need from the working copy. */
+ SVN_ERR(svn_dirent_get_absolute(&local_abspath,
+ svn_dirent_join(anchor, diff_relpath,
+ scratch_pool),
+ scratch_pool));
+
+ err = svn_wc__node_get_repos_info(NULL, repos_relpath, NULL, NULL,
+ wc_ctx, local_abspath,
+ result_pool, scratch_pool);
+
+ if (!ra_session
+ || ! err
+ || (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND))
+ {
+ return svn_error_trace(err);
+ }
+
+ /* The path represents a local working copy path, but does not
+ exist. Fall through to calculate an in-repository location
+ based on the ra session */
+
+ /* ### Maybe we should use the nearest existing ancestor instead? */
+ svn_error_clear(err);
+ }
+
+ {
+ const char *url;
+ const char *repos_root_url;
+
+ /* Would be nice if the RA layer could just provide the parent
+ repos_relpath of the ra session */
+ SVN_ERR(svn_ra_get_session_url(ra_session, &url, scratch_pool));
+
+ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
+ scratch_pool));
+
+ orig_repos_relpath = svn_uri_skip_ancestor(repos_root_url, url,
+ scratch_pool);
+
+ *repos_relpath = svn_relpath_join(orig_repos_relpath, diff_relpath,
+ result_pool);
+ }
+
+ 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. */
+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 *relative_to_dir,
+ const char *anchor,
+ 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;
+
+ if (anchor)
+ new_path = svn_dirent_join(anchor, new_path, result_pool);
+
+ if (relative_to_dir)
+ {
+ /* Possibly adjust the paths shown in the output (see issue #2723). */
+ const char *child_path = svn_dirent_is_child(relative_to_dir, new_path,
+ result_pool);
+
+ if (child_path)
+ new_path = child_path;
+ else if (! strcmp(relative_to_dir, new_path))
+ new_path = ".";
+ else
+ return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir);
+
+ child_path = svn_dirent_is_child(relative_to_dir, new_path1,
+ result_pool);
+ }
+
+ {
+ apr_size_t len;
+ svn_boolean_t is_url1;
+ svn_boolean_t is_url2;
+ /* ### Holy cow. Due to anchor/target weirdness, we can't
+ simply join diff_cmd_baton->orig_path_1 with path, ditto for
+ orig_path_2. That will work when they're directory URLs, but
+ not for file URLs. Nor can we just use anchor1 and anchor2
+ from do_diff(), at least not without some more logic here.
+ What a nightmare.
+
+ For now, to distinguish the two paths, we'll just put the
+ unique portions of the original targets in parentheses after
+ the received path, with ellipses for handwaving. This makes
+ the labels a bit clumsy, but at least distinctive. Better
+ solutions are possible, they'll just take more thought. */
+
+ /* ### BH: We can now just construct the repos_relpath, etc. as the
+ anchor is available. See also make_repos_relpath() */
+
+ is_url1 = svn_path_is_url(new_path1);
+ is_url2 = svn_path_is_url(new_path2);
+
+ if (is_url1 && is_url2)
+ len = strlen(svn_uri_get_longest_ancestor(new_path1, new_path2,
+ scratch_pool));
+ else if (!is_url1 && !is_url2)
+ len = strlen(svn_dirent_get_longest_ancestor(new_path1, new_path2,
+ scratch_pool));
+ else
+ len = 0; /* Path and URL */
+
+ new_path1 += len;
+ new_path2 += len;
+ }
+
+ /* ### Should diff labels print paths in local style? Is there
+ already a standard for this? In any case, this code depends on
+ a particular style, so not calling svn_dirent_local_style() on the
+ paths below.*/
+
+ if (new_path[0] == '\0')
+ new_path = ".";
+
+ if (new_path1[0] == '\0')
+ new_path1 = new_path;
+ else if (svn_path_is_url(new_path1))
+ new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path1);
+ else if (new_path1[0] == '/')
+ new_path1 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path1);
+ else
+ new_path1 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path1);
+
+ if (new_path2[0] == '\0')
+ new_path2 = new_path;
+ else if (svn_path_is_url(new_path2))
+ new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path2);
+ else if (new_path2[0] == '/')
+ new_path2 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path2);
+ else
+ 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;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Generate a label for the diff output for file PATH at revision REVNUM.
+ If REVNUM is invalid then it is assumed to be the current working
+ copy. Assumes the paths are already in the desired style (local
+ vs internal). Allocate the label in POOL. */
+static const char *
+diff_label(const char *path,
+ svn_revnum_t revnum,
+ apr_pool_t *pool)
+{
+ const char *label;
+ if (revnum != SVN_INVALID_REVNUM)
+ label = apr_psprintf(pool, _("%s\t(revision %ld)"), path, revnum);
+ else
+ label = apr_psprintf(pool, _("%s\t(working copy)"), path);
+
+ return label;
+}
+
+/* Print a git diff header for an addition within a diff between PATH1 and
+ * PATH2 to the stream OS using HEADER_ENCODING.
+ * All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_added(svn_stream_t *os, const char *header_encoding,
+ const char *path1, const char *path2,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "diff --git a/%s b/%s%s",
+ path1, path2, APR_EOL_STR));
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "new file mode 10644" APR_EOL_STR));
+ return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a deletion within a diff between PATH1 and
+ * PATH2 to the stream OS using HEADER_ENCODING.
+ * All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_deleted(svn_stream_t *os, const char *header_encoding,
+ const char *path1, const char *path2,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "diff --git a/%s b/%s%s",
+ path1, path2, APR_EOL_STR));
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "deleted file mode 10644"
+ APR_EOL_STR));
+ return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a copy from COPYFROM_PATH to PATH to the stream
+ * OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_copied(svn_stream_t *os, const char *header_encoding,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ const char *path,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "diff --git a/%s b/%s%s",
+ copyfrom_path, path, APR_EOL_STR));
+ if (copyfrom_rev != SVN_INVALID_REVNUM)
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "copy from %s@%ld%s", copyfrom_path,
+ copyfrom_rev, APR_EOL_STR));
+ else
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "copy from %s%s", copyfrom_path,
+ APR_EOL_STR));
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "copy to %s%s", path, APR_EOL_STR));
+ return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a rename from COPYFROM_PATH to PATH to the
+ * stream OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_renamed(svn_stream_t *os, const char *header_encoding,
+ const char *copyfrom_path, const char *path,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "diff --git a/%s b/%s%s",
+ copyfrom_path, path, APR_EOL_STR));
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "rename from %s%s", copyfrom_path,
+ APR_EOL_STR));
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "rename to %s%s", path, APR_EOL_STR));
+ return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a modification within a diff between PATH1 and
+ * PATH2 to the stream OS using HEADER_ENCODING.
+ * All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding,
+ const char *path1, const char *path2,
+ apr_pool_t *result_pool)
+{
+ SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+ "diff --git a/%s b/%s%s",
+ path1, path2, APR_EOL_STR));
+ return SVN_NO_ERROR;
+}
+
+/* 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
+ * 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 *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ const char *header_encoding,
+ apr_pool_t *scratch_pool)
+{
+ if (operation == svn_diff_op_deleted)
+ {
+ SVN_ERR(print_git_diff_header_deleted(os, header_encoding,
+ repos_relpath1, repos_relpath2,
+ scratch_pool));
+ *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
+ rev1, scratch_pool);
+ *label2 = diff_label("/dev/null", rev2, scratch_pool);
+
+ }
+ else if (operation == svn_diff_op_copied)
+ {
+ SVN_ERR(print_git_diff_header_copied(os, header_encoding,
+ copyfrom_path, copyfrom_rev,
+ repos_relpath2,
+ scratch_pool));
+ *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
+ rev1, scratch_pool);
+ *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+ rev2, scratch_pool);
+ }
+ else if (operation == svn_diff_op_added)
+ {
+ SVN_ERR(print_git_diff_header_added(os, header_encoding,
+ repos_relpath1, repos_relpath2,
+ scratch_pool));
+ *label1 = diff_label("/dev/null", rev1, scratch_pool);
+ *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+ rev2, scratch_pool);
+ }
+ else if (operation == svn_diff_op_modified)
+ {
+ SVN_ERR(print_git_diff_header_modified(os, header_encoding,
+ repos_relpath1, repos_relpath2,
+ scratch_pool));
+ *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
+ rev1, scratch_pool);
+ *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+ rev2, scratch_pool);
+ }
+ else if (operation == svn_diff_op_moved)
+ {
+ SVN_ERR(print_git_diff_header_renamed(os, header_encoding,
+ copyfrom_path, repos_relpath2,
+ scratch_pool));
+ *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
+ rev1, scratch_pool);
+ *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+ rev2, scratch_pool);
+ }
+
+ 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.
+
+ ### 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. */
+static svn_error_t *
+display_prop_diffs(const apr_array_header_t *propchanges,
+ apr_hash_t *original_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,
+ svn_stream_t *outstream,
+ const char *relative_to_dir,
+ svn_boolean_t show_diff_header,
+ svn_boolean_t use_git_diff_format,
+ svn_ra_session_t *ra_session,
+ 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;
+
+ if (use_git_diff_format)
+ {
+ SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, orig_path1,
+ ra_session, wc_ctx, anchor,
+ scratch_pool, scratch_pool));
+ SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, orig_path2,
+ ra_session, wc_ctx, 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,
+ scratch_pool, scratch_pool));
+
+ if (show_diff_header)
+ {
+ 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! */
+
+ 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));
+
+ 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,
+ encoding, scratch_pool));
+
+ /* --- label1
+ * +++ label2 */
+ SVN_ERR(svn_diff__unidiff_write_header(
+ outstream, encoding, label1, label2, scratch_pool));
+ }
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
+ _("%sProperty changes on: %s%s"),
+ APR_EOL_STR,
+ use_git_diff_format
+ ? repos_relpath1
+ : index_path,
+ APR_EOL_STR));
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
+ SVN_DIFF__UNDER_STRING APR_EOL_STR));
+
+ SVN_ERR(svn_diff__display_prop_diffs(
+ outstream, encoding, propchanges, original_props,
+ TRUE /* pretty_print_mergeinfo */, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/*-----------------------------------------------------------------*/
+
+/*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
+
+
+struct diff_cmd_baton {
+
+ /* If non-null, the external diff command to invoke. */
+ const char *diff_cmd;
+
+ /* This is allocated in this struct's pool or a higher-up pool. */
+ union {
+ /* If 'diff_cmd' is null, then this is the parsed options to
+ pass to the internal libsvn_diff implementation. */
+ svn_diff_file_options_t *for_internal;
+ /* Else if 'diff_cmd' is non-null, then... */
+ struct {
+ /* ...this is an argument array for the external command, and */
+ const char **argv;
+ /* ...this is the length of argv. */
+ int argc;
+ } for_external;
+ } options;
+
+ apr_pool_t *pool;
+ svn_stream_t *outstream;
+ svn_stream_t *errstream;
+
+ const char *header_encoding;
+
+ /* 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;
+
+ /* These are the numeric representations of the revisions passed to
+ svn_client_diff6(), either may be SVN_INVALID_REVNUM. We need these
+ because some of the svn_wc_diff_callbacks4_t don't get revision
+ arguments.
+
+ ### Perhaps we should change the callback signatures and eliminate
+ ### these?
+ */
+ svn_revnum_t revnum1;
+ svn_revnum_t revnum2;
+
+ /* Set this if you want diff output even for binary files. */
+ svn_boolean_t force_binary;
+
+ /* The directory that diff target paths should be considered as
+ relative to for output generation (see issue #2723). */
+ const char *relative_to_dir;
+
+ /* Whether property differences are ignored. */
+ svn_boolean_t ignore_properties;
+
+ /* Whether to show only property changes. */
+ svn_boolean_t properties_only;
+
+ /* Whether we're producing a git-style diff. */
+ svn_boolean_t use_git_diff_format;
+
+ /* Whether addition of a file is summarized versus showing a full diff. */
+ svn_boolean_t no_diff_added;
+
+ /* Whether deletion of a file is summarized versus showing a full diff. */
+ svn_boolean_t no_diff_deleted;
+
+ /* Whether to ignore copyfrom information when showing adds */
+ svn_boolean_t no_copyfrom_on_add;
+
+ /* Empty files for creating diffs or NULL if not used yet */
+ const char *empty_file;
+
+ svn_wc_context_t *wc_ctx;
+
+ /* The RA session used during diffs involving the repository. */
+ svn_ra_session_t *ra_session;
+
+ /* The anchor to prefix before wc paths */
+ const char *anchor;
+
+ /* Whether the local diff target of a repos->wc diff is a copy. */
+ svn_boolean_t repos_wc_diff_target_is_copy;
+};
+
+/* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added
+ */
+static svn_error_t *
+diff_props_changed(const char *diff_relpath,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ svn_boolean_t dir_was_added,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ svn_boolean_t show_diff_header,
+ struct diff_cmd_baton *diff_cmd_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *props;
+
+ /* If property differences are ignored, there's nothing to do. */
+ if (diff_cmd_baton->ignore_properties)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props,
+ scratch_pool));
+
+ if (props->nelts > 0)
+ {
+ /* We're using the revnums from the diff_cmd_baton since there's
+ * no revision argument to the svn_wc_diff_callback_t
+ * dir_props_changed(). */
+ SVN_ERR(display_prop_diffs(props, original_props,
+ diff_relpath,
+ diff_cmd_baton->anchor,
+ diff_cmd_baton->orig_path_1,
+ diff_cmd_baton->orig_path_2,
+ rev1,
+ rev2,
+ diff_cmd_baton->header_encoding,
+ diff_cmd_baton->outstream,
+ diff_cmd_baton->relative_to_dir,
+ show_diff_header,
+ diff_cmd_baton->use_git_diff_format,
+ diff_cmd_baton->ra_session,
+ diff_cmd_baton->wc_ctx,
+ scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_dir_props_changed(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ svn_boolean_t dir_was_added,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_cmd_baton *diff_cmd_baton = diff_baton;
+
+ return svn_error_trace(diff_props_changed(diff_relpath,
+ /* ### These revs be filled
+ * ### with per node info */
+ dir_was_added
+ ? 0 /* Magic legacy value */
+ : diff_cmd_baton->revnum1,
+ diff_cmd_baton->revnum2,
+ dir_was_added,
+ propchanges,
+ original_props,
+ TRUE /* show_diff_header */,
+ diff_cmd_baton,
+ scratch_pool));
+}
+
+
+/* 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.
+
+ If FORCE_DIFF is TRUE, always write a diff, even for empty diffs.
+
+ Set *WROTE_HEADER to TRUE if a diff header was written */
+static svn_error_t *
+diff_content_changed(svn_boolean_t *wrote_header,
+ const char *diff_relpath,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ svn_diff_operation_kind_t operation,
+ svn_boolean_t force_diff,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ struct diff_cmd_baton *diff_cmd_baton,
+ apr_pool_t *scratch_pool)
+{
+ int exitcode;
+ const char *rel_to_dir = diff_cmd_baton->relative_to_dir;
+ svn_stream_t *errstream = diff_cmd_baton->errstream;
+ svn_stream_t *outstream = diff_cmd_baton->outstream;
+ const char *label1, *label2;
+ svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
+ const char *index_path = diff_relpath;
+ const char *path1 = diff_cmd_baton->orig_path_1;
+ const char *path2 = diff_cmd_baton->orig_path_2;
+
+ /* If only property differences are shown, there's nothing to do. */
+ if (diff_cmd_baton->properties_only)
+ return SVN_NO_ERROR;
+
+ /* Generate the diff headers. */
+ SVN_ERR(adjust_paths_for_diff_labels(&index_path, &path1, &path2,
+ rel_to_dir, diff_cmd_baton->anchor,
+ scratch_pool, scratch_pool));
+
+ label1 = diff_label(path1, rev1, scratch_pool);
+ label2 = diff_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.
+ Print a warning and exit. */
+ if (mimetype1)
+ mt1_binary = svn_mime_type_is_binary(mimetype1);
+ if (mimetype2)
+ mt2_binary = svn_mime_type_is_binary(mimetype2);
+
+ if (! diff_cmd_baton->force_binary && (mt1_binary || mt2_binary))
+ {
+ /* Print out the diff header. */
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "Index: %s" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+
+ /* ### Print git diff headers. */
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ _("Cannot display: file marked as a binary type.%s"),
+ APR_EOL_STR));
+
+ if (mt1_binary && !mt2_binary)
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "svn:mime-type = %s" APR_EOL_STR, mimetype1));
+ else if (mt2_binary && !mt1_binary)
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "svn:mime-type = %s" APR_EOL_STR, mimetype2));
+ else if (mt1_binary && mt2_binary)
+ {
+ if (strcmp(mimetype1, mimetype2) == 0)
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "svn:mime-type = %s" APR_EOL_STR,
+ mimetype1));
+ else
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "svn:mime-type = (%s, %s)" APR_EOL_STR,
+ mimetype1, mimetype2));
+ }
+
+ /* Exit early. */
+ return SVN_NO_ERROR;
+ }
+
+
+ if (diff_cmd_baton->diff_cmd)
+ {
+ apr_file_t *outfile;
+ apr_file_t *errfile;
+ const char *outfilename;
+ const char *errfilename;
+ svn_stream_t *stream;
+
+ /* Print out the diff header. */
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "Index: %s" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+
+ /* ### 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
+ * ### is up to the external diff application. We may be dealing with
+ * ### a non-git compatible diff application.*/
+
+ /* We deal in streams, but svn_io_run_diff2() deals in file handles,
+ unfortunately, so we need to make these temporary files, and then
+ copy the contents to our stream. */
+ SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_io_run_diff2(".",
+ diff_cmd_baton->options.for_external.argv,
+ diff_cmd_baton->options.for_external.argc,
+ label1, label2,
+ tmpfile1, tmpfile2,
+ &exitcode, outfile, errfile,
+ diff_cmd_baton->diff_cmd, scratch_pool));
+
+ SVN_ERR(svn_io_file_close(outfile, scratch_pool));
+ SVN_ERR(svn_io_file_close(errfile, scratch_pool));
+
+ /* Now, open and copy our files to our output streams. */
+ SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(outstream,
+ scratch_pool),
+ NULL, NULL, scratch_pool));
+ SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(errstream,
+ scratch_pool),
+ NULL, NULL, scratch_pool));
+
+ /* We have a printed a diff for this path, mark it as visited. */
+ *wrote_header = TRUE;
+ }
+ else /* use libsvn_diff to generate the diff */
+ {
+ svn_diff_t *diff;
+
+ SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2,
+ diff_cmd_baton->options.for_internal,
+ scratch_pool));
+
+ if (force_diff
+ || diff_cmd_baton->use_git_diff_format
+ || svn_diff_contains_diffs(diff))
+ {
+ /* Print out the diff header. */
+ SVN_ERR(svn_stream_printf_from_utf8(outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "Index: %s" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+
+ if (diff_cmd_baton->use_git_diff_format)
+ {
+ const char *repos_relpath1;
+ const char *repos_relpath2;
+ SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
+ diff_cmd_baton->orig_path_1,
+ diff_cmd_baton->ra_session,
+ diff_cmd_baton->wc_ctx,
+ diff_cmd_baton->anchor,
+ scratch_pool, scratch_pool));
+ SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
+ diff_cmd_baton->orig_path_2,
+ diff_cmd_baton->ra_session,
+ diff_cmd_baton->wc_ctx,
+ diff_cmd_baton->anchor,
+ scratch_pool, scratch_pool));
+ SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
+ operation,
+ repos_relpath1, repos_relpath2,
+ rev1, rev2,
+ copyfrom_path,
+ copyfrom_rev,
+ diff_cmd_baton->header_encoding,
+ scratch_pool));
+ }
+
+ /* Output the actual diff */
+ if (force_diff || svn_diff_contains_diffs(diff))
+ SVN_ERR(svn_diff_file_output_unified3(outstream, diff,
+ tmpfile1, tmpfile2, label1, label2,
+ diff_cmd_baton->header_encoding, rel_to_dir,
+ diff_cmd_baton->options.for_internal->show_c_function,
+ scratch_pool));
+
+ /* We have a printed a diff for this path, mark it as visited. */
+ *wrote_header = TRUE;
+ }
+ }
+
+ /* ### todo: someday we'll need to worry about whether we're going
+ to need to write a diff plug-in mechanism that makes use of the
+ two paths, instead of just blindly running SVN_CLIENT_DIFF. */
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+diff_file_opened(svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ const char *diff_relpath,
+ svn_revnum_t rev,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_file_changed(svn_wc_notify_state_t *content_state,
+ svn_wc_notify_state_t *prop_state,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const apr_array_header_t *prop_changes,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_cmd_baton *diff_cmd_baton = diff_baton;
+ svn_boolean_t wrote_header = FALSE;
+
+ /* During repos->wc diff of a copy revision numbers obtained
+ * from the working copy are always SVN_INVALID_REVNUM. */
+ if (diff_cmd_baton->repos_wc_diff_target_is_copy)
+ {
+ if (rev1 == SVN_INVALID_REVNUM &&
+ diff_cmd_baton->revnum1 != SVN_INVALID_REVNUM)
+ rev1 = diff_cmd_baton->revnum1;
+
+ if (rev2 == SVN_INVALID_REVNUM &&
+ diff_cmd_baton->revnum2 != SVN_INVALID_REVNUM)
+ rev2 = diff_cmd_baton->revnum2;
+ }
+
+ if (tmpfile1)
+ SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
+ tmpfile1, tmpfile2, rev1, rev2,
+ mimetype1, mimetype2,
+ svn_diff_op_modified, FALSE,
+ NULL,
+ SVN_INVALID_REVNUM, diff_cmd_baton,
+ scratch_pool));
+ if (prop_changes->nelts > 0)
+ SVN_ERR(diff_props_changed(diff_relpath, rev1, rev2, FALSE, prop_changes,
+ original_props, !wrote_header,
+ diff_cmd_baton, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+/* Because the repos-diff editor passes at least one empty file to
+ each of these next two functions, they can be dumb wrappers around
+ the main workhorse routine. */
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_file_added(svn_wc_notify_state_t *content_state,
+ svn_wc_notify_state_t *prop_state,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ svn_revnum_t rev1,
+ svn_revnum_t rev2,
+ const char *mimetype1,
+ const char *mimetype2,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ const apr_array_header_t *prop_changes,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_cmd_baton *diff_cmd_baton = diff_baton;
+ svn_boolean_t wrote_header = FALSE;
+
+ /* During repos->wc diff of a copy revision numbers obtained
+ * from the working copy are always SVN_INVALID_REVNUM. */
+ if (diff_cmd_baton->repos_wc_diff_target_is_copy)
+ {
+ if (rev1 == SVN_INVALID_REVNUM &&
+ diff_cmd_baton->revnum1 != SVN_INVALID_REVNUM)
+ rev1 = diff_cmd_baton->revnum1;
+
+ if (rev2 == SVN_INVALID_REVNUM &&
+ diff_cmd_baton->revnum2 != SVN_INVALID_REVNUM)
+ rev2 = diff_cmd_baton->revnum2;
+ }
+
+ if (diff_cmd_baton->no_copyfrom_on_add
+ && (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_revision)))
+ {
+ apr_hash_t *empty_hash = apr_hash_make(scratch_pool);
+ apr_array_header_t *new_changes;
+
+ /* Rebase changes on having no left source. */
+ if (!diff_cmd_baton->empty_file)
+ SVN_ERR(svn_io_open_unique_file3(NULL, &diff_cmd_baton->empty_file,
+ NULL, svn_io_file_del_on_pool_cleanup,
+ diff_cmd_baton->pool, scratch_pool));
+
+ SVN_ERR(svn_prop_diffs(&new_changes,
+ svn_prop__patch(original_props, prop_changes,
+ scratch_pool),
+ empty_hash,
+ scratch_pool));
+
+ tmpfile1 = diff_cmd_baton->empty_file;
+ prop_changes = new_changes;
+ original_props = empty_hash;
+ copyfrom_revision = SVN_INVALID_REVNUM;
+ }
+
+ if (diff_cmd_baton->no_diff_added)
+ {
+ const char *index_path = diff_relpath;
+
+ if (diff_cmd_baton->anchor)
+ index_path = svn_dirent_join(diff_cmd_baton->anchor, diff_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_stream_printf_from_utf8(diff_cmd_baton->outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "Index: %s (added)" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+ wrote_header = TRUE;
+ }
+ else if (tmpfile1 && copyfrom_path)
+ SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
+ tmpfile1, tmpfile2, rev1, rev2,
+ mimetype1, mimetype2,
+ svn_diff_op_copied,
+ TRUE /* force diff output */,
+ copyfrom_path,
+ copyfrom_revision, diff_cmd_baton,
+ scratch_pool));
+ else if (tmpfile1)
+ SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
+ tmpfile1, tmpfile2, rev1, rev2,
+ mimetype1, mimetype2,
+ svn_diff_op_added,
+ TRUE /* force diff output */,
+ NULL, SVN_INVALID_REVNUM,
+ diff_cmd_baton, scratch_pool));
+
+ if (prop_changes->nelts > 0)
+ SVN_ERR(diff_props_changed(diff_relpath, rev1, rev2,
+ FALSE, prop_changes,
+ original_props, ! wrote_header,
+ diff_cmd_baton, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_file_deleted(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ const char *tmpfile1,
+ const char *tmpfile2,
+ const char *mimetype1,
+ const char *mimetype2,
+ apr_hash_t *original_props,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct diff_cmd_baton *diff_cmd_baton = diff_baton;
+
+ if (diff_cmd_baton->no_diff_deleted)
+ {
+ const char *index_path = diff_relpath;
+
+ if (diff_cmd_baton->anchor)
+ index_path = svn_dirent_join(diff_cmd_baton->anchor, diff_relpath,
+ scratch_pool);
+
+ SVN_ERR(svn_stream_printf_from_utf8(diff_cmd_baton->outstream,
+ diff_cmd_baton->header_encoding, scratch_pool,
+ "Index: %s (deleted)" APR_EOL_STR
+ SVN_DIFF__EQUAL_STRING APR_EOL_STR,
+ index_path));
+ }
+ else
+ {
+ svn_boolean_t wrote_header = FALSE;
+ if (tmpfile1)
+ SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
+ tmpfile1, tmpfile2,
+ diff_cmd_baton->revnum1,
+ diff_cmd_baton->revnum2,
+ mimetype1, mimetype2,
+ svn_diff_op_deleted, FALSE,
+ NULL, SVN_INVALID_REVNUM,
+ diff_cmd_baton,
+ scratch_pool));
+
+ /* Should we also report the properties as deleted? */
+ }
+
+ /* We don't list all the deleted properties. */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_dir_added(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *diff_relpath,
+ svn_revnum_t rev,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* Do nothing. */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_dir_deleted(svn_wc_notify_state_t *state,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* Do nothing. */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_dir_opened(svn_boolean_t *tree_conflicted,
+ svn_boolean_t *skip,
+ svn_boolean_t *skip_children,
+ const char *diff_relpath,
+ svn_revnum_t rev,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* Do nothing. */
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_wc_diff_callbacks4_t function. */
+static svn_error_t *
+diff_dir_closed(svn_wc_notify_state_t *contentstate,
+ svn_wc_notify_state_t *propstate,
+ svn_boolean_t *tree_conflicted,
+ const char *diff_relpath,
+ svn_boolean_t dir_was_added,
+ void *diff_baton,
+ apr_pool_t *scratch_pool)
+{
+ /* Do nothing. */
+
+ return SVN_NO_ERROR;
+}
+
+static const svn_wc_diff_callbacks4_t diff_callbacks =
+{
+ diff_file_opened,
+ diff_file_changed,
+ diff_file_added,
+ diff_file_deleted,
+ diff_dir_deleted,
+ diff_dir_opened,
+ diff_dir_added,
+ diff_dir_props_changed,
+ diff_dir_closed
+};
+
+/*-----------------------------------------------------------------*/
+
+/** The logic behind 'svn diff' and 'svn merge'. */
+
+
+/* Hi! This is a comment left behind by Karl, and Ben is too afraid
+ to erase it at this time, because he's not fully confident that all
+ this knowledge has been grokked yet.
+
+ There are five cases:
+ 1. path is not a URL and start_revision != end_revision
+ 2. path is not a URL and start_revision == end_revision
+ 3. path is a URL and start_revision != end_revision
+ 4. path is a URL and start_revision == end_revision
+ 5. path is not a URL and no revisions given
+
+ With only one distinct revision the working copy provides the
+ other. When path is a URL there is no working copy. Thus
+
+ 1: compare repository versions for URL coresponding to working copy
+ 2: compare working copy against repository version
+ 3: compare repository versions for URL
+ 4: nothing to do.
+ 5: compare working copy against text-base
+
+ Case 4 is not as stupid as it looks, for example it may occur if
+ the user specifies two dates that resolve to the same revision. */
+
+
+/** Check if paths PATH_OR_URL1 and PATH_OR_URL2 are urls and if the
+ * revisions REVISION1 and REVISION2 are local. If PEG_REVISION is not
+ * unspecified, ensure that at least one of the two revisions is not
+ * BASE or WORKING.
+ * If PATH_OR_URL1 can only be found in the repository, set *IS_REPOS1
+ * to TRUE. If PATH_OR_URL2 can only be found in the repository, set
+ * *IS_REPOS2 to TRUE. */
+static svn_error_t *
+check_paths(svn_boolean_t *is_repos1,
+ svn_boolean_t *is_repos2,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision)
+{
+ svn_boolean_t is_local_rev1, is_local_rev2;
+
+ /* Verify our revision arguments in light of the paths. */
+ if ((revision1->kind == svn_opt_revision_unspecified)
+ || (revision2->kind == svn_opt_revision_unspecified))
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("Not all required revisions are specified"));
+
+ /* Revisions can be said to be local or remote.
+ * BASE and WORKING are local revisions. */
+ is_local_rev1 =
+ ((revision1->kind == svn_opt_revision_base)
+ || (revision1->kind == svn_opt_revision_working));
+ is_local_rev2 =
+ ((revision2->kind == svn_opt_revision_base)
+ || (revision2->kind == svn_opt_revision_working));
+
+ if (peg_revision->kind != svn_opt_revision_unspecified &&
+ is_local_rev1 && is_local_rev2)
+ return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
+ _("At least one revision must be something other "
+ "than BASE or WORKING when diffing a URL"));
+
+ /* Working copy paths with non-local revisions get turned into
+ URLs. We don't do that here, though. We simply record that it
+ needs to be done, which is information that helps us choose our
+ diff helper function. */
+ *is_repos1 = ! is_local_rev1 || svn_path_is_url(path_or_url1);
+ *is_repos2 = ! is_local_rev2 || svn_path_is_url(path_or_url2);
+
+ return SVN_NO_ERROR;
+}
+
+/* Raise an error if the diff target URL does not exist at REVISION.
+ * If REVISION does not equal OTHER_REVISION, mention both revisions in
+ * the error message. Use RA_SESSION to contact the repository.
+ * Use POOL for temporary allocations. */
+static svn_error_t *
+check_diff_target_exists(const char *url,
+ svn_revnum_t revision,
+ svn_revnum_t other_revision,
+ svn_ra_session_t *ra_session,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+ const char *session_url;
+
+ SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
+
+ if (strcmp(url, session_url) != 0)
+ SVN_ERR(svn_ra_reparent(ra_session, url, pool));
+
+ SVN_ERR(svn_ra_check_path(ra_session, "", revision, &kind, pool));
+ if (kind == svn_node_none)
+ {
+ if (revision == other_revision)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Diff target '%s' was not found in the "
+ "repository at revision '%ld'"),
+ url, revision);
+ else
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Diff target '%s' was not found in the "
+ "repository at revision '%ld' or '%ld'"),
+ url, revision, other_revision);
+ }
+
+ if (strcmp(url, session_url) != 0)
+ SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Return in *RESOLVED_URL the URL which PATH_OR_URL@PEG_REVISION has in
+ * REVISION. If the object has no location in REVISION, set *RESOLVED_URL
+ * to NULL. */
+static svn_error_t *
+resolve_pegged_diff_target_url(const char **resolved_url,
+ svn_ra_session_t *ra_session,
+ const char *path_or_url,
+ const svn_opt_revision_t *peg_revision,
+ const svn_opt_revision_t *revision,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_error_t *err;
+
+ /* Check if the PATH_OR_URL exists at REVISION. */
+ err = svn_client__repos_locations(resolved_url, NULL,
+ NULL, NULL,
+ ra_session,
+ path_or_url,
+ peg_revision,
+ revision,
+ NULL,
+ ctx, scratch_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES ||
+ err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ *resolved_url = NULL;
+ }
+ else
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/** 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
+ * that at least one of the diff targets exists.
+ * Set *BASE_PATH corresponding to the URL opened in the new *RA_SESSION
+ * which is pointing at *ANCHOR1.
+ * Use client context CTX. Do all allocations in POOL. */
+static svn_error_t *
+diff_prepare_repos_repos(const char **url1,
+ const char **url2,
+ const char **base_path,
+ svn_revnum_t *rev1,
+ svn_revnum_t *rev2,
+ const char **anchor1,
+ const char **anchor2,
+ const char **target1,
+ const char **target2,
+ svn_node_kind_t *kind1,
+ svn_node_kind_t *kind2,
+ svn_ra_session_t **ra_session,
+ svn_client_ctx_t *ctx,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ apr_pool_t *pool)
+{
+ const char *abspath_or_url2;
+ const char *abspath_or_url1;
+ const char *repos_root_url;
+ const char *wri_abspath = NULL;
+
+ if (!svn_path_is_url(path_or_url2))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&abspath_or_url2, path_or_url2, pool));
+ SVN_ERR(svn_wc__node_get_url(url2, ctx->wc_ctx, abspath_or_url2,
+ pool, pool));
+ wri_abspath = abspath_or_url2;
+ }
+ else
+ *url2 = abspath_or_url2 = apr_pstrdup(pool, path_or_url2);
+
+ if (!svn_path_is_url(path_or_url1))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1, pool));
+ SVN_ERR(svn_wc__node_get_url(url1, ctx->wc_ctx, abspath_or_url1,
+ pool, pool));
+ wri_abspath = abspath_or_url1;
+ }
+ else
+ *url1 = abspath_or_url1 = apr_pstrdup(pool, path_or_url1);
+
+ /* We need exactly one BASE_PATH, so we'll let the BASE_PATH
+ calculated for PATH_OR_URL2 override the one for PATH_OR_URL1
+ (since the diff will be "applied" to URL2 anyway). */
+ *base_path = NULL;
+ if (strcmp(*url1, path_or_url1) != 0)
+ *base_path = path_or_url1;
+ if (strcmp(*url2, path_or_url2) != 0)
+ *base_path = path_or_url2;
+
+ SVN_ERR(svn_client_open_ra_session2(ra_session, *url2, wri_abspath,
+ ctx, pool, pool));
+
+ /* If we are performing a pegged diff, we need to find out what our
+ actual URLs will be. */
+ if (peg_revision->kind != svn_opt_revision_unspecified)
+ {
+ const char *resolved_url1;
+ const char *resolved_url2;
+
+ SVN_ERR(resolve_pegged_diff_target_url(&resolved_url2, *ra_session,
+ path_or_url2, peg_revision,
+ revision2, ctx, pool));
+
+ SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
+ SVN_ERR(resolve_pegged_diff_target_url(&resolved_url1, *ra_session,
+ path_or_url1, peg_revision,
+ revision1, ctx, pool));
+
+ /* Either or both URLs might have changed as a result of resolving
+ * the PATH_OR_URL@PEG_REVISION's history. If only one of the URLs
+ * could be resolved, use the same URL for URL1 and URL2, so we can
+ * show a diff that adds or removes the object (see issue #4153). */
+ if (resolved_url2)
+ {
+ *url2 = resolved_url2;
+ if (!resolved_url1)
+ *url1 = resolved_url2;
+ }
+ if (resolved_url1)
+ {
+ *url1 = resolved_url1;
+ if (!resolved_url2)
+ *url2 = resolved_url1;
+ }
+
+ /* Reparent the session, since *URL2 might have changed as a result
+ the above call. */
+ SVN_ERR(svn_ra_reparent(*ra_session, *url2, pool));
+ }
+
+ /* Resolve revision and get path kind for the second target. */
+ SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx,
+ (path_or_url2 == *url2) ? NULL : abspath_or_url2,
+ *ra_session, revision2, pool));
+ SVN_ERR(svn_ra_check_path(*ra_session, "", *rev2, kind2, pool));
+
+ /* Do the same for the first target. */
+ SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
+ SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx,
+ (strcmp(path_or_url1, *url1) == 0) ? NULL : abspath_or_url1,
+ *ra_session, revision1, pool));
+ SVN_ERR(svn_ra_check_path(*ra_session, "", *rev1, kind1, pool));
+
+ /* Either both URLs must exist at their respective revisions,
+ * or one of them may be missing from one side of the diff. */
+ if (*kind1 == svn_node_none && *kind2 == svn_node_none)
+ {
+ if (strcmp(*url1, *url2) == 0)
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Diff target '%s' was not found in the "
+ "repository at revisions '%ld' and '%ld'"),
+ *url1, *rev1, *rev2);
+ else
+ return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Diff targets '%s' and '%s' were not found "
+ "in the repository at revisions '%ld' and "
+ "'%ld'"),
+ *url1, *url2, *rev1, *rev2);
+ }
+ else if (*kind1 == svn_node_none)
+ SVN_ERR(check_diff_target_exists(*url1, *rev2, *rev1, *ra_session, pool));
+ else if (*kind2 == svn_node_none)
+ SVN_ERR(check_diff_target_exists(*url2, *rev1, *rev2, *ra_session, pool));
+
+ SVN_ERR(svn_ra_get_repos_root2(*ra_session, &repos_root_url, pool));
+
+ /* Choose useful anchors and targets for our two URLs. */
+ *anchor1 = *url1;
+ *anchor2 = *url2;
+ *target1 = "";
+ *target2 = "";
+
+ /* If none of the targets is the repository root open the parent directory
+ to allow describing replacement of the target itself */
+ if (strcmp(*url1, repos_root_url) != 0
+ && strcmp(*url2, repos_root_url) != 0)
+ {
+ svn_uri_split(anchor1, target1, *url1, pool);
+ svn_uri_split(anchor2, target2, *url2, pool);
+ if (*base_path
+ && (*kind1 == svn_node_file || *kind2 == svn_node_file))
+ *base_path = svn_dirent_dirname(*base_path, pool);
+ SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* 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
+ 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.
+
+ Now, the *reality* is that we have exactly three 'tools' for doing
+ diffing, and thus this routine is built around the use of the three
+ tools. Here they are, for clarity:
+
+ - svn_wc_diff: assumes both paths are the same wcpath.
+ compares wcpath@BASE vs. wcpath@WORKING
+
+ - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING
+
+ - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2
+
+ Since Subversion 1.8 we also have a variant of svn_wc_diff called
+ svn_client__arbitrary_nodes_diff, that allows handling WORKING-WORKING
+ comparisions between nodes in the working copy.
+
+ So the truth of the matter is, if the caller's arguments can't be
+ 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()
+ 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
+ a more pressing issue.
+ */
+
+/* Return a "you can't do that" error, optionally wrapping another
+ error CHILD_ERR. */
+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 "
+ "that is not yet supported"));
+}
+
+/* Perform a diff between two working-copy paths.
+
+ 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(). */
+static svn_error_t *
+diff_wc_wc(const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ const apr_array_header_t *changelists,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ struct diff_cmd_baton *callback_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *abspath1;
+ svn_error_t *err;
+ svn_node_kind_t kind;
+
+ SVN_ERR_ASSERT(! svn_path_is_url(path1));
+ SVN_ERR_ASSERT(! svn_path_is_url(path2));
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, pool));
+
+ /* Currently we support only the case where path1 and path2 are the
+ same path. */
+ if ((strcmp(path1, path2) != 0)
+ || (! ((revision1->kind == svn_opt_revision_base)
+ && (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"
+ )));
+
+
+ /* Resolve named revisions to real numbers. */
+ err = svn_client__get_revision_number(&callback_baton->revnum1, NULL,
+ ctx->wc_ctx, abspath1, NULL,
+ revision1, pool);
+
+ /* In case of an added node, we have no base rev, and we show a revision
+ * number of 0. Note that this code is currently always asking for
+ * svn_opt_revision_base.
+ * ### TODO: get rid of this 0 for added nodes. */
+ if (err && (err->apr_err == SVN_ERR_CLIENT_BAD_REVISION))
+ {
+ svn_error_clear(err);
+ callback_baton->revnum1 = 0;
+ }
+ else
+ SVN_ERR(err);
+
+ callback_baton->revnum2 = SVN_INVALID_REVNUM; /* WC */
+
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
+ TRUE, FALSE, pool));
+
+ if (kind != svn_node_dir)
+ callback_baton->anchor = svn_dirent_dirname(path1, pool);
+ else
+ callback_baton->anchor = path1;
+
+ SVN_ERR(svn_wc_diff6(ctx->wc_ctx,
+ abspath1,
+ callbacks, callback_baton,
+ depth,
+ ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+ return SVN_NO_ERROR;
+}
+
+/* Perform a diff between two repository paths.
+
+ PATH_OR_URL1 and PATH_OR_URL2 may be either URLs or the working copy paths.
+ REVISION1 and REVISION2 are their respective revisions.
+ If PEG_REVISION is specified, PATH_OR_URL2 is the path at the peg revision,
+ 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(). */
+static svn_error_t *
+diff_repos_repos(const svn_wc_diff_callbacks4_t *callbacks,
+ struct diff_cmd_baton *callback_baton,
+ svn_client_ctx_t *ctx,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *extra_ra_session;
+
+ const svn_ra_reporter3_t *reporter;
+ void *reporter_baton;
+
+ const svn_delta_editor_t *diff_editor;
+ void *diff_edit_baton;
+
+ const svn_diff_tree_processor_t *diff_processor;
+
+ const char *url1;
+ const char *url2;
+ const char *base_path;
+ svn_revnum_t rev1;
+ svn_revnum_t rev2;
+ svn_node_kind_t kind1;
+ svn_node_kind_t kind2;
+ const char *anchor1;
+ const char *anchor2;
+ const char *target1;
+ const char *target2;
+ svn_ra_session_t *ra_session;
+ const char *wri_abspath = NULL;
+
+ /* Prepare info for the repos repos diff. */
+ SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &base_path, &rev1, &rev2,
+ &anchor1, &anchor2, &target1, &target2,
+ &kind1, &kind2, &ra_session,
+ ctx, path_or_url1, path_or_url2,
+ revision1, revision2, peg_revision,
+ pool));
+
+ /* Find a WC path for the ra session */
+ if (!svn_path_is_url(path_or_url1))
+ SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url1, pool));
+ else if (!svn_path_is_url(path_or_url2))
+ SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url2, pool));
+
+ /* Set up the repos_diff editor on BASE_PATH, if available.
+ Otherwise, we just use "". */
+
+ SVN_ERR(svn_wc__wrap_diff_callbacks(&diff_processor,
+ callbacks, callback_baton,
+ TRUE /* walk_deleted_dirs */,
+ pool, pool));
+
+ /* Get actual URLs. */
+ callback_baton->orig_path_1 = url1;
+ callback_baton->orig_path_2 = url2;
+
+ /* Get numeric revisions. */
+ callback_baton->revnum1 = rev1;
+ callback_baton->revnum2 = rev2;
+
+ callback_baton->ra_session = ra_session;
+ callback_baton->anchor = base_path;
+
+ /* The repository can bring in a new working copy, but not delete
+ everything. Luckily our new diff handler can just be reversed. */
+ if (kind2 == svn_node_none)
+ {
+ const char *str_tmp;
+ svn_revnum_t rev_tmp;
+
+ str_tmp = url2;
+ url2 = url1;
+ url1 = str_tmp;
+
+ rev_tmp = rev2;
+ rev2 = rev1;
+ rev1 = rev_tmp;
+
+ str_tmp = anchor2;
+ anchor2 = anchor1;
+ anchor1 = str_tmp;
+
+ str_tmp = target2;
+ target2 = target1;
+ target1 = str_tmp;
+
+ diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
+ NULL, pool);
+ }
+
+ /* Filter the first path component using a filter processor, until we fixed
+ the diff processing to handle this directly */
+ if ((kind1 != svn_node_file && kind2 != svn_node_file) && target1[0] != '\0')
+ {
+ diff_processor = svn_diff__tree_processor_filter_create(diff_processor,
+ target1, pool);
+ }
+
+ /* Now, we open an extra RA session to the correct anchor
+ location for URL1. This is used during the editor calls to fetch file
+ contents. */
+ SVN_ERR(svn_client_open_ra_session2(&extra_ra_session, anchor1, wri_abspath,
+ ctx, pool, pool));
+
+ SVN_ERR(svn_client__get_diff_editor2(
+ &diff_editor, &diff_edit_baton,
+ extra_ra_session, depth,
+ rev1,
+ TRUE /* text_deltas */,
+ diff_processor,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+
+ /* We want to switch our txn into URL2 */
+ SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton,
+ rev2, target1,
+ depth, ignore_ancestry, TRUE /* text_deltas */,
+ url2, diff_editor, diff_edit_baton, pool));
+
+ /* Drive the reporter; do the diff. */
+ SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
+ svn_depth_infinity,
+ FALSE, NULL,
+ pool));
+
+ return svn_error_trace(reporter->finish_report(reporter_baton, pool));
+}
+
+/* Perform a diff between a repository path and a working-copy path.
+
+ PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a
+ working copy path. REVISION1 and REVISION2 are their respective
+ revisions. If REVERSE is TRUE, the diff will be done in reverse.
+ If PEG_REVISION is specified, then PATH_OR_URL1 is the path in the peg
+ revision, and the actual repository path to be compared is
+ determined by following copy history.
+
+ All other options are the same as those passed to svn_client_diff6(). */
+static svn_error_t *
+diff_repos_wc(const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *peg_revision,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t reverse,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ const apr_array_header_t *changelists,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *callback_baton,
+ struct diff_cmd_baton *cmd_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *pool = scratch_pool;
+ const char *url1, *anchor, *anchor_url, *target;
+ svn_revnum_t rev;
+ svn_ra_session_t *ra_session;
+ svn_depth_t diff_depth;
+ const svn_ra_reporter3_t *reporter;
+ void *reporter_baton;
+ const svn_delta_editor_t *diff_editor;
+ void *diff_edit_baton;
+ svn_boolean_t rev2_is_base = (revision2->kind == svn_opt_revision_base);
+ svn_boolean_t server_supports_depth;
+ const char *abspath_or_url1;
+ const char *abspath2;
+ const char *anchor_abspath;
+ svn_node_kind_t kind1;
+ svn_node_kind_t kind2;
+ svn_boolean_t is_copy;
+ svn_revnum_t cf_revision;
+ const char *cf_repos_relpath;
+ const char *cf_repos_root_url;
+
+ SVN_ERR_ASSERT(! svn_path_is_url(path2));
+
+ if (!svn_path_is_url(path_or_url1))
+ {
+ SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1, pool));
+ SVN_ERR(svn_wc__node_get_url(&url1, ctx->wc_ctx, abspath_or_url1,
+ pool, pool));
+ }
+ else
+ {
+ url1 = path_or_url1;
+ abspath_or_url1 = path_or_url1;
+ }
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath2, path2, pool));
+
+ /* Convert path_or_url1 to a URL to feed to do_diff. */
+ SVN_ERR(svn_wc_get_actual_target2(&anchor, &target,
+ ctx->wc_ctx, path2,
+ pool, pool));
+
+ /* Fetch the URL of the anchor directory. */
+ SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, pool));
+ SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
+ pool, pool));
+ SVN_ERR_ASSERT(anchor_url != NULL);
+
+ /* If we are performing a pegged diff, we need to find out what our
+ actual URLs will be. */
+ if (peg_revision->kind != svn_opt_revision_unspecified)
+ {
+ SVN_ERR(svn_client__repos_locations(&url1, NULL, NULL, NULL,
+ NULL,
+ path_or_url1,
+ peg_revision,
+ revision1, NULL,
+ ctx, pool));
+ if (!reverse)
+ {
+ cmd_baton->orig_path_1 = url1;
+ cmd_baton->orig_path_2 =
+ svn_path_url_add_component2(anchor_url, target, pool);
+ }
+ else
+ {
+ cmd_baton->orig_path_1 =
+ svn_path_url_add_component2(anchor_url, target, pool);
+ cmd_baton->orig_path_2 = url1;
+ }
+ }
+
+ /* Open an RA session to URL1 to figure out its node kind. */
+ SVN_ERR(svn_client_open_ra_session2(&ra_session, url1, abspath2,
+ ctx, pool, pool));
+ /* Resolve the revision to use for URL1. */
+ SVN_ERR(svn_client__get_revision_number(&rev, NULL, ctx->wc_ctx,
+ (strcmp(path_or_url1, url1) == 0)
+ ? NULL : abspath_or_url1,
+ ra_session, revision1, pool));
+ SVN_ERR(svn_ra_check_path(ra_session, "", rev, &kind1, pool));
+
+ /* Figure out the node kind of the local target. */
+ SVN_ERR(svn_wc_read_kind2(&kind2, ctx->wc_ctx, abspath2,
+ TRUE, FALSE, pool));
+
+ cmd_baton->ra_session = ra_session;
+ cmd_baton->anchor = anchor;
+
+ if (!reverse)
+ cmd_baton->revnum1 = rev;
+ else
+ cmd_baton->revnum2 = rev;
+
+ /* Check if our diff target is a copied node. */
+ SVN_ERR(svn_wc__node_get_origin(&is_copy,
+ &cf_revision,
+ &cf_repos_relpath,
+ &cf_repos_root_url,
+ NULL, NULL,
+ ctx->wc_ctx, abspath2,
+ FALSE, pool, pool));
+
+ /* Use the diff editor to generate the diff. */
+ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
+ SVN_RA_CAPABILITY_DEPTH, pool));
+ SVN_ERR(svn_wc__get_diff_editor(&diff_editor, &diff_edit_baton,
+ ctx->wc_ctx,
+ anchor_abspath,
+ target,
+ depth,
+ ignore_ancestry || is_copy,
+ show_copies_as_adds,
+ use_git_diff_format,
+ rev2_is_base,
+ reverse,
+ server_supports_depth,
+ changelists,
+ callbacks, callback_baton,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool, pool));
+ SVN_ERR(svn_ra_reparent(ra_session, anchor_url, pool));
+
+ if (depth != svn_depth_infinity)
+ diff_depth = depth;
+ else
+ diff_depth = svn_depth_unknown;
+
+ if (is_copy)
+ {
+ const char *copyfrom_parent_url;
+ const char *copyfrom_basename;
+ svn_depth_t copy_depth;
+
+ cmd_baton->repos_wc_diff_target_is_copy = TRUE;
+
+ /* We're diffing a locally copied/moved node.
+ * Describe the copy source to the reporter instead of the copy itself.
+ * Doing the latter would generate a single add_directory() call to the
+ * diff editor which results in an unexpected diff (the copy would
+ * be shown as deleted). */
+
+ if (cf_repos_relpath[0] == '\0')
+ {
+ copyfrom_parent_url = cf_repos_root_url;
+ copyfrom_basename = "";
+ }
+ else
+ {
+ const char *parent_relpath;
+ svn_relpath_split(&parent_relpath, &copyfrom_basename,
+ cf_repos_relpath, scratch_pool);
+
+ copyfrom_parent_url = svn_path_url_add_component2(cf_repos_root_url,
+ parent_relpath,
+ scratch_pool);
+ }
+ SVN_ERR(svn_ra_reparent(ra_session, copyfrom_parent_url, pool));
+
+ /* Tell the RA layer we want a delta to change our txn to URL1 */
+ SVN_ERR(svn_ra_do_diff3(ra_session,
+ &reporter, &reporter_baton,
+ rev,
+ target,
+ diff_depth,
+ ignore_ancestry,
+ TRUE, /* text_deltas */
+ url1,
+ diff_editor, diff_edit_baton, pool));
+
+ /* Report the copy source. */
+ SVN_ERR(svn_wc__node_get_depth(&copy_depth, ctx->wc_ctx, abspath2,
+ pool));
+
+ if (copy_depth == svn_depth_unknown)
+ copy_depth = svn_depth_infinity;
+
+ SVN_ERR(reporter->set_path(reporter_baton, "",
+ cf_revision,
+ copy_depth, FALSE, NULL, scratch_pool));
+
+ if (strcmp(target, copyfrom_basename) != 0)
+ SVN_ERR(reporter->link_path(reporter_baton, target,
+ svn_path_url_add_component2(
+ cf_repos_root_url,
+ cf_repos_relpath,
+ scratch_pool),
+ cf_revision,
+ copy_depth, FALSE, NULL, scratch_pool));
+
+ /* Finish the report to generate the diff. */
+ SVN_ERR(reporter->finish_report(reporter_baton, pool));
+ }
+ else
+ {
+ /* Tell the RA layer we want a delta to change our txn to URL1 */
+ SVN_ERR(svn_ra_do_diff3(ra_session,
+ &reporter, &reporter_baton,
+ rev,
+ target,
+ diff_depth,
+ ignore_ancestry,
+ TRUE, /* text_deltas */
+ url1,
+ diff_editor, diff_edit_baton, pool));
+
+ /* Create a txn mirror of path2; the diff editor will print
+ diffs in reverse. :-) */
+ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, abspath2,
+ reporter, reporter_baton,
+ FALSE, depth, TRUE,
+ (! server_supports_depth),
+ FALSE,
+ ctx->cancel_func, ctx->cancel_baton,
+ NULL, NULL, /* notification is N/A */
+ pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This is basically just the guts of svn_client_diff[_peg]6(). */
+static svn_error_t *
+do_diff(const svn_wc_diff_callbacks4_t *callbacks,
+ struct diff_cmd_baton *callback_baton,
+ svn_client_ctx_t *ctx,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ svn_boolean_t show_copies_as_adds,
+ svn_boolean_t use_git_diff_format,
+ const apr_array_header_t *changelists,
+ apr_pool_t *pool)
+{
+ svn_boolean_t is_repos1;
+ svn_boolean_t is_repos2;
+
+ /* Check if paths/revisions are urls/local. */
+ SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2,
+ revision1, revision2, peg_revision));
+
+ if (is_repos1)
+ {
+ if (is_repos2)
+ {
+ /* ### Ignores 'show_copies_as_adds'. */
+ SVN_ERR(diff_repos_repos(callbacks, callback_baton, ctx,
+ path_or_url1, path_or_url2,
+ revision1, revision2,
+ peg_revision, depth, ignore_ancestry,
+ pool));
+ }
+ else /* path_or_url2 is a working copy path */
+ {
+ SVN_ERR(diff_repos_wc(path_or_url1, revision1, peg_revision,
+ path_or_url2, revision2, FALSE, depth,
+ ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists,
+ callbacks, callback_baton, callback_baton,
+ ctx, pool));
+ }
+ }
+ else /* path_or_url1 is a working copy path */
+ {
+ if (is_repos2)
+ {
+ SVN_ERR(diff_repos_wc(path_or_url2, revision2, peg_revision,
+ path_or_url1, revision1, TRUE, depth,
+ ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists,
+ callbacks, callback_baton, callback_baton,
+ ctx, pool));
+ }
+ else /* path_or_url2 is a working copy path */
+ {
+ if (revision1->kind == svn_opt_revision_working
+ && revision2->kind == svn_opt_revision_working)
+ {
+ const char *abspath1;
+ const char *abspath2;
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, pool));
+ SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, pool));
+
+ SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2,
+ depth,
+ callbacks,
+ callback_baton,
+ ctx, pool));
+ }
+ else
+ SVN_ERR(diff_wc_wc(path_or_url1, revision1,
+ path_or_url2, revision2,
+ depth, ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists,
+ callbacks, callback_baton, ctx, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Perform a diff between a repository path and a working-copy path.
+
+ PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a
+ working copy path. REVISION1 and REVISION2 are their respective
+ revisions. If REVERSE is TRUE, the diff will be done in reverse.
+ If PEG_REVISION is specified, then PATH_OR_URL1 is the path in the peg
+ revision, and the actual repository path to be compared is
+ determined by following copy history.
+
+ All other options are the same as those passed to svn_client_diff6(). */
+static svn_error_t *
+diff_summarize_repos_wc(svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *peg_revision,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_boolean_t reverse,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ const char *anchor, *target;
+ svn_wc_diff_callbacks4_t *callbacks;
+ void *callback_baton;
+ struct diff_cmd_baton cmd_baton;
+
+ SVN_ERR_ASSERT(! svn_path_is_url(path2));
+
+ SVN_ERR(svn_wc_get_actual_target2(&anchor, &target,
+ ctx->wc_ctx, path2,
+ pool, pool));
+
+ SVN_ERR(svn_client__get_diff_summarize_callbacks(
+ &callbacks, &callback_baton, target, reverse,
+ summarize_func, summarize_baton, pool));
+
+ SVN_ERR(diff_repos_wc(path_or_url1, revision1, peg_revision,
+ path2, revision2, reverse,
+ depth, FALSE, TRUE, FALSE, changelists,
+ callbacks, callback_baton, &cmd_baton,
+ ctx, pool));
+ return SVN_NO_ERROR;
+}
+
+/* Perform a summary diff between two working-copy paths.
+
+ 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(). */
+static svn_error_t *
+diff_summarize_wc_wc(svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ const char *path1,
+ const svn_opt_revision_t *revision1,
+ const char *path2,
+ const svn_opt_revision_t *revision2,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ svn_wc_diff_callbacks4_t *callbacks;
+ void *callback_baton;
+ const char *abspath1, *target1;
+ svn_node_kind_t kind;
+
+ SVN_ERR_ASSERT(! svn_path_is_url(path1));
+ SVN_ERR_ASSERT(! svn_path_is_url(path2));
+
+ /* Currently we support only the case where path1 and path2 are the
+ same path. */
+ if ((strcmp(path1, path2) != 0)
+ || (! ((revision1->kind == svn_opt_revision_base)
+ && (revision2->kind == svn_opt_revision_working))))
+ return unsupported_diff_error
+ (svn_error_create
+ (SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Summarized diffs are only supported between a path's text-base "
+ "and its working files at this time")));
+
+ /* Find the node kind of PATH1 so that we know whether the diff drive will
+ be anchored at PATH1 or its parent dir. */
+ SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, pool));
+ SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
+ TRUE, FALSE, pool));
+ target1 = (kind == svn_node_dir) ? "" : svn_dirent_basename(path1, pool);
+ SVN_ERR(svn_client__get_diff_summarize_callbacks(
+ &callbacks, &callback_baton, target1, FALSE,
+ summarize_func, summarize_baton, pool));
+
+ SVN_ERR(svn_wc_diff6(ctx->wc_ctx,
+ abspath1,
+ callbacks, callback_baton,
+ depth,
+ ignore_ancestry, FALSE /* show_copies_as_adds */,
+ FALSE /* use_git_diff_format */, changelists,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+ return SVN_NO_ERROR;
+}
+
+/* Perform a diff summary between two repository paths. */
+static svn_error_t *
+diff_summarize_repos_repos(svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ apr_pool_t *pool)
+{
+ svn_ra_session_t *extra_ra_session;
+
+ const svn_ra_reporter3_t *reporter;
+ void *reporter_baton;
+
+ const svn_delta_editor_t *diff_editor;
+ void *diff_edit_baton;
+
+ const svn_diff_tree_processor_t *diff_processor;
+
+ const char *url1;
+ const char *url2;
+ const char *base_path;
+ svn_revnum_t rev1;
+ svn_revnum_t rev2;
+ svn_node_kind_t kind1;
+ svn_node_kind_t kind2;
+ const char *anchor1;
+ const char *anchor2;
+ const char *target1;
+ const char *target2;
+ svn_ra_session_t *ra_session;
+ svn_wc_diff_callbacks4_t *callbacks;
+ void *callback_baton;
+
+ /* Prepare info for the repos repos diff. */
+ SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &base_path, &rev1, &rev2,
+ &anchor1, &anchor2, &target1, &target2,
+ &kind1, &kind2, &ra_session,
+ ctx, path_or_url1, path_or_url2,
+ revision1, revision2,
+ peg_revision, pool));
+
+ /* Set up the repos_diff editor. */
+ SVN_ERR(svn_client__get_diff_summarize_callbacks(
+ &callbacks, &callback_baton,
+ target1, FALSE, summarize_func, summarize_baton, pool));
+
+ SVN_ERR(svn_wc__wrap_diff_callbacks(&diff_processor,
+ callbacks, callback_baton,
+ TRUE /* walk_deleted_dirs */,
+ pool, pool));
+
+
+ /* The repository can bring in a new working copy, but not delete
+ everything. Luckily our new diff handler can just be reversed. */
+ if (kind2 == svn_node_none)
+ {
+ const char *str_tmp;
+ svn_revnum_t rev_tmp;
+
+ str_tmp = url2;
+ url2 = url1;
+ url1 = str_tmp;
+
+ rev_tmp = rev2;
+ rev2 = rev1;
+ rev1 = rev_tmp;
+
+ str_tmp = anchor2;
+ anchor2 = anchor1;
+ anchor1 = str_tmp;
+
+ str_tmp = target2;
+ target2 = target1;
+ target1 = str_tmp;
+
+ diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
+ NULL, pool);
+ }
+
+ /* Now, we open an extra RA session to the correct anchor
+ location for URL1. This is used to get deleted path information. */
+ SVN_ERR(svn_client_open_ra_session2(&extra_ra_session, anchor1, NULL,
+ ctx, pool, pool));
+
+ SVN_ERR(svn_client__get_diff_editor2(&diff_editor, &diff_edit_baton,
+ extra_ra_session,
+ depth,
+ rev1,
+ FALSE /* text_deltas */,
+ diff_processor,
+ ctx->cancel_func, ctx->cancel_baton,
+ pool));
+
+ /* We want to switch our txn into URL2 */
+ SVN_ERR(svn_ra_do_diff3
+ (ra_session, &reporter, &reporter_baton, rev2, target1,
+ depth, ignore_ancestry,
+ FALSE /* do not create text delta */, url2, diff_editor,
+ diff_edit_baton, pool));
+
+ /* Drive the reporter; do the diff. */
+ SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
+ svn_depth_infinity,
+ FALSE, NULL, pool));
+ return svn_error_trace(reporter->finish_report(reporter_baton, pool));
+}
+
+/* This is basically just the guts of svn_client_diff_summarize[_peg]2(). */
+static svn_error_t *
+do_diff_summarize(svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ const char *path_or_url1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision1,
+ const svn_opt_revision_t *revision2,
+ const svn_opt_revision_t *peg_revision,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ apr_pool_t *pool)
+{
+ svn_boolean_t is_repos1;
+ svn_boolean_t is_repos2;
+
+ /* Check if paths/revisions are urls/local. */
+ SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2,
+ revision1, revision2, peg_revision));
+
+ if (is_repos1)
+ {
+ if (is_repos2)
+ SVN_ERR(diff_summarize_repos_repos(summarize_func, summarize_baton, ctx,
+ path_or_url1, path_or_url2,
+ revision1, revision2,
+ peg_revision, depth, ignore_ancestry,
+ pool));
+ else
+ SVN_ERR(diff_summarize_repos_wc(summarize_func, summarize_baton,
+ path_or_url1, revision1,
+ peg_revision,
+ path_or_url2, revision2,
+ FALSE, depth,
+ ignore_ancestry,
+ changelists,
+ ctx, pool));
+ }
+ else /* ! is_repos1 */
+ {
+ if (is_repos2)
+ SVN_ERR(diff_summarize_repos_wc(summarize_func, summarize_baton,
+ path_or_url2, revision2,
+ peg_revision,
+ path_or_url1, revision1,
+ TRUE, depth,
+ ignore_ancestry,
+ changelists,
+ ctx, pool));
+ else
+ {
+ if (revision1->kind == svn_opt_revision_working
+ && revision2->kind == svn_opt_revision_working)
+ {
+ const char *abspath1;
+ const char *abspath2;
+ svn_wc_diff_callbacks4_t *callbacks;
+ void *callback_baton;
+ const char *target;
+ svn_node_kind_t kind;
+
+ SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, pool));
+ SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, pool));
+
+ SVN_ERR(svn_io_check_resolved_path(abspath1, &kind, pool));
+
+ if (kind == svn_node_dir)
+ target = "";
+ else
+ target = svn_dirent_basename(path_or_url1, NULL);
+
+ SVN_ERR(svn_client__get_diff_summarize_callbacks(
+ &callbacks, &callback_baton, target, FALSE,
+ summarize_func, summarize_baton, pool));
+
+ SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2,
+ depth,
+ callbacks,
+ callback_baton,
+ ctx, pool));
+ }
+ else
+ SVN_ERR(diff_summarize_wc_wc(summarize_func, summarize_baton,
+ path_or_url1, revision1,
+ path_or_url2, revision2,
+ depth, ignore_ancestry,
+ changelists, ctx, pool));
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Initialize DIFF_CMD_BATON.diff_cmd and DIFF_CMD_BATON.options,
+ * according to OPTIONS and CONFIG. CONFIG and OPTIONS may be null.
+ * Allocate the fields in POOL, which should be at least as long-lived
+ * as the pool DIFF_CMD_BATON itself is allocated in.
+ */
+static svn_error_t *
+set_up_diff_cmd_and_options(struct diff_cmd_baton *diff_cmd_baton,
+ const apr_array_header_t *options,
+ apr_hash_t *config, apr_pool_t *pool)
+{
+ const char *diff_cmd = NULL;
+
+ /* See if there is a diff command and/or diff arguments. */
+ if (config)
+ {
+ svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
+ svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF_CMD, NULL);
+ if (options == NULL)
+ {
+ const char *diff_extensions;
+ svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS,
+ SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL);
+ if (diff_extensions)
+ options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE, pool);
+ }
+ }
+
+ if (options == NULL)
+ options = apr_array_make(pool, 0, sizeof(const char *));
+
+ if (diff_cmd)
+ SVN_ERR(svn_path_cstring_to_utf8(&diff_cmd_baton->diff_cmd, diff_cmd,
+ pool));
+ else
+ diff_cmd_baton->diff_cmd = NULL;
+
+ /* If there was a command, arrange options to pass to it. */
+ if (diff_cmd_baton->diff_cmd)
+ {
+ const char **argv = NULL;
+ int argc = options->nelts;
+ if (argc)
+ {
+ int i;
+ argv = apr_palloc(pool, argc * sizeof(char *));
+ for (i = 0; i < argc; i++)
+ SVN_ERR(svn_utf_cstring_to_utf8(&argv[i],
+ APR_ARRAY_IDX(options, i, const char *), pool));
+ }
+ diff_cmd_baton->options.for_external.argv = argv;
+ diff_cmd_baton->options.for_external.argc = argc;
+ }
+ else /* No command, so arrange options for internal invocation instead. */
+ {
+ diff_cmd_baton->options.for_internal
+ = svn_diff_file_options_create(pool);
+ SVN_ERR(svn_diff_file_options_parse
+ (diff_cmd_baton->options.for_internal, options, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/*----------------------------------------------------------------------- */
+
+/*** Public Interfaces. ***/
+
+/* Display context diffs between two PATH/REVISION pairs. Each of
+ these inputs will be one of the following:
+
+ - a repository URL at a given revision.
+ - a working copy path, ignoring local mods.
+ - a working copy path, including local mods.
+
+ We can establish a matrix that shows the nine possible types of
+ diffs we expect to support.
+
+
+ ` . DST || URL:rev | WC:base | WC:working |
+ ` . || | | |
+ SRC ` . || | | |
+ ============++============+============+============+
+ URL:rev || (*) | (*) | (*) |
+ || | | |
+ || | | |
+ || | | |
+ ------------++------------+------------+------------+
+ WC:base || (*) | |
+ || | New svn_wc_diff which |
+ || | is smart enough to |
+ || | handle two WC paths |
+ ------------++------------+ and their related +
+ WC:working || (*) | text-bases and working |
+ || | files. This operation |
+ || | is entirely local. |
+ || | |
+ ------------++------------+------------+------------+
+ * These cases require server communication.
+*/
+svn_error_t *
+svn_client_diff6(const apr_array_header_t *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)
+{
+ struct diff_cmd_baton diff_cmd_baton = { 0 };
+ svn_opt_revision_t peg_revision;
+
+ 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"));
+
+ /* We will never do a pegged diff from here. */
+ peg_revision.kind = svn_opt_revision_unspecified;
+
+ /* setup callback and baton */
+ diff_cmd_baton.orig_path_1 = path_or_url1;
+ diff_cmd_baton.orig_path_2 = path_or_url2;
+
+ SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton, options,
+ ctx->config, pool));
+ diff_cmd_baton.pool = pool;
+ diff_cmd_baton.outstream = outstream;
+ diff_cmd_baton.errstream = errstream;
+ diff_cmd_baton.header_encoding = header_encoding;
+ diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
+ diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
+
+ diff_cmd_baton.force_binary = ignore_content_type;
+ diff_cmd_baton.ignore_properties = ignore_properties;
+ diff_cmd_baton.properties_only = properties_only;
+ diff_cmd_baton.relative_to_dir = relative_to_dir;
+ diff_cmd_baton.use_git_diff_format = use_git_diff_format;
+ diff_cmd_baton.no_diff_added = no_diff_added;
+ diff_cmd_baton.no_diff_deleted = no_diff_deleted;
+ diff_cmd_baton.no_copyfrom_on_add = show_copies_as_adds;
+
+ diff_cmd_baton.wc_ctx = ctx->wc_ctx;
+ diff_cmd_baton.ra_session = NULL;
+ diff_cmd_baton.anchor = NULL;
+
+ return do_diff(&diff_callbacks, &diff_cmd_baton, ctx,
+ path_or_url1, path_or_url2, revision1, revision2,
+ &peg_revision,
+ depth, ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists, pool);
+}
+
+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)
+{
+ struct diff_cmd_baton diff_cmd_baton = { 0 };
+
+ 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 */
+ diff_cmd_baton.orig_path_1 = path_or_url;
+ diff_cmd_baton.orig_path_2 = path_or_url;
+
+ SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton, options,
+ ctx->config, pool));
+ diff_cmd_baton.pool = pool;
+ diff_cmd_baton.outstream = outstream;
+ diff_cmd_baton.errstream = errstream;
+ diff_cmd_baton.header_encoding = header_encoding;
+ diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
+ diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
+
+ diff_cmd_baton.force_binary = ignore_content_type;
+ diff_cmd_baton.ignore_properties = ignore_properties;
+ diff_cmd_baton.properties_only = properties_only;
+ diff_cmd_baton.relative_to_dir = relative_to_dir;
+ diff_cmd_baton.use_git_diff_format = use_git_diff_format;
+ diff_cmd_baton.no_diff_added = no_diff_added;
+ diff_cmd_baton.no_diff_deleted = no_diff_deleted;
+ diff_cmd_baton.no_copyfrom_on_add = show_copies_as_adds;
+
+ diff_cmd_baton.wc_ctx = ctx->wc_ctx;
+ diff_cmd_baton.ra_session = NULL;
+ diff_cmd_baton.anchor = NULL;
+
+ return do_diff(&diff_callbacks, &diff_cmd_baton, ctx,
+ path_or_url, path_or_url, start_revision, end_revision,
+ peg_revision,
+ depth, ignore_ancestry, show_copies_as_adds,
+ use_git_diff_format, changelists, pool);
+}
+
+svn_error_t *
+svn_client_diff_summarize2(const char *path_or_url1,
+ const svn_opt_revision_t *revision1,
+ const char *path_or_url2,
+ const svn_opt_revision_t *revision2,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ /* We will never do a pegged diff from here. */
+ svn_opt_revision_t peg_revision;
+ peg_revision.kind = svn_opt_revision_unspecified;
+
+ return do_diff_summarize(summarize_func, summarize_baton, ctx,
+ path_or_url1, path_or_url2, revision1, revision2,
+ &peg_revision,
+ depth, ignore_ancestry, changelists, pool);
+}
+
+svn_error_t *
+svn_client_diff_summarize_peg2(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,
+ svn_depth_t depth,
+ svn_boolean_t ignore_ancestry,
+ const apr_array_header_t *changelists,
+ svn_client_diff_summarize_func_t summarize_func,
+ void *summarize_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *pool)
+{
+ return do_diff_summarize(summarize_func, summarize_baton, ctx,
+ path_or_url, path_or_url,
+ start_revision, end_revision, peg_revision,
+ depth, ignore_ancestry, changelists, pool);
+}
+
+svn_client_diff_summarize_t *
+svn_client_diff_summarize_dup(const svn_client_diff_summarize_t *diff,
+ apr_pool_t *pool)
+{
+ svn_client_diff_summarize_t *dup_diff = apr_palloc(pool, sizeof(*dup_diff));
+
+ *dup_diff = *diff;
+
+ if (diff->path)
+ dup_diff->path = apr_pstrdup(pool, diff->path);
+
+ return dup_diff;
+}
OpenPOWER on IntegriCloud