You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by rh...@apache.org on 2013/01/03 15:41:13 UTC
svn commit: r1428366 [2/3] - in /subversion/trunk/subversion:
include/private/svn_client_private.h libsvn_client/diff.c
libsvn_client/diff_local.c svn/diff-cmd.c
Copied: subversion/trunk/subversion/libsvn_client/diff_local.c (from r1428337, subversion/trunk/subversion/libsvn_client/diff.c)
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_client/diff_local.c?p2=subversion/trunk/subversion/libsvn_client/diff_local.c&p1=subversion/trunk/subversion/libsvn_client/diff.c&r1=1428337&r2=1428366&rev=1428366&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_client/diff.c (original)
+++ subversion/trunk/subversion/libsvn_client/diff_local.c Thu Jan 3 14:41:13 2013
@@ -1,5 +1,5 @@
/*
- * diff.c: comparing
+ * diff_local.c: comparing local trees with each other
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
@@ -40,3425 +40,476 @@
#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_time.h"
#include "svn_sorts.h"
-#include "svn_subst.h"
#include "client.h"
#include "private/svn_wc_private.h"
-#include "private/svn_diff_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. */
+/* Try to get properties for LOCAL_ABSPATH and return them in the property
+ * hash *PROPS. If there are no properties because LOCAL_ABSPATH is not
+ * versioned, return an empty property hash. */
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)
+get_props(apr_hash_t **props,
+ const char *local_abspath,
+ svn_wc_context_t *wc_ctx,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- const char *local_abspath;
- const char *orig_repos_relpath = NULL;
+ svn_error_t *err;
- if (! ra_session
- || (anchor && !svn_path_is_url(orig_target)))
+ err = svn_wc_prop_list2(props, wc_ctx, local_abspath, result_pool,
+ scratch_pool);
+ if (err)
{
- 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_relpath(repos_relpath, wc_ctx,
- local_abspath,
- result_pool, scratch_pool);
-
- if (!ra_session
- || ! err
- || (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND))
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
+ err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY ||
+ err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
{
- return svn_error_trace(err);
+ svn_error_clear(err);
+ *props = apr_hash_make(result_pool);
}
-
- /* 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);
+ else
+ return svn_error_trace(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. */
+/* Produce a diff between two arbitrary files at LOCAL_ABSPATH1 and
+ * LOCAL_ABSPATH2, using the diff callbacks from CALLBACKS.
+ * Use PATH as the name passed to diff callbacks.
+ * FILE1_IS_EMPTY and FILE2_IS_EMPTY are used as hints which diff callback
+ * function to use to compare the files (added/deleted/changed).
+ *
+ * If ORIGINAL_PROPS_OVERRIDE is not NULL, use it as original properties
+ * instead of reading properties from LOCAL_ABSPATH1. This is required when
+ * a file replaces a directory, where LOCAL_ABSPATH1 is an empty file that
+ * file content must be diffed against, but properties to diff against come
+ * from the replaced directory. */
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)
+do_arbitrary_files_diff(const char *local_abspath1,
+ const char *local_abspath2,
+ const char *path,
+ svn_boolean_t file1_is_empty,
+ svn_boolean_t file2_is_empty,
+ apr_hash_t *original_props_override,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *diff_baton,
+ svn_client_ctx_t *ctx,
+ 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;
+ apr_hash_t *original_props;
+ apr_hash_t *modified_props;
+ apr_array_header_t *prop_changes;
+ svn_string_t *original_mime_type = NULL;
+ svn_string_t *modified_mime_type = NULL;
- if (anchor)
- new_path = svn_dirent_join(anchor, new_path, result_pool);
+ if (ctx->cancel_func)
+ SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
- 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);
+ /* Try to get properties from either file. It's OK if the files do not
+ * have properties, or if they are unversioned. */
+ if (original_props_override)
+ original_props = original_props_override;
+ else
+ SVN_ERR(get_props(&original_props, local_abspath1, ctx->wc_ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(get_props(&modified_props, local_abspath2, ctx->wc_ctx,
+ scratch_pool, scratch_pool));
- child_path = svn_dirent_is_child(relative_to_dir, new_path1,
- result_pool);
- }
+ SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props,
+ scratch_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. */
+ /* Try to determine the mime-type of each file. */
+ original_mime_type = apr_hash_get(original_props, SVN_PROP_MIME_TYPE,
+ APR_HASH_KEY_STRING);
+ if (!file1_is_empty && !original_mime_type)
+ {
+ const char *mime_type;
+ SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1,
+ ctx->mimetypes_map, scratch_pool));
- /* ### BH: We can now just construct the repos_relpath, etc. as the
- anchor is available. See also make_repos_relpath() */
+ if (mime_type)
+ original_mime_type = svn_string_create(mime_type, scratch_pool);
+ }
- is_url1 = svn_path_is_url(new_path1);
- is_url2 = svn_path_is_url(new_path2);
+ modified_mime_type = apr_hash_get(modified_props, SVN_PROP_MIME_TYPE,
+ APR_HASH_KEY_STRING);
+ if (!file2_is_empty && !modified_mime_type)
+ {
+ const char *mime_type;
+ SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1,
+ ctx->mimetypes_map, scratch_pool));
- 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 (mime_type)
+ modified_mime_type = svn_string_create(mime_type, scratch_pool);
+ }
- 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);
+ /* Produce the diff. */
+ if (file1_is_empty && !file2_is_empty)
+ SVN_ERR(callbacks->file_added(NULL, NULL, NULL, path,
+ local_abspath1, local_abspath2,
+ /* ### TODO get real revision info
+ * for versioned files? */
+ SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
+ original_mime_type ?
+ original_mime_type->data : NULL,
+ modified_mime_type ?
+ modified_mime_type->data : NULL,
+ /* ### TODO get copyfrom? */
+ NULL, SVN_INVALID_REVNUM,
+ prop_changes, original_props,
+ diff_baton, scratch_pool));
+ else if (!file1_is_empty && file2_is_empty)
+ SVN_ERR(callbacks->file_deleted(NULL, NULL, path,
+ local_abspath1, local_abspath2,
+ original_mime_type ?
+ original_mime_type->data : NULL,
+ modified_mime_type ?
+ modified_mime_type->data : NULL,
+ original_props,
+ diff_baton, scratch_pool));
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;
+ SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, path,
+ local_abspath1, local_abspath2,
+ /* ### TODO get real revision info
+ * for versioned files? */
+ SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
+ original_mime_type ?
+ original_mime_type->data : NULL,
+ modified_mime_type ?
+ modified_mime_type->data : NULL,
+ prop_changes, original_props,
+ diff_baton, scratch_pool));
return SVN_NO_ERROR;
}
+struct arbitrary_diff_walker_baton {
+ /* The root directories of the trees being compared. */
+ const char *root1_abspath;
+ const char *root2_abspath;
-/* 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);
+ /* TRUE if recursing within an added subtree of root2_abspath that
+ * does not exist in root1_abspath. */
+ svn_boolean_t recursing_within_added_subtree;
- return label;
-}
+ /* TRUE if recursing within an administrative (.i.e. .svn) directory. */
+ svn_boolean_t recursing_within_adm_dir;
-/* 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;
-}
+ /* The absolute path of the adm dir if RECURSING_WITHIN_ADM_DIR is TRUE.
+ * Else this is NULL.*/
+ const char *adm_dir_abspath;
-/* 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;
-}
+ /* A path to an empty file used for diffs that add/delete files. */
+ const char *empty_file_abspath;
-/* 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;
-}
+ const svn_wc_diff_callbacks4_t *callbacks;
+ void *diff_baton;
+ svn_client_ctx_t *ctx;
+ apr_pool_t *pool;
+} arbitrary_diff_walker_baton;
-/* 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. */
+/* Forward declaration needed because this function has a cyclic
+ * dependency with do_arbitrary_dirs_diff(). */
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;
-}
+arbitrary_diff_walker(void *baton, const char *local_abspath,
+ const apr_finfo_t *finfo,
+ apr_pool_t *scratch_pool);
-/* 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. */
+/* Produce a diff between two arbitrary directories at LOCAL_ABSPATH1 and
+ * LOCAL_ABSPATH2, using the provided diff callbacks to show file changes
+ * and, for versioned nodes, property changes.
+ *
+ * If ROOT_ABSPATH1 and ROOT_ABSPATH2 are not NULL, show paths in diffs
+ * relative to these roots, rather than relative to LOCAL_ABSPATH1 and
+ * LOCAL_ABSPATH2. This is needed when crawling a subtree that exists
+ * only within LOCAL_ABSPATH2. */
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)
+do_arbitrary_dirs_diff(const char *local_abspath1,
+ const char *local_abspath2,
+ const char *root_abspath1,
+ const char *root_abspath2,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *diff_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_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));
+ apr_file_t *empty_file;
+ svn_node_kind_t kind1;
+
+ struct arbitrary_diff_walker_baton b;
+
+ /* If LOCAL_ABSPATH1 is not a directory, crawl LOCAL_ABSPATH2 instead
+ * and compare it to LOCAL_ABSPATH1, showing only additions.
+ * This case can only happen during recursion from arbitrary_diff_walker(),
+ * because do_arbitrary_nodes_diff() prevents this from happening at
+ * the root of the comparison. */
+ SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
+ b.recursing_within_added_subtree = (kind1 != svn_node_dir);
+
+ b.root1_abspath = root_abspath1 ? root_abspath1 : local_abspath1;
+ b.root2_abspath = root_abspath2 ? root_abspath2 : local_abspath2;
+ b.recursing_within_adm_dir = FALSE;
+ b.adm_dir_abspath = NULL;
+ b.callbacks = callbacks;
+ b.diff_baton = diff_baton;
+ b.ctx = ctx;
+ b.pool = scratch_pool;
+
+ SVN_ERR(svn_io_open_unique_file3(&empty_file, &b.empty_file_abspath,
+ NULL, svn_io_file_del_on_pool_cleanup,
+ scratch_pool, scratch_pool));
+
+ SVN_ERR(svn_io_dir_walk2(b.recursing_within_added_subtree ? local_abspath2
+ : local_abspath1,
+ 0, arbitrary_diff_walker, &b, scratch_pool));
+
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. */
+/* An implementation of svn_io_walk_func_t.
+ * Note: LOCAL_ABSPATH is the path being crawled and can be on either side
+ * of the diff depending on baton->recursing_within_added_subtree. */
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,
+arbitrary_diff_walker(void *baton, const char *local_abspath,
+ const apr_finfo_t *finfo,
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);
+ struct arbitrary_diff_walker_baton *b = baton;
+ const char *local_abspath1;
+ const char *local_abspath2;
+ svn_node_kind_t kind1;
+ svn_node_kind_t kind2;
+ const char *child_relpath;
+ apr_hash_t *dirents1;
+ apr_hash_t *dirents2;
+ apr_hash_t *merged_dirents;
+ apr_array_header_t *sorted_dirents;
+ int i;
+ apr_pool_t *iterpool;
- }
- 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)
+ if (b->ctx->cancel_func)
+ SVN_ERR(b->ctx->cancel_func(b->ctx->cancel_baton));
+
+ if (finfo->filetype != APR_DIR)
+ return SVN_NO_ERROR;
+
+ if (b->recursing_within_adm_dir)
{
- 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);
+ if (svn_dirent_skip_ancestor(b->adm_dir_abspath, local_abspath))
+ return SVN_NO_ERROR;
+ else
+ {
+ b->recursing_within_adm_dir = FALSE;
+ b->adm_dir_abspath = NULL;
+ }
}
- else if (operation == svn_diff_op_moved)
+ else if (strcmp(svn_dirent_basename(local_abspath, scratch_pool),
+ SVN_WC_ADM_DIR_NAME) == 0)
{
- 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);
+ b->recursing_within_adm_dir = TRUE;
+ b->adm_dir_abspath = apr_pstrdup(b->pool, local_abspath);
+ return SVN_NO_ERROR;
}
- 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.
+ if (b->recursing_within_added_subtree)
+ child_relpath = svn_dirent_skip_ancestor(b->root2_abspath, local_abspath);
+ else
+ child_relpath = svn_dirent_skip_ancestor(b->root1_abspath, local_abspath);
+ if (!child_relpath)
+ return SVN_NO_ERROR;
- 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;
+ local_abspath1 = svn_dirent_join(b->root1_abspath, child_relpath,
+ scratch_pool);
+ SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
- 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));
- }
+ local_abspath2 = svn_dirent_join(b->root2_abspath, child_relpath,
+ scratch_pool);
+ SVN_ERR(svn_io_check_resolved_path(local_abspath2, &kind2, 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 (kind1 == svn_node_dir)
+ SVN_ERR(svn_io_get_dirents3(&dirents1, local_abspath1,
+ TRUE, /* only_check_type */
+ scratch_pool, scratch_pool));
+ else
+ dirents1 = apr_hash_make(scratch_pool);
- if (show_diff_header)
+ if (kind2 == svn_node_dir)
{
- 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! */
+ apr_hash_t *original_props;
+ apr_hash_t *modified_props;
+ apr_array_header_t *prop_changes;
- 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;
-
- /* Set this flag if you want diff_file_changed to output diffs
- unconditionally, even if the diffs are empty. */
- svn_boolean_t force_empty;
-
- /* 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 deletion of a file is summarized versus showing a full diff. */
- svn_boolean_t no_diff_deleted;
-
- 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(svn_wc_notify_state_t *state,
- svn_boolean_t *tree_conflicted,
- 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));
- }
-
- if (state)
- *state = svn_wc_notify_state_unknown;
- if (tree_conflicted)
- *tree_conflicted = FALSE;
-
- 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(state,
- tree_conflicted,
- diff_relpath,
- /* ### These revs be filled
- * ### with per node info */
- 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.
-
- 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,
- 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 (svn_diff_contains_diffs(diff) || diff_cmd_baton->force_empty ||
- diff_cmd_baton->use_git_diff_format)
- {
- /* 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 (svn_diff_contains_diffs(diff) || diff_cmd_baton->force_empty)
- 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, NULL,
- SVN_INVALID_REVNUM, diff_cmd_baton,
- scratch_pool));
- if (prop_changes->nelts > 0)
- SVN_ERR(diff_props_changed(prop_state, tree_conflicted,
- diff_relpath, rev1, rev2, FALSE, prop_changes,
- original_props, !wrote_header,
- diff_cmd_baton, scratch_pool));
- if (content_state)
- *content_state = svn_wc_notify_state_unknown;
- if (prop_state)
- *prop_state = svn_wc_notify_state_unknown;
- if (tree_conflicted)
- *tree_conflicted = FALSE;
- 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;
- }
-
- /* We want diff_file_changed to unconditionally show diffs, even if
- the diff is empty (as would be the case if an empty file were
- added.) It's important, because 'patch' would still see an empty
- diff and create an empty file. It's also important to let the
- user see that *something* happened. */
- diff_cmd_baton->force_empty = TRUE;
-
- if (tmpfile1 && copyfrom_path)
- SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
- tmpfile1, tmpfile2, rev1, rev2,
- mimetype1, mimetype2,
- svn_diff_op_copied, 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, NULL, SVN_INVALID_REVNUM,
- diff_cmd_baton, scratch_pool));
-
- if (prop_changes->nelts > 0)
- SVN_ERR(diff_props_changed(prop_state, tree_conflicted,
- diff_relpath, rev1, rev2,
- FALSE, prop_changes,
- original_props, ! wrote_header,
- diff_cmd_baton, scratch_pool));
- if (content_state)
- *content_state = svn_wc_notify_state_unknown;
- if (prop_state)
- *prop_state = svn_wc_notify_state_unknown;
- if (tree_conflicted)
- *tree_conflicted = FALSE;
-
- diff_cmd_baton->force_empty = FALSE;
-
- 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, 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. */
-
- if (state)
- *state = svn_wc_notify_state_unknown;
- if (tree_conflicted)
- *tree_conflicted = FALSE;
-
- 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. */
-
-
-
-
-/* Helper function: given a working-copy ABSPATH_OR_URL, return its
- associated url in *URL, allocated in RESULT_POOL. If ABSPATH_OR_URL is
- *already* a URL, that's fine, return ABSPATH_OR_URL allocated in
- RESULT_POOL.
-
- Use SCRATCH_POOL for temporary allocations. */
-static svn_error_t *
-convert_to_url(const char **url,
- svn_wc_context_t *wc_ctx,
- const char *abspath_or_url,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- if (svn_path_is_url(abspath_or_url))
- {
- *url = apr_pstrdup(result_pool, abspath_or_url);
- return SVN_NO_ERROR;
- }
-
- SVN_ERR(svn_wc__node_get_url(url, wc_ctx, abspath_or_url,
- result_pool, scratch_pool));
- if (! *url)
- return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
- _("Path '%s' has no URL"),
- svn_dirent_local_style(abspath_or_url,
- scratch_pool));
- return SVN_NO_ERROR;
-}
-
-/** 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;
-
- if (!svn_path_is_url(path_or_url2))
- SVN_ERR(svn_dirent_get_absolute(&abspath_or_url2, path_or_url2,
- pool));
- else
- abspath_or_url2 = path_or_url2;
-
- if (!svn_path_is_url(path_or_url1))
- SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1,
- pool));
- else
- abspath_or_url1 = path_or_url1;
-
- /* Figure out URL1 and URL2. */
- SVN_ERR(convert_to_url(url1, ctx->wc_ctx, abspath_or_url1,
- pool, pool));
- SVN_ERR(convert_to_url(url2, ctx->wc_ctx, abspath_or_url2,
- pool, pool));
-
- /* 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_session_internal(ra_session, NULL, *url2,
- NULL, NULL, FALSE,
- TRUE, ctx, 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));
-
- /* Choose useful anchors and targets for our two URLs. */
- *anchor1 = *url1;
- *anchor2 = *url2;
- *target1 = "";
- *target2 = "";
-
- /* If one of the targets is a file, use the parent directory as anchor. */
- if (*kind1 == svn_node_file || *kind2 == svn_node_file)
- {
- svn_uri_split(anchor1, target1, *url1, pool);
- svn_uri_split(anchor2, target2, *url2, pool);
- if (*base_path)
- *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
-
- So the truth of the matter is, if the caller's arguments can't be
- pigeonholed into one of these three 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"));
-}
-
-/* Try to get properties for LOCAL_ABSPATH and return them in the property
- * hash *PROPS. If there are no properties because LOCAL_ABSPATH is not
- * versioned, return an empty property hash. */
-static svn_error_t *
-get_props(apr_hash_t **props,
- const char *local_abspath,
- svn_wc_context_t *wc_ctx,
- apr_pool_t *result_pool,
- apr_pool_t *scratch_pool)
-{
- svn_error_t *err;
-
- err = svn_wc_prop_list2(props, wc_ctx, local_abspath, result_pool,
- scratch_pool);
- if (err)
- {
- if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
- err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY ||
- err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
- {
- svn_error_clear(err);
- *props = apr_hash_make(result_pool);
- }
- else
- return svn_error_trace(err);
- }
-
- return SVN_NO_ERROR;
-}
-
-/* Produce a diff between two arbitrary files at LOCAL_ABSPATH1 and
- * LOCAL_ABSPATH2, using the diff callbacks from CALLBACKS.
- * Use PATH as the name passed to diff callbacks.
- * FILE1_IS_EMPTY and FILE2_IS_EMPTY are used as hints which diff callback
- * function to use to compare the files (added/deleted/changed).
- *
- * If ORIGINAL_PROPS_OVERRIDE is not NULL, use it as original properties
- * instead of reading properties from LOCAL_ABSPATH1. This is required when
- * a file replaces a directory, where LOCAL_ABSPATH1 is an empty file that
- * file content must be diffed against, but properties to diff against come
- * from the replaced directory. */
-static svn_error_t *
-do_arbitrary_files_diff(const char *local_abspath1,
- const char *local_abspath2,
- const char *path,
- svn_boolean_t file1_is_empty,
- svn_boolean_t file2_is_empty,
- apr_hash_t *original_props_override,
- const svn_wc_diff_callbacks4_t *callbacks,
- struct diff_cmd_baton *diff_cmd_baton,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
-{
- apr_hash_t *original_props;
- apr_hash_t *modified_props;
- apr_array_header_t *prop_changes;
- svn_string_t *original_mime_type = NULL;
- svn_string_t *modified_mime_type = NULL;
-
- if (ctx->cancel_func)
- SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
-
- if (diff_cmd_baton->ignore_properties)
- {
- original_props = apr_hash_make(scratch_pool);
- modified_props = apr_hash_make(scratch_pool);
- }
- else
- {
- /* Try to get properties from either file. It's OK if the files do not
- * have properties, or if they are unversioned. */
- if (original_props_override)
- original_props = original_props_override;
- else
- SVN_ERR(get_props(&original_props, local_abspath1, ctx->wc_ctx,
- scratch_pool, scratch_pool));
- SVN_ERR(get_props(&modified_props, local_abspath2, ctx->wc_ctx,
- scratch_pool, scratch_pool));
- }
-
- SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props,
- scratch_pool));
-
- if (!diff_cmd_baton->force_binary)
- {
- /* Try to determine the mime-type of each file. */
- original_mime_type = apr_hash_get(original_props, SVN_PROP_MIME_TYPE,
- APR_HASH_KEY_STRING);
- if (!file1_is_empty && !original_mime_type)
- {
- const char *mime_type;
- SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1,
- ctx->mimetypes_map, scratch_pool));
-
- if (mime_type)
- original_mime_type = svn_string_create(mime_type, scratch_pool);
- }
-
- modified_mime_type = apr_hash_get(modified_props, SVN_PROP_MIME_TYPE,
- APR_HASH_KEY_STRING);
- if (!file2_is_empty && !modified_mime_type)
- {
- const char *mime_type;
- SVN_ERR(svn_io_detect_mimetype2(&mime_type, local_abspath1,
- ctx->mimetypes_map, scratch_pool));
-
- if (mime_type)
- modified_mime_type = svn_string_create(mime_type, scratch_pool);
- }
- }
-
- /* Produce the diff. */
- if (file1_is_empty && !file2_is_empty)
- SVN_ERR(callbacks->file_added(NULL, NULL, NULL, path,
- local_abspath1, local_abspath2,
- /* ### TODO get real revision info
- * for versioned files? */
- SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
- original_mime_type ?
- original_mime_type->data : NULL,
- modified_mime_type ?
- modified_mime_type->data : NULL,
- /* ### TODO get copyfrom? */
- NULL, SVN_INVALID_REVNUM,
- prop_changes, original_props,
- diff_cmd_baton, scratch_pool));
- else if (!file1_is_empty && file2_is_empty)
- SVN_ERR(callbacks->file_deleted(NULL, NULL, path,
- local_abspath1, local_abspath2,
- original_mime_type ?
- original_mime_type->data : NULL,
- modified_mime_type ?
- modified_mime_type->data : NULL,
- original_props,
- diff_cmd_baton, scratch_pool));
- else
- SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, path,
- local_abspath1, local_abspath2,
- /* ### TODO get real revision info
- * for versioned files? */
- SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
- original_mime_type ?
- original_mime_type->data : NULL,
- modified_mime_type ?
- modified_mime_type->data : NULL,
- prop_changes, original_props,
- diff_cmd_baton, scratch_pool));
-
- return SVN_NO_ERROR;
-}
-
-struct arbitrary_diff_walker_baton {
- /* The root directories of the trees being compared. */
- const char *root1_abspath;
- const char *root2_abspath;
-
- /* TRUE if recursing within an added subtree of root2_abspath that
- * does not exist in root1_abspath. */
- svn_boolean_t recursing_within_added_subtree;
-
- /* TRUE if recursing within an administrative (.i.e. .svn) directory. */
- svn_boolean_t recursing_within_adm_dir;
-
- /* The absolute path of the adm dir if RECURSING_WITHIN_ADM_DIR is TRUE.
- * Else this is NULL.*/
- const char *adm_dir_abspath;
-
- /* A path to an empty file used for diffs that add/delete files. */
- const char *empty_file_abspath;
-
- const svn_wc_diff_callbacks4_t *callbacks;
- struct diff_cmd_baton *callback_baton;
- svn_client_ctx_t *ctx;
- apr_pool_t *pool;
-} arbitrary_diff_walker_baton;
-
-/* Forward declaration needed because this function has a cyclic
- * dependency with do_arbitrary_dirs_diff(). */
-static svn_error_t *
-arbitrary_diff_walker(void *baton, const char *local_abspath,
- const apr_finfo_t *finfo,
- apr_pool_t *scratch_pool);
-
-/* Produce a diff between two arbitrary directories at LOCAL_ABSPATH1 and
- * LOCAL_ABSPATH2, using the provided diff callbacks to show file changes
- * and, for versioned nodes, property changes.
- *
- * If ROOT_ABSPATH1 and ROOT_ABSPATH2 are not NULL, show paths in diffs
- * relative to these roots, rather than relative to LOCAL_ABSPATH1 and
- * LOCAL_ABSPATH2. This is needed when crawling a subtree that exists
- * only within LOCAL_ABSPATH2. */
-static svn_error_t *
-do_arbitrary_dirs_diff(const char *local_abspath1,
- const char *local_abspath2,
- const char *root_abspath1,
- const char *root_abspath2,
- const svn_wc_diff_callbacks4_t *callbacks,
- struct diff_cmd_baton *callback_baton,
- svn_client_ctx_t *ctx,
- apr_pool_t *scratch_pool)
-{
- apr_file_t *empty_file;
- svn_node_kind_t kind1;
-
- struct arbitrary_diff_walker_baton b;
-
- /* If LOCAL_ABSPATH1 is not a directory, crawl LOCAL_ABSPATH2 instead
- * and compare it to LOCAL_ABSPATH1, showing only additions.
- * This case can only happen during recursion from arbitrary_diff_walker(),
- * because do_arbitrary_nodes_diff() prevents this from happening at
- * the root of the comparison. */
- SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
- b.recursing_within_added_subtree = (kind1 != svn_node_dir);
-
- b.root1_abspath = root_abspath1 ? root_abspath1 : local_abspath1;
- b.root2_abspath = root_abspath2 ? root_abspath2 : local_abspath2;
- b.recursing_within_adm_dir = FALSE;
- b.adm_dir_abspath = NULL;
- b.callbacks = callbacks;
- b.callback_baton = callback_baton;
- b.ctx = ctx;
- b.pool = scratch_pool;
-
- SVN_ERR(svn_io_open_unique_file3(&empty_file, &b.empty_file_abspath,
- NULL, svn_io_file_del_on_pool_cleanup,
- scratch_pool, scratch_pool));
-
- SVN_ERR(svn_io_dir_walk2(b.recursing_within_added_subtree ? local_abspath2
- : local_abspath1,
- 0, arbitrary_diff_walker, &b, scratch_pool));
-
- return SVN_NO_ERROR;
-}
-
-/* An implementation of svn_io_walk_func_t.
- * Note: LOCAL_ABSPATH is the path being crawled and can be on either side
- * of the diff depending on baton->recursing_within_added_subtree. */
-static svn_error_t *
-arbitrary_diff_walker(void *baton, const char *local_abspath,
- const apr_finfo_t *finfo,
- apr_pool_t *scratch_pool)
-{
- struct arbitrary_diff_walker_baton *b = baton;
- const char *local_abspath1;
- const char *local_abspath2;
- svn_node_kind_t kind1;
- svn_node_kind_t kind2;
- const char *child_relpath;
- apr_hash_t *dirents1;
- apr_hash_t *dirents2;
- apr_hash_t *merged_dirents;
- apr_array_header_t *sorted_dirents;
- int i;
- apr_pool_t *iterpool;
-
- if (b->ctx->cancel_func)
- SVN_ERR(b->ctx->cancel_func(b->ctx->cancel_baton));
-
- if (finfo->filetype != APR_DIR)
- return SVN_NO_ERROR;
-
- if (b->recursing_within_adm_dir)
- {
- if (svn_dirent_skip_ancestor(b->adm_dir_abspath, local_abspath))
- return SVN_NO_ERROR;
- else
- {
- b->recursing_within_adm_dir = FALSE;
- b->adm_dir_abspath = NULL;
- }
- }
- else if (strcmp(svn_dirent_basename(local_abspath, scratch_pool),
- SVN_WC_ADM_DIR_NAME) == 0)
- {
- b->recursing_within_adm_dir = TRUE;
- b->adm_dir_abspath = apr_pstrdup(b->pool, local_abspath);
- return SVN_NO_ERROR;
- }
-
- if (b->recursing_within_added_subtree)
- child_relpath = svn_dirent_skip_ancestor(b->root2_abspath, local_abspath);
- else
- child_relpath = svn_dirent_skip_ancestor(b->root1_abspath, local_abspath);
- if (!child_relpath)
- return SVN_NO_ERROR;
-
- local_abspath1 = svn_dirent_join(b->root1_abspath, child_relpath,
- scratch_pool);
- SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
-
- local_abspath2 = svn_dirent_join(b->root2_abspath, child_relpath,
- scratch_pool);
- SVN_ERR(svn_io_check_resolved_path(local_abspath2, &kind2, scratch_pool));
-
- if (kind1 == svn_node_dir)
- SVN_ERR(svn_io_get_dirents3(&dirents1, local_abspath1,
- TRUE, /* only_check_type */
- scratch_pool, scratch_pool));
- else
- dirents1 = apr_hash_make(scratch_pool);
-
- if (kind2 == svn_node_dir)
- {
- apr_hash_t *original_props;
- apr_hash_t *modified_props;
- apr_array_header_t *prop_changes;
-
- /* Show any property changes for this directory. */
- SVN_ERR(get_props(&original_props, local_abspath1, b->ctx->wc_ctx,
- scratch_pool, scratch_pool));
- SVN_ERR(get_props(&modified_props, local_abspath2, b->ctx->wc_ctx,
- scratch_pool, scratch_pool));
- SVN_ERR(svn_prop_diffs(&prop_changes, modified_props, original_props,
- scratch_pool));
- if (prop_changes->nelts > 0)
- SVN_ERR(diff_props_changed(NULL, NULL, child_relpath,
- b->callback_baton->revnum1,
- b->callback_baton->revnum2,
- b->recursing_within_added_subtree,
- prop_changes, original_props, TRUE,
- b->callback_baton, scratch_pool));
-
- /* Read directory entries. */
- SVN_ERR(svn_io_get_dirents3(&dirents2, local_abspath2,
- TRUE, /* only_check_type */
- scratch_pool, scratch_pool));
- }
- else
- dirents2 = apr_hash_make(scratch_pool);
-
- /* Compare dirents1 to dirents2 and show added/deleted/changed files. */
- merged_dirents = apr_hash_merge(scratch_pool, dirents1, dirents2,
- NULL, NULL);
- sorted_dirents = svn_sort__hash(merged_dirents,
- svn_sort_compare_items_as_paths,
- scratch_pool);
- iterpool = svn_pool_create(scratch_pool);
- for (i = 0; i < sorted_dirents->nelts; i++)
- {
- svn_sort__item_t elt = APR_ARRAY_IDX(sorted_dirents, i, svn_sort__item_t);
- const char *name = elt.key;
- svn_io_dirent2_t *dirent1;
- svn_io_dirent2_t *dirent2;
- const char *child1_abspath;
- const char *child2_abspath;
-
- svn_pool_clear(iterpool);
-
- if (b->ctx->cancel_func)
- SVN_ERR(b->ctx->cancel_func(b->ctx->cancel_baton));
-
- if (strcmp(name, SVN_WC_ADM_DIR_NAME) == 0)
- continue;
-
- dirent1 = apr_hash_get(dirents1, name, APR_HASH_KEY_STRING);
- if (!dirent1)
- {
- dirent1 = svn_io_dirent2_create(iterpool);
- dirent1->kind = svn_node_none;
- }
- dirent2 = apr_hash_get(dirents2, name, APR_HASH_KEY_STRING);
- if (!dirent2)
- {
- dirent2 = svn_io_dirent2_create(iterpool);
- dirent2->kind = svn_node_none;
- }
-
- child1_abspath = svn_dirent_join(local_abspath1, name, iterpool);
[... 1662 lines stripped ...]