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