* subversion/mod_dav_svn/reports/replay.c
[svn.git] / subversion / libsvn_client / diff.c
blob925232efcad951397a15b38980c1de1635f85bfd
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;
107 svn_stringbuf_t *merge_revstr;
109 if (old_mergeinfo_val)
110 SVN_ERR(svn_mergeinfo_parse(&old_mergeinfo_hash, old_mergeinfo_val, pool));
111 else
112 old_mergeinfo_hash = NULL;
114 if (new_mergeinfo_val)
115 SVN_ERR(svn_mergeinfo_parse(&new_mergeinfo_hash, new_mergeinfo_val, pool));
116 else
117 new_mergeinfo_hash = NULL;
119 SVN_ERR(svn_mergeinfo_diff(&deleted, &added, old_mergeinfo_hash,
120 new_mergeinfo_hash,
121 TRUE, pool));
123 for (hi = apr_hash_first(pool, deleted);
124 hi; hi = apr_hash_next(hi))
126 const void *key;
127 void *val;
129 apr_hash_this(hi, &key, NULL, &val);
130 from_path = key;
131 merge_revarray = val;
133 SVN_ERR(svn_rangelist_to_stringbuf(&merge_revstr, merge_revarray, pool));
135 SVN_ERR(file_printf_from_utf8(file, encoding,
136 _(" Reverted %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;
147 apr_hash_this(hi, &key, NULL, &val);
148 from_path = key;
149 merge_revarray = val;
151 SVN_ERR(svn_rangelist_to_stringbuf(&merge_revstr, merge_revarray, pool));
153 SVN_ERR(file_printf_from_utf8(file, encoding,
154 _(" Merged %s:r%s%s"),
155 from_path, merge_revstr->data,
156 APR_EOL_STR));
159 return SVN_NO_ERROR;
162 #define MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir) \
163 svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL, \
164 _("Path '%s' must be an immediate child of " \
165 "the directory '%s'"), path, relative_to_dir)
167 /* A helper func that writes out verbal descriptions of property diffs
168 to FILE. Of course, the apr_file_t will probably be the 'outfile'
169 passed to svn_client_diff4, which is probably stdout. */
170 static svn_error_t *
171 display_prop_diffs(const apr_array_header_t *propchanges,
172 apr_hash_t *original_props,
173 const char *path,
174 const char *encoding,
175 apr_file_t *file,
176 const char *relative_to_dir,
177 apr_pool_t *pool)
179 int i;
181 if (relative_to_dir)
183 /* Possibly adjust the path shown in the output (see issue #2723). */
184 const char *child_path = svn_path_is_child(relative_to_dir, path, pool);
186 if (child_path)
187 path = child_path;
188 else if (!svn_path_compare_paths(relative_to_dir, path))
189 path = ".";
190 else
191 return MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir);
194 SVN_ERR(file_printf_from_utf8(file, encoding,
195 _("%sProperty changes on: %s%s"),
196 APR_EOL_STR,
197 svn_path_local_style(path, pool),
198 APR_EOL_STR));
200 SVN_ERR(file_printf_from_utf8(file, encoding, "%s" APR_EOL_STR,
201 under_string));
203 for (i = 0; i < propchanges->nelts; i++)
205 const char *header_fmt;
206 const svn_string_t *original_value;
207 const svn_prop_t *propchange =
208 &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
210 if (original_props)
211 original_value = apr_hash_get(original_props,
212 propchange->name, APR_HASH_KEY_STRING);
213 else
214 original_value = NULL;
216 /* If the property doesn't exist on either side, or if it exists
217 with the same value, skip it. */
218 if ((! (original_value || propchange->value))
219 || (original_value && propchange->value
220 && svn_string_compare(original_value, propchange->value)))
221 continue;
223 if (! original_value)
224 header_fmt = _("Added: %s%s");
225 else if (! propchange->value)
226 header_fmt = _("Deleted: %s%s");
227 else
228 header_fmt = _("Modified: %s%s");
229 SVN_ERR(file_printf_from_utf8(file, encoding, header_fmt,
230 propchange->name, APR_EOL_STR));
232 if (strcmp(propchange->name, SVN_PROP_MERGEINFO) == 0)
234 const char *orig = original_value ? original_value->data : NULL;
235 const char *val = propchange->value ? propchange->value->data : NULL;
237 SVN_ERR(display_mergeinfo_diff(orig, val, encoding, file, pool));
239 continue;
242 /* For now, we have a rather simple heuristic: if this is an
243 "svn:" property, then assume the value is UTF-8 and must
244 therefore be converted before printing. Otherwise, just
245 print whatever's there and hope for the best. */
247 svn_boolean_t val_is_utf8 = svn_prop_is_svn_prop(propchange->name);
249 if (original_value != NULL)
251 if (val_is_utf8)
253 SVN_ERR(file_printf_from_utf8
254 (file, encoding,
255 " - %s" APR_EOL_STR, original_value->data));
257 else
259 /* ### todo: check for error? */
260 apr_file_printf
261 (file, " - %s" APR_EOL_STR, original_value->data);
265 if (propchange->value != NULL)
267 if (val_is_utf8)
269 SVN_ERR(file_printf_from_utf8
270 (file, encoding, " + %s" APR_EOL_STR,
271 propchange->value->data));
273 else
275 /* ### todo: check for error? */
276 apr_file_printf(file, " + %s" APR_EOL_STR,
277 propchange->value->data);
283 /* ### todo [issue #1533]: Use file_printf_from_utf8() to convert this
284 to native encoding, at least conditionally? Or is it better to
285 have under_string always output the same eol, so programs can
286 find it consistently? Also, what about checking for error? */
287 apr_file_printf(file, APR_EOL_STR);
289 return SVN_NO_ERROR;
293 /*-----------------------------------------------------------------*/
295 /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
298 struct diff_cmd_baton {
299 const apr_array_header_t *options;
300 apr_pool_t *pool;
301 apr_file_t *outfile;
302 apr_file_t *errfile;
304 const char *header_encoding;
306 /* The original targets passed to the diff command. We may need
307 these to construct distinctive diff labels when comparing the
308 same relative path in the same revision, under different anchors
309 (for example, when comparing a trunk against a branch). */
310 const char *orig_path_1;
311 const char *orig_path_2;
313 /* These are the numeric representations of the revisions passed to
314 svn_client_diff4, either may be SVN_INVALID_REVNUM. We need these
315 because some of the svn_wc_diff_callbacks2_t don't get revision
316 arguments.
318 ### Perhaps we should change the callback signatures and eliminate
319 ### these?
321 svn_revnum_t revnum1;
322 svn_revnum_t revnum2;
324 /* Client config hash (may be NULL). */
325 apr_hash_t *config;
327 /* Set this if you want diff output even for binary files. */
328 svn_boolean_t force_binary;
330 /* Set this flag if you want diff_file_changed to output diffs
331 unconditionally, even if the diffs are empty. */
332 svn_boolean_t force_empty;
334 /* The directory that diff target paths should be considered as
335 relative to for output generation (see issue #2723). */
336 const char *relative_to_dir;
340 /* Generate a label for the diff output for file PATH at revision REVNUM.
341 If REVNUM is invalid then it is assumed to be the current working
342 copy. Assumes the paths are already in the desired style (local
343 vs internal). Allocate the label in POOL. */
344 static const char *
345 diff_label(const char *path,
346 svn_revnum_t revnum,
347 apr_pool_t *pool)
349 const char *label;
350 if (revnum != SVN_INVALID_REVNUM)
351 label = apr_psprintf(pool, _("%s\t(revision %ld)"), path, revnum);
352 else
353 label = apr_psprintf(pool, _("%s\t(working copy)"), path);
355 return label;
358 /* A svn_wc_diff_callbacks2_t function. Used for both file and directory
359 property diffs. */
360 static svn_error_t *
361 diff_props_changed(svn_wc_adm_access_t *adm_access,
362 svn_wc_notify_state_t *state,
363 const char *path,
364 const apr_array_header_t *propchanges,
365 apr_hash_t *original_props,
366 void *diff_baton)
368 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
369 apr_array_header_t *props;
370 apr_pool_t *subpool = svn_pool_create(diff_cmd_baton->pool);
372 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, subpool));
374 if (props->nelts > 0)
375 SVN_ERR(display_prop_diffs(props, original_props, path,
376 diff_cmd_baton->header_encoding,
377 diff_cmd_baton->outfile,
378 diff_cmd_baton->relative_to_dir,
379 subpool));
381 if (state)
382 *state = svn_wc_notify_state_unknown;
384 svn_pool_destroy(subpool);
385 return SVN_NO_ERROR;
388 /* Show differences between TMPFILE1 and TMPFILE2. PATH, REV1, and REV2 are
389 used in the headers to indicate the file and revisions. If either
390 MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
391 but instead print a warning message. */
392 static svn_error_t *
393 diff_content_changed(const char *path,
394 const char *tmpfile1,
395 const char *tmpfile2,
396 svn_revnum_t rev1,
397 svn_revnum_t rev2,
398 const char *mimetype1,
399 const char *mimetype2,
400 void *diff_baton)
402 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
403 const char *diff_cmd = NULL;
404 const char **args = NULL;
405 int nargs, exitcode;
406 apr_pool_t *subpool = svn_pool_create(diff_cmd_baton->pool);
407 svn_stream_t *os;
408 const char *rel_to_dir = diff_cmd_baton->relative_to_dir;
409 apr_file_t *errfile = diff_cmd_baton->errfile;
410 const char *label1, *label2;
411 svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
412 const char *path1, *path2;
413 int i;
415 /* Get a stream from our output file. */
416 os = svn_stream_from_aprfile(diff_cmd_baton->outfile, subpool);
418 /* Assemble any option args. */
419 nargs = diff_cmd_baton->options->nelts;
420 if (nargs)
422 args = apr_palloc(subpool, nargs * sizeof(char *));
423 for (i = 0; i < diff_cmd_baton->options->nelts; i++)
425 args[i] = APR_ARRAY_IDX(diff_cmd_baton->options, i, const char *);
427 assert(i == nargs);
430 /* Generate the diff headers. */
432 /* ### Holy cow. Due to anchor/target weirdness, we can't
433 simply join diff_cmd_baton->orig_path_1 with path, ditto for
434 orig_path_2. That will work when they're directory URLs, but
435 not for file URLs. Nor can we just use anchor1 and anchor2
436 from do_diff(), at least not without some more logic here.
437 What a nightmare.
439 For now, to distinguish the two paths, we'll just put the
440 unique portions of the original targets in parentheses after
441 the received path, with ellipses for handwaving. This makes
442 the labels a bit clumsy, but at least distinctive. Better
443 solutions are possible, they'll just take more thought. */
445 path1 = diff_cmd_baton->orig_path_1;
446 path2 = diff_cmd_baton->orig_path_2;
448 for (i = 0; path1[i] && path2[i] && (path1[i] == path2[i]); i++)
451 /* Make sure the prefix is made of whole components. (Issue #1771) */
452 if (path1[i] || path2[i])
454 for ( ; (i > 0) && (path1[i] != '/'); i--)
458 path1 = path1 + i;
459 path2 = path2 + i;
461 /* ### Should diff labels print paths in local style? Is there
462 already a standard for this? In any case, this code depends on
463 a particular style, so not calling svn_path_local_style() on the
464 paths below.*/
465 if (path1[0] == '\0')
466 path1 = apr_psprintf(subpool, "%s", path);
467 else if (path1[0] == '/')
468 path1 = apr_psprintf(subpool, "%s\t(...%s)", path, path1);
469 else
470 path1 = apr_psprintf(subpool, "%s\t(.../%s)", path, path1);
472 if (path2[0] == '\0')
473 path2 = apr_psprintf(subpool, "%s", path);
474 else if (path2[0] == '/')
475 path2 = apr_psprintf(subpool, "%s\t(...%s)", path, path2);
476 else
477 path2 = apr_psprintf(subpool, "%s\t(.../%s)", path, path2);
479 if (diff_cmd_baton->relative_to_dir)
481 /* Possibly adjust the paths shown in the output (see issue #2723). */
482 const char *child_path = svn_path_is_child(rel_to_dir, path, subpool);
484 if (child_path)
485 path = child_path;
486 else if (!svn_path_compare_paths(rel_to_dir, path))
487 path = ".";
488 else
489 return MAKE_ERR_BAD_RELATIVE_PATH(path, rel_to_dir);
491 child_path = svn_path_is_child(rel_to_dir, path1, subpool);
493 if (child_path)
494 path1 = child_path;
495 else if (!svn_path_compare_paths(rel_to_dir, path1))
496 path1 = ".";
497 else
498 return MAKE_ERR_BAD_RELATIVE_PATH(path1, rel_to_dir);
500 child_path = svn_path_is_child(rel_to_dir, path2, subpool);
502 if (child_path)
503 path2 = child_path;
504 else if (!svn_path_compare_paths(rel_to_dir, path2))
505 path2 = ".";
506 else
507 return MAKE_ERR_BAD_RELATIVE_PATH(path2, rel_to_dir);
510 label1 = diff_label(path1, rev1, subpool);
511 label2 = diff_label(path2, rev2, subpool);
513 /* Possible easy-out: if either mime-type is binary and force was not
514 specified, don't attempt to generate a viewable diff at all.
515 Print a warning and exit. */
516 if (mimetype1)
517 mt1_binary = svn_mime_type_is_binary(mimetype1);
518 if (mimetype2)
519 mt2_binary = svn_mime_type_is_binary(mimetype2);
521 if (! diff_cmd_baton->force_binary && (mt1_binary || mt2_binary))
523 /* Print out the diff header. */
524 SVN_ERR(svn_stream_printf_from_utf8
525 (os, diff_cmd_baton->header_encoding, subpool,
526 "Index: %s" APR_EOL_STR "%s" APR_EOL_STR, path, equal_string));
528 SVN_ERR(svn_stream_printf_from_utf8
529 (os, diff_cmd_baton->header_encoding, subpool,
530 _("Cannot display: file marked as a binary type.%s"),
531 APR_EOL_STR));
533 if (mt1_binary && !mt2_binary)
534 SVN_ERR(svn_stream_printf_from_utf8
535 (os, diff_cmd_baton->header_encoding, subpool,
536 "svn:mime-type = %s" APR_EOL_STR, mimetype1));
537 else if (mt2_binary && !mt1_binary)
538 SVN_ERR(svn_stream_printf_from_utf8
539 (os, diff_cmd_baton->header_encoding, subpool,
540 "svn:mime-type = %s" APR_EOL_STR, mimetype2));
541 else if (mt1_binary && mt2_binary)
543 if (strcmp(mimetype1, mimetype2) == 0)
544 SVN_ERR(svn_stream_printf_from_utf8
545 (os, diff_cmd_baton->header_encoding, subpool,
546 "svn:mime-type = %s" APR_EOL_STR,
547 mimetype1));
548 else
549 SVN_ERR(svn_stream_printf_from_utf8
550 (os, diff_cmd_baton->header_encoding, subpool,
551 "svn:mime-type = (%s, %s)" APR_EOL_STR,
552 mimetype1, mimetype2));
555 /* Exit early. */
556 svn_pool_destroy(subpool);
557 return SVN_NO_ERROR;
561 /* Find out if we need to run an external diff */
562 if (diff_cmd_baton->config)
564 svn_config_t *cfg = apr_hash_get(diff_cmd_baton->config,
565 SVN_CONFIG_CATEGORY_CONFIG,
566 APR_HASH_KEY_STRING);
567 svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
568 SVN_CONFIG_OPTION_DIFF_CMD, NULL);
571 if (diff_cmd)
573 /* Print out the diff header. */
574 SVN_ERR(svn_stream_printf_from_utf8
575 (os, diff_cmd_baton->header_encoding, subpool,
576 "Index: %s" APR_EOL_STR "%s" APR_EOL_STR, path, equal_string));
577 /* Close the stream (flush) */
578 SVN_ERR(svn_stream_close(os));
580 SVN_ERR(svn_io_run_diff(".", args, nargs, label1, label2,
581 tmpfile1, tmpfile2,
582 &exitcode, diff_cmd_baton->outfile, errfile,
583 diff_cmd, subpool));
585 else /* use libsvn_diff to generate the diff */
587 svn_diff_t *diff;
588 svn_diff_file_options_t *opts = svn_diff_file_options_create(subpool);
590 if (diff_cmd_baton->options)
591 SVN_ERR(svn_diff_file_options_parse(opts, diff_cmd_baton->options,
592 subpool));
594 SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2, opts,
595 subpool));
597 if (svn_diff_contains_diffs(diff) || diff_cmd_baton->force_empty)
599 /* Print out the diff header. */
600 SVN_ERR(svn_stream_printf_from_utf8
601 (os, diff_cmd_baton->header_encoding, subpool,
602 "Index: %s" APR_EOL_STR "%s" APR_EOL_STR,
603 path, equal_string));
604 /* Output the actual diff */
605 SVN_ERR(svn_diff_file_output_unified3
606 (os, diff, tmpfile1, tmpfile2, label1, label2,
607 diff_cmd_baton->header_encoding, rel_to_dir, subpool));
611 /* ### todo: someday we'll need to worry about whether we're going
612 to need to write a diff plug-in mechanism that makes use of the
613 two paths, instead of just blindly running SVN_CLIENT_DIFF. */
615 /* Destroy the subpool. */
616 svn_pool_destroy(subpool);
618 return SVN_NO_ERROR;
621 /* A svn_wc_diff_callbacks2_t function. */
622 static svn_error_t *
623 diff_file_changed(svn_wc_adm_access_t *adm_access,
624 svn_wc_notify_state_t *content_state,
625 svn_wc_notify_state_t *prop_state,
626 const char *path,
627 const char *tmpfile1,
628 const char *tmpfile2,
629 svn_revnum_t rev1,
630 svn_revnum_t rev2,
631 const char *mimetype1,
632 const char *mimetype2,
633 const apr_array_header_t *prop_changes,
634 apr_hash_t *original_props,
635 void *diff_baton)
637 if (tmpfile1)
638 SVN_ERR(diff_content_changed(path,
639 tmpfile1, tmpfile2, rev1, rev2,
640 mimetype1, mimetype2, diff_baton));
641 if (prop_changes->nelts > 0)
642 SVN_ERR(diff_props_changed(adm_access, prop_state, path, prop_changes,
643 original_props, diff_baton));
644 if (content_state)
645 *content_state = svn_wc_notify_state_unknown;
646 if (prop_state)
647 *prop_state = svn_wc_notify_state_unknown;
648 return SVN_NO_ERROR;
651 /* Because the repos-diff editor passes at least one empty file to
652 each of these next two functions, they can be dumb wrappers around
653 the main workhorse routine. */
655 /* A svn_wc_diff_callbacks2_t function. */
656 static svn_error_t *
657 diff_file_added(svn_wc_adm_access_t *adm_access,
658 svn_wc_notify_state_t *content_state,
659 svn_wc_notify_state_t *prop_state,
660 const char *path,
661 const char *tmpfile1,
662 const char *tmpfile2,
663 svn_revnum_t rev1,
664 svn_revnum_t rev2,
665 const char *mimetype1,
666 const char *mimetype2,
667 const apr_array_header_t *prop_changes,
668 apr_hash_t *original_props,
669 void *diff_baton)
671 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
673 /* We want diff_file_changed to unconditionally show diffs, even if
674 the diff is empty (as would be the case if an empty file were
675 added.) It's important, because 'patch' would still see an empty
676 diff and create an empty file. It's also important to let the
677 user see that *something* happened. */
678 diff_cmd_baton->force_empty = TRUE;
680 SVN_ERR(diff_file_changed(adm_access, content_state, prop_state, path,
681 tmpfile1, tmpfile2,
682 rev1, rev2,
683 mimetype1, mimetype2,
684 prop_changes, original_props, diff_baton));
686 diff_cmd_baton->force_empty = FALSE;
688 return SVN_NO_ERROR;
691 /* A svn_wc_diff_callbacks2_t function. */
692 static svn_error_t *
693 diff_file_deleted_with_diff(svn_wc_adm_access_t *adm_access,
694 svn_wc_notify_state_t *state,
695 const char *path,
696 const char *tmpfile1,
697 const char *tmpfile2,
698 const char *mimetype1,
699 const char *mimetype2,
700 apr_hash_t *original_props,
701 void *diff_baton)
703 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
705 /* We don't list all the deleted properties. */
706 return diff_file_changed(adm_access, state, NULL, path,
707 tmpfile1, tmpfile2,
708 diff_cmd_baton->revnum1, diff_cmd_baton->revnum2,
709 mimetype1, mimetype2,
710 apr_array_make(diff_cmd_baton->pool, 1,
711 sizeof(svn_prop_t)),
712 apr_hash_make(diff_cmd_baton->pool), diff_baton);
715 /* A svn_wc_diff_callbacks2_t function. */
716 static svn_error_t *
717 diff_file_deleted_no_diff(svn_wc_adm_access_t *adm_access,
718 svn_wc_notify_state_t *state,
719 const char *path,
720 const char *tmpfile1,
721 const char *tmpfile2,
722 const char *mimetype1,
723 const char *mimetype2,
724 apr_hash_t *original_props,
725 void *diff_baton)
727 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
729 if (state)
730 *state = svn_wc_notify_state_unknown;
732 SVN_ERR(file_printf_from_utf8
733 (diff_cmd_baton->outfile,
734 diff_cmd_baton->header_encoding,
735 "Index: %s (deleted)" APR_EOL_STR "%s" APR_EOL_STR,
736 path, equal_string));
738 return SVN_NO_ERROR;
741 /* A svn_wc_diff_callbacks2_t function.
742 For now, let's have 'svn diff' send feedback to the top-level
743 application, so that something reasonable about directories and
744 propsets gets printed to stdout. */
745 static svn_error_t *
746 diff_dir_added(svn_wc_adm_access_t *adm_access,
747 svn_wc_notify_state_t *state,
748 const char *path,
749 svn_revnum_t rev,
750 void *diff_baton)
752 if (state)
753 *state = svn_wc_notify_state_unknown;
755 /* ### todo: send feedback to app */
756 return SVN_NO_ERROR;
759 /* A svn_wc_diff_callbacks2_t function. */
760 static svn_error_t *
761 diff_dir_deleted(svn_wc_adm_access_t *adm_access,
762 svn_wc_notify_state_t *state,
763 const char *path,
764 void *diff_baton)
766 if (state)
767 *state = svn_wc_notify_state_unknown;
769 return SVN_NO_ERROR;
773 /*-----------------------------------------------------------------*/
775 /** The logic behind 'svn diff' and 'svn merge'. */
778 /* Hi! This is a comment left behind by Karl, and Ben is too afraid
779 to erase it at this time, because he's not fully confident that all
780 this knowledge has been grokked yet.
782 There are five cases:
783 1. path is not an URL and start_revision != end_revision
784 2. path is not an URL and start_revision == end_revision
785 3. path is an URL and start_revision != end_revision
786 4. path is an URL and start_revision == end_revision
787 5. path is not an URL and no revisions given
789 With only one distinct revision the working copy provides the
790 other. When path is an URL there is no working copy. Thus
792 1: compare repository versions for URL coresponding to working copy
793 2: compare working copy against repository version
794 3: compare repository versions for URL
795 4: nothing to do.
796 5: compare working copy against text-base
798 Case 4 is not as stupid as it looks, for example it may occur if
799 the user specifies two dates that resolve to the same revision. */
804 /* Helper function: given a working-copy PATH, return its associated
805 url in *URL, allocated in POOL. If PATH is *already* a URL, that's
806 fine, just set *URL = PATH. */
807 static svn_error_t *
808 convert_to_url(const char **url,
809 const char *path,
810 apr_pool_t *pool)
812 svn_wc_adm_access_t *adm_access; /* ### FIXME local */
813 const svn_wc_entry_t *entry;
815 if (svn_path_is_url(path))
817 *url = path;
818 return SVN_NO_ERROR;
821 /* ### This may not be a good idea, see issue 880 */
822 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path, FALSE,
823 0, NULL, NULL, pool));
824 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
825 SVN_ERR(svn_wc_adm_close(adm_access));
827 if (entry->url)
828 *url = apr_pstrdup(pool, entry->url);
829 else
830 *url = apr_pstrdup(pool, entry->copyfrom_url);
831 return SVN_NO_ERROR;
834 /** Helper structure: for passing around the diff parameters */
835 struct diff_parameters
837 /* Additional parameters for diff tool */
838 const apr_array_header_t *options;
840 /* First input path */
841 const char *path1;
843 /* Revision of first input path */
844 const svn_opt_revision_t *revision1;
846 /* Second input path */
847 const char *path2;
849 /* Revision of second input path */
850 const svn_opt_revision_t *revision2;
852 /* Peg revision */
853 const svn_opt_revision_t *peg_revision;
855 /* Desired depth */
856 svn_depth_t depth;
858 /* Ignore ancestry */
859 svn_boolean_t ignore_ancestry;
861 /* Ignore deleted */
862 svn_boolean_t no_diff_deleted;
865 /** Helper structure: filled by check_paths() */
866 struct diff_paths
868 /* path1 can only be found in the repository? */
869 svn_boolean_t is_repos1;
871 /* path2 can only be found in the repository? */
872 svn_boolean_t is_repos2;
876 /** Check if paths are urls and if the revisions are local, and, for
877 pegged revisions, ensure that at least one revision is non-local.
878 Fills the PATHS structure. */
879 static svn_error_t *
880 check_paths(const struct diff_parameters *params,
881 struct diff_paths *paths)
883 svn_boolean_t is_local_rev1, is_local_rev2;
885 /* Verify our revision arguments in light of the paths. */
886 if ((params->revision1->kind == svn_opt_revision_unspecified)
887 || (params->revision2->kind == svn_opt_revision_unspecified))
888 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
889 _("Not all required revisions are specified"));
891 /* Revisions can be said to be local or remote. BASE and WORKING,
892 for example, are local. */
893 is_local_rev1 =
894 ((params->revision1->kind == svn_opt_revision_base)
895 || (params->revision1->kind == svn_opt_revision_working));
896 is_local_rev2 =
897 ((params->revision2->kind == svn_opt_revision_base)
898 || (params->revision2->kind == svn_opt_revision_working));
900 if (params->peg_revision->kind != svn_opt_revision_unspecified)
902 if (is_local_rev1 && is_local_rev2)
903 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
904 _("At least one revision must be non-local "
905 "for a pegged diff"));
907 paths->is_repos1 = ! is_local_rev1;
908 paths->is_repos2 = ! is_local_rev2;
910 else
912 /* Working copy paths with non-local revisions get turned into
913 URLs. We don't do that here, though. We simply record that it
914 needs to be done, which is information that helps us choose our
915 diff helper function. */
916 paths->is_repos1 = ! is_local_rev1 || svn_path_is_url(params->path1);
917 paths->is_repos2 = ! is_local_rev2 || svn_path_is_url(params->path2);
920 return SVN_NO_ERROR;
923 /** Helper structure filled by diff_prepare_repos_repos */
924 struct diff_repos_repos_t
926 /* URL created from path1 */
927 const char *url1;
929 /* URL created from path2 */
930 const char *url2;
932 /* The BASE_PATH for the diff */
933 const char *base_path;
935 /* url1 and url2 are the same */
936 svn_boolean_t same_urls;
938 /* Revision of url1 */
939 svn_revnum_t rev1;
941 /* Revision of url2 */
942 svn_revnum_t rev2;
944 /* Anchor based on url1 */
945 const char *anchor1;
947 /* Anchor based on url2 */
948 const char *anchor2;
950 /* Target based on url1 */
951 const char *target1;
953 /* Target based on url2 */
954 const char *target2;
956 /* RA session pointing at anchor1. */
957 svn_ra_session_t *ra_session;
960 /** Helper function: prepare a repos repos diff. Fills DRR
961 * structure. */
962 static svn_error_t *
963 diff_prepare_repos_repos(const struct diff_parameters *params,
964 struct diff_repos_repos_t *drr,
965 svn_client_ctx_t *ctx,
966 apr_pool_t *pool)
968 svn_ra_session_t *ra_session;
969 svn_node_kind_t kind1, kind2;
971 /* Figure out URL1 and URL2. */
972 SVN_ERR(convert_to_url(&drr->url1, params->path1, pool));
973 SVN_ERR(convert_to_url(&drr->url2, params->path2, pool));
974 drr->same_urls = (strcmp(drr->url1, drr->url2) == 0);
976 /* We need exactly one BASE_PATH, so we'll let the BASE_PATH
977 calculated for PATH2 override the one for PATH1 (since the diff
978 will be "applied" to URL2 anyway). */
979 drr->base_path = NULL;
980 if (drr->url1 != params->path1)
981 drr->base_path = params->path1;
982 if (drr->url2 != params->path2)
983 drr->base_path = params->path2;
985 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, drr->url2,
986 NULL, NULL, NULL, FALSE,
987 TRUE, ctx, pool));
989 /* If we are performing a pegged diff, we need to find out what our
990 actual URLs will be. */
991 if (params->peg_revision->kind != svn_opt_revision_unspecified)
993 svn_opt_revision_t *start_ignore, *end_ignore;
995 SVN_ERR(svn_client__repos_locations(&drr->url1, &start_ignore,
996 &drr->url2, &end_ignore,
997 ra_session,
998 params->path2,
999 params->peg_revision,
1000 params->revision1,
1001 params->revision2,
1002 ctx, pool));
1003 /* Reparent the session, since drr->url2 might have changed as a result
1004 the above call. */
1005 SVN_ERR(svn_ra_reparent(ra_session, drr->url2, pool));
1008 /* Resolve revision and get path kind for the second target. */
1009 SVN_ERR(svn_client__get_revision_number
1010 (&drr->rev2, NULL, ra_session, params->revision2,
1011 (params->path2 == drr->url2) ? NULL : params->path2, pool));
1012 SVN_ERR(svn_ra_check_path(ra_session, "", drr->rev2, &kind2, pool));
1013 if (kind2 == svn_node_none)
1014 return svn_error_createf
1015 (SVN_ERR_FS_NOT_FOUND, NULL,
1016 _("'%s' was not found in the repository at revision %ld"),
1017 drr->url2, drr->rev2);
1019 /* Do the same for the first target. */
1020 SVN_ERR(svn_ra_reparent(ra_session, drr->url1, pool));
1021 SVN_ERR(svn_client__get_revision_number
1022 (&drr->rev1, NULL, ra_session, params->revision1,
1023 (params->path1 == drr->url1) ? NULL : params->path1, pool));
1024 SVN_ERR(svn_ra_check_path(ra_session, "", drr->rev1, &kind1, pool));
1025 if (kind1 == svn_node_none)
1026 return svn_error_createf
1027 (SVN_ERR_FS_NOT_FOUND, NULL,
1028 _("'%s' was not found in the repository at revision %ld"),
1029 drr->url1, drr->rev1);
1031 /* Choose useful anchors and targets for our two URLs. */
1032 drr->anchor1 = drr->url1;
1033 drr->anchor2 = drr->url2;
1034 drr->target1 = "";
1035 drr->target2 = "";
1036 if ((kind1 == svn_node_file) || (kind2 == svn_node_file))
1038 svn_path_split(drr->url1, &drr->anchor1, &drr->target1, pool);
1039 drr->target1 = svn_path_uri_decode(drr->target1, pool);
1040 svn_path_split(drr->url2, &drr->anchor2, &drr->target2, pool);
1041 drr->target2 = svn_path_uri_decode(drr->target2, pool);
1042 if (drr->base_path)
1043 drr->base_path = svn_path_dirname(drr->base_path, pool);
1044 SVN_ERR(svn_ra_reparent(ra_session, drr->anchor1, pool));
1047 drr->ra_session = ra_session;
1048 return SVN_NO_ERROR;
1051 /* A Theoretical Note From Ben, regarding do_diff().
1053 This function is really svn_client_diff4(). If you read the public
1054 API description for svn_client_diff4(), it sounds quite Grand. It
1055 sounds really generalized and abstract and beautiful: that it will
1056 diff any two paths, be they working-copy paths or URLs, at any two
1057 revisions.
1059 Now, the *reality* is that we have exactly three 'tools' for doing
1060 diffing, and thus this routine is built around the use of the three
1061 tools. Here they are, for clarity:
1063 - svn_wc_diff: assumes both paths are the same wcpath.
1064 compares wcpath@BASE vs. wcpath@WORKING
1066 - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING
1068 - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2
1070 So the truth of the matter is, if the caller's arguments can't be
1071 pigeonholed into one of these three use-cases, we currently bail
1072 with a friendly apology.
1074 Perhaps someday a brave soul will truly make svn_client_diff4
1075 perfectly general. For now, we live with the 90% case. Certainly,
1076 the commandline client only calls this function in legal ways.
1077 When there are other users of svn_client.h, maybe this will become
1078 a more pressing issue.
1081 /* Return a "you can't do that" error, optionally wrapping another
1082 error CHILD_ERR. */
1083 static svn_error_t *
1084 unsupported_diff_error(svn_error_t *child_err)
1086 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
1087 _("Sorry, svn_client_diff4 was called in a way "
1088 "that is not yet supported"));
1092 /* Perform a diff between two working-copy paths.
1094 PATH1 and PATH2 are both working copy paths. REVISION1 and
1095 REVISION2 are their respective revisions.
1097 All other options are the same as those passed to svn_client_diff4(). */
1098 static svn_error_t *
1099 diff_wc_wc(const apr_array_header_t *options,
1100 const char *path1,
1101 const svn_opt_revision_t *revision1,
1102 const char *path2,
1103 const svn_opt_revision_t *revision2,
1104 svn_depth_t depth,
1105 svn_boolean_t ignore_ancestry,
1106 const svn_wc_diff_callbacks2_t *callbacks,
1107 struct diff_cmd_baton *callback_baton,
1108 svn_client_ctx_t *ctx,
1109 apr_pool_t *pool)
1111 svn_wc_adm_access_t *adm_access, *target_access;
1112 const char *target;
1113 int levels_to_lock = SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth);
1115 /* Assert that we have valid input. */
1116 assert(! svn_path_is_url(path1));
1117 assert(! svn_path_is_url(path2));
1119 /* Currently we support only the case where path1 and path2 are the
1120 same path. */
1121 if ((strcmp(path1, path2) != 0)
1122 || (! ((revision1->kind == svn_opt_revision_base)
1123 && (revision2->kind == svn_opt_revision_working))))
1124 return unsupported_diff_error
1125 (svn_error_create
1126 (SVN_ERR_INCORRECT_PARAMS, NULL,
1127 _("Only diffs between a path's text-base "
1128 "and its working files are supported at this time")));
1130 SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &target_access, &target,
1131 path1, FALSE, levels_to_lock,
1132 ctx->cancel_func, ctx->cancel_baton,
1133 pool));
1135 /* Resolve named revisions to real numbers. */
1136 SVN_ERR(svn_client__get_revision_number
1137 (&callback_baton->revnum1, NULL, NULL, revision1, path1, pool));
1138 callback_baton->revnum2 = SVN_INVALID_REVNUM; /* WC */
1140 SVN_ERR(svn_wc_diff4(adm_access, target, callbacks, callback_baton,
1141 depth, ignore_ancestry, pool));
1142 SVN_ERR(svn_wc_adm_close(adm_access));
1143 return SVN_NO_ERROR;
1147 /* Perform a diff between two repository paths.
1149 DIFF_PARAM.PATH1 and DIFF_PARAM.PATH2 may be either URLs or the working
1150 copy paths. DIFF_PARAM.REVISION1 and DIFF_PARAM.REVISION2 are their
1151 respective revisions. If DIFF_PARAM.PEG_REVISION is specified,
1152 DIFF_PARAM.PATH2 is the path at the peg revision, and the actual two
1153 paths compared are determined by following copy history from PATH2.
1155 All other options are the same as those passed to svn_client_diff4(). */
1156 static svn_error_t *
1157 diff_repos_repos(const struct diff_parameters *diff_param,
1158 const svn_wc_diff_callbacks2_t *callbacks,
1159 struct diff_cmd_baton *callback_baton,
1160 svn_client_ctx_t *ctx,
1161 apr_pool_t *pool)
1163 svn_ra_session_t *extra_ra_session;
1165 const svn_ra_reporter3_t *reporter;
1166 void *report_baton;
1168 const svn_delta_editor_t *diff_editor;
1169 void *diff_edit_baton;
1171 struct diff_repos_repos_t drr;
1173 /* Prepare info for the repos repos diff. */
1174 SVN_ERR(diff_prepare_repos_repos(diff_param, &drr, ctx, pool));
1176 /* Get actual URLs. */
1177 callback_baton->orig_path_1 = drr.url1;
1178 callback_baton->orig_path_2 = drr.url2;
1180 /* Get numeric revisions. */
1181 callback_baton->revnum1 = drr.rev1;
1182 callback_baton->revnum2 = drr.rev2;
1184 /* Now, we open an extra RA session to the correct anchor
1185 location for URL1. This is used during the editor calls to fetch file
1186 contents. */
1187 SVN_ERR(svn_client__open_ra_session_internal
1188 (&extra_ra_session, drr.anchor1, NULL, NULL, NULL, FALSE, TRUE, ctx,
1189 pool));
1191 /* Set up the repos_diff editor on BASE_PATH, if available.
1192 Otherwise, we just use "". */
1193 SVN_ERR(svn_client__get_diff_editor
1194 (drr.base_path ? drr.base_path : "",
1195 NULL, callbacks, callback_baton, diff_param->depth,
1196 FALSE /* doesn't matter for diff */, extra_ra_session, drr.rev1,
1197 NULL /* no notify_func */, NULL /* no notify_baton */,
1198 ctx->cancel_func, ctx->cancel_baton,
1199 &diff_editor, &diff_edit_baton, pool));
1201 /* We want to switch our txn into URL2 */
1202 SVN_ERR(svn_ra_do_diff3
1203 (drr.ra_session, &reporter, &report_baton, drr.rev2, drr.target1,
1204 diff_param->depth, diff_param->ignore_ancestry, TRUE,
1205 drr.url2, diff_editor, diff_edit_baton, pool));
1207 /* Drive the reporter; do the diff. */
1208 SVN_ERR(reporter->set_path(report_baton, "", drr.rev1,
1209 svn_depth_infinity,
1210 FALSE, NULL,
1211 pool));
1212 SVN_ERR(reporter->finish_report(report_baton, pool));
1214 return SVN_NO_ERROR;
1218 /* Perform a diff between a repository path and a working-copy path.
1220 PATH1 may be either a URL or a working copy path. PATH2 is a
1221 working copy path. REVISION1 and REVISION2 are their respective
1222 revisions. If REVERSE is TRUE, the diff will be done in reverse.
1223 If PEG_REVISION is specified, then PATH1 is the path in the peg
1224 revision, and the actual repository path to be compared is
1225 determined by following copy history.
1227 All other options are the same as those passed to svn_client_diff4(). */
1228 static svn_error_t *
1229 diff_repos_wc(const apr_array_header_t *options,
1230 const char *path1,
1231 const svn_opt_revision_t *revision1,
1232 const svn_opt_revision_t *peg_revision,
1233 const char *path2,
1234 const svn_opt_revision_t *revision2,
1235 svn_boolean_t reverse,
1236 svn_depth_t depth,
1237 svn_boolean_t ignore_ancestry,
1238 const svn_wc_diff_callbacks2_t *callbacks,
1239 struct diff_cmd_baton *callback_baton,
1240 svn_client_ctx_t *ctx,
1241 apr_pool_t *pool)
1243 const char *url1, *anchor, *anchor_url, *target;
1244 svn_wc_adm_access_t *adm_access, *dir_access;
1245 const svn_wc_entry_t *entry;
1246 svn_revnum_t rev;
1247 svn_ra_session_t *ra_session;
1248 const svn_ra_reporter3_t *reporter;
1249 void *report_baton;
1250 const svn_delta_editor_t *diff_editor;
1251 void *diff_edit_baton;
1252 svn_boolean_t rev2_is_base = (revision2->kind == svn_opt_revision_base);
1253 int levels_to_lock = SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth);
1254 svn_boolean_t server_supports_depth;
1256 /* Assert that we have valid input. */
1257 assert(! svn_path_is_url(path2));
1259 /* Convert path1 to a URL to feed to do_diff. */
1260 SVN_ERR(convert_to_url(&url1, path1, pool));
1262 SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target,
1263 path2, FALSE, levels_to_lock,
1264 ctx->cancel_func, ctx->cancel_baton,
1265 pool));
1266 anchor = svn_wc_adm_access_path(adm_access);
1268 /* Fetch the URL of the anchor directory. */
1269 SVN_ERR(svn_wc__entry_versioned(&entry, anchor, adm_access, FALSE, pool));
1270 if (! entry->url)
1271 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
1272 _("Directory '%s' has no URL"),
1273 svn_path_local_style(anchor, pool));
1274 anchor_url = apr_pstrdup(pool, entry->url);
1276 /* If we are performing a pegged diff, we need to find out what our
1277 actual URLs will be. */
1278 if (peg_revision->kind != svn_opt_revision_unspecified)
1280 svn_opt_revision_t *start_ignore, *end_ignore, end;
1281 const char *url_ignore;
1283 end.kind = svn_opt_revision_unspecified;
1285 SVN_ERR(svn_client__repos_locations(&url1, &start_ignore,
1286 &url_ignore, &end_ignore,
1287 NULL,
1288 path1,
1289 peg_revision,
1290 revision1, &end,
1291 ctx, pool));
1293 if (!reverse)
1295 callback_baton->orig_path_1 = url1;
1296 callback_baton->orig_path_2 = svn_path_join(anchor_url, target, pool);
1298 else
1300 callback_baton->orig_path_1 = svn_path_join(anchor_url, target, pool);
1301 callback_baton->orig_path_2 = url1;
1305 /* Establish RA session to path2's anchor */
1306 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, anchor_url,
1307 NULL, NULL, NULL, FALSE, TRUE,
1308 ctx, pool));
1310 SVN_ERR(svn_wc_get_diff_editor4(adm_access, target,
1311 callbacks, callback_baton,
1312 depth,
1313 ignore_ancestry,
1314 rev2_is_base,
1315 reverse,
1316 ctx->cancel_func, ctx->cancel_baton,
1317 &diff_editor, &diff_edit_baton,
1318 pool));
1320 /* Tell the RA layer we want a delta to change our txn to URL1 */
1321 SVN_ERR(svn_client__get_revision_number
1322 (&rev, NULL, ra_session, revision1,
1323 (path1 == url1) ? NULL : path1, pool));
1325 if (!reverse)
1326 callback_baton->revnum1 = rev;
1327 else
1328 callback_baton->revnum2 = rev;
1330 SVN_ERR(svn_ra_do_diff3(ra_session,
1331 &reporter, &report_baton,
1332 rev,
1333 target ? svn_path_uri_decode(target, pool) : NULL,
1334 depth,
1335 ignore_ancestry,
1336 TRUE, /* text_deltas */
1337 url1,
1338 diff_editor, diff_edit_baton, pool));
1340 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
1341 SVN_RA_CAPABILITY_DEPTH, pool));
1343 /* Create a txn mirror of path2; the diff editor will print
1344 diffs in reverse. :-) */
1345 SVN_ERR(svn_wc_crawl_revisions3(path2, dir_access,
1346 reporter, report_baton,
1347 FALSE, depth, (! server_supports_depth),
1348 FALSE, NULL, NULL, /* notification is N/A */
1349 NULL, pool));
1351 SVN_ERR(svn_wc_adm_close(adm_access));
1352 return SVN_NO_ERROR;
1356 /* This is basically just the guts of svn_client_diff[_peg]3(). */
1357 static svn_error_t *
1358 do_diff(const struct diff_parameters *diff_param,
1359 const svn_wc_diff_callbacks2_t *callbacks,
1360 struct diff_cmd_baton *callback_baton,
1361 svn_client_ctx_t *ctx,
1362 apr_pool_t *pool)
1364 struct diff_paths diff_paths;
1366 /* Check if paths/revisions are urls/local. */
1367 SVN_ERR(check_paths(diff_param, &diff_paths));
1369 if (diff_paths.is_repos1)
1371 if (diff_paths.is_repos2)
1373 SVN_ERR(diff_repos_repos(diff_param, callbacks, callback_baton,
1374 ctx, pool));
1376 else /* path2 is a working copy path */
1378 SVN_ERR(diff_repos_wc(diff_param->options,
1379 diff_param->path1, diff_param->revision1,
1380 diff_param->peg_revision,
1381 diff_param->path2, diff_param->revision2,
1382 FALSE, diff_param->depth,
1383 diff_param->ignore_ancestry,
1384 callbacks, callback_baton, ctx, pool));
1387 else /* path1 is a working copy path */
1389 if (diff_paths.is_repos2)
1391 SVN_ERR(diff_repos_wc(diff_param->options,
1392 diff_param->path2, diff_param->revision2,
1393 diff_param->peg_revision,
1394 diff_param->path1, diff_param->revision1,
1395 TRUE, diff_param->depth,
1396 diff_param->ignore_ancestry,
1397 callbacks, callback_baton, ctx, pool));
1399 else /* path2 is a working copy path */
1401 SVN_ERR(diff_wc_wc(diff_param->options,
1402 diff_param->path1, diff_param->revision1,
1403 diff_param->path2, diff_param->revision2,
1404 diff_param->depth,
1405 diff_param->ignore_ancestry,
1406 callbacks, callback_baton, ctx, pool));
1410 return SVN_NO_ERROR;
1413 /* Perform a diff summary between two repository paths. */
1414 static svn_error_t *
1415 diff_summarize_repos_repos(const struct diff_parameters *diff_param,
1416 svn_client_diff_summarize_func_t summarize_func,
1417 void *summarize_baton,
1418 svn_client_ctx_t *ctx,
1419 apr_pool_t *pool)
1421 svn_ra_session_t *extra_ra_session;
1423 const svn_ra_reporter3_t *reporter;
1424 void *report_baton;
1426 const svn_delta_editor_t *diff_editor;
1427 void *diff_edit_baton;
1429 struct diff_repos_repos_t drr;
1431 /* Prepare info for the repos repos diff. */
1432 SVN_ERR(diff_prepare_repos_repos(diff_param, &drr, ctx, pool));
1434 /* Now, we open an extra RA session to the correct anchor
1435 location for URL1. This is used to get the kind of deleted paths. */
1436 SVN_ERR(svn_client__open_ra_session_internal
1437 (&extra_ra_session, drr.anchor1, NULL, NULL, NULL, FALSE, TRUE,
1438 ctx, pool));
1440 /* Set up the repos_diff editor. */
1441 SVN_ERR(svn_client__get_diff_summarize_editor
1442 (drr.target2, summarize_func,
1443 summarize_baton, extra_ra_session, drr.rev1, ctx->cancel_func,
1444 ctx->cancel_baton, &diff_editor, &diff_edit_baton, pool));
1446 /* We want to switch our txn into URL2 */
1447 SVN_ERR(svn_ra_do_diff3
1448 (drr.ra_session, &reporter, &report_baton, drr.rev2, drr.target1,
1449 diff_param->depth, diff_param->ignore_ancestry,
1450 FALSE /* do not create text delta */, drr.url2, diff_editor,
1451 diff_edit_baton, pool));
1453 /* Drive the reporter; do the diff. */
1454 SVN_ERR(reporter->set_path(report_baton, "", drr.rev1,
1455 svn_depth_infinity,
1456 FALSE, NULL, pool));
1457 SVN_ERR(reporter->finish_report(report_baton, pool));
1459 return SVN_NO_ERROR;
1462 /* This is basically just the guts of svn_client_diff_summarize[_peg](). */
1463 static svn_error_t *
1464 do_diff_summarize(const struct diff_parameters *diff_param,
1465 svn_client_diff_summarize_func_t summarize_func,
1466 void *summarize_baton,
1467 svn_client_ctx_t *ctx,
1468 apr_pool_t *pool)
1470 struct diff_paths diff_paths;
1472 /* Check if paths/revisions are urls/local. */
1473 SVN_ERR(check_paths(diff_param, &diff_paths));
1475 if (diff_paths.is_repos1 && diff_paths.is_repos2)
1477 SVN_ERR(diff_summarize_repos_repos(diff_param, summarize_func,
1478 summarize_baton, ctx, pool));
1480 else
1481 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1482 _("Summarizing diff can only compare repository "
1483 "to repository"));
1485 return SVN_NO_ERROR;
1489 /*----------------------------------------------------------------------- */
1491 /*** Public Interfaces. ***/
1493 /* Display context diffs between two PATH/REVISION pairs. Each of
1494 these inputs will be one of the following:
1496 - a repository URL at a given revision.
1497 - a working copy path, ignoring local mods.
1498 - a working copy path, including local mods.
1500 We can establish a matrix that shows the nine possible types of
1501 diffs we expect to support.
1504 ` . DST || URL:rev | WC:base | WC:working |
1505 ` . || | | |
1506 SRC ` . || | | |
1507 ============++============+============+============+
1508 URL:rev || (*) | (*) | (*) |
1509 || | | |
1510 || | | |
1511 || | | |
1512 ------------++------------+------------+------------+
1513 WC:base || (*) | |
1514 || | New svn_wc_diff which |
1515 || | is smart enough to |
1516 || | handle two WC paths |
1517 ------------++------------+ and their related +
1518 WC:working || (*) | text-bases and working |
1519 || | files. This operation |
1520 || | is entirely local. |
1521 || | |
1522 ------------++------------+------------+------------+
1523 * These cases require server communication.
1525 svn_error_t *
1526 svn_client_diff4(const apr_array_header_t *options,
1527 const char *path1,
1528 const svn_opt_revision_t *revision1,
1529 const char *path2,
1530 const svn_opt_revision_t *revision2,
1531 const char *relative_to_dir,
1532 svn_depth_t depth,
1533 svn_boolean_t ignore_ancestry,
1534 svn_boolean_t no_diff_deleted,
1535 svn_boolean_t ignore_content_type,
1536 const char *header_encoding,
1537 apr_file_t *outfile,
1538 apr_file_t *errfile,
1539 svn_client_ctx_t *ctx,
1540 apr_pool_t *pool)
1542 struct diff_parameters diff_params;
1544 struct diff_cmd_baton diff_cmd_baton;
1545 svn_wc_diff_callbacks2_t diff_callbacks;
1547 /* We will never do a pegged diff from here. */
1548 svn_opt_revision_t peg_revision;
1549 peg_revision.kind = svn_opt_revision_unspecified;
1551 /* fill diff_param */
1552 diff_params.options = options;
1553 diff_params.path1 = path1;
1554 diff_params.revision1 = revision1;
1555 diff_params.path2 = path2;
1556 diff_params.revision2 = revision2;
1557 diff_params.peg_revision = &peg_revision;
1558 diff_params.depth = depth;
1559 diff_params.ignore_ancestry = ignore_ancestry;
1560 diff_params.no_diff_deleted = no_diff_deleted;
1562 /* setup callback and baton */
1563 diff_callbacks.file_changed = diff_file_changed;
1564 diff_callbacks.file_added = diff_file_added;
1565 diff_callbacks.file_deleted = no_diff_deleted ? diff_file_deleted_no_diff :
1566 diff_file_deleted_with_diff;
1567 diff_callbacks.dir_added = diff_dir_added;
1568 diff_callbacks.dir_deleted = diff_dir_deleted;
1569 diff_callbacks.dir_props_changed = diff_props_changed;
1571 diff_cmd_baton.orig_path_1 = path1;
1572 diff_cmd_baton.orig_path_2 = path2;
1574 diff_cmd_baton.options = options;
1575 diff_cmd_baton.pool = pool;
1576 diff_cmd_baton.outfile = outfile;
1577 diff_cmd_baton.errfile = errfile;
1578 diff_cmd_baton.header_encoding = header_encoding;
1579 diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
1580 diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
1582 diff_cmd_baton.config = ctx->config;
1583 diff_cmd_baton.force_empty = FALSE;
1584 diff_cmd_baton.force_binary = ignore_content_type;
1585 diff_cmd_baton.relative_to_dir = relative_to_dir;
1587 return do_diff(&diff_params, &diff_callbacks, &diff_cmd_baton, ctx, pool);
1590 svn_error_t *
1591 svn_client_diff3(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 svn_boolean_t recurse,
1597 svn_boolean_t ignore_ancestry,
1598 svn_boolean_t no_diff_deleted,
1599 svn_boolean_t ignore_content_type,
1600 const char *header_encoding,
1601 apr_file_t *outfile,
1602 apr_file_t *errfile,
1603 svn_client_ctx_t *ctx,
1604 apr_pool_t *pool)
1606 return svn_client_diff4(options, path1, revision1, path2,
1607 revision2, NULL,
1608 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1609 ignore_ancestry, no_diff_deleted,
1610 ignore_content_type, header_encoding,
1611 outfile, errfile, ctx, pool);
1614 svn_error_t *
1615 svn_client_diff2(const apr_array_header_t *options,
1616 const char *path1,
1617 const svn_opt_revision_t *revision1,
1618 const char *path2,
1619 const svn_opt_revision_t *revision2,
1620 svn_boolean_t recurse,
1621 svn_boolean_t ignore_ancestry,
1622 svn_boolean_t no_diff_deleted,
1623 svn_boolean_t ignore_content_type,
1624 apr_file_t *outfile,
1625 apr_file_t *errfile,
1626 svn_client_ctx_t *ctx,
1627 apr_pool_t *pool)
1629 return svn_client_diff3(options, path1, revision1, path2, revision2,
1630 recurse, ignore_ancestry, no_diff_deleted,
1631 ignore_content_type, SVN_APR_LOCALE_CHARSET,
1632 outfile, errfile, ctx, pool);
1635 svn_error_t *
1636 svn_client_diff(const apr_array_header_t *options,
1637 const char *path1,
1638 const svn_opt_revision_t *revision1,
1639 const char *path2,
1640 const svn_opt_revision_t *revision2,
1641 svn_boolean_t recurse,
1642 svn_boolean_t ignore_ancestry,
1643 svn_boolean_t no_diff_deleted,
1644 apr_file_t *outfile,
1645 apr_file_t *errfile,
1646 svn_client_ctx_t *ctx,
1647 apr_pool_t *pool)
1649 return svn_client_diff2(options, path1, revision1, path2, revision2,
1650 recurse, ignore_ancestry, no_diff_deleted, FALSE,
1651 outfile, errfile, ctx, pool);
1654 svn_error_t *
1655 svn_client_diff_peg4(const apr_array_header_t *options,
1656 const char *path,
1657 const svn_opt_revision_t *peg_revision,
1658 const svn_opt_revision_t *start_revision,
1659 const svn_opt_revision_t *end_revision,
1660 const char *relative_to_dir,
1661 svn_depth_t depth,
1662 svn_boolean_t ignore_ancestry,
1663 svn_boolean_t no_diff_deleted,
1664 svn_boolean_t ignore_content_type,
1665 const char *header_encoding,
1666 apr_file_t *outfile,
1667 apr_file_t *errfile,
1668 svn_client_ctx_t *ctx,
1669 apr_pool_t *pool)
1671 struct diff_parameters diff_params;
1673 struct diff_cmd_baton diff_cmd_baton;
1674 svn_wc_diff_callbacks2_t diff_callbacks;
1676 /* fill diff_param */
1677 diff_params.options = options;
1678 diff_params.path1 = path;
1679 diff_params.revision1 = start_revision;
1680 diff_params.path2 = path;
1681 diff_params.revision2 = end_revision;
1682 diff_params.peg_revision = peg_revision;
1683 diff_params.depth = depth;
1684 diff_params.ignore_ancestry = ignore_ancestry;
1685 diff_params.no_diff_deleted = no_diff_deleted;
1687 /* setup callback and baton */
1688 diff_callbacks.file_changed = diff_file_changed;
1689 diff_callbacks.file_added = diff_file_added;
1690 diff_callbacks.file_deleted = no_diff_deleted ? diff_file_deleted_no_diff :
1691 diff_file_deleted_with_diff;
1692 diff_callbacks.dir_added = diff_dir_added;
1693 diff_callbacks.dir_deleted = diff_dir_deleted;
1694 diff_callbacks.dir_props_changed = diff_props_changed;
1696 diff_cmd_baton.orig_path_1 = path;
1697 diff_cmd_baton.orig_path_2 = path;
1699 diff_cmd_baton.options = options;
1700 diff_cmd_baton.pool = pool;
1701 diff_cmd_baton.outfile = outfile;
1702 diff_cmd_baton.errfile = errfile;
1703 diff_cmd_baton.header_encoding = header_encoding;
1704 diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
1705 diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
1707 diff_cmd_baton.config = ctx->config;
1708 diff_cmd_baton.force_empty = FALSE;
1709 diff_cmd_baton.force_binary = ignore_content_type;
1710 diff_cmd_baton.relative_to_dir = relative_to_dir;
1712 return do_diff(&diff_params, &diff_callbacks, &diff_cmd_baton, ctx, pool);
1715 svn_error_t *
1716 svn_client_diff_peg3(const apr_array_header_t *options,
1717 const char *path,
1718 const svn_opt_revision_t *peg_revision,
1719 const svn_opt_revision_t *start_revision,
1720 const svn_opt_revision_t *end_revision,
1721 svn_boolean_t recurse,
1722 svn_boolean_t ignore_ancestry,
1723 svn_boolean_t no_diff_deleted,
1724 svn_boolean_t ignore_content_type,
1725 const char *header_encoding,
1726 apr_file_t *outfile,
1727 apr_file_t *errfile,
1728 svn_client_ctx_t *ctx,
1729 apr_pool_t *pool)
1731 return svn_client_diff_peg4(options,
1732 path,
1733 peg_revision,
1734 start_revision,
1735 end_revision,
1736 NULL,
1737 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1738 ignore_ancestry,
1739 no_diff_deleted,
1740 ignore_content_type,
1741 header_encoding,
1742 outfile,
1743 errfile,
1744 ctx,
1745 pool);
1748 svn_error_t *
1749 svn_client_diff_peg2(const apr_array_header_t *options,
1750 const char *path,
1751 const svn_opt_revision_t *peg_revision,
1752 const svn_opt_revision_t *start_revision,
1753 const svn_opt_revision_t *end_revision,
1754 svn_boolean_t recurse,
1755 svn_boolean_t ignore_ancestry,
1756 svn_boolean_t no_diff_deleted,
1757 svn_boolean_t ignore_content_type,
1758 apr_file_t *outfile,
1759 apr_file_t *errfile,
1760 svn_client_ctx_t *ctx,
1761 apr_pool_t *pool)
1763 return svn_client_diff_peg3(options, path, peg_revision, start_revision,
1764 end_revision,
1765 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1766 ignore_ancestry, no_diff_deleted,
1767 ignore_content_type, SVN_APR_LOCALE_CHARSET,
1768 outfile, errfile, ctx, pool);
1771 svn_error_t *
1772 svn_client_diff_peg(const apr_array_header_t *options,
1773 const char *path,
1774 const svn_opt_revision_t *peg_revision,
1775 const svn_opt_revision_t *start_revision,
1776 const svn_opt_revision_t *end_revision,
1777 svn_boolean_t recurse,
1778 svn_boolean_t ignore_ancestry,
1779 svn_boolean_t no_diff_deleted,
1780 apr_file_t *outfile,
1781 apr_file_t *errfile,
1782 svn_client_ctx_t *ctx,
1783 apr_pool_t *pool)
1785 return svn_client_diff_peg2(options, path, peg_revision,
1786 start_revision, end_revision, recurse,
1787 ignore_ancestry, no_diff_deleted, FALSE,
1788 outfile, errfile, ctx, pool);
1791 svn_error_t *
1792 svn_client_diff_summarize2(const char *path1,
1793 const svn_opt_revision_t *revision1,
1794 const char *path2,
1795 const svn_opt_revision_t *revision2,
1796 svn_depth_t depth,
1797 svn_boolean_t ignore_ancestry,
1798 svn_client_diff_summarize_func_t summarize_func,
1799 void *summarize_baton,
1800 svn_client_ctx_t *ctx,
1801 apr_pool_t *pool)
1803 struct diff_parameters diff_params;
1805 /* We will never do a pegged diff from here. */
1806 svn_opt_revision_t peg_revision;
1807 peg_revision.kind = svn_opt_revision_unspecified;
1809 /* fill diff_param */
1810 diff_params.options = NULL;
1811 diff_params.path1 = path1;
1812 diff_params.revision1 = revision1;
1813 diff_params.path2 = path2;
1814 diff_params.revision2 = revision2;
1815 diff_params.peg_revision = &peg_revision;
1816 diff_params.depth = depth;
1817 diff_params.ignore_ancestry = ignore_ancestry;
1818 diff_params.no_diff_deleted = FALSE;
1820 return do_diff_summarize(&diff_params, summarize_func, summarize_baton,
1821 ctx, pool);
1824 svn_error_t *
1825 svn_client_diff_summarize(const char *path1,
1826 const svn_opt_revision_t *revision1,
1827 const char *path2,
1828 const svn_opt_revision_t *revision2,
1829 svn_boolean_t recurse,
1830 svn_boolean_t ignore_ancestry,
1831 svn_client_diff_summarize_func_t summarize_func,
1832 void *summarize_baton,
1833 svn_client_ctx_t *ctx,
1834 apr_pool_t *pool)
1836 return svn_client_diff_summarize2(path1, revision1, path2,
1837 revision2,
1838 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1839 ignore_ancestry, summarize_func,
1840 summarize_baton, ctx, pool);
1843 svn_error_t *
1844 svn_client_diff_summarize_peg2(const char *path,
1845 const svn_opt_revision_t *peg_revision,
1846 const svn_opt_revision_t *start_revision,
1847 const svn_opt_revision_t *end_revision,
1848 svn_depth_t depth,
1849 svn_boolean_t ignore_ancestry,
1850 svn_client_diff_summarize_func_t summarize_func,
1851 void *summarize_baton,
1852 svn_client_ctx_t *ctx,
1853 apr_pool_t *pool)
1855 struct diff_parameters diff_params;
1857 /* fill diff_param */
1858 diff_params.options = NULL;
1859 diff_params.path1 = path;
1860 diff_params.revision1 = start_revision;
1861 diff_params.path2 = path;
1862 diff_params.revision2 = end_revision;
1863 diff_params.peg_revision = peg_revision;
1864 diff_params.depth = depth;
1865 diff_params.ignore_ancestry = ignore_ancestry;
1866 diff_params.no_diff_deleted = FALSE;
1868 return do_diff_summarize(&diff_params, summarize_func, summarize_baton,
1869 ctx, pool);
1873 svn_error_t *
1874 svn_client_diff_summarize_peg(const char *path,
1875 const svn_opt_revision_t *peg_revision,
1876 const svn_opt_revision_t *start_revision,
1877 const svn_opt_revision_t *end_revision,
1878 svn_boolean_t recurse,
1879 svn_boolean_t ignore_ancestry,
1880 svn_client_diff_summarize_func_t summarize_func,
1881 void *summarize_baton,
1882 svn_client_ctx_t *ctx,
1883 apr_pool_t *pool)
1885 return svn_client_diff_summarize_peg2(path, peg_revision,
1886 start_revision, end_revision,
1887 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1888 ignore_ancestry,
1889 summarize_func, summarize_baton,
1890 ctx, pool);
1893 svn_client_diff_summarize_t *
1894 svn_client_diff_summarize_dup(const svn_client_diff_summarize_t *diff,
1895 apr_pool_t *pool)
1897 svn_client_diff_summarize_t *dup_diff = apr_palloc(pool, sizeof(*dup_diff));
1899 *dup_diff = *diff;
1901 if (diff->path)
1902 dup_diff->path = apr_pstrdup(pool, diff->path);
1904 return dup_diff;