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