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"
55 #include "private/svn_ra_private.h"
57 #include "svn_private_config.h"
62 #define DIFF_REVNUM_NONEXISTENT ((svn_revnum_t) -100)
64 #define MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir) \
65 svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL, \
66 _("Path '%s' must be an immediate child of " \
67 "the directory '%s'"), path, relative_to_dir)
69 /* Calculate the repository relative path of DIFF_RELPATH, using
70 * SESSION_RELPATH and WC_CTX, and return the result in *REPOS_RELPATH.
71 * ORIG_TARGET is the related original target passed to the diff command,
72 * and may be used to derive leading path components missing from PATH.
73 * ANCHOR is the local path where the diff editor is anchored.
74 * Do all allocations in POOL. */
76 make_repos_relpath(const char **repos_relpath,
77 const char *diff_relpath,
78 const char *orig_target,
79 const char *session_relpath,
80 svn_wc_context_t *wc_ctx,
82 apr_pool_t *result_pool,
83 apr_pool_t *scratch_pool)
85 const char *local_abspath;
88 || (anchor && !svn_path_is_url(orig_target)))
91 /* We're doing a WC-WC diff, so we can retrieve all information we
92 * need from the working copy. */
93 SVN_ERR(svn_dirent_get_absolute(&local_abspath,
94 svn_dirent_join(anchor, diff_relpath,
98 err = svn_wc__node_get_repos_info(NULL, repos_relpath, NULL, NULL,
99 wc_ctx, local_abspath,
100 result_pool, scratch_pool);
104 || (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND))
106 return svn_error_trace(err);
109 /* The path represents a local working copy path, but does not
110 exist. Fall through to calculate an in-repository location
111 based on the ra session */
113 /* ### Maybe we should use the nearest existing ancestor instead? */
114 svn_error_clear(err);
117 *repos_relpath = svn_relpath_join(session_relpath, diff_relpath,
123 /* Adjust *INDEX_PATH, *ORIG_PATH_1 and *ORIG_PATH_2, representing the changed
124 * node and the two original targets passed to the diff command, to handle the
125 * case when we're dealing with different anchors. RELATIVE_TO_DIR is the
126 * directory the diff target should be considered relative to.
127 * ANCHOR is the local path where the diff editor is anchored. The resulting
128 * values are allocated in RESULT_POOL and temporary allocations are performed
129 * in SCRATCH_POOL. */
131 adjust_paths_for_diff_labels(const char **index_path,
132 const char **orig_path_1,
133 const char **orig_path_2,
134 const char *relative_to_dir,
136 apr_pool_t *result_pool,
137 apr_pool_t *scratch_pool)
139 const char *new_path = *index_path;
140 const char *new_path1 = *orig_path_1;
141 const char *new_path2 = *orig_path_2;
144 new_path = svn_dirent_join(anchor, new_path, result_pool);
148 /* Possibly adjust the paths shown in the output (see issue #2723). */
149 const char *child_path = svn_dirent_is_child(relative_to_dir, new_path,
153 new_path = child_path;
154 else if (! strcmp(relative_to_dir, new_path))
157 return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir);
162 svn_boolean_t is_url1;
163 svn_boolean_t is_url2;
164 /* ### Holy cow. Due to anchor/target weirdness, we can't
165 simply join dwi->orig_path_1 with path, ditto for
166 orig_path_2. That will work when they're directory URLs, but
167 not for file URLs. Nor can we just use anchor1 and anchor2
168 from do_diff(), at least not without some more logic here.
171 For now, to distinguish the two paths, we'll just put the
172 unique portions of the original targets in parentheses after
173 the received path, with ellipses for handwaving. This makes
174 the labels a bit clumsy, but at least distinctive. Better
175 solutions are possible, they'll just take more thought. */
177 /* ### BH: We can now just construct the repos_relpath, etc. as the
178 anchor is available. See also make_repos_relpath() */
180 is_url1 = svn_path_is_url(new_path1);
181 is_url2 = svn_path_is_url(new_path2);
183 if (is_url1 && is_url2)
184 len = strlen(svn_uri_get_longest_ancestor(new_path1, new_path2,
186 else if (!is_url1 && !is_url2)
187 len = strlen(svn_dirent_get_longest_ancestor(new_path1, new_path2,
190 len = 0; /* Path and URL */
196 /* ### Should diff labels print paths in local style? Is there
197 already a standard for this? In any case, this code depends on
198 a particular style, so not calling svn_dirent_local_style() on the
201 if (new_path[0] == '\0')
204 if (new_path1[0] == '\0')
205 new_path1 = new_path;
206 else if (svn_path_is_url(new_path1))
207 new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path1);
208 else if (new_path1[0] == '/')
209 new_path1 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path1);
211 new_path1 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path1);
213 if (new_path2[0] == '\0')
214 new_path2 = new_path;
215 else if (svn_path_is_url(new_path2))
216 new_path2 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path2);
217 else if (new_path2[0] == '/')
218 new_path2 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path2);
220 new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2);
222 *index_path = new_path;
223 *orig_path_1 = new_path1;
224 *orig_path_2 = new_path2;
230 /* Generate a label for the diff output for file PATH at revision REVNUM.
231 If REVNUM is invalid then it is assumed to be the current working
232 copy. Assumes the paths are already in the desired style (local
233 vs internal). Allocate the label in POOL. */
235 diff_label(const char *path,
241 label = apr_psprintf(pool, _("%s\t(revision %ld)"), path, revnum);
242 else if (revnum == DIFF_REVNUM_NONEXISTENT)
243 label = apr_psprintf(pool, _("%s\t(nonexistent)"), path);
244 else /* SVN_INVALID_REVNUM */
245 label = apr_psprintf(pool, _("%s\t(working copy)"), path);
250 /* Print a git diff header for an addition within a diff between PATH1 and
251 * PATH2 to the stream OS using HEADER_ENCODING.
252 * All allocations are done in RESULT_POOL. */
254 print_git_diff_header_added(svn_stream_t *os, const char *header_encoding,
255 const char *path1, const char *path2,
256 apr_pool_t *result_pool)
258 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
259 "diff --git a/%s b/%s%s",
260 path1, path2, APR_EOL_STR));
261 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
262 "new file mode 10644" APR_EOL_STR));
266 /* Print a git diff header for a deletion within a diff between PATH1 and
267 * PATH2 to the stream OS using HEADER_ENCODING.
268 * All allocations are done in RESULT_POOL. */
270 print_git_diff_header_deleted(svn_stream_t *os, const char *header_encoding,
271 const char *path1, const char *path2,
272 apr_pool_t *result_pool)
274 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
275 "diff --git a/%s b/%s%s",
276 path1, path2, APR_EOL_STR));
277 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
278 "deleted file mode 10644"
283 /* Print a git diff header for a copy from COPYFROM_PATH to PATH to the stream
284 * OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
286 print_git_diff_header_copied(svn_stream_t *os, const char *header_encoding,
287 const char *copyfrom_path,
288 svn_revnum_t copyfrom_rev,
290 apr_pool_t *result_pool)
292 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
293 "diff --git a/%s b/%s%s",
294 copyfrom_path, path, APR_EOL_STR));
295 if (copyfrom_rev != SVN_INVALID_REVNUM)
296 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
297 "copy from %s@%ld%s", copyfrom_path,
298 copyfrom_rev, APR_EOL_STR));
300 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
301 "copy from %s%s", copyfrom_path,
303 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
304 "copy to %s%s", path, APR_EOL_STR));
308 /* Print a git diff header for a rename from COPYFROM_PATH to PATH to the
309 * stream OS using HEADER_ENCODING. All allocations are done in RESULT_POOL. */
311 print_git_diff_header_renamed(svn_stream_t *os, const char *header_encoding,
312 const char *copyfrom_path, const char *path,
313 apr_pool_t *result_pool)
315 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
316 "diff --git a/%s b/%s%s",
317 copyfrom_path, path, APR_EOL_STR));
318 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
319 "rename from %s%s", copyfrom_path,
321 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
322 "rename to %s%s", path, APR_EOL_STR));
326 /* Print a git diff header for a modification within a diff between PATH1 and
327 * PATH2 to the stream OS using HEADER_ENCODING.
328 * All allocations are done in RESULT_POOL. */
330 print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding,
331 const char *path1, const char *path2,
332 apr_pool_t *result_pool)
334 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool,
335 "diff --git a/%s b/%s%s",
336 path1, path2, APR_EOL_STR));
340 /* Print a git diff header showing the OPERATION to the stream OS using
341 * HEADER_ENCODING. Return suitable diff labels for the git diff in *LABEL1
342 * and *LABEL2. REPOS_RELPATH1 and REPOS_RELPATH2 are relative to reposroot.
343 * are the paths passed to the original diff command. REV1 and REV2 are
344 * revisions being diffed. COPYFROM_PATH and COPYFROM_REV indicate where the
345 * diffed item was copied from.
346 * Use SCRATCH_POOL for temporary allocations. */
348 print_git_diff_header(svn_stream_t *os,
349 const char **label1, const char **label2,
350 svn_diff_operation_kind_t operation,
351 const char *repos_relpath1,
352 const char *repos_relpath2,
355 const char *copyfrom_path,
356 svn_revnum_t copyfrom_rev,
357 const char *header_encoding,
358 apr_pool_t *scratch_pool)
360 if (operation == svn_diff_op_deleted)
362 SVN_ERR(print_git_diff_header_deleted(os, header_encoding,
363 repos_relpath1, repos_relpath2,
365 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
367 *label2 = diff_label("/dev/null", rev2, scratch_pool);
370 else if (operation == svn_diff_op_copied)
372 SVN_ERR(print_git_diff_header_copied(os, header_encoding,
373 copyfrom_path, copyfrom_rev,
376 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
378 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
381 else if (operation == svn_diff_op_added)
383 SVN_ERR(print_git_diff_header_added(os, header_encoding,
384 repos_relpath1, repos_relpath2,
386 *label1 = diff_label("/dev/null", rev1, scratch_pool);
387 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
390 else if (operation == svn_diff_op_modified)
392 SVN_ERR(print_git_diff_header_modified(os, header_encoding,
393 repos_relpath1, repos_relpath2,
395 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
397 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
400 else if (operation == svn_diff_op_moved)
402 SVN_ERR(print_git_diff_header_renamed(os, header_encoding,
403 copyfrom_path, repos_relpath2,
405 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
407 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
414 /* A helper func that writes out verbal descriptions of property diffs
415 to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was
416 passed to svn_client_diff6(), which is probably stdout.
418 ### FIXME needs proper docstring
420 If USE_GIT_DIFF_FORMAT is TRUE, pring git diff headers, which always
421 show paths relative to the repository root. RA_SESSION and WC_CTX are
422 needed to normalize paths relative the repository root, and are ignored
423 if USE_GIT_DIFF_FORMAT is FALSE.
425 ANCHOR is the local path where the diff editor is anchored. */
427 display_prop_diffs(const apr_array_header_t *propchanges,
428 apr_hash_t *original_props,
429 const char *diff_relpath,
431 const char *orig_path1,
432 const char *orig_path2,
435 const char *encoding,
436 svn_stream_t *outstream,
437 const char *relative_to_dir,
438 svn_boolean_t show_diff_header,
439 svn_boolean_t use_git_diff_format,
440 const char *ra_session_relpath,
441 svn_cancel_func_t cancel_func,
443 svn_wc_context_t *wc_ctx,
444 apr_pool_t *scratch_pool)
446 const char *repos_relpath1 = NULL;
447 const char *repos_relpath2 = NULL;
448 const char *index_path = diff_relpath;
449 const char *adjusted_path1 = orig_path1;
450 const char *adjusted_path2 = orig_path2;
452 if (use_git_diff_format)
454 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, orig_path1,
455 ra_session_relpath, wc_ctx, anchor,
456 scratch_pool, scratch_pool));
457 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, orig_path2,
458 ra_session_relpath, wc_ctx, anchor,
459 scratch_pool, scratch_pool));
462 /* If we're creating a diff on the wc root, path would be empty. */
463 SVN_ERR(adjust_paths_for_diff_labels(&index_path, &adjusted_path1,
465 relative_to_dir, anchor,
466 scratch_pool, scratch_pool));
468 if (show_diff_header)
473 label1 = diff_label(adjusted_path1, rev1, scratch_pool);
474 label2 = diff_label(adjusted_path2, rev2, scratch_pool);
476 /* ### Should we show the paths in platform specific format,
477 * ### diff_content_changed() does not! */
479 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
480 "Index: %s" APR_EOL_STR
481 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
484 if (use_git_diff_format)
485 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
486 svn_diff_op_modified,
487 repos_relpath1, repos_relpath2,
490 encoding, scratch_pool));
494 SVN_ERR(svn_diff__unidiff_write_header(
495 outstream, encoding, label1, label2, scratch_pool));
498 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
499 _("%sProperty changes on: %s%s"),
506 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
507 SVN_DIFF__UNDER_STRING APR_EOL_STR));
509 SVN_ERR(svn_diff__display_prop_diffs(
510 outstream, encoding, propchanges, original_props,
511 TRUE /* pretty_print_mergeinfo */,
512 -1 /* context_size */,
513 cancel_func, cancel_baton, scratch_pool));
518 /*-----------------------------------------------------------------*/
520 /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
522 /* State provided by the diff drivers; used by the diff writer */
523 typedef struct diff_driver_info_t
525 /* The anchor to prefix before wc paths */
528 /* Relative path of ra session from repos_root_url */
529 const char *session_relpath;
531 /* The original targets passed to the diff command. We may need
532 these to construct distinctive diff labels when comparing the
533 same relative path in the same revision, under different anchors
534 (for example, when comparing a trunk against a branch). */
535 const char *orig_path_1;
536 const char *orig_path_2;
537 } diff_driver_info_t;
540 /* Diff writer state */
541 typedef struct diff_writer_info_t
543 /* If non-null, the external diff command to invoke. */
544 const char *diff_cmd;
546 /* This is allocated in this struct's pool or a higher-up pool. */
548 /* If 'diff_cmd' is null, then this is the parsed options to
549 pass to the internal libsvn_diff implementation. */
550 svn_diff_file_options_t *for_internal;
551 /* Else if 'diff_cmd' is non-null, then... */
553 /* ...this is an argument array for the external command, and */
555 /* ...this is the length of argv. */
561 svn_stream_t *outstream;
562 svn_stream_t *errstream;
564 const char *header_encoding;
566 /* Set this if you want diff output even for binary files. */
567 svn_boolean_t force_binary;
569 /* The directory that diff target paths should be considered as
570 relative to for output generation (see issue #2723). */
571 const char *relative_to_dir;
573 /* Whether property differences are ignored. */
574 svn_boolean_t ignore_properties;
576 /* Whether to show only property changes. */
577 svn_boolean_t properties_only;
579 /* Whether we're producing a git-style diff. */
580 svn_boolean_t use_git_diff_format;
582 /* Whether addition of a file is summarized versus showing a full diff. */
583 svn_boolean_t no_diff_added;
585 /* Whether deletion of a file is summarized versus showing a full diff. */
586 svn_boolean_t no_diff_deleted;
588 /* Whether to ignore copyfrom information when showing adds */
589 svn_boolean_t show_copies_as_adds;
591 /* Empty files for creating diffs or NULL if not used yet */
592 const char *empty_file;
594 svn_wc_context_t *wc_ctx;
596 svn_cancel_func_t cancel_func;
599 struct diff_driver_info_t ddi;
600 } diff_writer_info_t;
602 /* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added
605 diff_props_changed(const char *diff_relpath,
608 const apr_array_header_t *propchanges,
609 apr_hash_t *original_props,
610 svn_boolean_t show_diff_header,
611 diff_writer_info_t *dwi,
612 apr_pool_t *scratch_pool)
614 apr_array_header_t *props;
616 /* If property differences are ignored, there's nothing to do. */
617 if (dwi->ignore_properties)
620 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props,
623 if (props->nelts > 0)
625 /* We're using the revnums from the dwi since there's
626 * no revision argument to the svn_wc_diff_callback_t
627 * dir_props_changed(). */
628 SVN_ERR(display_prop_diffs(props, original_props,
631 dwi->ddi.orig_path_1,
632 dwi->ddi.orig_path_2,
635 dwi->header_encoding,
637 dwi->relative_to_dir,
639 dwi->use_git_diff_format,
640 dwi->ddi.session_relpath,
650 /* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and
651 REV2 are used in the headers to indicate the file and revisions. If either
652 MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
653 but instead print a warning message.
655 If FORCE_DIFF is TRUE, always write a diff, even for empty diffs.
657 Set *WROTE_HEADER to TRUE if a diff header was written */
659 diff_content_changed(svn_boolean_t *wrote_header,
660 const char *diff_relpath,
661 const char *tmpfile1,
662 const char *tmpfile2,
665 const char *mimetype1,
666 const char *mimetype2,
667 svn_diff_operation_kind_t operation,
668 svn_boolean_t force_diff,
669 const char *copyfrom_path,
670 svn_revnum_t copyfrom_rev,
671 diff_writer_info_t *dwi,
672 apr_pool_t *scratch_pool)
674 const char *rel_to_dir = dwi->relative_to_dir;
675 svn_stream_t *outstream = dwi->outstream;
676 const char *label1, *label2;
677 svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
678 const char *index_path = diff_relpath;
679 const char *path1 = dwi->ddi.orig_path_1;
680 const char *path2 = dwi->ddi.orig_path_2;
682 /* If only property differences are shown, there's nothing to do. */
683 if (dwi->properties_only)
686 /* Generate the diff headers. */
687 SVN_ERR(adjust_paths_for_diff_labels(&index_path, &path1, &path2,
688 rel_to_dir, dwi->ddi.anchor,
689 scratch_pool, scratch_pool));
691 label1 = diff_label(path1, rev1, scratch_pool);
692 label2 = diff_label(path2, rev2, scratch_pool);
694 /* Possible easy-out: if either mime-type is binary and force was not
695 specified, don't attempt to generate a viewable diff at all.
696 Print a warning and exit. */
698 mt1_binary = svn_mime_type_is_binary(mimetype1);
700 mt2_binary = svn_mime_type_is_binary(mimetype2);
702 if (! dwi->force_binary && (mt1_binary || mt2_binary))
704 /* Print out the diff header. */
705 SVN_ERR(svn_stream_printf_from_utf8(outstream,
706 dwi->header_encoding, scratch_pool,
707 "Index: %s" APR_EOL_STR
708 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
711 /* ### Print git diff headers. */
713 if (dwi->use_git_diff_format)
715 svn_stream_t *left_stream;
716 svn_stream_t *right_stream;
717 const char *repos_relpath1;
718 const char *repos_relpath2;
720 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
721 dwi->ddi.orig_path_1,
722 dwi->ddi.session_relpath,
725 scratch_pool, scratch_pool));
726 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
727 dwi->ddi.orig_path_2,
728 dwi->ddi.session_relpath,
731 scratch_pool, scratch_pool));
732 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
734 repos_relpath1, repos_relpath2,
738 dwi->header_encoding,
741 SVN_ERR(svn_stream_open_readonly(&left_stream, tmpfile1,
742 scratch_pool, scratch_pool));
743 SVN_ERR(svn_stream_open_readonly(&right_stream, tmpfile2,
744 scratch_pool, scratch_pool));
745 SVN_ERR(svn_diff_output_binary(outstream,
746 left_stream, right_stream,
747 dwi->cancel_func, dwi->cancel_baton,
752 SVN_ERR(svn_stream_printf_from_utf8(outstream,
753 dwi->header_encoding, scratch_pool,
754 _("Cannot display: file marked as a binary type.%s"),
757 if (mt1_binary && !mt2_binary)
758 SVN_ERR(svn_stream_printf_from_utf8(outstream,
759 dwi->header_encoding, scratch_pool,
760 "svn:mime-type = %s" APR_EOL_STR, mimetype1));
761 else if (mt2_binary && !mt1_binary)
762 SVN_ERR(svn_stream_printf_from_utf8(outstream,
763 dwi->header_encoding, scratch_pool,
764 "svn:mime-type = %s" APR_EOL_STR, mimetype2));
765 else if (mt1_binary && mt2_binary)
767 if (strcmp(mimetype1, mimetype2) == 0)
768 SVN_ERR(svn_stream_printf_from_utf8(outstream,
769 dwi->header_encoding, scratch_pool,
770 "svn:mime-type = %s" APR_EOL_STR,
773 SVN_ERR(svn_stream_printf_from_utf8(outstream,
774 dwi->header_encoding, scratch_pool,
775 "svn:mime-type = (%s, %s)" APR_EOL_STR,
776 mimetype1, mimetype2));
787 svn_stream_t *errstream = dwi->errstream;
790 const char *outfilename;
791 const char *errfilename;
792 svn_stream_t *stream;
795 /* Print out the diff header. */
796 SVN_ERR(svn_stream_printf_from_utf8(outstream,
797 dwi->header_encoding, scratch_pool,
798 "Index: %s" APR_EOL_STR
799 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
802 /* ### Do we want to add git diff headers here too? I'd say no. The
803 * ### 'Index' and '===' line is something subversion has added. The rest
804 * ### is up to the external diff application. We may be dealing with
805 * ### a non-git compatible diff application.*/
807 /* We deal in streams, but svn_io_run_diff2() deals in file handles,
808 so we may need to make temporary files and then copy the contents
810 outfile = svn_stream__aprfile(outstream);
814 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
815 svn_io_file_del_on_pool_cleanup,
816 scratch_pool, scratch_pool));
818 errfile = svn_stream__aprfile(errstream);
822 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
823 svn_io_file_del_on_pool_cleanup,
824 scratch_pool, scratch_pool));
826 SVN_ERR(svn_io_run_diff2(".",
827 dwi->options.for_external.argv,
828 dwi->options.for_external.argc,
831 &exitcode, outfile, errfile,
832 dwi->diff_cmd, scratch_pool));
834 /* Now, open and copy our files to our output streams. */
837 SVN_ERR(svn_io_file_close(outfile, scratch_pool));
838 SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
839 scratch_pool, scratch_pool));
840 SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(outstream,
842 NULL, NULL, scratch_pool));
846 SVN_ERR(svn_io_file_close(errfile, scratch_pool));
847 SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
848 scratch_pool, scratch_pool));
849 SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(errstream,
851 NULL, NULL, scratch_pool));
854 /* If we have printed a diff for this path, mark it as visited. */
856 *wrote_header = TRUE;
858 else /* use libsvn_diff to generate the diff */
862 SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2,
863 dwi->options.for_internal,
867 || dwi->use_git_diff_format
868 || svn_diff_contains_diffs(diff))
870 /* Print out the diff header. */
871 SVN_ERR(svn_stream_printf_from_utf8(outstream,
872 dwi->header_encoding, scratch_pool,
873 "Index: %s" APR_EOL_STR
874 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
877 if (dwi->use_git_diff_format)
879 const char *repos_relpath1;
880 const char *repos_relpath2;
881 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
882 dwi->ddi.orig_path_1,
883 dwi->ddi.session_relpath,
886 scratch_pool, scratch_pool));
887 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
888 dwi->ddi.orig_path_2,
889 dwi->ddi.session_relpath,
892 scratch_pool, scratch_pool));
893 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
895 repos_relpath1, repos_relpath2,
899 dwi->header_encoding,
903 /* Output the actual diff */
904 if (force_diff || svn_diff_contains_diffs(diff))
905 SVN_ERR(svn_diff_file_output_unified4(outstream, diff,
906 tmpfile1, tmpfile2, label1, label2,
907 dwi->header_encoding, rel_to_dir,
908 dwi->options.for_internal->show_c_function,
909 dwi->options.for_internal->context_size,
910 dwi->cancel_func, dwi->cancel_baton,
913 /* If we have printed a diff for this path, mark it as visited. */
914 if (dwi->use_git_diff_format || svn_diff_contains_diffs(diff))
915 *wrote_header = TRUE;
919 /* ### todo: someday we'll need to worry about whether we're going
920 to need to write a diff plug-in mechanism that makes use of the
921 two paths, instead of just blindly running SVN_CLIENT_DIFF. */
926 /* An svn_diff_tree_processor_t callback. */
928 diff_file_changed(const char *relpath,
929 const svn_diff_source_t *left_source,
930 const svn_diff_source_t *right_source,
931 const char *left_file,
932 const char *right_file,
933 /*const*/ apr_hash_t *left_props,
934 /*const*/ apr_hash_t *right_props,
935 svn_boolean_t file_modified,
936 const apr_array_header_t *prop_changes,
938 const struct svn_diff_tree_processor_t *processor,
939 apr_pool_t *scratch_pool)
941 diff_writer_info_t *dwi = processor->baton;
942 svn_boolean_t wrote_header = FALSE;
945 SVN_ERR(diff_content_changed(&wrote_header, relpath,
946 left_file, right_file,
947 left_source->revision,
948 right_source->revision,
949 svn_prop_get_value(left_props,
951 svn_prop_get_value(right_props,
953 svn_diff_op_modified, FALSE,
955 SVN_INVALID_REVNUM, dwi,
957 if (prop_changes->nelts > 0)
958 SVN_ERR(diff_props_changed(relpath,
959 left_source->revision,
960 right_source->revision, prop_changes,
961 left_props, !wrote_header,
966 /* Because the repos-diff editor passes at least one empty file to
967 each of these next two functions, they can be dumb wrappers around
968 the main workhorse routine. */
970 /* An svn_diff_tree_processor_t callback. */
972 diff_file_added(const char *relpath,
973 const svn_diff_source_t *copyfrom_source,
974 const svn_diff_source_t *right_source,
975 const char *copyfrom_file,
976 const char *right_file,
977 /*const*/ apr_hash_t *copyfrom_props,
978 /*const*/ apr_hash_t *right_props,
980 const struct svn_diff_tree_processor_t *processor,
981 apr_pool_t *scratch_pool)
983 diff_writer_info_t *dwi = processor->baton;
984 svn_boolean_t wrote_header = FALSE;
985 const char *left_file;
986 apr_hash_t *left_props;
987 apr_array_header_t *prop_changes;
989 /* During repos->wc diff of a copy revision numbers obtained
990 * from the working copy are always SVN_INVALID_REVNUM. */
991 if (copyfrom_source && !dwi->show_copies_as_adds)
993 left_file = copyfrom_file;
994 left_props = copyfrom_props ? copyfrom_props : apr_hash_make(scratch_pool);
998 if (!dwi->empty_file)
999 SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file,
1000 NULL, svn_io_file_del_on_pool_cleanup,
1001 dwi->pool, scratch_pool));
1003 left_file = dwi->empty_file;
1004 left_props = apr_hash_make(scratch_pool);
1006 copyfrom_source = NULL;
1007 copyfrom_file = NULL;
1010 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool));
1012 if (dwi->no_diff_added)
1014 const char *index_path = relpath;
1016 if (dwi->ddi.anchor)
1017 index_path = svn_dirent_join(dwi->ddi.anchor, relpath,
1020 SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream,
1021 dwi->header_encoding, scratch_pool,
1022 "Index: %s (added)" APR_EOL_STR
1023 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1025 wrote_header = TRUE;
1027 else if (copyfrom_source && right_file)
1028 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1029 left_file, right_file,
1030 copyfrom_source->revision,
1031 right_source->revision,
1032 svn_prop_get_value(left_props,
1033 SVN_PROP_MIME_TYPE),
1034 svn_prop_get_value(right_props,
1035 SVN_PROP_MIME_TYPE),
1037 TRUE /* force diff output */,
1038 copyfrom_source->repos_relpath,
1039 copyfrom_source->revision,
1040 dwi, scratch_pool));
1041 else if (right_file)
1042 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1043 left_file, right_file,
1044 DIFF_REVNUM_NONEXISTENT,
1045 right_source->revision,
1046 svn_prop_get_value(left_props,
1047 SVN_PROP_MIME_TYPE),
1048 svn_prop_get_value(right_props,
1049 SVN_PROP_MIME_TYPE),
1051 TRUE /* force diff output */,
1052 NULL, SVN_INVALID_REVNUM,
1053 dwi, scratch_pool));
1055 if (prop_changes->nelts > 0)
1056 SVN_ERR(diff_props_changed(relpath,
1057 copyfrom_source ? copyfrom_source->revision
1058 : DIFF_REVNUM_NONEXISTENT,
1059 right_source->revision,
1061 left_props, ! wrote_header,
1062 dwi, scratch_pool));
1064 return SVN_NO_ERROR;
1067 /* An svn_diff_tree_processor_t callback. */
1068 static svn_error_t *
1069 diff_file_deleted(const char *relpath,
1070 const svn_diff_source_t *left_source,
1071 const char *left_file,
1072 /*const*/ apr_hash_t *left_props,
1074 const struct svn_diff_tree_processor_t *processor,
1075 apr_pool_t *scratch_pool)
1077 diff_writer_info_t *dwi = processor->baton;
1079 if (dwi->no_diff_deleted)
1081 const char *index_path = relpath;
1083 if (dwi->ddi.anchor)
1084 index_path = svn_dirent_join(dwi->ddi.anchor, relpath,
1087 SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream,
1088 dwi->header_encoding, scratch_pool,
1089 "Index: %s (deleted)" APR_EOL_STR
1090 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1095 svn_boolean_t wrote_header = FALSE;
1097 if (!dwi->empty_file)
1098 SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file,
1099 NULL, svn_io_file_del_on_pool_cleanup,
1100 dwi->pool, scratch_pool));
1103 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1104 left_file, dwi->empty_file,
1105 left_source->revision,
1106 DIFF_REVNUM_NONEXISTENT,
1107 svn_prop_get_value(left_props,
1108 SVN_PROP_MIME_TYPE),
1110 svn_diff_op_deleted, FALSE,
1111 NULL, SVN_INVALID_REVNUM,
1115 if (left_props && apr_hash_count(left_props))
1117 apr_array_header_t *prop_changes;
1119 SVN_ERR(svn_prop_diffs(&prop_changes, apr_hash_make(scratch_pool),
1120 left_props, scratch_pool));
1122 SVN_ERR(diff_props_changed(relpath,
1123 left_source->revision,
1124 DIFF_REVNUM_NONEXISTENT,
1126 left_props, ! wrote_header,
1127 dwi, scratch_pool));
1131 return SVN_NO_ERROR;
1134 /* An svn_wc_diff_callbacks4_t function. */
1135 static svn_error_t *
1136 diff_dir_changed(const char *relpath,
1137 const svn_diff_source_t *left_source,
1138 const svn_diff_source_t *right_source,
1139 /*const*/ apr_hash_t *left_props,
1140 /*const*/ apr_hash_t *right_props,
1141 const apr_array_header_t *prop_changes,
1143 const struct svn_diff_tree_processor_t *processor,
1144 apr_pool_t *scratch_pool)
1146 diff_writer_info_t *dwi = processor->baton;
1148 SVN_ERR(diff_props_changed(relpath,
1149 left_source->revision,
1150 right_source->revision,
1153 TRUE /* show_diff_header */,
1157 return SVN_NO_ERROR;
1160 /* An svn_diff_tree_processor_t callback. */
1161 static svn_error_t *
1162 diff_dir_added(const char *relpath,
1163 const svn_diff_source_t *copyfrom_source,
1164 const svn_diff_source_t *right_source,
1165 /*const*/ apr_hash_t *copyfrom_props,
1166 /*const*/ apr_hash_t *right_props,
1168 const struct svn_diff_tree_processor_t *processor,
1169 apr_pool_t *scratch_pool)
1171 diff_writer_info_t *dwi = processor->baton;
1172 apr_hash_t *left_props;
1173 apr_array_header_t *prop_changes;
1175 if (dwi->no_diff_added)
1176 return SVN_NO_ERROR;
1178 if (copyfrom_source && !dwi->show_copies_as_adds)
1180 left_props = copyfrom_props ? copyfrom_props
1181 : apr_hash_make(scratch_pool);
1185 left_props = apr_hash_make(scratch_pool);
1186 copyfrom_source = NULL;
1189 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props,
1192 return svn_error_trace(diff_props_changed(relpath,
1193 copyfrom_source ? copyfrom_source->revision
1194 : DIFF_REVNUM_NONEXISTENT,
1195 right_source->revision,
1198 TRUE /* show_diff_header */,
1203 /* An svn_diff_tree_processor_t callback. */
1204 static svn_error_t *
1205 diff_dir_deleted(const char *relpath,
1206 const svn_diff_source_t *left_source,
1207 /*const*/ apr_hash_t *left_props,
1209 const struct svn_diff_tree_processor_t *processor,
1210 apr_pool_t *scratch_pool)
1212 diff_writer_info_t *dwi = processor->baton;
1213 apr_array_header_t *prop_changes;
1215 if (dwi->no_diff_deleted)
1216 return SVN_NO_ERROR;
1219 SVN_ERR(svn_prop_diffs(&prop_changes, apr_hash_make(scratch_pool),
1220 left_props, scratch_pool));
1222 SVN_ERR(diff_props_changed(relpath,
1223 left_source->revision,
1224 DIFF_REVNUM_NONEXISTENT,
1227 TRUE /* show_diff_header */,
1231 return SVN_NO_ERROR;
1234 /*-----------------------------------------------------------------*/
1236 /** The logic behind 'svn diff' and 'svn merge'. */
1239 /* Hi! This is a comment left behind by Karl, and Ben is too afraid
1240 to erase it at this time, because he's not fully confident that all
1241 this knowledge has been grokked yet.
1243 There are five cases:
1244 1. path is not a URL and start_revision != end_revision
1245 2. path is not a URL and start_revision == end_revision
1246 3. path is a URL and start_revision != end_revision
1247 4. path is a URL and start_revision == end_revision
1248 5. path is not a URL and no revisions given
1250 With only one distinct revision the working copy provides the
1251 other. When path is a URL there is no working copy. Thus
1253 1: compare repository versions for URL coresponding to working copy
1254 2: compare working copy against repository version
1255 3: compare repository versions for URL
1257 5: compare working copy against text-base
1259 Case 4 is not as stupid as it looks, for example it may occur if
1260 the user specifies two dates that resolve to the same revision. */
1263 /** Check if paths PATH_OR_URL1 and PATH_OR_URL2 are urls and if the
1264 * revisions REVISION1 and REVISION2 are local. If PEG_REVISION is not
1265 * unspecified, ensure that at least one of the two revisions is not
1267 * If PATH_OR_URL1 can only be found in the repository, set *IS_REPOS1
1268 * to TRUE. If PATH_OR_URL2 can only be found in the repository, set
1269 * *IS_REPOS2 to TRUE. */
1270 static svn_error_t *
1271 check_paths(svn_boolean_t *is_repos1,
1272 svn_boolean_t *is_repos2,
1273 const char *path_or_url1,
1274 const char *path_or_url2,
1275 const svn_opt_revision_t *revision1,
1276 const svn_opt_revision_t *revision2,
1277 const svn_opt_revision_t *peg_revision)
1279 svn_boolean_t is_local_rev1, is_local_rev2;
1281 /* Verify our revision arguments in light of the paths. */
1282 if ((revision1->kind == svn_opt_revision_unspecified)
1283 || (revision2->kind == svn_opt_revision_unspecified))
1284 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1285 _("Not all required revisions are specified"));
1287 /* Revisions can be said to be local or remote.
1288 * BASE and WORKING are local revisions. */
1290 ((revision1->kind == svn_opt_revision_base)
1291 || (revision1->kind == svn_opt_revision_working));
1293 ((revision2->kind == svn_opt_revision_base)
1294 || (revision2->kind == svn_opt_revision_working));
1296 if (peg_revision->kind != svn_opt_revision_unspecified &&
1297 is_local_rev1 && is_local_rev2)
1298 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1299 _("At least one revision must be something other "
1300 "than BASE or WORKING when diffing a URL"));
1302 /* Working copy paths with non-local revisions get turned into
1303 URLs. We don't do that here, though. We simply record that it
1304 needs to be done, which is information that helps us choose our
1305 diff helper function. */
1306 *is_repos1 = ! is_local_rev1 || svn_path_is_url(path_or_url1);
1307 *is_repos2 = ! is_local_rev2 || svn_path_is_url(path_or_url2);
1309 return SVN_NO_ERROR;
1312 /* Raise an error if the diff target URL does not exist at REVISION.
1313 * If REVISION does not equal OTHER_REVISION, mention both revisions in
1314 * the error message. Use RA_SESSION to contact the repository.
1315 * Use POOL for temporary allocations. */
1316 static svn_error_t *
1317 check_diff_target_exists(const char *url,
1318 svn_revnum_t revision,
1319 svn_revnum_t other_revision,
1320 svn_ra_session_t *ra_session,
1323 svn_node_kind_t kind;
1324 const char *session_url;
1326 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
1328 if (strcmp(url, session_url) != 0)
1329 SVN_ERR(svn_ra_reparent(ra_session, url, pool));
1331 SVN_ERR(svn_ra_check_path(ra_session, "", revision, &kind, pool));
1332 if (kind == svn_node_none)
1334 if (revision == other_revision)
1335 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1336 _("Diff target '%s' was not found in the "
1337 "repository at revision '%ld'"),
1340 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1341 _("Diff target '%s' was not found in the "
1342 "repository at revision '%ld' or '%ld'"),
1343 url, revision, other_revision);
1346 if (strcmp(url, session_url) != 0)
1347 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
1349 return SVN_NO_ERROR;
1352 /** Prepare a repos repos diff between PATH_OR_URL1 and
1353 * PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2.
1354 * Return URLs and peg revisions in *URL1, *REV1 and in *URL2, *REV2.
1355 * Return suitable anchors in *ANCHOR1 and *ANCHOR2, and targets in
1356 * *TARGET1 and *TARGET2, based on *URL1 and *URL2.
1357 * Indicate the corresponding node kinds in *KIND1 and *KIND2, and verify
1358 * that at least one of the diff targets exists.
1359 * Use client context CTX. Do all allocations in POOL. */
1360 static svn_error_t *
1361 diff_prepare_repos_repos(const char **url1,
1365 const char **anchor1,
1366 const char **anchor2,
1367 const char **target1,
1368 const char **target2,
1369 svn_node_kind_t *kind1,
1370 svn_node_kind_t *kind2,
1371 svn_ra_session_t **ra_session,
1372 svn_client_ctx_t *ctx,
1373 const char *path_or_url1,
1374 const char *path_or_url2,
1375 const svn_opt_revision_t *revision1,
1376 const svn_opt_revision_t *revision2,
1377 const svn_opt_revision_t *peg_revision,
1380 const char *local_abspath1 = NULL;
1381 const char *local_abspath2 = NULL;
1382 const char *repos_root_url;
1383 const char *wri_abspath = NULL;
1384 svn_client__pathrev_t *resolved1;
1385 svn_client__pathrev_t *resolved2 = NULL;
1386 enum svn_opt_revision_kind peg_kind = peg_revision->kind;
1388 if (!svn_path_is_url(path_or_url2))
1390 SVN_ERR(svn_dirent_get_absolute(&local_abspath2, path_or_url2, pool));
1391 SVN_ERR(svn_wc__node_get_url(url2, ctx->wc_ctx, local_abspath2,
1393 wri_abspath = local_abspath2;
1396 *url2 = apr_pstrdup(pool, path_or_url2);
1398 if (!svn_path_is_url(path_or_url1))
1400 SVN_ERR(svn_dirent_get_absolute(&local_abspath1, path_or_url1, pool));
1401 wri_abspath = local_abspath1;
1404 SVN_ERR(svn_client_open_ra_session2(ra_session, *url2, wri_abspath,
1407 /* If we are performing a pegged diff, we need to find out what our
1408 actual URLs will be. */
1409 if (peg_kind != svn_opt_revision_unspecified
1410 || path_or_url1 == path_or_url2
1415 err = svn_client__resolve_rev_and_url(&resolved2,
1416 *ra_session, path_or_url2,
1417 peg_revision, revision2,
1421 if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES
1422 && err->apr_err != SVN_ERR_FS_NOT_FOUND)
1423 return svn_error_trace(err);
1425 svn_error_clear(err);
1432 if (peg_kind != svn_opt_revision_unspecified
1433 || path_or_url1 == path_or_url2
1438 err = svn_client__resolve_rev_and_url(&resolved1,
1439 *ra_session, path_or_url1,
1440 peg_revision, revision1,
1444 if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES
1445 && err->apr_err != SVN_ERR_FS_NOT_FOUND)
1446 return svn_error_trace(err);
1448 svn_error_clear(err);
1457 *url1 = resolved1->url;
1458 *rev1 = resolved1->rev;
1462 /* It would be nice if we could just return an error when resolving a
1463 location fails... But in many such cases we prefer diffing against
1464 an not existing location to show adds od removes (see issue #4153) */
1467 && (peg_kind != svn_opt_revision_unspecified
1468 || path_or_url1 == path_or_url2))
1469 *url1 = resolved2->url;
1470 else if (! local_abspath1)
1471 *url1 = path_or_url1;
1473 SVN_ERR(svn_wc__node_get_url(url1, ctx->wc_ctx, local_abspath1,
1476 SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx,
1477 local_abspath1 /* may be NULL */,
1478 *ra_session, revision1, pool));
1483 *url2 = resolved2->url;
1484 *rev2 = resolved2->rev;
1488 /* It would be nice if we could just return an error when resolving a
1489 location fails... But in many such cases we prefer diffing against
1490 an not existing location to show adds od removes (see issue #4153) */
1493 && (peg_kind != svn_opt_revision_unspecified
1494 || path_or_url1 == path_or_url2))
1495 *url2 = resolved1->url;
1496 /* else keep url2 */
1498 SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx,
1499 local_abspath2 /* may be NULL */,
1500 *ra_session, revision2, pool));
1503 /* Resolve revision and get path kind for the second target. */
1504 SVN_ERR(svn_ra_reparent(*ra_session, *url2, pool));
1505 SVN_ERR(svn_ra_check_path(*ra_session, "", *rev2, kind2, pool));
1507 /* Do the same for the first target. */
1508 SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
1509 SVN_ERR(svn_ra_check_path(*ra_session, "", *rev1, kind1, pool));
1511 /* Either both URLs must exist at their respective revisions,
1512 * or one of them may be missing from one side of the diff. */
1513 if (*kind1 == svn_node_none && *kind2 == svn_node_none)
1515 if (strcmp(*url1, *url2) == 0)
1516 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1517 _("Diff target '%s' was not found in the "
1518 "repository at revisions '%ld' and '%ld'"),
1519 *url1, *rev1, *rev2);
1521 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1522 _("Diff targets '%s' and '%s' were not found "
1523 "in the repository at revisions '%ld' and "
1525 *url1, *url2, *rev1, *rev2);
1527 else if (*kind1 == svn_node_none)
1528 SVN_ERR(check_diff_target_exists(*url1, *rev2, *rev1, *ra_session, pool));
1529 else if (*kind2 == svn_node_none)
1530 SVN_ERR(check_diff_target_exists(*url2, *rev1, *rev2, *ra_session, pool));
1532 SVN_ERR(svn_ra_get_repos_root2(*ra_session, &repos_root_url, pool));
1534 /* Choose useful anchors and targets for our two URLs. */
1540 /* If none of the targets is the repository root open the parent directory
1541 to allow describing replacement of the target itself */
1542 if (strcmp(*url1, repos_root_url) != 0
1543 && strcmp(*url2, repos_root_url) != 0)
1545 svn_node_kind_t ignored_kind;
1548 svn_uri_split(anchor1, target1, *url1, pool);
1549 svn_uri_split(anchor2, target2, *url2, pool);
1551 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
1553 /* We might not have the necessary rights to read the root now.
1554 (It is ok to pass a revision here where the node doesn't exist) */
1555 err = svn_ra_check_path(*ra_session, "", *rev1, &ignored_kind, pool);
1557 if (err && (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN
1558 || err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED))
1560 svn_error_clear(err);
1562 /* Ok, lets undo the reparent...
1564 We can't report replacements this way, but at least we can
1565 report changes on the descendants */
1567 *anchor1 = svn_path_url_add_component2(*anchor1, *target1, pool);
1568 *anchor2 = svn_path_url_add_component2(*anchor2, *target2, pool);
1572 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
1578 return SVN_NO_ERROR;
1581 /* A Theoretical Note From Ben, regarding do_diff().
1583 This function is really svn_client_diff6(). If you read the public
1584 API description for svn_client_diff6(), it sounds quite Grand. It
1585 sounds really generalized and abstract and beautiful: that it will
1586 diff any two paths, be they working-copy paths or URLs, at any two
1589 Now, the *reality* is that we have exactly three 'tools' for doing
1590 diffing, and thus this routine is built around the use of the three
1591 tools. Here they are, for clarity:
1593 - svn_wc_diff: assumes both paths are the same wcpath.
1594 compares wcpath@BASE vs. wcpath@WORKING
1596 - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING
1598 - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2
1600 Since Subversion 1.8 we also have a variant of svn_wc_diff called
1601 svn_client__arbitrary_nodes_diff, that allows handling WORKING-WORKING
1602 comparisons between nodes in the working copy.
1604 So the truth of the matter is, if the caller's arguments can't be
1605 pigeonholed into one of these use-cases, we currently bail with a
1608 Perhaps someday a brave soul will truly make svn_client_diff6()
1609 perfectly general. For now, we live with the 90% case. Certainly,
1610 the commandline client only calls this function in legal ways.
1611 When there are other users of svn_client.h, maybe this will become
1612 a more pressing issue.
1615 /* Return a "you can't do that" error, optionally wrapping another
1617 static svn_error_t *
1618 unsupported_diff_error(svn_error_t *child_err)
1620 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
1621 _("Sorry, svn_client_diff6 was called in a way "
1622 "that is not yet supported"));
1625 /* Perform a diff between two working-copy paths.
1627 PATH1 and PATH2 are both working copy paths. REVISION1 and
1628 REVISION2 are their respective revisions.
1630 All other options are the same as those passed to svn_client_diff6(). */
1631 static svn_error_t *
1632 diff_wc_wc(const char **root_relpath,
1633 svn_boolean_t *root_is_dir,
1634 struct diff_driver_info_t *ddi,
1636 const svn_opt_revision_t *revision1,
1638 const svn_opt_revision_t *revision2,
1640 svn_boolean_t ignore_ancestry,
1641 const apr_array_header_t *changelists,
1642 const svn_diff_tree_processor_t *diff_processor,
1643 svn_client_ctx_t *ctx,
1644 apr_pool_t *result_pool,
1645 apr_pool_t *scratch_pool)
1647 const char *abspath1;
1649 SVN_ERR_ASSERT(! svn_path_is_url(path1));
1650 SVN_ERR_ASSERT(! svn_path_is_url(path2));
1652 SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, scratch_pool));
1654 /* Currently we support only the case where path1 and path2 are the
1656 if ((strcmp(path1, path2) != 0)
1657 || (! ((revision1->kind == svn_opt_revision_base)
1658 && (revision2->kind == svn_opt_revision_working))))
1659 return unsupported_diff_error(
1660 svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1661 _("Only diffs between a path's text-base "
1662 "and its working files are supported at this time"
1667 svn_node_kind_t kind;
1669 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
1670 TRUE, FALSE, scratch_pool));
1672 if (kind != svn_node_dir)
1673 ddi->anchor = svn_dirent_dirname(path1, scratch_pool);
1675 ddi->anchor = path1;
1678 SVN_ERR(svn_wc__diff7(root_relpath, root_is_dir,
1679 ctx->wc_ctx, abspath1, depth,
1680 ignore_ancestry, changelists,
1682 ctx->cancel_func, ctx->cancel_baton,
1683 result_pool, scratch_pool));
1684 return SVN_NO_ERROR;
1687 /* Perform a diff between two repository paths.
1689 PATH_OR_URL1 and PATH_OR_URL2 may be either URLs or the working copy paths.
1690 REVISION1 and REVISION2 are their respective revisions.
1691 If PEG_REVISION is specified, PATH_OR_URL2 is the path at the peg revision,
1692 and the actual two paths compared are determined by following copy
1693 history from PATH_OR_URL2.
1695 All other options are the same as those passed to svn_client_diff6(). */
1696 static svn_error_t *
1697 diff_repos_repos(const char **root_relpath,
1698 svn_boolean_t *root_is_dir,
1699 struct diff_driver_info_t *ddi,
1700 const char *path_or_url1,
1701 const char *path_or_url2,
1702 const svn_opt_revision_t *revision1,
1703 const svn_opt_revision_t *revision2,
1704 const svn_opt_revision_t *peg_revision,
1706 svn_boolean_t ignore_ancestry,
1707 svn_boolean_t text_deltas,
1708 const svn_diff_tree_processor_t *diff_processor,
1709 svn_client_ctx_t *ctx,
1710 apr_pool_t *result_pool,
1711 apr_pool_t *scratch_pool)
1713 svn_ra_session_t *extra_ra_session;
1715 const svn_ra_reporter3_t *reporter;
1716 void *reporter_baton;
1718 const svn_delta_editor_t *diff_editor;
1719 void *diff_edit_baton;
1725 svn_node_kind_t kind1;
1726 svn_node_kind_t kind2;
1727 const char *anchor1;
1728 const char *anchor2;
1729 const char *target1;
1730 const char *target2;
1731 svn_ra_session_t *ra_session;
1733 /* Prepare info for the repos repos diff. */
1734 SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &rev1, &rev2,
1735 &anchor1, &anchor2, &target1, &target2,
1736 &kind1, &kind2, &ra_session,
1737 ctx, path_or_url1, path_or_url2,
1738 revision1, revision2, peg_revision,
1741 /* Set up the repos_diff editor on BASE_PATH, if available.
1742 Otherwise, we just use "". */
1746 /* Get actual URLs. */
1747 ddi->orig_path_1 = url1;
1748 ddi->orig_path_2 = url2;
1750 /* This should be moved to the diff writer
1751 - path_or_url are provided by the caller
1752 - target1 is available as *root_relpath
1753 - (kind1 != svn_node_dir || kind2 != svn_node_dir) = !*root_is_dir */
1755 if (!svn_path_is_url(path_or_url2))
1756 ddi->anchor = path_or_url2;
1757 else if (!svn_path_is_url(path_or_url1))
1758 ddi->anchor = path_or_url1;
1762 if (*target1 && ddi->anchor
1763 && (kind1 != svn_node_dir || kind2 != svn_node_dir))
1764 ddi->anchor = svn_dirent_dirname(ddi->anchor, result_pool);
1767 /* The repository can bring in a new working copy, but not delete
1768 everything. Luckily our new diff handler can just be reversed. */
1769 if (kind2 == svn_node_none)
1771 const char *str_tmp;
1772 svn_revnum_t rev_tmp;
1790 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
1795 /* Filter the first path component using a filter processor, until we fixed
1796 the diff processing to handle this directly */
1798 *root_relpath = apr_pstrdup(result_pool, target1);
1799 else if ((kind1 != svn_node_file && kind2 != svn_node_file)
1800 && target1[0] != '\0')
1802 diff_processor = svn_diff__tree_processor_filter_create(
1803 diff_processor, target1, scratch_pool);
1806 /* Now, we open an extra RA session to the correct anchor
1807 location for URL1. This is used during the editor calls to fetch file
1809 SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor1,
1810 scratch_pool, scratch_pool));
1814 const char *repos_root_url;
1815 const char *session_url;
1817 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
1819 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url,
1822 ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url,
1827 SVN_ERR(svn_client__get_diff_editor2(
1828 &diff_editor, &diff_edit_baton,
1829 extra_ra_session, depth,
1833 ctx->cancel_func, ctx->cancel_baton,
1836 /* We want to switch our txn into URL2 */
1837 SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton,
1839 depth, ignore_ancestry, text_deltas,
1840 url2, diff_editor, diff_edit_baton, scratch_pool));
1842 /* Drive the reporter; do the diff. */
1843 SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
1848 return svn_error_trace(
1849 reporter->finish_report(reporter_baton, scratch_pool));
1852 /* Perform a diff between a repository path and a working-copy path.
1854 PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a
1855 working copy path. REVISION1 and REVISION2 are their respective
1856 revisions. If REVERSE is TRUE, the diff will be done in reverse.
1857 If PEG_REVISION is specified, then PATH_OR_URL1 is the path in the peg
1858 revision, and the actual repository path to be compared is
1859 determined by following copy history.
1861 All other options are the same as those passed to svn_client_diff6(). */
1862 static svn_error_t *
1863 diff_repos_wc(const char **root_relpath,
1864 svn_boolean_t *root_is_dir,
1865 struct diff_driver_info_t *ddi,
1866 const char *path_or_url1,
1867 const svn_opt_revision_t *revision1,
1868 const svn_opt_revision_t *peg_revision,
1870 const svn_opt_revision_t *revision2,
1871 svn_boolean_t reverse,
1873 svn_boolean_t ignore_ancestry,
1874 const apr_array_header_t *changelists,
1875 const svn_diff_tree_processor_t *diff_processor,
1876 svn_client_ctx_t *ctx,
1877 apr_pool_t *result_pool,
1878 apr_pool_t *scratch_pool)
1880 const char *anchor, *anchor_url, *target;
1881 svn_ra_session_t *ra_session;
1882 svn_depth_t diff_depth;
1883 const svn_ra_reporter3_t *reporter;
1884 void *reporter_baton;
1885 const svn_delta_editor_t *diff_editor;
1886 void *diff_edit_baton;
1887 svn_boolean_t rev2_is_base = (revision2->kind == svn_opt_revision_base);
1888 svn_boolean_t server_supports_depth;
1889 const char *abspath_or_url1;
1890 const char *abspath2;
1891 const char *anchor_abspath;
1892 svn_boolean_t is_copy;
1893 svn_revnum_t cf_revision;
1894 const char *cf_repos_relpath;
1895 const char *cf_repos_root_url;
1896 svn_depth_t cf_depth;
1897 const char *copy_root_abspath;
1898 const char *target_url;
1899 svn_client__pathrev_t *loc1;
1901 SVN_ERR_ASSERT(! svn_path_is_url(path2));
1903 if (!svn_path_is_url(path_or_url1))
1905 SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1,
1910 abspath_or_url1 = path_or_url1;
1913 SVN_ERR(svn_dirent_get_absolute(&abspath2, path2, scratch_pool));
1915 /* Check if our diff target is a copied node. */
1916 SVN_ERR(svn_wc__node_get_origin(&is_copy,
1922 ctx->wc_ctx, abspath2,
1923 FALSE, scratch_pool, scratch_pool));
1925 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc1,
1926 path_or_url1, abspath2,
1927 peg_revision, revision1,
1928 ctx, scratch_pool));
1930 if (revision2->kind == svn_opt_revision_base || !is_copy)
1932 /* Convert path_or_url1 to a URL to feed to do_diff. */
1933 SVN_ERR(svn_wc_get_actual_target2(&anchor, &target, ctx->wc_ctx, path2,
1934 scratch_pool, scratch_pool));
1936 /* Handle the ugly case where target is ".." */
1937 if (*target && !svn_path_is_single_path_component(target))
1939 anchor = svn_dirent_join(anchor, target, scratch_pool);
1944 *root_relpath = apr_pstrdup(result_pool, target);
1946 *root_is_dir = (*target == '\0');
1948 /* Fetch the URL of the anchor directory. */
1949 SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, scratch_pool));
1950 SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
1951 scratch_pool, scratch_pool));
1952 SVN_ERR_ASSERT(anchor_url != NULL);
1956 else /* is_copy && revision2->kind == svn_opt_revision_base */
1959 svn_node_kind_t kind;
1961 /* ### Ugly hack ahead ###
1963 * We're diffing a locally copied/moved node.
1964 * Describe the copy source to the reporter instead of the copy itself.
1965 * Doing the latter would generate a single add_directory() call to the
1966 * diff editor which results in an unexpected diff (the copy would
1967 * be shown as deleted).
1969 * ### But if we will receive any real changes from the repositor we
1970 * will most likely fail to apply them as the wc diff editor assumes
1971 * that we have the data to which the change applies in BASE...
1974 target_url = svn_path_url_add_component2(cf_repos_root_url,
1979 /*SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath2, FALSE, FALSE,
1982 if (kind != svn_node_dir
1983 || strcmp(copy_root_abspath, abspath2) != 0) */
1986 /* We are looking at a subdirectory of the repository,
1987 We can describe the parent directory as the anchor..
1989 ### This 'appears to work', but that is really dumb luck
1990 ### for the simple cases in the test suite */
1991 anchor_abspath = svn_dirent_dirname(abspath2, scratch_pool);
1992 anchor_url = svn_path_url_add_component2(cf_repos_root_url,
1993 svn_relpath_dirname(
1997 target = svn_dirent_basename(abspath2, NULL);
1998 anchor = svn_dirent_dirname(path2, scratch_pool);
2003 /* This code, while ok can't be enabled without causing test
2004 * failures. The repository will send some changes against
2005 * BASE for nodes that don't have BASE...
2007 anchor_abspath = abspath2;
2008 anchor_url = svn_path_url_add_component2(cf_repos_root_url,
2017 SVN_ERR(svn_ra_reparent(ra_session, anchor_url, scratch_pool));
2021 const char *repos_root_url;
2023 ddi->anchor = anchor;
2027 ddi->orig_path_1 = apr_pstrdup(result_pool, loc1->url);
2029 svn_path_url_add_component2(anchor_url, target, result_pool);
2034 svn_path_url_add_component2(anchor_url, target, result_pool);
2035 ddi->orig_path_2 = apr_pstrdup(result_pool, loc1->url);
2038 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
2041 ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url,
2047 diff_processor = svn_diff__tree_processor_reverse_create(
2048 diff_processor, NULL, scratch_pool);
2050 /* Use the diff editor to generate the diff. */
2051 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
2052 SVN_RA_CAPABILITY_DEPTH, scratch_pool));
2053 SVN_ERR(svn_wc__get_diff_editor(&diff_editor, &diff_edit_baton,
2061 server_supports_depth,
2064 ctx->cancel_func, ctx->cancel_baton,
2065 scratch_pool, scratch_pool));
2067 if (depth != svn_depth_infinity)
2070 diff_depth = svn_depth_unknown;
2074 if (is_copy && revision2->kind != svn_opt_revision_base)
2076 /* Tell the RA layer we want a delta to change our txn to URL1 */
2077 SVN_ERR(svn_ra_do_diff3(ra_session,
2078 &reporter, &reporter_baton,
2083 TRUE, /* text_deltas */
2085 diff_editor, diff_edit_baton,
2088 /* Report the copy source. */
2089 if (cf_depth == svn_depth_unknown)
2090 cf_depth = svn_depth_infinity;
2092 /* Reporting the in-wc revision as r0, makes the repository send
2093 everything as added, which avoids using BASE for pristine information,
2094 which is not there (or unrelated) for a copy */
2096 SVN_ERR(reporter->set_path(reporter_baton, "",
2097 ignore_ancestry ? 0 : cf_revision,
2098 cf_depth, FALSE, NULL, scratch_pool));
2101 SVN_ERR(reporter->link_path(reporter_baton, target,
2103 ignore_ancestry ? 0 : cf_revision,
2104 cf_depth, FALSE, NULL, scratch_pool));
2106 /* Finish the report to generate the diff. */
2107 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
2111 /* Tell the RA layer we want a delta to change our txn to URL1 */
2112 SVN_ERR(svn_ra_do_diff3(ra_session,
2113 &reporter, &reporter_baton,
2118 TRUE, /* text_deltas */
2120 diff_editor, diff_edit_baton,
2123 /* Create a txn mirror of path2; the diff editor will print
2124 diffs in reverse. :-) */
2125 SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, abspath2,
2126 reporter, reporter_baton,
2128 (! server_supports_depth),
2130 ctx->cancel_func, ctx->cancel_baton,
2131 NULL, NULL, /* notification is N/A */
2135 return SVN_NO_ERROR;
2139 /* This is basically just the guts of svn_client_diff[_summarize][_peg]6(). */
2140 static svn_error_t *
2141 do_diff(const char **root_relpath,
2142 svn_boolean_t *root_is_dir,
2143 diff_driver_info_t *ddi,
2144 const char *path_or_url1,
2145 const char *path_or_url2,
2146 const svn_opt_revision_t *revision1,
2147 const svn_opt_revision_t *revision2,
2148 const svn_opt_revision_t *peg_revision,
2150 svn_boolean_t ignore_ancestry,
2151 const apr_array_header_t *changelists,
2152 svn_boolean_t text_deltas,
2153 const svn_diff_tree_processor_t *diff_processor,
2154 svn_client_ctx_t *ctx,
2155 apr_pool_t *result_pool,
2156 apr_pool_t *scratch_pool)
2158 svn_boolean_t is_repos1;
2159 svn_boolean_t is_repos2;
2161 /* Check if paths/revisions are urls/local. */
2162 SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2,
2163 revision1, revision2, peg_revision));
2169 /* ### Ignores 'show_copies_as_adds'. */
2170 SVN_ERR(diff_repos_repos(root_relpath, root_is_dir,
2172 path_or_url1, path_or_url2,
2173 revision1, revision2,
2174 peg_revision, depth, ignore_ancestry,
2176 diff_processor, ctx,
2177 result_pool, scratch_pool));
2179 else /* path_or_url2 is a working copy path */
2181 SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi,
2182 path_or_url1, revision1, peg_revision,
2183 path_or_url2, revision2, FALSE, depth,
2184 ignore_ancestry, changelists,
2185 diff_processor, ctx,
2186 result_pool, scratch_pool));
2189 else /* path_or_url1 is a working copy path */
2193 SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi,
2194 path_or_url2, revision2, peg_revision,
2195 path_or_url1, revision1, TRUE, depth,
2196 ignore_ancestry, changelists,
2197 diff_processor, ctx,
2198 result_pool, scratch_pool));
2200 else /* path_or_url2 is a working copy path */
2202 if (revision1->kind == svn_opt_revision_working
2203 && revision2->kind == svn_opt_revision_working)
2205 const char *abspath1;
2206 const char *abspath2;
2208 SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1,
2210 SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2,
2213 /* ### What about ddi? */
2215 SVN_ERR(svn_client__arbitrary_nodes_diff(root_relpath, root_is_dir,
2220 result_pool, scratch_pool));
2224 SVN_ERR(diff_wc_wc(root_relpath, root_is_dir, ddi,
2225 path_or_url1, revision1,
2226 path_or_url2, revision2,
2227 depth, ignore_ancestry, changelists,
2228 diff_processor, ctx,
2229 result_pool, scratch_pool));
2234 return SVN_NO_ERROR;
2237 /* Initialize DWI.diff_cmd and DWI.options,
2238 * according to OPTIONS and CONFIG. CONFIG and OPTIONS may be null.
2239 * Allocate the fields in RESULT_POOL, which should be at least as long-lived
2240 * as the pool DWI itself is allocated in.
2242 static svn_error_t *
2243 create_diff_writer_info(diff_writer_info_t *dwi,
2244 const apr_array_header_t *options,
2245 apr_hash_t *config, apr_pool_t *result_pool)
2247 const char *diff_cmd = NULL;
2249 /* See if there is a diff command and/or diff arguments. */
2252 svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
2253 svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
2254 SVN_CONFIG_OPTION_DIFF_CMD, NULL);
2255 if (options == NULL)
2257 const char *diff_extensions;
2258 svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS,
2259 SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL);
2260 if (diff_extensions)
2261 options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE,
2266 if (options == NULL)
2267 options = apr_array_make(result_pool, 0, sizeof(const char *));
2270 SVN_ERR(svn_path_cstring_to_utf8(&dwi->diff_cmd, diff_cmd,
2273 dwi->diff_cmd = NULL;
2275 /* If there was a command, arrange options to pass to it. */
2278 const char **argv = NULL;
2279 int argc = options->nelts;
2283 argv = apr_palloc(result_pool, argc * sizeof(char *));
2284 for (i = 0; i < argc; i++)
2285 SVN_ERR(svn_utf_cstring_to_utf8(&argv[i],
2286 APR_ARRAY_IDX(options, i, const char *), result_pool));
2288 dwi->options.for_external.argv = argv;
2289 dwi->options.for_external.argc = argc;
2291 else /* No command, so arrange options for internal invocation instead. */
2293 dwi->options.for_internal = svn_diff_file_options_create(result_pool);
2294 SVN_ERR(svn_diff_file_options_parse(dwi->options.for_internal,
2295 options, result_pool));
2298 return SVN_NO_ERROR;
2301 /*----------------------------------------------------------------------- */
2303 /*** Public Interfaces. ***/
2305 /* Display context diffs between two PATH/REVISION pairs. Each of
2306 these inputs will be one of the following:
2308 - a repository URL at a given revision.
2309 - a working copy path, ignoring local mods.
2310 - a working copy path, including local mods.
2312 We can establish a matrix that shows the nine possible types of
2313 diffs we expect to support.
2316 ` . DST || URL:rev | WC:base | WC:working |
2319 ============++============+============+============+
2320 URL:rev || (*) | (*) | (*) |
2324 ------------++------------+------------+------------+
2326 || | New svn_wc_diff which |
2327 || | is smart enough to |
2328 || | handle two WC paths |
2329 ------------++------------+ and their related +
2330 WC:working || (*) | text-bases and working |
2331 || | files. This operation |
2332 || | is entirely local. |
2334 ------------++------------+------------+------------+
2335 * These cases require server communication.
2338 svn_client_diff6(const apr_array_header_t *options,
2339 const char *path_or_url1,
2340 const svn_opt_revision_t *revision1,
2341 const char *path_or_url2,
2342 const svn_opt_revision_t *revision2,
2343 const char *relative_to_dir,
2345 svn_boolean_t ignore_ancestry,
2346 svn_boolean_t no_diff_added,
2347 svn_boolean_t no_diff_deleted,
2348 svn_boolean_t show_copies_as_adds,
2349 svn_boolean_t ignore_content_type,
2350 svn_boolean_t ignore_properties,
2351 svn_boolean_t properties_only,
2352 svn_boolean_t use_git_diff_format,
2353 const char *header_encoding,
2354 svn_stream_t *outstream,
2355 svn_stream_t *errstream,
2356 const apr_array_header_t *changelists,
2357 svn_client_ctx_t *ctx,
2360 diff_writer_info_t dwi = { 0 };
2361 svn_opt_revision_t peg_revision;
2362 const svn_diff_tree_processor_t *diff_processor;
2363 svn_diff_tree_processor_t *processor;
2365 if (ignore_properties && properties_only)
2366 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2367 _("Cannot ignore properties and show only "
2368 "properties at the same time"));
2370 /* We will never do a pegged diff from here. */
2371 peg_revision.kind = svn_opt_revision_unspecified;
2373 /* setup callback and baton */
2374 dwi.ddi.orig_path_1 = path_or_url1;
2375 dwi.ddi.orig_path_2 = path_or_url2;
2377 SVN_ERR(create_diff_writer_info(&dwi, options,
2378 ctx->config, pool));
2380 dwi.outstream = outstream;
2381 dwi.errstream = errstream;
2382 dwi.header_encoding = header_encoding;
2384 dwi.force_binary = ignore_content_type;
2385 dwi.ignore_properties = ignore_properties;
2386 dwi.properties_only = properties_only;
2387 dwi.relative_to_dir = relative_to_dir;
2388 dwi.use_git_diff_format = use_git_diff_format;
2389 dwi.no_diff_added = no_diff_added;
2390 dwi.no_diff_deleted = no_diff_deleted;
2391 dwi.show_copies_as_adds = show_copies_as_adds;
2393 dwi.cancel_func = ctx->cancel_func;
2394 dwi.cancel_baton = ctx->cancel_baton;
2396 dwi.wc_ctx = ctx->wc_ctx;
2397 dwi.ddi.session_relpath = NULL;
2398 dwi.ddi.anchor = NULL;
2400 processor = svn_diff__tree_processor_create(&dwi, pool);
2402 processor->dir_added = diff_dir_added;
2403 processor->dir_changed = diff_dir_changed;
2404 processor->dir_deleted = diff_dir_deleted;
2406 processor->file_added = diff_file_added;
2407 processor->file_changed = diff_file_changed;
2408 processor->file_deleted = diff_file_deleted;
2410 diff_processor = processor;
2412 /* --show-copies-as-adds and --git imply --notice-ancestry */
2413 if (show_copies_as_adds || use_git_diff_format)
2414 ignore_ancestry = FALSE;
2416 return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi,
2417 path_or_url1, path_or_url2,
2418 revision1, revision2, &peg_revision,
2419 depth, ignore_ancestry, changelists,
2420 TRUE /* text_deltas */,
2421 diff_processor, ctx, pool, pool));
2425 svn_client_diff_peg6(const apr_array_header_t *options,
2426 const char *path_or_url,
2427 const svn_opt_revision_t *peg_revision,
2428 const svn_opt_revision_t *start_revision,
2429 const svn_opt_revision_t *end_revision,
2430 const char *relative_to_dir,
2432 svn_boolean_t ignore_ancestry,
2433 svn_boolean_t no_diff_added,
2434 svn_boolean_t no_diff_deleted,
2435 svn_boolean_t show_copies_as_adds,
2436 svn_boolean_t ignore_content_type,
2437 svn_boolean_t ignore_properties,
2438 svn_boolean_t properties_only,
2439 svn_boolean_t use_git_diff_format,
2440 const char *header_encoding,
2441 svn_stream_t *outstream,
2442 svn_stream_t *errstream,
2443 const apr_array_header_t *changelists,
2444 svn_client_ctx_t *ctx,
2447 diff_writer_info_t dwi = { 0 };
2448 const svn_diff_tree_processor_t *diff_processor;
2449 svn_diff_tree_processor_t *processor;
2451 if (ignore_properties && properties_only)
2452 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2453 _("Cannot ignore properties and show only "
2454 "properties at the same time"));
2456 /* setup callback and baton */
2457 dwi.ddi.orig_path_1 = path_or_url;
2458 dwi.ddi.orig_path_2 = path_or_url;
2460 SVN_ERR(create_diff_writer_info(&dwi, options,
2461 ctx->config, pool));
2463 dwi.outstream = outstream;
2464 dwi.errstream = errstream;
2465 dwi.header_encoding = header_encoding;
2467 dwi.force_binary = ignore_content_type;
2468 dwi.ignore_properties = ignore_properties;
2469 dwi.properties_only = properties_only;
2470 dwi.relative_to_dir = relative_to_dir;
2471 dwi.use_git_diff_format = use_git_diff_format;
2472 dwi.no_diff_added = no_diff_added;
2473 dwi.no_diff_deleted = no_diff_deleted;
2474 dwi.show_copies_as_adds = show_copies_as_adds;
2476 dwi.cancel_func = ctx->cancel_func;
2477 dwi.cancel_baton = ctx->cancel_baton;
2479 dwi.wc_ctx = ctx->wc_ctx;
2480 dwi.ddi.session_relpath = NULL;
2481 dwi.ddi.anchor = NULL;
2483 processor = svn_diff__tree_processor_create(&dwi, pool);
2485 processor->dir_added = diff_dir_added;
2486 processor->dir_changed = diff_dir_changed;
2487 processor->dir_deleted = diff_dir_deleted;
2489 processor->file_added = diff_file_added;
2490 processor->file_changed = diff_file_changed;
2491 processor->file_deleted = diff_file_deleted;
2493 diff_processor = processor;
2495 /* --show-copies-as-adds and --git imply --notice-ancestry */
2496 if (show_copies_as_adds || use_git_diff_format)
2497 ignore_ancestry = FALSE;
2499 return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi,
2500 path_or_url, path_or_url,
2501 start_revision, end_revision, peg_revision,
2502 depth, ignore_ancestry, changelists,
2503 TRUE /* text_deltas */,
2504 diff_processor, ctx, pool, pool));
2508 svn_client_diff_summarize2(const char *path_or_url1,
2509 const svn_opt_revision_t *revision1,
2510 const char *path_or_url2,
2511 const svn_opt_revision_t *revision2,
2513 svn_boolean_t ignore_ancestry,
2514 const apr_array_header_t *changelists,
2515 svn_client_diff_summarize_func_t summarize_func,
2516 void *summarize_baton,
2517 svn_client_ctx_t *ctx,
2520 const svn_diff_tree_processor_t *diff_processor;
2521 svn_opt_revision_t peg_revision;
2522 const char **p_root_relpath;
2524 /* We will never do a pegged diff from here. */
2525 peg_revision.kind = svn_opt_revision_unspecified;
2527 SVN_ERR(svn_client__get_diff_summarize_callbacks(
2528 &diff_processor, &p_root_relpath,
2529 summarize_func, summarize_baton,
2530 path_or_url1, pool, pool));
2532 return svn_error_trace(do_diff(p_root_relpath, NULL, NULL,
2533 path_or_url1, path_or_url2,
2534 revision1, revision2, &peg_revision,
2535 depth, ignore_ancestry, changelists,
2536 FALSE /* text_deltas */,
2537 diff_processor, ctx, pool, pool));
2541 svn_client_diff_summarize_peg2(const char *path_or_url,
2542 const svn_opt_revision_t *peg_revision,
2543 const svn_opt_revision_t *start_revision,
2544 const svn_opt_revision_t *end_revision,
2546 svn_boolean_t ignore_ancestry,
2547 const apr_array_header_t *changelists,
2548 svn_client_diff_summarize_func_t summarize_func,
2549 void *summarize_baton,
2550 svn_client_ctx_t *ctx,
2553 const svn_diff_tree_processor_t *diff_processor;
2554 const char **p_root_relpath;
2556 SVN_ERR(svn_client__get_diff_summarize_callbacks(
2557 &diff_processor, &p_root_relpath,
2558 summarize_func, summarize_baton,
2559 path_or_url, pool, pool));
2561 return svn_error_trace(do_diff(p_root_relpath, NULL, NULL,
2562 path_or_url, path_or_url,
2563 start_revision, end_revision, peg_revision,
2564 depth, ignore_ancestry, changelists,
2565 FALSE /* text_deltas */,
2566 diff_processor, ctx, pool, pool));