Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_client / diff.c
blob74f513edac87fe0faae79aa52235939000aa422f
1 /*
2 * diff.c: comparing
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
23 /*** Includes. ***/
25 #include <apr_strings.h>
26 #include <apr_pools.h>
27 #include <apr_hash.h>
28 #include "svn_types.h"
29 #include "svn_hash.h"
30 #include "svn_wc.h"
31 #include "svn_delta.h"
32 #include "svn_diff.h"
33 #include "svn_mergeinfo.h"
34 #include "svn_client.h"
35 #include "svn_string.h"
36 #include "svn_error.h"
37 #include "svn_path.h"
38 #include "svn_io.h"
39 #include "svn_utf.h"
40 #include "svn_pools.h"
41 #include "svn_config.h"
42 #include "svn_props.h"
43 #include "svn_time.h"
44 #include "svn_sorts.h"
45 #include "client.h"
46 #include <assert.h>
48 #include "private/svn_wc_private.h"
50 #include "svn_private_config.h"
54 * Constant separator strings
56 static const char equal_string[] =
57 "===================================================================";
58 static const char under_string[] =
59 "___________________________________________________________________";
62 /*-----------------------------------------------------------------*/
64 /* Utilities */
66 /* Wrapper for apr_file_printf(), which see. FORMAT is a utf8-encoded
67 string after it is formatted, so this function can convert it to
68 ENCODING before printing. */
69 static svn_error_t *
70 file_printf_from_utf8(apr_file_t *fptr, const char *encoding,
71 const char *format, ...)
72 __attribute__ ((format(printf, 3, 4)));
73 static svn_error_t *
74 file_printf_from_utf8(apr_file_t *fptr, const char *encoding,
75 const char *format, ...)
77 va_list ap;
78 const char *buf, *buf_apr;
80 va_start(ap, format);
81 buf = apr_pvsprintf(apr_file_pool_get(fptr), format, ap);
82 va_end(ap);
84 SVN_ERR(svn_utf_cstring_from_utf8_ex2(&buf_apr, buf, encoding,
85 apr_file_pool_get(fptr)));
87 return svn_io_file_write_full(fptr, buf_apr, strlen(buf_apr),
88 NULL, apr_file_pool_get(fptr));
92 /* A helper function for display_prop_diffs. Output the differences between
93 the mergeinfo stored in ORIG_MERGEINFO_VAL and NEW_MERGEINFO_VAL in a
94 human-readable form to FILE, using ENCODING. Use POOL for temporary
95 allocations. */
96 static svn_error_t *
97 display_mergeinfo_diff(const char *old_mergeinfo_val,
98 const char *new_mergeinfo_val,
99 const char *encoding,
100 apr_file_t *file,
101 apr_pool_t *pool)
103 apr_hash_t *old_mergeinfo_hash, *new_mergeinfo_hash, *added, *deleted;
104 apr_hash_index_t *hi;
105 const char *from_path;
106 apr_array_header_t *merge_revarray;
108 if (old_mergeinfo_val)
109 SVN_ERR(svn_mergeinfo_parse(&old_mergeinfo_hash, old_mergeinfo_val, pool));
110 else
111 old_mergeinfo_hash = NULL;
113 if (new_mergeinfo_val)
114 SVN_ERR(svn_mergeinfo_parse(&new_mergeinfo_hash, new_mergeinfo_val, pool));
115 else
116 new_mergeinfo_hash = NULL;
118 SVN_ERR(svn_mergeinfo_diff(&deleted, &added, old_mergeinfo_hash,
119 new_mergeinfo_hash,
120 TRUE, pool));
122 for (hi = apr_hash_first(pool, deleted);
123 hi; hi = apr_hash_next(hi))
125 const void *key;
126 void *val;
127 svn_string_t *merge_revstr;
129 apr_hash_this(hi, &key, NULL, &val);
130 from_path = key;
131 merge_revarray = val;
133 SVN_ERR(svn_rangelist_to_string(&merge_revstr, merge_revarray, pool));
135 SVN_ERR(file_printf_from_utf8(file, encoding,
136 _(" Reverse-merged %s:r%s%s"),
137 from_path, merge_revstr->data,
138 APR_EOL_STR));
141 for (hi = apr_hash_first(pool, added);
142 hi; hi = apr_hash_next(hi))
144 const void *key;
145 void *val;
146 svn_string_t *merge_revstr;
148 apr_hash_this(hi, &key, NULL, &val);
149 from_path = key;
150 merge_revarray = val;
152 SVN_ERR(svn_rangelist_to_string(&merge_revstr, merge_revarray, pool));
154 SVN_ERR(file_printf_from_utf8(file, encoding,
155 _(" Merged %s:r%s%s"),
156 from_path, merge_revstr->data,
157 APR_EOL_STR));
160 return SVN_NO_ERROR;
163 #define MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir) \
164 svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL, \
165 _("Path '%s' must be an immediate child of " \
166 "the directory '%s'"), path, relative_to_dir)
168 /* A helper func that writes out verbal descriptions of property diffs
169 to FILE. Of course, the apr_file_t will probably be the 'outfile'
170 passed to svn_client_diff4, which is probably stdout. */
171 static svn_error_t *
172 display_prop_diffs(const apr_array_header_t *propchanges,
173 apr_hash_t *original_props,
174 const char *path,
175 const char *encoding,
176 apr_file_t *file,
177 const char *relative_to_dir,
178 apr_pool_t *pool)
180 int i;
182 if (relative_to_dir)
184 /* Possibly adjust the path shown in the output (see issue #2723). */
185 const char *child_path = svn_path_is_child(relative_to_dir, path, pool);
187 if (child_path)
188 path = child_path;
189 else if (!svn_path_compare_paths(relative_to_dir, path))
190 path = ".";
191 else
192 return MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir);
195 SVN_ERR(file_printf_from_utf8(file, encoding,
196 _("%sProperty changes on: %s%s"),
197 APR_EOL_STR,
198 svn_path_local_style(path, pool),
199 APR_EOL_STR));
201 SVN_ERR(file_printf_from_utf8(file, encoding, "%s" APR_EOL_STR,
202 under_string));
204 for (i = 0; i < propchanges->nelts; i++)
206 const char *header_fmt;
207 const svn_string_t *original_value;
208 const svn_prop_t *propchange =
209 &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
211 if (original_props)
212 original_value = apr_hash_get(original_props,
213 propchange->name, APR_HASH_KEY_STRING);
214 else
215 original_value = NULL;
217 /* If the property doesn't exist on either side, or if it exists
218 with the same value, skip it. */
219 if ((! (original_value || propchange->value))
220 || (original_value && propchange->value
221 && svn_string_compare(original_value, propchange->value)))
222 continue;
224 if (! original_value)
225 header_fmt = _("Added: %s%s");
226 else if (! propchange->value)
227 header_fmt = _("Deleted: %s%s");
228 else
229 header_fmt = _("Modified: %s%s");
230 SVN_ERR(file_printf_from_utf8(file, encoding, header_fmt,
231 propchange->name, APR_EOL_STR));
233 if (strcmp(propchange->name, SVN_PROP_MERGEINFO) == 0)
235 const char *orig = original_value ? original_value->data : NULL;
236 const char *val = propchange->value ? propchange->value->data : NULL;
238 SVN_ERR(display_mergeinfo_diff(orig, val, encoding, file, pool));
240 continue;
243 /* For now, we have a rather simple heuristic: if this is an
244 "svn:" property, then assume the value is UTF-8 and must
245 therefore be converted before printing. Otherwise, just
246 print whatever's there and hope for the best. */
248 svn_boolean_t val_is_utf8 = svn_prop_is_svn_prop(propchange->name);
250 if (original_value != NULL)
252 if (val_is_utf8)
254 SVN_ERR(file_printf_from_utf8
255 (file, encoding,
256 " - %s" APR_EOL_STR, original_value->data));
258 else
260 /* ### todo: check for error? */
261 apr_file_printf
262 (file, " - %s" APR_EOL_STR, original_value->data);
266 if (propchange->value != NULL)
268 if (val_is_utf8)
270 SVN_ERR(file_printf_from_utf8
271 (file, encoding, " + %s" APR_EOL_STR,
272 propchange->value->data));
274 else
276 /* ### todo: check for error? */
277 apr_file_printf(file, " + %s" APR_EOL_STR,
278 propchange->value->data);
284 /* ### todo [issue #1533]: Use file_printf_from_utf8() to convert this
285 to native encoding, at least conditionally? Or is it better to
286 have under_string always output the same eol, so programs can
287 find it consistently? Also, what about checking for error? */
288 apr_file_printf(file, APR_EOL_STR);
290 return SVN_NO_ERROR;
294 /*-----------------------------------------------------------------*/
296 /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
299 struct diff_cmd_baton {
301 /* If non-null, the external diff command to invoke. */
302 const char *diff_cmd;
304 /* This is allocated in this struct's pool or a higher-up pool. */
305 union {
306 /* If 'diff_cmd' is null, then this is the parsed options to
307 pass to the internal libsvn_diff implementation. */
308 svn_diff_file_options_t *for_internal;
309 /* Else if 'diff_cmd' is non-null, then... */
310 struct {
311 /* ...this is an argument array for the external command, and */
312 const char **argv;
313 /* ...this is the length of argv. */
314 int argc;
315 } for_external;
316 } options;
318 apr_pool_t *pool;
319 apr_file_t *outfile;
320 apr_file_t *errfile;
322 const char *header_encoding;
324 /* The original targets passed to the diff command. We may need
325 these to construct distinctive diff labels when comparing the
326 same relative path in the same revision, under different anchors
327 (for example, when comparing a trunk against a branch). */
328 const char *orig_path_1;
329 const char *orig_path_2;
331 /* These are the numeric representations of the revisions passed to
332 svn_client_diff4, either may be SVN_INVALID_REVNUM. We need these
333 because some of the svn_wc_diff_callbacks3_t don't get revision
334 arguments.
336 ### Perhaps we should change the callback signatures and eliminate
337 ### these?
339 svn_revnum_t revnum1;
340 svn_revnum_t revnum2;
342 /* Set this if you want diff output even for binary files. */
343 svn_boolean_t force_binary;
345 /* Set this flag if you want diff_file_changed to output diffs
346 unconditionally, even if the diffs are empty. */
347 svn_boolean_t force_empty;
349 /* The directory that diff target paths should be considered as
350 relative to for output generation (see issue #2723). */
351 const char *relative_to_dir;
355 /* Generate a label for the diff output for file PATH at revision REVNUM.
356 If REVNUM is invalid then it is assumed to be the current working
357 copy. Assumes the paths are already in the desired style (local
358 vs internal). Allocate the label in POOL. */
359 static const char *
360 diff_label(const char *path,
361 svn_revnum_t revnum,
362 apr_pool_t *pool)
364 const char *label;
365 if (revnum != SVN_INVALID_REVNUM)
366 label = apr_psprintf(pool, _("%s\t(revision %ld)"), path, revnum);
367 else
368 label = apr_psprintf(pool, _("%s\t(working copy)"), path);
370 return label;
373 /* An svn_wc_diff_callbacks3_t function. Used for both file and directory
374 property diffs. */
375 static svn_error_t *
376 diff_props_changed(svn_wc_adm_access_t *adm_access,
377 svn_wc_notify_state_t *state,
378 const char *path,
379 const apr_array_header_t *propchanges,
380 apr_hash_t *original_props,
381 void *diff_baton)
383 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
384 apr_array_header_t *props;
385 apr_pool_t *subpool = svn_pool_create(diff_cmd_baton->pool);
387 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, subpool));
389 if (props->nelts > 0)
390 SVN_ERR(display_prop_diffs(props, original_props, path,
391 diff_cmd_baton->header_encoding,
392 diff_cmd_baton->outfile,
393 diff_cmd_baton->relative_to_dir,
394 subpool));
396 if (state)
397 *state = svn_wc_notify_state_unknown;
399 svn_pool_destroy(subpool);
400 return SVN_NO_ERROR;
403 /* Show differences between TMPFILE1 and TMPFILE2. PATH, REV1, and REV2 are
404 used in the headers to indicate the file and revisions. If either
405 MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
406 but instead print a warning message. */
407 static svn_error_t *
408 diff_content_changed(const char *path,
409 const char *tmpfile1,
410 const char *tmpfile2,
411 svn_revnum_t rev1,
412 svn_revnum_t rev2,
413 const char *mimetype1,
414 const char *mimetype2,
415 void *diff_baton)
417 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
418 int exitcode;
419 apr_pool_t *subpool = svn_pool_create(diff_cmd_baton->pool);
420 svn_stream_t *os;
421 const char *rel_to_dir = diff_cmd_baton->relative_to_dir;
422 apr_file_t *errfile = diff_cmd_baton->errfile;
423 const char *label1, *label2;
424 svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
425 const char *path1, *path2;
426 int i;
428 /* Get a stream from our output file. */
429 os = svn_stream_from_aprfile(diff_cmd_baton->outfile, subpool);
431 /* Generate the diff headers. */
433 /* ### Holy cow. Due to anchor/target weirdness, we can't
434 simply join diff_cmd_baton->orig_path_1 with path, ditto for
435 orig_path_2. That will work when they're directory URLs, but
436 not for file URLs. Nor can we just use anchor1 and anchor2
437 from do_diff(), at least not without some more logic here.
438 What a nightmare.
440 For now, to distinguish the two paths, we'll just put the
441 unique portions of the original targets in parentheses after
442 the received path, with ellipses for handwaving. This makes
443 the labels a bit clumsy, but at least distinctive. Better
444 solutions are possible, they'll just take more thought. */
446 path1 = diff_cmd_baton->orig_path_1;
447 path2 = diff_cmd_baton->orig_path_2;
449 for (i = 0; path1[i] && path2[i] && (path1[i] == path2[i]); i++)
452 /* Make sure the prefix is made of whole components. (Issue #1771) */
453 if (path1[i] || path2[i])
455 for ( ; (i > 0) && (path1[i] != '/'); i--)
459 path1 = path1 + i;
460 path2 = path2 + i;
462 /* ### Should diff labels print paths in local style? Is there
463 already a standard for this? In any case, this code depends on
464 a particular style, so not calling svn_path_local_style() on the
465 paths below.*/
466 if (path1[0] == '\0')
467 path1 = apr_psprintf(subpool, "%s", path);
468 else if (path1[0] == '/')
469 path1 = apr_psprintf(subpool, "%s\t(...%s)", path, path1);
470 else
471 path1 = apr_psprintf(subpool, "%s\t(.../%s)", path, path1);
473 if (path2[0] == '\0')
474 path2 = apr_psprintf(subpool, "%s", path);
475 else if (path2[0] == '/')
476 path2 = apr_psprintf(subpool, "%s\t(...%s)", path, path2);
477 else
478 path2 = apr_psprintf(subpool, "%s\t(.../%s)", path, path2);
480 if (diff_cmd_baton->relative_to_dir)
482 /* Possibly adjust the paths shown in the output (see issue #2723). */
483 const char *child_path = svn_path_is_child(rel_to_dir, path, subpool);
485 if (child_path)
486 path = child_path;
487 else if (!svn_path_compare_paths(rel_to_dir, path))
488 path = ".";
489 else
490 return MAKE_ERR_BAD_RELATIVE_PATH(path, rel_to_dir);
492 child_path = svn_path_is_child(rel_to_dir, path1, subpool);
494 if (child_path)
495 path1 = child_path;
496 else if (!svn_path_compare_paths(rel_to_dir, path1))
497 path1 = ".";
498 else
499 return MAKE_ERR_BAD_RELATIVE_PATH(path1, rel_to_dir);
501 child_path = svn_path_is_child(rel_to_dir, path2, subpool);
503 if (child_path)
504 path2 = child_path;
505 else if (!svn_path_compare_paths(rel_to_dir, path2))
506 path2 = ".";
507 else
508 return MAKE_ERR_BAD_RELATIVE_PATH(path2, rel_to_dir);
511 label1 = diff_label(path1, rev1, subpool);
512 label2 = diff_label(path2, rev2, subpool);
514 /* Possible easy-out: if either mime-type is binary and force was not
515 specified, don't attempt to generate a viewable diff at all.
516 Print a warning and exit. */
517 if (mimetype1)
518 mt1_binary = svn_mime_type_is_binary(mimetype1);
519 if (mimetype2)
520 mt2_binary = svn_mime_type_is_binary(mimetype2);
522 if (! diff_cmd_baton->force_binary && (mt1_binary || mt2_binary))
524 /* Print out the diff header. */
525 SVN_ERR(svn_stream_printf_from_utf8
526 (os, diff_cmd_baton->header_encoding, subpool,
527 "Index: %s" APR_EOL_STR "%s" APR_EOL_STR, path, equal_string));
529 SVN_ERR(svn_stream_printf_from_utf8
530 (os, diff_cmd_baton->header_encoding, subpool,
531 _("Cannot display: file marked as a binary type.%s"),
532 APR_EOL_STR));
534 if (mt1_binary && !mt2_binary)
535 SVN_ERR(svn_stream_printf_from_utf8
536 (os, diff_cmd_baton->header_encoding, subpool,
537 "svn:mime-type = %s" APR_EOL_STR, mimetype1));
538 else if (mt2_binary && !mt1_binary)
539 SVN_ERR(svn_stream_printf_from_utf8
540 (os, diff_cmd_baton->header_encoding, subpool,
541 "svn:mime-type = %s" APR_EOL_STR, mimetype2));
542 else if (mt1_binary && mt2_binary)
544 if (strcmp(mimetype1, mimetype2) == 0)
545 SVN_ERR(svn_stream_printf_from_utf8
546 (os, diff_cmd_baton->header_encoding, subpool,
547 "svn:mime-type = %s" APR_EOL_STR,
548 mimetype1));
549 else
550 SVN_ERR(svn_stream_printf_from_utf8
551 (os, diff_cmd_baton->header_encoding, subpool,
552 "svn:mime-type = (%s, %s)" APR_EOL_STR,
553 mimetype1, mimetype2));
556 /* Exit early. */
557 svn_pool_destroy(subpool);
558 return SVN_NO_ERROR;
562 if (diff_cmd_baton->diff_cmd)
564 /* Print out the diff header. */
565 SVN_ERR(svn_stream_printf_from_utf8
566 (os, diff_cmd_baton->header_encoding, subpool,
567 "Index: %s" APR_EOL_STR "%s" APR_EOL_STR, path, equal_string));
568 /* Close the stream (flush) */
569 SVN_ERR(svn_stream_close(os));
571 SVN_ERR(svn_io_run_diff(".",
572 diff_cmd_baton->options.for_external.argv,
573 diff_cmd_baton->options.for_external.argc,
574 label1, label2,
575 tmpfile1, tmpfile2,
576 &exitcode, diff_cmd_baton->outfile, errfile,
577 diff_cmd_baton->diff_cmd, subpool));
579 else /* use libsvn_diff to generate the diff */
581 svn_diff_t *diff;
583 SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2,
584 diff_cmd_baton->options.for_internal,
585 subpool));
587 if (svn_diff_contains_diffs(diff) || diff_cmd_baton->force_empty)
589 /* Print out the diff header. */
590 SVN_ERR(svn_stream_printf_from_utf8
591 (os, diff_cmd_baton->header_encoding, subpool,
592 "Index: %s" APR_EOL_STR "%s" APR_EOL_STR,
593 path, equal_string));
594 /* Output the actual diff */
595 SVN_ERR(svn_diff_file_output_unified3
596 (os, diff, tmpfile1, tmpfile2, label1, label2,
597 diff_cmd_baton->header_encoding, rel_to_dir,
598 diff_cmd_baton->options.for_internal->show_c_function,
599 subpool));
603 /* ### todo: someday we'll need to worry about whether we're going
604 to need to write a diff plug-in mechanism that makes use of the
605 two paths, instead of just blindly running SVN_CLIENT_DIFF. */
607 /* Destroy the subpool. */
608 svn_pool_destroy(subpool);
610 return SVN_NO_ERROR;
613 /* An svn_wc_diff_callbacks3_t function. */
614 static svn_error_t *
615 diff_file_changed(svn_wc_adm_access_t *adm_access,
616 svn_wc_notify_state_t *content_state,
617 svn_wc_notify_state_t *prop_state,
618 const char *path,
619 const char *tmpfile1,
620 const char *tmpfile2,
621 svn_revnum_t rev1,
622 svn_revnum_t rev2,
623 const char *mimetype1,
624 const char *mimetype2,
625 const apr_array_header_t *prop_changes,
626 apr_hash_t *original_props,
627 void *diff_baton)
629 if (tmpfile1)
630 SVN_ERR(diff_content_changed(path,
631 tmpfile1, tmpfile2, rev1, rev2,
632 mimetype1, mimetype2, diff_baton));
633 if (prop_changes->nelts > 0)
634 SVN_ERR(diff_props_changed(adm_access, prop_state, path, prop_changes,
635 original_props, diff_baton));
636 if (content_state)
637 *content_state = svn_wc_notify_state_unknown;
638 if (prop_state)
639 *prop_state = svn_wc_notify_state_unknown;
640 return SVN_NO_ERROR;
643 /* Because the repos-diff editor passes at least one empty file to
644 each of these next two functions, they can be dumb wrappers around
645 the main workhorse routine. */
647 /* An svn_wc_diff_callbacks3_t function. */
648 static svn_error_t *
649 diff_file_added(svn_wc_adm_access_t *adm_access,
650 svn_wc_notify_state_t *content_state,
651 svn_wc_notify_state_t *prop_state,
652 const char *path,
653 const char *tmpfile1,
654 const char *tmpfile2,
655 svn_revnum_t rev1,
656 svn_revnum_t rev2,
657 const char *mimetype1,
658 const char *mimetype2,
659 const apr_array_header_t *prop_changes,
660 apr_hash_t *original_props,
661 void *diff_baton)
663 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
665 /* We want diff_file_changed to unconditionally show diffs, even if
666 the diff is empty (as would be the case if an empty file were
667 added.) It's important, because 'patch' would still see an empty
668 diff and create an empty file. It's also important to let the
669 user see that *something* happened. */
670 diff_cmd_baton->force_empty = TRUE;
672 SVN_ERR(diff_file_changed(adm_access, content_state, prop_state, path,
673 tmpfile1, tmpfile2,
674 rev1, rev2,
675 mimetype1, mimetype2,
676 prop_changes, original_props, diff_baton));
678 diff_cmd_baton->force_empty = FALSE;
680 return SVN_NO_ERROR;
683 /* An svn_wc_diff_callbacks3_t function. */
684 static svn_error_t *
685 diff_file_deleted_with_diff(svn_wc_adm_access_t *adm_access,
686 svn_wc_notify_state_t *state,
687 const char *path,
688 const char *tmpfile1,
689 const char *tmpfile2,
690 const char *mimetype1,
691 const char *mimetype2,
692 apr_hash_t *original_props,
693 void *diff_baton)
695 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
697 /* We don't list all the deleted properties. */
698 return diff_file_changed(adm_access, state, NULL, path,
699 tmpfile1, tmpfile2,
700 diff_cmd_baton->revnum1, diff_cmd_baton->revnum2,
701 mimetype1, mimetype2,
702 apr_array_make(diff_cmd_baton->pool, 1,
703 sizeof(svn_prop_t)),
704 apr_hash_make(diff_cmd_baton->pool), diff_baton);
707 /* An svn_wc_diff_callbacks3_t function. */
708 static svn_error_t *
709 diff_file_deleted_no_diff(svn_wc_adm_access_t *adm_access,
710 svn_wc_notify_state_t *state,
711 const char *path,
712 const char *tmpfile1,
713 const char *tmpfile2,
714 const char *mimetype1,
715 const char *mimetype2,
716 apr_hash_t *original_props,
717 void *diff_baton)
719 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
721 if (state)
722 *state = svn_wc_notify_state_unknown;
724 SVN_ERR(file_printf_from_utf8
725 (diff_cmd_baton->outfile,
726 diff_cmd_baton->header_encoding,
727 "Index: %s (deleted)" APR_EOL_STR "%s" APR_EOL_STR,
728 path, equal_string));
730 return SVN_NO_ERROR;
733 /* An svn_wc_diff_callbacks3_t function.
734 For now, let's have 'svn diff' send feedback to the top-level
735 application, so that something reasonable about directories and
736 propsets gets printed to stdout. */
737 static svn_error_t *
738 diff_dir_added(svn_wc_adm_access_t *adm_access,
739 svn_wc_notify_state_t *state,
740 const char *path,
741 svn_revnum_t rev,
742 void *diff_baton)
744 if (state)
745 *state = svn_wc_notify_state_unknown;
747 /* ### todo: send feedback to app */
748 return SVN_NO_ERROR;
751 /* An svn_wc_diff_callbacks3_t function. */
752 static svn_error_t *
753 diff_dir_deleted(svn_wc_adm_access_t *adm_access,
754 svn_wc_notify_state_t *state,
755 const char *path,
756 void *diff_baton)
758 if (state)
759 *state = svn_wc_notify_state_unknown;
761 return SVN_NO_ERROR;
764 /* An svn_wc_diff_callbacks3_t function. */
765 static svn_error_t *
766 diff_dir_opened(svn_wc_adm_access_t *adm_access,
767 const char *path,
768 svn_revnum_t rev,
769 void *diff_baton)
771 return SVN_NO_ERROR;
774 /* An svn_wc_diff_callbacks3_t function. */
775 static svn_error_t *
776 diff_dir_closed(svn_wc_adm_access_t *adm_access,
777 svn_wc_notify_state_t *state,
778 const char *path,
779 void *diff_baton)
781 if (state)
782 *state = svn_wc_notify_state_unknown;
784 return SVN_NO_ERROR;
788 /*-----------------------------------------------------------------*/
790 /** The logic behind 'svn diff' and 'svn merge'. */
793 /* Hi! This is a comment left behind by Karl, and Ben is too afraid
794 to erase it at this time, because he's not fully confident that all
795 this knowledge has been grokked yet.
797 There are five cases:
798 1. path is not an URL and start_revision != end_revision
799 2. path is not an URL and start_revision == end_revision
800 3. path is an URL and start_revision != end_revision
801 4. path is an URL and start_revision == end_revision
802 5. path is not an URL and no revisions given
804 With only one distinct revision the working copy provides the
805 other. When path is an URL there is no working copy. Thus
807 1: compare repository versions for URL coresponding to working copy
808 2: compare working copy against repository version
809 3: compare repository versions for URL
810 4: nothing to do.
811 5: compare working copy against text-base
813 Case 4 is not as stupid as it looks, for example it may occur if
814 the user specifies two dates that resolve to the same revision. */
819 /* Helper function: given a working-copy PATH, return its associated
820 url in *URL, allocated in POOL. If PATH is *already* a URL, that's
821 fine, just set *URL = PATH. */
822 static svn_error_t *
823 convert_to_url(const char **url,
824 const char *path,
825 apr_pool_t *pool)
827 svn_wc_adm_access_t *adm_access; /* ### FIXME local */
828 const svn_wc_entry_t *entry;
830 if (svn_path_is_url(path))
832 *url = path;
833 return SVN_NO_ERROR;
836 /* ### This may not be a good idea, see issue 880 */
837 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path, FALSE,
838 0, NULL, NULL, pool));
839 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
840 SVN_ERR(svn_wc_adm_close(adm_access));
842 if (entry->url)
843 *url = apr_pstrdup(pool, entry->url);
844 else
845 *url = apr_pstrdup(pool, entry->copyfrom_url);
846 return SVN_NO_ERROR;
849 /** Helper structure: for passing around the diff parameters */
850 struct diff_parameters
852 /* First input path */
853 const char *path1;
855 /* Revision of first input path */
856 const svn_opt_revision_t *revision1;
858 /* Second input path */
859 const char *path2;
861 /* Revision of second input path */
862 const svn_opt_revision_t *revision2;
864 /* Peg revision */
865 const svn_opt_revision_t *peg_revision;
867 /* Desired depth */
868 svn_depth_t depth;
870 /* Ignore ancestry */
871 svn_boolean_t ignore_ancestry;
873 /* Ignore deleted */
874 svn_boolean_t no_diff_deleted;
876 /* Changelists of interest */
877 const apr_array_header_t *changelists;
880 /** Helper structure: filled by check_paths() */
881 struct diff_paths
883 /* path1 can only be found in the repository? */
884 svn_boolean_t is_repos1;
886 /* path2 can only be found in the repository? */
887 svn_boolean_t is_repos2;
891 /** Check if paths are urls and if the revisions are local, and, for
892 pegged revisions, ensure that at least one revision is non-local.
893 Fills the PATHS structure. */
894 static svn_error_t *
895 check_paths(const struct diff_parameters *params,
896 struct diff_paths *paths)
898 svn_boolean_t is_local_rev1, is_local_rev2;
900 /* Verify our revision arguments in light of the paths. */
901 if ((params->revision1->kind == svn_opt_revision_unspecified)
902 || (params->revision2->kind == svn_opt_revision_unspecified))
903 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
904 _("Not all required revisions are specified"));
906 /* Revisions can be said to be local or remote. BASE and WORKING,
907 for example, are local. */
908 is_local_rev1 =
909 ((params->revision1->kind == svn_opt_revision_base)
910 || (params->revision1->kind == svn_opt_revision_working));
911 is_local_rev2 =
912 ((params->revision2->kind == svn_opt_revision_base)
913 || (params->revision2->kind == svn_opt_revision_working));
915 if (params->peg_revision->kind != svn_opt_revision_unspecified)
917 if (is_local_rev1 && is_local_rev2)
918 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
919 _("At least one revision must be non-local "
920 "for a pegged diff"));
922 paths->is_repos1 = ! is_local_rev1;
923 paths->is_repos2 = ! is_local_rev2;
925 else
927 /* Working copy paths with non-local revisions get turned into
928 URLs. We don't do that here, though. We simply record that it
929 needs to be done, which is information that helps us choose our
930 diff helper function. */
931 paths->is_repos1 = ! is_local_rev1 || svn_path_is_url(params->path1);
932 paths->is_repos2 = ! is_local_rev2 || svn_path_is_url(params->path2);
935 return SVN_NO_ERROR;
938 /** Helper structure filled by diff_prepare_repos_repos */
939 struct diff_repos_repos_t
941 /* URL created from path1 */
942 const char *url1;
944 /* URL created from path2 */
945 const char *url2;
947 /* The BASE_PATH for the diff */
948 const char *base_path;
950 /* url1 and url2 are the same */
951 svn_boolean_t same_urls;
953 /* Revision of url1 */
954 svn_revnum_t rev1;
956 /* Revision of url2 */
957 svn_revnum_t rev2;
959 /* Anchor based on url1 */
960 const char *anchor1;
962 /* Anchor based on url2 */
963 const char *anchor2;
965 /* Target based on url1 */
966 const char *target1;
968 /* Target based on url2 */
969 const char *target2;
971 /* RA session pointing at anchor1. */
972 svn_ra_session_t *ra_session;
975 /** Helper function: prepare a repos repos diff. Fills DRR
976 * structure. */
977 static svn_error_t *
978 diff_prepare_repos_repos(const struct diff_parameters *params,
979 struct diff_repos_repos_t *drr,
980 svn_client_ctx_t *ctx,
981 apr_pool_t *pool)
983 svn_ra_session_t *ra_session;
984 svn_node_kind_t kind1, kind2;
986 /* Figure out URL1 and URL2. */
987 SVN_ERR(convert_to_url(&drr->url1, params->path1, pool));
988 SVN_ERR(convert_to_url(&drr->url2, params->path2, pool));
989 drr->same_urls = (strcmp(drr->url1, drr->url2) == 0);
991 /* We need exactly one BASE_PATH, so we'll let the BASE_PATH
992 calculated for PATH2 override the one for PATH1 (since the diff
993 will be "applied" to URL2 anyway). */
994 drr->base_path = NULL;
995 if (drr->url1 != params->path1)
996 drr->base_path = params->path1;
997 if (drr->url2 != params->path2)
998 drr->base_path = params->path2;
1000 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, drr->url2,
1001 NULL, NULL, NULL, FALSE,
1002 TRUE, ctx, pool));
1004 /* If we are performing a pegged diff, we need to find out what our
1005 actual URLs will be. */
1006 if (params->peg_revision->kind != svn_opt_revision_unspecified)
1008 svn_opt_revision_t *start_ignore, *end_ignore;
1010 SVN_ERR(svn_client__repos_locations(&drr->url1, &start_ignore,
1011 &drr->url2, &end_ignore,
1012 ra_session,
1013 params->path2,
1014 params->peg_revision,
1015 params->revision1,
1016 params->revision2,
1017 ctx, pool));
1018 /* Reparent the session, since drr->url2 might have changed as a result
1019 the above call. */
1020 SVN_ERR(svn_ra_reparent(ra_session, drr->url2, pool));
1023 /* Resolve revision and get path kind for the second target. */
1024 SVN_ERR(svn_client__get_revision_number
1025 (&drr->rev2, NULL, ra_session, params->revision2,
1026 (params->path2 == drr->url2) ? NULL : params->path2, pool));
1027 SVN_ERR(svn_ra_check_path(ra_session, "", drr->rev2, &kind2, pool));
1028 if (kind2 == svn_node_none)
1029 return svn_error_createf
1030 (SVN_ERR_FS_NOT_FOUND, NULL,
1031 _("'%s' was not found in the repository at revision %ld"),
1032 drr->url2, drr->rev2);
1034 /* Do the same for the first target. */
1035 SVN_ERR(svn_ra_reparent(ra_session, drr->url1, pool));
1036 SVN_ERR(svn_client__get_revision_number
1037 (&drr->rev1, NULL, ra_session, params->revision1,
1038 (params->path1 == drr->url1) ? NULL : params->path1, pool));
1039 SVN_ERR(svn_ra_check_path(ra_session, "", drr->rev1, &kind1, pool));
1040 if (kind1 == svn_node_none)
1041 return svn_error_createf
1042 (SVN_ERR_FS_NOT_FOUND, NULL,
1043 _("'%s' was not found in the repository at revision %ld"),
1044 drr->url1, drr->rev1);
1046 /* Choose useful anchors and targets for our two URLs. */
1047 drr->anchor1 = drr->url1;
1048 drr->anchor2 = drr->url2;
1049 drr->target1 = "";
1050 drr->target2 = "";
1051 if ((kind1 == svn_node_file) || (kind2 == svn_node_file))
1053 svn_path_split(drr->url1, &drr->anchor1, &drr->target1, pool);
1054 drr->target1 = svn_path_uri_decode(drr->target1, pool);
1055 svn_path_split(drr->url2, &drr->anchor2, &drr->target2, pool);
1056 drr->target2 = svn_path_uri_decode(drr->target2, pool);
1057 if (drr->base_path)
1058 drr->base_path = svn_path_dirname(drr->base_path, pool);
1059 SVN_ERR(svn_ra_reparent(ra_session, drr->anchor1, pool));
1062 drr->ra_session = ra_session;
1063 return SVN_NO_ERROR;
1066 /* A Theoretical Note From Ben, regarding do_diff().
1068 This function is really svn_client_diff4(). If you read the public
1069 API description for svn_client_diff4(), it sounds quite Grand. It
1070 sounds really generalized and abstract and beautiful: that it will
1071 diff any two paths, be they working-copy paths or URLs, at any two
1072 revisions.
1074 Now, the *reality* is that we have exactly three 'tools' for doing
1075 diffing, and thus this routine is built around the use of the three
1076 tools. Here they are, for clarity:
1078 - svn_wc_diff: assumes both paths are the same wcpath.
1079 compares wcpath@BASE vs. wcpath@WORKING
1081 - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING
1083 - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2
1085 So the truth of the matter is, if the caller's arguments can't be
1086 pigeonholed into one of these three use-cases, we currently bail
1087 with a friendly apology.
1089 Perhaps someday a brave soul will truly make svn_client_diff4
1090 perfectly general. For now, we live with the 90% case. Certainly,
1091 the commandline client only calls this function in legal ways.
1092 When there are other users of svn_client.h, maybe this will become
1093 a more pressing issue.
1096 /* Return a "you can't do that" error, optionally wrapping another
1097 error CHILD_ERR. */
1098 static svn_error_t *
1099 unsupported_diff_error(svn_error_t *child_err)
1101 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
1102 _("Sorry, svn_client_diff4 was called in a way "
1103 "that is not yet supported"));
1107 /* Perform a diff between two working-copy paths.
1109 PATH1 and PATH2 are both working copy paths. REVISION1 and
1110 REVISION2 are their respective revisions.
1112 All other options are the same as those passed to svn_client_diff4(). */
1113 static svn_error_t *
1114 diff_wc_wc(const char *path1,
1115 const svn_opt_revision_t *revision1,
1116 const char *path2,
1117 const svn_opt_revision_t *revision2,
1118 svn_depth_t depth,
1119 svn_boolean_t ignore_ancestry,
1120 const apr_array_header_t *changelists,
1121 const svn_wc_diff_callbacks3_t *callbacks,
1122 struct diff_cmd_baton *callback_baton,
1123 svn_client_ctx_t *ctx,
1124 apr_pool_t *pool)
1126 svn_wc_adm_access_t *adm_access, *target_access;
1127 const char *target;
1128 int levels_to_lock = SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth);
1130 /* Assert that we have valid input. */
1131 assert(! svn_path_is_url(path1));
1132 assert(! svn_path_is_url(path2));
1134 /* Currently we support only the case where path1 and path2 are the
1135 same path. */
1136 if ((strcmp(path1, path2) != 0)
1137 || (! ((revision1->kind == svn_opt_revision_base)
1138 && (revision2->kind == svn_opt_revision_working))))
1139 return unsupported_diff_error
1140 (svn_error_create
1141 (SVN_ERR_INCORRECT_PARAMS, NULL,
1142 _("Only diffs between a path's text-base "
1143 "and its working files are supported at this time")));
1145 SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &target_access, &target,
1146 path1, FALSE, levels_to_lock,
1147 ctx->cancel_func, ctx->cancel_baton,
1148 pool));
1150 /* Resolve named revisions to real numbers. */
1151 SVN_ERR(svn_client__get_revision_number
1152 (&callback_baton->revnum1, NULL, NULL, revision1, path1, pool));
1153 callback_baton->revnum2 = SVN_INVALID_REVNUM; /* WC */
1155 SVN_ERR(svn_wc_diff5(adm_access, target, callbacks, callback_baton,
1156 depth, ignore_ancestry, changelists, pool));
1157 SVN_ERR(svn_wc_adm_close(adm_access));
1158 return SVN_NO_ERROR;
1162 /* Perform a diff between two repository paths.
1164 DIFF_PARAM.PATH1 and DIFF_PARAM.PATH2 may be either URLs or the working
1165 copy paths. DIFF_PARAM.REVISION1 and DIFF_PARAM.REVISION2 are their
1166 respective revisions. If DIFF_PARAM.PEG_REVISION is specified,
1167 DIFF_PARAM.PATH2 is the path at the peg revision, and the actual two
1168 paths compared are determined by following copy history from PATH2.
1170 All other options are the same as those passed to svn_client_diff4(). */
1171 static svn_error_t *
1172 diff_repos_repos(const struct diff_parameters *diff_param,
1173 const svn_wc_diff_callbacks3_t *callbacks,
1174 struct diff_cmd_baton *callback_baton,
1175 svn_client_ctx_t *ctx,
1176 apr_pool_t *pool)
1178 svn_ra_session_t *extra_ra_session;
1180 const svn_ra_reporter3_t *reporter;
1181 void *report_baton;
1183 const svn_delta_editor_t *diff_editor;
1184 void *diff_edit_baton;
1186 struct diff_repos_repos_t drr;
1188 /* Prepare info for the repos repos diff. */
1189 SVN_ERR(diff_prepare_repos_repos(diff_param, &drr, ctx, pool));
1191 /* Get actual URLs. */
1192 callback_baton->orig_path_1 = drr.url1;
1193 callback_baton->orig_path_2 = drr.url2;
1195 /* Get numeric revisions. */
1196 callback_baton->revnum1 = drr.rev1;
1197 callback_baton->revnum2 = drr.rev2;
1199 /* Now, we open an extra RA session to the correct anchor
1200 location for URL1. This is used during the editor calls to fetch file
1201 contents. */
1202 SVN_ERR(svn_client__open_ra_session_internal
1203 (&extra_ra_session, drr.anchor1, NULL, NULL, NULL, FALSE, TRUE, ctx,
1204 pool));
1206 /* Set up the repos_diff editor on BASE_PATH, if available.
1207 Otherwise, we just use "". */
1208 SVN_ERR(svn_client__get_diff_editor
1209 (drr.base_path ? drr.base_path : "",
1210 NULL, callbacks, callback_baton, diff_param->depth,
1211 FALSE /* doesn't matter for diff */, extra_ra_session, drr.rev1,
1212 NULL /* no notify_func */, NULL /* no notify_baton */,
1213 ctx->cancel_func, ctx->cancel_baton,
1214 &diff_editor, &diff_edit_baton, pool));
1216 /* We want to switch our txn into URL2 */
1217 SVN_ERR(svn_ra_do_diff3
1218 (drr.ra_session, &reporter, &report_baton, drr.rev2, drr.target1,
1219 diff_param->depth, diff_param->ignore_ancestry, TRUE,
1220 drr.url2, diff_editor, diff_edit_baton, pool));
1222 /* Drive the reporter; do the diff. */
1223 SVN_ERR(reporter->set_path(report_baton, "", drr.rev1,
1224 svn_depth_infinity,
1225 FALSE, NULL,
1226 pool));
1227 SVN_ERR(reporter->finish_report(report_baton, pool));
1229 return SVN_NO_ERROR;
1233 /* Perform a diff between a repository path and a working-copy path.
1235 PATH1 may be either a URL or a working copy path. PATH2 is a
1236 working copy path. REVISION1 and REVISION2 are their respective
1237 revisions. If REVERSE is TRUE, the diff will be done in reverse.
1238 If PEG_REVISION is specified, then PATH1 is the path in the peg
1239 revision, and the actual repository path to be compared is
1240 determined by following copy history.
1242 All other options are the same as those passed to svn_client_diff4(). */
1243 static svn_error_t *
1244 diff_repos_wc(const char *path1,
1245 const svn_opt_revision_t *revision1,
1246 const svn_opt_revision_t *peg_revision,
1247 const char *path2,
1248 const svn_opt_revision_t *revision2,
1249 svn_boolean_t reverse,
1250 svn_depth_t depth,
1251 svn_boolean_t ignore_ancestry,
1252 const apr_array_header_t *changelists,
1253 const svn_wc_diff_callbacks3_t *callbacks,
1254 struct diff_cmd_baton *callback_baton,
1255 svn_client_ctx_t *ctx,
1256 apr_pool_t *pool)
1258 const char *url1, *anchor, *anchor_url, *target;
1259 svn_wc_adm_access_t *adm_access, *dir_access;
1260 const svn_wc_entry_t *entry;
1261 svn_revnum_t rev;
1262 svn_ra_session_t *ra_session;
1263 const svn_ra_reporter3_t *reporter;
1264 void *report_baton;
1265 const svn_delta_editor_t *diff_editor;
1266 void *diff_edit_baton;
1267 svn_boolean_t rev2_is_base = (revision2->kind == svn_opt_revision_base);
1268 int levels_to_lock = SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth);
1269 svn_boolean_t server_supports_depth;
1271 /* Assert that we have valid input. */
1272 assert(! svn_path_is_url(path2));
1274 /* Convert path1 to a URL to feed to do_diff. */
1275 SVN_ERR(convert_to_url(&url1, path1, pool));
1277 SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target,
1278 path2, FALSE, levels_to_lock,
1279 ctx->cancel_func, ctx->cancel_baton,
1280 pool));
1281 anchor = svn_wc_adm_access_path(adm_access);
1283 /* Fetch the URL of the anchor directory. */
1284 SVN_ERR(svn_wc__entry_versioned(&entry, anchor, adm_access, FALSE, pool));
1285 if (! entry->url)
1286 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
1287 _("Directory '%s' has no URL"),
1288 svn_path_local_style(anchor, pool));
1289 anchor_url = apr_pstrdup(pool, entry->url);
1291 /* If we are performing a pegged diff, we need to find out what our
1292 actual URLs will be. */
1293 if (peg_revision->kind != svn_opt_revision_unspecified)
1295 svn_opt_revision_t *start_ignore, *end_ignore, end;
1296 const char *url_ignore;
1298 end.kind = svn_opt_revision_unspecified;
1300 SVN_ERR(svn_client__repos_locations(&url1, &start_ignore,
1301 &url_ignore, &end_ignore,
1302 NULL,
1303 path1,
1304 peg_revision,
1305 revision1, &end,
1306 ctx, pool));
1308 if (!reverse)
1310 callback_baton->orig_path_1 = url1;
1311 callback_baton->orig_path_2 = svn_path_join(anchor_url, target, pool);
1313 else
1315 callback_baton->orig_path_1 = svn_path_join(anchor_url, target, pool);
1316 callback_baton->orig_path_2 = url1;
1320 /* Establish RA session to path2's anchor */
1321 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, anchor_url,
1322 NULL, NULL, NULL, FALSE, TRUE,
1323 ctx, pool));
1325 SVN_ERR(svn_wc_get_diff_editor5(adm_access, target,
1326 callbacks, callback_baton,
1327 depth,
1328 ignore_ancestry,
1329 rev2_is_base,
1330 reverse,
1331 ctx->cancel_func, ctx->cancel_baton,
1332 changelists,
1333 &diff_editor, &diff_edit_baton,
1334 pool));
1336 /* Tell the RA layer we want a delta to change our txn to URL1 */
1337 SVN_ERR(svn_client__get_revision_number
1338 (&rev, NULL, ra_session, revision1,
1339 (path1 == url1) ? NULL : path1, pool));
1341 if (!reverse)
1342 callback_baton->revnum1 = rev;
1343 else
1344 callback_baton->revnum2 = rev;
1346 SVN_ERR(svn_ra_do_diff3(ra_session,
1347 &reporter, &report_baton,
1348 rev,
1349 target ? svn_path_uri_decode(target, pool) : NULL,
1350 depth,
1351 ignore_ancestry,
1352 TRUE, /* text_deltas */
1353 url1,
1354 diff_editor, diff_edit_baton, pool));
1356 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
1357 SVN_RA_CAPABILITY_DEPTH, pool));
1359 /* Create a txn mirror of path2; the diff editor will print
1360 diffs in reverse. :-) */
1361 SVN_ERR(svn_wc_crawl_revisions3(path2, dir_access,
1362 reporter, report_baton,
1363 FALSE, depth, (! server_supports_depth),
1364 FALSE, NULL, NULL, /* notification is N/A */
1365 NULL, pool));
1367 SVN_ERR(svn_wc_adm_close(adm_access));
1368 return SVN_NO_ERROR;
1372 /* This is basically just the guts of svn_client_diff[_peg]3(). */
1373 static svn_error_t *
1374 do_diff(const struct diff_parameters *diff_param,
1375 const svn_wc_diff_callbacks3_t *callbacks,
1376 struct diff_cmd_baton *callback_baton,
1377 svn_client_ctx_t *ctx,
1378 apr_pool_t *pool)
1380 struct diff_paths diff_paths;
1382 /* Check if paths/revisions are urls/local. */
1383 SVN_ERR(check_paths(diff_param, &diff_paths));
1385 if (diff_paths.is_repos1)
1387 if (diff_paths.is_repos2)
1389 SVN_ERR(diff_repos_repos(diff_param, callbacks, callback_baton,
1390 ctx, pool));
1392 else /* path2 is a working copy path */
1394 SVN_ERR(diff_repos_wc(diff_param->path1, diff_param->revision1,
1395 diff_param->peg_revision,
1396 diff_param->path2, diff_param->revision2,
1397 FALSE, diff_param->depth,
1398 diff_param->ignore_ancestry,
1399 diff_param->changelists,
1400 callbacks, callback_baton, ctx, pool));
1403 else /* path1 is a working copy path */
1405 if (diff_paths.is_repos2)
1407 SVN_ERR(diff_repos_wc(diff_param->path2, diff_param->revision2,
1408 diff_param->peg_revision,
1409 diff_param->path1, diff_param->revision1,
1410 TRUE, diff_param->depth,
1411 diff_param->ignore_ancestry,
1412 diff_param->changelists,
1413 callbacks, callback_baton, ctx, pool));
1415 else /* path2 is a working copy path */
1417 SVN_ERR(diff_wc_wc(diff_param->path1, diff_param->revision1,
1418 diff_param->path2, diff_param->revision2,
1419 diff_param->depth,
1420 diff_param->ignore_ancestry,
1421 diff_param->changelists,
1422 callbacks, callback_baton, ctx, pool));
1426 return SVN_NO_ERROR;
1429 /* Perform a diff summary between two repository paths. */
1430 static svn_error_t *
1431 diff_summarize_repos_repos(const struct diff_parameters *diff_param,
1432 svn_client_diff_summarize_func_t summarize_func,
1433 void *summarize_baton,
1434 svn_client_ctx_t *ctx,
1435 apr_pool_t *pool)
1437 svn_ra_session_t *extra_ra_session;
1439 const svn_ra_reporter3_t *reporter;
1440 void *report_baton;
1442 const svn_delta_editor_t *diff_editor;
1443 void *diff_edit_baton;
1445 struct diff_repos_repos_t drr;
1447 /* Prepare info for the repos repos diff. */
1448 SVN_ERR(diff_prepare_repos_repos(diff_param, &drr, ctx, pool));
1450 /* Now, we open an extra RA session to the correct anchor
1451 location for URL1. This is used to get the kind of deleted paths. */
1452 SVN_ERR(svn_client__open_ra_session_internal
1453 (&extra_ra_session, drr.anchor1, NULL, NULL, NULL, FALSE, TRUE,
1454 ctx, pool));
1456 /* Set up the repos_diff editor. */
1457 SVN_ERR(svn_client__get_diff_summarize_editor
1458 (drr.target2, summarize_func,
1459 summarize_baton, extra_ra_session, drr.rev1, ctx->cancel_func,
1460 ctx->cancel_baton, &diff_editor, &diff_edit_baton, pool));
1462 /* We want to switch our txn into URL2 */
1463 SVN_ERR(svn_ra_do_diff3
1464 (drr.ra_session, &reporter, &report_baton, drr.rev2, drr.target1,
1465 diff_param->depth, diff_param->ignore_ancestry,
1466 FALSE /* do not create text delta */, drr.url2, diff_editor,
1467 diff_edit_baton, pool));
1469 /* Drive the reporter; do the diff. */
1470 SVN_ERR(reporter->set_path(report_baton, "", drr.rev1,
1471 svn_depth_infinity,
1472 FALSE, NULL, pool));
1473 SVN_ERR(reporter->finish_report(report_baton, pool));
1475 return SVN_NO_ERROR;
1478 /* This is basically just the guts of svn_client_diff_summarize[_peg](). */
1479 static svn_error_t *
1480 do_diff_summarize(const struct diff_parameters *diff_param,
1481 svn_client_diff_summarize_func_t summarize_func,
1482 void *summarize_baton,
1483 svn_client_ctx_t *ctx,
1484 apr_pool_t *pool)
1486 struct diff_paths diff_paths;
1488 /* Check if paths/revisions are urls/local. */
1489 SVN_ERR(check_paths(diff_param, &diff_paths));
1491 if (diff_paths.is_repos1 && diff_paths.is_repos2)
1493 SVN_ERR(diff_summarize_repos_repos(diff_param, summarize_func,
1494 summarize_baton, ctx, pool));
1496 else
1497 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1498 _("Summarizing diff can only compare repository "
1499 "to repository"));
1501 return SVN_NO_ERROR;
1505 /* Initialize DIFF_CMD_BATON.diff_cmd and DIFF_CMD_BATON.options,
1506 * according to OPTIONS and CONFIG. CONFIG may be null.
1507 * Allocate the fields in POOL, which should be at least as long-lived
1508 * as the pool DIFF_CMD_BATON itself is allocated in.
1510 static svn_error_t *
1511 set_up_diff_cmd_and_options(struct diff_cmd_baton *diff_cmd_baton,
1512 const apr_array_header_t *options,
1513 apr_hash_t *config, apr_pool_t *pool)
1515 const char *diff_cmd = NULL;
1517 /* See if there is a command. */
1518 if (config)
1520 svn_config_t *cfg = apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG,
1521 APR_HASH_KEY_STRING);
1522 svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
1523 SVN_CONFIG_OPTION_DIFF_CMD, NULL);
1526 diff_cmd_baton->diff_cmd = diff_cmd;
1528 /* If there was a command, arrange options to pass to it. */
1529 if (diff_cmd_baton->diff_cmd)
1531 const char **argv;
1532 int argc = options->nelts;
1533 if (argc)
1535 int i;
1536 argv = apr_palloc(pool, argc * sizeof(char *));
1537 for (i = 0; i < argc; i++)
1538 argv[i] = APR_ARRAY_IDX(options, i, const char *);
1540 diff_cmd_baton->options.for_external.argv = argv;
1541 diff_cmd_baton->options.for_external.argc = argc;
1543 else /* No command, so arrange options for internal invocation instead. */
1545 diff_cmd_baton->options.for_internal
1546 = svn_diff_file_options_create(pool);
1547 SVN_ERR(svn_diff_file_options_parse
1548 (diff_cmd_baton->options.for_internal, options, pool));
1551 return SVN_NO_ERROR;
1554 /*----------------------------------------------------------------------- */
1556 /*** Public Interfaces. ***/
1558 /* Display context diffs between two PATH/REVISION pairs. Each of
1559 these inputs will be one of the following:
1561 - a repository URL at a given revision.
1562 - a working copy path, ignoring local mods.
1563 - a working copy path, including local mods.
1565 We can establish a matrix that shows the nine possible types of
1566 diffs we expect to support.
1569 ` . DST || URL:rev | WC:base | WC:working |
1570 ` . || | | |
1571 SRC ` . || | | |
1572 ============++============+============+============+
1573 URL:rev || (*) | (*) | (*) |
1574 || | | |
1575 || | | |
1576 || | | |
1577 ------------++------------+------------+------------+
1578 WC:base || (*) | |
1579 || | New svn_wc_diff which |
1580 || | is smart enough to |
1581 || | handle two WC paths |
1582 ------------++------------+ and their related +
1583 WC:working || (*) | text-bases and working |
1584 || | files. This operation |
1585 || | is entirely local. |
1586 || | |
1587 ------------++------------+------------+------------+
1588 * These cases require server communication.
1590 svn_error_t *
1591 svn_client_diff4(const apr_array_header_t *options,
1592 const char *path1,
1593 const svn_opt_revision_t *revision1,
1594 const char *path2,
1595 const svn_opt_revision_t *revision2,
1596 const char *relative_to_dir,
1597 svn_depth_t depth,
1598 svn_boolean_t ignore_ancestry,
1599 svn_boolean_t no_diff_deleted,
1600 svn_boolean_t ignore_content_type,
1601 const char *header_encoding,
1602 apr_file_t *outfile,
1603 apr_file_t *errfile,
1604 const apr_array_header_t *changelists,
1605 svn_client_ctx_t *ctx,
1606 apr_pool_t *pool)
1608 struct diff_parameters diff_params;
1610 struct diff_cmd_baton diff_cmd_baton;
1611 svn_wc_diff_callbacks3_t diff_callbacks;
1613 /* We will never do a pegged diff from here. */
1614 svn_opt_revision_t peg_revision;
1615 peg_revision.kind = svn_opt_revision_unspecified;
1617 /* fill diff_param */
1618 diff_params.path1 = path1;
1619 diff_params.revision1 = revision1;
1620 diff_params.path2 = path2;
1621 diff_params.revision2 = revision2;
1622 diff_params.peg_revision = &peg_revision;
1623 diff_params.depth = depth;
1624 diff_params.ignore_ancestry = ignore_ancestry;
1625 diff_params.no_diff_deleted = no_diff_deleted;
1626 diff_params.changelists = changelists;
1628 /* setup callback and baton */
1629 diff_callbacks.file_changed = diff_file_changed;
1630 diff_callbacks.file_added = diff_file_added;
1631 diff_callbacks.file_deleted = no_diff_deleted ? diff_file_deleted_no_diff :
1632 diff_file_deleted_with_diff;
1633 diff_callbacks.dir_added = diff_dir_added;
1634 diff_callbacks.dir_deleted = diff_dir_deleted;
1635 diff_callbacks.dir_props_changed = diff_props_changed;
1636 diff_callbacks.dir_opened = diff_dir_opened;
1637 diff_callbacks.dir_closed = diff_dir_closed;
1639 diff_cmd_baton.orig_path_1 = path1;
1640 diff_cmd_baton.orig_path_2 = path2;
1642 SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton, options,
1643 ctx->config, pool));
1644 diff_cmd_baton.pool = pool;
1645 diff_cmd_baton.outfile = outfile;
1646 diff_cmd_baton.errfile = errfile;
1647 diff_cmd_baton.header_encoding = header_encoding;
1648 diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
1649 diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
1651 diff_cmd_baton.force_empty = FALSE;
1652 diff_cmd_baton.force_binary = ignore_content_type;
1653 diff_cmd_baton.relative_to_dir = relative_to_dir;
1655 return do_diff(&diff_params, &diff_callbacks, &diff_cmd_baton, ctx, pool);
1658 svn_error_t *
1659 svn_client_diff3(const apr_array_header_t *options,
1660 const char *path1,
1661 const svn_opt_revision_t *revision1,
1662 const char *path2,
1663 const svn_opt_revision_t *revision2,
1664 svn_boolean_t recurse,
1665 svn_boolean_t ignore_ancestry,
1666 svn_boolean_t no_diff_deleted,
1667 svn_boolean_t ignore_content_type,
1668 const char *header_encoding,
1669 apr_file_t *outfile,
1670 apr_file_t *errfile,
1671 svn_client_ctx_t *ctx,
1672 apr_pool_t *pool)
1674 return svn_client_diff4(options, path1, revision1, path2,
1675 revision2, NULL,
1676 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1677 ignore_ancestry, no_diff_deleted,
1678 ignore_content_type, header_encoding,
1679 outfile, errfile, NULL, ctx, pool);
1682 svn_error_t *
1683 svn_client_diff2(const apr_array_header_t *options,
1684 const char *path1,
1685 const svn_opt_revision_t *revision1,
1686 const char *path2,
1687 const svn_opt_revision_t *revision2,
1688 svn_boolean_t recurse,
1689 svn_boolean_t ignore_ancestry,
1690 svn_boolean_t no_diff_deleted,
1691 svn_boolean_t ignore_content_type,
1692 apr_file_t *outfile,
1693 apr_file_t *errfile,
1694 svn_client_ctx_t *ctx,
1695 apr_pool_t *pool)
1697 return svn_client_diff3(options, path1, revision1, path2, revision2,
1698 recurse, ignore_ancestry, no_diff_deleted,
1699 ignore_content_type, SVN_APR_LOCALE_CHARSET,
1700 outfile, errfile, ctx, pool);
1703 svn_error_t *
1704 svn_client_diff(const apr_array_header_t *options,
1705 const char *path1,
1706 const svn_opt_revision_t *revision1,
1707 const char *path2,
1708 const svn_opt_revision_t *revision2,
1709 svn_boolean_t recurse,
1710 svn_boolean_t ignore_ancestry,
1711 svn_boolean_t no_diff_deleted,
1712 apr_file_t *outfile,
1713 apr_file_t *errfile,
1714 svn_client_ctx_t *ctx,
1715 apr_pool_t *pool)
1717 return svn_client_diff2(options, path1, revision1, path2, revision2,
1718 recurse, ignore_ancestry, no_diff_deleted, FALSE,
1719 outfile, errfile, ctx, pool);
1722 svn_error_t *
1723 svn_client_diff_peg4(const apr_array_header_t *options,
1724 const char *path,
1725 const svn_opt_revision_t *peg_revision,
1726 const svn_opt_revision_t *start_revision,
1727 const svn_opt_revision_t *end_revision,
1728 const char *relative_to_dir,
1729 svn_depth_t depth,
1730 svn_boolean_t ignore_ancestry,
1731 svn_boolean_t no_diff_deleted,
1732 svn_boolean_t ignore_content_type,
1733 const char *header_encoding,
1734 apr_file_t *outfile,
1735 apr_file_t *errfile,
1736 const apr_array_header_t *changelists,
1737 svn_client_ctx_t *ctx,
1738 apr_pool_t *pool)
1740 struct diff_parameters diff_params;
1742 struct diff_cmd_baton diff_cmd_baton;
1743 svn_wc_diff_callbacks3_t diff_callbacks;
1745 if (svn_path_is_url(path) &&
1746 (start_revision->kind == svn_opt_revision_base
1747 || end_revision->kind == svn_opt_revision_base) )
1748 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1749 _("Revision type requires a working copy "
1750 "path, not a URL"));
1752 /* fill diff_param */
1753 diff_params.path1 = path;
1754 diff_params.revision1 = start_revision;
1755 diff_params.path2 = path;
1756 diff_params.revision2 = end_revision;
1757 diff_params.peg_revision = peg_revision;
1758 diff_params.depth = depth;
1759 diff_params.ignore_ancestry = ignore_ancestry;
1760 diff_params.no_diff_deleted = no_diff_deleted;
1761 diff_params.changelists = changelists;
1763 /* setup callback and baton */
1764 diff_callbacks.file_changed = diff_file_changed;
1765 diff_callbacks.file_added = diff_file_added;
1766 diff_callbacks.file_deleted = no_diff_deleted ? diff_file_deleted_no_diff :
1767 diff_file_deleted_with_diff;
1768 diff_callbacks.dir_added = diff_dir_added;
1769 diff_callbacks.dir_deleted = diff_dir_deleted;
1770 diff_callbacks.dir_props_changed = diff_props_changed;
1771 diff_callbacks.dir_opened = diff_dir_opened;
1772 diff_callbacks.dir_closed = diff_dir_closed;
1774 diff_cmd_baton.orig_path_1 = path;
1775 diff_cmd_baton.orig_path_2 = path;
1777 SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton, options,
1778 ctx->config, pool));
1779 diff_cmd_baton.pool = pool;
1780 diff_cmd_baton.outfile = outfile;
1781 diff_cmd_baton.errfile = errfile;
1782 diff_cmd_baton.header_encoding = header_encoding;
1783 diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
1784 diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
1786 diff_cmd_baton.force_empty = FALSE;
1787 diff_cmd_baton.force_binary = ignore_content_type;
1788 diff_cmd_baton.relative_to_dir = relative_to_dir;
1790 return do_diff(&diff_params, &diff_callbacks, &diff_cmd_baton, ctx, pool);
1793 svn_error_t *
1794 svn_client_diff_peg3(const apr_array_header_t *options,
1795 const char *path,
1796 const svn_opt_revision_t *peg_revision,
1797 const svn_opt_revision_t *start_revision,
1798 const svn_opt_revision_t *end_revision,
1799 svn_boolean_t recurse,
1800 svn_boolean_t ignore_ancestry,
1801 svn_boolean_t no_diff_deleted,
1802 svn_boolean_t ignore_content_type,
1803 const char *header_encoding,
1804 apr_file_t *outfile,
1805 apr_file_t *errfile,
1806 svn_client_ctx_t *ctx,
1807 apr_pool_t *pool)
1809 return svn_client_diff_peg4(options,
1810 path,
1811 peg_revision,
1812 start_revision,
1813 end_revision,
1814 NULL,
1815 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1816 ignore_ancestry,
1817 no_diff_deleted,
1818 ignore_content_type,
1819 header_encoding,
1820 outfile,
1821 errfile,
1822 NULL,
1823 ctx,
1824 pool);
1827 svn_error_t *
1828 svn_client_diff_peg2(const apr_array_header_t *options,
1829 const char *path,
1830 const svn_opt_revision_t *peg_revision,
1831 const svn_opt_revision_t *start_revision,
1832 const svn_opt_revision_t *end_revision,
1833 svn_boolean_t recurse,
1834 svn_boolean_t ignore_ancestry,
1835 svn_boolean_t no_diff_deleted,
1836 svn_boolean_t ignore_content_type,
1837 apr_file_t *outfile,
1838 apr_file_t *errfile,
1839 svn_client_ctx_t *ctx,
1840 apr_pool_t *pool)
1842 return svn_client_diff_peg3(options, path, peg_revision, start_revision,
1843 end_revision,
1844 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1845 ignore_ancestry, no_diff_deleted,
1846 ignore_content_type, SVN_APR_LOCALE_CHARSET,
1847 outfile, errfile, ctx, pool);
1850 svn_error_t *
1851 svn_client_diff_peg(const apr_array_header_t *options,
1852 const char *path,
1853 const svn_opt_revision_t *peg_revision,
1854 const svn_opt_revision_t *start_revision,
1855 const svn_opt_revision_t *end_revision,
1856 svn_boolean_t recurse,
1857 svn_boolean_t ignore_ancestry,
1858 svn_boolean_t no_diff_deleted,
1859 apr_file_t *outfile,
1860 apr_file_t *errfile,
1861 svn_client_ctx_t *ctx,
1862 apr_pool_t *pool)
1864 return svn_client_diff_peg2(options, path, peg_revision,
1865 start_revision, end_revision, recurse,
1866 ignore_ancestry, no_diff_deleted, FALSE,
1867 outfile, errfile, ctx, pool);
1870 svn_error_t *
1871 svn_client_diff_summarize2(const char *path1,
1872 const svn_opt_revision_t *revision1,
1873 const char *path2,
1874 const svn_opt_revision_t *revision2,
1875 svn_depth_t depth,
1876 svn_boolean_t ignore_ancestry,
1877 const apr_array_header_t *changelists,
1878 svn_client_diff_summarize_func_t summarize_func,
1879 void *summarize_baton,
1880 svn_client_ctx_t *ctx,
1881 apr_pool_t *pool)
1883 struct diff_parameters diff_params;
1885 /* We will never do a pegged diff from here. */
1886 svn_opt_revision_t peg_revision;
1887 peg_revision.kind = svn_opt_revision_unspecified;
1889 /* fill diff_param */
1890 diff_params.path1 = path1;
1891 diff_params.revision1 = revision1;
1892 diff_params.path2 = path2;
1893 diff_params.revision2 = revision2;
1894 diff_params.peg_revision = &peg_revision;
1895 diff_params.depth = depth;
1896 diff_params.ignore_ancestry = ignore_ancestry;
1897 diff_params.no_diff_deleted = FALSE;
1898 diff_params.changelists = changelists;
1900 return do_diff_summarize(&diff_params, summarize_func, summarize_baton,
1901 ctx, pool);
1904 svn_error_t *
1905 svn_client_diff_summarize(const char *path1,
1906 const svn_opt_revision_t *revision1,
1907 const char *path2,
1908 const svn_opt_revision_t *revision2,
1909 svn_boolean_t recurse,
1910 svn_boolean_t ignore_ancestry,
1911 svn_client_diff_summarize_func_t summarize_func,
1912 void *summarize_baton,
1913 svn_client_ctx_t *ctx,
1914 apr_pool_t *pool)
1916 return svn_client_diff_summarize2(path1, revision1, path2,
1917 revision2,
1918 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1919 ignore_ancestry, NULL, summarize_func,
1920 summarize_baton, ctx, pool);
1923 svn_error_t *
1924 svn_client_diff_summarize_peg2(const char *path,
1925 const svn_opt_revision_t *peg_revision,
1926 const svn_opt_revision_t *start_revision,
1927 const svn_opt_revision_t *end_revision,
1928 svn_depth_t depth,
1929 svn_boolean_t ignore_ancestry,
1930 const apr_array_header_t *changelists,
1931 svn_client_diff_summarize_func_t summarize_func,
1932 void *summarize_baton,
1933 svn_client_ctx_t *ctx,
1934 apr_pool_t *pool)
1936 struct diff_parameters diff_params;
1938 /* fill diff_param */
1939 diff_params.path1 = path;
1940 diff_params.revision1 = start_revision;
1941 diff_params.path2 = path;
1942 diff_params.revision2 = end_revision;
1943 diff_params.peg_revision = peg_revision;
1944 diff_params.depth = depth;
1945 diff_params.ignore_ancestry = ignore_ancestry;
1946 diff_params.no_diff_deleted = FALSE;
1947 diff_params.changelists = changelists;
1949 return do_diff_summarize(&diff_params, summarize_func, summarize_baton,
1950 ctx, pool);
1954 svn_error_t *
1955 svn_client_diff_summarize_peg(const char *path,
1956 const svn_opt_revision_t *peg_revision,
1957 const svn_opt_revision_t *start_revision,
1958 const svn_opt_revision_t *end_revision,
1959 svn_boolean_t recurse,
1960 svn_boolean_t ignore_ancestry,
1961 svn_client_diff_summarize_func_t summarize_func,
1962 void *summarize_baton,
1963 svn_client_ctx_t *ctx,
1964 apr_pool_t *pool)
1966 return svn_client_diff_summarize_peg2(path, peg_revision,
1967 start_revision, end_revision,
1968 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1969 ignore_ancestry, NULL,
1970 summarize_func, summarize_baton,
1971 ctx, pool);
1974 svn_client_diff_summarize_t *
1975 svn_client_diff_summarize_dup(const svn_client_diff_summarize_t *diff,
1976 apr_pool_t *pool)
1978 svn_client_diff_summarize_t *dup_diff = apr_palloc(pool, sizeof(*dup_diff));
1980 *dup_diff = *diff;
1982 if (diff->path)
1983 dup_diff->path = apr_pstrdup(pool, diff->path);
1985 return dup_diff;