Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_client / diff.c
blobd07f5fb1c5cc9438dd8846879ba8570cea44fd5f
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 _(" 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;
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 {
300 const apr_array_header_t *options;
301 apr_pool_t *pool;
302 apr_file_t *outfile;
303 apr_file_t *errfile;
305 const char *header_encoding;
307 /* The original targets passed to the diff command. We may need
308 these to construct distinctive diff labels when comparing the
309 same relative path in the same revision, under different anchors
310 (for example, when comparing a trunk against a branch). */
311 const char *orig_path_1;
312 const char *orig_path_2;
314 /* These are the numeric representations of the revisions passed to
315 svn_client_diff4, either may be SVN_INVALID_REVNUM. We need these
316 because some of the svn_wc_diff_callbacks2_t don't get revision
317 arguments.
319 ### Perhaps we should change the callback signatures and eliminate
320 ### these?
322 svn_revnum_t revnum1;
323 svn_revnum_t revnum2;
325 /* Client config hash (may be NULL). */
326 apr_hash_t *config;
328 /* Set this if you want diff output even for binary files. */
329 svn_boolean_t force_binary;
331 /* Set this flag if you want diff_file_changed to output diffs
332 unconditionally, even if the diffs are empty. */
333 svn_boolean_t force_empty;
335 /* The directory that diff target paths should be considered as
336 relative to for output generation (see issue #2723). */
337 const char *relative_to_dir;
341 /* Generate a label for the diff output for file PATH at revision REVNUM.
342 If REVNUM is invalid then it is assumed to be the current working
343 copy. Assumes the paths are already in the desired style (local
344 vs internal). Allocate the label in POOL. */
345 static const char *
346 diff_label(const char *path,
347 svn_revnum_t revnum,
348 apr_pool_t *pool)
350 const char *label;
351 if (revnum != SVN_INVALID_REVNUM)
352 label = apr_psprintf(pool, _("%s\t(revision %ld)"), path, revnum);
353 else
354 label = apr_psprintf(pool, _("%s\t(working copy)"), path);
356 return label;
359 /* A svn_wc_diff_callbacks2_t function. Used for both file and directory
360 property diffs. */
361 static svn_error_t *
362 diff_props_changed(svn_wc_adm_access_t *adm_access,
363 svn_wc_notify_state_t *state,
364 const char *path,
365 const apr_array_header_t *propchanges,
366 apr_hash_t *original_props,
367 void *diff_baton)
369 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
370 apr_array_header_t *props;
371 apr_pool_t *subpool = svn_pool_create(diff_cmd_baton->pool);
373 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, subpool));
375 if (props->nelts > 0)
376 SVN_ERR(display_prop_diffs(props, original_props, path,
377 diff_cmd_baton->header_encoding,
378 diff_cmd_baton->outfile,
379 diff_cmd_baton->relative_to_dir,
380 subpool));
382 if (state)
383 *state = svn_wc_notify_state_unknown;
385 svn_pool_destroy(subpool);
386 return SVN_NO_ERROR;
389 /* Show differences between TMPFILE1 and TMPFILE2. PATH, REV1, and REV2 are
390 used in the headers to indicate the file and revisions. If either
391 MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
392 but instead print a warning message. */
393 static svn_error_t *
394 diff_content_changed(const char *path,
395 const char *tmpfile1,
396 const char *tmpfile2,
397 svn_revnum_t rev1,
398 svn_revnum_t rev2,
399 const char *mimetype1,
400 const char *mimetype2,
401 void *diff_baton)
403 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
404 const char *diff_cmd = NULL;
405 const char **args = NULL;
406 int nargs, exitcode;
407 apr_pool_t *subpool = svn_pool_create(diff_cmd_baton->pool);
408 svn_stream_t *os;
409 const char *rel_to_dir = diff_cmd_baton->relative_to_dir;
410 apr_file_t *errfile = diff_cmd_baton->errfile;
411 const char *label1, *label2;
412 svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
413 const char *path1, *path2;
414 int i;
416 /* Get a stream from our output file. */
417 os = svn_stream_from_aprfile(diff_cmd_baton->outfile, subpool);
419 /* Assemble any option args. */
420 nargs = diff_cmd_baton->options->nelts;
421 if (nargs)
423 args = apr_palloc(subpool, nargs * sizeof(char *));
424 for (i = 0; i < diff_cmd_baton->options->nelts; i++)
426 args[i] = APR_ARRAY_IDX(diff_cmd_baton->options, i, const char *);
428 assert(i == nargs);
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 /* Find out if we need to run an external diff */
563 if (diff_cmd_baton->config)
565 svn_config_t *cfg = apr_hash_get(diff_cmd_baton->config,
566 SVN_CONFIG_CATEGORY_CONFIG,
567 APR_HASH_KEY_STRING);
568 svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
569 SVN_CONFIG_OPTION_DIFF_CMD, NULL);
572 if (diff_cmd)
574 /* Print out the diff header. */
575 SVN_ERR(svn_stream_printf_from_utf8
576 (os, diff_cmd_baton->header_encoding, subpool,
577 "Index: %s" APR_EOL_STR "%s" APR_EOL_STR, path, equal_string));
578 /* Close the stream (flush) */
579 SVN_ERR(svn_stream_close(os));
581 SVN_ERR(svn_io_run_diff(".", args, nargs, label1, label2,
582 tmpfile1, tmpfile2,
583 &exitcode, diff_cmd_baton->outfile, errfile,
584 diff_cmd, subpool));
586 else /* use libsvn_diff to generate the diff */
588 svn_diff_t *diff;
589 svn_diff_file_options_t *opts = svn_diff_file_options_create(subpool);
591 if (diff_cmd_baton->options)
592 SVN_ERR(svn_diff_file_options_parse(opts, diff_cmd_baton->options,
593 subpool));
595 SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2, opts,
596 subpool));
598 if (svn_diff_contains_diffs(diff) || diff_cmd_baton->force_empty)
600 /* Print out the diff header. */
601 SVN_ERR(svn_stream_printf_from_utf8
602 (os, diff_cmd_baton->header_encoding, subpool,
603 "Index: %s" APR_EOL_STR "%s" APR_EOL_STR,
604 path, equal_string));
605 /* Output the actual diff */
606 SVN_ERR(svn_diff_file_output_unified3
607 (os, diff, tmpfile1, tmpfile2, label1, label2,
608 diff_cmd_baton->header_encoding, rel_to_dir,
609 opts->show_c_function, subpool));
613 /* ### todo: someday we'll need to worry about whether we're going
614 to need to write a diff plug-in mechanism that makes use of the
615 two paths, instead of just blindly running SVN_CLIENT_DIFF. */
617 /* Destroy the subpool. */
618 svn_pool_destroy(subpool);
620 return SVN_NO_ERROR;
623 /* A svn_wc_diff_callbacks2_t function. */
624 static svn_error_t *
625 diff_file_changed(svn_wc_adm_access_t *adm_access,
626 svn_wc_notify_state_t *content_state,
627 svn_wc_notify_state_t *prop_state,
628 const char *path,
629 const char *tmpfile1,
630 const char *tmpfile2,
631 svn_revnum_t rev1,
632 svn_revnum_t rev2,
633 const char *mimetype1,
634 const char *mimetype2,
635 const apr_array_header_t *prop_changes,
636 apr_hash_t *original_props,
637 void *diff_baton)
639 if (tmpfile1)
640 SVN_ERR(diff_content_changed(path,
641 tmpfile1, tmpfile2, rev1, rev2,
642 mimetype1, mimetype2, diff_baton));
643 if (prop_changes->nelts > 0)
644 SVN_ERR(diff_props_changed(adm_access, prop_state, path, prop_changes,
645 original_props, diff_baton));
646 if (content_state)
647 *content_state = svn_wc_notify_state_unknown;
648 if (prop_state)
649 *prop_state = svn_wc_notify_state_unknown;
650 return SVN_NO_ERROR;
653 /* Because the repos-diff editor passes at least one empty file to
654 each of these next two functions, they can be dumb wrappers around
655 the main workhorse routine. */
657 /* A svn_wc_diff_callbacks2_t function. */
658 static svn_error_t *
659 diff_file_added(svn_wc_adm_access_t *adm_access,
660 svn_wc_notify_state_t *content_state,
661 svn_wc_notify_state_t *prop_state,
662 const char *path,
663 const char *tmpfile1,
664 const char *tmpfile2,
665 svn_revnum_t rev1,
666 svn_revnum_t rev2,
667 const char *mimetype1,
668 const char *mimetype2,
669 const apr_array_header_t *prop_changes,
670 apr_hash_t *original_props,
671 void *diff_baton)
673 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
675 /* We want diff_file_changed to unconditionally show diffs, even if
676 the diff is empty (as would be the case if an empty file were
677 added.) It's important, because 'patch' would still see an empty
678 diff and create an empty file. It's also important to let the
679 user see that *something* happened. */
680 diff_cmd_baton->force_empty = TRUE;
682 SVN_ERR(diff_file_changed(adm_access, content_state, prop_state, path,
683 tmpfile1, tmpfile2,
684 rev1, rev2,
685 mimetype1, mimetype2,
686 prop_changes, original_props, diff_baton));
688 diff_cmd_baton->force_empty = FALSE;
690 return SVN_NO_ERROR;
693 /* A svn_wc_diff_callbacks2_t function. */
694 static svn_error_t *
695 diff_file_deleted_with_diff(svn_wc_adm_access_t *adm_access,
696 svn_wc_notify_state_t *state,
697 const char *path,
698 const char *tmpfile1,
699 const char *tmpfile2,
700 const char *mimetype1,
701 const char *mimetype2,
702 apr_hash_t *original_props,
703 void *diff_baton)
705 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
707 /* We don't list all the deleted properties. */
708 return diff_file_changed(adm_access, state, NULL, path,
709 tmpfile1, tmpfile2,
710 diff_cmd_baton->revnum1, diff_cmd_baton->revnum2,
711 mimetype1, mimetype2,
712 apr_array_make(diff_cmd_baton->pool, 1,
713 sizeof(svn_prop_t)),
714 apr_hash_make(diff_cmd_baton->pool), diff_baton);
717 /* A svn_wc_diff_callbacks2_t function. */
718 static svn_error_t *
719 diff_file_deleted_no_diff(svn_wc_adm_access_t *adm_access,
720 svn_wc_notify_state_t *state,
721 const char *path,
722 const char *tmpfile1,
723 const char *tmpfile2,
724 const char *mimetype1,
725 const char *mimetype2,
726 apr_hash_t *original_props,
727 void *diff_baton)
729 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
731 if (state)
732 *state = svn_wc_notify_state_unknown;
734 SVN_ERR(file_printf_from_utf8
735 (diff_cmd_baton->outfile,
736 diff_cmd_baton->header_encoding,
737 "Index: %s (deleted)" APR_EOL_STR "%s" APR_EOL_STR,
738 path, equal_string));
740 return SVN_NO_ERROR;
743 /* A svn_wc_diff_callbacks2_t function.
744 For now, let's have 'svn diff' send feedback to the top-level
745 application, so that something reasonable about directories and
746 propsets gets printed to stdout. */
747 static svn_error_t *
748 diff_dir_added(svn_wc_adm_access_t *adm_access,
749 svn_wc_notify_state_t *state,
750 const char *path,
751 svn_revnum_t rev,
752 void *diff_baton)
754 if (state)
755 *state = svn_wc_notify_state_unknown;
757 /* ### todo: send feedback to app */
758 return SVN_NO_ERROR;
761 /* A svn_wc_diff_callbacks2_t function. */
762 static svn_error_t *
763 diff_dir_deleted(svn_wc_adm_access_t *adm_access,
764 svn_wc_notify_state_t *state,
765 const char *path,
766 void *diff_baton)
768 if (state)
769 *state = svn_wc_notify_state_unknown;
771 return SVN_NO_ERROR;
775 /*-----------------------------------------------------------------*/
777 /** The logic behind 'svn diff' and 'svn merge'. */
780 /* Hi! This is a comment left behind by Karl, and Ben is too afraid
781 to erase it at this time, because he's not fully confident that all
782 this knowledge has been grokked yet.
784 There are five cases:
785 1. path is not an URL and start_revision != end_revision
786 2. path is not an URL and start_revision == end_revision
787 3. path is an URL and start_revision != end_revision
788 4. path is an URL and start_revision == end_revision
789 5. path is not an URL and no revisions given
791 With only one distinct revision the working copy provides the
792 other. When path is an URL there is no working copy. Thus
794 1: compare repository versions for URL coresponding to working copy
795 2: compare working copy against repository version
796 3: compare repository versions for URL
797 4: nothing to do.
798 5: compare working copy against text-base
800 Case 4 is not as stupid as it looks, for example it may occur if
801 the user specifies two dates that resolve to the same revision. */
806 /* Helper function: given a working-copy PATH, return its associated
807 url in *URL, allocated in POOL. If PATH is *already* a URL, that's
808 fine, just set *URL = PATH. */
809 static svn_error_t *
810 convert_to_url(const char **url,
811 const char *path,
812 apr_pool_t *pool)
814 svn_wc_adm_access_t *adm_access; /* ### FIXME local */
815 const svn_wc_entry_t *entry;
817 if (svn_path_is_url(path))
819 *url = path;
820 return SVN_NO_ERROR;
823 /* ### This may not be a good idea, see issue 880 */
824 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path, FALSE,
825 0, NULL, NULL, pool));
826 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access, FALSE, pool));
827 SVN_ERR(svn_wc_adm_close(adm_access));
829 if (entry->url)
830 *url = apr_pstrdup(pool, entry->url);
831 else
832 *url = apr_pstrdup(pool, entry->copyfrom_url);
833 return SVN_NO_ERROR;
836 /** Helper structure: for passing around the diff parameters */
837 struct diff_parameters
839 /* Additional parameters for diff tool */
840 const apr_array_header_t *options;
842 /* First input path */
843 const char *path1;
845 /* Revision of first input path */
846 const svn_opt_revision_t *revision1;
848 /* Second input path */
849 const char *path2;
851 /* Revision of second input path */
852 const svn_opt_revision_t *revision2;
854 /* Peg revision */
855 const svn_opt_revision_t *peg_revision;
857 /* Desired depth */
858 svn_depth_t depth;
860 /* Ignore ancestry */
861 svn_boolean_t ignore_ancestry;
863 /* Ignore deleted */
864 svn_boolean_t no_diff_deleted;
866 /* Changelists of interest */
867 const apr_array_header_t *changelists;
870 /** Helper structure: filled by check_paths() */
871 struct diff_paths
873 /* path1 can only be found in the repository? */
874 svn_boolean_t is_repos1;
876 /* path2 can only be found in the repository? */
877 svn_boolean_t is_repos2;
881 /** Check if paths are urls and if the revisions are local, and, for
882 pegged revisions, ensure that at least one revision is non-local.
883 Fills the PATHS structure. */
884 static svn_error_t *
885 check_paths(const struct diff_parameters *params,
886 struct diff_paths *paths)
888 svn_boolean_t is_local_rev1, is_local_rev2;
890 /* Verify our revision arguments in light of the paths. */
891 if ((params->revision1->kind == svn_opt_revision_unspecified)
892 || (params->revision2->kind == svn_opt_revision_unspecified))
893 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
894 _("Not all required revisions are specified"));
896 /* Revisions can be said to be local or remote. BASE and WORKING,
897 for example, are local. */
898 is_local_rev1 =
899 ((params->revision1->kind == svn_opt_revision_base)
900 || (params->revision1->kind == svn_opt_revision_working));
901 is_local_rev2 =
902 ((params->revision2->kind == svn_opt_revision_base)
903 || (params->revision2->kind == svn_opt_revision_working));
905 if (params->peg_revision->kind != svn_opt_revision_unspecified)
907 if (is_local_rev1 && is_local_rev2)
908 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
909 _("At least one revision must be non-local "
910 "for a pegged diff"));
912 paths->is_repos1 = ! is_local_rev1;
913 paths->is_repos2 = ! is_local_rev2;
915 else
917 /* Working copy paths with non-local revisions get turned into
918 URLs. We don't do that here, though. We simply record that it
919 needs to be done, which is information that helps us choose our
920 diff helper function. */
921 paths->is_repos1 = ! is_local_rev1 || svn_path_is_url(params->path1);
922 paths->is_repos2 = ! is_local_rev2 || svn_path_is_url(params->path2);
925 return SVN_NO_ERROR;
928 /** Helper structure filled by diff_prepare_repos_repos */
929 struct diff_repos_repos_t
931 /* URL created from path1 */
932 const char *url1;
934 /* URL created from path2 */
935 const char *url2;
937 /* The BASE_PATH for the diff */
938 const char *base_path;
940 /* url1 and url2 are the same */
941 svn_boolean_t same_urls;
943 /* Revision of url1 */
944 svn_revnum_t rev1;
946 /* Revision of url2 */
947 svn_revnum_t rev2;
949 /* Anchor based on url1 */
950 const char *anchor1;
952 /* Anchor based on url2 */
953 const char *anchor2;
955 /* Target based on url1 */
956 const char *target1;
958 /* Target based on url2 */
959 const char *target2;
961 /* RA session pointing at anchor1. */
962 svn_ra_session_t *ra_session;
965 /** Helper function: prepare a repos repos diff. Fills DRR
966 * structure. */
967 static svn_error_t *
968 diff_prepare_repos_repos(const struct diff_parameters *params,
969 struct diff_repos_repos_t *drr,
970 svn_client_ctx_t *ctx,
971 apr_pool_t *pool)
973 svn_ra_session_t *ra_session;
974 svn_node_kind_t kind1, kind2;
976 /* Figure out URL1 and URL2. */
977 SVN_ERR(convert_to_url(&drr->url1, params->path1, pool));
978 SVN_ERR(convert_to_url(&drr->url2, params->path2, pool));
979 drr->same_urls = (strcmp(drr->url1, drr->url2) == 0);
981 /* We need exactly one BASE_PATH, so we'll let the BASE_PATH
982 calculated for PATH2 override the one for PATH1 (since the diff
983 will be "applied" to URL2 anyway). */
984 drr->base_path = NULL;
985 if (drr->url1 != params->path1)
986 drr->base_path = params->path1;
987 if (drr->url2 != params->path2)
988 drr->base_path = params->path2;
990 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, drr->url2,
991 NULL, NULL, NULL, FALSE,
992 TRUE, ctx, pool));
994 /* If we are performing a pegged diff, we need to find out what our
995 actual URLs will be. */
996 if (params->peg_revision->kind != svn_opt_revision_unspecified)
998 svn_opt_revision_t *start_ignore, *end_ignore;
1000 SVN_ERR(svn_client__repos_locations(&drr->url1, &start_ignore,
1001 &drr->url2, &end_ignore,
1002 ra_session,
1003 params->path2,
1004 params->peg_revision,
1005 params->revision1,
1006 params->revision2,
1007 ctx, pool));
1008 /* Reparent the session, since drr->url2 might have changed as a result
1009 the above call. */
1010 SVN_ERR(svn_ra_reparent(ra_session, drr->url2, pool));
1013 /* Resolve revision and get path kind for the second target. */
1014 SVN_ERR(svn_client__get_revision_number
1015 (&drr->rev2, NULL, ra_session, params->revision2,
1016 (params->path2 == drr->url2) ? NULL : params->path2, pool));
1017 SVN_ERR(svn_ra_check_path(ra_session, "", drr->rev2, &kind2, pool));
1018 if (kind2 == svn_node_none)
1019 return svn_error_createf
1020 (SVN_ERR_FS_NOT_FOUND, NULL,
1021 _("'%s' was not found in the repository at revision %ld"),
1022 drr->url2, drr->rev2);
1024 /* Do the same for the first target. */
1025 SVN_ERR(svn_ra_reparent(ra_session, drr->url1, pool));
1026 SVN_ERR(svn_client__get_revision_number
1027 (&drr->rev1, NULL, ra_session, params->revision1,
1028 (params->path1 == drr->url1) ? NULL : params->path1, pool));
1029 SVN_ERR(svn_ra_check_path(ra_session, "", drr->rev1, &kind1, pool));
1030 if (kind1 == svn_node_none)
1031 return svn_error_createf
1032 (SVN_ERR_FS_NOT_FOUND, NULL,
1033 _("'%s' was not found in the repository at revision %ld"),
1034 drr->url1, drr->rev1);
1036 /* Choose useful anchors and targets for our two URLs. */
1037 drr->anchor1 = drr->url1;
1038 drr->anchor2 = drr->url2;
1039 drr->target1 = "";
1040 drr->target2 = "";
1041 if ((kind1 == svn_node_file) || (kind2 == svn_node_file))
1043 svn_path_split(drr->url1, &drr->anchor1, &drr->target1, pool);
1044 drr->target1 = svn_path_uri_decode(drr->target1, pool);
1045 svn_path_split(drr->url2, &drr->anchor2, &drr->target2, pool);
1046 drr->target2 = svn_path_uri_decode(drr->target2, pool);
1047 if (drr->base_path)
1048 drr->base_path = svn_path_dirname(drr->base_path, pool);
1049 SVN_ERR(svn_ra_reparent(ra_session, drr->anchor1, pool));
1052 drr->ra_session = ra_session;
1053 return SVN_NO_ERROR;
1056 /* A Theoretical Note From Ben, regarding do_diff().
1058 This function is really svn_client_diff4(). If you read the public
1059 API description for svn_client_diff4(), it sounds quite Grand. It
1060 sounds really generalized and abstract and beautiful: that it will
1061 diff any two paths, be they working-copy paths or URLs, at any two
1062 revisions.
1064 Now, the *reality* is that we have exactly three 'tools' for doing
1065 diffing, and thus this routine is built around the use of the three
1066 tools. Here they are, for clarity:
1068 - svn_wc_diff: assumes both paths are the same wcpath.
1069 compares wcpath@BASE vs. wcpath@WORKING
1071 - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING
1073 - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2
1075 So the truth of the matter is, if the caller's arguments can't be
1076 pigeonholed into one of these three use-cases, we currently bail
1077 with a friendly apology.
1079 Perhaps someday a brave soul will truly make svn_client_diff4
1080 perfectly general. For now, we live with the 90% case. Certainly,
1081 the commandline client only calls this function in legal ways.
1082 When there are other users of svn_client.h, maybe this will become
1083 a more pressing issue.
1086 /* Return a "you can't do that" error, optionally wrapping another
1087 error CHILD_ERR. */
1088 static svn_error_t *
1089 unsupported_diff_error(svn_error_t *child_err)
1091 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
1092 _("Sorry, svn_client_diff4 was called in a way "
1093 "that is not yet supported"));
1097 /* Perform a diff between two working-copy paths.
1099 PATH1 and PATH2 are both working copy paths. REVISION1 and
1100 REVISION2 are their respective revisions.
1102 All other options are the same as those passed to svn_client_diff4(). */
1103 static svn_error_t *
1104 diff_wc_wc(const apr_array_header_t *options,
1105 const char *path1,
1106 const svn_opt_revision_t *revision1,
1107 const char *path2,
1108 const svn_opt_revision_t *revision2,
1109 svn_depth_t depth,
1110 svn_boolean_t ignore_ancestry,
1111 const apr_array_header_t *changelists,
1112 const svn_wc_diff_callbacks2_t *callbacks,
1113 struct diff_cmd_baton *callback_baton,
1114 svn_client_ctx_t *ctx,
1115 apr_pool_t *pool)
1117 svn_wc_adm_access_t *adm_access, *target_access;
1118 const char *target;
1119 int levels_to_lock = SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth);
1121 /* Assert that we have valid input. */
1122 assert(! svn_path_is_url(path1));
1123 assert(! svn_path_is_url(path2));
1125 /* Currently we support only the case where path1 and path2 are the
1126 same path. */
1127 if ((strcmp(path1, path2) != 0)
1128 || (! ((revision1->kind == svn_opt_revision_base)
1129 && (revision2->kind == svn_opt_revision_working))))
1130 return unsupported_diff_error
1131 (svn_error_create
1132 (SVN_ERR_INCORRECT_PARAMS, NULL,
1133 _("Only diffs between a path's text-base "
1134 "and its working files are supported at this time")));
1136 SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &target_access, &target,
1137 path1, FALSE, levels_to_lock,
1138 ctx->cancel_func, ctx->cancel_baton,
1139 pool));
1141 /* Resolve named revisions to real numbers. */
1142 SVN_ERR(svn_client__get_revision_number
1143 (&callback_baton->revnum1, NULL, NULL, revision1, path1, pool));
1144 callback_baton->revnum2 = SVN_INVALID_REVNUM; /* WC */
1146 SVN_ERR(svn_wc_diff4(adm_access, target, callbacks, callback_baton,
1147 depth, ignore_ancestry, changelists, pool));
1148 SVN_ERR(svn_wc_adm_close(adm_access));
1149 return SVN_NO_ERROR;
1153 /* Perform a diff between two repository paths.
1155 DIFF_PARAM.PATH1 and DIFF_PARAM.PATH2 may be either URLs or the working
1156 copy paths. DIFF_PARAM.REVISION1 and DIFF_PARAM.REVISION2 are their
1157 respective revisions. If DIFF_PARAM.PEG_REVISION is specified,
1158 DIFF_PARAM.PATH2 is the path at the peg revision, and the actual two
1159 paths compared are determined by following copy history from PATH2.
1161 All other options are the same as those passed to svn_client_diff4(). */
1162 static svn_error_t *
1163 diff_repos_repos(const struct diff_parameters *diff_param,
1164 const svn_wc_diff_callbacks2_t *callbacks,
1165 struct diff_cmd_baton *callback_baton,
1166 svn_client_ctx_t *ctx,
1167 apr_pool_t *pool)
1169 svn_ra_session_t *extra_ra_session;
1171 const svn_ra_reporter3_t *reporter;
1172 void *report_baton;
1174 const svn_delta_editor_t *diff_editor;
1175 void *diff_edit_baton;
1177 struct diff_repos_repos_t drr;
1179 /* Prepare info for the repos repos diff. */
1180 SVN_ERR(diff_prepare_repos_repos(diff_param, &drr, ctx, pool));
1182 /* Get actual URLs. */
1183 callback_baton->orig_path_1 = drr.url1;
1184 callback_baton->orig_path_2 = drr.url2;
1186 /* Get numeric revisions. */
1187 callback_baton->revnum1 = drr.rev1;
1188 callback_baton->revnum2 = drr.rev2;
1190 /* Now, we open an extra RA session to the correct anchor
1191 location for URL1. This is used during the editor calls to fetch file
1192 contents. */
1193 SVN_ERR(svn_client__open_ra_session_internal
1194 (&extra_ra_session, drr.anchor1, NULL, NULL, NULL, FALSE, TRUE, ctx,
1195 pool));
1197 /* Set up the repos_diff editor on BASE_PATH, if available.
1198 Otherwise, we just use "". */
1199 SVN_ERR(svn_client__get_diff_editor
1200 (drr.base_path ? drr.base_path : "",
1201 NULL, callbacks, callback_baton, diff_param->depth,
1202 FALSE /* doesn't matter for diff */, extra_ra_session, drr.rev1,
1203 NULL /* no notify_func */, NULL /* no notify_baton */,
1204 ctx->cancel_func, ctx->cancel_baton,
1205 &diff_editor, &diff_edit_baton, pool));
1207 /* We want to switch our txn into URL2 */
1208 SVN_ERR(svn_ra_do_diff3
1209 (drr.ra_session, &reporter, &report_baton, drr.rev2, drr.target1,
1210 diff_param->depth, diff_param->ignore_ancestry, TRUE,
1211 drr.url2, diff_editor, diff_edit_baton, pool));
1213 /* Drive the reporter; do the diff. */
1214 SVN_ERR(reporter->set_path(report_baton, "", drr.rev1,
1215 svn_depth_infinity,
1216 FALSE, NULL,
1217 pool));
1218 SVN_ERR(reporter->finish_report(report_baton, pool));
1220 return SVN_NO_ERROR;
1224 /* Perform a diff between a repository path and a working-copy path.
1226 PATH1 may be either a URL or a working copy path. PATH2 is a
1227 working copy path. REVISION1 and REVISION2 are their respective
1228 revisions. If REVERSE is TRUE, the diff will be done in reverse.
1229 If PEG_REVISION is specified, then PATH1 is the path in the peg
1230 revision, and the actual repository path to be compared is
1231 determined by following copy history.
1233 All other options are the same as those passed to svn_client_diff4(). */
1234 static svn_error_t *
1235 diff_repos_wc(const apr_array_header_t *options,
1236 const char *path1,
1237 const svn_opt_revision_t *revision1,
1238 const svn_opt_revision_t *peg_revision,
1239 const char *path2,
1240 const svn_opt_revision_t *revision2,
1241 svn_boolean_t reverse,
1242 svn_depth_t depth,
1243 svn_boolean_t ignore_ancestry,
1244 const apr_array_header_t *changelists,
1245 const svn_wc_diff_callbacks2_t *callbacks,
1246 struct diff_cmd_baton *callback_baton,
1247 svn_client_ctx_t *ctx,
1248 apr_pool_t *pool)
1250 const char *url1, *anchor, *anchor_url, *target;
1251 svn_wc_adm_access_t *adm_access, *dir_access;
1252 const svn_wc_entry_t *entry;
1253 svn_revnum_t rev;
1254 svn_ra_session_t *ra_session;
1255 const svn_ra_reporter3_t *reporter;
1256 void *report_baton;
1257 const svn_delta_editor_t *diff_editor;
1258 void *diff_edit_baton;
1259 svn_boolean_t rev2_is_base = (revision2->kind == svn_opt_revision_base);
1260 int levels_to_lock = SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth);
1261 svn_boolean_t server_supports_depth;
1263 /* Assert that we have valid input. */
1264 assert(! svn_path_is_url(path2));
1266 /* Convert path1 to a URL to feed to do_diff. */
1267 SVN_ERR(convert_to_url(&url1, path1, pool));
1269 SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target,
1270 path2, FALSE, levels_to_lock,
1271 ctx->cancel_func, ctx->cancel_baton,
1272 pool));
1273 anchor = svn_wc_adm_access_path(adm_access);
1275 /* Fetch the URL of the anchor directory. */
1276 SVN_ERR(svn_wc__entry_versioned(&entry, anchor, adm_access, FALSE, pool));
1277 if (! entry->url)
1278 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
1279 _("Directory '%s' has no URL"),
1280 svn_path_local_style(anchor, pool));
1281 anchor_url = apr_pstrdup(pool, entry->url);
1283 /* If we are performing a pegged diff, we need to find out what our
1284 actual URLs will be. */
1285 if (peg_revision->kind != svn_opt_revision_unspecified)
1287 svn_opt_revision_t *start_ignore, *end_ignore, end;
1288 const char *url_ignore;
1290 end.kind = svn_opt_revision_unspecified;
1292 SVN_ERR(svn_client__repos_locations(&url1, &start_ignore,
1293 &url_ignore, &end_ignore,
1294 NULL,
1295 path1,
1296 peg_revision,
1297 revision1, &end,
1298 ctx, pool));
1300 if (!reverse)
1302 callback_baton->orig_path_1 = url1;
1303 callback_baton->orig_path_2 = svn_path_join(anchor_url, target, pool);
1305 else
1307 callback_baton->orig_path_1 = svn_path_join(anchor_url, target, pool);
1308 callback_baton->orig_path_2 = url1;
1312 /* Establish RA session to path2's anchor */
1313 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, anchor_url,
1314 NULL, NULL, NULL, FALSE, TRUE,
1315 ctx, pool));
1317 SVN_ERR(svn_wc_get_diff_editor4(adm_access, target,
1318 callbacks, callback_baton,
1319 depth,
1320 ignore_ancestry,
1321 rev2_is_base,
1322 reverse,
1323 ctx->cancel_func, ctx->cancel_baton,
1324 changelists,
1325 &diff_editor, &diff_edit_baton,
1326 pool));
1328 /* Tell the RA layer we want a delta to change our txn to URL1 */
1329 SVN_ERR(svn_client__get_revision_number
1330 (&rev, NULL, ra_session, revision1,
1331 (path1 == url1) ? NULL : path1, pool));
1333 if (!reverse)
1334 callback_baton->revnum1 = rev;
1335 else
1336 callback_baton->revnum2 = rev;
1338 SVN_ERR(svn_ra_do_diff3(ra_session,
1339 &reporter, &report_baton,
1340 rev,
1341 target ? svn_path_uri_decode(target, pool) : NULL,
1342 depth,
1343 ignore_ancestry,
1344 TRUE, /* text_deltas */
1345 url1,
1346 diff_editor, diff_edit_baton, pool));
1348 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
1349 SVN_RA_CAPABILITY_DEPTH, pool));
1351 /* Create a txn mirror of path2; the diff editor will print
1352 diffs in reverse. :-) */
1353 SVN_ERR(svn_wc_crawl_revisions3(path2, dir_access,
1354 reporter, report_baton,
1355 FALSE, depth, (! server_supports_depth),
1356 FALSE, NULL, NULL, /* notification is N/A */
1357 NULL, pool));
1359 SVN_ERR(svn_wc_adm_close(adm_access));
1360 return SVN_NO_ERROR;
1364 /* This is basically just the guts of svn_client_diff[_peg]3(). */
1365 static svn_error_t *
1366 do_diff(const struct diff_parameters *diff_param,
1367 const svn_wc_diff_callbacks2_t *callbacks,
1368 struct diff_cmd_baton *callback_baton,
1369 svn_client_ctx_t *ctx,
1370 apr_pool_t *pool)
1372 struct diff_paths diff_paths;
1374 /* Check if paths/revisions are urls/local. */
1375 SVN_ERR(check_paths(diff_param, &diff_paths));
1377 if (diff_paths.is_repos1)
1379 if (diff_paths.is_repos2)
1381 SVN_ERR(diff_repos_repos(diff_param, callbacks, callback_baton,
1382 ctx, pool));
1384 else /* path2 is a working copy path */
1386 SVN_ERR(diff_repos_wc(diff_param->options,
1387 diff_param->path1, diff_param->revision1,
1388 diff_param->peg_revision,
1389 diff_param->path2, diff_param->revision2,
1390 FALSE, diff_param->depth,
1391 diff_param->ignore_ancestry,
1392 diff_param->changelists,
1393 callbacks, callback_baton, ctx, pool));
1396 else /* path1 is a working copy path */
1398 if (diff_paths.is_repos2)
1400 SVN_ERR(diff_repos_wc(diff_param->options,
1401 diff_param->path2, diff_param->revision2,
1402 diff_param->peg_revision,
1403 diff_param->path1, diff_param->revision1,
1404 TRUE, diff_param->depth,
1405 diff_param->ignore_ancestry,
1406 diff_param->changelists,
1407 callbacks, callback_baton, ctx, pool));
1409 else /* path2 is a working copy path */
1411 SVN_ERR(diff_wc_wc(diff_param->options,
1412 diff_param->path1, diff_param->revision1,
1413 diff_param->path2, diff_param->revision2,
1414 diff_param->depth,
1415 diff_param->ignore_ancestry,
1416 diff_param->changelists,
1417 callbacks, callback_baton, ctx, pool));
1421 return SVN_NO_ERROR;
1424 /* Perform a diff summary between two repository paths. */
1425 static svn_error_t *
1426 diff_summarize_repos_repos(const struct diff_parameters *diff_param,
1427 svn_client_diff_summarize_func_t summarize_func,
1428 void *summarize_baton,
1429 svn_client_ctx_t *ctx,
1430 apr_pool_t *pool)
1432 svn_ra_session_t *extra_ra_session;
1434 const svn_ra_reporter3_t *reporter;
1435 void *report_baton;
1437 const svn_delta_editor_t *diff_editor;
1438 void *diff_edit_baton;
1440 struct diff_repos_repos_t drr;
1442 /* Prepare info for the repos repos diff. */
1443 SVN_ERR(diff_prepare_repos_repos(diff_param, &drr, ctx, pool));
1445 /* Now, we open an extra RA session to the correct anchor
1446 location for URL1. This is used to get the kind of deleted paths. */
1447 SVN_ERR(svn_client__open_ra_session_internal
1448 (&extra_ra_session, drr.anchor1, NULL, NULL, NULL, FALSE, TRUE,
1449 ctx, pool));
1451 /* Set up the repos_diff editor. */
1452 SVN_ERR(svn_client__get_diff_summarize_editor
1453 (drr.target2, summarize_func,
1454 summarize_baton, extra_ra_session, drr.rev1, ctx->cancel_func,
1455 ctx->cancel_baton, &diff_editor, &diff_edit_baton, pool));
1457 /* We want to switch our txn into URL2 */
1458 SVN_ERR(svn_ra_do_diff3
1459 (drr.ra_session, &reporter, &report_baton, drr.rev2, drr.target1,
1460 diff_param->depth, diff_param->ignore_ancestry,
1461 FALSE /* do not create text delta */, drr.url2, diff_editor,
1462 diff_edit_baton, pool));
1464 /* Drive the reporter; do the diff. */
1465 SVN_ERR(reporter->set_path(report_baton, "", drr.rev1,
1466 svn_depth_infinity,
1467 FALSE, NULL, pool));
1468 SVN_ERR(reporter->finish_report(report_baton, pool));
1470 return SVN_NO_ERROR;
1473 /* This is basically just the guts of svn_client_diff_summarize[_peg](). */
1474 static svn_error_t *
1475 do_diff_summarize(const struct diff_parameters *diff_param,
1476 svn_client_diff_summarize_func_t summarize_func,
1477 void *summarize_baton,
1478 svn_client_ctx_t *ctx,
1479 apr_pool_t *pool)
1481 struct diff_paths diff_paths;
1483 /* Check if paths/revisions are urls/local. */
1484 SVN_ERR(check_paths(diff_param, &diff_paths));
1486 if (diff_paths.is_repos1 && diff_paths.is_repos2)
1488 SVN_ERR(diff_summarize_repos_repos(diff_param, summarize_func,
1489 summarize_baton, ctx, pool));
1491 else
1492 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1493 _("Summarizing diff can only compare repository "
1494 "to repository"));
1496 return SVN_NO_ERROR;
1500 /*----------------------------------------------------------------------- */
1502 /*** Public Interfaces. ***/
1504 /* Display context diffs between two PATH/REVISION pairs. Each of
1505 these inputs will be one of the following:
1507 - a repository URL at a given revision.
1508 - a working copy path, ignoring local mods.
1509 - a working copy path, including local mods.
1511 We can establish a matrix that shows the nine possible types of
1512 diffs we expect to support.
1515 ` . DST || URL:rev | WC:base | WC:working |
1516 ` . || | | |
1517 SRC ` . || | | |
1518 ============++============+============+============+
1519 URL:rev || (*) | (*) | (*) |
1520 || | | |
1521 || | | |
1522 || | | |
1523 ------------++------------+------------+------------+
1524 WC:base || (*) | |
1525 || | New svn_wc_diff which |
1526 || | is smart enough to |
1527 || | handle two WC paths |
1528 ------------++------------+ and their related +
1529 WC:working || (*) | text-bases and working |
1530 || | files. This operation |
1531 || | is entirely local. |
1532 || | |
1533 ------------++------------+------------+------------+
1534 * These cases require server communication.
1536 svn_error_t *
1537 svn_client_diff4(const apr_array_header_t *options,
1538 const char *path1,
1539 const svn_opt_revision_t *revision1,
1540 const char *path2,
1541 const svn_opt_revision_t *revision2,
1542 const char *relative_to_dir,
1543 svn_depth_t depth,
1544 svn_boolean_t ignore_ancestry,
1545 svn_boolean_t no_diff_deleted,
1546 svn_boolean_t ignore_content_type,
1547 const char *header_encoding,
1548 apr_file_t *outfile,
1549 apr_file_t *errfile,
1550 const apr_array_header_t *changelists,
1551 svn_client_ctx_t *ctx,
1552 apr_pool_t *pool)
1554 struct diff_parameters diff_params;
1556 struct diff_cmd_baton diff_cmd_baton;
1557 svn_wc_diff_callbacks2_t diff_callbacks;
1559 /* We will never do a pegged diff from here. */
1560 svn_opt_revision_t peg_revision;
1561 peg_revision.kind = svn_opt_revision_unspecified;
1563 /* fill diff_param */
1564 diff_params.options = options;
1565 diff_params.path1 = path1;
1566 diff_params.revision1 = revision1;
1567 diff_params.path2 = path2;
1568 diff_params.revision2 = revision2;
1569 diff_params.peg_revision = &peg_revision;
1570 diff_params.depth = depth;
1571 diff_params.ignore_ancestry = ignore_ancestry;
1572 diff_params.no_diff_deleted = no_diff_deleted;
1573 diff_params.changelists = changelists;
1575 /* setup callback and baton */
1576 diff_callbacks.file_changed = diff_file_changed;
1577 diff_callbacks.file_added = diff_file_added;
1578 diff_callbacks.file_deleted = no_diff_deleted ? diff_file_deleted_no_diff :
1579 diff_file_deleted_with_diff;
1580 diff_callbacks.dir_added = diff_dir_added;
1581 diff_callbacks.dir_deleted = diff_dir_deleted;
1582 diff_callbacks.dir_props_changed = diff_props_changed;
1584 diff_cmd_baton.orig_path_1 = path1;
1585 diff_cmd_baton.orig_path_2 = path2;
1587 diff_cmd_baton.options = options;
1588 diff_cmd_baton.pool = pool;
1589 diff_cmd_baton.outfile = outfile;
1590 diff_cmd_baton.errfile = errfile;
1591 diff_cmd_baton.header_encoding = header_encoding;
1592 diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
1593 diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
1595 diff_cmd_baton.config = ctx->config;
1596 diff_cmd_baton.force_empty = FALSE;
1597 diff_cmd_baton.force_binary = ignore_content_type;
1598 diff_cmd_baton.relative_to_dir = relative_to_dir;
1600 return do_diff(&diff_params, &diff_callbacks, &diff_cmd_baton, ctx, pool);
1603 svn_error_t *
1604 svn_client_diff3(const apr_array_header_t *options,
1605 const char *path1,
1606 const svn_opt_revision_t *revision1,
1607 const char *path2,
1608 const svn_opt_revision_t *revision2,
1609 svn_boolean_t recurse,
1610 svn_boolean_t ignore_ancestry,
1611 svn_boolean_t no_diff_deleted,
1612 svn_boolean_t ignore_content_type,
1613 const char *header_encoding,
1614 apr_file_t *outfile,
1615 apr_file_t *errfile,
1616 svn_client_ctx_t *ctx,
1617 apr_pool_t *pool)
1619 return svn_client_diff4(options, path1, revision1, path2,
1620 revision2, NULL,
1621 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1622 ignore_ancestry, no_diff_deleted,
1623 ignore_content_type, header_encoding,
1624 outfile, errfile, NULL, ctx, pool);
1627 svn_error_t *
1628 svn_client_diff2(const apr_array_header_t *options,
1629 const char *path1,
1630 const svn_opt_revision_t *revision1,
1631 const char *path2,
1632 const svn_opt_revision_t *revision2,
1633 svn_boolean_t recurse,
1634 svn_boolean_t ignore_ancestry,
1635 svn_boolean_t no_diff_deleted,
1636 svn_boolean_t ignore_content_type,
1637 apr_file_t *outfile,
1638 apr_file_t *errfile,
1639 svn_client_ctx_t *ctx,
1640 apr_pool_t *pool)
1642 return svn_client_diff3(options, path1, revision1, path2, revision2,
1643 recurse, ignore_ancestry, no_diff_deleted,
1644 ignore_content_type, SVN_APR_LOCALE_CHARSET,
1645 outfile, errfile, ctx, pool);
1648 svn_error_t *
1649 svn_client_diff(const apr_array_header_t *options,
1650 const char *path1,
1651 const svn_opt_revision_t *revision1,
1652 const char *path2,
1653 const svn_opt_revision_t *revision2,
1654 svn_boolean_t recurse,
1655 svn_boolean_t ignore_ancestry,
1656 svn_boolean_t no_diff_deleted,
1657 apr_file_t *outfile,
1658 apr_file_t *errfile,
1659 svn_client_ctx_t *ctx,
1660 apr_pool_t *pool)
1662 return svn_client_diff2(options, path1, revision1, path2, revision2,
1663 recurse, ignore_ancestry, no_diff_deleted, FALSE,
1664 outfile, errfile, ctx, pool);
1667 svn_error_t *
1668 svn_client_diff_peg4(const apr_array_header_t *options,
1669 const char *path,
1670 const svn_opt_revision_t *peg_revision,
1671 const svn_opt_revision_t *start_revision,
1672 const svn_opt_revision_t *end_revision,
1673 const char *relative_to_dir,
1674 svn_depth_t depth,
1675 svn_boolean_t ignore_ancestry,
1676 svn_boolean_t no_diff_deleted,
1677 svn_boolean_t ignore_content_type,
1678 const char *header_encoding,
1679 apr_file_t *outfile,
1680 apr_file_t *errfile,
1681 const apr_array_header_t *changelists,
1682 svn_client_ctx_t *ctx,
1683 apr_pool_t *pool)
1685 struct diff_parameters diff_params;
1687 struct diff_cmd_baton diff_cmd_baton;
1688 svn_wc_diff_callbacks2_t diff_callbacks;
1690 if (svn_path_is_url(path) &&
1691 (start_revision->kind == svn_opt_revision_base
1692 || end_revision->kind == svn_opt_revision_base) )
1693 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1694 _("Revision type requires a working copy "
1695 "path, not a URL"));
1697 /* fill diff_param */
1698 diff_params.options = options;
1699 diff_params.path1 = path;
1700 diff_params.revision1 = start_revision;
1701 diff_params.path2 = path;
1702 diff_params.revision2 = end_revision;
1703 diff_params.peg_revision = peg_revision;
1704 diff_params.depth = depth;
1705 diff_params.ignore_ancestry = ignore_ancestry;
1706 diff_params.no_diff_deleted = no_diff_deleted;
1707 diff_params.changelists = changelists;
1709 /* setup callback and baton */
1710 diff_callbacks.file_changed = diff_file_changed;
1711 diff_callbacks.file_added = diff_file_added;
1712 diff_callbacks.file_deleted = no_diff_deleted ? diff_file_deleted_no_diff :
1713 diff_file_deleted_with_diff;
1714 diff_callbacks.dir_added = diff_dir_added;
1715 diff_callbacks.dir_deleted = diff_dir_deleted;
1716 diff_callbacks.dir_props_changed = diff_props_changed;
1718 diff_cmd_baton.orig_path_1 = path;
1719 diff_cmd_baton.orig_path_2 = path;
1721 diff_cmd_baton.options = options;
1722 diff_cmd_baton.pool = pool;
1723 diff_cmd_baton.outfile = outfile;
1724 diff_cmd_baton.errfile = errfile;
1725 diff_cmd_baton.header_encoding = header_encoding;
1726 diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
1727 diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
1729 diff_cmd_baton.config = ctx->config;
1730 diff_cmd_baton.force_empty = FALSE;
1731 diff_cmd_baton.force_binary = ignore_content_type;
1732 diff_cmd_baton.relative_to_dir = relative_to_dir;
1734 return do_diff(&diff_params, &diff_callbacks, &diff_cmd_baton, ctx, pool);
1737 svn_error_t *
1738 svn_client_diff_peg3(const apr_array_header_t *options,
1739 const char *path,
1740 const svn_opt_revision_t *peg_revision,
1741 const svn_opt_revision_t *start_revision,
1742 const svn_opt_revision_t *end_revision,
1743 svn_boolean_t recurse,
1744 svn_boolean_t ignore_ancestry,
1745 svn_boolean_t no_diff_deleted,
1746 svn_boolean_t ignore_content_type,
1747 const char *header_encoding,
1748 apr_file_t *outfile,
1749 apr_file_t *errfile,
1750 svn_client_ctx_t *ctx,
1751 apr_pool_t *pool)
1753 return svn_client_diff_peg4(options,
1754 path,
1755 peg_revision,
1756 start_revision,
1757 end_revision,
1758 NULL,
1759 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1760 ignore_ancestry,
1761 no_diff_deleted,
1762 ignore_content_type,
1763 header_encoding,
1764 outfile,
1765 errfile,
1766 NULL,
1767 ctx,
1768 pool);
1771 svn_error_t *
1772 svn_client_diff_peg2(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 svn_boolean_t ignore_content_type,
1781 apr_file_t *outfile,
1782 apr_file_t *errfile,
1783 svn_client_ctx_t *ctx,
1784 apr_pool_t *pool)
1786 return svn_client_diff_peg3(options, path, peg_revision, start_revision,
1787 end_revision,
1788 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1789 ignore_ancestry, no_diff_deleted,
1790 ignore_content_type, SVN_APR_LOCALE_CHARSET,
1791 outfile, errfile, ctx, pool);
1794 svn_error_t *
1795 svn_client_diff_peg(const apr_array_header_t *options,
1796 const char *path,
1797 const svn_opt_revision_t *peg_revision,
1798 const svn_opt_revision_t *start_revision,
1799 const svn_opt_revision_t *end_revision,
1800 svn_boolean_t recurse,
1801 svn_boolean_t ignore_ancestry,
1802 svn_boolean_t no_diff_deleted,
1803 apr_file_t *outfile,
1804 apr_file_t *errfile,
1805 svn_client_ctx_t *ctx,
1806 apr_pool_t *pool)
1808 return svn_client_diff_peg2(options, path, peg_revision,
1809 start_revision, end_revision, recurse,
1810 ignore_ancestry, no_diff_deleted, FALSE,
1811 outfile, errfile, ctx, pool);
1814 svn_error_t *
1815 svn_client_diff_summarize2(const char *path1,
1816 const svn_opt_revision_t *revision1,
1817 const char *path2,
1818 const svn_opt_revision_t *revision2,
1819 svn_depth_t depth,
1820 svn_boolean_t ignore_ancestry,
1821 const apr_array_header_t *changelists,
1822 svn_client_diff_summarize_func_t summarize_func,
1823 void *summarize_baton,
1824 svn_client_ctx_t *ctx,
1825 apr_pool_t *pool)
1827 struct diff_parameters diff_params;
1829 /* We will never do a pegged diff from here. */
1830 svn_opt_revision_t peg_revision;
1831 peg_revision.kind = svn_opt_revision_unspecified;
1833 /* fill diff_param */
1834 diff_params.options = NULL;
1835 diff_params.path1 = path1;
1836 diff_params.revision1 = revision1;
1837 diff_params.path2 = path2;
1838 diff_params.revision2 = revision2;
1839 diff_params.peg_revision = &peg_revision;
1840 diff_params.depth = depth;
1841 diff_params.ignore_ancestry = ignore_ancestry;
1842 diff_params.no_diff_deleted = FALSE;
1843 diff_params.changelists = changelists;
1845 return do_diff_summarize(&diff_params, summarize_func, summarize_baton,
1846 ctx, pool);
1849 svn_error_t *
1850 svn_client_diff_summarize(const char *path1,
1851 const svn_opt_revision_t *revision1,
1852 const char *path2,
1853 const svn_opt_revision_t *revision2,
1854 svn_boolean_t recurse,
1855 svn_boolean_t ignore_ancestry,
1856 svn_client_diff_summarize_func_t summarize_func,
1857 void *summarize_baton,
1858 svn_client_ctx_t *ctx,
1859 apr_pool_t *pool)
1861 return svn_client_diff_summarize2(path1, revision1, path2,
1862 revision2,
1863 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1864 ignore_ancestry, NULL, summarize_func,
1865 summarize_baton, ctx, pool);
1868 svn_error_t *
1869 svn_client_diff_summarize_peg2(const char *path,
1870 const svn_opt_revision_t *peg_revision,
1871 const svn_opt_revision_t *start_revision,
1872 const svn_opt_revision_t *end_revision,
1873 svn_depth_t depth,
1874 svn_boolean_t ignore_ancestry,
1875 const apr_array_header_t *changelists,
1876 svn_client_diff_summarize_func_t summarize_func,
1877 void *summarize_baton,
1878 svn_client_ctx_t *ctx,
1879 apr_pool_t *pool)
1881 struct diff_parameters diff_params;
1883 /* fill diff_param */
1884 diff_params.options = NULL;
1885 diff_params.path1 = path;
1886 diff_params.revision1 = start_revision;
1887 diff_params.path2 = path;
1888 diff_params.revision2 = end_revision;
1889 diff_params.peg_revision = peg_revision;
1890 diff_params.depth = depth;
1891 diff_params.ignore_ancestry = ignore_ancestry;
1892 diff_params.no_diff_deleted = FALSE;
1893 diff_params.changelists = changelists;
1895 return do_diff_summarize(&diff_params, summarize_func, summarize_baton,
1896 ctx, pool);
1900 svn_error_t *
1901 svn_client_diff_summarize_peg(const char *path,
1902 const svn_opt_revision_t *peg_revision,
1903 const svn_opt_revision_t *start_revision,
1904 const svn_opt_revision_t *end_revision,
1905 svn_boolean_t recurse,
1906 svn_boolean_t ignore_ancestry,
1907 svn_client_diff_summarize_func_t summarize_func,
1908 void *summarize_baton,
1909 svn_client_ctx_t *ctx,
1910 apr_pool_t *pool)
1912 return svn_client_diff_summarize_peg2(path, peg_revision,
1913 start_revision, end_revision,
1914 SVN_DEPTH_INFINITY_OR_FILES(recurse),
1915 ignore_ancestry, NULL,
1916 summarize_func, summarize_baton,
1917 ctx, pool);
1920 svn_client_diff_summarize_t *
1921 svn_client_diff_summarize_dup(const svn_client_diff_summarize_t *diff,
1922 apr_pool_t *pool)
1924 svn_client_diff_summarize_t *dup_diff = apr_palloc(pool, sizeof(*dup_diff));
1926 *dup_diff = *diff;
1928 if (diff->path)
1929 dup_diff->path = apr_pstrdup(pool, diff->path);
1931 return dup_diff;