You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2010/09/15 21:32:38 UTC

svn commit: r997472 [9/41] - in /subversion/branches/py-tests-as-modules: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ contrib/server-side/ notes/ notes/tree-conflicts/ notes/wc-ng/ subversion/bindings/javahl/native/ subversio...

Modified: subversion/branches/py-tests-as-modules/subversion/libsvn_client/diff.c
URL: http://svn.apache.org/viewvc/subversion/branches/py-tests-as-modules/subversion/libsvn_client/diff.c?rev=997472&r1=997471&r2=997472&view=diff
==============================================================================
--- subversion/branches/py-tests-as-modules/subversion/libsvn_client/diff.c (original)
+++ subversion/branches/py-tests-as-modules/subversion/libsvn_client/diff.c Wed Sep 15 19:32:26 2010
@@ -190,38 +190,453 @@ maybe_append_eol(const svn_string_t *tok
     }
 }
 
+/* Adjust PATH to be relative to the repository root beneath ORIG_TARGET,
+ * using RA_SESSION and WC_CTX, and return the result in *ADJUSTED_PATH.
+ * ORIG_TARGET is one of the original targets passed to the diff command,
+ * and may be used to derive leading path components missing from PATH.
+ * WC_ROOT_ABSPATH is the absolute path to the root directory of a working
+ * copy involved in a repos-wc diff, and may be NULL.
+ * Do all allocations in POOL. */
+static svn_error_t *
+adjust_relative_to_repos_root(const char **adjusted_path,
+                              const char *path,
+                              const char *orig_target,
+                              svn_ra_session_t *ra_session,
+                              svn_wc_context_t *wc_ctx,
+                              const char *wc_root_abspath,
+                              apr_pool_t *pool)
+{
+  const char *local_abspath;
+  const char *orig_relpath;
+  const char *child_relpath;
+
+  if (! ra_session)
+    {
+      /* 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, path, pool));
+      SVN_ERR(svn_wc__node_get_repos_relpath(adjusted_path, wc_ctx,
+                                             local_abspath, pool, pool));
+      return SVN_NO_ERROR;
+    }
+
+  /* Now deal with the repos-repos and repos-wc diff cases.
+   * We need to make PATH appear as a child of ORIG_TARGET.
+   * ORIG_TARGET is either a URL or a path to a working copy. First,
+   * find out what ORIG_TARGET looks like relative to the repository root.*/
+  if (svn_path_is_url(orig_target))
+    SVN_ERR(svn_ra_get_path_relative_to_root(ra_session,
+                                             &orig_relpath,
+                                             orig_target, pool));
+  else
+    {
+      const char *orig_abspath;
+
+      SVN_ERR(svn_dirent_get_absolute(&orig_abspath, orig_target, pool));
+      SVN_ERR(svn_wc__node_get_repos_relpath(&orig_relpath, wc_ctx,
+                                             orig_abspath, pool, pool));
+    }
+
+  /* PATH is either a child of the working copy involved in the diff (in
+   * the repos-wc diff case), or it's a relative path we can readily use
+   * (in either of the repos-repos and repos-wc diff cases). */
+  child_relpath = NULL;
+  if (wc_root_abspath)
+    {
+      SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
+      child_relpath = svn_dirent_is_child(wc_root_abspath, local_abspath, pool);
+    }
+  if (child_relpath == NULL)
+    child_relpath = path;
+
+  *adjusted_path = svn_relpath_join(orig_relpath, child_relpath, pool);
+
+  return SVN_NO_ERROR;
+}
+
+/* Adjust PATH, ORIG_PATH_1 and ORIG_PATH_2, representing the changed file
+ * 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. All
+ * allocations are done in POOL. */
+static svn_error_t *
+adjust_paths_for_diff_labels(const char **path,
+                             const char **orig_path_1,
+                             const char **orig_path_2,
+                             const char *relative_to_dir,
+                             apr_pool_t *pool)
+{
+  apr_size_t len;
+  const char *new_path = *path;
+  const char *new_path1 = *orig_path_1;
+  const char *new_path2 = *orig_path_2;
+
+  /* ### 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. */
+
+  len = strlen(svn_dirent_get_longest_ancestor(new_path1, new_path2, pool));
+  new_path1 = new_path1 + len;
+  new_path2 = 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_path1[0] == '\0')
+    new_path1 = apr_psprintf(pool, "%s", new_path);
+  else if (new_path1[0] == '/')
+    new_path1 = apr_psprintf(pool, "%s\t(...%s)", new_path, new_path1);
+  else
+    new_path1 = apr_psprintf(pool, "%s\t(.../%s)", new_path, new_path1);
+
+  if (new_path2[0] == '\0')
+    new_path2 = apr_psprintf(pool, "%s", new_path);
+  else if (new_path2[0] == '/')
+    new_path2 = apr_psprintf(pool, "%s\t(...%s)", new_path, new_path2);
+  else
+    new_path2 = apr_psprintf(pool, "%s\t(.../%s)", new_path, new_path2);
+
+  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,
+                                                   pool);
+
+      if (child_path)
+        new_path = child_path;
+      else if (!svn_path_compare_paths(relative_to_dir, new_path))
+        new_path = ".";
+      else
+        return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir);
+
+      child_path = svn_dirent_is_child(relative_to_dir, new_path1, pool);
+
+      if (child_path)
+        new_path1 = child_path;
+      else if (!svn_path_compare_paths(relative_to_dir, new_path1))
+        new_path1 = ".";
+      else
+        return MAKE_ERR_BAD_RELATIVE_PATH(new_path1, relative_to_dir);
+
+      child_path = svn_dirent_is_child(relative_to_dir, new_path2, pool);
+
+      if (child_path)
+        new_path2 = child_path;
+      else if (!svn_path_compare_paths(relative_to_dir, new_path2))
+        new_path2 = ".";
+      else
+        return MAKE_ERR_BAD_RELATIVE_PATH(new_path2, relative_to_dir);
+    }
+  *path = new_path;
+  *orig_path_1 = new_path1;
+  *orig_path_2 = new_path2;
+
+  return SVN_NO_ERROR;
+}
+
+
+/* Generate a label for the diff output for file PATH at revision REVNUM.
+   If REVNUM is invalid then it is assumed to be the current working
+   copy.  Assumes the paths are already in the desired style (local
+   vs internal).  Allocate the label in POOL. */
+static const char *
+diff_label(const char *path,
+           svn_revnum_t revnum,
+           apr_pool_t *pool)
+{
+  const char *label;
+  if (revnum != SVN_INVALID_REVNUM)
+    label = apr_psprintf(pool, _("%s\t(revision %ld)"), path, revnum);
+  else
+    label = apr_psprintf(pool, _("%s\t(working copy)"), path);
+
+  return label;
+}
+
+/* Print a git diff header for an addition within a diff between PATH1 and
+ * PATH2 to the stream OS using HEADER_ENCODING.
+ * All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_added(svn_stream_t *os, const char *header_encoding, 
+                            const char *path1, const char *path2,
+                            apr_pool_t *result_pool)
+{
+  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+                                      "diff --git a/%s b/%s%s",
+                                      path1, path2, APR_EOL_STR));
+  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+                                      "new file mode 10644" APR_EOL_STR));
+  return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a deletion within a diff between PATH1 and
+ * PATH2 to the stream OS using HEADER_ENCODING.
+ * All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_deleted(svn_stream_t *os, const char *header_encoding, 
+                              const char *path1, const char *path2,
+                              apr_pool_t *result_pool)
+{
+  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+                                      "diff --git a/%s b/%s%s",
+                                      path1, path2, APR_EOL_STR));
+  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+                                      "deleted file mode 10644"
+                                      APR_EOL_STR));
+  return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a copy from COPYFROM_PATH to PATH to the stream
+ * OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_copied(svn_stream_t *os, const char *header_encoding, 
+                             const char *copyfrom_path, 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,
+                                      "copy from %s%s", copyfrom_path,
+                                      APR_EOL_STR));
+  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+                                      "copy to %s%s", path, APR_EOL_STR));
+  return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a rename from COPYFROM_PATH to PATH to the
+ * stream OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_renamed(svn_stream_t *os, const char *header_encoding,
+                              const char *copyfrom_path, const char *path,
+                              apr_pool_t *result_pool)
+{
+  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+                                      "diff --git a/%s b/%s%s",
+                                      copyfrom_path, path, APR_EOL_STR));
+  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+                                      "rename from %s%s", copyfrom_path, 
+                                      APR_EOL_STR));
+  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+                                      "rename to %s%s", path, APR_EOL_STR));
+  return SVN_NO_ERROR;
+}
+
+/* Print a git diff header for a modification within a diff between PATH1 and
+ * PATH2 to the stream OS using HEADER_ENCODING.
+ * All allocations are done in RESULT_POOL. */
+static svn_error_t *
+print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding, 
+                               const char *path1, const char *path2,
+                               apr_pool_t *result_pool)
+{
+  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
+                                      "diff --git a/%s b/%s%s",
+                                      path1, path2, APR_EOL_STR));
+  return SVN_NO_ERROR;
+}
+
+/* Print a git diff header showing the OPERATION to the stream OS using
+ * HEADER_ENCODING. Return suitable diff labels for the git diff in *LABEL1
+ * and *LABEL2. PATH is the path being diffed, ORIG_TARGET1 and ORIG_TARGET2
+ * are the paths passed to the original diff command. REV1 and REV2 are
+ * revisions being diffed. COPYFROM_PATH indicates where the diffed item
+ * was copied from. RA_SESSION and WC_CTX are used to adjust paths in the
+ * headers to be relative to the repository root.
+ * WC_ROOT_ABSPATH is the absolute path to the root directory of a working
+ * copy involved in a repos-wc diff, and may be NULL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+print_git_diff_header(svn_stream_t *os,
+                      const char **label1, const char **label2,
+                      svn_diff_operation_kind_t operation,
+                      const char *path,
+                      const char *path1,
+                      const char *path2,
+                      svn_revnum_t rev1,
+                      svn_revnum_t rev2,
+                      const char *copyfrom_path,
+                      const char *header_encoding,
+                      svn_ra_session_t *ra_session,
+                      svn_wc_context_t *wc_ctx,
+                      const char *wc_root_abspath,
+                      apr_pool_t *scratch_pool)
+{
+  const char *repos_relpath1;
+  const char *repos_relpath2;
+
+  SVN_ERR(adjust_relative_to_repos_root(&repos_relpath1, path, path1,
+                                        ra_session, wc_ctx,
+                                        wc_root_abspath,
+                                        scratch_pool));
+  SVN_ERR(adjust_relative_to_repos_root(&repos_relpath2, path, path2,
+                                        ra_session, wc_ctx,
+                                        wc_root_abspath,
+                                        scratch_pool));
+
+  if (operation == svn_diff_op_deleted)
+    {
+      SVN_ERR(print_git_diff_header_deleted(os, header_encoding,
+                                            repos_relpath1, repos_relpath2,
+                                            scratch_pool));
+      *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
+                           rev1, scratch_pool);
+      *label2 = diff_label("/dev/null", rev2, scratch_pool);
+
+    }
+  else if (operation == svn_diff_op_copied)
+    {
+      SVN_ERR(print_git_diff_header_copied(os, header_encoding,
+                                           copyfrom_path, repos_relpath2,
+                                           scratch_pool));
+      *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
+                           rev1, scratch_pool);
+      *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+                           rev2, scratch_pool);
+    }
+  else if (operation == svn_diff_op_added)
+    {
+      SVN_ERR(print_git_diff_header_added(os, header_encoding,
+                                          repos_relpath1, repos_relpath2,
+                                          scratch_pool));
+      *label1 = diff_label("/dev/null", rev1, scratch_pool);
+      *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+                           rev2, scratch_pool);
+    }
+  else if (operation == svn_diff_op_modified)
+    {
+      SVN_ERR(print_git_diff_header_modified(os, header_encoding,
+                                             repos_relpath1, repos_relpath2,
+                                             scratch_pool));
+      *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
+                           rev1, scratch_pool);
+      *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+                           rev2, scratch_pool);
+    }
+  else if (operation == svn_diff_op_moved)
+    {
+      SVN_ERR(print_git_diff_header_renamed(os, header_encoding,
+                                            copyfrom_path, repos_relpath2,
+                                            scratch_pool));
+      *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
+                           rev1, scratch_pool);
+      *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
+                           rev2, scratch_pool);
+    }
+
+  /* ### Print git headers for renames, too, in the future. */
+
+  return SVN_NO_ERROR;
+}
+
 /* A helper func that writes out verbal descriptions of property diffs
    to FILE.   Of course, the apr_file_t will probably be the 'outfile'
-   passed to svn_client_diff5, which is probably stdout. */
+   passed to svn_client_diff5, 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.
+   
+   WC_ROOT_ABSPATH is the absolute path to the root directory of a working
+   copy involved in a repos-wc diff, and may be NULL. */
 static svn_error_t *
 display_prop_diffs(const apr_array_header_t *propchanges,
                    apr_hash_t *original_props,
                    const char *path,
+                   const char *orig_path1,
+                   const char *orig_path2,
+                   svn_revnum_t rev1,
+                   svn_revnum_t rev2,
                    const char *encoding,
                    apr_file_t *file,
                    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,
+                   const char *wc_root_abspath,
                    apr_pool_t *pool)
 {
   int i;
+  const char *path1 = apr_pstrdup(pool, orig_path1);
+  const char *path2 = apr_pstrdup(pool, orig_path2);
 
-  if (relative_to_dir)
+  /* If we're creating a diff on the wc root, path would be empty. */
+  if (path[0] == '\0')
+    path = apr_psprintf(pool, ".");
+
+  if (use_git_diff_format)
     {
-      /* Possibly adjust the path shown in the output (see issue #2723). */
-      const char *child_path = svn_dirent_is_child(relative_to_dir, path,
-                                                   pool);
+      SVN_ERR(adjust_relative_to_repos_root(&path1, path, orig_path1,
+                                            ra_session, wc_ctx,
+                                            wc_root_abspath,
+                                            pool));
+      SVN_ERR(adjust_relative_to_repos_root(&path2, path, orig_path2,
+                                            ra_session, wc_ctx,
+                                            wc_root_abspath,
+                                            pool));
+    }
 
-      if (child_path)
-        path = child_path;
-      else if (!svn_path_compare_paths(relative_to_dir, path))
-        path = ".";
-      else
-        return MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir);
+  if (show_diff_header)
+    {
+      const char *label1;
+      const char *label2;
+      const char *adjusted_path1 = apr_pstrdup(pool, path1);
+      const char *adjusted_path2 = apr_pstrdup(pool, path2);
+
+      SVN_ERR(adjust_paths_for_diff_labels(&path, &adjusted_path1,
+                                           &adjusted_path2,
+                                           relative_to_dir, pool));
+
+      label1 = diff_label(adjusted_path1, rev1, pool);
+      label2 = diff_label(adjusted_path2, rev2, pool);
+
+      /* ### Should we show the paths in platform specific format,
+       * ### diff_content_changed() does not! */
+
+      SVN_ERR(file_printf_from_utf8(file, encoding,
+                                    "Index: %s" APR_EOL_STR 
+                                    "%s" APR_EOL_STR, 
+                                    path, equal_string));
+
+      if (use_git_diff_format)
+        {
+          svn_stream_t *os;
+
+          os = svn_stream_from_aprfile2(file, TRUE, pool);
+          SVN_ERR(print_git_diff_header(os, &label1, &label2,
+                                        svn_diff_op_modified, path,
+                                        orig_path1, orig_path2,
+                                        rev1, rev2, NULL,
+                                        encoding, ra_session, wc_ctx,
+                                        wc_root_abspath, pool));
+          SVN_ERR(svn_stream_close(os));
+        }
+
+      SVN_ERR(file_printf_from_utf8(file, encoding,
+                                          "--- %s" APR_EOL_STR
+                                          "+++ %s" APR_EOL_STR,
+                                          label1,
+                                          label2));
     }
 
   SVN_ERR(file_printf_from_utf8(file, encoding,
                                 _("%sProperty changes on: %s%s"),
                                 APR_EOL_STR,
-                                svn_dirent_local_style(path, pool),
+                                use_git_diff_format ? path1 : path,
                                 APR_EOL_STR));
 
   SVN_ERR(file_printf_from_utf8(file, encoding, "%s" APR_EOL_STR,
@@ -306,93 +721,6 @@ display_prop_diffs(const apr_array_heade
   return SVN_NO_ERROR;
 }
 
-#ifdef SVN_EXPERIMENTAL_PATCH
-
-/*
- * Print a git diff header for PATH 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 *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",
-                                      path, path, APR_EOL_STR));
-  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
-                                      "new file mode 10644" APR_EOL_STR));
-  return SVN_NO_ERROR;
-}
-
-/*
- * Print a git diff header for PATH 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 *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",
-                                      path, path, APR_EOL_STR));
-  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
-                                      "deleted file mode 10644"
-                                      APR_EOL_STR));
-  return SVN_NO_ERROR;
-}
-
-/*
- * Print a git diff header for PATH to the stream OS using HEADER_ENCODING.
- * COPYFROM_PATH is the origin of the operation.  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 *path, const char *copyfrom_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,
-                                      "copy from %s%s", copyfrom_path,
-                                      APR_EOL_STR));
-  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
-                                      "copy to %s%s", path, APR_EOL_STR));
-  return SVN_NO_ERROR;
-}
-
-/*
- * Print a git diff header for PATH to the stream OS using HEADER_ENCODING.
- * COPYFROM_PATH is the origin of the operation.  All allocations are done
- * in RESULT_POOL. */
-static svn_error_t *
-print_git_diff_header_moved(svn_stream_t *os, const char *header_encoding,
-                            const char *path, const char *copyfrom_path,
-                            apr_pool_t *result_pool)
-{
-  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
-                                      "diff --git a/%s b/%s%s",
-                                      copyfrom_path, path, APR_EOL_STR));
-  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
-                                      "rename from %s%s", copyfrom_path, 
-                                      APR_EOL_STR));
-  SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
-                                      "rename to %s%s", path, APR_EOL_STR));
-  return SVN_NO_ERROR;
-}
-
-/*
- * Print a git diff header for PATH to the stream OS using HEADER_ENCODING.
- * All allocations are done in RESULT_POOL. */
-static svn_error_t *
-print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding, 
-                               const char *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",
-                                      path, path, APR_EOL_STR));
-  return SVN_NO_ERROR;
-}
-#endif
-
 /*-----------------------------------------------------------------*/
 
 /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
