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