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