4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
30 #include <apr_strings.h>
31 #include <apr_pools.h>
33 #include "svn_types.h"
37 #include "svn_mergeinfo.h"
38 #include "svn_client.h"
39 #include "svn_string.h"
40 #include "svn_error.h"
41 #include "svn_dirent_uri.h"
45 #include "svn_pools.h"
46 #include "svn_config.h"
47 #include "svn_props.h"
48 #include "svn_subst.h"
51 #include "private/svn_wc_private.h"
52 #include "private/svn_diff_private.h"
53 #include "private/svn_subr_private.h"
54 #include "private/svn_io_private.h"
56 #include "svn_private_config.h"
62 #define MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir) \
63 svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL, \
64 _("Path '%s' must be an immediate child of " \
65 "the directory '%s'"), path, relative_to_dir)
67 /* Calculate the repository relative path of DIFF_RELPATH, using RA_SESSION
68 * and WC_CTX, and return the result in *REPOS_RELPATH.
69 * ORIG_TARGET is the related original target passed to the diff command,
70 * and may be used to derive leading path components missing from PATH.
71 * ANCHOR is the local path where the diff editor is anchored.
72 * Do all allocations in POOL. */
74 make_repos_relpath(const char **repos_relpath,
75 const char *diff_relpath,
76 const char *orig_target,
77 svn_ra_session_t *ra_session,
78 svn_wc_context_t *wc_ctx,
80 apr_pool_t *result_pool,
81 apr_pool_t *scratch_pool)
83 const char *local_abspath;
84 const char *orig_repos_relpath = NULL;
87 || (anchor && !svn_path_is_url(orig_target)))
90 /* We're doing a WC-WC diff, so we can retrieve all information we
91 * need from the working copy. */
92 SVN_ERR(svn_dirent_get_absolute(&local_abspath,
93 svn_dirent_join(anchor, diff_relpath,
97 err = svn_wc__node_get_repos_info(NULL, repos_relpath, NULL, NULL,
98 wc_ctx, local_abspath,
99 result_pool, scratch_pool);
103 || (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND))
105 return svn_error_trace(err);
108 /* The path represents a local working copy path, but does not
109 exist. Fall through to calculate an in-repository location
110 based on the ra session */
112 /* ### Maybe we should use the nearest existing ancestor instead? */
113 svn_error_clear(err);
118 const char *repos_root_url;
120 /* Would be nice if the RA layer could just provide the parent
121 repos_relpath of the ra session */
122 SVN_ERR(svn_ra_get_session_url(ra_session, &url, scratch_pool));
124 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
127 orig_repos_relpath = svn_uri_skip_ancestor(repos_root_url, url,
130 *repos_relpath = svn_relpath_join(orig_repos_relpath, diff_relpath,
137 /* Adjust *INDEX_PATH, *ORIG_PATH_1 and *ORIG_PATH_2, representing the changed
138 * node and the two original targets passed to the diff command, to handle the
139 * case when we're dealing with different anchors. RELATIVE_TO_DIR is the
140 * directory the diff target should be considered relative to.
141 * ANCHOR is the local path where the diff editor is anchored. The resulting
142 * values are allocated in RESULT_POOL and temporary allocations are performed
143 * in SCRATCH_POOL. */
145 adjust_paths_for_diff_labels(const char **index_path,
146 const char **orig_path_1,
147 const char **orig_path_2,
148 const char *relative_to_dir,
150 apr_pool_t *result_pool,
151 apr_pool_t *scratch_pool)
153 const char *new_path = *index_path;
154 const char *new_path1 = *orig_path_1;
155 const char *new_path2 = *orig_path_2;
158 new_path = svn_dirent_join(anchor, new_path, result_pool);
162 /* Possibly adjust the paths shown in the output (see issue #2723). */
163 const char *child_path = svn_dirent_is_child(relative_to_dir, new_path,
167 new_path = child_path;
168 else if (! strcmp(relative_to_dir, new_path))
171 return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir);
173 child_path = svn_dirent_is_child(relative_to_dir, new_path1,
179 svn_boolean_t is_url1;
180 svn_boolean_t is_url2;
181 /* ### Holy cow. Due to anchor/target weirdness, we can't
182 simply join diff_cmd_baton->orig_path_1 with path, ditto for
183 orig_path_2. That will work when they're directory URLs, but
184 not for file URLs. Nor can we just use anchor1 and anchor2
185 from do_diff(), at least not without some more logic here.
188 For now, to distinguish the two paths, we'll just put the
189 unique portions of the original targets in parentheses after
190 the received path, with ellipses for handwaving. This makes
191 the labels a bit clumsy, but at least distinctive. Better
192 solutions are possible, they'll just take more thought. */
194 /* ### BH: We can now just construct the repos_relpath, etc. as the
195 anchor is available. See also make_repos_relpath() */
197 is_url1 = svn_path_is_url(new_path1);
198 is_url2 = svn_path_is_url(new_path2);
200 if (is_url1 && is_url2)
201 len = strlen(svn_uri_get_longest_ancestor(new_path1, new_path2,
203 else if (!is_url1 && !is_url2)
204 len = strlen(svn_dirent_get_longest_ancestor(new_path1, new_path2,
207 len = 0; /* Path and URL */
213 /* ### Should diff labels print paths in local style? Is there
214 already a standard for this? In any case, this code depends on
215 a particular style, so not calling svn_dirent_local_style() on the
218 if (new_path[0] == '\0')
221 if (new_path1[0] == '\0')
222 new_path1 = new_path;
223 else if (svn_path_is_url(new_path1))
224 new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path1);
225 else if (new_path1[0] == '/')
226 new_path1 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path1);
228 new_path1 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path1);
230 if (new_path2[0] == '\0')
231 new_path2 = new_path;
232 else if (svn_path_is_url(new_path2))
233 new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path2);
234 else if (new_path2[0] == '/')
235 new_path2 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path2);
237 new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2);
239 *index_path = new_path;
240 *orig_path_1 = new_path1;
241 *orig_path_2 = new_path2;
247 /* Generate a label for the diff output for file PATH at revision REVNUM.
248 If REVNUM is invalid then it is assumed to be the current working
249 copy. Assumes the paths are already in the desired style (local
250 vs internal). Allocate the label in POOL. */
252 diff_label(const char *path,
257 if (revnum != SVN_INVALID_REVNUM)
258 label = apr_psprintf(pool, _("%s\t(revision %ld)"), path, revnum);
260 label = apr_psprintf(pool, _("%s\t(working copy)"), path);
265 /* Print a git diff header for an addition within a diff between PATH1 and
266 * PATH2 to the stream OS using HEADER_ENCODING.
267 * All allocations are done in RESULT_POOL. */
269 print_git_diff_header_added(svn_stream_t *os, const char *header_encoding,
270 const char *path1, const char *path2,
271 apr_pool_t *result_pool)
273 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
274 "diff --git a/%s b/%s%s",
275 path1, path2, APR_EOL_STR));
276 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
277 "new file mode 10644" APR_EOL_STR));
281 /* Print a git diff header for a deletion within a diff between PATH1 and
282 * PATH2 to the stream OS using HEADER_ENCODING.
283 * All allocations are done in RESULT_POOL. */
285 print_git_diff_header_deleted(svn_stream_t *os, const char *header_encoding,
286 const char *path1, const char *path2,
287 apr_pool_t *result_pool)
289 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
290 "diff --git a/%s b/%s%s",
291 path1, path2, APR_EOL_STR));
292 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
293 "deleted file mode 10644"
298 /* Print a git diff header for a copy from COPYFROM_PATH to PATH to the stream
299 * OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
301 print_git_diff_header_copied(svn_stream_t *os, const char *header_encoding,
302 const char *copyfrom_path,
303 svn_revnum_t copyfrom_rev,
305 apr_pool_t *result_pool)
307 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
308 "diff --git a/%s b/%s%s",
309 copyfrom_path, path, APR_EOL_STR));
310 if (copyfrom_rev != SVN_INVALID_REVNUM)
311 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
312 "copy from %s@%ld%s", copyfrom_path,
313 copyfrom_rev, APR_EOL_STR));
315 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
316 "copy from %s%s", copyfrom_path,
318 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
319 "copy to %s%s", path, APR_EOL_STR));
323 /* Print a git diff header for a rename from COPYFROM_PATH to PATH to the
324 * stream OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
326 print_git_diff_header_renamed(svn_stream_t *os, const char *header_encoding,
327 const char *copyfrom_path, const char *path,
328 apr_pool_t *result_pool)
330 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
331 "diff --git a/%s b/%s%s",
332 copyfrom_path, path, APR_EOL_STR));
333 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
334 "rename from %s%s", copyfrom_path,
336 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
337 "rename to %s%s", path, APR_EOL_STR));
341 /* Print a git diff header for a modification within a diff between PATH1 and
342 * PATH2 to the stream OS using HEADER_ENCODING.
343 * All allocations are done in RESULT_POOL. */
345 print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding,
346 const char *path1, const char *path2,
347 apr_pool_t *result_pool)
349 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
350 "diff --git a/%s b/%s%s",
351 path1, path2, APR_EOL_STR));
355 /* Print a git diff header showing the OPERATION to the stream OS using
356 * HEADER_ENCODING. Return suitable diff labels for the git diff in *LABEL1
357 * and *LABEL2. REPOS_RELPATH1 and REPOS_RELPATH2 are relative to reposroot.
358 * are the paths passed to the original diff command. REV1 and REV2 are
359 * revisions being diffed. COPYFROM_PATH and COPYFROM_REV indicate where the
360 * diffed item was copied from.
361 * Use SCRATCH_POOL for temporary allocations. */
363 print_git_diff_header(svn_stream_t *os,
364 const char **label1, const char **label2,
365 svn_diff_operation_kind_t operation,
366 const char *repos_relpath1,
367 const char *repos_relpath2,
370 const char *copyfrom_path,
371 svn_revnum_t copyfrom_rev,
372 const char *header_encoding,
373 apr_pool_t *scratch_pool)
375 if (operation == svn_diff_op_deleted)
377 SVN_ERR(print_git_diff_header_deleted(os, header_encoding,
378 repos_relpath1, repos_relpath2,
380 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
382 *label2 = diff_label("/dev/null", rev2, scratch_pool);
385 else if (operation == svn_diff_op_copied)
387 SVN_ERR(print_git_diff_header_copied(os, header_encoding,
388 copyfrom_path, copyfrom_rev,
391 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
393 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
396 else if (operation == svn_diff_op_added)
398 SVN_ERR(print_git_diff_header_added(os, header_encoding,
399 repos_relpath1, repos_relpath2,
401 *label1 = diff_label("/dev/null", rev1, scratch_pool);
402 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
405 else if (operation == svn_diff_op_modified)
407 SVN_ERR(print_git_diff_header_modified(os, header_encoding,
408 repos_relpath1, repos_relpath2,
410 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
412 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
415 else if (operation == svn_diff_op_moved)
417 SVN_ERR(print_git_diff_header_renamed(os, header_encoding,
418 copyfrom_path, repos_relpath2,
420 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
422 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
429 /* A helper func that writes out verbal descriptions of property diffs
430 to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was
431 passed to svn_client_diff6(), which is probably stdout.
433 ### FIXME needs proper docstring
435 If USE_GIT_DIFF_FORMAT is TRUE, pring git diff headers, which always
436 show paths relative to the repository root. RA_SESSION and WC_CTX are
437 needed to normalize paths relative the repository root, and are ignored
438 if USE_GIT_DIFF_FORMAT is FALSE.
440 ANCHOR is the local path where the diff editor is anchored. */
442 display_prop_diffs(const apr_array_header_t *propchanges,
443 apr_hash_t *original_props,
444 const char *diff_relpath,
446 const char *orig_path1,
447 const char *orig_path2,
450 const char *encoding,
451 svn_stream_t *outstream,
452 const char *relative_to_dir,
453 svn_boolean_t show_diff_header,
454 svn_boolean_t use_git_diff_format,
455 svn_ra_session_t *ra_session,
456 svn_wc_context_t *wc_ctx,
457 apr_pool_t *scratch_pool)
459 const char *repos_relpath1 = NULL;
460 const char *repos_relpath2 = NULL;
461 const char *index_path = diff_relpath;
462 const char *adjusted_path1 = orig_path1;
463 const char *adjusted_path2 = orig_path2;
465 if (use_git_diff_format)
467 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, orig_path1,
468 ra_session, wc_ctx, anchor,
469 scratch_pool, scratch_pool));
470 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, orig_path2,
471 ra_session, wc_ctx, anchor,
472 scratch_pool, scratch_pool));
475 /* If we're creating a diff on the wc root, path would be empty. */
476 SVN_ERR(adjust_paths_for_diff_labels(&index_path, &adjusted_path1,
478 relative_to_dir, anchor,
479 scratch_pool, scratch_pool));
481 if (show_diff_header)
486 label1 = diff_label(adjusted_path1, rev1, scratch_pool);
487 label2 = diff_label(adjusted_path2, rev2, scratch_pool);
489 /* ### Should we show the paths in platform specific format,
490 * ### diff_content_changed() does not! */
492 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
493 "Index: %s" APR_EOL_STR
494 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
497 if (use_git_diff_format)
498 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
499 svn_diff_op_modified,
500 repos_relpath1, repos_relpath2,
503 encoding, scratch_pool));
507 SVN_ERR(svn_diff__unidiff_write_header(
508 outstream, encoding, label1, label2, scratch_pool));
511 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
512 _("%sProperty changes on: %s%s"),
519 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
520 SVN_DIFF__UNDER_STRING APR_EOL_STR));
522 SVN_ERR(svn_diff__display_prop_diffs(
523 outstream, encoding, propchanges, original_props,
524 TRUE /* pretty_print_mergeinfo */, scratch_pool));
529 /*-----------------------------------------------------------------*/
531 /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
534 struct diff_cmd_baton {
536 /* If non-null, the external diff command to invoke. */
537 const char *diff_cmd;
539 /* This is allocated in this struct's pool or a higher-up pool. */
541 /* If 'diff_cmd' is null, then this is the parsed options to
542 pass to the internal libsvn_diff implementation. */
543 svn_diff_file_options_t *for_internal;
544 /* Else if 'diff_cmd' is non-null, then... */
546 /* ...this is an argument array for the external command, and */
548 /* ...this is the length of argv. */
554 svn_stream_t *outstream;
555 svn_stream_t *errstream;
557 const char *header_encoding;
559 /* The original targets passed to the diff command. We may need
560 these to construct distinctive diff labels when comparing the
561 same relative path in the same revision, under different anchors
562 (for example, when comparing a trunk against a branch). */
563 const char *orig_path_1;
564 const char *orig_path_2;
566 /* These are the numeric representations of the revisions passed to
567 svn_client_diff6(), either may be SVN_INVALID_REVNUM. We need these
568 because some of the svn_wc_diff_callbacks4_t don't get revision
571 ### Perhaps we should change the callback signatures and eliminate
574 svn_revnum_t revnum1;
575 svn_revnum_t revnum2;
577 /* Set this if you want diff output even for binary files. */
578 svn_boolean_t force_binary;
580 /* The directory that diff target paths should be considered as
581 relative to for output generation (see issue #2723). */
582 const char *relative_to_dir;
584 /* Whether property differences are ignored. */
585 svn_boolean_t ignore_properties;
587 /* Whether to show only property changes. */
588 svn_boolean_t properties_only;
590 /* Whether we're producing a git-style diff. */
591 svn_boolean_t use_git_diff_format;
593 /* Whether addition of a file is summarized versus showing a full diff. */
594 svn_boolean_t no_diff_added;
596 /* Whether deletion of a file is summarized versus showing a full diff. */
597 svn_boolean_t no_diff_deleted;
599 /* Whether to ignore copyfrom information when showing adds */
600 svn_boolean_t no_copyfrom_on_add;
602 /* Empty files for creating diffs or NULL if not used yet */
603 const char *empty_file;
605 svn_wc_context_t *wc_ctx;
607 /* The RA session used during diffs involving the repository. */
608 svn_ra_session_t *ra_session;
610 /* The anchor to prefix before wc paths */
613 /* Whether the local diff target of a repos->wc diff is a copy. */
614 svn_boolean_t repos_wc_diff_target_is_copy;
617 /* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added
620 diff_props_changed(const char *diff_relpath,
623 svn_boolean_t dir_was_added,
624 const apr_array_header_t *propchanges,
625 apr_hash_t *original_props,
626 svn_boolean_t show_diff_header,
627 struct diff_cmd_baton *diff_cmd_baton,
628 apr_pool_t *scratch_pool)
630 apr_array_header_t *props;
632 /* If property differences are ignored, there's nothing to do. */
633 if (diff_cmd_baton->ignore_properties)
636 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props,
639 if (props->nelts > 0)
641 /* We're using the revnums from the diff_cmd_baton since there's
642 * no revision argument to the svn_wc_diff_callback_t
643 * dir_props_changed(). */
644 SVN_ERR(display_prop_diffs(props, original_props,
646 diff_cmd_baton->anchor,
647 diff_cmd_baton->orig_path_1,
648 diff_cmd_baton->orig_path_2,
651 diff_cmd_baton->header_encoding,
652 diff_cmd_baton->outstream,
653 diff_cmd_baton->relative_to_dir,
655 diff_cmd_baton->use_git_diff_format,
656 diff_cmd_baton->ra_session,
657 diff_cmd_baton->wc_ctx,
664 /* An svn_wc_diff_callbacks4_t function. */
666 diff_dir_props_changed(svn_wc_notify_state_t *state,
667 svn_boolean_t *tree_conflicted,
668 const char *diff_relpath,
669 svn_boolean_t dir_was_added,
670 const apr_array_header_t *propchanges,
671 apr_hash_t *original_props,
673 apr_pool_t *scratch_pool)
675 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
677 return svn_error_trace(diff_props_changed(diff_relpath,
678 /* ### These revs be filled
679 * ### with per node info */
681 ? 0 /* Magic legacy value */
682 : diff_cmd_baton->revnum1,
683 diff_cmd_baton->revnum2,
687 TRUE /* show_diff_header */,
693 /* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and
694 REV2 are used in the headers to indicate the file and revisions. If either
695 MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
696 but instead print a warning message.
698 If FORCE_DIFF is TRUE, always write a diff, even for empty diffs.
700 Set *WROTE_HEADER to TRUE if a diff header was written */
702 diff_content_changed(svn_boolean_t *wrote_header,
703 const char *diff_relpath,
704 const char *tmpfile1,
705 const char *tmpfile2,
708 const char *mimetype1,
709 const char *mimetype2,
710 svn_diff_operation_kind_t operation,
711 svn_boolean_t force_diff,
712 const char *copyfrom_path,
713 svn_revnum_t copyfrom_rev,
714 struct diff_cmd_baton *diff_cmd_baton,
715 apr_pool_t *scratch_pool)
718 const char *rel_to_dir = diff_cmd_baton->relative_to_dir;
719 svn_stream_t *errstream = diff_cmd_baton->errstream;
720 svn_stream_t *outstream = diff_cmd_baton->outstream;
721 const char *label1, *label2;
722 svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
723 const char *index_path = diff_relpath;
724 const char *path1 = diff_cmd_baton->orig_path_1;
725 const char *path2 = diff_cmd_baton->orig_path_2;
727 /* If only property differences are shown, there's nothing to do. */
728 if (diff_cmd_baton->properties_only)
731 /* Generate the diff headers. */
732 SVN_ERR(adjust_paths_for_diff_labels(&index_path, &path1, &path2,
733 rel_to_dir, diff_cmd_baton->anchor,
734 scratch_pool, scratch_pool));
736 label1 = diff_label(path1, rev1, scratch_pool);
737 label2 = diff_label(path2, rev2, scratch_pool);
739 /* Possible easy-out: if either mime-type is binary and force was not
740 specified, don't attempt to generate a viewable diff at all.
741 Print a warning and exit. */
743 mt1_binary = svn_mime_type_is_binary(mimetype1);
745 mt2_binary = svn_mime_type_is_binary(mimetype2);
747 if (! diff_cmd_baton->force_binary && (mt1_binary || mt2_binary))
749 /* Print out the diff header. */
750 SVN_ERR(svn_stream_printf_from_utf8(outstream,
751 diff_cmd_baton->header_encoding, scratch_pool,
752 "Index: %s" APR_EOL_STR
753 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
756 /* ### Print git diff headers. */
758 SVN_ERR(svn_stream_printf_from_utf8(outstream,
759 diff_cmd_baton->header_encoding, scratch_pool,
760 _("Cannot display: file marked as a binary type.%s"),
763 if (mt1_binary && !mt2_binary)
764 SVN_ERR(svn_stream_printf_from_utf8(outstream,
765 diff_cmd_baton->header_encoding, scratch_pool,
766 "svn:mime-type = %s" APR_EOL_STR, mimetype1));
767 else if (mt2_binary && !mt1_binary)
768 SVN_ERR(svn_stream_printf_from_utf8(outstream,
769 diff_cmd_baton->header_encoding, scratch_pool,
770 "svn:mime-type = %s" APR_EOL_STR, mimetype2));
771 else if (mt1_binary && mt2_binary)
773 if (strcmp(mimetype1, mimetype2) == 0)
774 SVN_ERR(svn_stream_printf_from_utf8(outstream,
775 diff_cmd_baton->header_encoding, scratch_pool,
776 "svn:mime-type = %s" APR_EOL_STR,
779 SVN_ERR(svn_stream_printf_from_utf8(outstream,
780 diff_cmd_baton->header_encoding, scratch_pool,
781 "svn:mime-type = (%s, %s)" APR_EOL_STR,
782 mimetype1, mimetype2));
790 if (diff_cmd_baton->diff_cmd)
794 const char *outfilename;
795 const char *errfilename;
796 svn_stream_t *stream;
798 /* Print out the diff header. */
799 SVN_ERR(svn_stream_printf_from_utf8(outstream,
800 diff_cmd_baton->header_encoding, scratch_pool,
801 "Index: %s" APR_EOL_STR
802 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
805 /* ### Do we want to add git diff headers here too? I'd say no. The
806 * ### 'Index' and '===' line is something subversion has added. The rest
807 * ### is up to the external diff application. We may be dealing with
808 * ### a non-git compatible diff application.*/
810 /* We deal in streams, but svn_io_run_diff2() deals in file handles,
811 so we may need to make temporary files and then copy the contents
813 outfile = svn_stream__aprfile(outstream);
817 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
818 svn_io_file_del_on_pool_cleanup,
819 scratch_pool, scratch_pool));
821 errfile = svn_stream__aprfile(errstream);
825 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
826 svn_io_file_del_on_pool_cleanup,
827 scratch_pool, scratch_pool));
829 SVN_ERR(svn_io_run_diff2(".",
830 diff_cmd_baton->options.for_external.argv,
831 diff_cmd_baton->options.for_external.argc,
834 &exitcode, outfile, errfile,
835 diff_cmd_baton->diff_cmd, scratch_pool));
837 /* Now, open and copy our files to our output streams. */
840 SVN_ERR(svn_io_file_close(outfile, scratch_pool));
841 SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
842 scratch_pool, scratch_pool));
843 SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(outstream,
845 NULL, NULL, scratch_pool));
849 SVN_ERR(svn_io_file_close(errfile, scratch_pool));
850 SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
851 scratch_pool, scratch_pool));
852 SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(errstream,
854 NULL, NULL, scratch_pool));
857 /* We have a printed a diff for this path, mark it as visited. */
858 *wrote_header = TRUE;
860 else /* use libsvn_diff to generate the diff */
864 SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2,
865 diff_cmd_baton->options.for_internal,
869 || diff_cmd_baton->use_git_diff_format
870 || svn_diff_contains_diffs(diff))
872 /* Print out the diff header. */
873 SVN_ERR(svn_stream_printf_from_utf8(outstream,
874 diff_cmd_baton->header_encoding, scratch_pool,
875 "Index: %s" APR_EOL_STR
876 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
879 if (diff_cmd_baton->use_git_diff_format)
881 const char *repos_relpath1;
882 const char *repos_relpath2;
883 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
884 diff_cmd_baton->orig_path_1,
885 diff_cmd_baton->ra_session,
886 diff_cmd_baton->wc_ctx,
887 diff_cmd_baton->anchor,
888 scratch_pool, scratch_pool));
889 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
890 diff_cmd_baton->orig_path_2,
891 diff_cmd_baton->ra_session,
892 diff_cmd_baton->wc_ctx,
893 diff_cmd_baton->anchor,
894 scratch_pool, scratch_pool));
895 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
897 repos_relpath1, repos_relpath2,
901 diff_cmd_baton->header_encoding,
905 /* Output the actual diff */
906 if (force_diff || svn_diff_contains_diffs(diff))
907 SVN_ERR(svn_diff_file_output_unified3(outstream, diff,
908 tmpfile1, tmpfile2, label1, label2,
909 diff_cmd_baton->header_encoding, rel_to_dir,
910 diff_cmd_baton->options.for_internal->show_c_function,
913 /* We have a printed a diff for this path, mark it as visited. */
914 *wrote_header = TRUE;
918 /* ### todo: someday we'll need to worry about whether we're going
919 to need to write a diff plug-in mechanism that makes use of the
920 two paths, instead of just blindly running SVN_CLIENT_DIFF. */
926 diff_file_opened(svn_boolean_t *tree_conflicted,
928 const char *diff_relpath,
931 apr_pool_t *scratch_pool)
936 /* An svn_wc_diff_callbacks4_t function. */
938 diff_file_changed(svn_wc_notify_state_t *content_state,
939 svn_wc_notify_state_t *prop_state,
940 svn_boolean_t *tree_conflicted,
941 const char *diff_relpath,
942 const char *tmpfile1,
943 const char *tmpfile2,
946 const char *mimetype1,
947 const char *mimetype2,
948 const apr_array_header_t *prop_changes,
949 apr_hash_t *original_props,
951 apr_pool_t *scratch_pool)
953 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
954 svn_boolean_t wrote_header = FALSE;
956 /* During repos->wc diff of a copy revision numbers obtained
957 * from the working copy are always SVN_INVALID_REVNUM. */
958 if (diff_cmd_baton->repos_wc_diff_target_is_copy)
960 if (rev1 == SVN_INVALID_REVNUM &&
961 diff_cmd_baton->revnum1 != SVN_INVALID_REVNUM)
962 rev1 = diff_cmd_baton->revnum1;
964 if (rev2 == SVN_INVALID_REVNUM &&
965 diff_cmd_baton->revnum2 != SVN_INVALID_REVNUM)
966 rev2 = diff_cmd_baton->revnum2;
970 SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
971 tmpfile1, tmpfile2, rev1, rev2,
972 mimetype1, mimetype2,
973 svn_diff_op_modified, FALSE,
975 SVN_INVALID_REVNUM, diff_cmd_baton,
977 if (prop_changes->nelts > 0)
978 SVN_ERR(diff_props_changed(diff_relpath, rev1, rev2, FALSE, prop_changes,
979 original_props, !wrote_header,
980 diff_cmd_baton, scratch_pool));
984 /* Because the repos-diff editor passes at least one empty file to
985 each of these next two functions, they can be dumb wrappers around
986 the main workhorse routine. */
988 /* An svn_wc_diff_callbacks4_t function. */
990 diff_file_added(svn_wc_notify_state_t *content_state,
991 svn_wc_notify_state_t *prop_state,
992 svn_boolean_t *tree_conflicted,
993 const char *diff_relpath,
994 const char *tmpfile1,
995 const char *tmpfile2,
998 const char *mimetype1,
999 const char *mimetype2,
1000 const char *copyfrom_path,
1001 svn_revnum_t copyfrom_revision,
1002 const apr_array_header_t *prop_changes,
1003 apr_hash_t *original_props,
1005 apr_pool_t *scratch_pool)
1007 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
1008 svn_boolean_t wrote_header = FALSE;
1010 /* During repos->wc diff of a copy revision numbers obtained
1011 * from the working copy are always SVN_INVALID_REVNUM. */
1012 if (diff_cmd_baton->repos_wc_diff_target_is_copy)
1014 if (rev1 == SVN_INVALID_REVNUM &&
1015 diff_cmd_baton->revnum1 != SVN_INVALID_REVNUM)
1016 rev1 = diff_cmd_baton->revnum1;
1018 if (rev2 == SVN_INVALID_REVNUM &&
1019 diff_cmd_baton->revnum2 != SVN_INVALID_REVNUM)
1020 rev2 = diff_cmd_baton->revnum2;
1023 if (diff_cmd_baton->no_copyfrom_on_add
1024 && (copyfrom_path || SVN_IS_VALID_REVNUM(copyfrom_revision)))
1026 apr_hash_t *empty_hash = apr_hash_make(scratch_pool);
1027 apr_array_header_t *new_changes;
1029 /* Rebase changes on having no left source. */
1030 if (!diff_cmd_baton->empty_file)
1031 SVN_ERR(svn_io_open_unique_file3(NULL, &diff_cmd_baton->empty_file,
1032 NULL, svn_io_file_del_on_pool_cleanup,
1033 diff_cmd_baton->pool, scratch_pool));
1035 SVN_ERR(svn_prop_diffs(&new_changes,
1036 svn_prop__patch(original_props, prop_changes,
1041 tmpfile1 = diff_cmd_baton->empty_file;
1042 prop_changes = new_changes;
1043 original_props = empty_hash;
1044 copyfrom_revision = SVN_INVALID_REVNUM;
1047 if (diff_cmd_baton->no_diff_added)
1049 const char *index_path = diff_relpath;
1051 if (diff_cmd_baton->anchor)
1052 index_path = svn_dirent_join(diff_cmd_baton->anchor, diff_relpath,
1055 SVN_ERR(svn_stream_printf_from_utf8(diff_cmd_baton->outstream,
1056 diff_cmd_baton->header_encoding, scratch_pool,
1057 "Index: %s (added)" APR_EOL_STR
1058 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1060 wrote_header = TRUE;
1062 else if (tmpfile1 && copyfrom_path)
1063 SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
1064 tmpfile1, tmpfile2, rev1, rev2,
1065 mimetype1, mimetype2,
1067 TRUE /* force diff output */,
1069 copyfrom_revision, diff_cmd_baton,
1072 SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
1073 tmpfile1, tmpfile2, rev1, rev2,
1074 mimetype1, mimetype2,
1076 TRUE /* force diff output */,
1077 NULL, SVN_INVALID_REVNUM,
1078 diff_cmd_baton, scratch_pool));
1080 if (prop_changes->nelts > 0)
1081 SVN_ERR(diff_props_changed(diff_relpath, rev1, rev2,
1082 FALSE, prop_changes,
1083 original_props, ! wrote_header,
1084 diff_cmd_baton, scratch_pool));
1086 return SVN_NO_ERROR;
1089 /* An svn_wc_diff_callbacks4_t function. */
1090 static svn_error_t *
1091 diff_file_deleted(svn_wc_notify_state_t *state,
1092 svn_boolean_t *tree_conflicted,
1093 const char *diff_relpath,
1094 const char *tmpfile1,
1095 const char *tmpfile2,
1096 const char *mimetype1,
1097 const char *mimetype2,
1098 apr_hash_t *original_props,
1100 apr_pool_t *scratch_pool)
1102 struct diff_cmd_baton *diff_cmd_baton = diff_baton;
1104 if (diff_cmd_baton->no_diff_deleted)
1106 const char *index_path = diff_relpath;
1108 if (diff_cmd_baton->anchor)
1109 index_path = svn_dirent_join(diff_cmd_baton->anchor, diff_relpath,
1112 SVN_ERR(svn_stream_printf_from_utf8(diff_cmd_baton->outstream,
1113 diff_cmd_baton->header_encoding, scratch_pool,
1114 "Index: %s (deleted)" APR_EOL_STR
1115 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1120 svn_boolean_t wrote_header = FALSE;
1122 SVN_ERR(diff_content_changed(&wrote_header, diff_relpath,
1124 diff_cmd_baton->revnum1,
1125 diff_cmd_baton->revnum2,
1126 mimetype1, mimetype2,
1127 svn_diff_op_deleted, FALSE,
1128 NULL, SVN_INVALID_REVNUM,
1132 /* Should we also report the properties as deleted? */
1135 /* We don't list all the deleted properties. */
1137 return SVN_NO_ERROR;
1140 /* An svn_wc_diff_callbacks4_t function. */
1141 static svn_error_t *
1142 diff_dir_added(svn_wc_notify_state_t *state,
1143 svn_boolean_t *tree_conflicted,
1144 svn_boolean_t *skip,
1145 svn_boolean_t *skip_children,
1146 const char *diff_relpath,
1148 const char *copyfrom_path,
1149 svn_revnum_t copyfrom_revision,
1151 apr_pool_t *scratch_pool)
1155 return SVN_NO_ERROR;
1158 /* An svn_wc_diff_callbacks4_t function. */
1159 static svn_error_t *
1160 diff_dir_deleted(svn_wc_notify_state_t *state,
1161 svn_boolean_t *tree_conflicted,
1162 const char *diff_relpath,
1164 apr_pool_t *scratch_pool)
1168 return SVN_NO_ERROR;
1171 /* An svn_wc_diff_callbacks4_t function. */
1172 static svn_error_t *
1173 diff_dir_opened(svn_boolean_t *tree_conflicted,
1174 svn_boolean_t *skip,
1175 svn_boolean_t *skip_children,
1176 const char *diff_relpath,
1179 apr_pool_t *scratch_pool)
1183 return SVN_NO_ERROR;
1186 /* An svn_wc_diff_callbacks4_t function. */
1187 static svn_error_t *
1188 diff_dir_closed(svn_wc_notify_state_t *contentstate,
1189 svn_wc_notify_state_t *propstate,
1190 svn_boolean_t *tree_conflicted,
1191 const char *diff_relpath,
1192 svn_boolean_t dir_was_added,
1194 apr_pool_t *scratch_pool)
1198 return SVN_NO_ERROR;
1201 static const svn_wc_diff_callbacks4_t diff_callbacks =
1210 diff_dir_props_changed,
1214 /*-----------------------------------------------------------------*/
1216 /** The logic behind 'svn diff' and 'svn merge'. */
1219 /* Hi! This is a comment left behind by Karl, and Ben is too afraid
1220 to erase it at this time, because he's not fully confident that all
1221 this knowledge has been grokked yet.
1223 There are five cases:
1224 1. path is not a URL and start_revision != end_revision
1225 2. path is not a URL and start_revision == end_revision
1226 3. path is a URL and start_revision != end_revision
1227 4. path is a URL and start_revision == end_revision
1228 5. path is not a URL and no revisions given
1230 With only one distinct revision the working copy provides the
1231 other. When path is a URL there is no working copy. Thus
1233 1: compare repository versions for URL coresponding to working copy
1234 2: compare working copy against repository version
1235 3: compare repository versions for URL
1237 5: compare working copy against text-base
1239 Case 4 is not as stupid as it looks, for example it may occur if
1240 the user specifies two dates that resolve to the same revision. */
1243 /** Check if paths PATH_OR_URL1 and PATH_OR_URL2 are urls and if the
1244 * revisions REVISION1 and REVISION2 are local. If PEG_REVISION is not
1245 * unspecified, ensure that at least one of the two revisions is not
1247 * If PATH_OR_URL1 can only be found in the repository, set *IS_REPOS1
1248 * to TRUE. If PATH_OR_URL2 can only be found in the repository, set
1249 * *IS_REPOS2 to TRUE. */
1250 static svn_error_t *
1251 check_paths(svn_boolean_t *is_repos1,
1252 svn_boolean_t *is_repos2,
1253 const char *path_or_url1,
1254 const char *path_or_url2,
1255 const svn_opt_revision_t *revision1,
1256 const svn_opt_revision_t *revision2,
1257 const svn_opt_revision_t *peg_revision)
1259 svn_boolean_t is_local_rev1, is_local_rev2;
1261 /* Verify our revision arguments in light of the paths. */
1262 if ((revision1->kind == svn_opt_revision_unspecified)
1263 || (revision2->kind == svn_opt_revision_unspecified))
1264 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1265 _("Not all required revisions are specified"));
1267 /* Revisions can be said to be local or remote.
1268 * BASE and WORKING are local revisions. */
1270 ((revision1->kind == svn_opt_revision_base)
1271 || (revision1->kind == svn_opt_revision_working));
1273 ((revision2->kind == svn_opt_revision_base)
1274 || (revision2->kind == svn_opt_revision_working));
1276 if (peg_revision->kind != svn_opt_revision_unspecified &&
1277 is_local_rev1 && is_local_rev2)
1278 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1279 _("At least one revision must be something other "
1280 "than BASE or WORKING when diffing a URL"));
1282 /* Working copy paths with non-local revisions get turned into
1283 URLs. We don't do that here, though. We simply record that it
1284 needs to be done, which is information that helps us choose our
1285 diff helper function. */
1286 *is_repos1 = ! is_local_rev1 || svn_path_is_url(path_or_url1);
1287 *is_repos2 = ! is_local_rev2 || svn_path_is_url(path_or_url2);
1289 return SVN_NO_ERROR;
1292 /* Raise an error if the diff target URL does not exist at REVISION.
1293 * If REVISION does not equal OTHER_REVISION, mention both revisions in
1294 * the error message. Use RA_SESSION to contact the repository.
1295 * Use POOL for temporary allocations. */
1296 static svn_error_t *
1297 check_diff_target_exists(const char *url,
1298 svn_revnum_t revision,
1299 svn_revnum_t other_revision,
1300 svn_ra_session_t *ra_session,
1303 svn_node_kind_t kind;
1304 const char *session_url;
1306 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
1308 if (strcmp(url, session_url) != 0)
1309 SVN_ERR(svn_ra_reparent(ra_session, url, pool));
1311 SVN_ERR(svn_ra_check_path(ra_session, "", revision, &kind, pool));
1312 if (kind == svn_node_none)
1314 if (revision == other_revision)
1315 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1316 _("Diff target '%s' was not found in the "
1317 "repository at revision '%ld'"),
1320 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1321 _("Diff target '%s' was not found in the "
1322 "repository at revision '%ld' or '%ld'"),
1323 url, revision, other_revision);
1326 if (strcmp(url, session_url) != 0)
1327 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
1329 return SVN_NO_ERROR;
1333 /* Return in *RESOLVED_URL the URL which PATH_OR_URL@PEG_REVISION has in
1334 * REVISION. If the object has no location in REVISION, set *RESOLVED_URL
1336 static svn_error_t *
1337 resolve_pegged_diff_target_url(const char **resolved_url,
1338 svn_ra_session_t *ra_session,
1339 const char *path_or_url,
1340 const svn_opt_revision_t *peg_revision,
1341 const svn_opt_revision_t *revision,
1342 svn_client_ctx_t *ctx,
1343 apr_pool_t *scratch_pool)
1347 /* Check if the PATH_OR_URL exists at REVISION. */
1348 err = svn_client__repos_locations(resolved_url, NULL,
1358 if (err->apr_err == SVN_ERR_CLIENT_UNRELATED_RESOURCES ||
1359 err->apr_err == SVN_ERR_FS_NOT_FOUND)
1361 svn_error_clear(err);
1362 *resolved_url = NULL;
1365 return svn_error_trace(err);
1368 return SVN_NO_ERROR;
1371 /** Prepare a repos repos diff between PATH_OR_URL1 and
1372 * PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2.
1373 * Return URLs and peg revisions in *URL1, *REV1 and in *URL2, *REV2.
1374 * Return suitable anchors in *ANCHOR1 and *ANCHOR2, and targets in
1375 * *TARGET1 and *TARGET2, based on *URL1 and *URL2.
1376 * Indicate the corresponding node kinds in *KIND1 and *KIND2, and verify
1377 * that at least one of the diff targets exists.
1378 * Set *BASE_PATH corresponding to the URL opened in the new *RA_SESSION
1379 * which is pointing at *ANCHOR1.
1380 * Use client context CTX. Do all allocations in POOL. */
1381 static svn_error_t *
1382 diff_prepare_repos_repos(const char **url1,
1384 const char **base_path,
1387 const char **anchor1,
1388 const char **anchor2,
1389 const char **target1,
1390 const char **target2,
1391 svn_node_kind_t *kind1,
1392 svn_node_kind_t *kind2,
1393 svn_ra_session_t **ra_session,
1394 svn_client_ctx_t *ctx,
1395 const char *path_or_url1,
1396 const char *path_or_url2,
1397 const svn_opt_revision_t *revision1,
1398 const svn_opt_revision_t *revision2,
1399 const svn_opt_revision_t *peg_revision,
1402 const char *abspath_or_url2;
1403 const char *abspath_or_url1;
1404 const char *repos_root_url;
1405 const char *wri_abspath = NULL;
1407 if (!svn_path_is_url(path_or_url2))
1409 SVN_ERR(svn_dirent_get_absolute(&abspath_or_url2, path_or_url2, pool));
1410 SVN_ERR(svn_wc__node_get_url(url2, ctx->wc_ctx, abspath_or_url2,
1412 wri_abspath = abspath_or_url2;
1415 *url2 = abspath_or_url2 = apr_pstrdup(pool, path_or_url2);
1417 if (!svn_path_is_url(path_or_url1))
1419 SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1, pool));
1420 SVN_ERR(svn_wc__node_get_url(url1, ctx->wc_ctx, abspath_or_url1,
1422 wri_abspath = abspath_or_url1;
1425 *url1 = abspath_or_url1 = apr_pstrdup(pool, path_or_url1);
1427 /* We need exactly one BASE_PATH, so we'll let the BASE_PATH
1428 calculated for PATH_OR_URL2 override the one for PATH_OR_URL1
1429 (since the diff will be "applied" to URL2 anyway). */
1431 if (strcmp(*url1, path_or_url1) != 0)
1432 *base_path = path_or_url1;
1433 if (strcmp(*url2, path_or_url2) != 0)
1434 *base_path = path_or_url2;
1436 SVN_ERR(svn_client_open_ra_session2(ra_session, *url2, wri_abspath,
1439 /* If we are performing a pegged diff, we need to find out what our
1440 actual URLs will be. */
1441 if (peg_revision->kind != svn_opt_revision_unspecified)
1443 const char *resolved_url1;
1444 const char *resolved_url2;
1446 SVN_ERR(resolve_pegged_diff_target_url(&resolved_url2, *ra_session,
1447 path_or_url2, peg_revision,
1448 revision2, ctx, pool));
1450 SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
1451 SVN_ERR(resolve_pegged_diff_target_url(&resolved_url1, *ra_session,
1452 path_or_url1, peg_revision,
1453 revision1, ctx, pool));
1455 /* Either or both URLs might have changed as a result of resolving
1456 * the PATH_OR_URL@PEG_REVISION's history. If only one of the URLs
1457 * could be resolved, use the same URL for URL1 and URL2, so we can
1458 * show a diff that adds or removes the object (see issue #4153). */
1461 *url2 = resolved_url2;
1463 *url1 = resolved_url2;
1467 *url1 = resolved_url1;
1469 *url2 = resolved_url1;
1472 /* Reparent the session, since *URL2 might have changed as a result
1474 SVN_ERR(svn_ra_reparent(*ra_session, *url2, pool));
1477 /* Resolve revision and get path kind for the second target. */
1478 SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx,
1479 (path_or_url2 == *url2) ? NULL : abspath_or_url2,
1480 *ra_session, revision2, pool));
1481 SVN_ERR(svn_ra_check_path(*ra_session, "", *rev2, kind2, pool));
1483 /* Do the same for the first target. */
1484 SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
1485 SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx,
1486 (strcmp(path_or_url1, *url1) == 0) ? NULL : abspath_or_url1,
1487 *ra_session, revision1, pool));
1488 SVN_ERR(svn_ra_check_path(*ra_session, "", *rev1, kind1, pool));
1490 /* Either both URLs must exist at their respective revisions,
1491 * or one of them may be missing from one side of the diff. */
1492 if (*kind1 == svn_node_none && *kind2 == svn_node_none)
1494 if (strcmp(*url1, *url2) == 0)
1495 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1496 _("Diff target '%s' was not found in the "
1497 "repository at revisions '%ld' and '%ld'"),
1498 *url1, *rev1, *rev2);
1500 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1501 _("Diff targets '%s' and '%s' were not found "
1502 "in the repository at revisions '%ld' and "
1504 *url1, *url2, *rev1, *rev2);
1506 else if (*kind1 == svn_node_none)
1507 SVN_ERR(check_diff_target_exists(*url1, *rev2, *rev1, *ra_session, pool));
1508 else if (*kind2 == svn_node_none)
1509 SVN_ERR(check_diff_target_exists(*url2, *rev1, *rev2, *ra_session, pool));
1511 SVN_ERR(svn_ra_get_repos_root2(*ra_session, &repos_root_url, pool));
1513 /* Choose useful anchors and targets for our two URLs. */
1519 /* If none of the targets is the repository root open the parent directory
1520 to allow describing replacement of the target itself */
1521 if (strcmp(*url1, repos_root_url) != 0
1522 && strcmp(*url2, repos_root_url) != 0)
1524 svn_uri_split(anchor1, target1, *url1, pool);
1525 svn_uri_split(anchor2, target2, *url2, pool);
1527 && (*kind1 == svn_node_file || *kind2 == svn_node_file))
1528 *base_path = svn_dirent_dirname(*base_path, pool);
1529 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
1532 return SVN_NO_ERROR;
1535 /* A Theoretical Note From Ben, regarding do_diff().
1537 This function is really svn_client_diff6(). If you read the public
1538 API description for svn_client_diff6(), it sounds quite Grand. It
1539 sounds really generalized and abstract and beautiful: that it will
1540 diff any two paths, be they working-copy paths or URLs, at any two
1543 Now, the *reality* is that we have exactly three 'tools' for doing
1544 diffing, and thus this routine is built around the use of the three
1545 tools. Here they are, for clarity:
1547 - svn_wc_diff: assumes both paths are the same wcpath.
1548 compares wcpath@BASE vs. wcpath@WORKING
1550 - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING
1552 - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2
1554 Since Subversion 1.8 we also have a variant of svn_wc_diff called
1555 svn_client__arbitrary_nodes_diff, that allows handling WORKING-WORKING
1556 comparisions between nodes in the working copy.
1558 So the truth of the matter is, if the caller's arguments can't be
1559 pigeonholed into one of these use-cases, we currently bail with a
1562 Perhaps someday a brave soul will truly make svn_client_diff6()
1563 perfectly general. For now, we live with the 90% case. Certainly,
1564 the commandline client only calls this function in legal ways.
1565 When there are other users of svn_client.h, maybe this will become
1566 a more pressing issue.
1569 /* Return a "you can't do that" error, optionally wrapping another
1571 static svn_error_t *
1572 unsupported_diff_error(svn_error_t *child_err)
1574 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
1575 _("Sorry, svn_client_diff6 was called in a way "
1576 "that is not yet supported"));
1579 /* Perform a diff between two working-copy paths.
1581 PATH1 and PATH2 are both working copy paths. REVISION1 and
1582 REVISION2 are their respective revisions.
1584 All other options are the same as those passed to svn_client_diff6(). */
1585 static svn_error_t *
1586 diff_wc_wc(const char *path1,
1587 const svn_opt_revision_t *revision1,
1589 const svn_opt_revision_t *revision2,
1591 svn_boolean_t ignore_ancestry,
1592 svn_boolean_t show_copies_as_adds,
1593 svn_boolean_t use_git_diff_format,
1594 const apr_array_header_t *changelists,
1595 const svn_wc_diff_callbacks4_t *callbacks,
1596 struct diff_cmd_baton *callback_baton,
1597 svn_client_ctx_t *ctx,
1600 const char *abspath1;
1602 svn_node_kind_t kind;
1604 SVN_ERR_ASSERT(! svn_path_is_url(path1));
1605 SVN_ERR_ASSERT(! svn_path_is_url(path2));
1607 SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, pool));
1609 /* Currently we support only the case where path1 and path2 are the
1611 if ((strcmp(path1, path2) != 0)
1612 || (! ((revision1->kind == svn_opt_revision_base)
1613 && (revision2->kind == svn_opt_revision_working))))
1614 return unsupported_diff_error(
1615 svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1616 _("Only diffs between a path's text-base "
1617 "and its working files are supported at this time"
1621 /* Resolve named revisions to real numbers. */
1622 err = svn_client__get_revision_number(&callback_baton->revnum1, NULL,
1623 ctx->wc_ctx, abspath1, NULL,
1626 /* In case of an added node, we have no base rev, and we show a revision
1627 * number of 0. Note that this code is currently always asking for
1628 * svn_opt_revision_base.
1629 * ### TODO: get rid of this 0 for added nodes. */
1630 if (err && (err->apr_err == SVN_ERR_CLIENT_BAD_REVISION))
1632 svn_error_clear(err);
1633 callback_baton->revnum1 = 0;
1638 callback_baton->revnum2 = SVN_INVALID_REVNUM; /* WC */
1640 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
1641 TRUE, FALSE, pool));
1643 if (kind != svn_node_dir)
1644 callback_baton->anchor = svn_dirent_dirname(path1, pool);
1646 callback_baton->anchor = path1;
1648 SVN_ERR(svn_wc_diff6(ctx->wc_ctx,
1650 callbacks, callback_baton,
1652 ignore_ancestry, show_copies_as_adds,
1653 use_git_diff_format, changelists,
1654 ctx->cancel_func, ctx->cancel_baton,
1656 return SVN_NO_ERROR;
1659 /* Perform a diff between two repository paths.
1661 PATH_OR_URL1 and PATH_OR_URL2 may be either URLs or the working copy paths.
1662 REVISION1 and REVISION2 are their respective revisions.
1663 If PEG_REVISION is specified, PATH_OR_URL2 is the path at the peg revision,
1664 and the actual two paths compared are determined by following copy
1665 history from PATH_OR_URL2.
1667 All other options are the same as those passed to svn_client_diff6(). */
1668 static svn_error_t *
1669 diff_repos_repos(const svn_wc_diff_callbacks4_t *callbacks,
1670 struct diff_cmd_baton *callback_baton,
1671 svn_client_ctx_t *ctx,
1672 const char *path_or_url1,
1673 const char *path_or_url2,
1674 const svn_opt_revision_t *revision1,
1675 const svn_opt_revision_t *revision2,
1676 const svn_opt_revision_t *peg_revision,
1678 svn_boolean_t ignore_ancestry,
1681 svn_ra_session_t *extra_ra_session;
1683 const svn_ra_reporter3_t *reporter;
1684 void *reporter_baton;
1686 const svn_delta_editor_t *diff_editor;
1687 void *diff_edit_baton;
1689 const svn_diff_tree_processor_t *diff_processor;
1693 const char *base_path;
1696 svn_node_kind_t kind1;
1697 svn_node_kind_t kind2;
1698 const char *anchor1;
1699 const char *anchor2;
1700 const char *target1;
1701 const char *target2;
1702 svn_ra_session_t *ra_session;
1703 const char *wri_abspath = NULL;
1705 /* Prepare info for the repos repos diff. */
1706 SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &base_path, &rev1, &rev2,
1707 &anchor1, &anchor2, &target1, &target2,
1708 &kind1, &kind2, &ra_session,
1709 ctx, path_or_url1, path_or_url2,
1710 revision1, revision2, peg_revision,
1713 /* Find a WC path for the ra session */
1714 if (!svn_path_is_url(path_or_url1))
1715 SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url1, pool));
1716 else if (!svn_path_is_url(path_or_url2))
1717 SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url2, pool));
1719 /* Set up the repos_diff editor on BASE_PATH, if available.
1720 Otherwise, we just use "". */
1722 SVN_ERR(svn_wc__wrap_diff_callbacks(&diff_processor,
1723 callbacks, callback_baton,
1724 TRUE /* walk_deleted_dirs */,
1727 /* Get actual URLs. */
1728 callback_baton->orig_path_1 = url1;
1729 callback_baton->orig_path_2 = url2;
1731 /* Get numeric revisions. */
1732 callback_baton->revnum1 = rev1;
1733 callback_baton->revnum2 = rev2;
1735 callback_baton->ra_session = ra_session;
1736 callback_baton->anchor = base_path;
1738 /* The repository can bring in a new working copy, but not delete
1739 everything. Luckily our new diff handler can just be reversed. */
1740 if (kind2 == svn_node_none)
1742 const char *str_tmp;
1743 svn_revnum_t rev_tmp;
1761 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
1765 /* Filter the first path component using a filter processor, until we fixed
1766 the diff processing to handle this directly */
1767 if ((kind1 != svn_node_file && kind2 != svn_node_file) && target1[0] != '\0')
1769 diff_processor = svn_diff__tree_processor_filter_create(diff_processor,
1773 /* Now, we open an extra RA session to the correct anchor
1774 location for URL1. This is used during the editor calls to fetch file
1776 SVN_ERR(svn_client_open_ra_session2(&extra_ra_session, anchor1, wri_abspath,
1779 SVN_ERR(svn_client__get_diff_editor2(
1780 &diff_editor, &diff_edit_baton,
1781 extra_ra_session, depth,
1783 TRUE /* text_deltas */,
1785 ctx->cancel_func, ctx->cancel_baton,
1788 /* We want to switch our txn into URL2 */
1789 SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton,
1791 depth, ignore_ancestry, TRUE /* text_deltas */,
1792 url2, diff_editor, diff_edit_baton, pool));
1794 /* Drive the reporter; do the diff. */
1795 SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
1800 return svn_error_trace(reporter->finish_report(reporter_baton, pool));
1803 /* Perform a diff between a repository path and a working-copy path.
1805 PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a
1806 working copy path. REVISION1 and REVISION2 are their respective
1807 revisions. If REVERSE is TRUE, the diff will be done in reverse.
1808 If PEG_REVISION is specified, then PATH_OR_URL1 is the path in the peg
1809 revision, and the actual repository path to be compared is
1810 determined by following copy history.
1812 All other options are the same as those passed to svn_client_diff6(). */
1813 static svn_error_t *
1814 diff_repos_wc(const char *path_or_url1,
1815 const svn_opt_revision_t *revision1,
1816 const svn_opt_revision_t *peg_revision,
1818 const svn_opt_revision_t *revision2,
1819 svn_boolean_t reverse,
1821 svn_boolean_t ignore_ancestry,
1822 svn_boolean_t show_copies_as_adds,
1823 svn_boolean_t use_git_diff_format,
1824 const apr_array_header_t *changelists,
1825 const svn_wc_diff_callbacks4_t *callbacks,
1826 void *callback_baton,
1827 struct diff_cmd_baton *cmd_baton,
1828 svn_client_ctx_t *ctx,
1829 apr_pool_t *scratch_pool)
1831 apr_pool_t *pool = scratch_pool;
1832 const char *url1, *anchor, *anchor_url, *target;
1834 svn_ra_session_t *ra_session;
1835 svn_depth_t diff_depth;
1836 const svn_ra_reporter3_t *reporter;
1837 void *reporter_baton;
1838 const svn_delta_editor_t *diff_editor;
1839 void *diff_edit_baton;
1840 svn_boolean_t rev2_is_base = (revision2->kind == svn_opt_revision_base);
1841 svn_boolean_t server_supports_depth;
1842 const char *abspath_or_url1;
1843 const char *abspath2;
1844 const char *anchor_abspath;
1845 svn_node_kind_t kind1;
1846 svn_node_kind_t kind2;
1847 svn_boolean_t is_copy;
1848 svn_revnum_t cf_revision;
1849 const char *cf_repos_relpath;
1850 const char *cf_repos_root_url;
1852 SVN_ERR_ASSERT(! svn_path_is_url(path2));
1854 if (!svn_path_is_url(path_or_url1))
1856 SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1, pool));
1857 SVN_ERR(svn_wc__node_get_url(&url1, ctx->wc_ctx, abspath_or_url1,
1862 url1 = path_or_url1;
1863 abspath_or_url1 = path_or_url1;
1866 SVN_ERR(svn_dirent_get_absolute(&abspath2, path2, pool));
1868 /* Convert path_or_url1 to a URL to feed to do_diff. */
1869 SVN_ERR(svn_wc_get_actual_target2(&anchor, &target,
1873 /* Fetch the URL of the anchor directory. */
1874 SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, pool));
1875 SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
1877 SVN_ERR_ASSERT(anchor_url != NULL);
1879 /* If we are performing a pegged diff, we need to find out what our
1880 actual URLs will be. */
1881 if (peg_revision->kind != svn_opt_revision_unspecified)
1883 SVN_ERR(svn_client__repos_locations(&url1, NULL, NULL, NULL,
1891 cmd_baton->orig_path_1 = url1;
1892 cmd_baton->orig_path_2 =
1893 svn_path_url_add_component2(anchor_url, target, pool);
1897 cmd_baton->orig_path_1 =
1898 svn_path_url_add_component2(anchor_url, target, pool);
1899 cmd_baton->orig_path_2 = url1;
1903 /* Open an RA session to URL1 to figure out its node kind. */
1904 SVN_ERR(svn_client_open_ra_session2(&ra_session, url1, abspath2,
1906 /* Resolve the revision to use for URL1. */
1907 SVN_ERR(svn_client__get_revision_number(&rev, NULL, ctx->wc_ctx,
1908 (strcmp(path_or_url1, url1) == 0)
1909 ? NULL : abspath_or_url1,
1910 ra_session, revision1, pool));
1911 SVN_ERR(svn_ra_check_path(ra_session, "", rev, &kind1, pool));
1913 /* Figure out the node kind of the local target. */
1914 SVN_ERR(svn_wc_read_kind2(&kind2, ctx->wc_ctx, abspath2,
1915 TRUE, FALSE, pool));
1917 cmd_baton->ra_session = ra_session;
1918 cmd_baton->anchor = anchor;
1921 cmd_baton->revnum1 = rev;
1923 cmd_baton->revnum2 = rev;
1925 /* Check if our diff target is a copied node. */
1926 SVN_ERR(svn_wc__node_get_origin(&is_copy,
1931 ctx->wc_ctx, abspath2,
1932 FALSE, pool, pool));
1934 /* Use the diff editor to generate the diff. */
1935 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
1936 SVN_RA_CAPABILITY_DEPTH, pool));
1937 SVN_ERR(svn_wc__get_diff_editor(&diff_editor, &diff_edit_baton,
1942 ignore_ancestry || is_copy,
1943 show_copies_as_adds,
1944 use_git_diff_format,
1947 server_supports_depth,
1949 callbacks, callback_baton,
1950 ctx->cancel_func, ctx->cancel_baton,
1952 SVN_ERR(svn_ra_reparent(ra_session, anchor_url, pool));
1954 if (depth != svn_depth_infinity)
1957 diff_depth = svn_depth_unknown;
1961 const char *copyfrom_parent_url;
1962 const char *copyfrom_basename;
1963 svn_depth_t copy_depth;
1965 cmd_baton->repos_wc_diff_target_is_copy = TRUE;
1967 /* We're diffing a locally copied/moved node.
1968 * Describe the copy source to the reporter instead of the copy itself.
1969 * Doing the latter would generate a single add_directory() call to the
1970 * diff editor which results in an unexpected diff (the copy would
1971 * be shown as deleted). */
1973 if (cf_repos_relpath[0] == '\0')
1975 copyfrom_parent_url = cf_repos_root_url;
1976 copyfrom_basename = "";
1980 const char *parent_relpath;
1981 svn_relpath_split(&parent_relpath, ©from_basename,
1982 cf_repos_relpath, scratch_pool);
1984 copyfrom_parent_url = svn_path_url_add_component2(cf_repos_root_url,
1988 SVN_ERR(svn_ra_reparent(ra_session, copyfrom_parent_url, pool));
1990 /* Tell the RA layer we want a delta to change our txn to URL1 */
1991 SVN_ERR(svn_ra_do_diff3(ra_session,
1992 &reporter, &reporter_baton,
1997 TRUE, /* text_deltas */
1999 diff_editor, diff_edit_baton, pool));
2001 /* Report the copy source. */
2002 SVN_ERR(svn_wc__node_get_depth(©_depth, ctx->wc_ctx, abspath2,
2005 if (copy_depth == svn_depth_unknown)
2006 copy_depth = svn_depth_infinity;
2008 SVN_ERR(reporter->set_path(reporter_baton, "",
2010 copy_depth, FALSE, NULL, scratch_pool));
2012 if (strcmp(target, copyfrom_basename) != 0)
2013 SVN_ERR(reporter->link_path(reporter_baton, target,
2014 svn_path_url_add_component2(
2019 copy_depth, FALSE, NULL, scratch_pool));
2021 /* Finish the report to generate the diff. */
2022 SVN_ERR(reporter->finish_report(reporter_baton, pool));
2026 /* Tell the RA layer we want a delta to change our txn to URL1 */
2027 SVN_ERR(svn_ra_do_diff3(ra_session,
2028 &reporter, &reporter_baton,
2033 TRUE, /* text_deltas */
2035 diff_editor, diff_edit_baton, pool));
2037 /* Create a txn mirror of path2; the diff editor will print
2038 diffs in reverse. :-) */
2039 SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, abspath2,
2040 reporter, reporter_baton,
2042 (! server_supports_depth),
2044 ctx->cancel_func, ctx->cancel_baton,
2045 NULL, NULL, /* notification is N/A */
2049 return SVN_NO_ERROR;
2053 /* This is basically just the guts of svn_client_diff[_peg]6(). */
2054 static svn_error_t *
2055 do_diff(const svn_wc_diff_callbacks4_t *callbacks,
2056 struct diff_cmd_baton *callback_baton,
2057 svn_client_ctx_t *ctx,
2058 const char *path_or_url1,
2059 const char *path_or_url2,
2060 const svn_opt_revision_t *revision1,
2061 const svn_opt_revision_t *revision2,
2062 const svn_opt_revision_t *peg_revision,
2064 svn_boolean_t ignore_ancestry,
2065 svn_boolean_t show_copies_as_adds,
2066 svn_boolean_t use_git_diff_format,
2067 const apr_array_header_t *changelists,
2070 svn_boolean_t is_repos1;
2071 svn_boolean_t is_repos2;
2073 /* Check if paths/revisions are urls/local. */
2074 SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2,
2075 revision1, revision2, peg_revision));
2081 /* ### Ignores 'show_copies_as_adds'. */
2082 SVN_ERR(diff_repos_repos(callbacks, callback_baton, ctx,
2083 path_or_url1, path_or_url2,
2084 revision1, revision2,
2085 peg_revision, depth, ignore_ancestry,
2088 else /* path_or_url2 is a working copy path */
2090 SVN_ERR(diff_repos_wc(path_or_url1, revision1, peg_revision,
2091 path_or_url2, revision2, FALSE, depth,
2092 ignore_ancestry, show_copies_as_adds,
2093 use_git_diff_format, changelists,
2094 callbacks, callback_baton, callback_baton,
2098 else /* path_or_url1 is a working copy path */
2102 SVN_ERR(diff_repos_wc(path_or_url2, revision2, peg_revision,
2103 path_or_url1, revision1, TRUE, depth,
2104 ignore_ancestry, show_copies_as_adds,
2105 use_git_diff_format, changelists,
2106 callbacks, callback_baton, callback_baton,
2109 else /* path_or_url2 is a working copy path */
2111 if (revision1->kind == svn_opt_revision_working
2112 && revision2->kind == svn_opt_revision_working)
2114 const char *abspath1;
2115 const char *abspath2;
2117 SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, pool));
2118 SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, pool));
2120 SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2,
2127 SVN_ERR(diff_wc_wc(path_or_url1, revision1,
2128 path_or_url2, revision2,
2129 depth, ignore_ancestry, show_copies_as_adds,
2130 use_git_diff_format, changelists,
2131 callbacks, callback_baton, ctx, pool));
2135 return SVN_NO_ERROR;
2138 /* Perform a diff between a repository path and a working-copy path.
2140 PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a
2141 working copy path. REVISION1 and REVISION2 are their respective
2142 revisions. If REVERSE is TRUE, the diff will be done in reverse.
2143 If PEG_REVISION is specified, then PATH_OR_URL1 is the path in the peg
2144 revision, and the actual repository path to be compared is
2145 determined by following copy history.
2147 All other options are the same as those passed to svn_client_diff6(). */
2148 static svn_error_t *
2149 diff_summarize_repos_wc(svn_client_diff_summarize_func_t summarize_func,
2150 void *summarize_baton,
2151 const char *path_or_url1,
2152 const svn_opt_revision_t *revision1,
2153 const svn_opt_revision_t *peg_revision,
2155 const svn_opt_revision_t *revision2,
2156 svn_boolean_t reverse,
2158 svn_boolean_t ignore_ancestry,
2159 const apr_array_header_t *changelists,
2160 svn_client_ctx_t *ctx,
2163 const char *anchor, *target;
2164 svn_wc_diff_callbacks4_t *callbacks;
2165 void *callback_baton;
2166 struct diff_cmd_baton cmd_baton;
2168 SVN_ERR_ASSERT(! svn_path_is_url(path2));
2170 SVN_ERR(svn_wc_get_actual_target2(&anchor, &target,
2174 SVN_ERR(svn_client__get_diff_summarize_callbacks(
2175 &callbacks, &callback_baton, target, reverse,
2176 summarize_func, summarize_baton, pool));
2178 SVN_ERR(diff_repos_wc(path_or_url1, revision1, peg_revision,
2179 path2, revision2, reverse,
2180 depth, FALSE, TRUE, FALSE, changelists,
2181 callbacks, callback_baton, &cmd_baton,
2183 return SVN_NO_ERROR;
2186 /* Perform a summary diff between two working-copy paths.
2188 PATH1 and PATH2 are both working copy paths. REVISION1 and
2189 REVISION2 are their respective revisions.
2191 All other options are the same as those passed to svn_client_diff6(). */
2192 static svn_error_t *
2193 diff_summarize_wc_wc(svn_client_diff_summarize_func_t summarize_func,
2194 void *summarize_baton,
2196 const svn_opt_revision_t *revision1,
2198 const svn_opt_revision_t *revision2,
2200 svn_boolean_t ignore_ancestry,
2201 const apr_array_header_t *changelists,
2202 svn_client_ctx_t *ctx,
2205 svn_wc_diff_callbacks4_t *callbacks;
2206 void *callback_baton;
2207 const char *abspath1, *target1;
2208 svn_node_kind_t kind;
2210 SVN_ERR_ASSERT(! svn_path_is_url(path1));
2211 SVN_ERR_ASSERT(! svn_path_is_url(path2));
2213 /* Currently we support only the case where path1 and path2 are the
2215 if ((strcmp(path1, path2) != 0)
2216 || (! ((revision1->kind == svn_opt_revision_base)
2217 && (revision2->kind == svn_opt_revision_working))))
2218 return unsupported_diff_error
2220 (SVN_ERR_INCORRECT_PARAMS, NULL,
2221 _("Summarized diffs are only supported between a path's text-base "
2222 "and its working files at this time")));
2224 /* Find the node kind of PATH1 so that we know whether the diff drive will
2225 be anchored at PATH1 or its parent dir. */
2226 SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, pool));
2227 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
2228 TRUE, FALSE, pool));
2229 target1 = (kind == svn_node_dir) ? "" : svn_dirent_basename(path1, pool);
2230 SVN_ERR(svn_client__get_diff_summarize_callbacks(
2231 &callbacks, &callback_baton, target1, FALSE,
2232 summarize_func, summarize_baton, pool));
2234 SVN_ERR(svn_wc_diff6(ctx->wc_ctx,
2236 callbacks, callback_baton,
2238 ignore_ancestry, FALSE /* show_copies_as_adds */,
2239 FALSE /* use_git_diff_format */, changelists,
2240 ctx->cancel_func, ctx->cancel_baton,
2242 return SVN_NO_ERROR;
2245 /* Perform a diff summary between two repository paths. */
2246 static svn_error_t *
2247 diff_summarize_repos_repos(svn_client_diff_summarize_func_t summarize_func,
2248 void *summarize_baton,
2249 svn_client_ctx_t *ctx,
2250 const char *path_or_url1,
2251 const char *path_or_url2,
2252 const svn_opt_revision_t *revision1,
2253 const svn_opt_revision_t *revision2,
2254 const svn_opt_revision_t *peg_revision,
2256 svn_boolean_t ignore_ancestry,
2259 svn_ra_session_t *extra_ra_session;
2261 const svn_ra_reporter3_t *reporter;
2262 void *reporter_baton;
2264 const svn_delta_editor_t *diff_editor;
2265 void *diff_edit_baton;
2267 const svn_diff_tree_processor_t *diff_processor;
2271 const char *base_path;
2274 svn_node_kind_t kind1;
2275 svn_node_kind_t kind2;
2276 const char *anchor1;
2277 const char *anchor2;
2278 const char *target1;
2279 const char *target2;
2280 svn_ra_session_t *ra_session;
2281 svn_wc_diff_callbacks4_t *callbacks;
2282 void *callback_baton;
2284 /* Prepare info for the repos repos diff. */
2285 SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &base_path, &rev1, &rev2,
2286 &anchor1, &anchor2, &target1, &target2,
2287 &kind1, &kind2, &ra_session,
2288 ctx, path_or_url1, path_or_url2,
2289 revision1, revision2,
2290 peg_revision, pool));
2292 /* Set up the repos_diff editor. */
2293 SVN_ERR(svn_client__get_diff_summarize_callbacks(
2294 &callbacks, &callback_baton,
2295 target1, FALSE, summarize_func, summarize_baton, pool));
2297 SVN_ERR(svn_wc__wrap_diff_callbacks(&diff_processor,
2298 callbacks, callback_baton,
2299 TRUE /* walk_deleted_dirs */,
2303 /* The repository can bring in a new working copy, but not delete
2304 everything. Luckily our new diff handler can just be reversed. */
2305 if (kind2 == svn_node_none)
2307 const char *str_tmp;
2308 svn_revnum_t rev_tmp;
2326 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
2330 /* Now, we open an extra RA session to the correct anchor
2331 location for URL1. This is used to get deleted path information. */
2332 SVN_ERR(svn_client_open_ra_session2(&extra_ra_session, anchor1, NULL,
2335 SVN_ERR(svn_client__get_diff_editor2(&diff_editor, &diff_edit_baton,
2339 FALSE /* text_deltas */,
2341 ctx->cancel_func, ctx->cancel_baton,
2344 /* We want to switch our txn into URL2 */
2345 SVN_ERR(svn_ra_do_diff3
2346 (ra_session, &reporter, &reporter_baton, rev2, target1,
2347 depth, ignore_ancestry,
2348 FALSE /* do not create text delta */, url2, diff_editor,
2349 diff_edit_baton, pool));
2351 /* Drive the reporter; do the diff. */
2352 SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
2354 FALSE, NULL, pool));
2355 return svn_error_trace(reporter->finish_report(reporter_baton, pool));
2358 /* This is basically just the guts of svn_client_diff_summarize[_peg]2(). */
2359 static svn_error_t *
2360 do_diff_summarize(svn_client_diff_summarize_func_t summarize_func,
2361 void *summarize_baton,
2362 svn_client_ctx_t *ctx,
2363 const char *path_or_url1,
2364 const char *path_or_url2,
2365 const svn_opt_revision_t *revision1,
2366 const svn_opt_revision_t *revision2,
2367 const svn_opt_revision_t *peg_revision,
2369 svn_boolean_t ignore_ancestry,
2370 const apr_array_header_t *changelists,
2373 svn_boolean_t is_repos1;
2374 svn_boolean_t is_repos2;
2376 /* Check if paths/revisions are urls/local. */
2377 SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2,
2378 revision1, revision2, peg_revision));
2383 SVN_ERR(diff_summarize_repos_repos(summarize_func, summarize_baton, ctx,
2384 path_or_url1, path_or_url2,
2385 revision1, revision2,
2386 peg_revision, depth, ignore_ancestry,
2389 SVN_ERR(diff_summarize_repos_wc(summarize_func, summarize_baton,
2390 path_or_url1, revision1,
2392 path_or_url2, revision2,
2398 else /* ! is_repos1 */
2401 SVN_ERR(diff_summarize_repos_wc(summarize_func, summarize_baton,
2402 path_or_url2, revision2,
2404 path_or_url1, revision1,
2411 if (revision1->kind == svn_opt_revision_working
2412 && revision2->kind == svn_opt_revision_working)
2414 const char *abspath1;
2415 const char *abspath2;
2416 svn_wc_diff_callbacks4_t *callbacks;
2417 void *callback_baton;
2419 svn_node_kind_t kind;
2421 SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, pool));
2422 SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, pool));
2424 SVN_ERR(svn_io_check_resolved_path(abspath1, &kind, pool));
2426 if (kind == svn_node_dir)
2429 target = svn_dirent_basename(path_or_url1, NULL);
2431 SVN_ERR(svn_client__get_diff_summarize_callbacks(
2432 &callbacks, &callback_baton, target, FALSE,
2433 summarize_func, summarize_baton, pool));
2435 SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2,
2442 SVN_ERR(diff_summarize_wc_wc(summarize_func, summarize_baton,
2443 path_or_url1, revision1,
2444 path_or_url2, revision2,
2445 depth, ignore_ancestry,
2446 changelists, ctx, pool));
2450 return SVN_NO_ERROR;
2454 /* Initialize DIFF_CMD_BATON.diff_cmd and DIFF_CMD_BATON.options,
2455 * according to OPTIONS and CONFIG. CONFIG and OPTIONS may be null.
2456 * Allocate the fields in POOL, which should be at least as long-lived
2457 * as the pool DIFF_CMD_BATON itself is allocated in.
2459 static svn_error_t *
2460 set_up_diff_cmd_and_options(struct diff_cmd_baton *diff_cmd_baton,
2461 const apr_array_header_t *options,
2462 apr_hash_t *config, apr_pool_t *pool)
2464 const char *diff_cmd = NULL;
2466 /* See if there is a diff command and/or diff arguments. */
2469 svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
2470 svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
2471 SVN_CONFIG_OPTION_DIFF_CMD, NULL);
2472 if (options == NULL)
2474 const char *diff_extensions;
2475 svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS,
2476 SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL);
2477 if (diff_extensions)
2478 options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE, pool);
2482 if (options == NULL)
2483 options = apr_array_make(pool, 0, sizeof(const char *));
2486 SVN_ERR(svn_path_cstring_to_utf8(&diff_cmd_baton->diff_cmd, diff_cmd,
2489 diff_cmd_baton->diff_cmd = NULL;
2491 /* If there was a command, arrange options to pass to it. */
2492 if (diff_cmd_baton->diff_cmd)
2494 const char **argv = NULL;
2495 int argc = options->nelts;
2499 argv = apr_palloc(pool, argc * sizeof(char *));
2500 for (i = 0; i < argc; i++)
2501 SVN_ERR(svn_utf_cstring_to_utf8(&argv[i],
2502 APR_ARRAY_IDX(options, i, const char *), pool));
2504 diff_cmd_baton->options.for_external.argv = argv;
2505 diff_cmd_baton->options.for_external.argc = argc;
2507 else /* No command, so arrange options for internal invocation instead. */
2509 diff_cmd_baton->options.for_internal
2510 = svn_diff_file_options_create(pool);
2511 SVN_ERR(svn_diff_file_options_parse
2512 (diff_cmd_baton->options.for_internal, options, pool));
2515 return SVN_NO_ERROR;
2518 /*----------------------------------------------------------------------- */
2520 /*** Public Interfaces. ***/
2522 /* Display context diffs between two PATH/REVISION pairs. Each of
2523 these inputs will be one of the following:
2525 - a repository URL at a given revision.
2526 - a working copy path, ignoring local mods.
2527 - a working copy path, including local mods.
2529 We can establish a matrix that shows the nine possible types of
2530 diffs we expect to support.
2533 ` . DST || URL:rev | WC:base | WC:working |
2536 ============++============+============+============+
2537 URL:rev || (*) | (*) | (*) |
2541 ------------++------------+------------+------------+
2543 || | New svn_wc_diff which |
2544 || | is smart enough to |
2545 || | handle two WC paths |
2546 ------------++------------+ and their related +
2547 WC:working || (*) | text-bases and working |
2548 || | files. This operation |
2549 || | is entirely local. |
2551 ------------++------------+------------+------------+
2552 * These cases require server communication.
2555 svn_client_diff6(const apr_array_header_t *options,
2556 const char *path_or_url1,
2557 const svn_opt_revision_t *revision1,
2558 const char *path_or_url2,
2559 const svn_opt_revision_t *revision2,
2560 const char *relative_to_dir,
2562 svn_boolean_t ignore_ancestry,
2563 svn_boolean_t no_diff_added,
2564 svn_boolean_t no_diff_deleted,
2565 svn_boolean_t show_copies_as_adds,
2566 svn_boolean_t ignore_content_type,
2567 svn_boolean_t ignore_properties,
2568 svn_boolean_t properties_only,
2569 svn_boolean_t use_git_diff_format,
2570 const char *header_encoding,
2571 svn_stream_t *outstream,
2572 svn_stream_t *errstream,
2573 const apr_array_header_t *changelists,
2574 svn_client_ctx_t *ctx,
2577 struct diff_cmd_baton diff_cmd_baton = { 0 };
2578 svn_opt_revision_t peg_revision;
2580 if (ignore_properties && properties_only)
2581 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2582 _("Cannot ignore properties and show only "
2583 "properties at the same time"));
2585 /* We will never do a pegged diff from here. */
2586 peg_revision.kind = svn_opt_revision_unspecified;
2588 /* setup callback and baton */
2589 diff_cmd_baton.orig_path_1 = path_or_url1;
2590 diff_cmd_baton.orig_path_2 = path_or_url2;
2592 SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton, options,
2593 ctx->config, pool));
2594 diff_cmd_baton.pool = pool;
2595 diff_cmd_baton.outstream = outstream;
2596 diff_cmd_baton.errstream = errstream;
2597 diff_cmd_baton.header_encoding = header_encoding;
2598 diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
2599 diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
2601 diff_cmd_baton.force_binary = ignore_content_type;
2602 diff_cmd_baton.ignore_properties = ignore_properties;
2603 diff_cmd_baton.properties_only = properties_only;
2604 diff_cmd_baton.relative_to_dir = relative_to_dir;
2605 diff_cmd_baton.use_git_diff_format = use_git_diff_format;
2606 diff_cmd_baton.no_diff_added = no_diff_added;
2607 diff_cmd_baton.no_diff_deleted = no_diff_deleted;
2608 diff_cmd_baton.no_copyfrom_on_add = show_copies_as_adds;
2610 diff_cmd_baton.wc_ctx = ctx->wc_ctx;
2611 diff_cmd_baton.ra_session = NULL;
2612 diff_cmd_baton.anchor = NULL;
2614 return do_diff(&diff_callbacks, &diff_cmd_baton, ctx,
2615 path_or_url1, path_or_url2, revision1, revision2,
2617 depth, ignore_ancestry, show_copies_as_adds,
2618 use_git_diff_format, changelists, pool);
2622 svn_client_diff_peg6(const apr_array_header_t *options,
2623 const char *path_or_url,
2624 const svn_opt_revision_t *peg_revision,
2625 const svn_opt_revision_t *start_revision,
2626 const svn_opt_revision_t *end_revision,
2627 const char *relative_to_dir,
2629 svn_boolean_t ignore_ancestry,
2630 svn_boolean_t no_diff_added,
2631 svn_boolean_t no_diff_deleted,
2632 svn_boolean_t show_copies_as_adds,
2633 svn_boolean_t ignore_content_type,
2634 svn_boolean_t ignore_properties,
2635 svn_boolean_t properties_only,
2636 svn_boolean_t use_git_diff_format,
2637 const char *header_encoding,
2638 svn_stream_t *outstream,
2639 svn_stream_t *errstream,
2640 const apr_array_header_t *changelists,
2641 svn_client_ctx_t *ctx,
2644 struct diff_cmd_baton diff_cmd_baton = { 0 };
2646 if (ignore_properties && properties_only)
2647 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2648 _("Cannot ignore properties and show only "
2649 "properties at the same time"));
2651 /* setup callback and baton */
2652 diff_cmd_baton.orig_path_1 = path_or_url;
2653 diff_cmd_baton.orig_path_2 = path_or_url;
2655 SVN_ERR(set_up_diff_cmd_and_options(&diff_cmd_baton, options,
2656 ctx->config, pool));
2657 diff_cmd_baton.pool = pool;
2658 diff_cmd_baton.outstream = outstream;
2659 diff_cmd_baton.errstream = errstream;
2660 diff_cmd_baton.header_encoding = header_encoding;
2661 diff_cmd_baton.revnum1 = SVN_INVALID_REVNUM;
2662 diff_cmd_baton.revnum2 = SVN_INVALID_REVNUM;
2664 diff_cmd_baton.force_binary = ignore_content_type;
2665 diff_cmd_baton.ignore_properties = ignore_properties;
2666 diff_cmd_baton.properties_only = properties_only;
2667 diff_cmd_baton.relative_to_dir = relative_to_dir;
2668 diff_cmd_baton.use_git_diff_format = use_git_diff_format;
2669 diff_cmd_baton.no_diff_added = no_diff_added;
2670 diff_cmd_baton.no_diff_deleted = no_diff_deleted;
2671 diff_cmd_baton.no_copyfrom_on_add = show_copies_as_adds;
2673 diff_cmd_baton.wc_ctx = ctx->wc_ctx;
2674 diff_cmd_baton.ra_session = NULL;
2675 diff_cmd_baton.anchor = NULL;
2677 return do_diff(&diff_callbacks, &diff_cmd_baton, ctx,
2678 path_or_url, path_or_url, start_revision, end_revision,
2680 depth, ignore_ancestry, show_copies_as_adds,
2681 use_git_diff_format, changelists, pool);
2685 svn_client_diff_summarize2(const char *path_or_url1,
2686 const svn_opt_revision_t *revision1,
2687 const char *path_or_url2,
2688 const svn_opt_revision_t *revision2,
2690 svn_boolean_t ignore_ancestry,
2691 const apr_array_header_t *changelists,
2692 svn_client_diff_summarize_func_t summarize_func,
2693 void *summarize_baton,
2694 svn_client_ctx_t *ctx,
2697 /* We will never do a pegged diff from here. */
2698 svn_opt_revision_t peg_revision;
2699 peg_revision.kind = svn_opt_revision_unspecified;
2701 return do_diff_summarize(summarize_func, summarize_baton, ctx,
2702 path_or_url1, path_or_url2, revision1, revision2,
2704 depth, ignore_ancestry, changelists, pool);
2708 svn_client_diff_summarize_peg2(const char *path_or_url,
2709 const svn_opt_revision_t *peg_revision,
2710 const svn_opt_revision_t *start_revision,
2711 const svn_opt_revision_t *end_revision,
2713 svn_boolean_t ignore_ancestry,
2714 const apr_array_header_t *changelists,
2715 svn_client_diff_summarize_func_t summarize_func,
2716 void *summarize_baton,
2717 svn_client_ctx_t *ctx,
2720 return do_diff_summarize(summarize_func, summarize_baton, ctx,
2721 path_or_url, path_or_url,
2722 start_revision, end_revision, peg_revision,
2723 depth, ignore_ancestry, changelists, pool);
2726 svn_client_diff_summarize_t *
2727 svn_client_diff_summarize_dup(const svn_client_diff_summarize_t *diff,
2730 svn_client_diff_summarize_t *dup_diff = apr_palloc(pool, sizeof(*dup_diff));
2735 dup_diff->path = apr_pstrdup(pool, diff->path);