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