@@ -451,25 +779,25 @@ struct diff_cmd_baton {
   /* The directory that diff target paths should be considered as
      relative to for output generation (see issue #2723). */
   const char *relative_to_dir;
-};
 
-/* 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);
+  /* Whether we're producing a git-style diff. */
+  svn_boolean_t use_git_diff_format;
+
+  svn_wc_context_t *wc_ctx;
+
+  /* The RA session used during diffs involving the repository. */
+  svn_ra_session_t *ra_session;
+
+  /* During a repos-wc diff, this is the absolute path to the root
+   * directory of the working copy involved in the diff. */
+  const char *wc_root_abspath;
+
+  /* A hashtable using the visited paths as keys. 
+   * ### This is needed for us to know if we need to print a diff header for
+   * ### a path that has property changes. */
+  apr_hash_t *visited_paths;
+};
 
-  return label;
-}
 
 /* An svn_wc_diff_callbacks4_t function.  Used for both file and directory
    property diffs. */
@@ -485,16 +813,42 @@ diff_props_changed(const char *local_dir
 {
   struct diff_cmd_baton *diff_cmd_baton = diff_baton;
   apr_array_header_t *props;
+  svn_boolean_t show_diff_header;
   apr_pool_t *subpool = svn_pool_create(diff_cmd_baton->pool);
 
   SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, subpool));
 
