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 RESULT-POOL. */
235 diff_label(const char *path,
237 apr_pool_t *result_pool)
241 label = apr_psprintf(result_pool, _("%s\t(revision %ld)"), path, revnum);
242 else if (revnum == DIFF_REVNUM_NONEXISTENT)
243 label = apr_psprintf(result_pool, _("%s\t(nonexistent)"), path);
244 else /* SVN_INVALID_REVNUM */
245 label = apr_psprintf(result_pool, _("%s\t(working copy)"), path);
250 /* Standard modes produced in git style diffs */
251 static const int exec_mode = 0755;
252 static const int noexec_mode = 0644;
253 static const int kind_file_mode = 0100000;
254 /*static const kind_dir_mode = 0040000;*/
255 static const int kind_symlink_mode = 0120000;
257 /* Print a git diff header for an addition within a diff between PATH1 and
258 * PATH2 to the stream OS using HEADER_ENCODING. */
260 print_git_diff_header_added(svn_stream_t *os, const char *header_encoding,
261 const char *path1, const char *path2,
262 svn_boolean_t exec_bit,
263 svn_boolean_t symlink_bit,
264 apr_pool_t *scratch_pool)
266 int new_mode = (exec_bit ? exec_mode : noexec_mode)
267 | (symlink_bit ? kind_symlink_mode : kind_file_mode);
269 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
270 "diff --git a/%s b/%s%s",
271 path1, path2, APR_EOL_STR));
272 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
273 "new file mode %06o" APR_EOL_STR,
278 /* Print a git diff header for a deletion within a diff between PATH1 and
279 * PATH2 to the stream OS using HEADER_ENCODING. */
281 print_git_diff_header_deleted(svn_stream_t *os, const char *header_encoding,
282 const char *path1, const char *path2,
283 svn_boolean_t exec_bit,
284 svn_boolean_t symlink_bit,
285 apr_pool_t *scratch_pool)
287 int old_mode = (exec_bit ? exec_mode : noexec_mode)
288 | (symlink_bit ? kind_symlink_mode : kind_file_mode);
289 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_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, scratch_pool,
293 "deleted file mode %06o" APR_EOL_STR,
298 /* Print a git diff header for a copy from COPYFROM_PATH to PATH to the stream
299 * OS using HEADER_ENCODING. */
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 *scratch_pool)
307 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_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, scratch_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, scratch_pool,
316 "copy from %s%s", copyfrom_path,
318 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_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. */
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 *scratch_pool)
330 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_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, scratch_pool,
334 "rename from %s%s", copyfrom_path,
336 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_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. */
344 print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding,
345 const char *path1, const char *path2,
346 apr_pool_t *scratch_pool)
348 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
349 "diff --git a/%s b/%s%s",
350 path1, path2, APR_EOL_STR));
354 /* Helper function for print_git_diff_header */
356 maybe_print_mode_change(svn_stream_t *os,
357 const char *header_encoding,
358 svn_boolean_t exec_bit1,
359 svn_boolean_t exec_bit2,
360 svn_boolean_t symlink_bit1,
361 svn_boolean_t symlink_bit2,
362 const char *git_index_shas,
363 apr_pool_t *scratch_pool)
365 int old_mode = (exec_bit1 ? exec_mode : noexec_mode)
366 | (symlink_bit1 ? kind_symlink_mode : kind_file_mode);
367 int new_mode = (exec_bit2 ? exec_mode : noexec_mode)
368 | (symlink_bit2 ? kind_symlink_mode : kind_file_mode);
369 if (old_mode == new_mode)
372 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
373 "index %s %06o" APR_EOL_STR,
374 git_index_shas, old_mode));
378 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
379 "old mode %06o" APR_EOL_STR, old_mode));
380 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool,
381 "new mode %06o" APR_EOL_STR, new_mode));
385 /* Print a git diff header showing the OPERATION to the stream OS using
386 * HEADER_ENCODING. Return suitable diff labels for the git diff in *LABEL1
387 * and *LABEL2. REPOS_RELPATH1 and REPOS_RELPATH2 are relative to reposroot.
388 * are the paths passed to the original diff command. REV1 and REV2 are
389 * revisions being diffed. COPYFROM_PATH and COPYFROM_REV indicate where the
390 * diffed item was copied from.
391 * Use SCRATCH_POOL for temporary allocations. */
393 print_git_diff_header(svn_stream_t *os,
394 const char **label1, const char **label2,
395 svn_diff_operation_kind_t operation,
396 const char *repos_relpath1,
397 const char *repos_relpath2,
400 const char *copyfrom_path,
401 svn_revnum_t copyfrom_rev,
402 apr_hash_t *left_props,
403 apr_hash_t *right_props,
404 const char *git_index_shas,
405 const char *header_encoding,
406 apr_pool_t *scratch_pool)
408 svn_boolean_t exec_bit1 = (svn_prop_get_value(left_props,
409 SVN_PROP_EXECUTABLE) != NULL);
410 svn_boolean_t exec_bit2 = (svn_prop_get_value(right_props,
411 SVN_PROP_EXECUTABLE) != NULL);
412 svn_boolean_t symlink_bit1 = (svn_prop_get_value(left_props,
413 SVN_PROP_SPECIAL) != NULL);
414 svn_boolean_t symlink_bit2 = (svn_prop_get_value(right_props,
415 SVN_PROP_SPECIAL) != NULL);
417 if (operation == svn_diff_op_deleted)
419 SVN_ERR(print_git_diff_header_deleted(os, header_encoding,
420 repos_relpath1, repos_relpath2,
421 exec_bit1, symlink_bit1,
423 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
425 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
429 else if (operation == svn_diff_op_copied)
431 SVN_ERR(print_git_diff_header_copied(os, header_encoding,
432 copyfrom_path, copyfrom_rev,
435 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
437 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
439 SVN_ERR(maybe_print_mode_change(os, header_encoding,
440 exec_bit1, exec_bit2,
441 symlink_bit1, symlink_bit2,
445 else if (operation == svn_diff_op_added)
447 SVN_ERR(print_git_diff_header_added(os, header_encoding,
448 repos_relpath1, repos_relpath2,
449 exec_bit2, symlink_bit2,
451 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
453 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
456 else if (operation == svn_diff_op_modified)
458 SVN_ERR(print_git_diff_header_modified(os, header_encoding,
459 repos_relpath1, repos_relpath2,
461 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1),
463 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
465 SVN_ERR(maybe_print_mode_change(os, header_encoding,
466 exec_bit1, exec_bit2,
467 symlink_bit1, symlink_bit2,
471 else if (operation == svn_diff_op_moved)
473 SVN_ERR(print_git_diff_header_renamed(os, header_encoding,
474 copyfrom_path, repos_relpath2,
476 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path),
478 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2),
480 SVN_ERR(maybe_print_mode_change(os, header_encoding,
481 exec_bit1, exec_bit2,
482 symlink_bit1, symlink_bit2,
490 /* A helper func that writes out verbal descriptions of property diffs
491 to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was
492 passed to svn_client_diff6(), which is probably stdout.
494 ### FIXME needs proper docstring
496 If USE_GIT_DIFF_FORMAT is TRUE, pring git diff headers, which always
497 show paths relative to the repository root. RA_SESSION and WC_CTX are
498 needed to normalize paths relative the repository root, and are ignored
499 if USE_GIT_DIFF_FORMAT is FALSE.
501 ANCHOR is the local path where the diff editor is anchored. */
503 display_prop_diffs(const apr_array_header_t *propchanges,
504 apr_hash_t *left_props,
505 apr_hash_t *right_props,
506 const char *diff_relpath,
508 const char *orig_path1,
509 const char *orig_path2,
512 const char *encoding,
513 svn_stream_t *outstream,
514 const char *relative_to_dir,
515 svn_boolean_t show_diff_header,
516 svn_boolean_t use_git_diff_format,
517 const char *ra_session_relpath,
518 svn_cancel_func_t cancel_func,
520 svn_wc_context_t *wc_ctx,
521 apr_pool_t *scratch_pool)
523 const char *repos_relpath1 = NULL;
524 const char *repos_relpath2 = NULL;
525 const char *index_path = diff_relpath;
526 const char *adjusted_path1 = orig_path1;
527 const char *adjusted_path2 = orig_path2;
529 if (use_git_diff_format)
531 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, orig_path1,
532 ra_session_relpath, wc_ctx, anchor,
533 scratch_pool, scratch_pool));
534 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, orig_path2,
535 ra_session_relpath, wc_ctx, anchor,
536 scratch_pool, scratch_pool));
539 /* If we're creating a diff on the wc root, path would be empty. */
540 SVN_ERR(adjust_paths_for_diff_labels(&index_path, &adjusted_path1,
542 relative_to_dir, anchor,
543 scratch_pool, scratch_pool));
545 if (show_diff_header)
550 label1 = diff_label(adjusted_path1, rev1, scratch_pool);
551 label2 = diff_label(adjusted_path2, rev2, scratch_pool);
553 /* ### Should we show the paths in platform specific format,
554 * ### diff_content_changed() does not! */
556 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
557 "Index: %s" APR_EOL_STR
558 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
561 if (use_git_diff_format)
562 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
563 svn_diff_op_modified,
564 repos_relpath1, repos_relpath2,
570 encoding, scratch_pool));
574 SVN_ERR(svn_diff__unidiff_write_header(
575 outstream, encoding, label1, label2, scratch_pool));
578 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
580 "Property changes on: %s"
586 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool,
587 SVN_DIFF__UNDER_STRING APR_EOL_STR));
589 SVN_ERR(svn_diff__display_prop_diffs(
590 outstream, encoding, propchanges, left_props,
591 TRUE /* pretty_print_mergeinfo */,
592 -1 /* context_size */,
593 cancel_func, cancel_baton, scratch_pool));
598 /*-----------------------------------------------------------------*/
600 /*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/
602 /* State provided by the diff drivers; used by the diff writer */
603 typedef struct diff_driver_info_t
605 /* The anchor to prefix before wc paths */
608 /* Relative path of ra session from repos_root_url */
609 const char *session_relpath;
611 /* The original targets passed to the diff command. We may need
612 these to construct distinctive diff labels when comparing the
613 same relative path in the same revision, under different anchors
614 (for example, when comparing a trunk against a branch). */
615 const char *orig_path_1;
616 const char *orig_path_2;
617 } diff_driver_info_t;
620 /* Diff writer state */
621 typedef struct diff_writer_info_t
623 /* If non-null, the external diff command to invoke. */
624 const char *diff_cmd;
626 /* This is allocated in this struct's pool or a higher-up pool. */
628 /* If 'diff_cmd' is null, then this is the parsed options to
629 pass to the internal libsvn_diff implementation. */
630 svn_diff_file_options_t *for_internal;
631 /* Else if 'diff_cmd' is non-null, then... */
633 /* ...this is an argument array for the external command, and */
635 /* ...this is the length of argv. */
641 svn_stream_t *outstream;
642 svn_stream_t *errstream;
644 const char *header_encoding;
646 /* Set this if you want diff output even for binary files. */
647 svn_boolean_t force_binary;
649 /* The directory that diff target paths should be considered as
650 relative to for output generation (see issue #2723). */
651 const char *relative_to_dir;
653 /* Whether property differences are ignored. */
654 svn_boolean_t ignore_properties;
656 /* Whether to show only property changes. */
657 svn_boolean_t properties_only;
659 /* Whether we're producing a git-style diff. */
660 svn_boolean_t use_git_diff_format;
662 /* Whether addition of a file is summarized versus showing a full diff. */
663 svn_boolean_t no_diff_added;
665 /* Whether deletion of a file is summarized versus showing a full diff. */
666 svn_boolean_t no_diff_deleted;
668 /* Whether to ignore copyfrom information when showing adds */
669 svn_boolean_t show_copies_as_adds;
671 /* Empty files for creating diffs or NULL if not used yet */
672 const char *empty_file;
674 svn_wc_context_t *wc_ctx;
676 svn_cancel_func_t cancel_func;
679 struct diff_driver_info_t ddi;
680 } diff_writer_info_t;
682 /* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added
685 diff_props_changed(const char *diff_relpath,
688 const apr_array_header_t *propchanges,
689 apr_hash_t *left_props,
690 apr_hash_t *right_props,
691 svn_boolean_t show_diff_header,
692 diff_writer_info_t *dwi,
693 apr_pool_t *scratch_pool)
695 apr_array_header_t *props;
697 /* If property differences are ignored, there's nothing to do. */
698 if (dwi->ignore_properties)
701 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props,
704 if (props->nelts > 0)
706 /* We're using the revnums from the dwi since there's
707 * no revision argument to the svn_wc_diff_callback_t
708 * dir_props_changed(). */
709 SVN_ERR(display_prop_diffs(props, left_props, right_props,
712 dwi->ddi.orig_path_1,
713 dwi->ddi.orig_path_2,
716 dwi->header_encoding,
718 dwi->relative_to_dir,
720 dwi->use_git_diff_format,
721 dwi->ddi.session_relpath,
731 /* Given a file ORIG_TMPFILE, return a path to a temporary file that lives at
732 * least as long as RESULT_POOL, containing the git-like represention of
735 transform_link_to_git(const char **new_tmpfile,
736 const char **git_sha1,
737 const char *orig_tmpfile,
738 apr_pool_t *result_pool,
739 apr_pool_t *scratch_pool)
743 svn_stringbuf_t *line;
747 SVN_ERR(svn_io_file_open(&orig, orig_tmpfile, APR_READ, APR_OS_DEFAULT,
749 SVN_ERR(svn_io_open_unique_file3(&gitlike, new_tmpfile, NULL,
750 svn_io_file_del_on_pool_cleanup,
751 result_pool, scratch_pool));
753 SVN_ERR(svn_io_file_readline(orig, &line, NULL, NULL, 2 * APR_PATH_MAX + 2,
754 scratch_pool, scratch_pool));
756 if (line->len > 5 && !strncmp(line->data, "link ", 5))
759 svn_checksum_t *checksum;
761 svn_stringbuf_remove(line, 0, 5);
763 SVN_ERR(svn_io_file_write_full(gitlike, line->data, line->len,
764 NULL, scratch_pool));
766 /* git calculates the sha over "blob X\0" + the actual data,
767 where X is the decimal size of the blob. */
768 sz_str = apr_psprintf(scratch_pool, "blob %u", (unsigned int)line->len);
769 svn_stringbuf_insert(line, 0, sz_str, strlen(sz_str) + 1);
771 SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1,
772 line->data, line->len, scratch_pool));
774 *git_sha1 = svn_checksum_to_cstring(checksum, result_pool);
778 /* Not a link... so can't convert */
779 *new_tmpfile = apr_pstrdup(result_pool, orig_tmpfile);
782 SVN_ERR(svn_io_file_close(orig, scratch_pool));
783 SVN_ERR(svn_io_file_close(gitlike, scratch_pool));
787 /* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and
788 REV2 are used in the headers to indicate the file and revisions. If either
789 MIMETYPE1 or MIMETYPE2 indicate binary content, don't show a diff,
790 but instead print a warning message.
792 If FORCE_DIFF is TRUE, always write a diff, even for empty diffs.
794 Set *WROTE_HEADER to TRUE if a diff header was written */
796 diff_content_changed(svn_boolean_t *wrote_header,
797 const char *diff_relpath,
798 const char *tmpfile1,
799 const char *tmpfile2,
802 apr_hash_t *left_props,
803 apr_hash_t *right_props,
804 svn_diff_operation_kind_t operation,
805 svn_boolean_t force_diff,
806 const char *copyfrom_path,
807 svn_revnum_t copyfrom_rev,
808 diff_writer_info_t *dwi,
809 apr_pool_t *scratch_pool)
811 const char *rel_to_dir = dwi->relative_to_dir;
812 svn_stream_t *outstream = dwi->outstream;
813 const char *label1, *label2;
814 svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE;
815 const char *index_path = diff_relpath;
816 const char *path1 = dwi->ddi.orig_path_1;
817 const char *path2 = dwi->ddi.orig_path_2;
818 const char *mimetype1 = svn_prop_get_value(left_props, SVN_PROP_MIME_TYPE);
819 const char *mimetype2 = svn_prop_get_value(right_props, SVN_PROP_MIME_TYPE);
820 const char *index_shas = NULL;
822 /* If only property differences are shown, there's nothing to do. */
823 if (dwi->properties_only)
826 /* Generate the diff headers. */
827 SVN_ERR(adjust_paths_for_diff_labels(&index_path, &path1, &path2,
828 rel_to_dir, dwi->ddi.anchor,
829 scratch_pool, scratch_pool));
831 label1 = diff_label(path1, rev1, scratch_pool);
832 label2 = diff_label(path2, rev2, scratch_pool);
834 /* Possible easy-out: if either mime-type is binary and force was not
835 specified, don't attempt to generate a viewable diff at all.
836 Print a warning and exit. */
838 mt1_binary = svn_mime_type_is_binary(mimetype1);
840 mt2_binary = svn_mime_type_is_binary(mimetype2);
842 if (dwi->use_git_diff_format)
844 const char *l_hash = NULL;
845 const char *r_hash = NULL;
847 /* Change symlinks to their 'git like' plain format */
848 if (svn_prop_get_value(left_props, SVN_PROP_SPECIAL))
849 SVN_ERR(transform_link_to_git(&tmpfile1, &l_hash, tmpfile1,
850 scratch_pool, scratch_pool));
851 if (svn_prop_get_value(right_props, SVN_PROP_SPECIAL))
852 SVN_ERR(transform_link_to_git(&tmpfile2, &r_hash, tmpfile2,
853 scratch_pool, scratch_pool));
855 if (l_hash && r_hash)
857 /* The symlink has changed. But we can't tell the user of the
858 diff whether we are writing git diffs or svn diffs of the
859 symlink... except when we add a git-like index line */
861 l_hash = apr_pstrndup(scratch_pool, l_hash, 8);
862 r_hash = apr_pstrndup(scratch_pool, r_hash, 8);
864 index_shas = apr_psprintf(scratch_pool, "%8s..%8s",
869 if (! dwi->force_binary && (mt1_binary || mt2_binary))
871 /* Print out the diff header. */
872 SVN_ERR(svn_stream_printf_from_utf8(outstream,
873 dwi->header_encoding, scratch_pool,
874 "Index: %s" APR_EOL_STR
875 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
878 *wrote_header = TRUE;
880 /* ### Print git diff headers. */
882 if (dwi->use_git_diff_format)
884 svn_stream_t *left_stream;
885 svn_stream_t *right_stream;
886 const char *repos_relpath1;
887 const char *repos_relpath2;
888 const char *copyfrom_repos_relpath = NULL;
890 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
891 dwi->ddi.orig_path_1,
892 dwi->ddi.session_relpath,
895 scratch_pool, scratch_pool));
896 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
897 dwi->ddi.orig_path_2,
898 dwi->ddi.session_relpath,
901 scratch_pool, scratch_pool));
903 SVN_ERR(make_repos_relpath(©from_repos_relpath, copyfrom_path,
904 dwi->ddi.orig_path_2,
905 dwi->ddi.session_relpath,
908 scratch_pool, scratch_pool));
909 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
911 repos_relpath1, repos_relpath2,
913 copyfrom_repos_relpath,
918 dwi->header_encoding,
921 SVN_ERR(svn_stream_open_readonly(&left_stream, tmpfile1,
922 scratch_pool, scratch_pool));
923 SVN_ERR(svn_stream_open_readonly(&right_stream, tmpfile2,
924 scratch_pool, scratch_pool));
925 SVN_ERR(svn_diff_output_binary(outstream,
926 left_stream, right_stream,
927 dwi->cancel_func, dwi->cancel_baton,
932 SVN_ERR(svn_stream_printf_from_utf8(outstream,
933 dwi->header_encoding, scratch_pool,
934 _("Cannot display: file marked as a binary type.%s"),
937 if (mt1_binary && !mt2_binary)
938 SVN_ERR(svn_stream_printf_from_utf8(outstream,
939 dwi->header_encoding, scratch_pool,
940 "svn:mime-type = %s" APR_EOL_STR, mimetype1));
941 else if (mt2_binary && !mt1_binary)
942 SVN_ERR(svn_stream_printf_from_utf8(outstream,
943 dwi->header_encoding, scratch_pool,
944 "svn:mime-type = %s" APR_EOL_STR, mimetype2));
945 else if (mt1_binary && mt2_binary)
947 if (strcmp(mimetype1, mimetype2) == 0)
948 SVN_ERR(svn_stream_printf_from_utf8(outstream,
949 dwi->header_encoding, scratch_pool,
950 "svn:mime-type = %s" APR_EOL_STR,
953 SVN_ERR(svn_stream_printf_from_utf8(outstream,
954 dwi->header_encoding, scratch_pool,
955 "svn:mime-type = (%s, %s)" APR_EOL_STR,
956 mimetype1, mimetype2));
967 svn_stream_t *errstream = dwi->errstream;
970 const char *outfilename;
971 const char *errfilename;
972 svn_stream_t *stream;
975 /* Print out the diff header. */
976 SVN_ERR(svn_stream_printf_from_utf8(outstream,
977 dwi->header_encoding, scratch_pool,
978 "Index: %s" APR_EOL_STR
979 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
982 /* ### Do we want to add git diff headers here too? I'd say no. The
983 * ### 'Index' and '===' line is something subversion has added. The rest
984 * ### is up to the external diff application. We may be dealing with
985 * ### a non-git compatible diff application.*/
987 /* We deal in streams, but svn_io_run_diff2() deals in file handles,
988 so we may need to make temporary files and then copy the contents
990 outfile = svn_stream__aprfile(outstream);
994 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
995 svn_io_file_del_on_pool_cleanup,
996 scratch_pool, scratch_pool));
998 errfile = svn_stream__aprfile(errstream);
1002 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
1003 svn_io_file_del_on_pool_cleanup,
1004 scratch_pool, scratch_pool));
1006 SVN_ERR(svn_io_run_diff2(".",
1007 dwi->options.for_external.argv,
1008 dwi->options.for_external.argc,
1011 &exitcode, outfile, errfile,
1012 dwi->diff_cmd, scratch_pool));
1014 /* Now, open and copy our files to our output streams. */
1017 SVN_ERR(svn_io_file_close(outfile, scratch_pool));
1018 SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
1019 scratch_pool, scratch_pool));
1020 SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(outstream,
1022 NULL, NULL, scratch_pool));
1026 SVN_ERR(svn_io_file_close(errfile, scratch_pool));
1027 SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
1028 scratch_pool, scratch_pool));
1029 SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(errstream,
1031 NULL, NULL, scratch_pool));
1034 /* If we have printed a diff for this path, mark it as visited. */
1036 *wrote_header = TRUE;
1038 else /* use libsvn_diff to generate the diff */
1042 SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2,
1043 dwi->options.for_internal,
1047 || dwi->use_git_diff_format
1048 || svn_diff_contains_diffs(diff))
1050 /* Print out the diff header. */
1051 SVN_ERR(svn_stream_printf_from_utf8(outstream,
1052 dwi->header_encoding, scratch_pool,
1053 "Index: %s" APR_EOL_STR
1054 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1057 if (dwi->use_git_diff_format)
1059 const char *repos_relpath1;
1060 const char *repos_relpath2;
1061 const char *copyfrom_repos_relpath = NULL;
1063 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath,
1064 dwi->ddi.orig_path_1,
1065 dwi->ddi.session_relpath,
1068 scratch_pool, scratch_pool));
1069 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath,
1070 dwi->ddi.orig_path_2,
1071 dwi->ddi.session_relpath,
1074 scratch_pool, scratch_pool));
1076 SVN_ERR(make_repos_relpath(©from_repos_relpath,
1078 dwi->ddi.orig_path_2,
1079 dwi->ddi.session_relpath,
1082 scratch_pool, scratch_pool));
1083 SVN_ERR(print_git_diff_header(outstream, &label1, &label2,
1085 repos_relpath1, repos_relpath2,
1087 copyfrom_repos_relpath,
1092 dwi->header_encoding,
1096 /* Output the actual diff */
1097 if (force_diff || svn_diff_contains_diffs(diff))
1098 SVN_ERR(svn_diff_file_output_unified4(outstream, diff,
1099 tmpfile1, tmpfile2, label1, label2,
1100 dwi->header_encoding, rel_to_dir,
1101 dwi->options.for_internal->show_c_function,
1102 dwi->options.for_internal->context_size,
1103 dwi->cancel_func, dwi->cancel_baton,
1106 /* If we have printed a diff for this path, mark it as visited. */
1107 if (dwi->use_git_diff_format || svn_diff_contains_diffs(diff))
1108 *wrote_header = TRUE;
1112 return SVN_NO_ERROR;
1115 /* An svn_diff_tree_processor_t callback. */
1116 static svn_error_t *
1117 diff_file_changed(const char *relpath,
1118 const svn_diff_source_t *left_source,
1119 const svn_diff_source_t *right_source,
1120 const char *left_file,
1121 const char *right_file,
1122 /*const*/ apr_hash_t *left_props,
1123 /*const*/ apr_hash_t *right_props,
1124 svn_boolean_t file_modified,
1125 const apr_array_header_t *prop_changes,
1127 const struct svn_diff_tree_processor_t *processor,
1128 apr_pool_t *scratch_pool)
1130 diff_writer_info_t *dwi = processor->baton;
1131 svn_boolean_t wrote_header = FALSE;
1134 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1135 left_file, right_file,
1136 left_source->revision,
1137 right_source->revision,
1138 left_props, right_props,
1139 svn_diff_op_modified, FALSE,
1141 SVN_INVALID_REVNUM, dwi,
1143 if (prop_changes->nelts > 0)
1144 SVN_ERR(diff_props_changed(relpath,
1145 left_source->revision,
1146 right_source->revision, prop_changes,
1147 left_props, right_props, !wrote_header,
1148 dwi, scratch_pool));
1149 return SVN_NO_ERROR;
1152 /* Because the repos-diff editor passes at least one empty file to
1153 each of these next two functions, they can be dumb wrappers around
1154 the main workhorse routine. */
1156 /* An svn_diff_tree_processor_t callback. */
1157 static svn_error_t *
1158 diff_file_added(const char *relpath,
1159 const svn_diff_source_t *copyfrom_source,
1160 const svn_diff_source_t *right_source,
1161 const char *copyfrom_file,
1162 const char *right_file,
1163 /*const*/ apr_hash_t *copyfrom_props,
1164 /*const*/ apr_hash_t *right_props,
1166 const struct svn_diff_tree_processor_t *processor,
1167 apr_pool_t *scratch_pool)
1169 diff_writer_info_t *dwi = processor->baton;
1170 svn_boolean_t wrote_header = FALSE;
1171 const char *left_file;
1172 apr_hash_t *left_props;
1173 apr_array_header_t *prop_changes;
1175 if (dwi->no_diff_added)
1177 const char *index_path = relpath;
1179 if (dwi->ddi.anchor)
1180 index_path = svn_dirent_join(dwi->ddi.anchor, relpath,
1183 SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream,
1184 dwi->header_encoding, scratch_pool,
1185 "Index: %s (added)" APR_EOL_STR
1186 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1188 wrote_header = TRUE;
1189 return SVN_NO_ERROR;
1192 /* During repos->wc diff of a copy revision numbers obtained
1193 * from the working copy are always SVN_INVALID_REVNUM. */
1194 if (copyfrom_source && !dwi->show_copies_as_adds)
1196 left_file = copyfrom_file;
1197 left_props = copyfrom_props ? copyfrom_props : apr_hash_make(scratch_pool);
1201 if (!dwi->empty_file)
1202 SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file,
1203 NULL, svn_io_file_del_on_pool_cleanup,
1204 dwi->pool, scratch_pool));
1206 left_file = dwi->empty_file;
1207 left_props = apr_hash_make(scratch_pool);
1209 copyfrom_source = NULL;
1210 copyfrom_file = NULL;
1213 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool));
1215 if (copyfrom_source && right_file)
1216 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1217 left_file, right_file,
1218 copyfrom_source->revision,
1219 right_source->revision,
1220 left_props, right_props,
1221 copyfrom_source->moved_from_relpath
1223 : svn_diff_op_copied,
1224 TRUE /* force diff output */,
1225 copyfrom_source->moved_from_relpath
1226 ? copyfrom_source->moved_from_relpath
1227 : copyfrom_source->repos_relpath,
1228 copyfrom_source->revision,
1229 dwi, scratch_pool));
1230 else if (right_file)
1231 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1232 left_file, right_file,
1233 DIFF_REVNUM_NONEXISTENT,
1234 right_source->revision,
1235 left_props, right_props,
1237 TRUE /* force diff output */,
1238 NULL, SVN_INVALID_REVNUM,
1239 dwi, scratch_pool));
1241 if (prop_changes->nelts > 0)
1242 SVN_ERR(diff_props_changed(relpath,
1243 copyfrom_source ? copyfrom_source->revision
1244 : DIFF_REVNUM_NONEXISTENT,
1245 right_source->revision,
1247 left_props, right_props,
1248 ! wrote_header, dwi, scratch_pool));
1250 return SVN_NO_ERROR;
1253 /* An svn_diff_tree_processor_t callback. */
1254 static svn_error_t *
1255 diff_file_deleted(const char *relpath,
1256 const svn_diff_source_t *left_source,
1257 const char *left_file,
1258 /*const*/ apr_hash_t *left_props,
1260 const struct svn_diff_tree_processor_t *processor,
1261 apr_pool_t *scratch_pool)
1263 diff_writer_info_t *dwi = processor->baton;
1265 if (dwi->no_diff_deleted)
1267 const char *index_path = relpath;
1269 if (dwi->ddi.anchor)
1270 index_path = svn_dirent_join(dwi->ddi.anchor, relpath,
1273 SVN_ERR(svn_stream_printf_from_utf8(dwi->outstream,
1274 dwi->header_encoding, scratch_pool,
1275 "Index: %s (deleted)" APR_EOL_STR
1276 SVN_DIFF__EQUAL_STRING APR_EOL_STR,
1281 svn_boolean_t wrote_header = FALSE;
1283 if (!dwi->empty_file)
1284 SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file,
1285 NULL, svn_io_file_del_on_pool_cleanup,
1286 dwi->pool, scratch_pool));
1289 SVN_ERR(diff_content_changed(&wrote_header, relpath,
1290 left_file, dwi->empty_file,
1291 left_source->revision,
1292 DIFF_REVNUM_NONEXISTENT,
1295 svn_diff_op_deleted, FALSE,
1296 NULL, SVN_INVALID_REVNUM,
1300 if (left_props && apr_hash_count(left_props))
1302 apr_array_header_t *prop_changes;
1304 SVN_ERR(svn_prop_diffs(&prop_changes, apr_hash_make(scratch_pool),
1305 left_props, scratch_pool));
1307 SVN_ERR(diff_props_changed(relpath,
1308 left_source->revision,
1309 DIFF_REVNUM_NONEXISTENT,
1312 ! wrote_header, dwi, scratch_pool));
1316 return SVN_NO_ERROR;
1319 /* An svn_wc_diff_callbacks4_t function. */
1320 static svn_error_t *
1321 diff_dir_changed(const char *relpath,
1322 const svn_diff_source_t *left_source,
1323 const svn_diff_source_t *right_source,
1324 /*const*/ apr_hash_t *left_props,
1325 /*const*/ apr_hash_t *right_props,
1326 const apr_array_header_t *prop_changes,
1328 const struct svn_diff_tree_processor_t *processor,
1329 apr_pool_t *scratch_pool)
1331 diff_writer_info_t *dwi = processor->baton;
1333 SVN_ERR(diff_props_changed(relpath,
1334 left_source->revision,
1335 right_source->revision,
1337 left_props, right_props,
1338 TRUE /* show_diff_header */,
1342 return SVN_NO_ERROR;
1345 /* An svn_diff_tree_processor_t callback. */
1346 static svn_error_t *
1347 diff_dir_added(const char *relpath,
1348 const svn_diff_source_t *copyfrom_source,
1349 const svn_diff_source_t *right_source,
1350 /*const*/ apr_hash_t *copyfrom_props,
1351 /*const*/ apr_hash_t *right_props,
1353 const struct svn_diff_tree_processor_t *processor,
1354 apr_pool_t *scratch_pool)
1356 diff_writer_info_t *dwi = processor->baton;
1357 apr_hash_t *left_props;
1358 apr_array_header_t *prop_changes;
1360 if (dwi->no_diff_added)
1361 return SVN_NO_ERROR;
1363 if (copyfrom_source && !dwi->show_copies_as_adds)
1365 left_props = copyfrom_props ? copyfrom_props
1366 : apr_hash_make(scratch_pool);
1370 left_props = apr_hash_make(scratch_pool);
1371 copyfrom_source = NULL;
1374 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props,
1377 return svn_error_trace(diff_props_changed(relpath,
1378 copyfrom_source ? copyfrom_source->revision
1379 : DIFF_REVNUM_NONEXISTENT,
1380 right_source->revision,
1382 left_props, right_props,
1383 TRUE /* show_diff_header */,
1388 /* An svn_diff_tree_processor_t callback. */
1389 static svn_error_t *
1390 diff_dir_deleted(const char *relpath,
1391 const svn_diff_source_t *left_source,
1392 /*const*/ apr_hash_t *left_props,
1394 const struct svn_diff_tree_processor_t *processor,
1395 apr_pool_t *scratch_pool)
1397 diff_writer_info_t *dwi = processor->baton;
1398 apr_array_header_t *prop_changes;
1399 apr_hash_t *right_props;
1401 if (dwi->no_diff_deleted)
1402 return SVN_NO_ERROR;
1404 right_props = apr_hash_make(scratch_pool);
1405 SVN_ERR(svn_prop_diffs(&prop_changes, right_props,
1406 left_props, scratch_pool));
1408 SVN_ERR(diff_props_changed(relpath,
1409 left_source->revision,
1410 DIFF_REVNUM_NONEXISTENT,
1412 left_props, right_props,
1413 TRUE /* show_diff_header */,
1417 return SVN_NO_ERROR;
1420 /*-----------------------------------------------------------------*/
1422 /** The logic behind 'svn diff' and 'svn merge'. */
1425 /* Hi! This is a comment left behind by Karl, and Ben is too afraid
1426 to erase it at this time, because he's not fully confident that all
1427 this knowledge has been grokked yet.
1429 There are five cases:
1430 1. path is not a URL and start_revision != end_revision
1431 2. path is not a URL and start_revision == end_revision
1432 3. path is a URL and start_revision != end_revision
1433 4. path is a URL and start_revision == end_revision
1434 5. path is not a URL and no revisions given
1436 With only one distinct revision the working copy provides the
1437 other. When path is a URL there is no working copy. Thus
1439 1: compare repository versions for URL coresponding to working copy
1440 2: compare working copy against repository version
1441 3: compare repository versions for URL
1443 5: compare working copy against text-base
1445 Case 4 is not as stupid as it looks, for example it may occur if
1446 the user specifies two dates that resolve to the same revision. */
1449 /** Check if paths PATH_OR_URL1 and PATH_OR_URL2 are urls and if the
1450 * revisions REVISION1 and REVISION2 are local. If PEG_REVISION is not
1451 * unspecified, ensure that at least one of the two revisions is not
1453 * If PATH_OR_URL1 can only be found in the repository, set *IS_REPOS1
1454 * to TRUE. If PATH_OR_URL2 can only be found in the repository, set
1455 * *IS_REPOS2 to TRUE. */
1456 static svn_error_t *
1457 check_paths(svn_boolean_t *is_repos1,
1458 svn_boolean_t *is_repos2,
1459 const char *path_or_url1,
1460 const char *path_or_url2,
1461 const svn_opt_revision_t *revision1,
1462 const svn_opt_revision_t *revision2,
1463 const svn_opt_revision_t *peg_revision)
1465 svn_boolean_t is_local_rev1, is_local_rev2;
1467 /* Verify our revision arguments in light of the paths. */
1468 if ((revision1->kind == svn_opt_revision_unspecified)
1469 || (revision2->kind == svn_opt_revision_unspecified))
1470 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1471 _("Not all required revisions are specified"));
1473 /* Revisions can be said to be local or remote.
1474 * BASE and WORKING are local revisions. */
1476 ((revision1->kind == svn_opt_revision_base)
1477 || (revision1->kind == svn_opt_revision_working));
1479 ((revision2->kind == svn_opt_revision_base)
1480 || (revision2->kind == svn_opt_revision_working));
1482 if (peg_revision->kind != svn_opt_revision_unspecified &&
1483 is_local_rev1 && is_local_rev2)
1484 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
1485 _("At least one revision must be something other "
1486 "than BASE or WORKING when diffing a URL"));
1488 /* Working copy paths with non-local revisions get turned into
1489 URLs. We don't do that here, though. We simply record that it
1490 needs to be done, which is information that helps us choose our
1491 diff helper function. */
1492 *is_repos1 = ! is_local_rev1 || svn_path_is_url(path_or_url1);
1493 *is_repos2 = ! is_local_rev2 || svn_path_is_url(path_or_url2);
1495 return SVN_NO_ERROR;
1498 /* Raise an error if the diff target URL does not exist at REVISION.
1499 * If REVISION does not equal OTHER_REVISION, mention both revisions in
1500 * the error message. Use RA_SESSION to contact the repository.
1501 * Use POOL for temporary allocations. */
1502 static svn_error_t *
1503 check_diff_target_exists(const char *url,
1504 svn_revnum_t revision,
1505 svn_revnum_t other_revision,
1506 svn_ra_session_t *ra_session,
1509 svn_node_kind_t kind;
1510 const char *session_url;
1512 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
1514 if (strcmp(url, session_url) != 0)
1515 SVN_ERR(svn_ra_reparent(ra_session, url, pool));
1517 SVN_ERR(svn_ra_check_path(ra_session, "", revision, &kind, pool));
1518 if (kind == svn_node_none)
1520 if (revision == other_revision)
1521 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1522 _("Diff target '%s' was not found in the "
1523 "repository at revision '%ld'"),
1526 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1527 _("Diff target '%s' was not found in the "
1528 "repository at revision '%ld' or '%ld'"),
1529 url, revision, other_revision);
1532 if (strcmp(url, session_url) != 0)
1533 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
1535 return SVN_NO_ERROR;
1538 /** Prepare a repos repos diff between PATH_OR_URL1 and
1539 * PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2.
1540 * Return URLs and peg revisions in *URL1, *REV1 and in *URL2, *REV2.
1541 * Return suitable anchors in *ANCHOR1 and *ANCHOR2, and targets in
1542 * *TARGET1 and *TARGET2, based on *URL1 and *URL2.
1543 * Indicate the corresponding node kinds in *KIND1 and *KIND2, and verify
1544 * that at least one of the diff targets exists.
1545 * Use client context CTX. Do all allocations in POOL. */
1546 static svn_error_t *
1547 diff_prepare_repos_repos(const char **url1,
1551 const char **anchor1,
1552 const char **anchor2,
1553 const char **target1,
1554 const char **target2,
1555 svn_node_kind_t *kind1,
1556 svn_node_kind_t *kind2,
1557 svn_ra_session_t **ra_session,
1558 svn_client_ctx_t *ctx,
1559 const char *path_or_url1,
1560 const char *path_or_url2,
1561 const svn_opt_revision_t *revision1,
1562 const svn_opt_revision_t *revision2,
1563 const svn_opt_revision_t *peg_revision,
1566 const char *local_abspath1 = NULL;
1567 const char *local_abspath2 = NULL;
1568 const char *repos_root_url;
1569 const char *wri_abspath = NULL;
1570 svn_client__pathrev_t *resolved1;
1571 svn_client__pathrev_t *resolved2 = NULL;
1572 enum svn_opt_revision_kind peg_kind = peg_revision->kind;
1574 if (!svn_path_is_url(path_or_url2))
1576 SVN_ERR(svn_dirent_get_absolute(&local_abspath2, path_or_url2, pool));
1577 SVN_ERR(svn_wc__node_get_url(url2, ctx->wc_ctx, local_abspath2,
1579 wri_abspath = local_abspath2;
1582 *url2 = apr_pstrdup(pool, path_or_url2);
1584 if (!svn_path_is_url(path_or_url1))
1586 SVN_ERR(svn_dirent_get_absolute(&local_abspath1, path_or_url1, pool));
1587 wri_abspath = local_abspath1;
1590 SVN_ERR(svn_client_open_ra_session2(ra_session, *url2, wri_abspath,
1593 /* If we are performing a pegged diff, we need to find out what our
1594 actual URLs will be. */
1595 if (peg_kind != svn_opt_revision_unspecified
1596 || path_or_url1 == path_or_url2
1601 err = svn_client__resolve_rev_and_url(&resolved2,
1602 *ra_session, path_or_url2,
1603 peg_revision, revision2,
1607 if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES
1608 && err->apr_err != SVN_ERR_FS_NOT_FOUND)
1609 return svn_error_trace(err);
1611 svn_error_clear(err);
1618 if (peg_kind != svn_opt_revision_unspecified
1619 || path_or_url1 == path_or_url2
1624 err = svn_client__resolve_rev_and_url(&resolved1,
1625 *ra_session, path_or_url1,
1626 peg_revision, revision1,
1630 if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES
1631 && err->apr_err != SVN_ERR_FS_NOT_FOUND)
1632 return svn_error_trace(err);
1634 svn_error_clear(err);
1643 *url1 = resolved1->url;
1644 *rev1 = resolved1->rev;
1648 /* It would be nice if we could just return an error when resolving a
1649 location fails... But in many such cases we prefer diffing against
1650 an not existing location to show adds od removes (see issue #4153) */
1653 && (peg_kind != svn_opt_revision_unspecified
1654 || path_or_url1 == path_or_url2))
1655 *url1 = resolved2->url;
1656 else if (! local_abspath1)
1657 *url1 = path_or_url1;
1659 SVN_ERR(svn_wc__node_get_url(url1, ctx->wc_ctx, local_abspath1,
1662 SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx,
1663 local_abspath1 /* may be NULL */,
1664 *ra_session, revision1, pool));
1669 *url2 = resolved2->url;
1670 *rev2 = resolved2->rev;
1674 /* It would be nice if we could just return an error when resolving a
1675 location fails... But in many such cases we prefer diffing against
1676 an not existing location to show adds od removes (see issue #4153) */
1679 && (peg_kind != svn_opt_revision_unspecified
1680 || path_or_url1 == path_or_url2))
1681 *url2 = resolved1->url;
1682 /* else keep url2 */
1684 SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx,
1685 local_abspath2 /* may be NULL */,
1686 *ra_session, revision2, pool));
1689 /* Resolve revision and get path kind for the second target. */
1690 SVN_ERR(svn_ra_reparent(*ra_session, *url2, pool));
1691 SVN_ERR(svn_ra_check_path(*ra_session, "", *rev2, kind2, pool));
1693 /* Do the same for the first target. */
1694 SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool));
1695 SVN_ERR(svn_ra_check_path(*ra_session, "", *rev1, kind1, pool));
1697 /* Either both URLs must exist at their respective revisions,
1698 * or one of them may be missing from one side of the diff. */
1699 if (*kind1 == svn_node_none && *kind2 == svn_node_none)
1701 if (strcmp(*url1, *url2) == 0)
1702 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1703 _("Diff target '%s' was not found in the "
1704 "repository at revisions '%ld' and '%ld'"),
1705 *url1, *rev1, *rev2);
1707 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1708 _("Diff targets '%s' and '%s' were not found "
1709 "in the repository at revisions '%ld' and "
1711 *url1, *url2, *rev1, *rev2);
1713 else if (*kind1 == svn_node_none)
1714 SVN_ERR(check_diff_target_exists(*url1, *rev2, *rev1, *ra_session, pool));
1715 else if (*kind2 == svn_node_none)
1716 SVN_ERR(check_diff_target_exists(*url2, *rev1, *rev2, *ra_session, pool));
1718 SVN_ERR(svn_ra_get_repos_root2(*ra_session, &repos_root_url, pool));
1720 /* Choose useful anchors and targets for our two URLs. */
1726 /* If none of the targets is the repository root open the parent directory
1727 to allow describing replacement of the target itself */
1728 if (strcmp(*url1, repos_root_url) != 0
1729 && strcmp(*url2, repos_root_url) != 0)
1731 svn_node_kind_t ignored_kind;
1734 svn_uri_split(anchor1, target1, *url1, pool);
1735 svn_uri_split(anchor2, target2, *url2, pool);
1737 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
1739 /* We might not have the necessary rights to read the root now.
1740 (It is ok to pass a revision here where the node doesn't exist) */
1741 err = svn_ra_check_path(*ra_session, "", *rev1, &ignored_kind, pool);
1743 if (err && (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN
1744 || err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED))
1746 svn_error_clear(err);
1748 /* Ok, lets undo the reparent...
1750 We can't report replacements this way, but at least we can
1751 report changes on the descendants */
1753 *anchor1 = svn_path_url_add_component2(*anchor1, *target1, pool);
1754 *anchor2 = svn_path_url_add_component2(*anchor2, *target2, pool);
1758 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool));
1764 return SVN_NO_ERROR;
1767 /* A Theoretical Note From Ben, regarding do_diff().
1769 This function is really svn_client_diff6(). If you read the public
1770 API description for svn_client_diff6(), it sounds quite Grand. It
1771 sounds really generalized and abstract and beautiful: that it will
1772 diff any two paths, be they working-copy paths or URLs, at any two
1775 Now, the *reality* is that we have exactly three 'tools' for doing
1776 diffing, and thus this routine is built around the use of the three
1777 tools. Here they are, for clarity:
1779 - svn_wc_diff: assumes both paths are the same wcpath.
1780 compares wcpath@BASE vs. wcpath@WORKING
1782 - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING
1784 - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2
1786 Since Subversion 1.8 we also have a variant of svn_wc_diff called
1787 svn_client__arbitrary_nodes_diff, that allows handling WORKING-WORKING
1788 comparisons between nodes in the working copy.
1790 So the truth of the matter is, if the caller's arguments can't be
1791 pigeonholed into one of these use-cases, we currently bail with a
1794 Perhaps someday a brave soul will truly make svn_client_diff6()
1795 perfectly general. For now, we live with the 90% case. Certainly,
1796 the commandline client only calls this function in legal ways.
1797 When there are other users of svn_client.h, maybe this will become
1798 a more pressing issue.
1801 /* Return a "you can't do that" error, optionally wrapping another
1803 static svn_error_t *
1804 unsupported_diff_error(svn_error_t *child_err)
1806 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err,
1807 _("Sorry, svn_client_diff6 was called in a way "
1808 "that is not yet supported"));
1811 /* Perform a diff between two working-copy paths.
1813 PATH1 and PATH2 are both working copy paths. REVISION1 and
1814 REVISION2 are their respective revisions.
1816 All other options are the same as those passed to svn_client_diff6(). */
1817 static svn_error_t *
1818 diff_wc_wc(const char **root_relpath,
1819 svn_boolean_t *root_is_dir,
1820 struct diff_driver_info_t *ddi,
1822 const svn_opt_revision_t *revision1,
1824 const svn_opt_revision_t *revision2,
1826 svn_boolean_t ignore_ancestry,
1827 const apr_array_header_t *changelists,
1828 const svn_diff_tree_processor_t *diff_processor,
1829 svn_client_ctx_t *ctx,
1830 apr_pool_t *result_pool,
1831 apr_pool_t *scratch_pool)
1833 const char *abspath1;
1835 SVN_ERR_ASSERT(! svn_path_is_url(path1));
1836 SVN_ERR_ASSERT(! svn_path_is_url(path2));
1838 SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, scratch_pool));
1840 /* Currently we support only the case where path1 and path2 are the
1842 if ((strcmp(path1, path2) != 0)
1843 || (! ((revision1->kind == svn_opt_revision_base)
1844 && (revision2->kind == svn_opt_revision_working))))
1845 return unsupported_diff_error(
1846 svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
1847 _("Only diffs between a path's text-base "
1848 "and its working files are supported at this time"
1853 svn_node_kind_t kind;
1855 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1,
1856 TRUE, FALSE, scratch_pool));
1858 if (kind != svn_node_dir)
1859 ddi->anchor = svn_dirent_dirname(path1, scratch_pool);
1861 ddi->anchor = path1;
1864 SVN_ERR(svn_wc__diff7(root_relpath, root_is_dir,
1865 ctx->wc_ctx, abspath1, depth,
1866 ignore_ancestry, changelists,
1868 ctx->cancel_func, ctx->cancel_baton,
1869 result_pool, scratch_pool));
1870 return SVN_NO_ERROR;
1873 /* Perform a diff between two repository paths.
1875 PATH_OR_URL1 and PATH_OR_URL2 may be either URLs or the working copy paths.
1876 REVISION1 and REVISION2 are their respective revisions.
1877 If PEG_REVISION is specified, PATH_OR_URL2 is the path at the peg revision,
1878 and the actual two paths compared are determined by following copy
1879 history from PATH_OR_URL2.
1881 All other options are the same as those passed to svn_client_diff6(). */
1882 static svn_error_t *
1883 diff_repos_repos(const char **root_relpath,
1884 svn_boolean_t *root_is_dir,
1885 struct diff_driver_info_t *ddi,
1886 const char *path_or_url1,
1887 const char *path_or_url2,
1888 const svn_opt_revision_t *revision1,
1889 const svn_opt_revision_t *revision2,
1890 const svn_opt_revision_t *peg_revision,
1892 svn_boolean_t ignore_ancestry,
1893 svn_boolean_t text_deltas,
1894 const svn_diff_tree_processor_t *diff_processor,
1895 svn_client_ctx_t *ctx,
1896 apr_pool_t *result_pool,
1897 apr_pool_t *scratch_pool)
1899 svn_ra_session_t *extra_ra_session;
1901 const svn_ra_reporter3_t *reporter;
1902 void *reporter_baton;
1904 const svn_delta_editor_t *diff_editor;
1905 void *diff_edit_baton;
1911 svn_node_kind_t kind1;
1912 svn_node_kind_t kind2;
1913 const char *anchor1;
1914 const char *anchor2;
1915 const char *target1;
1916 const char *target2;
1917 svn_ra_session_t *ra_session;
1919 /* Prepare info for the repos repos diff. */
1920 SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &rev1, &rev2,
1921 &anchor1, &anchor2, &target1, &target2,
1922 &kind1, &kind2, &ra_session,
1923 ctx, path_or_url1, path_or_url2,
1924 revision1, revision2, peg_revision,
1927 /* Set up the repos_diff editor on BASE_PATH, if available.
1928 Otherwise, we just use "". */
1932 /* Get actual URLs. */
1933 ddi->orig_path_1 = url1;
1934 ddi->orig_path_2 = url2;
1936 /* This should be moved to the diff writer
1937 - path_or_url are provided by the caller
1938 - target1 is available as *root_relpath
1939 - (kind1 != svn_node_dir || kind2 != svn_node_dir) = !*root_is_dir */
1941 if (!svn_path_is_url(path_or_url2))
1942 ddi->anchor = path_or_url2;
1943 else if (!svn_path_is_url(path_or_url1))
1944 ddi->anchor = path_or_url1;
1948 if (*target1 && ddi->anchor
1949 && (kind1 != svn_node_dir || kind2 != svn_node_dir))
1950 ddi->anchor = svn_dirent_dirname(ddi->anchor, result_pool);
1953 /* The repository can bring in a new working copy, but not delete
1954 everything. Luckily our new diff handler can just be reversed. */
1955 if (kind2 == svn_node_none)
1957 const char *str_tmp;
1958 svn_revnum_t rev_tmp;
1976 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor,
1981 /* Filter the first path component using a filter processor, until we fixed
1982 the diff processing to handle this directly */
1984 *root_relpath = apr_pstrdup(result_pool, target1);
1985 else if ((kind1 != svn_node_file && kind2 != svn_node_file)
1986 && target1[0] != '\0')
1988 diff_processor = svn_diff__tree_processor_filter_create(
1989 diff_processor, target1, scratch_pool);
1992 /* Now, we open an extra RA session to the correct anchor
1993 location for URL1. This is used during the editor calls to fetch file
1995 SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor1,
1996 scratch_pool, scratch_pool));
2000 const char *repos_root_url;
2001 const char *session_url;
2003 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
2005 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url,
2008 ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url,
2013 SVN_ERR(svn_client__get_diff_editor2(
2014 &diff_editor, &diff_edit_baton,
2015 extra_ra_session, depth,
2019 ctx->cancel_func, ctx->cancel_baton,
2022 /* We want to switch our txn into URL2 */
2023 SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton,
2025 depth, ignore_ancestry, text_deltas,
2026 url2, diff_editor, diff_edit_baton, scratch_pool));
2028 /* Drive the reporter; do the diff. */
2029 SVN_ERR(reporter->set_path(reporter_baton, "", rev1,
2034 return svn_error_trace(
2035 reporter->finish_report(reporter_baton, scratch_pool));
2038 /* Perform a diff between a repository path and a working-copy path.
2040 PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a
2041 working copy path. REVISION1 is the revision of URL1. If PEG_REVISION1
2042 is specified, then PATH_OR_URL1 is the path in the peg revision, and the
2043 actual repository path to be compared is determined by following copy
2046 REVISION_KIND2 specifies which revision should be reported from the
2047 working copy (BASE or WORKING)
2049 If REVERSE is TRUE, the diff will be reported in reverse.
2051 All other options are the same as those passed to svn_client_diff6(). */
2052 static svn_error_t *
2053 diff_repos_wc(const char **root_relpath,
2054 svn_boolean_t *root_is_dir,
2055 struct diff_driver_info_t *ddi,
2056 const char *path_or_url1,
2057 const svn_opt_revision_t *revision1,
2058 const svn_opt_revision_t *peg_revision1,
2060 enum svn_opt_revision_kind revision2_kind,
2061 svn_boolean_t reverse,
2063 svn_boolean_t ignore_ancestry,
2064 const apr_array_header_t *changelists,
2065 const svn_diff_tree_processor_t *diff_processor,
2066 svn_client_ctx_t *ctx,
2067 apr_pool_t *result_pool,
2068 apr_pool_t *scratch_pool)
2070 const char *anchor, *anchor_url, *target;
2071 svn_ra_session_t *ra_session;
2072 svn_depth_t diff_depth;
2073 const svn_ra_reporter3_t *reporter;
2074 void *reporter_baton;
2075 const svn_delta_editor_t *diff_editor;
2076 void *diff_edit_baton;
2077 svn_boolean_t rev2_is_base = (revision2_kind == svn_opt_revision_base);
2078 svn_boolean_t server_supports_depth;
2079 const char *abspath_or_url1;
2080 const char *abspath2;
2081 const char *anchor_abspath;
2082 svn_boolean_t is_copy;
2083 svn_revnum_t cf_revision;
2084 const char *cf_repos_relpath;
2085 const char *cf_repos_root_url;
2086 svn_depth_t cf_depth;
2087 const char *copy_root_abspath;
2088 const char *target_url;
2089 svn_client__pathrev_t *loc1;
2091 SVN_ERR_ASSERT(! svn_path_is_url(path2));
2093 if (!svn_path_is_url(path_or_url1))
2095 SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1,
2100 abspath_or_url1 = path_or_url1;
2103 SVN_ERR(svn_dirent_get_absolute(&abspath2, path2, scratch_pool));
2105 /* Check if our diff target is a copied node. */
2106 SVN_ERR(svn_wc__node_get_origin(&is_copy,
2112 ctx->wc_ctx, abspath2,
2113 FALSE, scratch_pool, scratch_pool));
2115 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc1,
2116 path_or_url1, abspath2,
2117 peg_revision1, revision1,
2118 ctx, scratch_pool));
2120 if (revision2_kind == svn_opt_revision_base || !is_copy)
2122 /* Convert path_or_url1 to a URL to feed to do_diff. */
2123 SVN_ERR(svn_wc_get_actual_target2(&anchor, &target, ctx->wc_ctx, path2,
2124 scratch_pool, scratch_pool));
2126 /* Handle the ugly case where target is ".." */
2127 if (*target && !svn_path_is_single_path_component(target))
2129 anchor = svn_dirent_join(anchor, target, scratch_pool);
2134 *root_relpath = apr_pstrdup(result_pool, target);
2136 *root_is_dir = (*target == '\0');
2138 /* Fetch the URL of the anchor directory. */
2139 SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, scratch_pool));
2140 SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
2141 scratch_pool, scratch_pool));
2142 SVN_ERR_ASSERT(anchor_url != NULL);
2146 else /* is_copy && revision2->kind == svn_opt_revision_base */
2149 svn_node_kind_t kind;
2151 /* ### Ugly hack ahead ###
2153 * We're diffing a locally copied/moved node.
2154 * Describe the copy source to the reporter instead of the copy itself.
2155 * Doing the latter would generate a single add_directory() call to the
2156 * diff editor which results in an unexpected diff (the copy would
2157 * be shown as deleted).
2159 * ### But if we will receive any real changes from the repositor we
2160 * will most likely fail to apply them as the wc diff editor assumes
2161 * that we have the data to which the change applies in BASE...
2164 target_url = svn_path_url_add_component2(cf_repos_root_url,
2169 /*SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath2, FALSE, FALSE,
2172 if (kind != svn_node_dir
2173 || strcmp(copy_root_abspath, abspath2) != 0) */
2176 /* We are looking at a subdirectory of the repository,
2177 We can describe the parent directory as the anchor..
2179 ### This 'appears to work', but that is really dumb luck
2180 ### for the simple cases in the test suite */
2181 anchor_abspath = svn_dirent_dirname(abspath2, scratch_pool);
2182 anchor_url = svn_path_url_add_component2(cf_repos_root_url,
2183 svn_relpath_dirname(
2187 target = svn_dirent_basename(abspath2, NULL);
2188 anchor = svn_dirent_dirname(path2, scratch_pool);
2193 /* This code, while ok can't be enabled without causing test
2194 * failures. The repository will send some changes against
2195 * BASE for nodes that don't have BASE...
2197 anchor_abspath = abspath2;
2198 anchor_url = svn_path_url_add_component2(cf_repos_root_url,
2207 SVN_ERR(svn_ra_reparent(ra_session, anchor_url, scratch_pool));
2211 const char *repos_root_url;
2213 ddi->anchor = anchor;
2217 ddi->orig_path_1 = apr_pstrdup(result_pool, loc1->url);
2219 svn_path_url_add_component2(anchor_url, target, result_pool);
2224 svn_path_url_add_component2(anchor_url, target, result_pool);
2225 ddi->orig_path_2 = apr_pstrdup(result_pool, loc1->url);
2228 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
2231 ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url,
2237 diff_processor = svn_diff__tree_processor_reverse_create(
2238 diff_processor, NULL, scratch_pool);
2240 /* Use the diff editor to generate the diff. */
2241 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
2242 SVN_RA_CAPABILITY_DEPTH, scratch_pool));
2243 SVN_ERR(svn_wc__get_diff_editor(&diff_editor, &diff_edit_baton,
2251 server_supports_depth,
2254 ctx->cancel_func, ctx->cancel_baton,
2255 scratch_pool, scratch_pool));
2257 if (depth != svn_depth_infinity)
2260 diff_depth = svn_depth_unknown;
2262 /* Tell the RA layer we want a delta to change our txn to URL1 */
2263 SVN_ERR(svn_ra_do_diff3(ra_session,
2264 &reporter, &reporter_baton,
2269 TRUE, /* text_deltas */
2271 diff_editor, diff_edit_baton,
2274 if (is_copy && revision2_kind != svn_opt_revision_base)
2276 /* Report the copy source. */
2277 if (cf_depth == svn_depth_unknown)
2278 cf_depth = svn_depth_infinity;
2280 /* Reporting the in-wc revision as r0, makes the repository send
2281 everything as added, which avoids using BASE for pristine information,
2282 which is not there (or unrelated) for a copy */
2284 SVN_ERR(reporter->set_path(reporter_baton, "",
2285 ignore_ancestry ? 0 : cf_revision,
2286 cf_depth, FALSE, NULL, scratch_pool));
2289 SVN_ERR(reporter->link_path(reporter_baton, target,
2291 ignore_ancestry ? 0 : cf_revision,
2292 cf_depth, FALSE, NULL, scratch_pool));
2294 /* Finish the report to generate the diff. */
2295 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool));
2299 /* Create a txn mirror of path2; the diff editor will print
2300 diffs in reverse. :-) */
2301 SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, abspath2,
2302 reporter, reporter_baton,
2304 (! server_supports_depth),
2306 ctx->cancel_func, ctx->cancel_baton,
2307 NULL, NULL, /* notification is N/A */
2311 return SVN_NO_ERROR;
2315 /* This is basically just the guts of svn_client_diff[_summarize][_peg]6(). */
2316 static svn_error_t *
2317 do_diff(const char **root_relpath,
2318 svn_boolean_t *root_is_dir,
2319 diff_driver_info_t *ddi,
2320 const char *path_or_url1,
2321 const char *path_or_url2,
2322 const svn_opt_revision_t *revision1,
2323 const svn_opt_revision_t *revision2,
2324 const svn_opt_revision_t *peg_revision,
2325 svn_boolean_t no_peg_revision,
2327 svn_boolean_t ignore_ancestry,
2328 const apr_array_header_t *changelists,
2329 svn_boolean_t text_deltas,
2330 const svn_diff_tree_processor_t *diff_processor,
2331 svn_client_ctx_t *ctx,
2332 apr_pool_t *result_pool,
2333 apr_pool_t *scratch_pool)
2335 svn_boolean_t is_repos1;
2336 svn_boolean_t is_repos2;
2338 /* Check if paths/revisions are urls/local. */
2339 SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2,
2340 revision1, revision2, peg_revision));
2346 /* Ignores changelists. */
2347 SVN_ERR(diff_repos_repos(root_relpath, root_is_dir,
2349 path_or_url1, path_or_url2,
2350 revision1, revision2,
2351 peg_revision, depth, ignore_ancestry,
2353 diff_processor, ctx,
2354 result_pool, scratch_pool));
2356 else /* path_or_url2 is a working copy path */
2358 SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi,
2359 path_or_url1, revision1,
2360 no_peg_revision ? revision1
2362 path_or_url2, revision2->kind,
2364 ignore_ancestry, changelists,
2365 diff_processor, ctx,
2366 result_pool, scratch_pool));
2369 else /* path_or_url1 is a working copy path */
2373 SVN_ERR(diff_repos_wc(root_relpath, root_is_dir, ddi,
2374 path_or_url2, revision2,
2375 no_peg_revision ? revision2
2380 ignore_ancestry, changelists,
2381 diff_processor, ctx,
2382 result_pool, scratch_pool));
2384 else /* path_or_url2 is a working copy path */
2386 if (revision1->kind == svn_opt_revision_working
2387 && revision2->kind == svn_opt_revision_working)
2389 const char *abspath1;
2390 const char *abspath2;
2392 SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1,
2394 SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2,
2397 /* ### What about ddi? */
2398 /* Ignores changelists, ignore_ancestry */
2399 SVN_ERR(svn_client__arbitrary_nodes_diff(root_relpath, root_is_dir,
2404 result_pool, scratch_pool));
2408 SVN_ERR(diff_wc_wc(root_relpath, root_is_dir, ddi,
2409 path_or_url1, revision1,
2410 path_or_url2, revision2,
2411 depth, ignore_ancestry, changelists,
2412 diff_processor, ctx,
2413 result_pool, scratch_pool));
2418 return SVN_NO_ERROR;
2421 /* Initialize DWI.diff_cmd and DWI.options,
2422 * according to OPTIONS and CONFIG. CONFIG and OPTIONS may be null.
2423 * Allocate the fields in RESULT_POOL, which should be at least as long-lived
2424 * as the pool DWI itself is allocated in.
2426 static svn_error_t *
2427 create_diff_writer_info(diff_writer_info_t *dwi,
2428 const apr_array_header_t *options,
2429 apr_hash_t *config, apr_pool_t *result_pool)
2431 const char *diff_cmd = NULL;
2433 /* See if there is a diff command and/or diff arguments. */
2436 svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG);
2437 svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS,
2438 SVN_CONFIG_OPTION_DIFF_CMD, NULL);
2439 if (options == NULL)
2441 const char *diff_extensions;
2442 svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS,
2443 SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL);
2444 if (diff_extensions)
2445 options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE,
2450 if (options == NULL)
2451 options = apr_array_make(result_pool, 0, sizeof(const char *));
2454 SVN_ERR(svn_path_cstring_to_utf8(&dwi->diff_cmd, diff_cmd,
2457 dwi->diff_cmd = NULL;
2459 /* If there was a command, arrange options to pass to it. */
2462 const char **argv = NULL;
2463 int argc = options->nelts;
2467 argv = apr_palloc(result_pool, argc * sizeof(char *));
2468 for (i = 0; i < argc; i++)
2469 SVN_ERR(svn_utf_cstring_to_utf8(&argv[i],
2470 APR_ARRAY_IDX(options, i, const char *), result_pool));
2472 dwi->options.for_external.argv = argv;
2473 dwi->options.for_external.argc = argc;
2475 else /* No command, so arrange options for internal invocation instead. */
2477 dwi->options.for_internal = svn_diff_file_options_create(result_pool);
2478 SVN_ERR(svn_diff_file_options_parse(dwi->options.for_internal,
2479 options, result_pool));
2482 return SVN_NO_ERROR;
2485 /*----------------------------------------------------------------------- */
2487 /*** Public Interfaces. ***/
2489 /* Display context diffs between two PATH/REVISION pairs. Each of
2490 these inputs will be one of the following:
2492 - a repository URL at a given revision.
2493 - a working copy path, ignoring local mods.
2494 - a working copy path, including local mods.
2496 We can establish a matrix that shows the nine possible types of
2497 diffs we expect to support.
2500 ` . DST || URL:rev | WC:base | WC:working |
2503 ============++============+============+============+
2504 URL:rev || (*) | (*) | (*) |
2508 ------------++------------+------------+------------+
2510 || | New svn_wc_diff which |
2511 || | is smart enough to |
2512 || | handle two WC paths |
2513 ------------++------------+ and their related +
2514 WC:working || (*) | text-bases and working |
2515 || | files. This operation |
2516 || | is entirely local. |
2518 ------------++------------+------------+------------+
2519 * These cases require server communication.
2522 svn_client_diff6(const apr_array_header_t *options,
2523 const char *path_or_url1,
2524 const svn_opt_revision_t *revision1,
2525 const char *path_or_url2,
2526 const svn_opt_revision_t *revision2,
2527 const char *relative_to_dir,
2529 svn_boolean_t ignore_ancestry,
2530 svn_boolean_t no_diff_added,
2531 svn_boolean_t no_diff_deleted,
2532 svn_boolean_t show_copies_as_adds,
2533 svn_boolean_t ignore_content_type,
2534 svn_boolean_t ignore_properties,
2535 svn_boolean_t properties_only,
2536 svn_boolean_t use_git_diff_format,
2537 const char *header_encoding,
2538 svn_stream_t *outstream,
2539 svn_stream_t *errstream,
2540 const apr_array_header_t *changelists,
2541 svn_client_ctx_t *ctx,
2544 diff_writer_info_t dwi = { 0 };
2545 svn_opt_revision_t peg_revision;
2546 const svn_diff_tree_processor_t *diff_processor;
2547 svn_diff_tree_processor_t *processor;
2549 if (ignore_properties && properties_only)
2550 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2551 _("Cannot ignore properties and show only "
2552 "properties at the same time"));
2554 /* We will never do a pegged diff from here. */
2555 peg_revision.kind = svn_opt_revision_unspecified;
2557 /* setup callback and baton */
2558 dwi.ddi.orig_path_1 = path_or_url1;
2559 dwi.ddi.orig_path_2 = path_or_url2;
2561 SVN_ERR(create_diff_writer_info(&dwi, options,
2562 ctx->config, pool));
2564 dwi.outstream = outstream;
2565 dwi.errstream = errstream;
2566 dwi.header_encoding = header_encoding;
2568 dwi.force_binary = ignore_content_type;
2569 dwi.ignore_properties = ignore_properties;
2570 dwi.properties_only = properties_only;
2571 dwi.relative_to_dir = relative_to_dir;
2572 dwi.use_git_diff_format = use_git_diff_format;
2573 dwi.no_diff_added = no_diff_added;
2574 dwi.no_diff_deleted = no_diff_deleted;
2575 dwi.show_copies_as_adds = show_copies_as_adds;
2577 dwi.cancel_func = ctx->cancel_func;
2578 dwi.cancel_baton = ctx->cancel_baton;
2580 dwi.wc_ctx = ctx->wc_ctx;
2581 dwi.ddi.session_relpath = NULL;
2582 dwi.ddi.anchor = NULL;
2584 processor = svn_diff__tree_processor_create(&dwi, pool);
2586 processor->dir_added = diff_dir_added;
2587 processor->dir_changed = diff_dir_changed;
2588 processor->dir_deleted = diff_dir_deleted;
2590 processor->file_added = diff_file_added;
2591 processor->file_changed = diff_file_changed;
2592 processor->file_deleted = diff_file_deleted;
2594 diff_processor = processor;
2596 /* --show-copies-as-adds and --git imply --notice-ancestry */
2597 if (show_copies_as_adds || use_git_diff_format)
2598 ignore_ancestry = FALSE;
2600 return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi,
2601 path_or_url1, path_or_url2,
2602 revision1, revision2,
2603 &peg_revision, TRUE /* no_peg_revision */,
2604 depth, ignore_ancestry, changelists,
2605 TRUE /* text_deltas */,
2606 diff_processor, ctx, pool, pool));
2610 svn_client_diff_peg6(const apr_array_header_t *options,
2611 const char *path_or_url,
2612 const svn_opt_revision_t *peg_revision,
2613 const svn_opt_revision_t *start_revision,
2614 const svn_opt_revision_t *end_revision,
2615 const char *relative_to_dir,
2617 svn_boolean_t ignore_ancestry,
2618 svn_boolean_t no_diff_added,
2619 svn_boolean_t no_diff_deleted,
2620 svn_boolean_t show_copies_as_adds,
2621 svn_boolean_t ignore_content_type,
2622 svn_boolean_t ignore_properties,
2623 svn_boolean_t properties_only,
2624 svn_boolean_t use_git_diff_format,
2625 const char *header_encoding,
2626 svn_stream_t *outstream,
2627 svn_stream_t *errstream,
2628 const apr_array_header_t *changelists,
2629 svn_client_ctx_t *ctx,
2632 diff_writer_info_t dwi = { 0 };
2633 const svn_diff_tree_processor_t *diff_processor;
2634 svn_diff_tree_processor_t *processor;
2636 if (ignore_properties && properties_only)
2637 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2638 _("Cannot ignore properties and show only "
2639 "properties at the same time"));
2641 /* setup callback and baton */
2642 dwi.ddi.orig_path_1 = path_or_url;
2643 dwi.ddi.orig_path_2 = path_or_url;
2645 SVN_ERR(create_diff_writer_info(&dwi, options,
2646 ctx->config, pool));
2648 dwi.outstream = outstream;
2649 dwi.errstream = errstream;
2650 dwi.header_encoding = header_encoding;
2652 dwi.force_binary = ignore_content_type;
2653 dwi.ignore_properties = ignore_properties;
2654 dwi.properties_only = properties_only;
2655 dwi.relative_to_dir = relative_to_dir;
2656 dwi.use_git_diff_format = use_git_diff_format;
2657 dwi.no_diff_added = no_diff_added;
2658 dwi.no_diff_deleted = no_diff_deleted;
2659 dwi.show_copies_as_adds = show_copies_as_adds;
2661 dwi.cancel_func = ctx->cancel_func;
2662 dwi.cancel_baton = ctx->cancel_baton;
2664 dwi.wc_ctx = ctx->wc_ctx;
2665 dwi.ddi.session_relpath = NULL;
2666 dwi.ddi.anchor = NULL;
2668 processor = svn_diff__tree_processor_create(&dwi, pool);
2670 processor->dir_added = diff_dir_added;
2671 processor->dir_changed = diff_dir_changed;
2672 processor->dir_deleted = diff_dir_deleted;
2674 processor->file_added = diff_file_added;
2675 processor->file_changed = diff_file_changed;
2676 processor->file_deleted = diff_file_deleted;
2678 diff_processor = processor;
2680 /* --show-copies-as-adds and --git imply --notice-ancestry */
2681 if (show_copies_as_adds || use_git_diff_format)
2682 ignore_ancestry = FALSE;
2684 return svn_error_trace(do_diff(NULL, NULL, &dwi.ddi,
2685 path_or_url, path_or_url,
2686 start_revision, end_revision,
2687 peg_revision, FALSE /* no_peg_revision */,
2688 depth, ignore_ancestry, changelists,
2689 TRUE /* text_deltas */,
2690 diff_processor, ctx, pool, pool));
2694 svn_client_diff_summarize2(const char *path_or_url1,
2695 const svn_opt_revision_t *revision1,
2696 const char *path_or_url2,
2697 const svn_opt_revision_t *revision2,
2699 svn_boolean_t ignore_ancestry,
2700 const apr_array_header_t *changelists,
2701 svn_client_diff_summarize_func_t summarize_func,
2702 void *summarize_baton,
2703 svn_client_ctx_t *ctx,
2706 const svn_diff_tree_processor_t *diff_processor;
2707 svn_opt_revision_t peg_revision;
2708 const char **p_root_relpath;
2710 /* We will never do a pegged diff from here. */
2711 peg_revision.kind = svn_opt_revision_unspecified;
2713 SVN_ERR(svn_client__get_diff_summarize_callbacks(
2714 &diff_processor, &p_root_relpath,
2715 summarize_func, summarize_baton,
2716 path_or_url1, pool, pool));
2718 return svn_error_trace(do_diff(p_root_relpath, NULL, NULL,
2719 path_or_url1, path_or_url2,
2720 revision1, revision2,
2721 &peg_revision, TRUE /* no_peg_revision */,
2722 depth, ignore_ancestry, changelists,
2723 FALSE /* text_deltas */,
2724 diff_processor, ctx, pool, pool));
2728 svn_client_diff_summarize_peg2(const char *path_or_url,
2729 const svn_opt_revision_t *peg_revision,
2730 const svn_opt_revision_t *start_revision,
2731 const svn_opt_revision_t *end_revision,
2733 svn_boolean_t ignore_ancestry,
2734 const apr_array_header_t *changelists,
2735 svn_client_diff_summarize_func_t summarize_func,
2736 void *summarize_baton,
2737 svn_client_ctx_t *ctx,
2740 const svn_diff_tree_processor_t *diff_processor;
2741 const char **p_root_relpath;
2743 SVN_ERR(svn_client__get_diff_summarize_callbacks(
2744 &diff_processor, &p_root_relpath,
2745 summarize_func, summarize_baton,
2746 path_or_url, pool, pool));
2748 return svn_error_trace(do_diff(p_root_relpath, NULL, NULL,
2749 path_or_url, path_or_url,
2750 start_revision, end_revision,
2751 peg_revision, FALSE /* no_peg_revision */,
2752 depth, ignore_ancestry, changelists,
2753 FALSE /* text_deltas */,
2754 diff_processor, ctx, pool, pool));