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 /* ==================================================================== */
25 #include <apr_strings.h>
26 #include <apr_pools.h>
28 #include "svn_types.h"
31 #include "svn_delta.h"
33 #include "svn_mergeinfo.h"
34 #include "svn_client.h"
35 #include "svn_string.h"
36 #include "svn_error.h"
40 #include "svn_pools.h"
41 #include "svn_config.h"
42 #include "svn_props.h"
44 #include "svn_sorts.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 /*-----------------------------------------------------------------*/
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. */
70 file_printf_from_utf8(apr_file_t
*fptr
, const char *encoding
,
71 const char *format
, ...)
72 __attribute__ ((format(printf
, 3, 4)));
74 file_printf_from_utf8(apr_file_t
*fptr
, const char *encoding
,
75 const char *format
, ...)
78 const char *buf
, *buf_apr
;
81 buf
= apr_pvsprintf(apr_file_pool_get(fptr
), format
, 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
97 display_mergeinfo_diff(const char *old_mergeinfo_val
,
98 const char *new_mergeinfo_val
,
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
));
111 old_mergeinfo_hash
= NULL
;
113 if (new_mergeinfo_val
)
114 SVN_ERR(svn_mergeinfo_parse(&new_mergeinfo_hash
, new_mergeinfo_val
, pool
));
116 new_mergeinfo_hash
= NULL
;
118 SVN_ERR(svn_mergeinfo_diff(&deleted
, &added
, old_mergeinfo_hash
,
122 for (hi
= apr_hash_first(pool
, deleted
);
123 hi
; hi
= apr_hash_next(hi
))
127 svn_string_t
*merge_revstr
;
129 apr_hash_this(hi
, &key
, NULL
, &val
);
131 merge_revarray
= val
;
133 SVN_ERR(svn_rangelist_to_string(&merge_revstr
, merge_revarray
, pool
));
135 SVN_ERR(file_printf_from_utf8(file
, encoding
,
136 _(" Reverse-merged %s:r%s%s"),
137 from_path
, merge_revstr
->data
,
141 for (hi
= apr_hash_first(pool
, added
);
142 hi
; hi
= apr_hash_next(hi
))
146 svn_string_t
*merge_revstr
;
148 apr_hash_this(hi
, &key
, NULL
, &val
);
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
,
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. */
172 display_prop_diffs(const apr_array_header_t
*propchanges
,
173 apr_hash_t
*original_props
,
175 const char *encoding
,
177 const char *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
);
189 else if (!svn_path_compare_paths(relative_to_dir
, path
))
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"),
198 svn_path_local_style(path
, pool
),
201 SVN_ERR(file_printf_from_utf8(file
, encoding
, "%s" APR_EOL_STR
,
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
);
212 original_value
= apr_hash_get(original_props
,
213 propchange
->name
, APR_HASH_KEY_STRING
);
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
)))
224 if (! original_value
)
225 header_fmt
= _("Added: %s%s");
226 else if (! propchange
->value
)
227 header_fmt
= _("Deleted: %s%s");
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
));
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
)
254 SVN_ERR(file_printf_from_utf8
256 " - %s" APR_EOL_STR
, original_value
->data
));
260 /* ### todo: check for error? */
262 (file
, " - %s" APR_EOL_STR
, original_value
->data
);
266 if (propchange
->value
!= NULL
)
270 SVN_ERR(file_printf_from_utf8
271 (file
, encoding
, " + %s" APR_EOL_STR
,
272 propchange
->value
->data
));
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
);
294 /*-----------------------------------------------------------------*/
296 /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
299 struct diff_cmd_baton
{
301 /* If non-null, the external diff command to invoke. */
302 const char *diff_cmd
;
304 /* This is allocated in this struct's pool or a higher-up pool. */
306 /* If 'diff_cmd' is null, then this is the parsed options to
307 pass to the internal libsvn_diff implementation. */
308 svn_diff_file_options_t
*for_internal
;
309 /* Else if 'diff_cmd' is non-null, then... */
311 /* ...this is an argument array for the external command, and */
313 /* ...this is the length of argv. */
322 const char *header_encoding
;
324 /* The original targets passed to the diff command. We may need
325 these to construct distinctive diff labels when comparing the
326 same relative path in the same revision, under different anchors
327 (for example, when comparing a trunk against a branch). */
328 const char *orig_path_1
;
329 const char *orig_path_2
;
331 /* These are the numeric representations of the revisions passed to
332 svn_client_diff4, either may be SVN_INVALID_REVNUM. We need these
333 because some of the svn_wc_diff_callbacks3_t don't get revision
336 ### Perhaps we should change the callback signatures and eliminate
339 svn_revnum_t revnum1
;
340 svn_revnum_t revnum2
;
342 /* Set this if you want diff output even for binary files. */
343 svn_boolean_t force_binary
;
345 /* Set this flag if you want diff_file_changed to output diffs
346 unconditionally, even if the diffs are empty. */
347 svn_boolean_t force_empty
;
349 /* The directory that diff target paths should be considered as
350 relative to for output generation (see issue #2723). */
351 const char *relative_to_dir
;
355 /* Generate a label for the diff output for file PATH at revision REVNUM.
356 If REVNUM is invalid then it is assumed to be the current working
357 copy. Assumes the paths are already in the desired style (local
358 vs internal). Allocate the label in POOL. */
360 diff_label(const char *path
,
365 if (revnum
!= SVN_INVALID_REVNUM
)
366 label
= apr_psprintf(pool
, _("%s\t(revision %ld)"), path
, revnum
);
368 label
= apr_psprintf(pool
, _("%s\t(working copy)"), path
);
373 /* An svn_wc_diff_callbacks3_t function. Used for both file and directory
376 diff_props_changed(svn_wc_adm_access_t
*adm_access
,
377 svn_wc_notify_state_t
*state
,
379 const apr_array_header_t
*propchanges
,
380 apr_hash_t
*original_props
,
383 struct diff_cmd_baton
*diff_cmd_baton
= diff_baton
;
384 apr_array_header_t
*props
;
385 apr_pool_t
*subpool
= svn_pool_create(diff_cmd_baton
->pool
);
387 SVN_ERR(svn_categorize_props(propchanges
, NULL
, NULL
, &props
, subpool
));
389 if (props
->nelts
> 0)
390 SVN_ERR(display_prop_diffs(props
, original_props
, path
,
391 diff_cmd_baton
->header_encoding
,
392 diff_cmd_baton
->outfile
,
393 diff_cmd_baton
->relative_to_dir
,
397 *state
= svn_wc_notify_state_unknown
;
399 svn_pool_destroy(subpool
);
403 /* Show differences between TMPFILE1 and TMPFILE2. PATH, REV1, and REV2 are
404 used in the headers to indicate the file and revisions. If either
405 MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
406 but instead print a warning message. */
408 diff_content_changed(const char *path
,
409 const char *tmpfile1
,
410 const char *tmpfile2
,
413 const char *mimetype1
,
414 const char *mimetype2
,
417 struct diff_cmd_baton
*diff_cmd_baton
= diff_baton
;
419 apr_pool_t
*subpool
= svn_pool_create(diff_cmd_baton
->pool
);
421 const char *rel_to_dir
= diff_cmd_baton
->relative_to_dir
;
422 apr_file_t
*errfile
= diff_cmd_baton
->errfile
;
423 const char *label1
, *label2
;
424 svn_boolean_t mt1_binary
= FALSE
, mt2_binary
= FALSE
;
425 const char *path1
, *path2
;
428 /* Get a stream from our output file. */
429 os
= svn_stream_from_aprfile(diff_cmd_baton
->outfile
, subpool
);
431 /* Generate the diff headers. */
433 /* ### Holy cow. Due to anchor/target weirdness, we can't
434 simply join diff_cmd_baton->orig_path_1 with path, ditto for
435 orig_path_2. That will work when they're directory URLs, but
436 not for file URLs. Nor can we just use anchor1 and anchor2
437 from do_diff(), at least not without some more logic here.
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
--)
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
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
);
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
);
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
);
487 else if (!svn_path_compare_paths(rel_to_dir
, path
))
490 return MAKE_ERR_BAD_RELATIVE_PATH(path
, rel_to_dir
);
492 child_path
= svn_path_is_child(rel_to_dir
, path1
, subpool
);
496 else if (!svn_path_compare_paths(rel_to_dir
, path1
))
499 return MAKE_ERR_BAD_RELATIVE_PATH(path1
, rel_to_dir
);
501 child_path
= svn_path_is_child(rel_to_dir
, path2
, subpool
);
505 else if (!svn_path_compare_paths(rel_to_dir
, path2
))
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. */
518 mt1_binary
= svn_mime_type_is_binary(mimetype1
);
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"),
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
,
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
));
557 svn_pool_destroy(subpool
);
562 if (diff_cmd_baton
->diff_cmd
)
564 /* Print out the diff header. */
565 SVN_ERR(svn_stream_printf_from_utf8
566 (os
, diff_cmd_baton
->header_encoding
, subpool
,
567 "Index: %s" APR_EOL_STR
"%s" APR_EOL_STR
, path
, equal_string
));
568 /* Close the stream (flush) */
569 SVN_ERR(svn_stream_close(os
));
571 SVN_ERR(svn_io_run_diff(".",
572 diff_cmd_baton
->options
.for_external
.argv
,
573 diff_cmd_baton
->options
.for_external
.argc
,
576 &exitcode
, diff_cmd_baton
->outfile
, errfile
,
577 diff_cmd_baton
->diff_cmd
, subpool
));
579 else /* use libsvn_diff to generate the diff */
583 SVN_ERR(svn_diff_file_diff_2(&diff
, tmpfile1
, tmpfile2
,
584 diff_cmd_baton
->options
.for_internal
,
587 if (svn_diff_contains_diffs(diff
) || diff_cmd_baton
->force_empty
)
589 /* Print out the diff header. */
590 SVN_ERR(svn_stream_printf_from_utf8
591 (os
, diff_cmd_baton
->header_encoding
, subpool
,
592 "Index: %s" APR_EOL_STR
"%s" APR_EOL_STR
,
593 path
, equal_string
));
594 /* Output the actual diff */
595 SVN_ERR(svn_diff_file_output_unified3
596 (os
, diff
, tmpfile1
, tmpfile2
, label1
, label2
,
597 diff_cmd_baton
->header_encoding
, rel_to_dir
,
598 diff_cmd_baton
->options
.for_internal
->show_c_function
,
603 /* ### todo: someday we'll need to worry about whether we're going
604 to need to write a diff plug-in mechanism that makes use of the
605 two paths, instead of just blindly running SVN_CLIENT_DIFF. */
607 /* Destroy the subpool. */
608 svn_pool_destroy(subpool
);
613 /* An svn_wc_diff_callbacks3_t function. */
615 diff_file_changed(svn_wc_adm_access_t
*adm_access
,
616 svn_wc_notify_state_t
*content_state
,
617 svn_wc_notify_state_t
*prop_state
,
619 const char *tmpfile1
,
620 const char *tmpfile2
,
623 const char *mimetype1
,
624 const char *mimetype2
,
625 const apr_array_header_t
*prop_changes
,
626 apr_hash_t
*original_props
,
630 SVN_ERR(diff_content_changed(path
,
631 tmpfile1
, tmpfile2
, rev1
, rev2
,
632 mimetype1
, mimetype2
, diff_baton
));
633 if (prop_changes
->nelts
> 0)
634 SVN_ERR(diff_props_changed(adm_access
, prop_state
, path
, prop_changes
,
635 original_props
, diff_baton
));
637 *content_state
= svn_wc_notify_state_unknown
;
639 *prop_state
= svn_wc_notify_state_unknown
;
643 /* Because the repos-diff editor passes at least one empty file to
644 each of these next two functions, they can be dumb wrappers around
645 the main workhorse routine. */
647 /* An svn_wc_diff_callbacks3_t function. */
649 diff_file_added(svn_wc_adm_access_t
*adm_access
,
650 svn_wc_notify_state_t
*content_state
,
651 svn_wc_notify_state_t
*prop_state
,
653 const char *tmpfile1
,
654 const char *tmpfile2
,
657 const char *mimetype1
,
658 const char *mimetype2
,
659 const apr_array_header_t
*prop_changes
,
660 apr_hash_t
*original_props
,
663 struct diff_cmd_baton
*diff_cmd_baton
= diff_baton
;
665 /* We want diff_file_changed to unconditionally show diffs, even if
666 the diff is empty (as would be the case if an empty file were
667 added.) It's important, because 'patch' would still see an empty
668 diff and create an empty file. It's also important to let the
669 user see that *something* happened. */
670 diff_cmd_baton
->force_empty
= TRUE
;
672 SVN_ERR(diff_file_changed(adm_access
, content_state
, prop_state
, path
,
675 mimetype1
, mimetype2
,
676 prop_changes
, original_props
, diff_baton
));
678 diff_cmd_baton
->force_empty
= FALSE
;
683 /* An svn_wc_diff_callbacks3_t function. */
685 diff_file_deleted_with_diff(svn_wc_adm_access_t
*adm_access
,
686 svn_wc_notify_state_t
*state
,
688 const char *tmpfile1
,
689 const char *tmpfile2
,
690 const char *mimetype1
,
691 const char *mimetype2
,
692 apr_hash_t
*original_props
,
695 struct diff_cmd_baton
*diff_cmd_baton
= diff_baton
;
697 /* We don't list all the deleted properties. */
698 return diff_file_changed(adm_access
, state
, NULL
, path
,
700 diff_cmd_baton
->revnum1
, diff_cmd_baton
->revnum2
,
701 mimetype1
, mimetype2
,
702 apr_array_make(diff_cmd_baton
->pool
, 1,
704 apr_hash_make(diff_cmd_baton
->pool
), diff_baton
);
707 /* An svn_wc_diff_callbacks3_t function. */
709 diff_file_deleted_no_diff(svn_wc_adm_access_t
*adm_access
,
710 svn_wc_notify_state_t
*state
,
712 const char *tmpfile1
,
713 const char *tmpfile2
,
714 const char *mimetype1
,
715 const char *mimetype2
,
716 apr_hash_t
*original_props
,
719 struct diff_cmd_baton
*diff_cmd_baton
= diff_baton
;
722 *state
= svn_wc_notify_state_unknown
;
724 SVN_ERR(file_printf_from_utf8
725 (diff_cmd_baton
->outfile
,
726 diff_cmd_baton
->header_encoding
,
727 "Index: %s (deleted)" APR_EOL_STR
"%s" APR_EOL_STR
,
728 path
, equal_string
));
733 /* An svn_wc_diff_callbacks3_t function.
734 For now, let's have 'svn diff' send feedback to the top-level
735 application, so that something reasonable about directories and
736 propsets gets printed to stdout. */
738 diff_dir_added(svn_wc_adm_access_t
*adm_access
,
739 svn_wc_notify_state_t
*state
,
745 *state
= svn_wc_notify_state_unknown
;
747 /* ### todo: send feedback to app */
751 /* An svn_wc_diff_callbacks3_t function. */
753 diff_dir_deleted(svn_wc_adm_access_t
*adm_access
,
754 svn_wc_notify_state_t
*state
,
759 *state
= svn_wc_notify_state_unknown
;
764 /* An svn_wc_diff_callbacks3_t function. */
766 diff_dir_opened(svn_wc_adm_access_t
*adm_access
,
774 /* An svn_wc_diff_callbacks3_t function. */
776 diff_dir_closed(svn_wc_adm_access_t
*adm_access
,
777 svn_wc_notify_state_t
*state
,
782 *state
= svn_wc_notify_state_unknown
;
788 /*-----------------------------------------------------------------*/
790 /** The logic behind 'svn diff' and 'svn merge'. */
793 /* Hi! This is a comment left behind by Karl, and Ben is too afraid
794 to erase it at this time, because he's not fully confident that all
795 this knowledge has been grokked yet.
797 There are five cases:
798 1. path is not an URL and start_revision != end_revision
799 2. path is not an URL and start_revision == end_revision
800 3. path is an URL and start_revision != end_revision
801 4. path is an URL and start_revision == end_revision
802 5. path is not an URL and no revisions given
804 With only one distinct revision the working copy provides the
805 other. When path is an URL there is no working copy. Thus
807 1: compare repository versions for URL coresponding to working copy
808 2: compare working copy against repository version
809 3: compare repository versions for URL
811 5: compare working copy against text-base
813 Case 4 is not as stupid as it looks, for example it may occur if
814 the user specifies two dates that resolve to the same revision. */
819 /* Helper function: given a working-copy PATH, return its associated
820 url in *URL, allocated in POOL. If PATH is *already* a URL, that's
821 fine, just set *URL = PATH. */
823 convert_to_url(const char **url
,
827 svn_wc_adm_access_t
*adm_access
; /* ### FIXME local */
828 const svn_wc_entry_t
*entry
;
830 if (svn_path_is_url(path
))
836 /* ### This may not be a good idea, see issue 880 */
837 SVN_ERR(svn_wc_adm_probe_open3(&adm_access
, NULL
, path
, FALSE
,
838 0, NULL
, NULL
, pool
));
839 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
, FALSE
, pool
));
840 SVN_ERR(svn_wc_adm_close(adm_access
));
843 *url
= apr_pstrdup(pool
, entry
->url
);
845 *url
= apr_pstrdup(pool
, entry
->copyfrom_url
);
849 /** Helper structure: for passing around the diff parameters */
850 struct diff_parameters
852 /* First input path */
855 /* Revision of first input path */
856 const svn_opt_revision_t
*revision1
;
858 /* Second input path */
861 /* Revision of second input path */
862 const svn_opt_revision_t
*revision2
;
865 const svn_opt_revision_t
*peg_revision
;
870 /* Ignore ancestry */
871 svn_boolean_t ignore_ancestry
;
874 svn_boolean_t no_diff_deleted
;
876 /* Changelists of interest */
877 const apr_array_header_t
*changelists
;
880 /** Helper structure: filled by check_paths() */
883 /* path1 can only be found in the repository? */
884 svn_boolean_t is_repos1
;
886 /* path2 can only be found in the repository? */
887 svn_boolean_t is_repos2
;
891 /** Check if paths are urls and if the revisions are local, and, for
892 pegged revisions, ensure that at least one revision is non-local.
893 Fills the PATHS structure. */
895 check_paths(const struct diff_parameters
*params
,
896 struct diff_paths
*paths
)
898 svn_boolean_t is_local_rev1
, is_local_rev2
;
900 /* Verify our revision arguments in light of the paths. */
901 if ((params
->revision1
->kind
== svn_opt_revision_unspecified
)
902 || (params
->revision2
->kind
== svn_opt_revision_unspecified
))
903 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION
, NULL
,
904 _("Not all required revisions are specified"));
906 /* Revisions can be said to be local or remote. BASE and WORKING,
907 for example, are local. */
909 ((params
->revision1
->kind
== svn_opt_revision_base
)
910 || (params
->revision1
->kind
== svn_opt_revision_working
));
912 ((params
->revision2
->kind
== svn_opt_revision_base
)
913 || (params
->revision2
->kind
== svn_opt_revision_working
));
915 if (params
->peg_revision
->kind
!= svn_opt_revision_unspecified
)
917 if (is_local_rev1
&& is_local_rev2
)
918 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION
, NULL
,
919 _("At least one revision must be non-local "
920 "for a pegged diff"));
922 paths
->is_repos1
= ! is_local_rev1
;
923 paths
->is_repos2
= ! is_local_rev2
;
927 /* Working copy paths with non-local revisions get turned into
928 URLs. We don't do that here, though. We simply record that it
929 needs to be done, which is information that helps us choose our
930 diff helper function. */
931 paths
->is_repos1
= ! is_local_rev1
|| svn_path_is_url(params
->path1
);
932 paths
->is_repos2
= ! is_local_rev2
|| svn_path_is_url(params
->path2
);
938 /** Helper structure filled by diff_prepare_repos_repos */
939 struct diff_repos_repos_t
941 /* URL created from path1 */
944 /* URL created from path2 */
947 /* The BASE_PATH for the diff */
948 const char *base_path
;
950 /* url1 and url2 are the same */
951 svn_boolean_t same_urls
;
953 /* Revision of url1 */
956 /* Revision of url2 */
959 /* Anchor based on url1 */
962 /* Anchor based on url2 */
965 /* Target based on url1 */
968 /* Target based on url2 */
971 /* RA session pointing at anchor1. */
972 svn_ra_session_t
*ra_session
;
975 /** Helper function: prepare a repos repos diff. Fills DRR
978 diff_prepare_repos_repos(const struct diff_parameters
*params
,
979 struct diff_repos_repos_t
*drr
,
980 svn_client_ctx_t
*ctx
,
983 svn_ra_session_t
*ra_session
;
984 svn_node_kind_t kind1
, kind2
;
986 /* Figure out URL1 and URL2. */
987 SVN_ERR(convert_to_url(&drr
->url1
, params
->path1
, pool
));
988 SVN_ERR(convert_to_url(&drr
->url2
, params
->path2
, pool
));
989 drr
->same_urls
= (strcmp(drr
->url1
, drr
->url2
) == 0);
991 /* We need exactly one BASE_PATH, so we'll let the BASE_PATH
992 calculated for PATH2 override the one for PATH1 (since the diff
993 will be "applied" to URL2 anyway). */
994 drr
->base_path
= NULL
;
995 if (drr
->url1
!= params
->path1
)
996 drr
->base_path
= params
->path1
;
997 if (drr
->url2
!= params
->path2
)
998 drr
->base_path
= params
->path2
;
1000 SVN_ERR(svn_client__open_ra_session_internal(&ra_session
, drr
->url2
,
1001 NULL
, NULL
, NULL
, FALSE
,
1004 /* If we are performing a pegged diff, we need to find out what our
1005 actual URLs will be. */
1006 if (params
->peg_revision
->kind
!= svn_opt_revision_unspecified
)
1008 svn_opt_revision_t
*start_ignore
, *end_ignore
;
1010 SVN_ERR(svn_client__repos_locations(&drr
->url1
, &start_ignore
,
1011 &drr
->url2
, &end_ignore
,
1014 params
->peg_revision
,
1018 /* Reparent the session, since drr->url2 might have changed as a result
1020 SVN_ERR(svn_ra_reparent(ra_session
, drr
->url2
, pool
));
1023 /* Resolve revision and get path kind for the second target. */
1024 SVN_ERR(svn_client__get_revision_number
1025 (&drr
->rev2
, NULL
, ra_session
, params
->revision2
,
1026 (params
->path2
== drr
->url2
) ? NULL
: params
->path2
, pool
));
1027 SVN_ERR(svn_ra_check_path(ra_session
, "", drr
->rev2
, &kind2
, pool
));
1028 if (kind2
== svn_node_none
)
1029 return svn_error_createf
1030 (SVN_ERR_FS_NOT_FOUND
, NULL
,
1031 _("'%s' was not found in the repository at revision %ld"),
1032 drr
->url2
, drr
->rev2
);
1034 /* Do the same for the first target. */
1035 SVN_ERR(svn_ra_reparent(ra_session
, drr
->url1
, pool
));
1036 SVN_ERR(svn_client__get_revision_number
1037 (&drr
->rev1
, NULL
, ra_session
, params
->revision1
,
1038 (params
->path1
== drr
->url1
) ? NULL
: params
->path1
, pool
));
1039 SVN_ERR(svn_ra_check_path(ra_session
, "", drr
->rev1
, &kind1
, pool
));
1040 if (kind1
== svn_node_none
)
1041 return svn_error_createf
1042 (SVN_ERR_FS_NOT_FOUND
, NULL
,
1043 _("'%s' was not found in the repository at revision %ld"),
1044 drr
->url1
, drr
->rev1
);
1046 /* Choose useful anchors and targets for our two URLs. */
1047 drr
->anchor1
= drr
->url1
;
1048 drr
->anchor2
= drr
->url2
;
1051 if ((kind1
== svn_node_file
) || (kind2
== svn_node_file
))
1053 svn_path_split(drr
->url1
, &drr
->anchor1
, &drr
->target1
, pool
);
1054 drr
->target1
= svn_path_uri_decode(drr
->target1
, pool
);
1055 svn_path_split(drr
->url2
, &drr
->anchor2
, &drr
->target2
, pool
);
1056 drr
->target2
= svn_path_uri_decode(drr
->target2
, pool
);
1058 drr
->base_path
= svn_path_dirname(drr
->base_path
, pool
);
1059 SVN_ERR(svn_ra_reparent(ra_session
, drr
->anchor1
, pool
));
1062 drr
->ra_session
= ra_session
;
1063 return SVN_NO_ERROR
;
1066 /* A Theoretical Note From Ben, regarding do_diff().
1068 This function is really svn_client_diff4(). If you read the public
1069 API description for svn_client_diff4(), it sounds quite Grand. It
1070 sounds really generalized and abstract and beautiful: that it will
1071 diff any two paths, be they working-copy paths or URLs, at any two
1074 Now, the *reality* is that we have exactly three 'tools' for doing
1075 diffing, and thus this routine is built around the use of the three
1076 tools. Here they are, for clarity:
1078 - svn_wc_diff: assumes both paths are the same wcpath.
1079 compares wcpath@BASE vs. wcpath@WORKING
1081 - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING
1083 - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2
1085 So the truth of the matter is, if the caller's arguments can't be
1086 pigeonholed into one of these three use-cases, we currently bail
1087 with a friendly apology.
1089 Perhaps someday a brave soul will truly make svn_client_diff4
1090 perfectly general. For now, we live with the 90% case. Certainly,
1091 the commandline client only calls this function in legal ways.
1092 When there are other users of svn_client.h, maybe this will become
1093 a more pressing issue.
1096 /* Return a "you can't do that" error, optionally wrapping another
1098 static svn_error_t
*
1099 unsupported_diff_error(svn_error_t
*child_err
)
1101 return svn_error_create(SVN_ERR_INCORRECT_PARAMS
, child_err
,
1102 _("Sorry, svn_client_diff4 was called in a way "
1103 "that is not yet supported"));
1107 /* Perform a diff between two working-copy paths.
1109 PATH1 and PATH2 are both working copy paths. REVISION1 and
1110 REVISION2 are their respective revisions.
1112 All other options are the same as those passed to svn_client_diff4(). */
1113 static svn_error_t
*
1114 diff_wc_wc(const char *path1
,
1115 const svn_opt_revision_t
*revision1
,
1117 const svn_opt_revision_t
*revision2
,
1119 svn_boolean_t ignore_ancestry
,
1120 const apr_array_header_t
*changelists
,
1121 const svn_wc_diff_callbacks3_t
*callbacks
,
1122 struct diff_cmd_baton
*callback_baton
,
1123 svn_client_ctx_t
*ctx
,
1126 svn_wc_adm_access_t
*adm_access
, *target_access
;
1128 int levels_to_lock
= SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth
);
1130 /* Assert that we have valid input. */
1131 assert(! svn_path_is_url(path1
));
1132 assert(! svn_path_is_url(path2
));
1134 /* Currently we support only the case where path1 and path2 are the
1136 if ((strcmp(path1
, path2
) != 0)
1137 || (! ((revision1
->kind
== svn_opt_revision_base
)
1138 && (revision2
->kind
== svn_opt_revision_working
))))
1139 return unsupported_diff_error
1141 (SVN_ERR_INCORRECT_PARAMS
, NULL
,
1142 _("Only diffs between a path's text-base "
1143 "and its working files are supported at this time")));
1145 SVN_ERR(svn_wc_adm_open_anchor(&adm_access
, &target_access
, &target
,
1146 path1
, FALSE
, levels_to_lock
,
1147 ctx
->cancel_func
, ctx
->cancel_baton
,
1150 /* Resolve named revisions to real numbers. */
1151 SVN_ERR(svn_client__get_revision_number
1152 (&callback_baton
->revnum1
, NULL
, NULL
, revision1
, path1
, pool
));
1153 callback_baton
->revnum2
= SVN_INVALID_REVNUM
; /* WC */
1155 SVN_ERR(svn_wc_diff5(adm_access
, target
, callbacks
, callback_baton
,
1156 depth
, ignore_ancestry
, changelists
, pool
));
1157 SVN_ERR(svn_wc_adm_close(adm_access
));
1158 return SVN_NO_ERROR
;
1162 /* Perform a diff between two repository paths.
1164 DIFF_PARAM.PATH1 and DIFF_PARAM.PATH2 may be either URLs or the working
1165 copy paths. DIFF_PARAM.REVISION1 and DIFF_PARAM.REVISION2 are their
1166 respective revisions. If DIFF_PARAM.PEG_REVISION is specified,
1167 DIFF_PARAM.PATH2 is the path at the peg revision, and the actual two
1168 paths compared are determined by following copy history from PATH2.
1170 All other options are the same as those passed to svn_client_diff4(). */
1171 static svn_error_t
*
1172 diff_repos_repos(const struct diff_parameters
*diff_param
,
1173 const svn_wc_diff_callbacks3_t
*callbacks
,
1174 struct diff_cmd_baton
*callback_baton
,
1175 svn_client_ctx_t
*ctx
,
1178 svn_ra_session_t
*extra_ra_session
;
1180 const svn_ra_reporter3_t
*reporter
;
1183 const svn_delta_editor_t
*diff_editor
;
1184 void *diff_edit_baton
;
1186 struct diff_repos_repos_t drr
;
1188 /* Prepare info for the repos repos diff. */
1189 SVN_ERR(diff_prepare_repos_repos(diff_param
, &drr
, ctx
, pool
));
1191 /* Get actual URLs. */
1192 callback_baton
->orig_path_1
= drr
.url1
;
1193 callback_baton
->orig_path_2
= drr
.url2
;
1195 /* Get numeric revisions. */
1196 callback_baton
->revnum1
= drr
.rev1
;
1197 callback_baton
->revnum2
= drr
.rev2
;
1199 /* Now, we open an extra RA session to the correct anchor
1200 location for URL1. This is used during the editor calls to fetch file
1202 SVN_ERR(svn_client__open_ra_session_internal
1203 (&extra_ra_session
, drr
.anchor1
, NULL
, NULL
, NULL
, FALSE
, TRUE
, ctx
,
1206 /* Set up the repos_diff editor on BASE_PATH, if available.
1207 Otherwise, we just use "". */
1208 SVN_ERR(svn_client__get_diff_editor
1209 (drr
.base_path
? drr
.base_path
: "",
1210 NULL
, callbacks
, callback_baton
, diff_param
->depth
,
1211 FALSE
/* doesn't matter for diff */, extra_ra_session
, drr
.rev1
,
1212 NULL
/* no notify_func */, NULL
/* no notify_baton */,
1213 ctx
->cancel_func
, ctx
->cancel_baton
,
1214 &diff_editor
, &diff_edit_baton
, pool
));
1216 /* We want to switch our txn into URL2 */
1217 SVN_ERR(svn_ra_do_diff3
1218 (drr
.ra_session
, &reporter
, &report_baton
, drr
.rev2
, drr
.target1
,
1219 diff_param
->depth
, diff_param
->ignore_ancestry
, TRUE
,
1220 drr
.url2
, diff_editor
, diff_edit_baton
, pool
));
1222 /* Drive the reporter; do the diff. */
1223 SVN_ERR(reporter
->set_path(report_baton
, "", drr
.rev1
,
1227 SVN_ERR(reporter
->finish_report(report_baton
, pool
));
1229 return SVN_NO_ERROR
;
1233 /* Perform a diff between a repository path and a working-copy path.
1235 PATH1 may be either a URL or a working copy path. PATH2 is a
1236 working copy path. REVISION1 and REVISION2 are their respective
1237 revisions. If REVERSE is TRUE, the diff will be done in reverse.
1238 If PEG_REVISION is specified, then PATH1 is the path in the peg
1239 revision, and the actual repository path to be compared is
1240 determined by following copy history.
1242 All other options are the same as those passed to svn_client_diff4(). */
1243 static svn_error_t
*
1244 diff_repos_wc(const char *path1
,
1245 const svn_opt_revision_t
*revision1
,
1246 const svn_opt_revision_t
*peg_revision
,
1248 const svn_opt_revision_t
*revision2
,
1249 svn_boolean_t reverse
,
1251 svn_boolean_t ignore_ancestry
,
1252 const apr_array_header_t
*changelists
,
1253 const svn_wc_diff_callbacks3_t
*callbacks
,
1254 struct diff_cmd_baton
*callback_baton
,
1255 svn_client_ctx_t
*ctx
,
1258 const char *url1
, *anchor
, *anchor_url
, *target
;
1259 svn_wc_adm_access_t
*adm_access
, *dir_access
;
1260 const svn_wc_entry_t
*entry
;
1262 svn_ra_session_t
*ra_session
;
1263 const svn_ra_reporter3_t
*reporter
;
1265 const svn_delta_editor_t
*diff_editor
;
1266 void *diff_edit_baton
;
1267 svn_boolean_t rev2_is_base
= (revision2
->kind
== svn_opt_revision_base
);
1268 int levels_to_lock
= SVN_WC__LEVELS_TO_LOCK_FROM_DEPTH(depth
);
1269 svn_boolean_t server_supports_depth
;
1271 /* Assert that we have valid input. */
1272 assert(! svn_path_is_url(path2
));
1274 /* Convert path1 to a URL to feed to do_diff. */
1275 SVN_ERR(convert_to_url(&url1
, path1
, pool
));
1277 SVN_ERR(svn_wc_adm_open_anchor(&adm_access
, &dir_access
, &target
,
1278 path2
, FALSE
, levels_to_lock
,
1279 ctx
->cancel_func
, ctx
->cancel_baton
,
1281 anchor
= svn_wc_adm_access_path(adm_access
);
1283 /* Fetch the URL of the anchor directory. */
1284 SVN_ERR(svn_wc__entry_versioned(&entry
, anchor
, adm_access
, FALSE
, pool
));
1286 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL
, NULL
,
1287 _("Directory '%s' has no URL"),
1288 svn_path_local_style(anchor
, pool
));
1289 anchor_url
= apr_pstrdup(pool
, entry
->url
);
1291 /* If we are performing a pegged diff, we need to find out what our
1292 actual URLs will be. */
1293 if (peg_revision
->kind
!= svn_opt_revision_unspecified
)
1295 svn_opt_revision_t
*start_ignore
, *end_ignore
, end
;
1296 const char *url_ignore
;
1298 end
.kind
= svn_opt_revision_unspecified
;
1300 SVN_ERR(svn_client__repos_locations(&url1
, &start_ignore
,
1301 &url_ignore
, &end_ignore
,
1310 callback_baton
->orig_path_1
= url1
;
1311 callback_baton
->orig_path_2
= svn_path_join(anchor_url
, target
, pool
);
1315 callback_baton
->orig_path_1
= svn_path_join(anchor_url
, target
, pool
);
1316 callback_baton
->orig_path_2
= url1
;
1320 /* Establish RA session to path2's anchor */
1321 SVN_ERR(svn_client__open_ra_session_internal(&ra_session
, anchor_url
,
1322 NULL
, NULL
, NULL
, FALSE
, TRUE
,
1325 SVN_ERR(svn_wc_get_diff_editor5(adm_access
, target
,
1326 callbacks
, callback_baton
,
1331 ctx
->cancel_func
, ctx
->cancel_baton
,
1333 &diff_editor
, &diff_edit_baton
,
1336 /* Tell the RA layer we want a delta to change our txn to URL1 */
1337 SVN_ERR(svn_client__get_revision_number
1338 (&rev
, NULL
, ra_session
, revision1
,
1339 (path1
== url1
) ? NULL
: path1
, pool
));
1342 callback_baton
->revnum1
= rev
;
1344 callback_baton
->revnum2
= rev
;
1346 SVN_ERR(svn_ra_do_diff3(ra_session
,
1347 &reporter
, &report_baton
,
1349 target
? svn_path_uri_decode(target
, pool
) : NULL
,
1352 TRUE
, /* text_deltas */
1354 diff_editor
, diff_edit_baton
, pool
));
1356 SVN_ERR(svn_ra_has_capability(ra_session
, &server_supports_depth
,
1357 SVN_RA_CAPABILITY_DEPTH
, pool
));
1359 /* Create a txn mirror of path2; the diff editor will print
1360 diffs in reverse. :-) */
1361 SVN_ERR(svn_wc_crawl_revisions3(path2
, dir_access
,
1362 reporter
, report_baton
,
1363 FALSE
, depth
, (! server_supports_depth
),
1364 FALSE
, NULL
, NULL
, /* notification is N/A */
1367 SVN_ERR(svn_wc_adm_close(adm_access
));
1368 return SVN_NO_ERROR
;
1372 /* This is basically just the guts of svn_client_diff[_peg]3(). */
1373 static svn_error_t
*
1374 do_diff(const struct diff_parameters
*diff_param
,
1375 const svn_wc_diff_callbacks3_t
*callbacks
,
1376 struct diff_cmd_baton
*callback_baton
,
1377 svn_client_ctx_t
*ctx
,
1380 struct diff_paths diff_paths
;
1382 /* Check if paths/revisions are urls/local. */
1383 SVN_ERR(check_paths(diff_param
, &diff_paths
));
1385 if (diff_paths
.is_repos1
)
1387 if (diff_paths
.is_repos2
)
1389 SVN_ERR(diff_repos_repos(diff_param
, callbacks
, callback_baton
,
1392 else /* path2 is a working copy path */
1394 SVN_ERR(diff_repos_wc(diff_param
->path1
, diff_param
->revision1
,
1395 diff_param
->peg_revision
,
1396 diff_param
->path2
, diff_param
->revision2
,
1397 FALSE
, diff_param
->depth
,
1398 diff_param
->ignore_ancestry
,
1399 diff_param
->changelists
,
1400 callbacks
, callback_baton
, ctx
, pool
));
1403 else /* path1 is a working copy path */
1405 if (diff_paths
.is_repos2
)
1407 SVN_ERR(diff_repos_wc(diff_param
->path2
, diff_param
->revision2
,
1408 diff_param
->peg_revision
,
1409 diff_param
->path1
, diff_param
->revision1
,
1410 TRUE
, diff_param
->depth
,
1411 diff_param
->ignore_ancestry
,
1412 diff_param
->changelists
,
1413 callbacks
, callback_baton
, ctx
, pool
));
1415 else /* path2 is a working copy path */
1417 SVN_ERR(diff_wc_wc(diff_param
->path1
, diff_param
->revision1
,
1418 diff_param
->path2
, diff_param
->revision2
,
1420 diff_param
->ignore_ancestry
,
1421 diff_param
->changelists
,
1422 callbacks
, callback_baton
, ctx
, pool
));
1426 return SVN_NO_ERROR
;
1429 /* Perform a diff summary between two repository paths. */
1430 static svn_error_t
*
1431 diff_summarize_repos_repos(const struct diff_parameters
*diff_param
,
1432 svn_client_diff_summarize_func_t summarize_func
,
1433 void *summarize_baton
,
1434 svn_client_ctx_t
*ctx
,
1437 svn_ra_session_t
*extra_ra_session
;
1439 const svn_ra_reporter3_t
*reporter
;
1442 const svn_delta_editor_t
*diff_editor
;
1443 void *diff_edit_baton
;
1445 struct diff_repos_repos_t drr
;
1447 /* Prepare info for the repos repos diff. */
1448 SVN_ERR(diff_prepare_repos_repos(diff_param
, &drr
, ctx
, pool
));
1450 /* Now, we open an extra RA session to the correct anchor
1451 location for URL1. This is used to get the kind of deleted paths. */
1452 SVN_ERR(svn_client__open_ra_session_internal
1453 (&extra_ra_session
, drr
.anchor1
, NULL
, NULL
, NULL
, FALSE
, TRUE
,
1456 /* Set up the repos_diff editor. */
1457 SVN_ERR(svn_client__get_diff_summarize_editor
1458 (drr
.target2
, summarize_func
,
1459 summarize_baton
, extra_ra_session
, drr
.rev1
, ctx
->cancel_func
,
1460 ctx
->cancel_baton
, &diff_editor
, &diff_edit_baton
, pool
));
1462 /* We want to switch our txn into URL2 */
1463 SVN_ERR(svn_ra_do_diff3
1464 (drr
.ra_session
, &reporter
, &report_baton
, drr
.rev2
, drr
.target1
,
1465 diff_param
->depth
, diff_param
->ignore_ancestry
,
1466 FALSE
/* do not create text delta */, drr
.url2
, diff_editor
,
1467 diff_edit_baton
, pool
));
1469 /* Drive the reporter; do the diff. */
1470 SVN_ERR(reporter
->set_path(report_baton
, "", drr
.rev1
,
1472 FALSE
, NULL
, pool
));
1473 SVN_ERR(reporter
->finish_report(report_baton
, pool
));
1475 return SVN_NO_ERROR
;
1478 /* This is basically just the guts of svn_client_diff_summarize[_peg](). */
1479 static svn_error_t
*
1480 do_diff_summarize(const struct diff_parameters
*diff_param
,
1481 svn_client_diff_summarize_func_t summarize_func
,
1482 void *summarize_baton
,
1483 svn_client_ctx_t
*ctx
,
1486 struct diff_paths diff_paths
;
1488 /* Check if paths/revisions are urls/local. */
1489 SVN_ERR(check_paths(diff_param
, &diff_paths
));
1491 if (diff_paths
.is_repos1
&& diff_paths
.is_repos2
)
1493 SVN_ERR(diff_summarize_repos_repos(diff_param
, summarize_func
,
1494 summarize_baton
, ctx
, pool
));
1497 return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
1498 _("Summarizing diff can only compare repository "
1501 return SVN_NO_ERROR
;
1505 /* Initialize DIFF_CMD_BATON.diff_cmd and DIFF_CMD_BATON.options,
1506 * according to OPTIONS and CONFIG. CONFIG may be null.
1507 * Allocate the fields in POOL, which should be at least as long-lived
1508 * as the pool DIFF_CMD_BATON itself is allocated in.
1510 static svn_error_t
*
1511 set_up_diff_cmd_and_options(struct diff_cmd_baton
*diff_cmd_baton
,
1512 const apr_array_header_t
*options
,
1513 apr_hash_t
*config
, apr_pool_t
*pool
)
1515 const char *diff_cmd
= NULL
;
1517 /* See if there is a command. */
1520 svn_config_t
*cfg
= apr_hash_get(config
, SVN_CONFIG_CATEGORY_CONFIG
,
1521 APR_HASH_KEY_STRING
);
1522 svn_config_get(cfg
, &diff_cmd
, SVN_CONFIG_SECTION_HELPERS
,
1523 SVN_CONFIG_OPTION_DIFF_CMD
, NULL
);
1526 diff_cmd_baton
->diff_cmd
= diff_cmd
;
1528 /* If there was a command, arrange options to pass to it. */
1529 if (diff_cmd_baton
->diff_cmd
)
1532 int argc
= options
->nelts
;
1536 argv
= apr_palloc(pool
, argc
* sizeof(char *));
1537 for (i
= 0; i
< argc
; i
++)
1538 argv
[i
] = APR_ARRAY_IDX(options
, i
, const char *);
1540 diff_cmd_baton
->options
.for_external
.argv
= argv
;
1541 diff_cmd_baton
->options
.for_external
.argc
= argc
;
1543 else /* No command, so arrange options for internal invocation instead. */
1545 diff_cmd_baton
->options
.for_internal
1546 = svn_diff_file_options_create(pool
);
1547 SVN_ERR(svn_diff_file_options_parse
1548 (diff_cmd_baton
->options
.for_internal
, options
, pool
));
1551 return SVN_NO_ERROR
;
1554 /*----------------------------------------------------------------------- */
1556 /*** Public Interfaces. ***/
1558 /* Display context diffs between two PATH/REVISION pairs. Each of
1559 these inputs will be one of the following:
1561 - a repository URL at a given revision.
1562 - a working copy path, ignoring local mods.
1563 - a working copy path, including local mods.
1565 We can establish a matrix that shows the nine possible types of
1566 diffs we expect to support.
1569 ` . DST || URL:rev | WC:base | WC:working |
1572 ============++============+============+============+
1573 URL:rev || (*) | (*) | (*) |
1577 ------------++------------+------------+------------+
1579 || | New svn_wc_diff which |
1580 || | is smart enough to |
1581 || | handle two WC paths |
1582 ------------++------------+ and their related +
1583 WC:working || (*) | text-bases and working |
1584 || | files. This operation |
1585 || | is entirely local. |
1587 ------------++------------+------------+------------+
1588 * These cases require server communication.
1591 svn_client_diff4(const apr_array_header_t
*options
,
1593 const svn_opt_revision_t
*revision1
,
1595 const svn_opt_revision_t
*revision2
,
1596 const char *relative_to_dir
,
1598 svn_boolean_t ignore_ancestry
,
1599 svn_boolean_t no_diff_deleted
,
1600 svn_boolean_t ignore_content_type
,
1601 const char *header_encoding
,
1602 apr_file_t
*outfile
,
1603 apr_file_t
*errfile
,
1604 const apr_array_header_t
*changelists
,
1605 svn_client_ctx_t
*ctx
,
1608 struct diff_parameters diff_params
;
1610 struct diff_cmd_baton diff_cmd_baton
;
1611 svn_wc_diff_callbacks3_t diff_callbacks
;
1613 /* We will never do a pegged diff from here. */
1614 svn_opt_revision_t peg_revision
;
1615 peg_revision
.kind
= svn_opt_revision_unspecified
;
1617 /* fill diff_param */
1618 diff_params
.path1
= path1
;
1619 diff_params
.revision1
= revision1
;
1620 diff_params
.path2
= path2
;
1621 diff_params
.revision2
= revision2
;
1622 diff_params
.peg_revision
= &peg_revision
;
1623 diff_params
.depth
= depth
;
1624 diff_params
.ignore_ancestry
= ignore_ancestry
;
1625 diff_params
.no_diff_deleted
= no_diff_deleted
;
1626 diff_params
.changelists
= changelists
;
1628 /* setup callback and baton */
1629 diff_callbacks
.file_changed
= diff_file_changed
;
1630 diff_callbacks
.file_added
= diff_file_added
;
1631 diff_callbacks
.file_deleted
= no_diff_deleted
? diff_file_deleted_no_diff
:
1632 diff_file_deleted_with_diff
;
1633 diff_callbacks
.dir_added
= diff_dir_added
;
1634 diff_callbacks
.dir_deleted
= diff_dir_deleted
;
1635 diff_callbacks
.dir_props_changed
= diff_props_changed
;
1636 diff_callbacks
.dir_opened
= diff_dir_opened
;
1637 diff_callbacks
.dir_closed
= diff_dir_closed
;
1639 diff_cmd_baton
.orig_path_1
= path1
;
1640 diff_cmd_baton
.orig_path_2
= path2
;
1642 SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton
, options
,
1643 ctx
->config
, pool
));
1644 diff_cmd_baton
.pool
= pool
;
1645 diff_cmd_baton
.outfile
= outfile
;
1646 diff_cmd_baton
.errfile
= errfile
;
1647 diff_cmd_baton
.header_encoding
= header_encoding
;
1648 diff_cmd_baton
.revnum1
= SVN_INVALID_REVNUM
;
1649 diff_cmd_baton
.revnum2
= SVN_INVALID_REVNUM
;
1651 diff_cmd_baton
.force_empty
= FALSE
;
1652 diff_cmd_baton
.force_binary
= ignore_content_type
;
1653 diff_cmd_baton
.relative_to_dir
= relative_to_dir
;
1655 return do_diff(&diff_params
, &diff_callbacks
, &diff_cmd_baton
, ctx
, pool
);
1659 svn_client_diff3(const apr_array_header_t
*options
,
1661 const svn_opt_revision_t
*revision1
,
1663 const svn_opt_revision_t
*revision2
,
1664 svn_boolean_t recurse
,
1665 svn_boolean_t ignore_ancestry
,
1666 svn_boolean_t no_diff_deleted
,
1667 svn_boolean_t ignore_content_type
,
1668 const char *header_encoding
,
1669 apr_file_t
*outfile
,
1670 apr_file_t
*errfile
,
1671 svn_client_ctx_t
*ctx
,
1674 return svn_client_diff4(options
, path1
, revision1
, path2
,
1676 SVN_DEPTH_INFINITY_OR_FILES(recurse
),
1677 ignore_ancestry
, no_diff_deleted
,
1678 ignore_content_type
, header_encoding
,
1679 outfile
, errfile
, NULL
, ctx
, pool
);
1683 svn_client_diff2(const apr_array_header_t
*options
,
1685 const svn_opt_revision_t
*revision1
,
1687 const svn_opt_revision_t
*revision2
,
1688 svn_boolean_t recurse
,
1689 svn_boolean_t ignore_ancestry
,
1690 svn_boolean_t no_diff_deleted
,
1691 svn_boolean_t ignore_content_type
,
1692 apr_file_t
*outfile
,
1693 apr_file_t
*errfile
,
1694 svn_client_ctx_t
*ctx
,
1697 return svn_client_diff3(options
, path1
, revision1
, path2
, revision2
,
1698 recurse
, ignore_ancestry
, no_diff_deleted
,
1699 ignore_content_type
, SVN_APR_LOCALE_CHARSET
,
1700 outfile
, errfile
, ctx
, pool
);
1704 svn_client_diff(const apr_array_header_t
*options
,
1706 const svn_opt_revision_t
*revision1
,
1708 const svn_opt_revision_t
*revision2
,
1709 svn_boolean_t recurse
,
1710 svn_boolean_t ignore_ancestry
,
1711 svn_boolean_t no_diff_deleted
,
1712 apr_file_t
*outfile
,
1713 apr_file_t
*errfile
,
1714 svn_client_ctx_t
*ctx
,
1717 return svn_client_diff2(options
, path1
, revision1
, path2
, revision2
,
1718 recurse
, ignore_ancestry
, no_diff_deleted
, FALSE
,
1719 outfile
, errfile
, ctx
, pool
);
1723 svn_client_diff_peg4(const apr_array_header_t
*options
,
1725 const svn_opt_revision_t
*peg_revision
,
1726 const svn_opt_revision_t
*start_revision
,
1727 const svn_opt_revision_t
*end_revision
,
1728 const char *relative_to_dir
,
1730 svn_boolean_t ignore_ancestry
,
1731 svn_boolean_t no_diff_deleted
,
1732 svn_boolean_t ignore_content_type
,
1733 const char *header_encoding
,
1734 apr_file_t
*outfile
,
1735 apr_file_t
*errfile
,
1736 const apr_array_header_t
*changelists
,
1737 svn_client_ctx_t
*ctx
,
1740 struct diff_parameters diff_params
;
1742 struct diff_cmd_baton diff_cmd_baton
;
1743 svn_wc_diff_callbacks3_t diff_callbacks
;
1745 if (svn_path_is_url(path
) &&
1746 (start_revision
->kind
== svn_opt_revision_base
1747 || end_revision
->kind
== svn_opt_revision_base
) )
1748 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION
, NULL
,
1749 _("Revision type requires a working copy "
1750 "path, not a URL"));
1752 /* fill diff_param */
1753 diff_params
.path1
= path
;
1754 diff_params
.revision1
= start_revision
;
1755 diff_params
.path2
= path
;
1756 diff_params
.revision2
= end_revision
;
1757 diff_params
.peg_revision
= peg_revision
;
1758 diff_params
.depth
= depth
;
1759 diff_params
.ignore_ancestry
= ignore_ancestry
;
1760 diff_params
.no_diff_deleted
= no_diff_deleted
;
1761 diff_params
.changelists
= changelists
;
1763 /* setup callback and baton */
1764 diff_callbacks
.file_changed
= diff_file_changed
;
1765 diff_callbacks
.file_added
= diff_file_added
;
1766 diff_callbacks
.file_deleted
= no_diff_deleted
? diff_file_deleted_no_diff
:
1767 diff_file_deleted_with_diff
;
1768 diff_callbacks
.dir_added
= diff_dir_added
;
1769 diff_callbacks
.dir_deleted
= diff_dir_deleted
;
1770 diff_callbacks
.dir_props_changed
= diff_props_changed
;
1771 diff_callbacks
.dir_opened
= diff_dir_opened
;
1772 diff_callbacks
.dir_closed
= diff_dir_closed
;
1774 diff_cmd_baton
.orig_path_1
= path
;
1775 diff_cmd_baton
.orig_path_2
= path
;
1777 SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton
, options
,
1778 ctx
->config
, pool
));
1779 diff_cmd_baton
.pool
= pool
;
1780 diff_cmd_baton
.outfile
= outfile
;
1781 diff_cmd_baton
.errfile
= errfile
;
1782 diff_cmd_baton
.header_encoding
= header_encoding
;
1783 diff_cmd_baton
.revnum1
= SVN_INVALID_REVNUM
;
1784 diff_cmd_baton
.revnum2
= SVN_INVALID_REVNUM
;
1786 diff_cmd_baton
.force_empty
= FALSE
;
1787 diff_cmd_baton
.force_binary
= ignore_content_type
;
1788 diff_cmd_baton
.relative_to_dir
= relative_to_dir
;
1790 return do_diff(&diff_params
, &diff_callbacks
, &diff_cmd_baton
, ctx
, pool
);
1794 svn_client_diff_peg3(const apr_array_header_t
*options
,
1796 const svn_opt_revision_t
*peg_revision
,
1797 const svn_opt_revision_t
*start_revision
,
1798 const svn_opt_revision_t
*end_revision
,
1799 svn_boolean_t recurse
,
1800 svn_boolean_t ignore_ancestry
,
1801 svn_boolean_t no_diff_deleted
,
1802 svn_boolean_t ignore_content_type
,
1803 const char *header_encoding
,
1804 apr_file_t
*outfile
,
1805 apr_file_t
*errfile
,
1806 svn_client_ctx_t
*ctx
,
1809 return svn_client_diff_peg4(options
,
1815 SVN_DEPTH_INFINITY_OR_FILES(recurse
),
1818 ignore_content_type
,
1828 svn_client_diff_peg2(const apr_array_header_t
*options
,
1830 const svn_opt_revision_t
*peg_revision
,
1831 const svn_opt_revision_t
*start_revision
,
1832 const svn_opt_revision_t
*end_revision
,
1833 svn_boolean_t recurse
,
1834 svn_boolean_t ignore_ancestry
,
1835 svn_boolean_t no_diff_deleted
,
1836 svn_boolean_t ignore_content_type
,
1837 apr_file_t
*outfile
,
1838 apr_file_t
*errfile
,
1839 svn_client_ctx_t
*ctx
,
1842 return svn_client_diff_peg3(options
, path
, peg_revision
, start_revision
,
1844 SVN_DEPTH_INFINITY_OR_FILES(recurse
),
1845 ignore_ancestry
, no_diff_deleted
,
1846 ignore_content_type
, SVN_APR_LOCALE_CHARSET
,
1847 outfile
, errfile
, ctx
, pool
);
1851 svn_client_diff_peg(const apr_array_header_t
*options
,
1853 const svn_opt_revision_t
*peg_revision
,
1854 const svn_opt_revision_t
*start_revision
,
1855 const svn_opt_revision_t
*end_revision
,
1856 svn_boolean_t recurse
,
1857 svn_boolean_t ignore_ancestry
,
1858 svn_boolean_t no_diff_deleted
,
1859 apr_file_t
*outfile
,
1860 apr_file_t
*errfile
,
1861 svn_client_ctx_t
*ctx
,
1864 return svn_client_diff_peg2(options
, path
, peg_revision
,
1865 start_revision
, end_revision
, recurse
,
1866 ignore_ancestry
, no_diff_deleted
, FALSE
,
1867 outfile
, errfile
, ctx
, pool
);
1871 svn_client_diff_summarize2(const char *path1
,
1872 const svn_opt_revision_t
*revision1
,
1874 const svn_opt_revision_t
*revision2
,
1876 svn_boolean_t ignore_ancestry
,
1877 const apr_array_header_t
*changelists
,
1878 svn_client_diff_summarize_func_t summarize_func
,
1879 void *summarize_baton
,
1880 svn_client_ctx_t
*ctx
,
1883 struct diff_parameters diff_params
;
1885 /* We will never do a pegged diff from here. */
1886 svn_opt_revision_t peg_revision
;
1887 peg_revision
.kind
= svn_opt_revision_unspecified
;
1889 /* fill diff_param */
1890 diff_params
.path1
= path1
;
1891 diff_params
.revision1
= revision1
;
1892 diff_params
.path2
= path2
;
1893 diff_params
.revision2
= revision2
;
1894 diff_params
.peg_revision
= &peg_revision
;
1895 diff_params
.depth
= depth
;
1896 diff_params
.ignore_ancestry
= ignore_ancestry
;
1897 diff_params
.no_diff_deleted
= FALSE
;
1898 diff_params
.changelists
= changelists
;
1900 return do_diff_summarize(&diff_params
, summarize_func
, summarize_baton
,
1905 svn_client_diff_summarize(const char *path1
,
1906 const svn_opt_revision_t
*revision1
,
1908 const svn_opt_revision_t
*revision2
,
1909 svn_boolean_t recurse
,
1910 svn_boolean_t ignore_ancestry
,
1911 svn_client_diff_summarize_func_t summarize_func
,
1912 void *summarize_baton
,
1913 svn_client_ctx_t
*ctx
,
1916 return svn_client_diff_summarize2(path1
, revision1
, path2
,
1918 SVN_DEPTH_INFINITY_OR_FILES(recurse
),
1919 ignore_ancestry
, NULL
, summarize_func
,
1920 summarize_baton
, ctx
, pool
);
1924 svn_client_diff_summarize_peg2(const char *path
,
1925 const svn_opt_revision_t
*peg_revision
,
1926 const svn_opt_revision_t
*start_revision
,
1927 const svn_opt_revision_t
*end_revision
,
1929 svn_boolean_t ignore_ancestry
,
1930 const apr_array_header_t
*changelists
,
1931 svn_client_diff_summarize_func_t summarize_func
,
1932 void *summarize_baton
,
1933 svn_client_ctx_t
*ctx
,
1936 struct diff_parameters diff_params
;
1938 /* fill diff_param */
1939 diff_params
.path1
= path
;
1940 diff_params
.revision1
= start_revision
;
1941 diff_params
.path2
= path
;
1942 diff_params
.revision2
= end_revision
;
1943 diff_params
.peg_revision
= peg_revision
;
1944 diff_params
.depth
= depth
;
1945 diff_params
.ignore_ancestry
= ignore_ancestry
;
1946 diff_params
.no_diff_deleted
= FALSE
;
1947 diff_params
.changelists
= changelists
;
1949 return do_diff_summarize(&diff_params
, summarize_func
, summarize_baton
,
1955 svn_client_diff_summarize_peg(const char *path
,
1956 const svn_opt_revision_t
*peg_revision
,
1957 const svn_opt_revision_t
*start_revision
,
1958 const svn_opt_revision_t
*end_revision
,
1959 svn_boolean_t recurse
,
1960 svn_boolean_t ignore_ancestry
,
1961 svn_client_diff_summarize_func_t summarize_func
,
1962 void *summarize_baton
,
1963 svn_client_ctx_t
*ctx
,
1966 return svn_client_diff_summarize_peg2(path
, peg_revision
,
1967 start_revision
, end_revision
,
1968 SVN_DEPTH_INFINITY_OR_FILES(recurse
),
1969 ignore_ancestry
, NULL
,
1970 summarize_func
, summarize_baton
,
1974 svn_client_diff_summarize_t
*
1975 svn_client_diff_summarize_dup(const svn_client_diff_summarize_t
*diff
,
1978 svn_client_diff_summarize_t
*dup_diff
= apr_palloc(pool
, sizeof(*dup_diff
));
1983 dup_diff
->path
= apr_pstrdup(pool
, diff
->path
);