+  if (apr_hash_get(diff_cmd_baton->visited_paths, path, APR_HASH_KEY_STRING))
+    show_diff_header = FALSE;
+  else
+    show_diff_header = TRUE;
+
   if (props->nelts > 0)
-    SVN_ERR(display_prop_diffs(props, original_props, path,
-                               diff_cmd_baton->header_encoding,
-                               diff_cmd_baton->outfile,
-                               diff_cmd_baton->relative_to_dir,
-                               subpool));
+    {
+      /* 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, path,
+                                 diff_cmd_baton->orig_path_1,
+                                 diff_cmd_baton->orig_path_2,
+                                 diff_cmd_baton->revnum1,
+                                 diff_cmd_baton->revnum2,
+                                 diff_cmd_baton->header_encoding,
+                                 diff_cmd_baton->outfile,
+                                 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,
+                                 diff_cmd_baton->wc_root_abspath,
+                                 subpool));
+
+      /* We've printed the diff header so now we can mark the path as
+       * visited. */
+      if (show_diff_header)
+        apr_hash_set(diff_cmd_baton->visited_paths, path,
+                     APR_HASH_KEY_STRING, path);
+    }
 
   if (state)
     *state = svn_wc_notify_state_unknown;
@@ -518,6 +872,7 @@ diff_content_changed(const char *path,
                      const char *mimetype1,
                      const char *mimetype2,
                      svn_diff_operation_kind_t operation,
+                     const char *copyfrom_path,
                      void *diff_baton)
 {
   struct diff_cmd_baton *diff_cmd_baton = diff_baton;
@@ -529,80 +884,17 @@ diff_content_changed(const char *path,
   const char *label1, *label2;
   svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
   const char *path1, *path2;
-  apr_size_t len;
 
   /* Get a stream from our output file. */
   os = svn_stream_from_aprfile2(diff_cmd_baton->outfile, TRUE, subpool);
 
   /* Generate the diff headers. */
 
-  /* ### 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. */
-
-  path1 = diff_cmd_baton->orig_path_1;
-  path2 = diff_cmd_baton->orig_path_2;
-  len = strlen(svn_dirent_get_longest_ancestor(path1, path2, subpool));
-  path1 = path1 + len;
-  path2 = 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 (path1[0] == '\0')
-    path1 = apr_psprintf(subpool, "%s", path);
-  else if (path1[0] == '/')
-    path1 = apr_psprintf(subpool, "%s\t(...%s)", path, path1);
-  else
-    path1 = apr_psprintf(subpool, "%s\t(.../%s)", path, path1);
-
-  if (path2[0] == '\0')
-    path2 = apr_psprintf(subpool, "%s", path);
-  else if (path2[0] == '/')
-    path2 = apr_psprintf(subpool, "%s\t(...%s)", path, path2);
-  else
-    path2 = apr_psprintf(subpool, "%s\t(.../%s)", path, path2);
-
-  if (diff_cmd_baton->relative_to_dir)
-    {
-      /* Possibly adjust the paths shown in the output (see issue #2723). */
-      const char *child_path = svn_dirent_is_child(rel_to_dir, path, subpool);
-
-      if (child_path)
-        path = child_path;
-      else if (!svn_path_compare_paths(rel_to_dir, path))
-        path = ".";
-      else
-        return MAKE_ERR_BAD_RELATIVE_PATH(path, rel_to_dir);
+  path1 = apr_pstrdup(subpool, diff_cmd_baton->orig_path_1);
+  path2 = apr_pstrdup(subpool, diff_cmd_baton->orig_path_2);
 
-      child_path = svn_dirent_is_child(rel_to_dir, path1, subpool);
-
-      if (child_path)
-        path1 = child_path;
-      else if (!svn_path_compare_paths(rel_to_dir, path1))
-        path1 = ".";
-      else
-        return MAKE_ERR_BAD_RELATIVE_PATH(path1, rel_to_dir);
-
-      child_path = svn_dirent_is_child(rel_to_dir, path2, subpool);
-
-      if (child_path)
-        path2 = child_path;
-      else if (!svn_path_compare_paths(rel_to_dir, path2))
-        path2 = ".";
-      else
-        return MAKE_ERR_BAD_RELATIVE_PATH(path2, rel_to_dir);
-    }
+  SVN_ERR(adjust_paths_for_diff_labels(&path, &path1, &path2,
+                                       rel_to_dir, subpool));
 
   label1 = diff_label(path1, rev1, subpool);
   label2 = diff_label(path2, rev2, subpool);
@@ -687,60 +979,43 @@ diff_content_changed(const char *path,
                                    diff_cmd_baton->options.for_internal,
                                    subpool));
 
-      if (svn_diff_contains_diffs(diff) || diff_cmd_baton->force_empty)
+      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
                   (os, diff_cmd_baton->header_encoding, subpool,
                    "Index: %s" APR_EOL_STR "%s" APR_EOL_STR,
                    path, equal_string));
-#ifdef SVN_EXPERIMENTAL_PATCH
 
-          /* Add git headers and adjust the labels. 
-           * ### Once we're using the git format everywhere, we can create
-           * ### one func that sets the correct labels in one place. */
-          if (operation == svn_diff_op_deleted)
-            {
-              SVN_ERR(print_git_diff_header_deleted(
-                                            os, 
-                                            diff_cmd_baton->header_encoding,
-                                            path, subpool));
-
-              label1 = diff_label(apr_psprintf(subpool, "a/%s", path1), rev1,
-                                  subpool);
-              label2 = diff_label("/dev/null", rev2, subpool);
-            }
-          else if (operation == svn_diff_op_added)
-            {
-              SVN_ERR(print_git_diff_header_added(
-                                            os, 
-                                            diff_cmd_baton->header_encoding,
-                                            path, subpool));
-              label1 = diff_label("/dev/null", rev1, subpool);
-              label2 = diff_label(apr_psprintf(subpool, "b/%s", path2), rev2,
-                                  subpool);
-            }
-          else if (operation == svn_diff_op_modified)
-            {
-              SVN_ERR(print_git_diff_header_modified(
-                                            os, 
-                                            diff_cmd_baton->header_encoding,
-                                            path, subpool));
-              label1 = diff_label(apr_psprintf(subpool, "a/%s", path1), rev1,
-                                  subpool);
-              label2 = diff_label(apr_psprintf(subpool, "b/%s", path2), rev2,
-                                  subpool);
-            }
+          if (diff_cmd_baton->use_git_diff_format)
+            SVN_ERR(print_git_diff_header(os, &label1, &label2, operation,
+                                          path, diff_cmd_baton->orig_path_1,
+                                          diff_cmd_baton->orig_path_2,
+                                          rev1, rev2,
+                                          copyfrom_path,
+                                          diff_cmd_baton->header_encoding,
+                                          diff_cmd_baton->ra_session,
+                                          diff_cmd_baton->wc_ctx,
+                                          diff_cmd_baton->wc_root_abspath,
+                                          subpool));
 
-          /* ### Print git headers for copies and renames too. */
-#endif
           /* Output the actual diff */
-          SVN_ERR(svn_diff_file_output_unified3
-                  (os, diff, tmpfile1, tmpfile2, label1, label2,
-                   diff_cmd_baton->header_encoding, rel_to_dir,
-                   diff_cmd_baton->options.for_internal->show_c_function,
-                   subpool));
+          if (svn_diff_contains_diffs(diff) || diff_cmd_baton->force_empty)
+            SVN_ERR(svn_diff_file_output_unified3
+                    (os, diff, tmpfile1, tmpfile2, label1, label2,
+                     diff_cmd_baton->header_encoding, rel_to_dir,
+                     diff_cmd_baton->options.for_internal->show_c_function,
+                     subpool));
+
+          /* We have a printed a diff for this path, mark it as visited. */
+          apr_hash_set(diff_cmd_baton->visited_paths, path,
+                       APR_HASH_KEY_STRING, path);
+
         }
+
+      /* Close the stream (flush) */
+      SVN_ERR(svn_stream_close(os));
     }
 
   /* ### todo: someday we'll need to worry about whether we're going
@@ -775,7 +1050,7 @@ diff_file_changed(const char *local_dir_
     SVN_ERR(diff_content_changed(path,
                                  tmpfile1, tmpfile2, rev1, rev2,
                                  mimetype1, mimetype2,
-                                 svn_diff_op_modified, diff_baton));
+                                 svn_diff_op_modified, NULL, diff_baton));
   if (prop_changes->nelts > 0)
     SVN_ERR(diff_props_changed(local_dir_abspath, prop_state, tree_conflicted,
                                path, prop_changes,
@@ -822,11 +1097,17 @@ diff_file_added(const char *local_dir_ab
      user see that *something* happened. */
   diff_cmd_baton->force_empty = TRUE;
 
