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(©from_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 ©_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