aboutsummaryrefslogtreecommitdiff
path: root/subversion/libsvn_client/diff_local.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/diff_local.c')
-rw-r--r--subversion/libsvn_client/diff_local.c633
1 files changed, 633 insertions, 0 deletions
diff --git a/subversion/libsvn_client/diff_local.c b/subversion/libsvn_client/diff_local.c
new file mode 100644
index 000000000000..cc7184f12cba
--- /dev/null
+++ b/subversion/libsvn_client/diff_local.c
@@ -0,0 +1,633 @@
+/*
+ * diff_local.c: comparing local trees with each other
+ *
+ * ====================================================================
+ * 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_hash.h"
+#include "svn_types.h"
+#include "svn_wc.h"
+#include "svn_diff.h"
+#include "svn_client.h"
+#include "svn_string.h"
+#include "svn_error.h"
+#include "svn_dirent_uri.h"
+#include "svn_io.h"
+#include "svn_pools.h"
+#include "svn_props.h"
+#include "svn_sorts.h"
+#include "svn_subst.h"
+#include "client.h"
+
+#include "private/svn_wc_private.h"
+
+#include "svn_private_config.h"
+
+
+/* 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)
+ {
+ svn_error_clear(err);
+ *props = apr_hash_make(result_pool);
+
+ /* ### Apply autoprops, like 'svn add' would? */
+ }
+ 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,
+ void *diff_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));
+
+ /* 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));
+
+ /* Try to determine the mime-type of each file. */
+ original_mime_type = svn_hash_gets(original_props, SVN_PROP_MIME_TYPE);
+ 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 = svn_hash_gets(modified_props, SVN_PROP_MIME_TYPE);
+ 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_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
+ {
+ svn_stream_t *file1;
+ svn_stream_t *file2;
+ svn_boolean_t same;
+ svn_string_t *val;
+ /* We have two files, which may or may not be the same.
+
+ ### Our caller assumes that we should ignore symlinks here and
+ handle them as normal paths. Perhaps that should change?
+ */
+ SVN_ERR(svn_stream_open_readonly(&file1, local_abspath1, scratch_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_stream_open_readonly(&file2, local_abspath2, scratch_pool,
+ scratch_pool));
+
+ /* Wrap with normalization, etc. if necessary */
+ if (original_props)
+ {
+ val = svn_hash_gets(original_props, SVN_PROP_EOL_STYLE);
+
+ if (val)
+ {
+ svn_subst_eol_style_t style;
+ const char *eol;
+ svn_subst_eol_style_from_value(&style, &eol, val->data);
+
+ /* ### Ignoring keywords */
+ if (eol)
+ file1 = svn_subst_stream_translated(file1, eol, TRUE,
+ NULL, FALSE,
+ scratch_pool);
+ }
+ }
+
+ if (modified_props)
+ {
+ val = svn_hash_gets(modified_props, SVN_PROP_EOL_STYLE);
+
+ if (val)
+ {
+ svn_subst_eol_style_t style;
+ const char *eol;
+ svn_subst_eol_style_from_value(&style, &eol, val->data);
+
+ /* ### Ignoring keywords */
+ if (eol)
+ file2 = svn_subst_stream_translated(file2, eol, TRUE,
+ NULL, FALSE,
+ scratch_pool);
+ }
+ }
+
+ SVN_ERR(svn_stream_contents_same2(&same, file1, file2, scratch_pool));
+
+ if (! same || prop_changes->nelts > 0)
+ {
+ /* ### We should probably pass the normalized data we created using
+ the subst streams as that is what diff users expect */
+ SVN_ERR(callbacks->file_changed(NULL, NULL, NULL, path,
+ same ? NULL : local_abspath1,
+ same ? NULL : local_abspath2,
+ /* ### TODO get real revision info
+ * for versioned files? */
+ SVN_INVALID_REVNUM /* rev1 */,
+ SVN_INVALID_REVNUM /* rev2 */,
+ 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;
+
+ /* 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;
+ void *diff_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);
+
+/* Another forward declaration. */
+static svn_error_t *
+arbitrary_diff_this_dir(struct arbitrary_diff_walker_baton *b,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool);
+
+/* Produce a diff of depth DEPTH 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,
+ svn_depth_t depth,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *diff_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.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));
+
+ if (depth <= svn_depth_immediates)
+ SVN_ERR(arbitrary_diff_this_dir(&b, local_abspath1, depth, scratch_pool));
+ else if (depth == svn_depth_infinity)
+ 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;
+}
+
+/* Produce a diff of depth DEPTH for the directory at LOCAL_ABSPATH,
+ * using information from the arbitrary_diff_walker_baton B.
+ * 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_this_dir(struct arbitrary_diff_walker_baton *b,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *scratch_pool)
+{
+ 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->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 (svn_wc_is_adm_dir(svn_dirent_basename(local_abspath, NULL),
+ scratch_pool))
+ {
+ 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 (depth > svn_depth_empty)
+ {
+ 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(b->callbacks->dir_props_changed(NULL, NULL, child_relpath,
+ FALSE /* was_added */,
+ prop_changes, original_props,
+ b->diff_baton,
+ scratch_pool));
+
+ if (depth > svn_depth_empty)
+ {
+ /* Read directory entries. */
+ SVN_ERR(svn_io_get_dirents3(&dirents2, local_abspath2,
+ TRUE, /* only_check_type */
+ scratch_pool, scratch_pool));
+ }
+ }
+ else if (depth > svn_depth_empty)
+ dirents2 = apr_hash_make(scratch_pool);
+
+ if (depth <= svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* 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 = svn_hash_gets(dirents1, name);
+ if (!dirent1)
+ {
+ dirent1 = svn_io_dirent2_create(iterpool);
+ dirent1->kind = svn_node_none;
+ }
+ dirent2 = svn_hash_gets(dirents2, name);
+ if (!dirent2)
+ {
+ dirent2 = svn_io_dirent2_create(iterpool);
+ dirent2->kind = svn_node_none;
+ }
+
+ child1_abspath = svn_dirent_join(local_abspath1, name, iterpool);
+ child2_abspath = svn_dirent_join(local_abspath2, name, iterpool);
+
+ if (dirent1->special)
+ SVN_ERR(svn_io_check_resolved_path(child1_abspath, &dirent1->kind,
+ iterpool));
+ if (dirent2->special)
+ SVN_ERR(svn_io_check_resolved_path(child1_abspath, &dirent2->kind,
+ iterpool));
+
+ if (dirent1->kind == svn_node_dir &&
+ dirent2->kind == svn_node_dir)
+ {
+ if (depth == svn_depth_immediates)
+ {
+ /* Not using the walker, so show property diffs on these dirs. */
+ SVN_ERR(do_arbitrary_dirs_diff(child1_abspath, child2_abspath,
+ b->root1_abspath, b->root2_abspath,
+ svn_depth_empty,
+ b->callbacks, b->diff_baton,
+ b->ctx, iterpool));
+ }
+ else
+ {
+ /* Either the walker will visit these directories (with
+ * depth=infinity) and they will be processed as 'this dir'
+ * later, or we're showing file children only (depth=files). */
+ continue;
+ }
+
+ }
+
+ /* Files that exist only in dirents1. */
+ if (dirent1->kind == svn_node_file &&
+ (dirent2->kind == svn_node_dir || dirent2->kind == svn_node_none))
+ SVN_ERR(do_arbitrary_files_diff(child1_abspath, b->empty_file_abspath,
+ svn_relpath_join(child_relpath, name,
+ iterpool),
+ FALSE, TRUE, NULL,
+ b->callbacks, b->diff_baton,
+ b->ctx, iterpool));
+
+ /* Files that exist only in dirents2. */
+ if (dirent2->kind == svn_node_file &&
+ (dirent1->kind == svn_node_dir || dirent1->kind == svn_node_none))
+ {
+ apr_hash_t *original_props;
+
+ SVN_ERR(get_props(&original_props, child1_abspath, b->ctx->wc_ctx,
+ scratch_pool, scratch_pool));
+ SVN_ERR(do_arbitrary_files_diff(b->empty_file_abspath, child2_abspath,
+ svn_relpath_join(child_relpath, name,
+ iterpool),
+ TRUE, FALSE, original_props,
+ b->callbacks, b->diff_baton,
+ b->ctx, iterpool));
+ }
+
+ /* Files that exist in dirents1 and dirents2. */
+ if (dirent1->kind == svn_node_file && dirent2->kind == svn_node_file)
+ SVN_ERR(do_arbitrary_files_diff(child1_abspath, child2_abspath,
+ svn_relpath_join(child_relpath, name,
+ iterpool),
+ FALSE, FALSE, NULL,
+ b->callbacks, b->diff_baton,
+ b->ctx, scratch_pool));
+
+ /* Directories that only exist in dirents2. These aren't crawled
+ * by this walker so we have to crawl them separately. */
+ if (depth > svn_depth_files &&
+ dirent2->kind == svn_node_dir &&
+ (dirent1->kind == svn_node_file || dirent1->kind == svn_node_none))
+ SVN_ERR(do_arbitrary_dirs_diff(child1_abspath, child2_abspath,
+ b->root1_abspath, b->root2_abspath,
+ depth <= svn_depth_immediates
+ ? svn_depth_empty
+ : svn_depth_infinity ,
+ b->callbacks, b->diff_baton,
+ b->ctx, iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ 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;
+
+ if (b->ctx->cancel_func)
+ SVN_ERR(b->ctx->cancel_func(b->ctx->cancel_baton));
+
+ if (finfo->filetype != APR_DIR)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(arbitrary_diff_this_dir(b, local_abspath, svn_depth_infinity,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_client__arbitrary_nodes_diff(const char *local_abspath1,
+ const char *local_abspath2,
+ svn_depth_t depth,
+ const svn_wc_diff_callbacks4_t *callbacks,
+ void *diff_baton,
+ svn_client_ctx_t *ctx,
+ apr_pool_t *scratch_pool)
+{
+ svn_node_kind_t kind1;
+ svn_node_kind_t kind2;
+
+ SVN_ERR(svn_io_check_resolved_path(local_abspath1, &kind1, scratch_pool));
+ SVN_ERR(svn_io_check_resolved_path(local_abspath2, &kind2, scratch_pool));
+
+ if (kind1 != kind2)
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is not the same node kind as '%s'"),
+ local_abspath1, local_abspath2);
+
+ if (depth == svn_depth_unknown)
+ depth = svn_depth_infinity;
+
+ if (kind1 == svn_node_file)
+ SVN_ERR(do_arbitrary_files_diff(local_abspath1, local_abspath2,
+ svn_dirent_basename(local_abspath1,
+ scratch_pool),
+ FALSE, FALSE, NULL,
+ callbacks, diff_baton,
+ ctx, scratch_pool));
+ else if (kind1 == svn_node_dir)
+ SVN_ERR(do_arbitrary_dirs_diff(local_abspath1, local_abspath2,
+ NULL, NULL, depth,
+ callbacks, diff_baton,
+ ctx, scratch_pool));
+ else
+ return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+ _("'%s' is not a file or directory"),
+ kind1 == svn_node_none ?
+ local_abspath1 : local_abspath2);
+ return SVN_NO_ERROR;
+}