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