-  if (tmpfile1)
+  if (tmpfile1 && copyfrom_path)
+    SVN_ERR(diff_content_changed(path,
+                                 tmpfile1, tmpfile2, rev1, rev2,
+                                 mimetype1, mimetype2,
+                                 svn_diff_op_copied, copyfrom_path,
+                                 diff_baton));
+  else if (tmpfile1)
     SVN_ERR(diff_content_changed(path,
                                  tmpfile1, tmpfile2, rev1, rev2,
                                  mimetype1, mimetype2,
-                                 svn_diff_op_added, diff_baton));
+                                 svn_diff_op_added, NULL, diff_baton));
   if (prop_changes->nelts > 0)
     SVN_ERR(diff_props_changed(local_dir_abspath, prop_state, tree_conflicted,
                                path, prop_changes,
@@ -864,7 +1145,7 @@ diff_file_deleted_with_diff(const char *
                                  tmpfile1, tmpfile2, diff_cmd_baton->revnum1, 
                                  diff_cmd_baton->revnum2,
                                  mimetype1, mimetype2,
-                                 svn_diff_op_deleted, diff_baton));
+                                 svn_diff_op_deleted, NULL, diff_baton));
 
   /* We don't list all the deleted properties. */
 
@@ -1048,84 +1329,89 @@ convert_to_url(const char **url,
   return SVN_NO_ERROR;
 }
 
-/** Helper structure: for passing around the diff parameters */
-struct diff_parameters
+/* Return the absolute path to the root of the working copy which
+ * LOCAL_ABSPATH is located in, or NULL if LOCAL_ABSPATH is not within
+ * a working copy. Use working copy context WC_CTX.
+ * Allocate the result in RESULT_POOL.
+ * Use SCRATCH_POOL for temporary allocations. */
+static const char *
+find_wc_root(const char *local_abspath, svn_wc_context_t *wc_ctx,
+             apr_pool_t *result_pool, apr_pool_t *scratch_pool)
 {
-  /* First input path */
-  const char *path1;
-
-  /* Revision of first input path */
-  const svn_opt_revision_t *revision1;
-
-  /* Second input path */
-  const char *path2;
+  svn_boolean_t wc_root_found;
+  apr_pool_t *iterpool;
 
-  /* Revision of second input path */
-  const svn_opt_revision_t *revision2;
-
-  /* Peg revision */
-  const svn_opt_revision_t *peg_revision;
-
-  /* Desired depth */
-  svn_depth_t depth;
-
-  /* Ignore ancestry */
-  svn_boolean_t ignore_ancestry;
+  wc_root_found = FALSE;
+  iterpool = svn_pool_create(scratch_pool);
+  while (! wc_root_found && *local_abspath)
+    {
+      svn_error_t *err;
 
-  /* Ignore deleted */
-  svn_boolean_t no_diff_deleted;
+      svn_pool_clear(iterpool);
 
-  /* Don't follow copyfrom when diffing copies. */
-  svn_boolean_t show_copies_as_adds;
+      err = svn_wc_is_wc_root2(&wc_root_found, wc_ctx, local_abspath,
+                               iterpool);
+      if (err)
+        {
+          /* Ignore all errors. We don't care, because if all we get is
+           * errors, the path is not in a working copy. */
+          svn_error_clear(err);
+        }
 
-  /* Changelists of interest */
-  const apr_array_header_t *changelists;
-};
+      if (svn_dirent_is_root(local_abspath, strlen(local_abspath)))
+        break;
 
-/** Helper structure: filled by check_paths() */
-struct diff_paths
-{
-  /* path1 can only be found in the repository? */
-  svn_boolean_t is_repos1;
+      if (! wc_root_found)
+        local_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+    }
+  svn_pool_destroy(iterpool);
 
-  /* path2 can only be found in the repository? */
-  svn_boolean_t is_repos2;
-};
+  if (wc_root_found)
+    return apr_pstrdup(result_pool, local_abspath);
 
+  return NULL;
+}
 
-/** Check if paths are urls and if the revisions are local, and, for
-    pegged revisions, ensure that at least one revision is non-local.
-    Fills the PATHS structure. */
-static svn_error_t *
-check_paths(const struct diff_parameters *params,
-            struct diff_paths *paths)
+/** Check if paths PATH1 and PATH2 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 non-local.
+ *  If PATH1 can only be found in the repository, set *IS_REPOS1 to TRUE.
+ *  If PATH2 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 *path1,
+            const char *path2,
+            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 ((params->revision1->kind == svn_opt_revision_unspecified)
-      || (params->revision2->kind == svn_opt_revision_unspecified))
+  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,
      for example, are local.  */
   is_local_rev1 =
-    ((params->revision1->kind == svn_opt_revision_base)
-     || (params->revision1->kind == svn_opt_revision_working));
+    ((revision1->kind == svn_opt_revision_base)
+     || (revision1->kind == svn_opt_revision_working));
   is_local_rev2 =
-    ((params->revision2->kind == svn_opt_revision_base)
-     || (params->revision2->kind == svn_opt_revision_working));
+    ((revision2->kind == svn_opt_revision_base)
+     || (revision2->kind == svn_opt_revision_working));
 
-  if (params->peg_revision->kind != svn_opt_revision_unspecified)
+  if (peg_revision->kind != svn_opt_revision_unspecified)
     {
       if (is_local_rev1 && is_local_rev2)
         return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
                                 _("At least one revision must be non-local "
                                   "for a pegged diff"));
 
-      paths->is_repos1 = ! is_local_rev1;
-      paths->is_repos2 = ! is_local_rev2;
+      *is_repos1 = ! is_local_rev1;
+      *is_repos2 = ! is_local_rev2;
     }
   else
     {
@@ -1133,154 +1419,133 @@ check_paths(const struct diff_parameters
          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.  */
-      paths->is_repos1 = ! is_local_rev1 || svn_path_is_url(params->path1);
-      paths->is_repos2 = ! is_local_rev2 || svn_path_is_url(params->path2);
+      *is_repos1 = ! is_local_rev1 || svn_path_is_url(path1);
+      *is_repos2 = ! is_local_rev2 || svn_path_is_url(path2);
     }
 
   return SVN_NO_ERROR;
 }
 
