]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/diff.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_client / diff.c
1 /*
2  * diff.c: comparing
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23
24 /* ==================================================================== */
25
26
27 \f
28 /*** Includes. ***/
29
30 #include <apr_strings.h>
31 #include <apr_pools.h>
32 #include <apr_hash.h>
33 #include "svn_types.h"
34 #include "svn_hash.h"
35 #include "svn_wc.h"
36 #include "svn_diff.h"
37 #include "svn_mergeinfo.h"
38 #include "svn_client.h"
39 #include "svn_string.h"
40 #include "svn_error.h"
41 #include "svn_dirent_uri.h"
42 #include "svn_path.h"
43 #include "svn_io.h"
44 #include "svn_utf.h"
45 #include "svn_pools.h"
46 #include "svn_config.h"
47 #include "svn_props.h"
48 #include "svn_subst.h"
49 #include "client.h"
50
51 #include "private/svn_wc_private.h"
52 #include "private/svn_diff_private.h"
53 #include "private/svn_subr_private.h"
54 #include "private/svn_io_private.h"
55 #include "private/svn_ra_private.h"
56
57 #include "svn_private_config.h"
58
59 \f
60 /* Utilities */
61
62 #define DIFF_REVNUM_NONEXISTENT ((svn_revnum_t) -100)
63
64 #define MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir) \
65         svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL, \
66                           _("Path '%s' must be an immediate child of " \
67                             "the directory '%s'"), path, relative_to_dir)
68
69 /* Calculate the repository relative path of DIFF_RELPATH, using
70  * SESSION_RELPATH and WC_CTX, and return the result in *REPOS_RELPATH.
71  * ORIG_TARGET is the related original target passed to the diff command,
72  * and may be used to derive leading path components missing from PATH.
73  * ANCHOR is the local path where the diff editor is anchored.
74  * Do all allocations in POOL. */
75 static svn_error_t *
76 make_repos_relpath(const char **repos_relpath,
77                    const char *diff_relpath,
78                    const char *orig_target,
79                    const char *session_relpath,
80                    svn_wc_context_t *wc_ctx,
81                    const char *anchor,
82                    apr_pool_t *result_pool,
83                    apr_pool_t *scratch_pool)
84 {
85   const char *local_abspath;
86
87   if (! session_relpath
88       || (anchor && !svn_path_is_url(orig_target)))
89     {
90       svn_error_t *err;
91       /* We're doing a WC-WC diff, so we can retrieve all information we
92        * need from the working copy. */
93       SVN_ERR(svn_dirent_get_absolute(&local_abspath,
94                                       svn_dirent_join(anchor, diff_relpath,
95                                                       scratch_pool),
96                                       scratch_pool));
97
98       err = svn_wc__node_get_repos_info(NULL, repos_relpath, NULL, NULL,
99                                         wc_ctx, local_abspath,
100                                         result_pool, scratch_pool);
101
102       if (!session_relpath
103           || ! err
104           || (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND))
105         {
106            return svn_error_trace(err);
107         }
108
109       /* The path represents a local working copy path, but does not
110          exist. Fall through to calculate an in-repository location
111          based on the ra session */
112
113       /* ### Maybe we should use the nearest existing ancestor instead? */
114       svn_error_clear(err);
115     }
116
117   *repos_relpath = svn_relpath_join(session_relpath, diff_relpath,
118                                     result_pool);
119
120   return SVN_NO_ERROR;
121 }
122
123 /* Adjust *INDEX_PATH, *ORIG_PATH_1 and *ORIG_PATH_2, representing the changed
124  * node and the two original targets passed to the diff command, to handle the
125  * case when we're dealing with different anchors. RELATIVE_TO_DIR is the
126  * directory the diff target should be considered relative to.
127  * ANCHOR is the local path where the diff editor is anchored. The resulting
128  * values are allocated in RESULT_POOL and temporary allocations are performed
129  * in SCRATCH_POOL. */
130 static svn_error_t *
131 adjust_paths_for_diff_labels(const char **index_path,
132                              const char **orig_path_1,
133                              const char **orig_path_2,
134                              const char *relative_to_dir,
135                              const char *anchor,
136                              apr_pool_t *result_pool,
137                              apr_pool_t *scratch_pool)
138 {
139   const char *new_path = *index_path;
140   const char *new_path1 = *orig_path_1;
141   const char *new_path2 = *orig_path_2;
142
143   if (anchor)
144     new_path = svn_dirent_join(anchor, new_path, result_pool);
145
146   if (relative_to_dir)
147     {
148       /* Possibly adjust the paths shown in the output (see issue #2723). */
149       const char *child_path = svn_dirent_is_child(relative_to_dir, new_path,
150                                                    result_pool);
151
152       if (child_path)
153         new_path = child_path;
154       else if (! strcmp(relative_to_dir, new_path))
155         new_path = ".";
156       else
157         return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir);
158     }
159
160   {
161     apr_size_t len;
162     svn_boolean_t is_url1;
163     svn_boolean_t is_url2;
164     /* ### Holy cow.  Due to anchor/target weirdness, we can't
165        simply join dwi->orig_path_1 with path, ditto for
166        orig_path_2.  That will work when they're directory URLs, but
167        not for file URLs.  Nor can we just use anchor1 and anchor2
168        from do_diff(), at least not without some more logic here.
169        What a nightmare.
170
171        For now, to distinguish the two paths, we'll just put the
172        unique portions of the original targets in parentheses after
173        the received path, with ellipses for handwaving.  This makes
174        the labels a bit clumsy, but at least distinctive.  Better
175        solutions are possible, they'll just take more thought. */
176
177     /* ### BH: We can now just construct the repos_relpath, etc. as the
178            anchor is available. See also make_repos_relpath() */
179
180     is_url1 = svn_path_is_url(new_path1);
181     is_url2 = svn_path_is_url(new_path2);
182
183     if (is_url1 && is_url2)
184       len = strlen(svn_uri_get_longest_ancestor(new_path1, new_path2,
185                                                 scratch_pool));
186     else if (!is_url1 && !is_url2)
187       len = strlen(svn_dirent_get_longest_ancestor(new_path1, new_path2,
188                                                    scratch_pool));
189     else
190       len = 0; /* Path and URL */
191
192     new_path1 += len;
193     new_path2 += len;
194   }
195
196   /* ### Should diff labels print paths in local style?  Is there
197      already a standard for this?  In any case, this code depends on
198      a particular style, so not calling svn_dirent_local_style() on the
199      paths below.*/
200
201   if (new_path[0] == '\0')
202     new_path = ".";
203
204   if (new_path1[0] == '\0')
205     new_path1 = new_path;
206   else if (svn_path_is_url(new_path1))
207     new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path1);
208   else if (new_path1[0] == '/')
209     new_path1 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path1);
210   else
211     new_path1 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path1);
212
213   if (new_path2[0] == '\0')
214     new_path2 = new_path;
215   else if (svn_path_is_url(new_path2))
216     new_path2 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path2);
217   else if (new_path2[0] == '/')
218     new_path2 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path2);
219   else
220     new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2);
221
222   *index_path = new_path;
223   *orig_path_1 = new_path1;
224   *orig_path_2 = new_path2;
225
226   return SVN_NO_ERROR;
227 }
228
229
230 /* Generate a label for the diff output for file PATH at revision REVNUM.
231    If REVNUM is invalid then it is assumed to be the current working
232    copy.  Assumes the paths are already in the desired style (local
233    vs internal).  Allocate the label in POOL. */
234 static const char *
235 diff_label(const char *path,
236            svn_revnum_t revnum,
237            apr_pool_t *pool)
238 {
239   const char *label;
240   if (revnum >= 0)
241     label = apr_psprintf(pool, _("%s\t(revision %ld)"), path, revnum);
242   else if (revnum == DIFF_REVNUM_NONEXISTENT)
243     label = apr_psprintf(pool, _("%s\t(nonexistent)"), path);
244   else /* SVN_INVALID_REVNUM */
245     label = apr_psprintf(pool, _("%s\t(working copy)"), path);
246
247   return label;
248 }
249
250 /* Print a git diff header for an addition within a diff between PATH1 and
251  * PATH2 to the stream OS using HEADER_ENCODING.
252  * All allocations are done in RESULT_POOL. */
253 static svn_error_t *
254 print_git_diff_header_added(svn_stream_t *os, const char *header_encoding,
255                             const char *path1, const char *path2,
256                             apr_pool_t *result_pool)
257 {
258   SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
259                                       "diff --git a/%s b/%s%s",
260                                       path1, path2, APR_EOL_STR));
261   SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
262                                       "new file mode 10644" APR_EOL_STR));
263   return SVN_NO_ERROR;
264 }
265
266 /* Print a git diff header for a deletion within a diff between PATH1 and
267  * PATH2 to the stream OS using HEADER_ENCODING.
268  * All allocations are done in RESULT_POOL. */
269 static svn_error_t *
270 print_git_diff_header_deleted(svn_stream_t *os, const char *header_encoding,
271                               const char *path1, const char *path2,
272                               apr_pool_t *result_pool)
273 {
274   SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
275                                       "diff --git a/%s b/%s%s",
276                                       path1, path2, APR_EOL_STR));
277   SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
278                                       "deleted file mode 10644"
279                                       APR_EOL_STR));
280   return SVN_NO_ERROR;
281 }
282
283 /* Print a git diff header for a copy from COPYFROM_PATH to PATH to the stream
284  * OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
285 static svn_error_t *
286 print_git_diff_header_copied(svn_stream_t *os, const char *header_encoding,
287                              const char *copyfrom_path,
288                              svn_revnum_t copyfrom_rev,
289                              const char *path,
290                              apr_pool_t *result_pool)
291 {
292   SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
293                                       "diff --git a/%s b/%s%s",
294                                       copyfrom_path, path, APR_EOL_STR));
295   if (copyfrom_rev != SVN_INVALID_REVNUM)
296     SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
297                                         "copy from %s@%ld%s", copyfrom_path,
298                                         copyfrom_rev, APR_EOL_STR));
299   else
300     SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
301                                         "copy from %s%s", copyfrom_path,
302                                         APR_EOL_STR));
303   SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
304                                       "copy to %s%s", path, APR_EOL_STR));
305   return SVN_NO_ERROR;
306 }
307
308 /* Print a git diff header for a rename from COPYFROM_PATH to PATH to the
309  * stream OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
310 static svn_error_t *
311 print_git_diff_header_renamed(svn_stream_t *os, const char *header_encoding,
312                               const char *copyfrom_path, const char *path,
313                               apr_pool_t *result_pool)
314 {
315   SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
316                                       "diff --git a/%s b/%s%s",
317                                       copyfrom_path, path, APR_EOL_STR));
318   SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
319                                       "rename from %s%s", copyfrom_path,
320                                       APR_EOL_STR));
321   SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
322                                       "rename to %s%s", path, APR_EOL_STR));
323   return SVN_NO_ERROR;
324 }
325
326 /* Print a git diff header for a modification within a diff between PATH1 and
327  * PATH2 to the stream OS using HEADER_ENCODING.
328  * All allocations are done in RESULT_POOL. */
329 static svn_error_t *
330 print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding,
331                                const char *path1, const char *path2,
332                                apr_pool_t *result_pool)
333 {
334   SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
335                                       "diff --git a/%s b/%s%s",
336                                       path1, path2, APR_EOL_STR));
337   return SVN_NO_ERROR;
338 }
339
340 /* Print a git diff header showing the OPERATION to the stream OS using
341  * HEADER_ENCODING. Return suitable diff labels for the git diff in *LABEL1
342  * and *LABEL2. REPOS_RELPATH1 and REPOS_RELPATH2 are relative to reposroot.
343  * are the paths passed to the original diff command. REV1 and REV2 are
344  * revisions being diffed. COPYFROM_PATH and COPYFROM_REV indicate where the
345  * diffed item was copied from.
346  * Use SCRATCH_POOL for temporary allocations. */
347 static svn_error_t *
348 print_git_diff_header(svn_stream_t *os,
349                       const char **label1, const char **label2,
350                       svn_diff_operation_kind_t operation,
351                       const char *repos_relpath1,
352                       const char *repos_relpath2,
353                       svn_revnum_t rev1,
354                       svn_revnum_t rev2,
355                       const char *copyfrom_path,
356                       svn_revnum_t copyfrom_rev,
357                       const char *header_encoding,
358                       apr_pool_t *scratch_pool)
359 {
360   if (operation == svn_diff_op_deleted)
361     {
362       SVN_ERR(print_git_diff_header_deleted(os, header_encoding,
363                                             repos_relpath1, repos_relpath2,
364                                             scratch_pool));
365       *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
366                            rev1, scratch_pool);
367       *label2 = diff_label("/dev/null", rev2, scratch_pool);
368
369     }
370   else if (operation == svn_diff_op_copied)
371     {
372       SVN_ERR(print_git_diff_header_copied(os, header_encoding,
373                                            copyfrom_path, copyfrom_rev,
374                                            repos_relpath2,
375                                            scratch_pool));
376       *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
377                            rev1, scratch_pool);
378       *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
379                            rev2, scratch_pool);
380     }
381   else if (operation == svn_diff_op_added)
382     {
383       SVN_ERR(print_git_diff_header_added(os, header_encoding,
384                                           repos_relpath1, repos_relpath2,
385                                           scratch_pool));
386       *label1 = diff_label("/dev/null", rev1, scratch_pool);
387       *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
388                            rev2, scratch_pool);
389     }
390   else if (operation == svn_diff_op_modified)
391     {
392       SVN_ERR(print_git_diff_header_modified(os, header_encoding,
393                                              repos_relpath1, repos_relpath2,
394                                              scratch_pool));
395       *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
396                            rev1, scratch_pool);
397       *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
398                            rev2, scratch_pool);
399     }
400   else if (operation == svn_diff_op_moved)
401     {
402       SVN_ERR(print_git_diff_header_renamed(os, header_encoding,
403                                             copyfrom_path, repos_relpath2,
404                                             scratch_pool));
405       *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
406                            rev1, scratch_pool);
407       *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
408                            rev2, scratch_pool);
409     }
410
411   return SVN_NO_ERROR;
412 }
413
414 /* A helper func that writes out verbal descriptions of property diffs
415    to OUTSTREAM.   Of course, OUTSTREAM will probably be whatever was
416    passed to svn_client_diff6(), which is probably stdout.
417
418    ### FIXME needs proper docstring
419
420    If USE_GIT_DIFF_FORMAT is TRUE, pring git diff headers, which always
421    show paths relative to the repository root. RA_SESSION and WC_CTX are
422    needed to normalize paths relative the repository root, and are ignored
423    if USE_GIT_DIFF_FORMAT is FALSE.
424
425    ANCHOR is the local path where the diff editor is anchored. */
426 static svn_error_t *
427 display_prop_diffs(const apr_array_header_t *propchanges,
428                    apr_hash_t *original_props,
429                    const char *diff_relpath,
430                    const char *anchor,
431                    const char *orig_path1,
432                    const char *orig_path2,
433                    svn_revnum_t rev1,
434                    svn_revnum_t rev2,
435                    const char *encoding,
436                    svn_stream_t *outstream,
437                    const char *relative_to_dir,
438                    svn_boolean_t show_diff_header,
439                    svn_boolean_t use_git_diff_format,
440                    const char *ra_session_relpath,
441                    svn_cancel_func_t cancel_func,
442                    void *cancel_baton,
443                    svn_wc_context_t *wc_ctx,
444                    apr_pool_t *scratch_pool)
445 {
446   const char *repos_relpath1 = NULL;
447   const char *repos_relpath2 = NULL;
448   const char *index_path = diff_relpath;
449   const char *adjusted_path1 = orig_path1;
450   const char *adjusted_path2 = orig_path2;
451
452   if (use_git_diff_format)
453     {
454       SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, orig_path1,
455                                  ra_session_relpath, wc_ctx, anchor,
456                                  scratch_pool, scratch_pool));
457       SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, orig_path2,
458                                  ra_session_relpath, wc_ctx, anchor,
459                                  scratch_pool, scratch_pool));
460     }
461
462   /* If we're creating a diff on the wc root, path would be empty. */
463   SVN_ERR(adjust_paths_for_diff_labels(&index_path, &adjusted_path1,
464                                        &adjusted_path2,
465                                        relative_to_dir, anchor,
466                                        scratch_pool, scratch_pool));
467
468   if (show_diff_header)
469     {
470       const char *label1;
471       const char *label2;
472
473       label1 = diff_label(adjusted_path1, rev1, scratch_pool);
474       label2 = diff_label(adjusted_path2, rev2, scratch_pool);
475
476       /* ### Should we show the paths in platform specific format,
477        * ### diff_content_changed() does not! */
478
479       SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
480                                           "Index: %s" APR_EOL_STR
481                                           SVN_DIFF__EQUAL_STRING APR_EOL_STR,
482                                           index_path));
483
484       if (use_git_diff_format)
485         SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
486                                       svn_diff_op_modified,
487                                       repos_relpath1, repos_relpath2,
488                                       rev1, rev2, NULL,
489                                       SVN_INVALID_REVNUM,
490                                       encoding, scratch_pool));
491
492       /* --- label1
493        * +++ label2 */
494       SVN_ERR(svn_diff__unidiff_write_header(
495         outstream, encoding, label1, label2, scratch_pool));
496     }
497
498   SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
499                                       _("%sProperty changes on: %s%s"),
500                                       APR_EOL_STR,
501                                       use_git_diff_format
502                                             ? repos_relpath1
503                                             : index_path,
504                                       APR_EOL_STR));
505
506   SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
507                                       SVN_DIFF__UNDER_STRING APR_EOL_STR));
508
509   SVN_ERR(svn_diff__display_prop_diffs(
510             outstream, encoding, propchanges, original_props,
511             TRUE /* pretty_print_mergeinfo */,
512             -1 /* context_size */,
513             cancel_func, cancel_baton, scratch_pool));
514
515   return SVN_NO_ERROR;
516 }
517
518 /*-----------------------------------------------------------------*/
519 \f
520 /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
521
522 /* State provided by the diff drivers; used by the diff writer */
523 typedef struct diff_driver_info_t
524 {
525   /* The anchor to prefix before wc paths */
526   const char *anchor;
527
528    /* Relative path of ra session from repos_root_url */
529   const char *session_relpath;
530
531   /* The original targets passed to the diff command.  We may need
532      these to construct distinctive diff labels when comparing the
533      same relative path in the same revision, under different anchors
534      (for example, when comparing a trunk against a branch). */
535   const char *orig_path_1;
536   const char *orig_path_2;
537 } diff_driver_info_t;
538
539
540 /* Diff writer state */
541 typedef struct diff_writer_info_t
542 {
543   /* If non-null, the external diff command to invoke. */
544   const char *diff_cmd;
545
546   /* This is allocated in this struct's pool or a higher-up pool. */
547   union {
548     /* If 'diff_cmd' is null, then this is the parsed options to
549        pass to the internal libsvn_diff implementation. */
550     svn_diff_file_options_t *for_internal;
551     /* Else if 'diff_cmd' is non-null, then... */
552     struct {
553       /* ...this is an argument array for the external command, and */
554       const char **argv;
555       /* ...this is the length of argv. */
556       int argc;
557     } for_external;
558   } options;
559
560   apr_pool_t *pool;
561   svn_stream_t *outstream;
562   svn_stream_t *errstream;
563
564   const char *header_encoding;
565
566   /* Set this if you want diff output even for binary files. */
567   svn_boolean_t force_binary;
568
569   /* The directory that diff target paths should be considered as
570      relative to for output generation (see issue #2723). */
571   const char *relative_to_dir;
572
573   /* Whether property differences are ignored. */
574   svn_boolean_t ignore_properties;
575
576   /* Whether to show only property changes. */
577   svn_boolean_t properties_only;
578
579   /* Whether we're producing a git-style diff. */
580   svn_boolean_t use_git_diff_format;
581
582   /* Whether addition of a file is summarized versus showing a full diff. */
583   svn_boolean_t no_diff_added;
584
585   /* Whether deletion of a file is summarized versus showing a full diff. */
586   svn_boolean_t no_diff_deleted;
587
588   /* Whether to ignore copyfrom information when showing adds */
589   svn_boolean_t show_copies_as_adds;
590
591   /* Empty files for creating diffs or NULL if not used yet */
592   const char *empty_file;
593
594   svn_wc_context_t *wc_ctx;
595
596   svn_cancel_func_t cancel_func;
597   void *cancel_baton;
598
599   struct diff_driver_info_t ddi;
600 } diff_writer_info_t;
601
602 /* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added
603  */
604 static svn_error_t *
605 diff_props_changed(const char *diff_relpath,
606                    svn_revnum_t rev1,
607                    svn_revnum_t rev2,
608                    const apr_array_header_t *propchanges,
609                    apr_hash_t *original_props,
610                    svn_boolean_t show_diff_header,
611                    diff_writer_info_t *dwi,
612                    apr_pool_t *scratch_pool)
613 {
614   apr_array_header_t *props;
615
616   /* If property differences are ignored, there's nothing to do. */
617   if (dwi->ignore_properties)
618     return SVN_NO_ERROR;
619
620   SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props,
621                                scratch_pool));
622
623   if (props->nelts > 0)
624     {
625       /* We're using the revnums from the dwi since there's
626        * no revision argument to the svn_wc_diff_callback_t
627        * dir_props_changed(). */
628       SVN_ERR(display_prop_diffs(props, original_props,
629                                  diff_relpath,
630                                  dwi->ddi.anchor,
631                                  dwi->ddi.orig_path_1,
632                                  dwi->ddi.orig_path_2,
633                                  rev1,
634                                  rev2,
635                                  dwi->header_encoding,
636                                  dwi->outstream,
637                                  dwi->relative_to_dir,
638                                  show_diff_header,
639                                  dwi->use_git_diff_format,
640                                  dwi->ddi.session_relpath,
641                                  dwi->cancel_func,
642                                  dwi->cancel_baton,
643                                  dwi->wc_ctx,
644                                  scratch_pool));
645     }
646
647   return SVN_NO_ERROR;
648 }
649
650 /* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and
651    REV2 are used in the headers to indicate the file and revisions.  If either
652    MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
653    but instead print a warning message.
654
655    If FORCE_DIFF is TRUE, always write a diff, even for empty diffs.
656
657    Set *WROTE_HEADER to TRUE if a diff header was written */
658 static svn_error_t *
659 diff_content_changed(svn_boolean_t *wrote_header,
660                      const char *diff_relpath,
661                      const char *tmpfile1,
662                      const char *tmpfile2,
663                      svn_revnum_t rev1,
664                      svn_revnum_t rev2,
665                      const char *mimetype1,
666                      const char *mimetype2,
667                      svn_diff_operation_kind_t operation,
668                      svn_boolean_t force_diff,
669                      const char *copyfrom_path,
670                      svn_revnum_t copyfrom_rev,
671                      diff_writer_info_t *dwi,
672                      apr_pool_t *scratch_pool)
673 {
674   const char *rel_to_dir = dwi->relative_to_dir;
675   svn_stream_t *outstream = dwi->outstream;
676   const char *label1, *label2;
677   svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
678   const char *index_path = diff_relpath;
679   const char *path1 = dwi->ddi.orig_path_1;
680   const char *path2 = dwi->ddi.orig_path_2;
681
682   /* If only property differences are shown, there's nothing to do. */
683   if (dwi->properties_only)
684     return SVN_NO_ERROR;
685
686   /* Generate the diff headers. */
687   SVN_ERR(adjust_paths_for_diff_labels(&index_path, &path1, &path2,
688                                        rel_to_dir, dwi->ddi.anchor,
689                                        scratch_pool, scratch_pool));
690
691   label1 = diff_label(path1, rev1, scratch_pool);
692   label2 = diff_label(path2, rev2, scratch_pool);
693
694   /* Possible easy-out: if either mime-type is binary and force was not
695      specified, don't attempt to generate a viewable diff at all.
696      Print a warning and exit. */
697   if (mimetype1)
698     mt1_binary = svn_mime_type_is_binary(mimetype1);
699   if (mimetype2)
700     mt2_binary = svn_mime_type_is_binary(mimetype2);
701
702   if (! dwi->force_binary && (mt1_binary || mt2_binary))
703     {
704       /* Print out the diff header. */
705       SVN_ERR(svn_stream_printf_from_utf8(outstream,
706                dwi->header_encoding, scratch_pool,
707                "Index: %s" APR_EOL_STR
708                SVN_DIFF__EQUAL_STRING APR_EOL_STR,
709                index_path));
710
711       /* ### Print git diff headers. */
712
713       if (dwi->use_git_diff_format)
714         {
715           svn_stream_t *left_stream;
716           svn_stream_t *right_stream;
717           const char *repos_relpath1;
718           const char *repos_relpath2;
719
720           SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
721                                       dwi->ddi.orig_path_1,
722                                       dwi->ddi.session_relpath,
723                                       dwi->wc_ctx,
724                                       dwi->ddi.anchor,
725                                       scratch_pool, scratch_pool));
726           SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
727                                       dwi->ddi.orig_path_2,
728                                       dwi->ddi.session_relpath,
729                                       dwi->wc_ctx,
730                                       dwi->ddi.anchor,
731                                       scratch_pool, scratch_pool));
732           SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
733                                         operation,
734                                         repos_relpath1, repos_relpath2,
735                                         rev1, rev2,
736                                         copyfrom_path,
737                                         copyfrom_rev,
738                                         dwi->header_encoding,
739                                         scratch_pool));
740
741           SVN_ERR(svn_stream_open_readonly(&left_stream, tmpfile1,
742                                            scratch_pool, scratch_pool));
743           SVN_ERR(svn_stream_open_readonly(&right_stream, tmpfile2,
744                                            scratch_pool, scratch_pool));
745           SVN_ERR(svn_diff_output_binary(outstream,
746                                          left_stream, right_stream,
747                                          dwi->cancel_func, dwi->cancel_baton,
748                                          scratch_pool));
749         }
750       else
751         {
752           SVN_ERR(svn_stream_printf_from_utf8(outstream,
753                    dwi->header_encoding, scratch_pool,
754                    _("Cannot display: file marked as a binary type.%s"),
755                    APR_EOL_STR));
756
757           if (mt1_binary && !mt2_binary)
758             SVN_ERR(svn_stream_printf_from_utf8(outstream,
759                      dwi->header_encoding, scratch_pool,
760                      "svn:mime-type = %s" APR_EOL_STR, mimetype1));
761           else if (mt2_binary && !mt1_binary)
762             SVN_ERR(svn_stream_printf_from_utf8(outstream,
763                      dwi->header_encoding, scratch_pool,
764                      "svn:mime-type = %s" APR_EOL_STR, mimetype2));
765           else if (mt1_binary && mt2_binary)
766             {
767               if (strcmp(mimetype1, mimetype2) == 0)
768                 SVN_ERR(svn_stream_printf_from_utf8(outstream,
769                          dwi->header_encoding, scratch_pool,
770                          "svn:mime-type = %s" APR_EOL_STR,
771                          mimetype1));
772               else
773                 SVN_ERR(svn_stream_printf_from_utf8(outstream,
774                          dwi->header_encoding, scratch_pool,
775                          "svn:mime-type = (%s, %s)" APR_EOL_STR,
776                          mimetype1, mimetype2));
777             }
778         }
779
780       /* Exit early. */
781       return SVN_NO_ERROR;
782     }
783
784
785   if (dwi->diff_cmd)
786     {
787       svn_stream_t *errstream = dwi->errstream;
788       apr_file_t *outfile;
789       apr_file_t *errfile;
790       const char *outfilename;
791       const char *errfilename;
792       svn_stream_t *stream;
793       int exitcode;
794
795       /* Print out the diff header. */
796       SVN_ERR(svn_stream_printf_from_utf8(outstream,
797                dwi->header_encoding, scratch_pool,
798                "Index: %s" APR_EOL_STR
799                SVN_DIFF__EQUAL_STRING APR_EOL_STR,
800                index_path));
801
802       /* ### Do we want to add git diff headers here too? I'd say no. The
803        * ### 'Index' and '===' line is something subversion has added. The rest
804        * ### is up to the external diff application. We may be dealing with
805        * ### a non-git compatible diff application.*/
806
807       /* We deal in streams, but svn_io_run_diff2() deals in file handles,
808          so we may need to make temporary files and then copy the contents
809          to our stream. */
810       outfile = svn_stream__aprfile(outstream);
811       if (outfile)
812         outfilename = NULL;
813       else
814         SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
815                                          svn_io_file_del_on_pool_cleanup,
816                                          scratch_pool, scratch_pool));
817
818       errfile = svn_stream__aprfile(errstream);
819       if (errfile)
820         errfilename = NULL;
821       else
822         SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
823                                          svn_io_file_del_on_pool_cleanup,
824                                          scratch_pool, scratch_pool));
825
826       SVN_ERR(svn_io_run_diff2(".",
827                                dwi->options.for_external.argv,
828                                dwi->options.for_external.argc,
829                                label1, label2,
830                                tmpfile1, tmpfile2,
831                                &exitcode, outfile, errfile,
832                                dwi->diff_cmd, scratch_pool));
833
834       /* Now, open and copy our files to our output streams. */
835       if (outfilename)
836         {
837           SVN_ERR(svn_io_file_close(outfile, scratch_pool));
838           SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
839                                            scratch_pool, scratch_pool));
840           SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(outstream,
841                                                              scratch_pool),
842                                    NULL, NULL, scratch_pool));
843         }
844       if (errfilename)
845         {
846           SVN_ERR(svn_io_file_close(errfile, scratch_pool));
847           SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
848                                            scratch_pool, scratch_pool));
849           SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(errstream,
850                                                              scratch_pool),
851                                    NULL, NULL, scratch_pool));
852         }
853
854       /* If we have printed a diff for this path, mark it as visited. */
855       if (exitcode == 1)
856         *wrote_header = TRUE;
857     }
858   else   /* use libsvn_diff to generate the diff  */
859     {
860       svn_diff_t *diff;
861
862       SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2,
863                                    dwi->options.for_internal,
864                                    scratch_pool));
865
866       if (force_diff
867           || dwi->use_git_diff_format
868           || svn_diff_contains_diffs(diff))
869         {
870           /* Print out the diff header. */
871           SVN_ERR(svn_stream_printf_from_utf8(outstream,
872                    dwi->header_encoding, scratch_pool,
873                    "Index: %s" APR_EOL_STR
874                    SVN_DIFF__EQUAL_STRING APR_EOL_STR,
875                    index_path));
876
877           if (dwi->use_git_diff_format)
878             {
879               const char *repos_relpath1;
880               const char *repos_relpath2;
881               SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
882                                          dwi->ddi.orig_path_1,
883                                          dwi->ddi.session_relpath,
884                                          dwi->wc_ctx,
885                                          dwi->ddi.anchor,
886                                          scratch_pool, scratch_pool));
887               SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
888                                          dwi->ddi.orig_path_2,
889                                          dwi->ddi.session_relpath,
890                                          dwi->wc_ctx,
891                                          dwi->ddi.anchor,
892                                          scratch_pool, scratch_pool));
893               SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
894                                             operation,
895                                             repos_relpath1, repos_relpath2,
896                                             rev1, rev2,
897                                             copyfrom_path,
898                                             copyfrom_rev,
899                                             dwi->header_encoding,
900                                             scratch_pool));
901             }
902
903           /* Output the actual diff */
904           if (force_diff || svn_diff_contains_diffs(diff))
905             SVN_ERR(svn_diff_file_output_unified4(outstream, diff,
906                      tmpfile1, tmpfile2, label1, label2,
907                      dwi->header_encoding, rel_to_dir,
908                      dwi->options.for_internal->show_c_function,
909                      dwi->options.for_internal->context_size,
910                      dwi->cancel_func, dwi->cancel_baton,
911                      scratch_pool));
912
913           /* If we have printed a diff for this path, mark it as visited. */
914           if (dwi->use_git_diff_format || svn_diff_contains_diffs(diff))
915             *wrote_header = TRUE;
916         }
917     }
918
919   /* ### todo: someday we'll need to worry about whether we're going
920      to need to write a diff plug-in mechanism that makes use of the
921      two paths, instead of just blindly running SVN_CLIENT_DIFF.  */
922
923   return SVN_NO_ERROR;
924 }
925
926 /* An svn_diff_tree_processor_t callback. */
927 static svn_error_t *
928 diff_file_changed(const char *relpath,
929                   const svn_diff_source_t *left_source,
930                   const svn_diff_source_t *right_source,
931                   const char *left_file,
932                   const char *right_file,
933                   /*const*/ apr_hash_t *left_props,
934                   /*const*/ apr_hash_t *right_props,
935                   svn_boolean_t file_modified,
936                   const apr_array_header_t *prop_changes,
937                   void *file_baton,
938                   const struct svn_diff_tree_processor_t *processor,
939                   apr_pool_t *scratch_pool)
940 {
941   diff_writer_info_t *dwi = processor->baton;
942   svn_boolean_t wrote_header = FALSE;
943
944   if (file_modified)
945     SVN_ERR(diff_content_changed(&wrote_header, relpath,
946                                  left_file, right_file,
947                                  left_source->revision,
948                                  right_source->revision,
949                                  svn_prop_get_value(left_props,
950                                                     SVN_PROP_MIME_TYPE),
951                                  svn_prop_get_value(right_props,
952                                                     SVN_PROP_MIME_TYPE),
953                                  svn_diff_op_modified, FALSE,
954                                  NULL,
955                                  SVN_INVALID_REVNUM, dwi,
956                                  scratch_pool));
957   if (prop_changes->nelts > 0)
958     SVN_ERR(diff_props_changed(relpath,
959                                left_source->revision,
960                                right_source->revision, prop_changes,
961                                left_props, !wrote_header,
962                                dwi, scratch_pool));
963   return SVN_NO_ERROR;
964 }
965
966 /* Because the repos-diff editor passes at least one empty file to
967    each of these next two functions, they can be dumb wrappers around
968    the main workhorse routine. */
969
970 /* An svn_diff_tree_processor_t callback. */
971 static svn_error_t *
972 diff_file_added(const char *relpath,
973                 const svn_diff_source_t *copyfrom_source,
974                 const svn_diff_source_t *right_source,
975                 const char *copyfrom_file,
976                 const char *right_file,
977                 /*const*/ apr_hash_t *copyfrom_props,
978                 /*const*/ apr_hash_t *right_props,
979                 void *file_baton,
980                 const struct svn_diff_tree_processor_t *processor,
981                 apr_pool_t *scratch_pool)
982 {
983   diff_writer_info_t *dwi = processor->baton;
984   svn_boolean_t wrote_header = FALSE;
985   const char *left_file;
986   apr_hash_t *left_props;
987   apr_array_header_t *prop_changes;
988
989   /* During repos->wc diff of a copy revision numbers obtained
990    * from the working copy are always SVN_INVALID_REVNUM. */
991   if (copyfrom_source && !dwi->show_copies_as_adds)
992     {
993       left_file = copyfrom_file;
994       left_props = copyfrom_props ? copyfrom_props : apr_hash_make(scratch_pool);
995     }
996   else
997     {
998       if (!dwi->empty_file)
999         SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file,
1000                                          NULL, svn_io_file_del_on_pool_cleanup,
1001                                          dwi->pool, scratch_pool));
1002
1003       left_file = dwi->empty_file;
1004       left_props = apr_hash_make(scratch_pool);
1005
1006       copyfrom_source = NULL;
1007       copyfrom_file = NULL;
1008     }
1009
1010   SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool));
1011
1012   if (dwi->no_diff_added)
1013     {
1014       const char *index_path = relpath;
1015
1016       if (dwi->ddi.anchor)
1017         index_path = svn_dirent_join(dwi->ddi.anchor, relpath,
1018                                      scratch_pool);
1019
1020       SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream,
1021                 dwi->header_encoding, scratch_pool,
1022                 "Index: %s (added)" APR_EOL_STR
1023                 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1024                 index_path));
1025       wrote_header = TRUE;
1026     }
1027   else if (copyfrom_source && right_file)
1028     SVN_ERR(diff_content_changed(&wrote_header, relpath,
1029                                  left_file, right_file,
1030                                  copyfrom_source->revision,
1031                                  right_source->revision,
1032                                  svn_prop_get_value(left_props,
1033                                                     SVN_PROP_MIME_TYPE),
1034                                  svn_prop_get_value(right_props,
1035                                                     SVN_PROP_MIME_TYPE),
1036                                  svn_diff_op_copied,
1037                                  TRUE /* force diff output */,
1038                                  copyfrom_source->repos_relpath,
1039                                  copyfrom_source->revision,
1040                                  dwi, scratch_pool));
1041   else if (right_file)
1042     SVN_ERR(diff_content_changed(&wrote_header, relpath,
1043                                  left_file, right_file,
1044                                  DIFF_REVNUM_NONEXISTENT,
1045                                  right_source->revision,
1046                                  svn_prop_get_value(left_props,
1047                                                     SVN_PROP_MIME_TYPE),
1048                                  svn_prop_get_value(right_props,
1049                                                     SVN_PROP_MIME_TYPE),
1050                                  svn_diff_op_added,
1051                                  TRUE /* force diff output */,
1052                                  NULL, SVN_INVALID_REVNUM,
1053                                  dwi, scratch_pool));
1054
1055   if (prop_changes->nelts > 0)
1056     SVN_ERR(diff_props_changed(relpath,
1057                                copyfrom_source ? copyfrom_source->revision
1058                                                : DIFF_REVNUM_NONEXISTENT,
1059                                right_source->revision,
1060                                prop_changes,
1061                                left_props, ! wrote_header,
1062                                dwi, scratch_pool));
1063
1064   return SVN_NO_ERROR;
1065 }
1066
1067 /* An svn_diff_tree_processor_t callback. */
1068 static svn_error_t *
1069 diff_file_deleted(const char *relpath,
1070                   const svn_diff_source_t *left_source,
1071                   const char *left_file,
1072                   /*const*/ apr_hash_t *left_props,
1073                   void *file_baton,
1074                   const struct svn_diff_tree_processor_t *processor,
1075                   apr_pool_t *scratch_pool)
1076 {
1077   diff_writer_info_t *dwi = processor->baton;
1078
1079   if (dwi->no_diff_deleted)
1080     {
1081       const char *index_path = relpath;
1082
1083       if (dwi->ddi.anchor)
1084         index_path = svn_dirent_join(dwi->ddi.anchor, relpath,
1085                                      scratch_pool);
1086
1087       SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream,
1088                 dwi->header_encoding, scratch_pool,
1089                 "Index: %s (deleted)" APR_EOL_STR
1090                 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1091                 index_path));
1092     }
1093   else
1094     {
1095       svn_boolean_t wrote_header = FALSE;
1096
1097       if (!dwi->empty_file)
1098         SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file,
1099                                          NULL, svn_io_file_del_on_pool_cleanup,
1100                                          dwi->pool, scratch_pool));
1101
1102       if (left_file)
1103         SVN_ERR(diff_content_changed(&wrote_header, relpath,
1104                                      left_file, dwi->empty_file,
1105                                      left_source->revision,
1106                                      DIFF_REVNUM_NONEXISTENT,
1107                                      svn_prop_get_value(left_props,
1108                                                         SVN_PROP_MIME_TYPE),
1109                                      NULL,
1110                                      svn_diff_op_deleted, FALSE,
1111                                      NULL, SVN_INVALID_REVNUM,
1112                                      dwi,
1113                                      scratch_pool));
1114
1115       if (left_props && apr_hash_count(left_props))
1116         {
1117           apr_array_header_t *prop_changes;
1118
1119           SVN_ERR(svn_prop_diffs(&prop_changes, apr_hash_make(scratch_pool),
1120                                  left_props, scratch_pool));
1121
1122           SVN_ERR(diff_props_changed(relpath,
1123                                      left_source->revision,
1124                                      DIFF_REVNUM_NONEXISTENT,
1125                                      prop_changes,
1126                                      left_props, ! wrote_header,
1127                                      dwi, scratch_pool));
1128         }
1129     }
1130
1131   return SVN_NO_ERROR;
1132 }
1133
1134 /* An svn_wc_diff_callbacks4_t function. */
1135 static svn_error_t *
1136 diff_dir_changed(const char *relpath,
1137                  const svn_diff_source_t *left_source,
1138                  const svn_diff_source_t *right_source,
1139                  /*const*/ apr_hash_t *left_props,
1140                  /*const*/ apr_hash_t *right_props,
1141                  const apr_array_header_t *prop_changes,
1142                  void *dir_baton,
1143                  const struct svn_diff_tree_processor_t *processor,
1144                  apr_pool_t *scratch_pool)
1145 {
1146   diff_writer_info_t *dwi = processor->baton;
1147
1148   SVN_ERR(diff_props_changed(relpath,
1149                              left_source->revision,
1150                              right_source->revision,
1151                              prop_changes,
1152                              left_props,
1153                              TRUE /* show_diff_header */,
1154                              dwi,
1155                              scratch_pool));
1156
1157   return SVN_NO_ERROR;
1158 }
1159
1160 /* An svn_diff_tree_processor_t callback. */
1161 static svn_error_t *
1162 diff_dir_added(const char *relpath,
1163                const svn_diff_source_t *copyfrom_source,
1164                const svn_diff_source_t *right_source,
1165                /*const*/ apr_hash_t *copyfrom_props,
1166                /*const*/ apr_hash_t *right_props,
1167                void *dir_baton,
1168                const struct svn_diff_tree_processor_t *processor,
1169                apr_pool_t *scratch_pool)
1170 {
1171   diff_writer_info_t *dwi = processor->baton;
1172   apr_hash_t *left_props;
1173   apr_array_header_t *prop_changes;
1174
1175   if (dwi->no_diff_added)
1176     return SVN_NO_ERROR;
1177
1178   if (copyfrom_source && !dwi->show_copies_as_adds)
1179     {
1180       left_props = copyfrom_props ? copyfrom_props
1181                                   : apr_hash_make(scratch_pool);
1182     }
1183   else
1184     {
1185       left_props = apr_hash_make(scratch_pool);
1186       copyfrom_source = NULL;
1187     }
1188
1189   SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props,
1190                          scratch_pool));
1191
1192   return svn_error_trace(diff_props_changed(relpath,
1193                                             copyfrom_source ? copyfrom_source->revision
1194                                                             : DIFF_REVNUM_NONEXISTENT,
1195                                             right_source->revision,
1196                                             prop_changes,
1197                                             left_props,
1198                                             TRUE /* show_diff_header */,
1199                                             dwi,
1200                                             scratch_pool));
1201 }
1202
1203 /* An svn_diff_tree_processor_t callback. */
1204 static svn_error_t *
1205 diff_dir_deleted(const char *relpath,
1206                  const svn_diff_source_t *left_source,
1207                  /*const*/ apr_hash_t *left_props,
1208                  void *dir_baton,
1209                  const struct svn_diff_tree_processor_t *processor,
1210                  apr_pool_t *scratch_pool)
1211 {
1212   diff_writer_info_t *dwi = processor->baton;
1213   apr_array_header_t *prop_changes;
1214
1215   if (dwi->no_diff_deleted)
1216     return SVN_NO_ERROR;
1217
1218
1219   SVN_ERR(svn_prop_diffs(&prop_changes, apr_hash_make(scratch_pool),
1220                          left_props, scratch_pool));
1221
1222   SVN_ERR(diff_props_changed(relpath,
1223                              left_source->revision,
1224                              DIFF_REVNUM_NONEXISTENT,
1225                              prop_changes,
1226                              left_props,
1227                              TRUE /* show_diff_header */,
1228                              dwi,
1229                              scratch_pool));
1230
1231   return SVN_NO_ERROR;
1232 }
1233
1234 /*-----------------------------------------------------------------*/
1235 \f
1236 /** The logic behind 'svn diff' and 'svn merge'.  */
1237
1238
1239 /* Hi!  This is a comment left behind by Karl, and Ben is too afraid
1240    to erase it at this time, because he's not fully confident that all
1241    this knowledge has been grokked yet.
1242
1243    There are five cases:
1244       1. path is not a URL and start_revision != end_revision
1245       2. path is not a URL and start_revision == end_revision
1246       3. path is a URL and start_revision != end_revision
1247       4. path is a URL and start_revision == end_revision
1248       5. path is not a URL and no revisions given
1249
1250    With only one distinct revision the working copy provides the
1251    other.  When path is a URL there is no working copy. Thus
1252
1253      1: compare repository versions for URL coresponding to working copy
1254      2: compare working copy against repository version
1255      3: compare repository versions for URL
1256      4: nothing to do.
1257      5: compare working copy against text-base
1258
1259    Case 4 is not as stupid as it looks, for example it may occur if
1260    the user specifies two dates that resolve to the same revision.  */
1261
1262
1263 /** Check if paths PATH_OR_URL1 and PATH_OR_URL2 are urls and if the
1264  * revisions REVISION1 and REVISION2 are local. If PEG_REVISION is not
1265  * unspecified, ensure that at least one of the two revisions is not
1266  * BASE or WORKING.
1267  * If PATH_OR_URL1 can only be found in the repository, set *IS_REPOS1
1268  * to TRUE. If PATH_OR_URL2 can only be found in the repository, set
1269  * *IS_REPOS2 to TRUE. */
1270 static svn_error_t *
1271 check_paths(svn_boolean_t *is_repos1,
1272             svn_boolean_t *is_repos2,
1273             const char *path_or_url1,
1274             const char *path_or_url2,
1275             const svn_opt_revision_t *revision1,
1276             const svn_opt_revision_t *revision2,
1277             const svn_opt_revision_t *peg_revision)
1278 {
1279   svn_boolean_t is_local_rev1, is_local_rev2;
1280
1281   /* Verify our revision arguments in light of the paths. */
1282   if ((revision1->kind == svn_opt_revision_unspecified)
1283       || (revision2->kind == svn_opt_revision_unspecified))
1284     return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1285                             _("Not all required revisions are specified"));
1286
1287   /* Revisions can be said to be local or remote.
1288    * BASE and WORKING are local revisions.  */
1289   is_local_rev1 =
1290     ((revision1->kind == svn_opt_revision_base)
1291      || (revision1->kind == svn_opt_revision_working));
1292   is_local_rev2 =
1293     ((revision2->kind == svn_opt_revision_base)
1294      || (revision2->kind == svn_opt_revision_working));
1295
1296   if (peg_revision->kind != svn_opt_revision_unspecified &&
1297       is_local_rev1 && is_local_rev2)
1298     return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1299                             _("At least one revision must be something other "
1300                               "than BASE or WORKING when diffing a URL"));
1301
1302   /* Working copy paths with non-local revisions get turned into
1303      URLs.  We don't do that here, though.  We simply record that it
1304      needs to be done, which is information that helps us choose our
1305      diff helper function.  */
1306   *is_repos1 = ! is_local_rev1 || svn_path_is_url(path_or_url1);
1307   *is_repos2 = ! is_local_rev2 || svn_path_is_url(path_or_url2);
1308
1309   return SVN_NO_ERROR;
1310 }
1311
1312 /* Raise an error if the diff target URL does not exist at REVISION.
1313  * If REVISION does not equal OTHER_REVISION, mention both revisions in
1314  * the error message. Use RA_SESSION to contact the repository.
1315  * Use POOL for temporary allocations. */
1316 static svn_error_t *
1317 check_diff_target_exists(const char *url,
1318                          svn_revnum_t revision,
1319                          svn_revnum_t other_revision,
1320                          svn_ra_session_t *ra_session,
1321                          apr_pool_t *pool)
1322 {
1323   svn_node_kind_t kind;
1324   const char *session_url;
1325
1326   SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
1327
1328   if (strcmp(url, session_url) != 0)
1329     SVN_ERR(svn_ra_reparent(ra_session, url, pool));
1330
1331   SVN_ERR(svn_ra_check_path(ra_session, "", revision, &kind, pool));
1332   if (kind == svn_node_none)
1333     {
1334       if (revision == other_revision)
1335         return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1336                                  _("Diff target '%s' was not found in the "
1337                                    "repository at revision '%ld'"),
1338                                  url, revision);
1339       else
1340         return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1341                                  _("Diff target '%s' was not found in the "
1342                                    "repository at revision '%ld' or '%ld'"),
1343                                  url, revision, other_revision);
1344      }
1345
1346   if (strcmp(url, session_url) != 0)
1347     SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
1348
1349   return SVN_NO_ERROR;
1350 }
1351
1352 /** Prepare a repos repos diff between PATH_OR_URL1 and
1353  * PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2.
1354  * Return URLs and peg revisions in *URL1, *REV1 and in *URL2, *REV2.
1355  * Return suitable anchors in *ANCHOR1 and *ANCHOR2, and targets in
1356  * *TARGET1 and *TARGET2, based on *URL1 and *URL2.
1357  * Indicate the corresponding node kinds in *KIND1 and *KIND2, and verify
1358  * that at least one of the diff targets exists.
1359  * Use client context CTX. Do all allocations in POOL. */
1360 static svn_error_t *
1361 diff_prepare_repos_repos(const char **url1,
1362                          const char **url2,
1363                          svn_revnum_t *rev1,
1364                          svn_revnum_t *rev2,
1365                          const char **anchor1,
1366                          const char **anchor2,
1367                          const char **target1,
1368                          const char **target2,
1369                          svn_node_kind_t *kind1,
1370                          svn_node_kind_t *kind2,
1371                          svn_ra_session_t **ra_session,
1372                          svn_client_ctx_t *ctx,
1373                          const char *path_or_url1,
1374                          const char *path_or_url2,
1375                          const svn_opt_revision_t *revision1,
1376                          const svn_opt_revision_t *revision2,
1377                          const svn_opt_revision_t *peg_revision,
1378                          apr_pool_t *pool)
1379 {
1380   const char *local_abspath1 = NULL;
1381   const char *local_abspath2 = NULL;
1382   const char *repos_root_url;
1383   const char *wri_abspath = NULL;
1384   svn_client__pathrev_t *resolved1;
1385   svn_client__pathrev_t *resolved2 = NULL;
1386   enum svn_opt_revision_kind peg_kind = peg_revision->kind;
1387
1388   if (!svn_path_is_url(path_or_url2))
1389     {
1390       SVN_ERR(svn_dirent_get_absolute(&local_abspath2, path_or_url2, pool));
1391       SVN_ERR(svn_wc__node_get_url(url2, ctx->wc_ctx, local_abspath2,
1392                                    pool, pool));
1393       wri_abspath = local_abspath2;
1394     }
1395   else
1396     *url2 = apr_pstrdup(pool, path_or_url2);
1397
1398   if (!svn_path_is_url(path_or_url1))
1399     {
1400       SVN_ERR(svn_dirent_get_absolute(&local_abspath1, path_or_url1, pool));
1401       wri_abspath = local_abspath1;
1402     }
1403
1404   SVN_ERR(svn_client_open_ra_session2(ra_session, *url2, wri_abspath,
1405                                       ctx, pool, pool));
1406
1407   /* If we are performing a pegged diff, we need to find out what our
1408      actual URLs will be. */
1409   if (peg_kind != svn_opt_revision_unspecified
1410       || path_or_url1 == path_or_url2
1411       || local_abspath2)
1412     {
1413       svn_error_t *err;
1414
1415       err = svn_client__resolve_rev_and_url(&resolved2,
1416                                             *ra_session, path_or_url2,
1417                                             peg_revision, revision2,
1418                                             ctx, pool);
1419       if (err)
1420         {
1421           if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES
1422               && err->apr_err != SVN_ERR_FS_NOT_FOUND)
1423             return svn_error_trace(err);
1424
1425           svn_error_clear(err);
1426           resolved2 = NULL;
1427         }
1428     }
1429   else
1430     resolved2 = NULL;
1431
1432   if (peg_kind != svn_opt_revision_unspecified
1433       || path_or_url1 == path_or_url2
1434       || local_abspath1)
1435     {
1436       svn_error_t *err;
1437
1438       err = svn_client__resolve_rev_and_url(&resolved1,
1439                                             *ra_session, path_or_url1,
1440                                             peg_revision, revision1,
1441                                             ctx, pool);
1442       if (err)
1443         {
1444           if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES
1445               && err->apr_err != SVN_ERR_FS_NOT_FOUND)
1446             return svn_error_trace(err);
1447
1448           svn_error_clear(err);
1449           resolved1 = NULL;
1450         }
1451     }
1452   else
1453     resolved1 = NULL;
1454
1455   if (resolved1)
1456     {
1457       *url1 = resolved1->url;
1458       *rev1 = resolved1->rev;
1459     }
1460   else
1461     {
1462       /* It would be nice if we could just return an error when resolving a
1463          location fails... But in many such cases we prefer diffing against
1464          an not existing location to show adds od removes (see issue #4153) */
1465
1466       if (resolved2
1467           && (peg_kind != svn_opt_revision_unspecified
1468               || path_or_url1 == path_or_url2))
1469         *url1 = resolved2->url;
1470       else if (! local_abspath1)
1471         *url1 = path_or_url1;
1472       else
1473         SVN_ERR(svn_wc__node_get_url(url1, ctx->wc_ctx, local_abspath1,
1474                                      pool, pool));
1475
1476       SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx,
1477                                               local_abspath1 /* may be NULL */,
1478                                               *ra_session, revision1, pool));
1479     }
1480
1481   if (resolved2)
1482     {
1483       *url2 = resolved2->url;
1484       *rev2 = resolved2->rev;
1485     }
1486   else
1487     {
1488       /* It would be nice if we could just return an error when resolving a
1489          location fails... But in many such cases we prefer diffing against
1490          an not existing location to show adds od removes (see issue #4153) */
1491
1492       if (resolved1
1493           && (peg_kind != svn_opt_revision_unspecified
1494               || path_or_url1 == path_or_url2))
1495         *url2 = resolved1->url;
1496       /* else keep url2 */
1497
1498       SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx,
1499                                               local_abspath2 /* may be NULL */,
1500                                               *ra_session, revision2, pool));
1501     }
1502
1503   /* Resolve revision and get path kind for the second target. */
1504   SVN_ERR(svn_ra_reparent(*ra_session, *url2, pool));
1505   SVN_ERR(svn_ra_check_path(*ra_session, "", *rev2, kind2, pool));
1506
1507   /* Do the same for the first target. */
1508   SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
1509   SVN_ERR(svn_ra_check_path(*ra_session, "", *rev1, kind1, pool));
1510
1511   /* Either both URLs must exist at their respective revisions,
1512    * or one of them may be missing from one side of the diff. */
1513   if (*kind1 == svn_node_none && *kind2 == svn_node_none)
1514     {
1515       if (strcmp(*url1, *url2) == 0)
1516         return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1517                                  _("Diff target '%s' was not found in the "
1518                                    "repository at revisions '%ld' and '%ld'"),
1519                                  *url1, *rev1, *rev2);
1520       else
1521         return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1522                                  _("Diff targets '%s' and '%s' were not found "
1523                                    "in the repository at revisions '%ld' and "
1524                                    "'%ld'"),
1525                                  *url1, *url2, *rev1, *rev2);
1526     }
1527   else if (*kind1 == svn_node_none)
1528     SVN_ERR(check_diff_target_exists(*url1, *rev2, *rev1, *ra_session, pool));
1529   else if (*kind2 == svn_node_none)
1530     SVN_ERR(check_diff_target_exists(*url2, *rev1, *rev2, *ra_session, pool));
1531
1532   SVN_ERR(svn_ra_get_repos_root2(*ra_session, &repos_root_url, pool));
1533
1534   /* Choose useful anchors and targets for our two URLs. */
1535   *anchor1 = *url1;
1536   *anchor2 = *url2;
1537   *target1 = "";
1538   *target2 = "";
1539
1540   /* If none of the targets is the repository root open the parent directory
1541      to allow describing replacement of the target itself */
1542   if (strcmp(*url1, repos_root_url) != 0
1543       && strcmp(*url2, repos_root_url) != 0)
1544     {
1545       svn_node_kind_t ignored_kind;
1546       svn_error_t *err;
1547
1548       svn_uri_split(anchor1, target1, *url1, pool);
1549       svn_uri_split(anchor2, target2, *url2, pool);
1550
1551       SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
1552
1553       /* We might not have the necessary rights to read the root now.
1554          (It is ok to pass a revision here where the node doesn't exist) */
1555       err = svn_ra_check_path(*ra_session, "", *rev1, &ignored_kind, pool);
1556
1557       if (err && (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN
1558                   || err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED))
1559         {
1560           svn_error_clear(err);
1561
1562           /* Ok, lets undo the reparent...
1563
1564              We can't report replacements this way, but at least we can
1565              report changes on the descendants */
1566
1567           *anchor1 = svn_path_url_add_component2(*anchor1, *target1, pool);
1568           *anchor2 = svn_path_url_add_component2(*anchor2, *target2, pool);
1569           *target1 = "";
1570           *target2 = "";
1571
1572           SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
1573         }
1574       else
1575         SVN_ERR(err);
1576     }
1577
1578   return SVN_NO_ERROR;
1579 }
1580
1581 /* A Theoretical Note From Ben, regarding do_diff().
1582
1583    This function is really svn_client_diff6().  If you read the public
1584    API description for svn_client_diff6(), it sounds quite Grand.  It
1585    sounds really generalized and abstract and beautiful: that it will
1586    diff any two paths, be they working-copy paths or URLs, at any two
1587    revisions.
1588
1589    Now, the *reality* is that we have exactly three 'tools' for doing
1590    diffing, and thus this routine is built around the use of the three
1591    tools.  Here they are, for clarity:
1592
1593      - svn_wc_diff:  assumes both paths are the same wcpath.
1594                      compares wcpath@BASE vs. wcpath@WORKING
1595
1596      - svn_wc_get_diff_editor:  compares some URL@REV vs. wcpath@WORKING
1597
1598      - svn_client__get_diff_editor:  compares some URL1@REV1 vs. URL2@REV2
1599
1600    Since Subversion 1.8 we also have a variant of svn_wc_diff called
1601    svn_client__arbitrary_nodes_diff, that allows handling WORKING-WORKING
1602    comparisons between nodes in the working copy.
1603
1604    So the truth of the matter is, if the caller's arguments can't be
1605    pigeonholed into one of these use-cases, we currently bail with a
1606    friendly apology.
1607
1608    Perhaps someday a brave soul will truly make svn_client_diff6()
1609    perfectly general.  For now, we live with the 90% case.  Certainly,
1610    the commandline client only calls this function in legal ways.
1611    When there are other users of svn_client.h, maybe this will become
1612    a more pressing issue.
1613  */
1614
1615 /* Return a "you can't do that" error, optionally wrapping another
1616    error CHILD_ERR. */
1617 static svn_error_t *
1618 unsupported_diff_error(svn_error_t *child_err)
1619 {
1620   return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
1621                           _("Sorry, svn_client_diff6 was called in a way "
1622                             "that is not yet supported"));
1623 }
1624
1625 /* Perform a diff between two working-copy paths.
1626
1627    PATH1 and PATH2 are both working copy paths.  REVISION1 and
1628    REVISION2 are their respective revisions.
1629
1630    All other options are the same as those passed to svn_client_diff6(). */
1631 static svn_error_t *
1632 diff_wc_wc(const char **root_relpath,
1633            svn_boolean_t *root_is_dir,
1634            struct diff_driver_info_t *ddi,
1635            const char *path1,
1636            const svn_opt_revision_t *revision1,
1637            const char *path2,
1638            const svn_opt_revision_t *revision2,
1639            svn_depth_t depth,
1640            svn_boolean_t ignore_ancestry,
1641            const apr_array_header_t *changelists,
1642            const svn_diff_tree_processor_t *diff_processor,
1643            svn_client_ctx_t *ctx,
1644            apr_pool_t *result_pool,
1645            apr_pool_t *scratch_pool)
1646 {
1647   const char *abspath1;
1648
1649   SVN_ERR_ASSERT(! svn_path_is_url(path1));
1650   SVN_ERR_ASSERT(! svn_path_is_url(path2));
1651
1652   SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, scratch_pool));
1653
1654   /* Currently we support only the case where path1 and path2 are the
1655      same path. */
1656   if ((strcmp(path1, path2) != 0)
1657       || (! ((revision1->kind == svn_opt_revision_base)
1658              && (revision2->kind == svn_opt_revision_working))))
1659     return unsupported_diff_error(
1660        svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1661                         _("Only diffs between a path's text-base "
1662                           "and its working files are supported at this time"
1663                           )));
1664
1665   if (ddi)
1666     {
1667       svn_node_kind_t kind;
1668
1669       SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
1670                               TRUE, FALSE, scratch_pool));
1671
1672       if (kind != svn_node_dir)
1673         ddi->anchor = svn_dirent_dirname(path1, scratch_pool);
1674       else
1675         ddi->anchor = path1;
1676     }
1677
1678   SVN_ERR(svn_wc__diff7(root_relpath, root_is_dir,
1679                         ctx->wc_ctx, abspath1, depth,
1680                         ignore_ancestry, changelists,
1681                         diff_processor,
1682                         ctx->cancel_func, ctx->cancel_baton,
1683                         result_pool, scratch_pool));
1684   return SVN_NO_ERROR;
1685 }
1686
1687 /* Perform a diff between two repository paths.
1688
1689    PATH_OR_URL1 and PATH_OR_URL2 may be either URLs or the working copy paths.
1690    REVISION1 and REVISION2 are their respective revisions.
1691    If PEG_REVISION is specified, PATH_OR_URL2 is the path at the peg revision,
1692    and the actual two paths compared are determined by following copy
1693    history from PATH_OR_URL2.
1694
1695    All other options are the same as those passed to svn_client_diff6(). */
1696 static svn_error_t *
1697 diff_repos_repos(const char **root_relpath,
1698                  svn_boolean_t *root_is_dir,
1699                  struct diff_driver_info_t *ddi,
1700                  const char *path_or_url1,
1701                  const char *path_or_url2,
1702                  const svn_opt_revision_t *revision1,
1703                  const svn_opt_revision_t *revision2,
1704                  const svn_opt_revision_t *peg_revision,
1705                  svn_depth_t depth,
1706                  svn_boolean_t ignore_ancestry,
1707                  svn_boolean_t text_deltas,
1708                  const svn_diff_tree_processor_t *diff_processor,
1709                  svn_client_ctx_t *ctx,
1710                  apr_pool_t *result_pool,
1711                  apr_pool_t *scratch_pool)
1712 {
1713   svn_ra_session_t *extra_ra_session;
1714
1715   const svn_ra_reporter3_t *reporter;
1716   void *reporter_baton;
1717
1718   const svn_delta_editor_t *diff_editor;
1719   void *diff_edit_baton;
1720
1721   const char *url1;
1722   const char *url2;
1723   svn_revnum_t rev1;
1724   svn_revnum_t rev2;
1725   svn_node_kind_t kind1;
1726   svn_node_kind_t kind2;
1727   const char *anchor1;
1728   const char *anchor2;
1729   const char *target1;
1730   const char *target2;
1731   svn_ra_session_t *ra_session;
1732
1733   /* Prepare info for the repos repos diff. */
1734   SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &rev1, &rev2,
1735                                    &anchor1, &anchor2, &target1, &target2,
1736                                    &kind1, &kind2, &ra_session,
1737                                    ctx, path_or_url1, path_or_url2,
1738                                    revision1, revision2, peg_revision,
1739                                    scratch_pool));
1740
1741   /* Set up the repos_diff editor on BASE_PATH, if available.
1742      Otherwise, we just use "". */
1743
1744   if (ddi)
1745     {
1746       /* Get actual URLs. */
1747       ddi->orig_path_1 = url1;
1748       ddi->orig_path_2 = url2;
1749
1750       /* This should be moved to the diff writer
1751          - path_or_url are provided by the caller
1752          - target1 is available as *root_relpath
1753          - (kind1 != svn_node_dir || kind2 != svn_node_dir) = !*root_is_dir */
1754
1755       if (!svn_path_is_url(path_or_url2))
1756         ddi->anchor = path_or_url2;
1757       else if (!svn_path_is_url(path_or_url1))
1758         ddi->anchor = path_or_url1;
1759       else
1760         ddi->anchor = NULL;
1761
1762       if (*target1 && ddi->anchor
1763           && (kind1 != svn_node_dir || kind2 != svn_node_dir))
1764         ddi->anchor = svn_dirent_dirname(ddi->anchor, result_pool);
1765     }
1766
1767   /* The repository can bring in a new working copy, but not delete
1768      everything. Luckily our new diff handler can just be reversed. */
1769   if (kind2 == svn_node_none)
1770     {
1771       const char *str_tmp;
1772       svn_revnum_t rev_tmp;
1773
1774       str_tmp = url2;
1775       url2 = url1;
1776       url1 = str_tmp;
1777
1778       rev_tmp = rev2;
1779       rev2 = rev1;
1780       rev1 = rev_tmp;
1781
1782       str_tmp = anchor2;
1783       anchor2 = anchor1;
1784       anchor1 = str_tmp;
1785
1786       str_tmp = target2;
1787       target2 = target1;
1788       target1 = str_tmp;
1789
1790       diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
1791                                                                NULL,
1792                                                                scratch_pool);
1793     }
1794
1795   /* Filter the first path component using a filter processor, until we fixed
1796      the diff processing to handle this directly */
1797   if (root_relpath)
1798     *root_relpath = apr_pstrdup(result_pool, target1);
1799   else if ((kind1 != svn_node_file && kind2 != svn_node_file)
1800            && target1[0] != '\0')
1801     {
1802       diff_processor = svn_diff__tree_processor_filter_create(
1803                                         diff_processor, target1, scratch_pool);
1804     }
1805
1806   /* Now, we open an extra RA session to the correct anchor
1807      location for URL1.  This is used during the editor calls to fetch file
1808      contents.  */
1809   SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor1,
1810                               scratch_pool, scratch_pool));
1811
1812   if (ddi)
1813     {
1814       const char *repos_root_url;
1815       const char *session_url;
1816
1817       SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
1818                                       scratch_pool));
1819       SVN_ERR(svn_ra_get_session_url(ra_session, &session_url,
1820                                       scratch_pool));
1821
1822       ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url,
1823                                                     session_url,
1824                                                     result_pool);
1825     }
1826
1827   SVN_ERR(svn_client__get_diff_editor2(
1828                 &diff_editor, &diff_edit_baton,
1829                 extra_ra_session, depth,
1830                 rev1,
1831                 text_deltas,
1832                 diff_processor,
1833                 ctx->cancel_func, ctx->cancel_baton,
1834                 scratch_pool));
1835
1836   /* We want to switch our txn into URL2 */
1837   SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton,
1838                           rev2, target1,
1839                           depth, ignore_ancestry, text_deltas,
1840                           url2, diff_editor, diff_edit_baton, scratch_pool));
1841
1842   /* Drive the reporter; do the diff. */
1843   SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
1844                              svn_depth_infinity,
1845                              FALSE, NULL,
1846                              scratch_pool));
1847
1848   return svn_error_trace(
1849                   reporter->finish_report(reporter_baton, scratch_pool));
1850 }
1851
1852 /* Perform a diff between a repository path and a working-copy path.
1853
1854    PATH_OR_URL1 may be either a URL or a working copy path.  PATH2 is a
1855    working copy path.  REVISION1 and REVISION2 are their respective
1856    revisions.  If REVERSE is TRUE, the diff will be done in reverse.
1857    If PEG_REVISION is specified, then PATH_OR_URL1 is the path in the peg
1858    revision, and the actual repository path to be compared is
1859    determined by following copy history.
1860
1861    All other options are the same as those passed to svn_client_diff6(). */
1862 static svn_error_t *
1863 diff_repos_wc(const char **root_relpath,
1864               svn_boolean_t *root_is_dir,
1865               struct diff_driver_info_t *ddi,
1866               const char *path_or_url1,
1867               const svn_opt_revision_t *revision1,
1868               const svn_opt_revision_t *peg_revision,
1869               const char *path2,
1870               const svn_opt_revision_t *revision2,
1871               svn_boolean_t reverse,
1872               svn_depth_t depth,
1873               svn_boolean_t ignore_ancestry,
1874               const apr_array_header_t *changelists,
1875               const svn_diff_tree_processor_t *diff_processor,
1876               svn_client_ctx_t *ctx,
1877               apr_pool_t *result_pool,
1878               apr_pool_t *scratch_pool)
1879 {
1880   const char *anchor, *anchor_url, *target;
1881   svn_ra_session_t *ra_session;
1882   svn_depth_t diff_depth;
1883   const svn_ra_reporter3_t *reporter;
1884   void *reporter_baton;
1885   const svn_delta_editor_t *diff_editor;
1886   void *diff_edit_baton;
1887   svn_boolean_t rev2_is_base = (revision2->kind == svn_opt_revision_base);
1888   svn_boolean_t server_supports_depth;
1889   const char *abspath_or_url1;
1890   const char *abspath2;
1891   const char *anchor_abspath;
1892   svn_boolean_t is_copy;
1893   svn_revnum_t cf_revision;
1894   const char *cf_repos_relpath;
1895   const char *cf_repos_root_url;
1896   svn_depth_t cf_depth;
1897   const char *copy_root_abspath;
1898   const char *target_url;
1899   svn_client__pathrev_t *loc1;
1900
1901   SVN_ERR_ASSERT(! svn_path_is_url(path2));
1902
1903   if (!svn_path_is_url(path_or_url1))
1904     {
1905       SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1,
1906                                       scratch_pool));
1907     }
1908   else
1909     {
1910       abspath_or_url1 = path_or_url1;
1911     }
1912
1913   SVN_ERR(svn_dirent_get_absolute(&abspath2, path2, scratch_pool));
1914
1915   /* Check if our diff target is a copied node. */
1916   SVN_ERR(svn_wc__node_get_origin(&is_copy,
1917                                   &cf_revision,
1918                                   &cf_repos_relpath,
1919                                   &cf_repos_root_url,
1920                                   NULL, &cf_depth,
1921                                   &copy_root_abspath,
1922                                   ctx->wc_ctx, abspath2,
1923                                   FALSE, scratch_pool, scratch_pool));
1924
1925   SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc1,
1926                                             path_or_url1, abspath2,
1927                                             peg_revision, revision1,
1928                                             ctx, scratch_pool));
1929
1930   if (revision2->kind == svn_opt_revision_base || !is_copy)
1931     {
1932       /* Convert path_or_url1 to a URL to feed to do_diff. */
1933       SVN_ERR(svn_wc_get_actual_target2(&anchor, &target, ctx->wc_ctx, path2,
1934                                         scratch_pool, scratch_pool));
1935
1936       /* Handle the ugly case where target is ".." */
1937       if (*target && !svn_path_is_single_path_component(target))
1938         {
1939           anchor = svn_dirent_join(anchor, target, scratch_pool);
1940           target = "";
1941         }
1942
1943       if (root_relpath)
1944         *root_relpath = apr_pstrdup(result_pool, target);
1945       if (root_is_dir)
1946         *root_is_dir = (*target == '\0');
1947
1948       /* Fetch the URL of the anchor directory. */
1949       SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, scratch_pool));
1950       SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
1951                                    scratch_pool, scratch_pool));
1952       SVN_ERR_ASSERT(anchor_url != NULL);
1953
1954       target_url = NULL;
1955     }
1956   else /* is_copy && revision2->kind == svn_opt_revision_base */
1957     {
1958 #if 0
1959       svn_node_kind_t kind;
1960 #endif
1961       /* ### Ugly hack ahead ###
1962        *
1963        * We're diffing a locally copied/moved node.
1964        * Describe the copy source to the reporter instead of the copy itself.
1965        * Doing the latter would generate a single add_directory() call to the
1966        * diff editor which results in an unexpected diff (the copy would
1967        * be shown as deleted).
1968        *
1969        * ### But if we will receive any real changes from the repositor we
1970        * will most likely fail to apply them as the wc diff editor assumes
1971        * that we have the data to which the change applies in BASE...
1972        */
1973
1974       target_url = svn_path_url_add_component2(cf_repos_root_url,
1975                                                cf_repos_relpath,
1976                                                scratch_pool);
1977
1978 #if 0
1979       /*SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath2, FALSE, FALSE,
1980                                 scratch_pool));
1981
1982       if (kind != svn_node_dir
1983           || strcmp(copy_root_abspath, abspath2) != 0) */
1984 #endif
1985         {
1986           /* We are looking at a subdirectory of the repository,
1987              We can describe the parent directory as the anchor..
1988
1989              ### This 'appears to work', but that is really dumb luck
1990              ### for the simple cases in the test suite */
1991           anchor_abspath = svn_dirent_dirname(abspath2, scratch_pool);
1992           anchor_url = svn_path_url_add_component2(cf_repos_root_url,
1993                                                    svn_relpath_dirname(
1994                                                             cf_repos_relpath,
1995                                                             scratch_pool),
1996                                                    scratch_pool);
1997           target = svn_dirent_basename(abspath2, NULL);
1998           anchor = svn_dirent_dirname(path2, scratch_pool);
1999         }
2000 #if 0
2001       else
2002         {
2003           /* This code, while ok can't be enabled without causing test
2004            * failures. The repository will send some changes against
2005            * BASE for nodes that don't have BASE...
2006            */
2007           anchor_abspath = abspath2;
2008           anchor_url = svn_path_url_add_component2(cf_repos_root_url,
2009                                                    cf_repos_relpath,
2010                                                    scratch_pool);
2011           anchor = path2;
2012           target = "";
2013         }
2014 #endif
2015     }
2016
2017   SVN_ERR(svn_ra_reparent(ra_session, anchor_url, scratch_pool));
2018
2019   if (ddi)
2020     {
2021       const char *repos_root_url;
2022
2023       ddi->anchor = anchor;
2024
2025       if (!reverse)
2026         {
2027           ddi->orig_path_1 = apr_pstrdup(result_pool, loc1->url);
2028           ddi->orig_path_2 =
2029             svn_path_url_add_component2(anchor_url, target, result_pool);
2030         }
2031       else
2032         {
2033           ddi->orig_path_1 =
2034             svn_path_url_add_component2(anchor_url, target, result_pool);
2035           ddi->orig_path_2 = apr_pstrdup(result_pool, loc1->url);
2036         }
2037
2038       SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
2039                                       scratch_pool));
2040
2041       ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url,
2042                                                    anchor_url,
2043                                                    result_pool);
2044     }
2045
2046   if (reverse)
2047     diff_processor = svn_diff__tree_processor_reverse_create(
2048                               diff_processor, NULL, scratch_pool);
2049
2050   /* Use the diff editor to generate the diff. */
2051   SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
2052                                 SVN_RA_CAPABILITY_DEPTH, scratch_pool));
2053   SVN_ERR(svn_wc__get_diff_editor(&diff_editor, &diff_edit_baton,
2054                                   ctx->wc_ctx,
2055                                   anchor_abspath,
2056                                   target,
2057                                   depth,
2058                                   ignore_ancestry,
2059                                   rev2_is_base,
2060                                   reverse,
2061                                   server_supports_depth,
2062                                   changelists,
2063                                   diff_processor,
2064                                   ctx->cancel_func, ctx->cancel_baton,
2065                                   scratch_pool, scratch_pool));
2066
2067   if (depth != svn_depth_infinity)
2068     diff_depth = depth;
2069   else
2070     diff_depth = svn_depth_unknown;
2071
2072
2073
2074   if (is_copy && revision2->kind != svn_opt_revision_base)
2075     {
2076       /* Tell the RA layer we want a delta to change our txn to URL1 */
2077       SVN_ERR(svn_ra_do_diff3(ra_session,
2078                               &reporter, &reporter_baton,
2079                               loc1->rev,
2080                               target,
2081                               diff_depth,
2082                               ignore_ancestry,
2083                               TRUE,  /* text_deltas */
2084                               loc1->url,
2085                               diff_editor, diff_edit_baton,
2086                               scratch_pool));
2087
2088       /* Report the copy source. */
2089       if (cf_depth == svn_depth_unknown)
2090         cf_depth = svn_depth_infinity;
2091
2092       /* Reporting the in-wc revision as r0, makes the repository send
2093          everything as added, which avoids using BASE for pristine information,
2094          which is not there (or unrelated) for a copy */
2095
2096       SVN_ERR(reporter->set_path(reporter_baton, "",
2097                                  ignore_ancestry ? 0 : cf_revision,
2098                                  cf_depth, FALSE, NULL, scratch_pool));
2099
2100       if (*target)
2101         SVN_ERR(reporter->link_path(reporter_baton, target,
2102                                     target_url,
2103                                     ignore_ancestry ? 0 : cf_revision,
2104                                     cf_depth, FALSE, NULL, scratch_pool));
2105
2106       /* Finish the report to generate the diff. */
2107       SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
2108     }
2109   else
2110     {
2111       /* Tell the RA layer we want a delta to change our txn to URL1 */
2112       SVN_ERR(svn_ra_do_diff3(ra_session,
2113                               &reporter, &reporter_baton,
2114                               loc1->rev,
2115                               target,
2116                               diff_depth,
2117                               ignore_ancestry,
2118                               TRUE,  /* text_deltas */
2119                               loc1->url,
2120                               diff_editor, diff_edit_baton,
2121                               scratch_pool));
2122
2123       /* Create a txn mirror of path2;  the diff editor will print
2124          diffs in reverse.  :-)  */
2125       SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, abspath2,
2126                                       reporter, reporter_baton,
2127                                       FALSE, depth, TRUE,
2128                                       (! server_supports_depth),
2129                                       FALSE,
2130                                       ctx->cancel_func, ctx->cancel_baton,
2131                                       NULL, NULL, /* notification is N/A */
2132                                       scratch_pool));
2133     }
2134
2135   return SVN_NO_ERROR;
2136 }
2137
2138
2139 /* This is basically just the guts of svn_client_diff[_summarize][_peg]6(). */
2140 static svn_error_t *
2141 do_diff(const char **root_relpath,
2142         svn_boolean_t *root_is_dir,
2143         diff_driver_info_t *ddi,
2144         const char *path_or_url1,
2145         const char *path_or_url2,
2146         const svn_opt_revision_t *revision1,
2147         const svn_opt_revision_t *revision2,
2148         const svn_opt_revision_t *peg_revision,
2149         svn_depth_t depth,
2150         svn_boolean_t ignore_ancestry,
2151         const apr_array_header_t *changelists,
2152         svn_boolean_t text_deltas,
2153         const svn_diff_tree_processor_t *diff_processor,
2154         svn_client_ctx_t *ctx,
2155         apr_pool_t *result_pool,
2156         apr_pool_t *scratch_pool)
2157 {
2158   svn_boolean_t is_repos1;
2159   svn_boolean_t is_repos2;
2160
2161   /* Check if paths/revisions are urls/local. */
2162   SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2,
2163                       revision1, revision2, peg_revision));
2164
2165   if (is_repos1)
2166     {
2167       if (is_repos2)
2168         {
2169           /* ### Ignores 'show_copies_as_adds'. */
2170           SVN_ERR(diff_repos_repos(root_relpath, root_is_dir,
2171                                    ddi,
2172                                    path_or_url1, path_or_url2,
2173                                    revision1, revision2,
2174                                    peg_revision, depth, ignore_ancestry,
2175                                    text_deltas,
2176                                    diff_processor, ctx,
2177                                    result_pool, scratch_pool));
2178         }
2179       else /* path_or_url2 is a working copy path */
2180         {
2181           SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi,
2182                                 path_or_url1, revision1, peg_revision,
2183                                 path_or_url2, revision2, FALSE, depth,
2184                                 ignore_ancestry, changelists,
2185                                 diff_processor, ctx,
2186                                 result_pool, scratch_pool));
2187         }
2188     }
2189   else /* path_or_url1 is a working copy path */
2190     {
2191       if (is_repos2)
2192         {
2193           SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi,
2194                                 path_or_url2, revision2, peg_revision,
2195                                 path_or_url1, revision1, TRUE, depth,
2196                                 ignore_ancestry, changelists,
2197                                 diff_processor, ctx,
2198                                 result_pool, scratch_pool));
2199         }
2200       else /* path_or_url2 is a working copy path */
2201         {
2202           if (revision1->kind == svn_opt_revision_working
2203               && revision2->kind == svn_opt_revision_working)
2204             {
2205               const char *abspath1;
2206               const char *abspath2;
2207
2208               SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1,
2209                                               scratch_pool));
2210               SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2,
2211                                               scratch_pool));
2212
2213               /* ### What about ddi? */
2214
2215               SVN_ERR(svn_client__arbitrary_nodes_diff(root_relpath, root_is_dir,
2216                                                        abspath1, abspath2,
2217                                                        depth,
2218                                                        diff_processor,
2219                                                        ctx,
2220                                                        result_pool, scratch_pool));
2221             }
2222           else
2223             {
2224               SVN_ERR(diff_wc_wc(root_relpath, root_is_dir, ddi,
2225                                  path_or_url1, revision1,
2226                                  path_or_url2, revision2,
2227                                  depth, ignore_ancestry, changelists,
2228                                  diff_processor, ctx,
2229                                  result_pool, scratch_pool));
2230             }
2231         }
2232     }
2233
2234   return SVN_NO_ERROR;
2235 }
2236
2237 /* Initialize DWI.diff_cmd and DWI.options,
2238  * according to OPTIONS and CONFIG.  CONFIG and OPTIONS may be null.
2239  * Allocate the fields in RESULT_POOL, which should be at least as long-lived
2240  * as the pool DWI itself is allocated in.
2241  */
2242 static svn_error_t *
2243 create_diff_writer_info(diff_writer_info_t *dwi,
2244                         const apr_array_header_t *options,
2245                         apr_hash_t *config, apr_pool_t *result_pool)
2246 {
2247   const char *diff_cmd = NULL;
2248
2249   /* See if there is a diff command and/or diff arguments. */
2250   if (config)
2251     {
2252       svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
2253       svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
2254                      SVN_CONFIG_OPTION_DIFF_CMD, NULL);
2255       if (options == NULL)
2256         {
2257           const char *diff_extensions;
2258           svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS,
2259                          SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL);
2260           if (diff_extensions)
2261             options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE,
2262                                         result_pool);
2263         }
2264     }
2265
2266   if (options == NULL)
2267     options = apr_array_make(result_pool, 0, sizeof(const char *));
2268
2269   if (diff_cmd)
2270     SVN_ERR(svn_path_cstring_to_utf8(&dwi->diff_cmd, diff_cmd,
2271                                      result_pool));
2272   else
2273     dwi->diff_cmd = NULL;
2274
2275   /* If there was a command, arrange options to pass to it. */
2276   if (dwi->diff_cmd)
2277     {
2278       const char **argv = NULL;
2279       int argc = options->nelts;
2280       if (argc)
2281         {
2282           int i;
2283           argv = apr_palloc(result_pool, argc * sizeof(char *));
2284           for (i = 0; i < argc; i++)
2285             SVN_ERR(svn_utf_cstring_to_utf8(&argv[i],
2286                       APR_ARRAY_IDX(options, i, const char *), result_pool));
2287         }
2288       dwi->options.for_external.argv = argv;
2289       dwi->options.for_external.argc = argc;
2290     }
2291   else  /* No command, so arrange options for internal invocation instead. */
2292     {
2293       dwi->options.for_internal = svn_diff_file_options_create(result_pool);
2294       SVN_ERR(svn_diff_file_options_parse(dwi->options.for_internal,
2295                                           options, result_pool));
2296     }
2297
2298   return SVN_NO_ERROR;
2299 }
2300
2301 /*----------------------------------------------------------------------- */
2302 \f
2303 /*** Public Interfaces. ***/
2304
2305 /* Display context diffs between two PATH/REVISION pairs.  Each of
2306    these inputs will be one of the following:
2307
2308    - a repository URL at a given revision.
2309    - a working copy path, ignoring local mods.
2310    - a working copy path, including local mods.
2311
2312    We can establish a matrix that shows the nine possible types of
2313    diffs we expect to support.
2314
2315
2316       ` .     DST ||  URL:rev   | WC:base    | WC:working |
2317           ` .     ||            |            |            |
2318       SRC     ` . ||            |            |            |
2319       ============++============+============+============+
2320        URL:rev    || (*)        | (*)        | (*)        |
2321                   ||            |            |            |
2322                   ||            |            |            |
2323                   ||            |            |            |
2324       ------------++------------+------------+------------+
2325        WC:base    || (*)        |                         |
2326                   ||            | New svn_wc_diff which   |
2327                   ||            | is smart enough to      |
2328                   ||            | handle two WC paths     |
2329       ------------++------------+ and their related       +
2330        WC:working || (*)        | text-bases and working  |
2331                   ||            | files.  This operation  |
2332                   ||            | is entirely local.      |
2333                   ||            |                         |
2334       ------------++------------+------------+------------+
2335       * These cases require server communication.
2336 */
2337 svn_error_t *
2338 svn_client_diff6(const apr_array_header_t *options,
2339                  const char *path_or_url1,
2340                  const svn_opt_revision_t *revision1,
2341                  const char *path_or_url2,
2342                  const svn_opt_revision_t *revision2,
2343                  const char *relative_to_dir,
2344                  svn_depth_t depth,
2345                  svn_boolean_t ignore_ancestry,
2346                  svn_boolean_t no_diff_added,
2347                  svn_boolean_t no_diff_deleted,
2348                  svn_boolean_t show_copies_as_adds,
2349                  svn_boolean_t ignore_content_type,
2350                  svn_boolean_t ignore_properties,
2351                  svn_boolean_t properties_only,
2352                  svn_boolean_t use_git_diff_format,
2353                  const char *header_encoding,
2354                  svn_stream_t *outstream,
2355                  svn_stream_t *errstream,
2356                  const apr_array_header_t *changelists,
2357                  svn_client_ctx_t *ctx,
2358                  apr_pool_t *pool)
2359 {
2360   diff_writer_info_t dwi = { 0 };
2361   svn_opt_revision_t peg_revision;
2362   const svn_diff_tree_processor_t *diff_processor;
2363   svn_diff_tree_processor_t *processor;
2364
2365   if (ignore_properties && properties_only)
2366     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2367                             _("Cannot ignore properties and show only "
2368                               "properties at the same time"));
2369
2370   /* We will never do a pegged diff from here. */
2371   peg_revision.kind = svn_opt_revision_unspecified;
2372
2373   /* setup callback and baton */
2374   dwi.ddi.orig_path_1 = path_or_url1;
2375   dwi.ddi.orig_path_2 = path_or_url2;
2376
2377   SVN_ERR(create_diff_writer_info(&dwi, options,
2378                                   ctx->config, pool));
2379   dwi.pool = pool;
2380   dwi.outstream = outstream;
2381   dwi.errstream = errstream;
2382   dwi.header_encoding = header_encoding;
2383
2384   dwi.force_binary = ignore_content_type;
2385   dwi.ignore_properties = ignore_properties;
2386   dwi.properties_only = properties_only;
2387   dwi.relative_to_dir = relative_to_dir;
2388   dwi.use_git_diff_format = use_git_diff_format;
2389   dwi.no_diff_added = no_diff_added;
2390   dwi.no_diff_deleted = no_diff_deleted;
2391   dwi.show_copies_as_adds = show_copies_as_adds;
2392
2393   dwi.cancel_func = ctx->cancel_func;
2394   dwi.cancel_baton = ctx->cancel_baton;
2395
2396   dwi.wc_ctx = ctx->wc_ctx;
2397   dwi.ddi.session_relpath = NULL;
2398   dwi.ddi.anchor = NULL;
2399
2400   processor = svn_diff__tree_processor_create(&dwi, pool);
2401
2402   processor->dir_added = diff_dir_added;
2403   processor->dir_changed = diff_dir_changed;
2404   processor->dir_deleted = diff_dir_deleted;
2405
2406   processor->file_added = diff_file_added;
2407   processor->file_changed = diff_file_changed;
2408   processor->file_deleted = diff_file_deleted;
2409
2410   diff_processor = processor;
2411
2412   /* --show-copies-as-adds and --git imply --notice-ancestry */
2413   if (show_copies_as_adds || use_git_diff_format)
2414     ignore_ancestry = FALSE;
2415
2416   return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi,
2417                                  path_or_url1, path_or_url2,
2418                                  revision1, revision2, &peg_revision,
2419                                  depth, ignore_ancestry, changelists,
2420                                  TRUE /* text_deltas */,
2421                                  diff_processor, ctx, pool, pool));
2422 }
2423
2424 svn_error_t *
2425 svn_client_diff_peg6(const apr_array_header_t *options,
2426                      const char *path_or_url,
2427                      const svn_opt_revision_t *peg_revision,
2428                      const svn_opt_revision_t *start_revision,
2429                      const svn_opt_revision_t *end_revision,
2430                      const char *relative_to_dir,
2431                      svn_depth_t depth,
2432                      svn_boolean_t ignore_ancestry,
2433                      svn_boolean_t no_diff_added,
2434                      svn_boolean_t no_diff_deleted,
2435                      svn_boolean_t show_copies_as_adds,
2436                      svn_boolean_t ignore_content_type,
2437                      svn_boolean_t ignore_properties,
2438                      svn_boolean_t properties_only,
2439                      svn_boolean_t use_git_diff_format,
2440                      const char *header_encoding,
2441                      svn_stream_t *outstream,
2442                      svn_stream_t *errstream,
2443                      const apr_array_header_t *changelists,
2444                      svn_client_ctx_t *ctx,
2445                      apr_pool_t *pool)
2446 {
2447   diff_writer_info_t dwi = { 0 };
2448   const svn_diff_tree_processor_t *diff_processor;
2449   svn_diff_tree_processor_t *processor;
2450
2451   if (ignore_properties && properties_only)
2452     return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2453                             _("Cannot ignore properties and show only "
2454                               "properties at the same time"));
2455
2456   /* setup callback and baton */
2457   dwi.ddi.orig_path_1 = path_or_url;
2458   dwi.ddi.orig_path_2 = path_or_url;
2459
2460   SVN_ERR(create_diff_writer_info(&dwi, options,
2461                                   ctx->config, pool));
2462   dwi.pool = pool;
2463   dwi.outstream = outstream;
2464   dwi.errstream = errstream;
2465   dwi.header_encoding = header_encoding;
2466
2467   dwi.force_binary = ignore_content_type;
2468   dwi.ignore_properties = ignore_properties;
2469   dwi.properties_only = properties_only;
2470   dwi.relative_to_dir = relative_to_dir;
2471   dwi.use_git_diff_format = use_git_diff_format;
2472   dwi.no_diff_added = no_diff_added;
2473   dwi.no_diff_deleted = no_diff_deleted;
2474   dwi.show_copies_as_adds = show_copies_as_adds;
2475
2476   dwi.cancel_func = ctx->cancel_func;
2477   dwi.cancel_baton = ctx->cancel_baton;
2478
2479   dwi.wc_ctx = ctx->wc_ctx;
2480   dwi.ddi.session_relpath = NULL;
2481   dwi.ddi.anchor = NULL;
2482
2483   processor = svn_diff__tree_processor_create(&dwi, pool);
2484
2485   processor->dir_added = diff_dir_added;
2486   processor->dir_changed = diff_dir_changed;
2487   processor->dir_deleted = diff_dir_deleted;
2488
2489   processor->file_added = diff_file_added;
2490   processor->file_changed = diff_file_changed;
2491   processor->file_deleted = diff_file_deleted;
2492
2493   diff_processor = processor;
2494
2495   /* --show-copies-as-adds and --git imply --notice-ancestry */
2496   if (show_copies_as_adds || use_git_diff_format)
2497     ignore_ancestry = FALSE;
2498
2499   return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi,
2500                                  path_or_url, path_or_url,
2501                                  start_revision, end_revision, peg_revision,
2502                                  depth, ignore_ancestry, changelists,
2503                                  TRUE /* text_deltas */,
2504                                  diff_processor, ctx, pool, pool));
2505 }
2506
2507 svn_error_t *
2508 svn_client_diff_summarize2(const char *path_or_url1,
2509                            const svn_opt_revision_t *revision1,
2510                            const char *path_or_url2,
2511                            const svn_opt_revision_t *revision2,
2512                            svn_depth_t depth,
2513                            svn_boolean_t ignore_ancestry,
2514                            const apr_array_header_t *changelists,
2515                            svn_client_diff_summarize_func_t summarize_func,
2516                            void *summarize_baton,
2517                            svn_client_ctx_t *ctx,
2518                            apr_pool_t *pool)
2519 {
2520   const svn_diff_tree_processor_t *diff_processor;
2521   svn_opt_revision_t peg_revision;
2522   const char **p_root_relpath;
2523
2524   /* We will never do a pegged diff from here. */
2525   peg_revision.kind = svn_opt_revision_unspecified;
2526
2527   SVN_ERR(svn_client__get_diff_summarize_callbacks(
2528                      &diff_processor, &p_root_relpath,
2529                      summarize_func, summarize_baton,
2530                      path_or_url1, pool, pool));
2531
2532   return svn_error_trace(do_diff(p_root_relpath, NULL, NULL,
2533                                  path_or_url1, path_or_url2,
2534                                  revision1, revision2, &peg_revision,
2535                                  depth, ignore_ancestry, changelists,
2536                                  FALSE /* text_deltas */,
2537                                  diff_processor, ctx, pool, pool));
2538 }
2539
2540 svn_error_t *
2541 svn_client_diff_summarize_peg2(const char *path_or_url,
2542                                const svn_opt_revision_t *peg_revision,
2543                                const svn_opt_revision_t *start_revision,
2544                                const svn_opt_revision_t *end_revision,
2545                                svn_depth_t depth,
2546                                svn_boolean_t ignore_ancestry,
2547                                const apr_array_header_t *changelists,
2548                                svn_client_diff_summarize_func_t summarize_func,
2549                                void *summarize_baton,
2550                                svn_client_ctx_t *ctx,
2551                                apr_pool_t *pool)
2552 {
2553   const svn_diff_tree_processor_t *diff_processor;
2554   const char **p_root_relpath;
2555
2556   SVN_ERR(svn_client__get_diff_summarize_callbacks(
2557                      &diff_processor, &p_root_relpath,
2558                      summarize_func, summarize_baton,
2559                      path_or_url, pool, pool));
2560
2561   return svn_error_trace(do_diff(p_root_relpath, NULL, NULL,
2562                                  path_or_url, path_or_url,
2563                                  start_revision, end_revision, peg_revision,
2564                                  depth, ignore_ancestry, changelists,
2565                                  FALSE /* text_deltas */,
2566                                  diff_processor, ctx, pool, pool));
2567 }
2568