1/* 2 * diff.c: comparing 3 * 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 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 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 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include <apr_strings.h> 31#include <apr_pools.h> 32#include <apr_hash.h> 33#include "svn_types.h" 34#include "svn_hash.h" 35#include "svn_wc.h" 36#include "svn_diff.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" 42#include "svn_path.h" 43#include "svn_io.h" 44#include "svn_utf.h" 45#include "svn_pools.h" 46#include "svn_config.h" 47#include "svn_props.h" 48#include "svn_subst.h" 49#include "client.h" 50 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" 56 57#include "svn_private_config.h" 58 59 60/* Utilities */ 61 62#define DIFF_REVNUM_NONEXISTENT ((svn_revnum_t) -100) 63 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) 68 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. */ 75static svn_error_t * 76make_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, 81 const char *anchor, 82 apr_pool_t *result_pool, 83 apr_pool_t *scratch_pool) 84{ 85 const char *local_abspath; 86 87 if (! session_relpath 88 || (anchor && !svn_path_is_url(orig_target))) 89 { 90 svn_error_t *err; 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, 95 scratch_pool), 96 scratch_pool)); 97 98 err = svn_wc__node_get_repos_info(NULL, repos_relpath, NULL, NULL, 99 wc_ctx, local_abspath, 100 result_pool, scratch_pool); 101 102 if (!session_relpath 103 || ! err 104 || (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)) 105 { 106 return svn_error_trace(err); 107 } 108 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 */ 112 113 /* ### Maybe we should use the nearest existing ancestor instead? */ 114 svn_error_clear(err); 115 } 116 117 *repos_relpath = svn_relpath_join(session_relpath, diff_relpath, 118 result_pool); 119 120 return SVN_NO_ERROR; 121} 122 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. */ 130static svn_error_t * 131adjust_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, 135 const char *anchor, 136 apr_pool_t *result_pool, 137 apr_pool_t *scratch_pool) 138{ 139 const char *new_path = *index_path; 140 const char *new_path1 = *orig_path_1; 141 const char *new_path2 = *orig_path_2; 142 143 if (anchor) 144 new_path = svn_dirent_join(anchor, new_path, result_pool); 145 146 if (relative_to_dir) 147 { 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, 150 result_pool); 151 152 if (child_path) 153 new_path = child_path; 154 else if (! strcmp(relative_to_dir, new_path)) 155 new_path = "."; 156 else 157 return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir); 158 } 159 160 { 161 apr_size_t len; 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. 169 What a nightmare. 170 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. */ 176 177 /* ### BH: We can now just construct the repos_relpath, etc. as the 178 anchor is available. See also make_repos_relpath() */ 179 180 is_url1 = svn_path_is_url(new_path1); 181 is_url2 = svn_path_is_url(new_path2); 182 183 if (is_url1 && is_url2) 184 len = strlen(svn_uri_get_longest_ancestor(new_path1, new_path2, 185 scratch_pool)); 186 else if (!is_url1 && !is_url2) 187 len = strlen(svn_dirent_get_longest_ancestor(new_path1, new_path2, 188 scratch_pool)); 189 else 190 len = 0; /* Path and URL */ 191 192 new_path1 += len; 193 new_path2 += len; 194 } 195 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 199 paths below.*/ 200 201 if (new_path[0] == '\0') 202 new_path = "."; 203 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); 210 else 211 new_path1 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path1); 212 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); 219 else 220 new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2); 221 222 *index_path = new_path; 223 *orig_path_1 = new_path1; 224 *orig_path_2 = new_path2; 225 226 return SVN_NO_ERROR; 227} 228 229 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. */ 234static const char * 235diff_label(const char *path, 236 svn_revnum_t revnum, 237 apr_pool_t *pool) 238{ 239 const char *label; 240 if (revnum >= 0) 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); 246 247 return label; 248} 249 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. */ 253static svn_error_t * 254print_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) 257{ 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)); 263 return SVN_NO_ERROR; 264} 265 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. */ 269static svn_error_t * 270print_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) 273{ 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" 279 APR_EOL_STR)); 280 return SVN_NO_ERROR; 281} 282 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. */ 285static svn_error_t * 286print_git_diff_header_copied(svn_stream_t *os, const char *header_encoding, 287 const char *copyfrom_path, 288 svn_revnum_t copyfrom_rev, 289 const char *path, 290 apr_pool_t *result_pool) 291{ 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)); 299 else 300 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool, 301 "copy from %s%s", copyfrom_path, 302 APR_EOL_STR)); 303 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool, 304 "copy to %s%s", path, APR_EOL_STR)); 305 return SVN_NO_ERROR; 306} 307 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. */ 310static svn_error_t * 311print_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) 314{ 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, 320 APR_EOL_STR)); 321 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, result_pool, 322 "rename to %s%s", path, APR_EOL_STR)); 323 return SVN_NO_ERROR; 324} 325 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. */ 329static svn_error_t * 330print_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) 333{ 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)); 337 return SVN_NO_ERROR; 338} 339 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. */ 347static svn_error_t * 348print_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, 353 svn_revnum_t rev1, 354 svn_revnum_t rev2, 355 const char *copyfrom_path, 356 svn_revnum_t copyfrom_rev, 357 const char *header_encoding, 358 apr_pool_t *scratch_pool) 359{ 360 if (operation == svn_diff_op_deleted) 361 { 362 SVN_ERR(print_git_diff_header_deleted(os, header_encoding, 363 repos_relpath1, repos_relpath2, 364 scratch_pool)); 365 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1), 366 rev1, scratch_pool); 367 *label2 = diff_label("/dev/null", rev2, scratch_pool); 368 369 } 370 else if (operation == svn_diff_op_copied) 371 { 372 SVN_ERR(print_git_diff_header_copied(os, header_encoding, 373 copyfrom_path, copyfrom_rev, 374 repos_relpath2, 375 scratch_pool)); 376 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path), 377 rev1, scratch_pool); 378 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), 379 rev2, scratch_pool); 380 } 381 else if (operation == svn_diff_op_added) 382 { 383 SVN_ERR(print_git_diff_header_added(os, header_encoding, 384 repos_relpath1, repos_relpath2, 385 scratch_pool)); 386 *label1 = diff_label("/dev/null", rev1, scratch_pool); 387 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), 388 rev2, scratch_pool); 389 } 390 else if (operation == svn_diff_op_modified) 391 { 392 SVN_ERR(print_git_diff_header_modified(os, header_encoding, 393 repos_relpath1, repos_relpath2, 394 scratch_pool)); 395 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1), 396 rev1, scratch_pool); 397 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), 398 rev2, scratch_pool); 399 } 400 else if (operation == svn_diff_op_moved) 401 { 402 SVN_ERR(print_git_diff_header_renamed(os, header_encoding, 403 copyfrom_path, repos_relpath2, 404 scratch_pool)); 405 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path), 406 rev1, scratch_pool); 407 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), 408 rev2, scratch_pool); 409 } 410 411 return SVN_NO_ERROR; 412} 413 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. 417 418 ### FIXME needs proper docstring 419 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. 424 425 ANCHOR is the local path where the diff editor is anchored. */ 426static svn_error_t * 427display_prop_diffs(const apr_array_header_t *propchanges, 428 apr_hash_t *original_props, 429 const char *diff_relpath, 430 const char *anchor, 431 const char *orig_path1, 432 const char *orig_path2, 433 svn_revnum_t rev1, 434 svn_revnum_t rev2, 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, 442 void *cancel_baton, 443 svn_wc_context_t *wc_ctx, 444 apr_pool_t *scratch_pool) 445{ 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; 451 452 if (use_git_diff_format) 453 { 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)); 460 } 461 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, 464 &adjusted_path2, 465 relative_to_dir, anchor, 466 scratch_pool, scratch_pool)); 467 468 if (show_diff_header) 469 { 470 const char *label1; 471 const char *label2; 472 473 label1 = diff_label(adjusted_path1, rev1, scratch_pool); 474 label2 = diff_label(adjusted_path2, rev2, scratch_pool); 475 476 /* ### Should we show the paths in platform specific format, 477 * ### diff_content_changed() does not! */ 478 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, 482 index_path)); 483 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, 488 rev1, rev2, NULL, 489 SVN_INVALID_REVNUM, 490 encoding, scratch_pool)); 491 492 /* --- label1 493 * +++ label2 */ 494 SVN_ERR(svn_diff__unidiff_write_header( 495 outstream, encoding, label1, label2, scratch_pool)); 496 } 497 498 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool, 499 _("%sProperty changes on: %s%s"), 500 APR_EOL_STR, 501 use_git_diff_format 502 ? repos_relpath1 503 : index_path, 504 APR_EOL_STR)); 505 506 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool, 507 SVN_DIFF__UNDER_STRING APR_EOL_STR)); 508 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)); 514 515 return SVN_NO_ERROR; 516} 517 518/*-----------------------------------------------------------------*/ 519 520/*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/ 521 522/* State provided by the diff drivers; used by the diff writer */ 523typedef struct diff_driver_info_t 524{ 525 /* The anchor to prefix before wc paths */ 526 const char *anchor; 527 528 /* Relative path of ra session from repos_root_url */ 529 const char *session_relpath; 530 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; 538 539 540/* Diff writer state */ 541typedef struct diff_writer_info_t 542{ 543 /* If non-null, the external diff command to invoke. */ 544 const char *diff_cmd; 545 546 /* This is allocated in this struct's pool or a higher-up pool. */ 547 union { 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... */ 552 struct { 553 /* ...this is an argument array for the external command, and */ 554 const char **argv; 555 /* ...this is the length of argv. */ 556 int argc; 557 } for_external; 558 } options; 559 560 apr_pool_t *pool; 561 svn_stream_t *outstream; 562 svn_stream_t *errstream; 563 564 const char *header_encoding; 565 566 /* Set this if you want diff output even for binary files. */ 567 svn_boolean_t force_binary; 568 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; 572 573 /* Whether property differences are ignored. */ 574 svn_boolean_t ignore_properties; 575 576 /* Whether to show only property changes. */ 577 svn_boolean_t properties_only; 578 579 /* Whether we're producing a git-style diff. */ 580 svn_boolean_t use_git_diff_format; 581 582 /* Whether addition of a file is summarized versus showing a full diff. */ 583 svn_boolean_t no_diff_added; 584 585 /* Whether deletion of a file is summarized versus showing a full diff. */ 586 svn_boolean_t no_diff_deleted; 587 588 /* Whether to ignore copyfrom information when showing adds */ 589 svn_boolean_t show_copies_as_adds; 590 591 /* Empty files for creating diffs or NULL if not used yet */ 592 const char *empty_file; 593 594 svn_wc_context_t *wc_ctx; 595 596 svn_cancel_func_t cancel_func; 597 void *cancel_baton; 598 599 struct diff_driver_info_t ddi; 600} diff_writer_info_t; 601 602/* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added 603 */ 604static svn_error_t * 605diff_props_changed(const char *diff_relpath, 606 svn_revnum_t rev1, 607 svn_revnum_t rev2, 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) 613{ 614 apr_array_header_t *props; 615 616 /* If property differences are ignored, there's nothing to do. */ 617 if (dwi->ignore_properties) 618 return SVN_NO_ERROR; 619 620 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, 621 scratch_pool)); 622 623 if (props->nelts > 0) 624 { 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, 629 diff_relpath, 630 dwi->ddi.anchor, 631 dwi->ddi.orig_path_1, 632 dwi->ddi.orig_path_2, 633 rev1, 634 rev2, 635 dwi->header_encoding, 636 dwi->outstream, 637 dwi->relative_to_dir, 638 show_diff_header, 639 dwi->use_git_diff_format, 640 dwi->ddi.session_relpath, 641 dwi->cancel_func, 642 dwi->cancel_baton, 643 dwi->wc_ctx, 644 scratch_pool)); 645 } 646 647 return SVN_NO_ERROR; 648} 649 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. 654 655 If FORCE_DIFF is TRUE, always write a diff, even for empty diffs. 656 657 Set *WROTE_HEADER to TRUE if a diff header was written */ 658static svn_error_t * 659diff_content_changed(svn_boolean_t *wrote_header, 660 const char *diff_relpath, 661 const char *tmpfile1, 662 const char *tmpfile2, 663 svn_revnum_t rev1, 664 svn_revnum_t rev2, 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) 673{ 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; 681 682 /* If only property differences are shown, there's nothing to do. */ 683 if (dwi->properties_only) 684 return SVN_NO_ERROR; 685 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)); 690 691 label1 = diff_label(path1, rev1, scratch_pool); 692 label2 = diff_label(path2, rev2, scratch_pool); 693 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. */ 697 if (mimetype1) 698 mt1_binary = svn_mime_type_is_binary(mimetype1); 699 if (mimetype2) 700 mt2_binary = svn_mime_type_is_binary(mimetype2); 701 702 if (! dwi->force_binary && (mt1_binary || mt2_binary)) 703 { 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, 709 index_path)); 710 711 /* ### Print git diff headers. */ 712 713 if (dwi->use_git_diff_format) 714 { 715 svn_stream_t *left_stream; 716 svn_stream_t *right_stream; 717 const char *repos_relpath1; 718 const char *repos_relpath2; 719 720 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, 721 dwi->ddi.orig_path_1, 722 dwi->ddi.session_relpath, 723 dwi->wc_ctx, 724 dwi->ddi.anchor, 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, 729 dwi->wc_ctx, 730 dwi->ddi.anchor, 731 scratch_pool, scratch_pool)); 732 SVN_ERR(print_git_diff_header(outstream, &label1, &label2, 733 operation, 734 repos_relpath1, repos_relpath2, 735 rev1, rev2, 736 copyfrom_path, 737 copyfrom_rev, 738 dwi->header_encoding, 739 scratch_pool)); 740 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, 748 scratch_pool)); 749 } 750 else 751 { 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"), 755 APR_EOL_STR)); 756 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) 766 { 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, 771 mimetype1)); 772 else 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)); 777 } 778 } 779 780 /* Exit early. */ 781 return SVN_NO_ERROR; 782 } 783 784 785 if (dwi->diff_cmd) 786 { 787 svn_stream_t *errstream = dwi->errstream; 788 apr_file_t *outfile; 789 apr_file_t *errfile; 790 const char *outfilename; 791 const char *errfilename; 792 svn_stream_t *stream; 793 int exitcode; 794 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, 800 index_path)); 801 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.*/ 806 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 809 to our stream. */ 810 outfile = svn_stream__aprfile(outstream); 811 if (outfile) 812 outfilename = NULL; 813 else 814 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL, 815 svn_io_file_del_on_pool_cleanup, 816 scratch_pool, scratch_pool)); 817 818 errfile = svn_stream__aprfile(errstream); 819 if (errfile) 820 errfilename = NULL; 821 else 822 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL, 823 svn_io_file_del_on_pool_cleanup, 824 scratch_pool, scratch_pool)); 825 826 SVN_ERR(svn_io_run_diff2(".", 827 dwi->options.for_external.argv, 828 dwi->options.for_external.argc, 829 label1, label2, 830 tmpfile1, tmpfile2, 831 &exitcode, outfile, errfile, 832 dwi->diff_cmd, scratch_pool)); 833 834 /* Now, open and copy our files to our output streams. */ 835 if (outfilename) 836 { 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, 841 scratch_pool), 842 NULL, NULL, scratch_pool)); 843 } 844 if (errfilename) 845 { 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, 850 scratch_pool), 851 NULL, NULL, scratch_pool)); 852 } 853 854 /* If we have printed a diff for this path, mark it as visited. */ 855 if (exitcode == 1) 856 *wrote_header = TRUE; 857 } 858 else /* use libsvn_diff to generate the diff */ 859 { 860 svn_diff_t *diff; 861 862 SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2, 863 dwi->options.for_internal, 864 scratch_pool)); 865 866 if (force_diff 867 || dwi->use_git_diff_format 868 || svn_diff_contains_diffs(diff)) 869 { 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, 875 index_path)); 876 877 if (dwi->use_git_diff_format) 878 { 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, 884 dwi->wc_ctx, 885 dwi->ddi.anchor, 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, 890 dwi->wc_ctx, 891 dwi->ddi.anchor, 892 scratch_pool, scratch_pool)); 893 SVN_ERR(print_git_diff_header(outstream, &label1, &label2, 894 operation, 895 repos_relpath1, repos_relpath2, 896 rev1, rev2, 897 copyfrom_path, 898 copyfrom_rev, 899 dwi->header_encoding, 900 scratch_pool)); 901 } 902 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, 911 scratch_pool)); 912 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; 916 } 917 } 918 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. */ 922 923 return SVN_NO_ERROR; 924} 925 926/* An svn_diff_tree_processor_t callback. */ 927static svn_error_t * 928diff_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, 937 void *file_baton, 938 const struct svn_diff_tree_processor_t *processor, 939 apr_pool_t *scratch_pool) 940{ 941 diff_writer_info_t *dwi = processor->baton; 942 svn_boolean_t wrote_header = FALSE; 943 944 if (file_modified) 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, 950 SVN_PROP_MIME_TYPE), 951 svn_prop_get_value(right_props, 952 SVN_PROP_MIME_TYPE), 953 svn_diff_op_modified, FALSE, 954 NULL, 955 SVN_INVALID_REVNUM, dwi, 956 scratch_pool)); 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, 962 dwi, scratch_pool)); 963 return SVN_NO_ERROR; 964} 965 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. */ 969 970/* An svn_diff_tree_processor_t callback. */ 971static svn_error_t * 972diff_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, 979 void *file_baton, 980 const struct svn_diff_tree_processor_t *processor, 981 apr_pool_t *scratch_pool) 982{ 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; 988 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) 992 { 993 left_file = copyfrom_file; 994 left_props = copyfrom_props ? copyfrom_props : apr_hash_make(scratch_pool); 995 } 996 else 997 { 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)); 1002 1003 left_file = dwi->empty_file; 1004 left_props = apr_hash_make(scratch_pool); 1005 1006 copyfrom_source = NULL; 1007 copyfrom_file = NULL; 1008 } 1009 1010 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool)); 1011 1012 if (dwi->no_diff_added) 1013 { 1014 const char *index_path = relpath; 1015 1016 if (dwi->ddi.anchor) 1017 index_path = svn_dirent_join(dwi->ddi.anchor, relpath, 1018 scratch_pool); 1019 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, 1024 index_path)); 1025 wrote_header = TRUE; 1026 } 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), 1036 svn_diff_op_copied, 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), 1050 svn_diff_op_added, 1051 TRUE /* force diff output */, 1052 NULL, SVN_INVALID_REVNUM, 1053 dwi, scratch_pool)); 1054 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, 1060 prop_changes, 1061 left_props, ! wrote_header, 1062 dwi, scratch_pool)); 1063 1064 return SVN_NO_ERROR; 1065} 1066 1067/* An svn_diff_tree_processor_t callback. */ 1068static svn_error_t * 1069diff_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, 1073 void *file_baton, 1074 const struct svn_diff_tree_processor_t *processor, 1075 apr_pool_t *scratch_pool) 1076{ 1077 diff_writer_info_t *dwi = processor->baton; 1078 1079 if (dwi->no_diff_deleted) 1080 { 1081 const char *index_path = relpath; 1082 1083 if (dwi->ddi.anchor) 1084 index_path = svn_dirent_join(dwi->ddi.anchor, relpath, 1085 scratch_pool); 1086 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, 1091 index_path)); 1092 } 1093 else 1094 { 1095 svn_boolean_t wrote_header = FALSE; 1096 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)); 1101 1102 if (left_file) 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), 1109 NULL, 1110 svn_diff_op_deleted, FALSE, 1111 NULL, SVN_INVALID_REVNUM, 1112 dwi, 1113 scratch_pool)); 1114 1115 if (left_props && apr_hash_count(left_props)) 1116 { 1117 apr_array_header_t *prop_changes; 1118 1119 SVN_ERR(svn_prop_diffs(&prop_changes, apr_hash_make(scratch_pool), 1120 left_props, scratch_pool)); 1121 1122 SVN_ERR(diff_props_changed(relpath, 1123 left_source->revision, 1124 DIFF_REVNUM_NONEXISTENT, 1125 prop_changes, 1126 left_props, ! wrote_header, 1127 dwi, scratch_pool)); 1128 } 1129 } 1130 1131 return SVN_NO_ERROR; 1132} 1133 1134/* An svn_wc_diff_callbacks4_t function. */ 1135static svn_error_t * 1136diff_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, 1142 void *dir_baton, 1143 const struct svn_diff_tree_processor_t *processor, 1144 apr_pool_t *scratch_pool) 1145{ 1146 diff_writer_info_t *dwi = processor->baton; 1147 1148 SVN_ERR(diff_props_changed(relpath, 1149 left_source->revision, 1150 right_source->revision, 1151 prop_changes, 1152 left_props, 1153 TRUE /* show_diff_header */, 1154 dwi, 1155 scratch_pool)); 1156 1157 return SVN_NO_ERROR; 1158} 1159 1160/* An svn_diff_tree_processor_t callback. */ 1161static svn_error_t * 1162diff_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, 1167 void *dir_baton, 1168 const struct svn_diff_tree_processor_t *processor, 1169 apr_pool_t *scratch_pool) 1170{ 1171 diff_writer_info_t *dwi = processor->baton; 1172 apr_hash_t *left_props; 1173 apr_array_header_t *prop_changes; 1174 1175 if (dwi->no_diff_added) 1176 return SVN_NO_ERROR; 1177 1178 if (copyfrom_source && !dwi->show_copies_as_adds) 1179 { 1180 left_props = copyfrom_props ? copyfrom_props 1181 : apr_hash_make(scratch_pool); 1182 } 1183 else 1184 { 1185 left_props = apr_hash_make(scratch_pool); 1186 copyfrom_source = NULL; 1187 } 1188 1189 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, 1190 scratch_pool)); 1191 1192 return svn_error_trace(diff_props_changed(relpath, 1193 copyfrom_source ? copyfrom_source->revision 1194 : DIFF_REVNUM_NONEXISTENT, 1195 right_source->revision, 1196 prop_changes, 1197 left_props, 1198 TRUE /* show_diff_header */, 1199 dwi, 1200 scratch_pool)); 1201} 1202 1203/* An svn_diff_tree_processor_t callback. */ 1204static svn_error_t * 1205diff_dir_deleted(const char *relpath, 1206 const svn_diff_source_t *left_source, 1207 /*const*/ apr_hash_t *left_props, 1208 void *dir_baton, 1209 const struct svn_diff_tree_processor_t *processor, 1210 apr_pool_t *scratch_pool) 1211{ 1212 diff_writer_info_t *dwi = processor->baton; 1213 apr_array_header_t *prop_changes; 1214 1215 if (dwi->no_diff_deleted) 1216 return SVN_NO_ERROR; 1217 1218 1219 SVN_ERR(svn_prop_diffs(&prop_changes, apr_hash_make(scratch_pool), 1220 left_props, scratch_pool)); 1221 1222 SVN_ERR(diff_props_changed(relpath, 1223 left_source->revision, 1224 DIFF_REVNUM_NONEXISTENT, 1225 prop_changes, 1226 left_props, 1227 TRUE /* show_diff_header */, 1228 dwi, 1229 scratch_pool)); 1230 1231 return SVN_NO_ERROR; 1232} 1233 1234/*-----------------------------------------------------------------*/ 1235 1236/** The logic behind 'svn diff' and 'svn merge'. */ 1237 1238 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. 1242 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 1249 1250 With only one distinct revision the working copy provides the 1251 other. When path is a URL there is no working copy. Thus 1252 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 1256 4: nothing to do. 1257 5: compare working copy against text-base 1258 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. */ 1261 1262 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 1266 * BASE or WORKING. 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. */ 1270static svn_error_t * 1271check_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) 1278{ 1279 svn_boolean_t is_local_rev1, is_local_rev2; 1280 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")); 1286 1287 /* Revisions can be said to be local or remote. 1288 * BASE and WORKING are local revisions. */ 1289 is_local_rev1 = 1290 ((revision1->kind == svn_opt_revision_base) 1291 || (revision1->kind == svn_opt_revision_working)); 1292 is_local_rev2 = 1293 ((revision2->kind == svn_opt_revision_base) 1294 || (revision2->kind == svn_opt_revision_working)); 1295 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")); 1301 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); 1308 1309 return SVN_NO_ERROR; 1310} 1311 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. */ 1316static svn_error_t * 1317check_diff_target_exists(const char *url, 1318 svn_revnum_t revision, 1319 svn_revnum_t other_revision, 1320 svn_ra_session_t *ra_session, 1321 apr_pool_t *pool) 1322{ 1323 svn_node_kind_t kind; 1324 const char *session_url; 1325 1326 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool)); 1327 1328 if (strcmp(url, session_url) != 0) 1329 SVN_ERR(svn_ra_reparent(ra_session, url, pool)); 1330 1331 SVN_ERR(svn_ra_check_path(ra_session, "", revision, &kind, pool)); 1332 if (kind == svn_node_none) 1333 { 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'"), 1338 url, revision); 1339 else 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); 1344 } 1345 1346 if (strcmp(url, session_url) != 0) 1347 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool)); 1348 1349 return SVN_NO_ERROR; 1350} 1351 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. */ 1360static svn_error_t * 1361diff_prepare_repos_repos(const char **url1, 1362 const char **url2, 1363 svn_revnum_t *rev1, 1364 svn_revnum_t *rev2, 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, 1378 apr_pool_t *pool) 1379{ 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; 1387 1388 if (!svn_path_is_url(path_or_url2)) 1389 { 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, 1392 pool, pool)); 1393 wri_abspath = local_abspath2; 1394 } 1395 else 1396 *url2 = apr_pstrdup(pool, path_or_url2); 1397 1398 if (!svn_path_is_url(path_or_url1)) 1399 { 1400 SVN_ERR(svn_dirent_get_absolute(&local_abspath1, path_or_url1, pool)); 1401 wri_abspath = local_abspath1; 1402 } 1403 1404 SVN_ERR(svn_client_open_ra_session2(ra_session, *url2, wri_abspath, 1405 ctx, pool, pool)); 1406 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 1411 || local_abspath2) 1412 { 1413 svn_error_t *err; 1414 1415 err = svn_client__resolve_rev_and_url(&resolved2, 1416 *ra_session, path_or_url2, 1417 peg_revision, revision2, 1418 ctx, pool); 1419 if (err) 1420 { 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); 1424 1425 svn_error_clear(err); 1426 resolved2 = NULL; 1427 } 1428 } 1429 else 1430 resolved2 = NULL; 1431 1432 if (peg_kind != svn_opt_revision_unspecified 1433 || path_or_url1 == path_or_url2 1434 || local_abspath1) 1435 { 1436 svn_error_t *err; 1437 1438 err = svn_client__resolve_rev_and_url(&resolved1, 1439 *ra_session, path_or_url1, 1440 peg_revision, revision1, 1441 ctx, pool); 1442 if (err) 1443 { 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); 1447 1448 svn_error_clear(err); 1449 resolved1 = NULL; 1450 } 1451 } 1452 else 1453 resolved1 = NULL; 1454 1455 if (resolved1) 1456 { 1457 *url1 = resolved1->url; 1458 *rev1 = resolved1->rev; 1459 } 1460 else 1461 { 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) */ 1465 1466 if (resolved2 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; 1472 else 1473 SVN_ERR(svn_wc__node_get_url(url1, ctx->wc_ctx, local_abspath1, 1474 pool, pool)); 1475 1476 SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx, 1477 local_abspath1 /* may be NULL */, 1478 *ra_session, revision1, pool)); 1479 } 1480 1481 if (resolved2) 1482 { 1483 *url2 = resolved2->url; 1484 *rev2 = resolved2->rev; 1485 } 1486 else 1487 { 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) */ 1491 1492 if (resolved1 1493 && (peg_kind != svn_opt_revision_unspecified 1494 || path_or_url1 == path_or_url2)) 1495 *url2 = resolved1->url; 1496 /* else keep url2 */ 1497 1498 SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx, 1499 local_abspath2 /* may be NULL */, 1500 *ra_session, revision2, pool)); 1501 } 1502 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)); 1506 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)); 1510 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) 1514 { 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); 1520 else 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 " 1524 "'%ld'"), 1525 *url1, *url2, *rev1, *rev2); 1526 } 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)); 1531 1532 SVN_ERR(svn_ra_get_repos_root2(*ra_session, &repos_root_url, pool)); 1533 1534 /* Choose useful anchors and targets for our two URLs. */ 1535 *anchor1 = *url1; 1536 *anchor2 = *url2; 1537 *target1 = ""; 1538 *target2 = ""; 1539 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) 1544 { 1545 svn_node_kind_t ignored_kind; 1546 svn_error_t *err; 1547 1548 svn_uri_split(anchor1, target1, *url1, pool); 1549 svn_uri_split(anchor2, target2, *url2, pool); 1550 1551 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool)); 1552 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); 1556 1557 if (err && (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN 1558 || err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED)) 1559 { 1560 svn_error_clear(err); 1561 1562 /* Ok, lets undo the reparent... 1563 1564 We can't report replacements this way, but at least we can 1565 report changes on the descendants */ 1566 1567 *anchor1 = svn_path_url_add_component2(*anchor1, *target1, pool); 1568 *anchor2 = svn_path_url_add_component2(*anchor2, *target2, pool); 1569 *target1 = ""; 1570 *target2 = ""; 1571 1572 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool)); 1573 } 1574 else 1575 SVN_ERR(err); 1576 } 1577 1578 return SVN_NO_ERROR; 1579} 1580 1581/* A Theoretical Note From Ben, regarding do_diff(). 1582 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 1587 revisions. 1588 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: 1592 1593 - svn_wc_diff: assumes both paths are the same wcpath. 1594 compares wcpath@BASE vs. wcpath@WORKING 1595 1596 - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING 1597 1598 - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2 1599 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. 1603 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 1606 friendly apology. 1607 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. 1613 */ 1614 1615/* Return a "you can't do that" error, optionally wrapping another 1616 error CHILD_ERR. */ 1617static svn_error_t * 1618unsupported_diff_error(svn_error_t *child_err) 1619{ 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")); 1623} 1624 1625/* Perform a diff between two working-copy paths. 1626 1627 PATH1 and PATH2 are both working copy paths. REVISION1 and 1628 REVISION2 are their respective revisions. 1629 1630 All other options are the same as those passed to svn_client_diff6(). */ 1631static svn_error_t * 1632diff_wc_wc(const char **root_relpath, 1633 svn_boolean_t *root_is_dir, 1634 struct diff_driver_info_t *ddi, 1635 const char *path1, 1636 const svn_opt_revision_t *revision1, 1637 const char *path2, 1638 const svn_opt_revision_t *revision2, 1639 svn_depth_t depth, 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) 1646{ 1647 const char *abspath1; 1648 1649 SVN_ERR_ASSERT(! svn_path_is_url(path1)); 1650 SVN_ERR_ASSERT(! svn_path_is_url(path2)); 1651 1652 SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, scratch_pool)); 1653 1654 /* Currently we support only the case where path1 and path2 are the 1655 same path. */ 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" 1663 ))); 1664 1665 if (ddi) 1666 { 1667 svn_node_kind_t kind; 1668 1669 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath1, 1670 TRUE, FALSE, scratch_pool)); 1671 1672 if (kind != svn_node_dir) 1673 ddi->anchor = svn_dirent_dirname(path1, scratch_pool); 1674 else 1675 ddi->anchor = path1; 1676 } 1677 1678 SVN_ERR(svn_wc__diff7(root_relpath, root_is_dir, 1679 ctx->wc_ctx, abspath1, depth, 1680 ignore_ancestry, changelists, 1681 diff_processor, 1682 ctx->cancel_func, ctx->cancel_baton, 1683 result_pool, scratch_pool)); 1684 return SVN_NO_ERROR; 1685} 1686 1687/* Perform a diff between two repository paths. 1688 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. 1694 1695 All other options are the same as those passed to svn_client_diff6(). */ 1696static svn_error_t * 1697diff_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, 1705 svn_depth_t depth, 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) 1712{ 1713 svn_ra_session_t *extra_ra_session; 1714 1715 const svn_ra_reporter3_t *reporter; 1716 void *reporter_baton; 1717 1718 const svn_delta_editor_t *diff_editor; 1719 void *diff_edit_baton; 1720 1721 const char *url1; 1722 const char *url2; 1723 svn_revnum_t rev1; 1724 svn_revnum_t rev2; 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; 1732 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, 1739 scratch_pool)); 1740 1741 /* Set up the repos_diff editor on BASE_PATH, if available. 1742 Otherwise, we just use "". */ 1743 1744 if (ddi) 1745 { 1746 /* Get actual URLs. */ 1747 ddi->orig_path_1 = url1; 1748 ddi->orig_path_2 = url2; 1749 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 */ 1754 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; 1759 else 1760 ddi->anchor = NULL; 1761 1762 if (*target1 && ddi->anchor 1763 && (kind1 != svn_node_dir || kind2 != svn_node_dir)) 1764 ddi->anchor = svn_dirent_dirname(ddi->anchor, result_pool); 1765 } 1766 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) 1770 { 1771 const char *str_tmp; 1772 svn_revnum_t rev_tmp; 1773 1774 str_tmp = url2; 1775 url2 = url1; 1776 url1 = str_tmp; 1777 1778 rev_tmp = rev2; 1779 rev2 = rev1; 1780 rev1 = rev_tmp; 1781 1782 str_tmp = anchor2; 1783 anchor2 = anchor1; 1784 anchor1 = str_tmp; 1785 1786 str_tmp = target2; 1787 target2 = target1; 1788 target1 = str_tmp; 1789 1790 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, 1791 NULL, 1792 scratch_pool); 1793 } 1794 1795 /* Filter the first path component using a filter processor, until we fixed 1796 the diff processing to handle this directly */ 1797 if (root_relpath) 1798 *root_relpath = apr_pstrdup(result_pool, target1); 1799 else if ((kind1 != svn_node_file && kind2 != svn_node_file) 1800 && target1[0] != '\0') 1801 { 1802 diff_processor = svn_diff__tree_processor_filter_create( 1803 diff_processor, target1, scratch_pool); 1804 } 1805 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 1808 contents. */ 1809 SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor1, 1810 scratch_pool, scratch_pool)); 1811 1812 if (ddi) 1813 { 1814 const char *repos_root_url; 1815 const char *session_url; 1816 1817 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, 1818 scratch_pool)); 1819 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, 1820 scratch_pool)); 1821 1822 ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url, 1823 session_url, 1824 result_pool); 1825 } 1826 1827 SVN_ERR(svn_client__get_diff_editor2( 1828 &diff_editor, &diff_edit_baton, 1829 extra_ra_session, depth, 1830 rev1, 1831 text_deltas, 1832 diff_processor, 1833 ctx->cancel_func, ctx->cancel_baton, 1834 scratch_pool)); 1835 1836 /* We want to switch our txn into URL2 */ 1837 SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton, 1838 rev2, target1, 1839 depth, ignore_ancestry, text_deltas, 1840 url2, diff_editor, diff_edit_baton, scratch_pool)); 1841 1842 /* Drive the reporter; do the diff. */ 1843 SVN_ERR(reporter->set_path(reporter_baton, "", rev1, 1844 svn_depth_infinity, 1845 FALSE, NULL, 1846 scratch_pool)); 1847 1848 return svn_error_trace( 1849 reporter->finish_report(reporter_baton, scratch_pool)); 1850} 1851 1852/* Perform a diff between a repository path and a working-copy path. 1853 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. 1860 1861 All other options are the same as those passed to svn_client_diff6(). */ 1862static svn_error_t * 1863diff_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, 1869 const char *path2, 1870 const svn_opt_revision_t *revision2, 1871 svn_boolean_t reverse, 1872 svn_depth_t depth, 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) 1879{ 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; 1900 1901 SVN_ERR_ASSERT(! svn_path_is_url(path2)); 1902 1903 if (!svn_path_is_url(path_or_url1)) 1904 { 1905 SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1, 1906 scratch_pool)); 1907 } 1908 else 1909 { 1910 abspath_or_url1 = path_or_url1; 1911 } 1912 1913 SVN_ERR(svn_dirent_get_absolute(&abspath2, path2, scratch_pool)); 1914 1915 /* Check if our diff target is a copied node. */ 1916 SVN_ERR(svn_wc__node_get_origin(&is_copy, 1917 &cf_revision, 1918 &cf_repos_relpath, 1919 &cf_repos_root_url, 1920 NULL, &cf_depth, 1921 ©_root_abspath, 1922 ctx->wc_ctx, abspath2, 1923 FALSE, scratch_pool, scratch_pool)); 1924 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)); 1929 1930 if (revision2->kind == svn_opt_revision_base || !is_copy) 1931 { 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)); 1935 1936 /* Handle the ugly case where target is ".." */ 1937 if (*target && !svn_path_is_single_path_component(target)) 1938 { 1939 anchor = svn_dirent_join(anchor, target, scratch_pool); 1940 target = ""; 1941 } 1942 1943 if (root_relpath) 1944 *root_relpath = apr_pstrdup(result_pool, target); 1945 if (root_is_dir) 1946 *root_is_dir = (*target == '\0'); 1947 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); 1953 1954 target_url = NULL; 1955 } 1956 else /* is_copy && revision2->kind == svn_opt_revision_base */ 1957 { 1958#if 0 1959 svn_node_kind_t kind; 1960#endif 1961 /* ### Ugly hack ahead ### 1962 * 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). 1968 * 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... 1972 */ 1973 1974 target_url = svn_path_url_add_component2(cf_repos_root_url, 1975 cf_repos_relpath, 1976 scratch_pool); 1977 1978#if 0 1979 /*SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath2, FALSE, FALSE, 1980 scratch_pool)); 1981 1982 if (kind != svn_node_dir 1983 || strcmp(copy_root_abspath, abspath2) != 0) */ 1984#endif 1985 { 1986 /* We are looking at a subdirectory of the repository, 1987 We can describe the parent directory as the anchor.. 1988 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( 1994 cf_repos_relpath, 1995 scratch_pool), 1996 scratch_pool); 1997 target = svn_dirent_basename(abspath2, NULL); 1998 anchor = svn_dirent_dirname(path2, scratch_pool); 1999 } 2000#if 0 2001 else 2002 { 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... 2006 */ 2007 anchor_abspath = abspath2; 2008 anchor_url = svn_path_url_add_component2(cf_repos_root_url, 2009 cf_repos_relpath, 2010 scratch_pool); 2011 anchor = path2; 2012 target = ""; 2013 } 2014#endif 2015 } 2016 2017 SVN_ERR(svn_ra_reparent(ra_session, anchor_url, scratch_pool)); 2018 2019 if (ddi) 2020 { 2021 const char *repos_root_url; 2022 2023 ddi->anchor = anchor; 2024 2025 if (!reverse) 2026 { 2027 ddi->orig_path_1 = apr_pstrdup(result_pool, loc1->url); 2028 ddi->orig_path_2 = 2029 svn_path_url_add_component2(anchor_url, target, result_pool); 2030 } 2031 else 2032 { 2033 ddi->orig_path_1 = 2034 svn_path_url_add_component2(anchor_url, target, result_pool); 2035 ddi->orig_path_2 = apr_pstrdup(result_pool, loc1->url); 2036 } 2037 2038 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, 2039 scratch_pool)); 2040 2041 ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url, 2042 anchor_url, 2043 result_pool); 2044 } 2045 2046 if (reverse) 2047 diff_processor = svn_diff__tree_processor_reverse_create( 2048 diff_processor, NULL, scratch_pool); 2049 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, 2054 ctx->wc_ctx, 2055 anchor_abspath, 2056 target, 2057 depth, 2058 ignore_ancestry, 2059 rev2_is_base, 2060 reverse, 2061 server_supports_depth, 2062 changelists, 2063 diff_processor, 2064 ctx->cancel_func, ctx->cancel_baton, 2065 scratch_pool, scratch_pool)); 2066 2067 if (depth != svn_depth_infinity) 2068 diff_depth = depth; 2069 else 2070 diff_depth = svn_depth_unknown; 2071 2072 2073 2074 if (is_copy && revision2->kind != svn_opt_revision_base) 2075 { 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, 2079 loc1->rev, 2080 target, 2081 diff_depth, 2082 ignore_ancestry, 2083 TRUE, /* text_deltas */ 2084 loc1->url, 2085 diff_editor, diff_edit_baton, 2086 scratch_pool)); 2087 2088 /* Report the copy source. */ 2089 if (cf_depth == svn_depth_unknown) 2090 cf_depth = svn_depth_infinity; 2091 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 */ 2095 2096 SVN_ERR(reporter->set_path(reporter_baton, "", 2097 ignore_ancestry ? 0 : cf_revision, 2098 cf_depth, FALSE, NULL, scratch_pool)); 2099 2100 if (*target) 2101 SVN_ERR(reporter->link_path(reporter_baton, target, 2102 target_url, 2103 ignore_ancestry ? 0 : cf_revision, 2104 cf_depth, FALSE, NULL, scratch_pool)); 2105 2106 /* Finish the report to generate the diff. */ 2107 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool)); 2108 } 2109 else 2110 { 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, 2114 loc1->rev, 2115 target, 2116 diff_depth, 2117 ignore_ancestry, 2118 TRUE, /* text_deltas */ 2119 loc1->url, 2120 diff_editor, diff_edit_baton, 2121 scratch_pool)); 2122 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, 2127 FALSE, depth, TRUE, 2128 (! server_supports_depth), 2129 FALSE, 2130 ctx->cancel_func, ctx->cancel_baton, 2131 NULL, NULL, /* notification is N/A */ 2132 scratch_pool)); 2133 } 2134 2135 return SVN_NO_ERROR; 2136} 2137 2138 2139/* This is basically just the guts of svn_client_diff[_summarize][_peg]6(). */ 2140static svn_error_t * 2141do_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, 2149 svn_depth_t depth, 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) 2157{ 2158 svn_boolean_t is_repos1; 2159 svn_boolean_t is_repos2; 2160 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)); 2164 2165 if (is_repos1) 2166 { 2167 if (is_repos2) 2168 { 2169 /* ### Ignores 'show_copies_as_adds'. */ 2170 SVN_ERR(diff_repos_repos(root_relpath, root_is_dir, 2171 ddi, 2172 path_or_url1, path_or_url2, 2173 revision1, revision2, 2174 peg_revision, depth, ignore_ancestry, 2175 text_deltas, 2176 diff_processor, ctx, 2177 result_pool, scratch_pool)); 2178 } 2179 else /* path_or_url2 is a working copy path */ 2180 { 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)); 2187 } 2188 } 2189 else /* path_or_url1 is a working copy path */ 2190 { 2191 if (is_repos2) 2192 { 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)); 2199 } 2200 else /* path_or_url2 is a working copy path */ 2201 { 2202 if (revision1->kind == svn_opt_revision_working 2203 && revision2->kind == svn_opt_revision_working) 2204 { 2205 const char *abspath1; 2206 const char *abspath2; 2207 2208 SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, 2209 scratch_pool)); 2210 SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, 2211 scratch_pool)); 2212 2213 /* ### What about ddi? */ 2214 2215 SVN_ERR(svn_client__arbitrary_nodes_diff(root_relpath, root_is_dir, 2216 abspath1, abspath2, 2217 depth, 2218 diff_processor, 2219 ctx, 2220 result_pool, scratch_pool)); 2221 } 2222 else 2223 { 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)); 2230 } 2231 } 2232 } 2233 2234 return SVN_NO_ERROR; 2235} 2236 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. 2241 */ 2242static svn_error_t * 2243create_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) 2246{ 2247 const char *diff_cmd = NULL; 2248 2249 /* See if there is a diff command and/or diff arguments. */ 2250 if (config) 2251 { 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) 2256 { 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, 2262 result_pool); 2263 } 2264 } 2265 2266 if (options == NULL) 2267 options = apr_array_make(result_pool, 0, sizeof(const char *)); 2268 2269 if (diff_cmd) 2270 SVN_ERR(svn_path_cstring_to_utf8(&dwi->diff_cmd, diff_cmd, 2271 result_pool)); 2272 else 2273 dwi->diff_cmd = NULL; 2274 2275 /* If there was a command, arrange options to pass to it. */ 2276 if (dwi->diff_cmd) 2277 { 2278 const char **argv = NULL; 2279 int argc = options->nelts; 2280 if (argc) 2281 { 2282 int i; 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)); 2287 } 2288 dwi->options.for_external.argv = argv; 2289 dwi->options.for_external.argc = argc; 2290 } 2291 else /* No command, so arrange options for internal invocation instead. */ 2292 { 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)); 2296 } 2297 2298 return SVN_NO_ERROR; 2299} 2300 2301/*----------------------------------------------------------------------- */ 2302 2303/*** Public Interfaces. ***/ 2304 2305/* Display context diffs between two PATH/REVISION pairs. Each of 2306 these inputs will be one of the following: 2307 2308 - a repository URL at a given revision. 2309 - a working copy path, ignoring local mods. 2310 - a working copy path, including local mods. 2311 2312 We can establish a matrix that shows the nine possible types of 2313 diffs we expect to support. 2314 2315 2316 ` . DST || URL:rev | WC:base | WC:working | 2317 ` . || | | | 2318 SRC ` . || | | | 2319 ============++============+============+============+ 2320 URL:rev || (*) | (*) | (*) | 2321 || | | | 2322 || | | | 2323 || | | | 2324 ------------++------------+------------+------------+ 2325 WC:base || (*) | | 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. | 2333 || | | 2334 ------------++------------+------------+------------+ 2335 * These cases require server communication. 2336*/ 2337svn_error_t * 2338svn_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, 2344 svn_depth_t depth, 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, 2358 apr_pool_t *pool) 2359{ 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; 2364 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")); 2369 2370 /* We will never do a pegged diff from here. */ 2371 peg_revision.kind = svn_opt_revision_unspecified; 2372 2373 /* setup callback and baton */ 2374 dwi.ddi.orig_path_1 = path_or_url1; 2375 dwi.ddi.orig_path_2 = path_or_url2; 2376 2377 SVN_ERR(create_diff_writer_info(&dwi, options, 2378 ctx->config, pool)); 2379 dwi.pool = pool; 2380 dwi.outstream = outstream; 2381 dwi.errstream = errstream; 2382 dwi.header_encoding = header_encoding; 2383 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; 2392 2393 dwi.cancel_func = ctx->cancel_func; 2394 dwi.cancel_baton = ctx->cancel_baton; 2395 2396 dwi.wc_ctx = ctx->wc_ctx; 2397 dwi.ddi.session_relpath = NULL; 2398 dwi.ddi.anchor = NULL; 2399 2400 processor = svn_diff__tree_processor_create(&dwi, pool); 2401 2402 processor->dir_added = diff_dir_added; 2403 processor->dir_changed = diff_dir_changed; 2404 processor->dir_deleted = diff_dir_deleted; 2405 2406 processor->file_added = diff_file_added; 2407 processor->file_changed = diff_file_changed; 2408 processor->file_deleted = diff_file_deleted; 2409 2410 diff_processor = processor; 2411 2412 /* --show-copies-as-adds and --git imply --notice-ancestry */ 2413 if (show_copies_as_adds || use_git_diff_format) 2414 ignore_ancestry = FALSE; 2415 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)); 2422} 2423 2424svn_error_t * 2425svn_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, 2431 svn_depth_t depth, 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, 2445 apr_pool_t *pool) 2446{ 2447 diff_writer_info_t dwi = { 0 }; 2448 const svn_diff_tree_processor_t *diff_processor; 2449 svn_diff_tree_processor_t *processor; 2450 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")); 2455 2456 /* setup callback and baton */ 2457 dwi.ddi.orig_path_1 = path_or_url; 2458 dwi.ddi.orig_path_2 = path_or_url; 2459 2460 SVN_ERR(create_diff_writer_info(&dwi, options, 2461 ctx->config, pool)); 2462 dwi.pool = pool; 2463 dwi.outstream = outstream; 2464 dwi.errstream = errstream; 2465 dwi.header_encoding = header_encoding; 2466 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; 2475 2476 dwi.cancel_func = ctx->cancel_func; 2477 dwi.cancel_baton = ctx->cancel_baton; 2478 2479 dwi.wc_ctx = ctx->wc_ctx; 2480 dwi.ddi.session_relpath = NULL; 2481 dwi.ddi.anchor = NULL; 2482 2483 processor = svn_diff__tree_processor_create(&dwi, pool); 2484 2485 processor->dir_added = diff_dir_added; 2486 processor->dir_changed = diff_dir_changed; 2487 processor->dir_deleted = diff_dir_deleted; 2488 2489 processor->file_added = diff_file_added; 2490 processor->file_changed = diff_file_changed; 2491 processor->file_deleted = diff_file_deleted; 2492 2493 diff_processor = processor; 2494 2495 /* --show-copies-as-adds and --git imply --notice-ancestry */ 2496 if (show_copies_as_adds || use_git_diff_format) 2497 ignore_ancestry = FALSE; 2498 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)); 2505} 2506 2507svn_error_t * 2508svn_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, 2512 svn_depth_t depth, 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, 2518 apr_pool_t *pool) 2519{ 2520 const svn_diff_tree_processor_t *diff_processor; 2521 svn_opt_revision_t peg_revision; 2522 const char **p_root_relpath; 2523 2524 /* We will never do a pegged diff from here. */ 2525 peg_revision.kind = svn_opt_revision_unspecified; 2526 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)); 2531 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)); 2538} 2539 2540svn_error_t * 2541svn_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, 2545 svn_depth_t depth, 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, 2551 apr_pool_t *pool) 2552{ 2553 const svn_diff_tree_processor_t *diff_processor; 2554 const char **p_root_relpath; 2555 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)); 2560 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)); 2567} 2568 2569