-/** Helper structure filled by diff_prepare_repos_repos */
-struct diff_repos_repos_t
-{
-  /* URL created from path1 */
-  const char *url1;
-
-  /* URL created from path2 */
-  const char *url2;
-
-  /* The BASE_PATH for the diff */
-  const char *base_path;
-
-  /* url1 and url2 are the same */
-  svn_boolean_t same_urls;
-
-  /* Revision of url1 */
-  svn_revnum_t rev1;
-
-  /* Revision of url2 */
-  svn_revnum_t rev2;
-
-  /* Anchor based on url1 */
-  const char *anchor1;
-
-  /* Anchor based on url2 */
-  const char *anchor2;
-
-  /* Target based on url1 */
-  const char *target1;
-
-  /* Target based on url2 */
-  const char *target2;
-
-  /* RA session pointing at anchor1. */
-  svn_ra_session_t *ra_session;
-};
-
-/** Helper function: prepare a repos repos diff. Fills DRR
- * structure. */
-static svn_error_t *
-diff_prepare_repos_repos(const struct diff_parameters *params,
-                         struct diff_repos_repos_t *drr,
+/** Prepare a repos repos diff between PATH1 and PATH2@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.
+ * 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_ra_session_t **ra_session,
                          svn_client_ctx_t *ctx,
+                         const char *path1,
+                         const char *path2,
+                         const svn_opt_revision_t *revision1,
+                         const svn_opt_revision_t *revision2,
+                         const svn_opt_revision_t *peg_revision,
                          apr_pool_t *pool)
 {
-  svn_ra_session_t *ra_session;
   svn_node_kind_t kind1, kind2;
-  const char *params_path2_abspath;
-  const char *params_path1_abspath;
+  const char *path2_abspath;
+  const char *path1_abspath;
 
-  if (!svn_path_is_url(params->path2))
-    SVN_ERR(svn_dirent_get_absolute(&params_path2_abspath, params->path2,
+  if (!svn_path_is_url(path2))
+    SVN_ERR(svn_dirent_get_absolute(&path2_abspath, path2,
                                     pool));
   else
-    params_path2_abspath = params->path2;
+    path2_abspath = path2;
 
-  if (!svn_path_is_url(params->path1))
-    SVN_ERR(svn_dirent_get_absolute(&params_path1_abspath, params->path1,
+  if (!svn_path_is_url(path1))
+    SVN_ERR(svn_dirent_get_absolute(&path1_abspath, path1,
                                     pool));
   else
-    params_path1_abspath = params->path1;
+    path1_abspath = path1;
 
   /* Figure out URL1 and URL2. */
-  SVN_ERR(convert_to_url(&drr->url1, ctx->wc_ctx, params_path1_abspath,
+  SVN_ERR(convert_to_url(url1, ctx->wc_ctx, path1_abspath,
                          pool, pool));
-  SVN_ERR(convert_to_url(&drr->url2, ctx->wc_ctx, params_path2_abspath,
+  SVN_ERR(convert_to_url(url2, ctx->wc_ctx, path2_abspath,
                          pool, pool));
-  drr->same_urls = (strcmp(drr->url1, drr->url2) == 0);
 
   /* We need exactly one BASE_PATH, so we'll let the BASE_PATH
      calculated for PATH2 override the one for PATH1 (since the diff
      will be "applied" to URL2 anyway). */
-  drr->base_path = NULL;
-  if (strcmp(drr->url1, params->path1) != 0)
-    drr->base_path = params->path1;
-  if (strcmp(drr->url2, params->path2) != 0)
-    drr->base_path = params->path2;
+  *base_path = NULL;
+  if (strcmp(*url1, path1) != 0)
+    *base_path = path1;
+  if (strcmp(*url2, path2) != 0)
+    *base_path = path2;
 
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, drr->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 (params->peg_revision->kind != svn_opt_revision_unspecified)
+  if (peg_revision->kind != svn_opt_revision_unspecified)
     {
       svn_opt_revision_t *start_ignore, *end_ignore;
 
-      SVN_ERR(svn_client__repos_locations(&drr->url1, &start_ignore,
-                                          &drr->url2, &end_ignore,
-                                          ra_session,
-                                          params->path2,
-                                          params->peg_revision,
-                                          params->revision1,
-                                          params->revision2,
+      SVN_ERR(svn_client__repos_locations(url1, &start_ignore,
+                                          url2, &end_ignore,
+                                          *ra_session,
+                                          path2,
+                                          peg_revision,
+                                          revision1,
+                                          revision2,
                                           ctx, pool));
-      /* Reparent the session, since drr->url2 might have changed as a result
+      /* Reparent the session, since *URL2 might have changed as a result
          the above call. */
-      SVN_ERR(svn_ra_reparent(ra_session, drr->url2, pool));
+      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(&drr->rev2, NULL, ctx->wc_ctx,
-           (params->path2 == drr->url2) ? NULL : params_path2_abspath,
-           ra_session, params->revision2, pool));
-  SVN_ERR(svn_ra_check_path(ra_session, "", drr->rev2, &kind2, pool));
+  SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx,
+           (path2 == *url2) ? NULL : path2_abspath,
+           *ra_session, revision2, pool));
+  SVN_ERR(svn_ra_check_path(*ra_session, "", *rev2, &kind2, pool));
   if (kind2 == svn_node_none)
     return svn_error_createf
       (SVN_ERR_FS_NOT_FOUND, NULL,
        _("'%s' was not found in the repository at revision %ld"),
-       drr->url2, drr->rev2);
+       *url2, *rev2);
 
   /* Do the same for the first target. */
-  SVN_ERR(svn_ra_reparent(ra_session, drr->url1, pool));
-  SVN_ERR(svn_client__get_revision_number(&drr->rev1, NULL, ctx->wc_ctx,
-           (params->path1 == drr->url1) ? NULL : params_path1_abspath,
-           ra_session, params->revision1, pool));
-  SVN_ERR(svn_ra_check_path(ra_session, "", drr->rev1, &kind1, pool));
+  SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
+  SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx,
+           (strcmp(path1, *url1) == 0) ? NULL : path1_abspath,
+           *ra_session, revision1, pool));
+  SVN_ERR(svn_ra_check_path(*ra_session, "", *rev1, &kind1, pool));
   if (kind1 == svn_node_none)
     return svn_error_createf
       (SVN_ERR_FS_NOT_FOUND, NULL,
        _("'%s' was not found in the repository at revision %ld"),
-       drr->url1, drr->rev1);
+       *url1, *rev1);
 
   /* Choose useful anchors and targets for our two URLs. */
-  drr->anchor1 = drr->url1;
-  drr->anchor2 = drr->url2;
-  drr->target1 = "";
-  drr->target2 = "";
+  *anchor1 = *url1;
+  *anchor2 = *url2;
+  *target1 = "";
+  *target2 = "";
   if ((kind1 == svn_node_file) || (kind2 == svn_node_file))
     {
-      svn_uri_split(drr->url1, &drr->anchor1, &drr->target1, pool);
-      drr->target1 = svn_path_uri_decode(drr->target1, pool);
-      svn_uri_split(drr->url2, &drr->anchor2, &drr->target2, pool);
-      drr->target2 = svn_path_uri_decode(drr->target2, pool);
-      if (drr->base_path)
-        drr->base_path = svn_dirent_dirname(drr->base_path, pool);
-      SVN_ERR(svn_ra_reparent(ra_session, drr->anchor1, pool));
+      svn_uri_split(anchor1, target1, *url1, pool);
+      *target1 = svn_path_uri_decode(*target1, pool);
+      svn_uri_split(anchor2, target2, *url2, pool);
+      *target2 = svn_path_uri_decode(*target2, pool);
+      if (*base_path)
+        *base_path = svn_dirent_dirname(*base_path, pool);
+      SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
     }
 
-  drr->ra_session = ra_session;
   return SVN_NO_ERROR;
 }
 
@@ -1339,6 +1604,7 @@ diff_wc_wc(const char *path1,
            svn_depth_t depth,
            svn_boolean_t ignore_ancestry,
            svn_boolean_t show_copies_as_adds,
+           svn_boolean_t use_git_diff_format,
            const apr_array_header_t *changelists,
            const svn_wc_diff_callbacks4_t *callbacks,
            struct diff_cmd_baton *callback_baton,
@@ -1387,9 +1653,8 @@ diff_wc_wc(const char *path1,
                        path1,
                        callbacks, callback_baton,
                        depth,
-                       ignore_ancestry,
-                       show_copies_as_adds,
-                       changelists,
+                       ignore_ancestry, show_copies_as_adds,
+                       use_git_diff_format, changelists,
                        ctx->cancel_func, ctx->cancel_baton,
                        pool));
   return SVN_NO_ERROR;
