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 ...]