@@ -1398,18 +1663,24 @@ diff_wc_wc(const char *path1,
 
 /* Perform a diff between two repository paths.
 
-   DIFF_PARAM.PATH1 and DIFF_PARAM.PATH2 may be either URLs or the working
-   copy paths. DIFF_PARAM.REVISION1 and DIFF_PARAM.REVISION2 are their
-   respective revisions. If DIFF_PARAM.PEG_REVISION is specified,
-   DIFF_PARAM.PATH2 is the path at the peg revision, and the actual two
-   paths compared are determined by following copy history from PATH2.
+   PATH1 and PATH2 may be either URLs or the working copy paths.
+   REVISION1 and REVISION2 are their respective revisions.
+   If PEG_REVISION is specified, PATH2 is the path at the peg revision,
+   and the actual two paths compared are determined by following copy
+   history from PATH2.
 
    All other options are the same as those passed to svn_client_diff5(). */
 static svn_error_t *
-diff_repos_repos(const struct diff_parameters *diff_param,
-                 const svn_wc_diff_callbacks4_t *callbacks,
+diff_repos_repos(const svn_wc_diff_callbacks4_t *callbacks,
                  struct diff_cmd_baton *callback_baton,
                  svn_client_ctx_t *ctx,
+                 const char *path1,
+                 const char *path2,
+                 const svn_opt_revision_t *revision1,
+                 const svn_opt_revision_t *revision2,
+                 const svn_opt_revision_t *peg_revision,
+                 svn_depth_t depth,
+                 svn_boolean_t ignore_ancestry,
                  apr_pool_t *pool)
 {
   svn_ra_session_t *extra_ra_session;
@@ -1420,44 +1691,59 @@ diff_repos_repos(const struct diff_param
   const svn_delta_editor_t *diff_editor;
   void *diff_edit_baton;
 
-  struct diff_repos_repos_t drr;
+  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_ra_session_t *ra_session;
 
   /* Prepare info for the repos repos diff. */
-  SVN_ERR(diff_prepare_repos_repos(diff_param, &drr, ctx, pool));
+  SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &base_path, &rev1, &rev2,
+                                   &anchor1, &anchor2, &target1, &target2,
+                                   &ra_session, ctx, path1, path2,
+                                   revision1, revision2, peg_revision,
+                                   pool));
 
   /* Get actual URLs. */
-  callback_baton->orig_path_1 = drr.url1;
-  callback_baton->orig_path_2 = drr.url2;
+  callback_baton->orig_path_1 = url1;
+  callback_baton->orig_path_2 = url2;
 
   /* Get numeric revisions. */
-  callback_baton->revnum1 = drr.rev1;
-  callback_baton->revnum2 = drr.rev2;
+  callback_baton->revnum1 = rev1;
+  callback_baton->revnum2 = rev2;
+
+  callback_baton->ra_session = ra_session;
 
   /* Now, we open an extra RA session to the correct anchor
      location for URL1.  This is used during the editor calls to fetch file
      contents.  */
-  SVN_ERR(svn_client__open_ra_session_internal(&extra_ra_session, drr.anchor1,
-                                               NULL, NULL, FALSE, TRUE, ctx,
-                                               pool));
+  SVN_ERR(svn_client__open_ra_session_internal(&extra_ra_session, NULL,
+                                               anchor1, NULL, NULL, FALSE,
+                                               TRUE, ctx, pool));
 
   /* Set up the repos_diff editor on BASE_PATH, if available.
      Otherwise, we just use "". */
   SVN_ERR(svn_client__get_diff_editor
-          (drr.base_path ? drr.base_path : "",
-           NULL, callbacks, callback_baton, diff_param->depth,
-           FALSE /* doesn't matter for diff */, extra_ra_session, drr.rev1,
+          (base_path ? base_path : "",
+           NULL, callbacks, callback_baton, depth,
+           FALSE /* doesn't matter for diff */, extra_ra_session, rev1,
            NULL /* no notify_func */, NULL /* no notify_baton */,
            ctx->cancel_func, ctx->cancel_baton,
            &diff_editor, &diff_edit_baton, pool));
 
   /* We want to switch our txn into URL2 */
   SVN_ERR(svn_ra_do_diff3
-          (drr.ra_session, &reporter, &reporter_baton, drr.rev2, drr.target1,
-           diff_param->depth, diff_param->ignore_ancestry, TRUE,
-           drr.url2, diff_editor, diff_edit_baton, pool));
+          (ra_session, &reporter, &reporter_baton, rev2, target1,
+           depth, ignore_ancestry, TRUE,
+           url2, diff_editor, diff_edit_baton, pool));
 
   /* Drive the reporter; do the diff. */
-  SVN_ERR(reporter->set_path(reporter_baton, "", drr.rev1,
+  SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
                              svn_depth_infinity,
                              FALSE, NULL,
                              pool));
@@ -1485,6 +1771,7 @@ diff_repos_wc(const char *path1,
               svn_depth_t depth,
               svn_boolean_t ignore_ancestry,
               svn_boolean_t show_copies_as_adds,
+              svn_boolean_t use_git_diff_format,
               const apr_array_header_t *changelists,
               const svn_wc_diff_callbacks4_t *callbacks,
               struct diff_cmd_baton *callback_baton,
@@ -1558,9 +1845,13 @@ diff_repos_wc(const char *path1,
     }
 
   /* Establish RA session to path2's anchor */
-  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, anchor_url,
+  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, anchor_url,
                                                NULL, NULL, FALSE, TRUE,
                                                ctx, pool));
+  callback_baton->ra_session = ra_session;
+  if (use_git_diff_format)
+    callback_baton->wc_root_abspath = find_wc_root(anchor_abspath, ctx->wc_ctx,
+                                                   pool, pool);
 
   SVN_ERR(svn_wc_get_diff_editor6(&diff_editor, &diff_edit_baton,
                                   ctx->wc_ctx,
@@ -1570,6 +1861,7 @@ diff_repos_wc(const char *path1,
                                   depth,
                                   ignore_ancestry,
                                   show_copies_as_adds,
+                                  use_git_diff_format,
                                   rev2_is_base,
                                   reverse,
                                   changelists,
@@ -1578,7 +1870,7 @@ diff_repos_wc(const char *path1,
 
   /* Tell the RA layer we want a delta to change our txn to URL1 */
   SVN_ERR(svn_client__get_revision_number(&rev, NULL, ctx->wc_ctx,
-                                          (path1 == url1) ? NULL : abspath1,
+                                          (strcmp(path1, url1) == 0) ? NULL : abspath1,
                                           ra_session, revision1, pool));
 
   if (!reverse)
@@ -1613,57 +1905,61 @@ diff_repos_wc(const char *path1,
 
 /* This is basically just the guts of svn_client_diff[_peg]5(). */
 static svn_error_t *
-do_diff(const struct diff_parameters *diff_param,
-        const svn_wc_diff_callbacks4_t *callbacks,
+do_diff(const svn_wc_diff_callbacks4_t *callbacks,
         struct diff_cmd_baton *callback_baton,
         svn_client_ctx_t *ctx,
+        const char *path1,
+        const char *path2,
+        const svn_opt_revision_t *revision1,
+        const svn_opt_revision_t *revision2,
+        const svn_opt_revision_t *peg_revision,
+        svn_depth_t depth,
+        svn_boolean_t ignore_ancestry,
+        svn_boolean_t show_copies_as_adds,
+        svn_boolean_t use_git_diff_format,
+        const apr_array_header_t *changelists,
         apr_pool_t *pool)
 {
-  struct diff_paths diff_paths;
+  svn_boolean_t is_repos1;
+  svn_boolean_t is_repos2;
 
   /* Check if paths/revisions are urls/local. */
-  SVN_ERR(check_paths(diff_param, &diff_paths));
+  SVN_ERR(check_paths(&is_repos1, &is_repos2, path1, path2,
+                      revision1, revision2, peg_revision));
 
-  if (diff_paths.is_repos1)
+  if (is_repos1)
     {
-      if (diff_paths.is_repos2)
+      if (is_repos2)
         {
-          SVN_ERR(diff_repos_repos(diff_param, callbacks, callback_baton,
-                                   ctx, pool));
+          SVN_ERR(diff_repos_repos(callbacks, callback_baton, ctx,
+                                   path1, path2, revision1, revision2,
+                                   peg_revision, depth, ignore_ancestry,
+                                   pool));
         }
       else /* path2 is a working copy path */
         {
-          SVN_ERR(diff_repos_wc(diff_param->path1, diff_param->revision1,
-                                diff_param->peg_revision,
-                                diff_param->path2, diff_param->revision2,
-                                FALSE, diff_param->depth,
-                                diff_param->ignore_ancestry,
-                                diff_param->show_copies_as_adds,
-                                diff_param->changelists,
+          SVN_ERR(diff_repos_wc(path1, revision1, peg_revision,
+                                path2, revision2, FALSE, depth,
+                                ignore_ancestry, show_copies_as_adds,
+                                use_git_diff_format, changelists,
                                 callbacks, callback_baton, ctx, pool));
         }
     }
   else /* path1 is a working copy path */
     {
-      if (diff_paths.is_repos2)
+      if (is_repos2)
         {
-          SVN_ERR(diff_repos_wc(diff_param->path2, diff_param->revision2,
-                                diff_param->peg_revision,
-                                diff_param->path1, diff_param->revision1,
-                                TRUE, diff_param->depth,
-                                diff_param->ignore_ancestry,
-                                diff_param->show_copies_as_adds,
-                                diff_param->changelists,
+          SVN_ERR(diff_repos_wc(path2, revision2, peg_revision,
+                                path1, revision1, TRUE, depth,
+                                ignore_ancestry, show_copies_as_adds,
+                                use_git_diff_format, changelists,
                                 callbacks, callback_baton, ctx, pool));
         }
       else /* path2 is a working copy path */
         {
-          SVN_ERR(diff_wc_wc(diff_param->path1, diff_param->revision1,
-                             diff_param->path2, diff_param->revision2,
-                             diff_param->depth,
-                             diff_param->ignore_ancestry,
-                             diff_param->show_copies_as_adds,
-                             diff_param->changelists,
+          SVN_ERR(diff_wc_wc(path1, revision1, path2, revision2,
+                             depth, ignore_ancestry, show_copies_as_adds,
+                             use_git_diff_format, changelists,
                              callbacks, callback_baton, ctx, pool));
         }
     }
@@ -1673,10 +1969,16 @@ do_diff(const struct diff_parameters *di
 
 /* Perform a diff summary between two repository paths. */
 static svn_error_t *
-diff_summarize_repos_repos(const struct diff_parameters *diff_param,
-                           svn_client_diff_summarize_func_t summarize_func,
+diff_summarize_repos_repos(svn_client_diff_summarize_func_t summarize_func,
                            void *summarize_baton,
                            svn_client_ctx_t *ctx,
+                           const char *path1,
+                           const char *path2,
+                           const svn_opt_revision_t *revision1,
+                           const svn_opt_revision_t *revision2,
+                           const svn_opt_revision_t *peg_revision,
+                           svn_depth_t depth,
+                           svn_boolean_t ignore_ancestry,
                            apr_pool_t *pool)
 {
   svn_ra_session_t *extra_ra_session;
@@ -1687,32 +1989,45 @@ diff_summarize_repos_repos(const struct 
   const svn_delta_editor_t *diff_editor;
   void *diff_edit_baton;
 
-  struct diff_repos_repos_t drr;
+  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_ra_session_t *ra_session;
 
   /* Prepare info for the repos repos diff. */
-  SVN_ERR(diff_prepare_repos_repos(diff_param, &drr, ctx, pool));
+  SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &base_path, &rev1, &rev2,
+                                   &anchor1, &anchor2, &target1, &target2,
+                                   &ra_session, ctx,
+                                   path1, path2, revision1, revision2,
+                                   peg_revision, pool));
 
   /* Now, we open an extra RA session to the correct anchor
      location for URL1.  This is used to get the kind of deleted paths.  */
-  SVN_ERR(svn_client__open_ra_session_internal(&extra_ra_session, drr.anchor1,
-                                               NULL, NULL, FALSE, TRUE,
-                                               ctx, pool));
+  SVN_ERR(svn_client__open_ra_session_internal(&extra_ra_session, NULL,
+                                               anchor1, NULL, NULL, FALSE,
+                                               TRUE, ctx, pool));
 
   /* Set up the repos_diff editor. */
   SVN_ERR(svn_client__get_diff_summarize_editor
-          (drr.target2, summarize_func,
-           summarize_baton, extra_ra_session, drr.rev1, ctx->cancel_func,
+          (target2, summarize_func,
+           summarize_baton, extra_ra_session, rev1, ctx->cancel_func,
            ctx->cancel_baton, &diff_editor, &diff_edit_baton, pool));
 
   /* We want to switch our txn into URL2 */
   SVN_ERR(svn_ra_do_diff3
-          (drr.ra_session, &reporter, &reporter_baton, drr.rev2, drr.target1,
-           diff_param->depth, diff_param->ignore_ancestry,
-           FALSE /* do not create text delta */, drr.url2, diff_editor,
+          (ra_session, &reporter, &reporter_baton, rev2, target1,
+           depth, ignore_ancestry,
+           FALSE /* do not create text delta */, url2, diff_editor,
            diff_edit_baton, pool));
 
   /* Drive the reporter; do the diff. */
-  SVN_ERR(reporter->set_path(reporter_baton, "", drr.rev1,
+  SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
                              svn_depth_infinity,
                              FALSE, NULL, pool));
   return reporter->finish_report(reporter_baton, pool);
@@ -1720,20 +2035,30 @@ diff_summarize_repos_repos(const struct 
 
 /* This is basically just the guts of svn_client_diff_summarize[_peg]2(). */
 static svn_error_t *
-do_diff_summarize(const struct diff_parameters *diff_param,
-                  svn_client_diff_summarize_func_t summarize_func,
+do_diff_summarize(svn_client_diff_summarize_func_t summarize_func,
                   void *summarize_baton,
                   svn_client_ctx_t *ctx,
+                  const char *path1,
+                  const char *path2,
+                  const svn_opt_revision_t *revision1,
+                  const svn_opt_revision_t *revision2,
+                  const svn_opt_revision_t *peg_revision,
+                  svn_depth_t depth,
+                  svn_boolean_t ignore_ancestry,
                   apr_pool_t *pool)
 {
-  struct diff_paths diff_paths;
+  svn_boolean_t is_repos1;
+  svn_boolean_t is_repos2;
 
   /* Check if paths/revisions are urls/local. */
-  SVN_ERR(check_paths(diff_param, &diff_paths));
+  SVN_ERR(check_paths(&is_repos1, &is_repos2, path1, path2,
+                      revision1, revision2, peg_revision));
 
-  if (diff_paths.is_repos1 && diff_paths.is_repos2)
-    return diff_summarize_repos_repos(diff_param, summarize_func,
-                                      summarize_baton, ctx, pool);
+  if (is_repos1 && is_repos2)
+    return diff_summarize_repos_repos(summarize_func, summarize_baton, ctx,
+                                      path1, path2, revision1, revision2,
+                                      peg_revision, depth, ignore_ancestry,
+                                      pool);
   else
     return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
                             _("Summarizing diff can only compare repository "
@@ -1854,6 +2179,7 @@ svn_client_diff5(const apr_array_header_
                  svn_boolean_t no_diff_deleted,
                  svn_boolean_t show_copies_as_adds,
                  svn_boolean_t ignore_content_type,
+                 svn_boolean_t use_git_diff_format,
                  const char *header_encoding,
                  apr_file_t *outfile,
                  apr_file_t *errfile,
@@ -1861,8 +2187,6 @@ svn_client_diff5(const apr_array_header_
                  svn_client_ctx_t *ctx,
                  apr_pool_t *pool)
 {
-  struct diff_parameters diff_params;
-
   struct diff_cmd_baton diff_cmd_baton;
   svn_wc_diff_callbacks4_t diff_callbacks;
 
@@ -1870,18 +2194,6 @@ svn_client_diff5(const apr_array_header_
   svn_opt_revision_t peg_revision;
   peg_revision.kind = svn_opt_revision_unspecified;
 
-  /* fill diff_param */
-  diff_params.path1 = path1;
-  diff_params.revision1 = revision1;
-  diff_params.path2 = path2;
-  diff_params.revision2 = revision2;
-  diff_params.peg_revision = &peg_revision;
-  diff_params.depth = depth;
-  diff_params.ignore_ancestry = ignore_ancestry;
-  diff_params.no_diff_deleted = no_diff_deleted;
-  diff_params.show_copies_as_adds = show_copies_as_adds;
-  diff_params.changelists = changelists;
-
   /* setup callback and baton */
   diff_callbacks.file_changed = diff_file_changed;
   diff_callbacks.file_added = diff_file_added;
@@ -1908,8 +2220,16 @@ svn_client_diff5(const apr_array_header_
   diff_cmd_baton.force_empty = FALSE;
   diff_cmd_baton.force_binary = ignore_content_type;
   diff_cmd_baton.relative_to_dir = relative_to_dir;
-
-  return do_diff(&diff_params, &diff_callbacks, &diff_cmd_baton, ctx, pool);
+  diff_cmd_baton.use_git_diff_format = use_git_diff_format;
+  diff_cmd_baton.wc_ctx = ctx->wc_ctx;
+  diff_cmd_baton.visited_paths = apr_hash_make(pool);
+  diff_cmd_baton.ra_session = NULL;
+  diff_cmd_baton.wc_root_abspath = NULL;
+
+  return do_diff(&diff_callbacks, &diff_cmd_baton, ctx,
+                 path1, path2, revision1, revision2, &peg_revision,
+                 depth, ignore_ancestry, show_copies_as_adds,
+                 use_git_diff_format, changelists, pool);
 }
 
 svn_error_t *
@@ -1924,6 +2244,7 @@ svn_client_diff_peg5(const apr_array_hea
                      svn_boolean_t no_diff_deleted,
                      svn_boolean_t show_copies_as_adds,
                      svn_boolean_t ignore_content_type,
+                     svn_boolean_t use_git_diff_format,
                      const char *header_encoding,
                      apr_file_t *outfile,
                      apr_file_t *errfile,
@@ -1931,23 +2252,9 @@ svn_client_diff_peg5(const apr_array_hea
                      svn_client_ctx_t *ctx,
                      apr_pool_t *pool)
 {
-  struct diff_parameters diff_params;
-
   struct diff_cmd_baton diff_cmd_baton;
   svn_wc_diff_callbacks4_t diff_callbacks;
 
-  /* fill diff_param */
-  diff_params.path1 = path;
-  diff_params.revision1 = start_revision;
-  diff_params.path2 = path;
-  diff_params.revision2 = end_revision;
-  diff_params.peg_revision = peg_revision;
-  diff_params.depth = depth;
-  diff_params.ignore_ancestry = ignore_ancestry;
-  diff_params.no_diff_deleted = no_diff_deleted;
-  diff_params.show_copies_as_adds = show_copies_as_adds;
-  diff_params.changelists = changelists;
-
   /* setup callback and baton */
   diff_callbacks.file_changed = diff_file_changed;
   diff_callbacks.file_added = diff_file_added;
@@ -1974,8 +2281,16 @@ svn_client_diff_peg5(const apr_array_hea
   diff_cmd_baton.force_empty = FALSE;
   diff_cmd_baton.force_binary = ignore_content_type;
   diff_cmd_baton.relative_to_dir = relative_to_dir;
-
-  return do_diff(&diff_params, &diff_callbacks, &diff_cmd_baton, ctx, pool);
+  diff_cmd_baton.use_git_diff_format = use_git_diff_format;
+  diff_cmd_baton.wc_ctx = ctx->wc_ctx;
+  diff_cmd_baton.visited_paths = apr_hash_make(pool);
+  diff_cmd_baton.ra_session = NULL;
+  diff_cmd_baton.wc_root_abspath = NULL;
+
+  return do_diff(&diff_callbacks, &diff_cmd_baton, ctx,
+                 path, path, start_revision, end_revision, peg_revision,
+                 depth, ignore_ancestry, show_copies_as_adds,
+                 use_git_diff_format, changelists, pool);
 }
 
 svn_error_t *
@@ -1991,25 +2306,14 @@ svn_client_diff_summarize2(const char *p
                            svn_client_ctx_t *ctx,
                            apr_pool_t *pool)
 {
-  struct diff_parameters diff_params;
-
   /* We will never do a pegged diff from here. */
   svn_opt_revision_t peg_revision;
   peg_revision.kind = svn_opt_revision_unspecified;
 
-  /* fill diff_param */
-  diff_params.path1 = path1;
-  diff_params.revision1 = revision1;
-  diff_params.path2 = path2;
-  diff_params.revision2 = revision2;
-  diff_params.peg_revision = &peg_revision;
-  diff_params.depth = depth;
-  diff_params.ignore_ancestry = ignore_ancestry;
-  diff_params.no_diff_deleted = FALSE;
-  diff_params.changelists = changelists;
-
-  return do_diff_summarize(&diff_params, summarize_func, summarize_baton,
-                           ctx, pool);
+  /* ### CHANGELISTS parameter isn't used */
+  return do_diff_summarize(summarize_func, summarize_baton, ctx,
+                           path1, path2, revision1, revision2, &peg_revision,
+                           depth, ignore_ancestry, pool);
 }
 
 svn_error_t *
@@ -2025,21 +2329,10 @@ svn_client_diff_summarize_peg2(const cha
                                svn_client_ctx_t *ctx,
                                apr_pool_t *pool)
 {
-  struct diff_parameters diff_params;
-
-  /* fill diff_param */
-  diff_params.path1 = path;
-  diff_params.revision1 = start_revision;
-  diff_params.path2 = path;
-  diff_params.revision2 = end_revision;
-  diff_params.peg_revision = peg_revision;
-  diff_params.depth = depth;
-  diff_params.ignore_ancestry = ignore_ancestry;
-  diff_params.no_diff_deleted = FALSE;
-  diff_params.changelists = changelists;
-
-  return do_diff_summarize(&diff_params, summarize_func, summarize_baton,
-                           ctx, pool);
+  /* ### CHANGELISTS parameter isn't used */
+  return do_diff_summarize(summarize_func, summarize_baton, ctx,
+                           path, path, start_revision, end_revision,
+                           peg_revision, depth, ignore_ancestry, pool);
 }
 
 svn_client_diff_summarize_t *

Modified: subversion/branches/py-tests-as-modules/subversion/libsvn_client/export.c
URL: http://svn.apache.org/viewvc/subversion/branches/py-tests-as-modules/subversion/libsvn_client/export.c?rev=997472&r1=997471&r2=997472&view=diff
==============================================================================
--- subversion/branches/py-tests-as-modules/subversion/libsvn_client/export.c (original)
+++ subversion/branches/py-tests-as-modules/subversion/libsvn_client/export.c Wed Sep 15 19:32:26 2010
@@ -326,11 +326,11 @@ copy_versioned_files(const char *from,
                                     from_abspath, pool));
       if (is_added)
         {
-          const char *copyfrom_url;
-          SVN_ERR(svn_wc__node_get_copyfrom_info(&copyfrom_url, NULL, NULL,
-                                                 ctx->wc_ctx, from_abspath,
-                                                 pool, pool));
-          if (! copyfrom_url)
+          const char *is_copied;
+          SVN_ERR(svn_wc__node_get_copyfrom_info(&is_copied, NULL, NULL,
+                                                 NULL, NULL, ctx->wc_ctx,
+                                                 from_abspath, pool, pool));
+          if (! is_copied)
             return SVN_NO_ERROR;
         }
     }
@@ -349,21 +349,26 @@ copy_versioned_files(const char *from,
 
   if (from_kind == svn_node_dir)
     {
+      apr_fileperms_t perm = APR_OS_DEFAULT;
+
       /* Try to make the new directory.  If this fails because the
          directory already exists, check our FORCE flag to see if we
          care. */
 
-      /* Skip retrieving the umask on windows. Apr does not implement setting
+      /* Keep the source directory's permissions if applicable.
+         Skip retrieving the umask on windows. Apr does not implement setting
          filesystem privileges on Windows.
          Retrieving the file permissions with APR_FINFO_PROT | APR_FINFO_OWNER
          is documented to be 'incredibly expensive' */
-#ifdef WIN32
-      err = svn_io_dir_make(to, APR_OS_DEFAULT, pool);
-#else
-      apr_finfo_t finfo;
-      SVN_ERR(svn_io_stat(&finfo, from, APR_FINFO_PROT, pool));
-      err = svn_io_dir_make(to, finfo.protection, pool);
+#ifndef WIN32
+      if (revision->kind == svn_opt_revision_working)
+        {
+          apr_finfo_t finfo;
+          SVN_ERR(svn_io_stat(&finfo, from, APR_FINFO_PROT, pool));
+          perm = finfo.protection;
+        }
 #endif
+      err = svn_io_dir_make(to, perm, pool);
       if (err)
         {
           if (! APR_STATUS_IS_EEXIST(err->apr_err))
@@ -708,7 +713,12 @@ add_file(const char *path,
   struct edit_baton *eb = pb->edit_baton;
   struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb));
   const char *full_path = svn_dirent_join(eb->root_path, path, pool);
-  const char *full_url = svn_uri_join(eb->root_url, path, pool);
+
+  /* PATH is not canonicalized, i.e. it may still contain spaces etc.
+   * but EB->root_url is. */
+  const char *full_url = svn_path_url_add_component2(eb->root_url,
+                                                     path,
+                                                     pool);
 
   fb->edit_baton = eb;
   fb->path = full_path;
@@ -937,6 +947,11 @@ svn_client_export5(svn_revnum_t *result_
   SVN_ERR_ASSERT(peg_revision != NULL);
   SVN_ERR_ASSERT(revision != NULL);
 
+  if (svn_path_is_url(to))
+    return svn_error_return(svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
+                                              _("'%s' is not a local path"),
+                                              to));
+    
   peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, from);
   revision = svn_cl__rev_default_to_peg(revision, peg_revision);
 
@@ -1083,7 +1098,8 @@ svn_client_export5(svn_revnum_t *result_
               SVN_ERR(svn_client__fetch_externals(eb->externals,
                                                   from, to_abspath,
                                                   repos_root_url, depth, TRUE,
-                                                  &use_sleep, ctx, pool));
+                                                  native_eol, &use_sleep,
+                                                  ctx, pool));
             }
         }
       else if (kind == svn_node_none)

Modified: subversion/branches/py-tests-as-modules/subversion/libsvn_client/externals.c
URL: http://svn.apache.org/viewvc/subversion/branches/py-tests-as-modules/subversion/libsvn_client/externals.c?rev=997472&r1=997471&r2=997472&view=diff
==============================================================================
--- subversion/branches/py-tests-as-modules/subversion/libsvn_client/externals.c (original)
+++ subversion/branches/py-tests-as-modules/subversion/libsvn_client/externals.c Wed Sep 15 19:32:26 2010
@@ -62,6 +62,9 @@ struct handle_external_item_change_baton
   /* Passed through to svn_client_* functions. */
   svn_client_ctx_t *ctx;
 
+  /* Passed to svn_client_exportX() */
+  const char *native_eol;
+
   svn_boolean_t *timestamp_sleep;
   svn_boolean_t is_export;
 
@@ -116,7 +119,7 @@ relegate_dir_external(void *baton,
       svn_error_clear(err);
       err = SVN_NO_ERROR;
 
-      svn_dirent_split(b->local_abspath, &parent_dir, &dirname, scratch_pool);
+      svn_dirent_split(&parent_dir, &dirname, b->local_abspath, scratch_pool);
 
       /* Reserve the new dir name. */
       SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path,
@@ -221,8 +224,8 @@ switch_dir_external(const char *path,
 
                   /* Get the repos root of the new URL. */
                   SVN_ERR(svn_client__open_ra_session_internal
-                          (&ra_session, url, NULL, NULL, FALSE, TRUE,
-                           ctx, subpool));
+                          (&ra_session, NULL, url, NULL, NULL,
+                           FALSE, TRUE, ctx, subpool));
                   SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root,
                                                  subpool));
 
@@ -274,9 +277,10 @@ switch_dir_external(const char *path,
       baton.cancel_baton = ctx->cancel_baton;
 
       /* Buh-bye, old and busted ... */
-      SVN_ERR(svn_wc__call_with_write_lock(relegate_dir_external, &baton,
-                                           ctx->wc_ctx, local_abspath,
-                                           pool, pool));
+      SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, local_abspath,
+                                         FALSE, pool, pool));
+      
+      SVN_ERR(relegate_dir_external(&baton, pool, pool));
     }
   else
     {
@@ -362,7 +366,7 @@ switch_file_external(const char *path,
            url, dest_wc_repos_root_url);
 
       SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, anchor_abspath,
-                                         subpool, subpool));
+                                         FALSE, subpool, subpool));
     }
 
   err = svn_wc_read_kind(&kind, ctx->wc_ctx, local_abspath, FALSE, subpool);
@@ -439,8 +443,8 @@ switch_file_external(const char *path,
       if (err)
         goto cleanup;
 
-      err = svn_wc_register_file_external(ctx->wc_ctx, local_abspath, url,
-                                          peg_revision, revision, subpool);
+      err = svn_wc__register_file_external(ctx->wc_ctx, local_abspath, url,
+                                           peg_revision, revision, subpool);
       if (err)
         goto cleanup;
     }
@@ -854,7 +858,8 @@ handle_external_item_change(const void *
             SVN_ERR(svn_client_export4(NULL, new_item->url, local_abspath,
                                        &(new_item->peg_revision),
                                        &(new_item->revision),
-                                       TRUE, FALSE, svn_depth_infinity, NULL,
+                                       TRUE, FALSE, svn_depth_infinity, 
+                                       ib->native_eol,
                                        ib->ctx, ib->iter_pool));
           else
             SVN_ERR(svn_client__checkout_internal
@@ -872,7 +877,8 @@ handle_external_item_change(const void *
             SVN_ERR(svn_client_export4(NULL, new_item->url, local_abspath,
                                        &(new_item->peg_revision),
                                        &(new_item->revision),
-                                       FALSE, TRUE, svn_depth_infinity, NULL,
+                                       FALSE, TRUE, svn_depth_infinity,
+                                       ib->native_eol,
                                        ib->ctx, ib->iter_pool));
           else
             SVN_ERR(switch_file_external(local_abspath,
@@ -909,7 +915,7 @@ handle_external_item_change(const void *
       if (! lock_existed)
         {
           SVN_ERR(svn_wc__acquire_write_lock(NULL, ib->ctx->wc_ctx,
-                                             local_abspath,
+                                             local_abspath, FALSE,
                                              ib->iter_pool,
                                              ib->iter_pool));
         }
@@ -1067,6 +1073,9 @@ struct handle_externals_desc_change_bato
   svn_boolean_t *timestamp_sleep;
   svn_boolean_t is_export;
 
+  /* Passed to svn_client_exportX() */
+  const char *native_eol;
+
   apr_pool_t *pool;
 };
 
@@ -1157,6 +1166,7 @@ handle_externals_desc_change(const void 
   ib.repos_root_url    = cb->repos_root_url;
   ib.ctx               = cb->ctx;
   ib.is_export         = cb->is_export;
+  ib.native_eol        = cb->native_eol;
   ib.timestamp_sleep   = cb->timestamp_sleep;
   ib.pool              = cb->pool;
   ib.iter_pool         = svn_pool_create(cb->pool);
@@ -1261,6 +1271,7 @@ svn_client__handle_externals(apr_hash_t 
   cb.ctx               = ctx;
   cb.timestamp_sleep   = timestamp_sleep;
   cb.is_export         = FALSE;
+  cb.native_eol        = NULL;
   cb.pool              = pool;
 
   return svn_hash_diff(cb.externals_old, cb.externals_new,
@@ -1275,6 +1286,7 @@ svn_client__fetch_externals(apr_hash_t *
                             const char *repos_root_url,
                             svn_depth_t requested_depth,
                             svn_boolean_t is_export,
+                            const char *native_eol,
                             svn_boolean_t *timestamp_sleep,
                             svn_client_ctx_t *ctx,
                             apr_pool_t *pool)
@@ -1292,6 +1304,7 @@ svn_client__fetch_externals(apr_hash_t *
   cb.to_abspath        = to_abspath;
   cb.repos_root_url    = repos_root_url;
   cb.timestamp_sleep   = timestamp_sleep;
+  cb.native_eol        = native_eol;
   cb.is_export         = is_export;
   cb.pool              = pool;
 
@@ -1367,7 +1380,7 @@ svn_client__do_external_status(svn_clien
           SVN_ERR(svn_client_status5(NULL, ctx, fullpath,
                                      &(external->revision),
                                      depth, get_all, update,
-                                     no_ignore, FALSE, NULL,
+                                     no_ignore, FALSE, FALSE, NULL,
                                      status_func, status_baton,
                                      iterpool));
         }