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/* We define this here to remove any further warnings about the usage of 27 experimental functions in this file. */ 28#define SVN_EXPERIMENTAL 29 30 31/*** Includes. ***/ 32 33#include <apr_strings.h> 34#include <apr_pools.h> 35#include <apr_hash.h> 36#include "svn_types.h" 37#include "svn_hash.h" 38#include "svn_wc.h" 39#include "svn_diff.h" 40#include "svn_mergeinfo.h" 41#include "svn_client.h" 42#include "svn_string.h" 43#include "svn_error.h" 44#include "svn_dirent_uri.h" 45#include "svn_path.h" 46#include "svn_io.h" 47#include "svn_utf.h" 48#include "svn_pools.h" 49#include "svn_config.h" 50#include "svn_props.h" 51#include "svn_subst.h" 52#include "client.h" 53 54#include "private/svn_client_shelf.h" 55#include "private/svn_wc_private.h" 56#include "private/svn_diff_private.h" 57#include "private/svn_subr_private.h" 58#include "private/svn_io_private.h" 59#include "private/svn_ra_private.h" 60 61#include "svn_private_config.h" 62 63 64/* Utilities */ 65 66#define DIFF_REVNUM_NONEXISTENT ((svn_revnum_t) -100) 67 68#define MAKE_ERR_BAD_RELATIVE_PATH(path, relative_to_dir) \ 69 svn_error_createf(SVN_ERR_BAD_RELATIVE_PATH, NULL, \ 70 _("Path '%s' must be an immediate child of " \ 71 "the directory '%s'"), path, relative_to_dir) 72 73/* State provided by the diff drivers; used by the diff writer */ 74typedef struct diff_driver_info_t 75{ 76 /* The anchor to prefix before wc paths */ 77 const char *anchor; 78 79 /* Relative path of ra session from repos_root_url. 80 81 Used only in printing git diff headers. The repository-root-relative 82 path of ... ### what user-visible property of the diff? */ 83 const char *session_relpath; 84 85 /* Used only in printing git diff headers. Used to find the 86 repository-root-relative path of a WC path. */ 87 svn_wc_context_t *wc_ctx; 88 89 /* The original targets passed to the diff command. We may need 90 these to construct distinctive diff labels when comparing the 91 same relative path in the same revision, under different anchors 92 (for example, when comparing a trunk against a branch). */ 93 const char *orig_path_1; 94 const char *orig_path_2; 95} diff_driver_info_t; 96 97 98/* Calculate the repository relative path of DIFF_RELPATH, using 99 * SESSION_RELPATH and WC_CTX, and return the result in *REPOS_RELPATH. 100 * ORIG_TARGET is the related original target passed to the diff command, 101 * and may be used to derive leading path components missing from PATH. 102 * ANCHOR is the local path where the diff editor is anchored. 103 * Do all allocations in POOL. */ 104static svn_error_t * 105make_repos_relpath(const char **repos_relpath, 106 const char *diff_relpath, 107 const char *orig_target, 108 const char *session_relpath, 109 svn_wc_context_t *wc_ctx, 110 const char *anchor, 111 apr_pool_t *result_pool, 112 apr_pool_t *scratch_pool) 113{ 114 const char *local_abspath; 115 116 if (! session_relpath 117 || (anchor && !svn_path_is_url(orig_target))) 118 { 119 svn_error_t *err; 120 /* We're doing a WC-WC diff, so we can retrieve all information we 121 * need from the working copy. */ 122 SVN_ERR(svn_dirent_get_absolute(&local_abspath, 123 svn_dirent_join(anchor, diff_relpath, 124 scratch_pool), 125 scratch_pool)); 126 127 err = svn_wc__node_get_repos_info(NULL, repos_relpath, NULL, NULL, 128 wc_ctx, local_abspath, 129 result_pool, scratch_pool); 130 131 if (!session_relpath 132 || ! err 133 || (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)) 134 { 135 return svn_error_trace(err); 136 } 137 138 /* The path represents a local working copy path, but does not 139 exist. Fall through to calculate an in-repository location 140 based on the ra session */ 141 142 /* ### Maybe we should use the nearest existing ancestor instead? */ 143 svn_error_clear(err); 144 } 145 146 *repos_relpath = svn_relpath_join(session_relpath, diff_relpath, 147 result_pool); 148 149 return SVN_NO_ERROR; 150} 151 152/* Adjust paths to handle the case when we're dealing with different anchors. 153 * 154 * Set *INDEX_PATH to the new relative path. Set *LABEL_PATH1 and 155 * *LABEL_PATH2 to that path annotated with the unique parts of ORIG_PATH_1 156 * and ORIG_PATH_2 respectively, like this: 157 * 158 * INDEX_PATH: "path" 159 * LABEL_PATH1: "path\t(.../branches/branch1)" 160 * LABEL_PATH2: "path\t(.../trunk)" 161 * 162 * Make the output paths relative to RELATIVE_TO_DIR (if not null) by 163 * removing it from the beginning of (ANCHOR + RELPATH). 164 * 165 * ANCHOR (if not null) is the local path where the diff editor is anchored. 166 * RELPATH is the path to the changed node within the diff editor, so 167 * relative to ANCHOR. 168 * 169 * RELATIVE_TO_DIR and ANCHOR are of the same form -- either absolute local 170 * paths or relative paths relative to the same base. 171 * 172 * ORIG_PATH_1 and ORIG_PATH_2 represent the two original target paths or 173 * URLs passed to the diff command. 174 * 175 * Allocate results in RESULT_POOL (or as a pointer to RELPATH) and 176 * temporary data in SCRATCH_POOL. 177 */ 178static svn_error_t * 179adjust_paths_for_diff_labels(const char **index_path, 180 const char **label_path1, 181 const char **label_path2, 182 const char *relative_to_dir, 183 const char *anchor, 184 const char *relpath, 185 const char *orig_path_1, 186 const char *orig_path_2, 187 apr_pool_t *result_pool, 188 apr_pool_t *scratch_pool) 189{ 190 const char *new_path = relpath; 191 const char *new_path1 = orig_path_1; 192 const char *new_path2 = orig_path_2; 193 194 if (anchor) 195 new_path = svn_dirent_join(anchor, new_path, result_pool); 196 197 if (relative_to_dir) 198 { 199 /* Possibly adjust the paths shown in the output (see issue #2723). */ 200 const char *child_path = svn_dirent_is_child(relative_to_dir, new_path, 201 result_pool); 202 203 if (child_path) 204 new_path = child_path; 205 else if (! strcmp(relative_to_dir, new_path)) 206 new_path = "."; 207 else 208 return MAKE_ERR_BAD_RELATIVE_PATH(new_path, relative_to_dir); 209 } 210 211 { 212 apr_size_t len; 213 svn_boolean_t is_url1; 214 svn_boolean_t is_url2; 215 /* ### Holy cow. Due to anchor/target weirdness, we can't 216 simply join dwi->orig_path_1 with path, ditto for 217 orig_path_2. That will work when they're directory URLs, but 218 not for file URLs. Nor can we just use anchor1 and anchor2 219 from do_diff(), at least not without some more logic here. 220 What a nightmare. 221 222 For now, to distinguish the two paths, we'll just put the 223 unique portions of the original targets in parentheses after 224 the received path, with ellipses for handwaving. This makes 225 the labels a bit clumsy, but at least distinctive. Better 226 solutions are possible, they'll just take more thought. */ 227 228 /* ### BH: We can now just construct the repos_relpath, etc. as the 229 anchor is available. See also make_repos_relpath() */ 230 231 /* Remove the common prefix of NEW_PATH1 and NEW_PATH2. */ 232 is_url1 = svn_path_is_url(new_path1); 233 is_url2 = svn_path_is_url(new_path2); 234 235 if (is_url1 && is_url2) 236 len = strlen(svn_uri_get_longest_ancestor(new_path1, new_path2, 237 scratch_pool)); 238 else if (!is_url1 && !is_url2) 239 len = strlen(svn_dirent_get_longest_ancestor(new_path1, new_path2, 240 scratch_pool)); 241 else 242 len = 0; /* Path and URL */ 243 244 new_path1 += len; 245 new_path2 += len; 246 } 247 248 /* ### Should diff labels print paths in local style? Is there 249 already a standard for this? In any case, this code depends on 250 a particular style, so not calling svn_dirent_local_style() on the 251 paths below.*/ 252 253 if (new_path[0] == '\0') 254 new_path = "."; 255 256 if (new_path1[0] == '\0') 257 new_path1 = new_path; 258 else if (svn_path_is_url(new_path1)) 259 new_path1 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path1); 260 else if (new_path1[0] == '/') 261 new_path1 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path1); 262 else 263 new_path1 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path1); 264 265 if (new_path2[0] == '\0') 266 new_path2 = new_path; 267 else if (svn_path_is_url(new_path2)) 268 new_path2 = apr_psprintf(result_pool, "%s\t(%s)", new_path, new_path2); 269 else if (new_path2[0] == '/') 270 new_path2 = apr_psprintf(result_pool, "%s\t(...%s)", new_path, new_path2); 271 else 272 new_path2 = apr_psprintf(result_pool, "%s\t(.../%s)", new_path, new_path2); 273 274 *index_path = new_path; 275 *label_path1 = new_path1; 276 *label_path2 = new_path2; 277 278 return SVN_NO_ERROR; 279} 280 281 282/* Generate a label for the diff output for file PATH at revision REVNUM. 283 If REVNUM is invalid then it is assumed to be the current working 284 copy. Assumes the paths are already in the desired style (local 285 vs internal). Allocate the label in RESULT-POOL. */ 286static const char * 287diff_label(const char *path, 288 svn_revnum_t revnum, 289 apr_pool_t *result_pool) 290{ 291 const char *label; 292 if (revnum >= 0) 293 label = apr_psprintf(result_pool, _("%s\t(revision %ld)"), path, revnum); 294 else if (revnum == DIFF_REVNUM_NONEXISTENT) 295 label = apr_psprintf(result_pool, _("%s\t(nonexistent)"), path); 296 else /* SVN_INVALID_REVNUM */ 297 label = apr_psprintf(result_pool, _("%s\t(working copy)"), path); 298 299 return label; 300} 301 302/* Standard modes produced in git style diffs */ 303static const int exec_mode = 0755; 304static const int noexec_mode = 0644; 305static const int kind_file_mode = 0100000; 306/*static const kind_dir_mode = 0040000;*/ 307static const int kind_symlink_mode = 0120000; 308 309/* Print a git diff header for an addition within a diff between PATH1 and 310 * PATH2 to the stream OS using HEADER_ENCODING. */ 311static svn_error_t * 312print_git_diff_header_added(svn_stream_t *os, const char *header_encoding, 313 const char *path1, const char *path2, 314 svn_boolean_t exec_bit, 315 svn_boolean_t symlink_bit, 316 apr_pool_t *scratch_pool) 317{ 318 int new_mode = (exec_bit ? exec_mode : noexec_mode) 319 | (symlink_bit ? kind_symlink_mode : kind_file_mode); 320 321 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 322 "diff --git a/%s b/%s%s", 323 path1, path2, APR_EOL_STR)); 324 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 325 "new file mode %06o" APR_EOL_STR, 326 new_mode)); 327 return SVN_NO_ERROR; 328} 329 330/* Print a git diff header for a deletion within a diff between PATH1 and 331 * PATH2 to the stream OS using HEADER_ENCODING. */ 332static svn_error_t * 333print_git_diff_header_deleted(svn_stream_t *os, const char *header_encoding, 334 const char *path1, const char *path2, 335 svn_boolean_t exec_bit, 336 svn_boolean_t symlink_bit, 337 apr_pool_t *scratch_pool) 338{ 339 int old_mode = (exec_bit ? exec_mode : noexec_mode) 340 | (symlink_bit ? kind_symlink_mode : kind_file_mode); 341 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 342 "diff --git a/%s b/%s%s", 343 path1, path2, APR_EOL_STR)); 344 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 345 "deleted file mode %06o" APR_EOL_STR, 346 old_mode)); 347 return SVN_NO_ERROR; 348} 349 350/* Print a git diff header for a copy from COPYFROM_PATH to PATH to the stream 351 * OS using HEADER_ENCODING. */ 352static svn_error_t * 353print_git_diff_header_copied(svn_stream_t *os, const char *header_encoding, 354 const char *copyfrom_path, 355 svn_revnum_t copyfrom_rev, 356 const char *path, 357 apr_pool_t *scratch_pool) 358{ 359 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 360 "diff --git a/%s b/%s%s", 361 copyfrom_path, path, APR_EOL_STR)); 362 if (copyfrom_rev != SVN_INVALID_REVNUM) 363 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 364 "copy from %s@%ld%s", copyfrom_path, 365 copyfrom_rev, APR_EOL_STR)); 366 else 367 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 368 "copy from %s%s", copyfrom_path, 369 APR_EOL_STR)); 370 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 371 "copy to %s%s", path, APR_EOL_STR)); 372 return SVN_NO_ERROR; 373} 374 375/* Print a git diff header for a rename from COPYFROM_PATH to PATH to the 376 * stream OS using HEADER_ENCODING. */ 377static svn_error_t * 378print_git_diff_header_renamed(svn_stream_t *os, const char *header_encoding, 379 const char *copyfrom_path, const char *path, 380 apr_pool_t *scratch_pool) 381{ 382 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 383 "diff --git a/%s b/%s%s", 384 copyfrom_path, path, APR_EOL_STR)); 385 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 386 "rename from %s%s", copyfrom_path, 387 APR_EOL_STR)); 388 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 389 "rename to %s%s", path, APR_EOL_STR)); 390 return SVN_NO_ERROR; 391} 392 393/* Print a git diff header for a modification within a diff between PATH1 and 394 * PATH2 to the stream OS using HEADER_ENCODING. */ 395static svn_error_t * 396print_git_diff_header_modified(svn_stream_t *os, const char *header_encoding, 397 const char *path1, const char *path2, 398 apr_pool_t *scratch_pool) 399{ 400 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 401 "diff --git a/%s b/%s%s", 402 path1, path2, APR_EOL_STR)); 403 return SVN_NO_ERROR; 404} 405 406/* Helper function for print_git_diff_header */ 407static svn_error_t * 408maybe_print_mode_change(svn_stream_t *os, 409 const char *header_encoding, 410 svn_boolean_t exec_bit1, 411 svn_boolean_t exec_bit2, 412 svn_boolean_t symlink_bit1, 413 svn_boolean_t symlink_bit2, 414 const char *git_index_shas, 415 apr_pool_t *scratch_pool) 416{ 417 int old_mode = (exec_bit1 ? exec_mode : noexec_mode) 418 | (symlink_bit1 ? kind_symlink_mode : kind_file_mode); 419 int new_mode = (exec_bit2 ? exec_mode : noexec_mode) 420 | (symlink_bit2 ? kind_symlink_mode : kind_file_mode); 421 if (old_mode == new_mode) 422 { 423 if (git_index_shas) 424 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 425 "index %s %06o" APR_EOL_STR, 426 git_index_shas, old_mode)); 427 return SVN_NO_ERROR; 428 } 429 430 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 431 "old mode %06o" APR_EOL_STR, old_mode)); 432 SVN_ERR(svn_stream_printf_from_utf8(os, header_encoding, scratch_pool, 433 "new mode %06o" APR_EOL_STR, new_mode)); 434 return SVN_NO_ERROR; 435} 436 437/* Print a git diff header showing the OPERATION to the stream OS using 438 * HEADER_ENCODING. 439 * 440 * Return suitable diff labels for the git diff in *LABEL1 and *LABEL2. 441 * 442 * REV1 and REV2 are the revisions being diffed. 443 * COPYFROM_PATH and COPYFROM_REV indicate where the 444 * diffed item was copied from. 445 * Use SCRATCH_POOL for temporary allocations. */ 446static svn_error_t * 447print_git_diff_header(svn_stream_t *os, 448 const char **label1, const char **label2, 449 svn_diff_operation_kind_t operation, 450 svn_revnum_t rev1, 451 svn_revnum_t rev2, 452 const char *diff_relpath, 453 const char *copyfrom_path, 454 svn_revnum_t copyfrom_rev, 455 apr_hash_t *left_props, 456 apr_hash_t *right_props, 457 const char *git_index_shas, 458 const char *header_encoding, 459 const diff_driver_info_t *ddi, 460 apr_pool_t *scratch_pool) 461{ 462 const char *repos_relpath1; 463 const char *repos_relpath2; 464 const char *copyfrom_repos_relpath = NULL; 465 svn_boolean_t exec_bit1 = (svn_prop_get_value(left_props, 466 SVN_PROP_EXECUTABLE) != NULL); 467 svn_boolean_t exec_bit2 = (svn_prop_get_value(right_props, 468 SVN_PROP_EXECUTABLE) != NULL); 469 svn_boolean_t symlink_bit1 = (svn_prop_get_value(left_props, 470 SVN_PROP_SPECIAL) != NULL); 471 svn_boolean_t symlink_bit2 = (svn_prop_get_value(right_props, 472 SVN_PROP_SPECIAL) != NULL); 473 474 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, 475 ddi->orig_path_1, 476 ddi->session_relpath, 477 ddi->wc_ctx, 478 ddi->anchor, 479 scratch_pool, scratch_pool)); 480 SVN_ERR(make_repos_relpath(&repos_relpath2, diff_relpath, 481 ddi->orig_path_2, 482 ddi->session_relpath, 483 ddi->wc_ctx, 484 ddi->anchor, 485 scratch_pool, scratch_pool)); 486 if (copyfrom_path) 487 SVN_ERR(make_repos_relpath(©from_repos_relpath, copyfrom_path, 488 ddi->orig_path_2, 489 ddi->session_relpath, 490 ddi->wc_ctx, 491 ddi->anchor, 492 scratch_pool, scratch_pool)); 493 494 if (operation == svn_diff_op_deleted) 495 { 496 SVN_ERR(print_git_diff_header_deleted(os, header_encoding, 497 repos_relpath1, repos_relpath2, 498 exec_bit1, symlink_bit1, 499 scratch_pool)); 500 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1), 501 rev1, scratch_pool); 502 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), 503 rev2, scratch_pool); 504 505 } 506 else if (operation == svn_diff_op_copied) 507 { 508 SVN_ERR(print_git_diff_header_copied(os, header_encoding, 509 copyfrom_path, copyfrom_rev, 510 repos_relpath2, 511 scratch_pool)); 512 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path), 513 rev1, scratch_pool); 514 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), 515 rev2, scratch_pool); 516 SVN_ERR(maybe_print_mode_change(os, header_encoding, 517 exec_bit1, exec_bit2, 518 symlink_bit1, symlink_bit2, 519 git_index_shas, 520 scratch_pool)); 521 } 522 else if (operation == svn_diff_op_added) 523 { 524 SVN_ERR(print_git_diff_header_added(os, header_encoding, 525 repos_relpath1, repos_relpath2, 526 exec_bit2, symlink_bit2, 527 scratch_pool)); 528 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1), 529 rev1, scratch_pool); 530 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), 531 rev2, scratch_pool); 532 } 533 else if (operation == svn_diff_op_modified) 534 { 535 SVN_ERR(print_git_diff_header_modified(os, header_encoding, 536 repos_relpath1, repos_relpath2, 537 scratch_pool)); 538 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", repos_relpath1), 539 rev1, scratch_pool); 540 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), 541 rev2, scratch_pool); 542 SVN_ERR(maybe_print_mode_change(os, header_encoding, 543 exec_bit1, exec_bit2, 544 symlink_bit1, symlink_bit2, 545 git_index_shas, 546 scratch_pool)); 547 } 548 else if (operation == svn_diff_op_moved) 549 { 550 SVN_ERR(print_git_diff_header_renamed(os, header_encoding, 551 copyfrom_path, repos_relpath2, 552 scratch_pool)); 553 *label1 = diff_label(apr_psprintf(scratch_pool, "a/%s", copyfrom_path), 554 rev1, scratch_pool); 555 *label2 = diff_label(apr_psprintf(scratch_pool, "b/%s", repos_relpath2), 556 rev2, scratch_pool); 557 SVN_ERR(maybe_print_mode_change(os, header_encoding, 558 exec_bit1, exec_bit2, 559 symlink_bit1, symlink_bit2, 560 git_index_shas, 561 scratch_pool)); 562 } 563 564 return SVN_NO_ERROR; 565} 566 567/* Print the "Index:" and "=====" lines. 568 * Show the paths in platform-independent format ('/' separators) 569 */ 570static svn_error_t * 571print_diff_index_header(svn_stream_t *outstream, 572 const char *header_encoding, 573 const char *index_path, 574 const char *suffix, 575 apr_pool_t *scratch_pool) 576{ 577 SVN_ERR(svn_stream_printf_from_utf8(outstream, 578 header_encoding, scratch_pool, 579 "Index: %s%s" APR_EOL_STR 580 SVN_DIFF__EQUAL_STRING APR_EOL_STR, 581 index_path, suffix)); 582 return SVN_NO_ERROR; 583} 584 585/* A helper func that writes out verbal descriptions of property diffs 586 to OUTSTREAM. Of course, OUTSTREAM will probably be whatever was 587 passed to svn_client_diff7(), which is probably stdout. 588 589 ### FIXME needs proper docstring 590 591 If USE_GIT_DIFF_FORMAT is TRUE, pring git diff headers, which always 592 show paths relative to the repository root. DDI->session_relpath and 593 DDI->wc_ctx are needed to normalize paths relative the repository root, 594 and are ignored if USE_GIT_DIFF_FORMAT is FALSE. 595 596 If @a pretty_print_mergeinfo is true, then describe 'svn:mergeinfo' 597 property changes in a human-readable form that says what changes were 598 merged or reverse merged; otherwise (or if the mergeinfo property values 599 don't parse correctly) display them just like any other property. 600 */ 601static svn_error_t * 602display_prop_diffs(const apr_array_header_t *propchanges, 603 apr_hash_t *left_props, 604 apr_hash_t *right_props, 605 const char *diff_relpath, 606 svn_revnum_t rev1, 607 svn_revnum_t rev2, 608 const char *encoding, 609 svn_stream_t *outstream, 610 const char *relative_to_dir, 611 svn_boolean_t show_diff_header, 612 svn_boolean_t use_git_diff_format, 613 svn_boolean_t pretty_print_mergeinfo, 614 const diff_driver_info_t *ddi, 615 svn_cancel_func_t cancel_func, 616 void *cancel_baton, 617 apr_pool_t *scratch_pool) 618{ 619 const char *repos_relpath1 = NULL; 620 const char *index_path; 621 const char *label_path1, *label_path2; 622 623 if (use_git_diff_format) 624 { 625 SVN_ERR(make_repos_relpath(&repos_relpath1, diff_relpath, ddi->orig_path_1, 626 ddi->session_relpath, ddi->wc_ctx, ddi->anchor, 627 scratch_pool, scratch_pool)); 628 } 629 630 /* If we're creating a diff on the wc root, path would be empty. */ 631 SVN_ERR(adjust_paths_for_diff_labels(&index_path, 632 &label_path1, &label_path2, 633 relative_to_dir, ddi->anchor, 634 diff_relpath, 635 ddi->orig_path_1, ddi->orig_path_2, 636 scratch_pool, scratch_pool)); 637 638 if (show_diff_header) 639 { 640 const char *label1; 641 const char *label2; 642 643 label1 = diff_label(label_path1, rev1, scratch_pool); 644 label2 = diff_label(label_path2, rev2, scratch_pool); 645 646 SVN_ERR(print_diff_index_header(outstream, encoding, 647 index_path, "", scratch_pool)); 648 649 if (use_git_diff_format) 650 SVN_ERR(print_git_diff_header(outstream, &label1, &label2, 651 svn_diff_op_modified, 652 rev1, rev2, 653 diff_relpath, 654 NULL, SVN_INVALID_REVNUM, 655 left_props, right_props, 656 NULL, 657 encoding, ddi, scratch_pool)); 658 659 /* --- label1 660 * +++ label2 */ 661 SVN_ERR(svn_diff__unidiff_write_header( 662 outstream, encoding, label1, label2, scratch_pool)); 663 } 664 665 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool, 666 APR_EOL_STR 667 "Property changes on: %s" 668 APR_EOL_STR, 669 use_git_diff_format 670 ? repos_relpath1 671 : index_path)); 672 673 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, scratch_pool, 674 SVN_DIFF__UNDER_STRING APR_EOL_STR)); 675 676 SVN_ERR(svn_diff__display_prop_diffs( 677 outstream, encoding, propchanges, left_props, 678 pretty_print_mergeinfo, 679 -1 /* context_size */, 680 cancel_func, cancel_baton, scratch_pool)); 681 682 return SVN_NO_ERROR; 683} 684 685/*-----------------------------------------------------------------*/ 686 687/*** Callbacks for 'svn diff', invoked by the repos-diff editor. ***/ 688 689/* Diff writer state */ 690typedef struct diff_writer_info_t 691{ 692 /* If non-null, the external diff command to invoke. */ 693 const char *diff_cmd; 694 695 /* This is allocated in this struct's pool or a higher-up pool. */ 696 union { 697 /* If 'diff_cmd' is null, then this is the parsed options to 698 pass to the internal libsvn_diff implementation. */ 699 svn_diff_file_options_t *for_internal; 700 /* Else if 'diff_cmd' is non-null, then... */ 701 struct { 702 /* ...this is an argument array for the external command, and */ 703 const char **argv; 704 /* ...this is the length of argv. */ 705 int argc; 706 } for_external; 707 } options; 708 709 apr_pool_t *pool; 710 svn_stream_t *outstream; 711 svn_stream_t *errstream; 712 713 const char *header_encoding; 714 715 /* Set this if you want diff output even for binary files. */ 716 svn_boolean_t force_binary; 717 718 /* The directory that diff target paths should be considered as 719 relative to for output generation (see issue #2723). */ 720 const char *relative_to_dir; 721 722 /* Whether property differences are ignored. */ 723 svn_boolean_t ignore_properties; 724 725 /* Whether to show only property changes. */ 726 svn_boolean_t properties_only; 727 728 /* Whether we're producing a git-style diff. */ 729 svn_boolean_t use_git_diff_format; 730 731 /* Whether addition of a file is summarized versus showing a full diff. */ 732 svn_boolean_t no_diff_added; 733 734 /* Whether deletion of a file is summarized versus showing a full diff. */ 735 svn_boolean_t no_diff_deleted; 736 737 /* Whether to ignore copyfrom information when showing adds */ 738 svn_boolean_t show_copies_as_adds; 739 740 /* Whether to show mergeinfo prop changes in human-readable form */ 741 svn_boolean_t pretty_print_mergeinfo; 742 743 /* Empty files for creating diffs or NULL if not used yet */ 744 const char *empty_file; 745 746 svn_cancel_func_t cancel_func; 747 void *cancel_baton; 748 749 struct diff_driver_info_t ddi; 750} diff_writer_info_t; 751 752/* An helper for diff_dir_props_changed, diff_file_changed and diff_file_added 753 */ 754static svn_error_t * 755diff_props_changed(const char *diff_relpath, 756 svn_revnum_t rev1, 757 svn_revnum_t rev2, 758 const apr_array_header_t *propchanges, 759 apr_hash_t *left_props, 760 apr_hash_t *right_props, 761 svn_boolean_t show_diff_header, 762 diff_writer_info_t *dwi, 763 apr_pool_t *scratch_pool) 764{ 765 apr_array_header_t *props; 766 767 /* If property differences are ignored, there's nothing to do. */ 768 if (dwi->ignore_properties) 769 return SVN_NO_ERROR; 770 771 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, 772 scratch_pool)); 773 774 if (props->nelts > 0) 775 { 776 /* We're using the revnums from the dwi since there's 777 * no revision argument to the svn_wc_diff_callback_t 778 * dir_props_changed(). */ 779 SVN_ERR(display_prop_diffs(props, left_props, right_props, 780 diff_relpath, 781 rev1, 782 rev2, 783 dwi->header_encoding, 784 dwi->outstream, 785 dwi->relative_to_dir, 786 show_diff_header, 787 dwi->use_git_diff_format, 788 dwi->pretty_print_mergeinfo, 789 &dwi->ddi, 790 dwi->cancel_func, 791 dwi->cancel_baton, 792 scratch_pool)); 793 } 794 795 return SVN_NO_ERROR; 796} 797 798/* Given a file ORIG_TMPFILE, return a path to a temporary file that lives at 799 * least as long as RESULT_POOL, containing the git-like represention of 800 * ORIG_TMPFILE */ 801static svn_error_t * 802transform_link_to_git(const char **new_tmpfile, 803 const char **git_sha1, 804 const char *orig_tmpfile, 805 apr_pool_t *result_pool, 806 apr_pool_t *scratch_pool) 807{ 808 apr_file_t *orig; 809 apr_file_t *gitlike; 810 svn_stringbuf_t *line; 811 812 *git_sha1 = NULL; 813 814 SVN_ERR(svn_io_file_open(&orig, orig_tmpfile, APR_READ, APR_OS_DEFAULT, 815 scratch_pool)); 816 SVN_ERR(svn_io_open_unique_file3(&gitlike, new_tmpfile, NULL, 817 svn_io_file_del_on_pool_cleanup, 818 result_pool, scratch_pool)); 819 820 SVN_ERR(svn_io_file_readline(orig, &line, NULL, NULL, 2 * APR_PATH_MAX + 2, 821 scratch_pool, scratch_pool)); 822 823 if (line->len > 5 && !strncmp(line->data, "link ", 5)) 824 { 825 const char *sz_str; 826 svn_checksum_t *checksum; 827 828 svn_stringbuf_remove(line, 0, 5); 829 830 SVN_ERR(svn_io_file_write_full(gitlike, line->data, line->len, 831 NULL, scratch_pool)); 832 833 /* git calculates the sha over "blob X\0" + the actual data, 834 where X is the decimal size of the blob. */ 835 sz_str = apr_psprintf(scratch_pool, "blob %u", (unsigned int)line->len); 836 svn_stringbuf_insert(line, 0, sz_str, strlen(sz_str) + 1); 837 838 SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, 839 line->data, line->len, scratch_pool)); 840 841 *git_sha1 = svn_checksum_to_cstring(checksum, result_pool); 842 } 843 else 844 { 845 /* Not a link... so can't convert */ 846 *new_tmpfile = apr_pstrdup(result_pool, orig_tmpfile); 847 } 848 849 SVN_ERR(svn_io_file_close(orig, scratch_pool)); 850 SVN_ERR(svn_io_file_close(gitlike, scratch_pool)); 851 return SVN_NO_ERROR; 852} 853 854/* Show differences between TMPFILE1 and TMPFILE2. DIFF_RELPATH, REV1, and 855 REV2 are used in the headers to indicate the file and revisions. 856 857 If either side has an svn:mime-type property that indicates 'binary' 858 content, then if DWI->force_binary is set, attempt to produce the 859 diff in the usual way, otherwise produce a 'GIT binary diff' in git mode 860 or print a warning message in non-git mode. 861 862 If FORCE_DIFF is TRUE, always write a diff, even for empty diffs. 863 864 Set *WROTE_HEADER to TRUE if a diff header was written */ 865static svn_error_t * 866diff_content_changed(svn_boolean_t *wrote_header, 867 const char *diff_relpath, 868 const char *tmpfile1, 869 const char *tmpfile2, 870 svn_revnum_t rev1, 871 svn_revnum_t rev2, 872 apr_hash_t *left_props, 873 apr_hash_t *right_props, 874 svn_diff_operation_kind_t operation, 875 svn_boolean_t force_diff, 876 const char *copyfrom_path, 877 svn_revnum_t copyfrom_rev, 878 diff_writer_info_t *dwi, 879 apr_pool_t *scratch_pool) 880{ 881 const char *rel_to_dir = dwi->relative_to_dir; 882 svn_stream_t *outstream = dwi->outstream; 883 const char *label1, *label2; 884 svn_boolean_t mt1_binary = FALSE, mt2_binary = FALSE; 885 const char *index_path; 886 const char *label_path1, *label_path2; 887 const char *mimetype1 = svn_prop_get_value(left_props, SVN_PROP_MIME_TYPE); 888 const char *mimetype2 = svn_prop_get_value(right_props, SVN_PROP_MIME_TYPE); 889 const char *index_shas = NULL; 890 891 /* If only property differences are shown, there's nothing to do. */ 892 if (dwi->properties_only) 893 return SVN_NO_ERROR; 894 895 /* Generate the diff headers. */ 896 SVN_ERR(adjust_paths_for_diff_labels(&index_path, 897 &label_path1, &label_path2, 898 rel_to_dir, dwi->ddi.anchor, 899 diff_relpath, 900 dwi->ddi.orig_path_1, dwi->ddi.orig_path_2, 901 scratch_pool, scratch_pool)); 902 903 label1 = diff_label(label_path1, rev1, scratch_pool); 904 label2 = diff_label(label_path2, rev2, scratch_pool); 905 906 /* Possible easy-out: if either mime-type is binary and force was not 907 specified, don't attempt to generate a viewable diff at all. 908 Print a warning and exit. */ 909 if (mimetype1) 910 mt1_binary = svn_mime_type_is_binary(mimetype1); 911 if (mimetype2) 912 mt2_binary = svn_mime_type_is_binary(mimetype2); 913 914 if (dwi->use_git_diff_format) 915 { 916 const char *l_hash = NULL; 917 const char *r_hash = NULL; 918 919 /* Change symlinks to their 'git like' plain format */ 920 if (svn_prop_get_value(left_props, SVN_PROP_SPECIAL)) 921 SVN_ERR(transform_link_to_git(&tmpfile1, &l_hash, tmpfile1, 922 scratch_pool, scratch_pool)); 923 if (svn_prop_get_value(right_props, SVN_PROP_SPECIAL)) 924 SVN_ERR(transform_link_to_git(&tmpfile2, &r_hash, tmpfile2, 925 scratch_pool, scratch_pool)); 926 927 if (l_hash && r_hash) 928 { 929 /* The symlink has changed. But we can't tell the user of the 930 diff whether we are writing git diffs or svn diffs of the 931 symlink... except when we add a git-like index line */ 932 933 l_hash = apr_pstrndup(scratch_pool, l_hash, 8); 934 r_hash = apr_pstrndup(scratch_pool, r_hash, 8); 935 936 index_shas = apr_psprintf(scratch_pool, "%8s..%8s", 937 l_hash, r_hash); 938 } 939 } 940 941 if (! dwi->force_binary && (mt1_binary || mt2_binary)) 942 { 943 /* Print out the diff header. */ 944 SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, 945 index_path, "", scratch_pool)); 946 *wrote_header = TRUE; 947 948 /* ### Print git diff headers. */ 949 950 if (dwi->use_git_diff_format) 951 { 952 svn_stream_t *left_stream; 953 svn_stream_t *right_stream; 954 955 SVN_ERR(print_git_diff_header(outstream, 956 &label1, &label2, 957 operation, 958 rev1, rev2, 959 diff_relpath, 960 copyfrom_path, copyfrom_rev, 961 left_props, right_props, 962 index_shas, 963 dwi->header_encoding, 964 &dwi->ddi, scratch_pool)); 965 966 SVN_ERR(svn_stream_open_readonly(&left_stream, tmpfile1, 967 scratch_pool, scratch_pool)); 968 SVN_ERR(svn_stream_open_readonly(&right_stream, tmpfile2, 969 scratch_pool, scratch_pool)); 970 SVN_ERR(svn_diff_output_binary(outstream, 971 left_stream, right_stream, 972 dwi->cancel_func, dwi->cancel_baton, 973 scratch_pool)); 974 } 975 else 976 { 977 SVN_ERR(svn_stream_printf_from_utf8(outstream, 978 dwi->header_encoding, scratch_pool, 979 _("Cannot display: file marked as a binary type.%s"), 980 APR_EOL_STR)); 981 982 if (mt1_binary && !mt2_binary) 983 SVN_ERR(svn_stream_printf_from_utf8(outstream, 984 dwi->header_encoding, scratch_pool, 985 "svn:mime-type = %s" APR_EOL_STR, mimetype1)); 986 else if (mt2_binary && !mt1_binary) 987 SVN_ERR(svn_stream_printf_from_utf8(outstream, 988 dwi->header_encoding, scratch_pool, 989 "svn:mime-type = %s" APR_EOL_STR, mimetype2)); 990 else if (mt1_binary && mt2_binary) 991 { 992 if (strcmp(mimetype1, mimetype2) == 0) 993 SVN_ERR(svn_stream_printf_from_utf8(outstream, 994 dwi->header_encoding, scratch_pool, 995 "svn:mime-type = %s" APR_EOL_STR, 996 mimetype1)); 997 else 998 SVN_ERR(svn_stream_printf_from_utf8(outstream, 999 dwi->header_encoding, scratch_pool, 1000 "svn:mime-type = (%s, %s)" APR_EOL_STR, 1001 mimetype1, mimetype2)); 1002 } 1003 } 1004 1005 /* Exit early. */ 1006 return SVN_NO_ERROR; 1007 } 1008 1009 1010 if (dwi->diff_cmd) 1011 { 1012 svn_stream_t *errstream = dwi->errstream; 1013 apr_file_t *outfile; 1014 apr_file_t *errfile; 1015 const char *outfilename; 1016 const char *errfilename; 1017 svn_stream_t *stream; 1018 int exitcode; 1019 1020 /* Print out the diff header. */ 1021 SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, 1022 index_path, "", scratch_pool)); 1023 *wrote_header = TRUE; 1024 1025 /* ### Do we want to add git diff headers here too? I'd say no. The 1026 * ### 'Index' and '===' line is something subversion has added. The rest 1027 * ### is up to the external diff application. We may be dealing with 1028 * ### a non-git compatible diff application.*/ 1029 1030 /* We deal in streams, but svn_io_run_diff2() deals in file handles, 1031 so we may need to make temporary files and then copy the contents 1032 to our stream. */ 1033 outfile = svn_stream__aprfile(outstream); 1034 if (outfile) 1035 outfilename = NULL; 1036 else 1037 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL, 1038 svn_io_file_del_on_pool_cleanup, 1039 scratch_pool, scratch_pool)); 1040 1041 errfile = svn_stream__aprfile(errstream); 1042 if (errfile) 1043 errfilename = NULL; 1044 else 1045 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL, 1046 svn_io_file_del_on_pool_cleanup, 1047 scratch_pool, scratch_pool)); 1048 1049 SVN_ERR(svn_io_run_diff2(".", 1050 dwi->options.for_external.argv, 1051 dwi->options.for_external.argc, 1052 label1, label2, 1053 tmpfile1, tmpfile2, 1054 &exitcode, outfile, errfile, 1055 dwi->diff_cmd, scratch_pool)); 1056 1057 /* Now, open and copy our files to our output streams. */ 1058 if (outfilename) 1059 { 1060 SVN_ERR(svn_io_file_close(outfile, scratch_pool)); 1061 SVN_ERR(svn_stream_open_readonly(&stream, outfilename, 1062 scratch_pool, scratch_pool)); 1063 SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(outstream, 1064 scratch_pool), 1065 NULL, NULL, scratch_pool)); 1066 } 1067 if (errfilename) 1068 { 1069 SVN_ERR(svn_io_file_close(errfile, scratch_pool)); 1070 SVN_ERR(svn_stream_open_readonly(&stream, errfilename, 1071 scratch_pool, scratch_pool)); 1072 SVN_ERR(svn_stream_copy3(stream, svn_stream_disown(errstream, 1073 scratch_pool), 1074 NULL, NULL, scratch_pool)); 1075 } 1076 } 1077 else /* use libsvn_diff to generate the diff */ 1078 { 1079 svn_diff_t *diff; 1080 1081 SVN_ERR(svn_diff_file_diff_2(&diff, tmpfile1, tmpfile2, 1082 dwi->options.for_internal, 1083 scratch_pool)); 1084 1085 if (force_diff 1086 || dwi->use_git_diff_format 1087 || svn_diff_contains_diffs(diff)) 1088 { 1089 /* Print out the diff header. */ 1090 SVN_ERR(print_diff_index_header(outstream, dwi->header_encoding, 1091 index_path, "", scratch_pool)); 1092 *wrote_header = TRUE; 1093 1094 if (dwi->use_git_diff_format) 1095 { 1096 SVN_ERR(print_git_diff_header(outstream, 1097 &label1, &label2, 1098 operation, 1099 rev1, rev2, 1100 diff_relpath, 1101 copyfrom_path, copyfrom_rev, 1102 left_props, right_props, 1103 index_shas, 1104 dwi->header_encoding, 1105 &dwi->ddi, scratch_pool)); 1106 } 1107 1108 /* Output the actual diff */ 1109 if (force_diff || svn_diff_contains_diffs(diff)) 1110 SVN_ERR(svn_diff_file_output_unified4(outstream, diff, 1111 tmpfile1, tmpfile2, label1, label2, 1112 dwi->header_encoding, rel_to_dir, 1113 dwi->options.for_internal->show_c_function, 1114 dwi->options.for_internal->context_size, 1115 dwi->cancel_func, dwi->cancel_baton, 1116 scratch_pool)); 1117 } 1118 } 1119 1120 return SVN_NO_ERROR; 1121} 1122 1123/* An svn_diff_tree_processor_t callback. */ 1124static svn_error_t * 1125diff_file_changed(const char *relpath, 1126 const svn_diff_source_t *left_source, 1127 const svn_diff_source_t *right_source, 1128 const char *left_file, 1129 const char *right_file, 1130 /*const*/ apr_hash_t *left_props, 1131 /*const*/ apr_hash_t *right_props, 1132 svn_boolean_t file_modified, 1133 const apr_array_header_t *prop_changes, 1134 void *file_baton, 1135 const struct svn_diff_tree_processor_t *processor, 1136 apr_pool_t *scratch_pool) 1137{ 1138 diff_writer_info_t *dwi = processor->baton; 1139 svn_boolean_t wrote_header = FALSE; 1140 1141 if (file_modified) 1142 SVN_ERR(diff_content_changed(&wrote_header, relpath, 1143 left_file, right_file, 1144 left_source->revision, 1145 right_source->revision, 1146 left_props, right_props, 1147 svn_diff_op_modified, FALSE, 1148 NULL, 1149 SVN_INVALID_REVNUM, dwi, 1150 scratch_pool)); 1151 if (prop_changes->nelts > 0) 1152 SVN_ERR(diff_props_changed(relpath, 1153 left_source->revision, 1154 right_source->revision, prop_changes, 1155 left_props, right_props, !wrote_header, 1156 dwi, scratch_pool)); 1157 return SVN_NO_ERROR; 1158} 1159 1160/* Because the repos-diff editor passes at least one empty file to 1161 each of these next two functions, they can be dumb wrappers around 1162 the main workhorse routine. */ 1163 1164/* An svn_diff_tree_processor_t callback. */ 1165static svn_error_t * 1166diff_file_added(const char *relpath, 1167 const svn_diff_source_t *copyfrom_source, 1168 const svn_diff_source_t *right_source, 1169 const char *copyfrom_file, 1170 const char *right_file, 1171 /*const*/ apr_hash_t *copyfrom_props, 1172 /*const*/ apr_hash_t *right_props, 1173 void *file_baton, 1174 const struct svn_diff_tree_processor_t *processor, 1175 apr_pool_t *scratch_pool) 1176{ 1177 diff_writer_info_t *dwi = processor->baton; 1178 svn_boolean_t wrote_header = FALSE; 1179 const char *left_file; 1180 apr_hash_t *left_props; 1181 apr_array_header_t *prop_changes; 1182 1183 if (dwi->no_diff_added) 1184 { 1185 const char *index_path = relpath; 1186 1187 if (dwi->ddi.anchor) 1188 index_path = svn_dirent_join(dwi->ddi.anchor, relpath, 1189 scratch_pool); 1190 1191 SVN_ERR(print_diff_index_header(dwi->outstream, dwi->header_encoding, 1192 index_path, " (added)", 1193 scratch_pool)); 1194 wrote_header = TRUE; 1195 return SVN_NO_ERROR; 1196 } 1197 1198 /* During repos->wc diff of a copy revision numbers obtained 1199 * from the working copy are always SVN_INVALID_REVNUM. */ 1200 if (copyfrom_source && !dwi->show_copies_as_adds) 1201 { 1202 left_file = copyfrom_file; 1203 left_props = copyfrom_props ? copyfrom_props : apr_hash_make(scratch_pool); 1204 } 1205 else 1206 { 1207 if (!dwi->empty_file) 1208 SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file, 1209 NULL, svn_io_file_del_on_pool_cleanup, 1210 dwi->pool, scratch_pool)); 1211 1212 left_file = dwi->empty_file; 1213 left_props = apr_hash_make(scratch_pool); 1214 1215 copyfrom_source = NULL; 1216 copyfrom_file = NULL; 1217 } 1218 1219 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, scratch_pool)); 1220 1221 if (copyfrom_source && right_file) 1222 SVN_ERR(diff_content_changed(&wrote_header, relpath, 1223 left_file, right_file, 1224 copyfrom_source->revision, 1225 right_source->revision, 1226 left_props, right_props, 1227 copyfrom_source->moved_from_relpath 1228 ? svn_diff_op_moved 1229 : svn_diff_op_copied, 1230 TRUE /* force diff output */, 1231 copyfrom_source->moved_from_relpath 1232 ? copyfrom_source->moved_from_relpath 1233 : copyfrom_source->repos_relpath, 1234 copyfrom_source->revision, 1235 dwi, scratch_pool)); 1236 else if (right_file) 1237 SVN_ERR(diff_content_changed(&wrote_header, relpath, 1238 left_file, right_file, 1239 DIFF_REVNUM_NONEXISTENT, 1240 right_source->revision, 1241 left_props, right_props, 1242 svn_diff_op_added, 1243 TRUE /* force diff output */, 1244 NULL, SVN_INVALID_REVNUM, 1245 dwi, scratch_pool)); 1246 1247 if (prop_changes->nelts > 0) 1248 SVN_ERR(diff_props_changed(relpath, 1249 copyfrom_source ? copyfrom_source->revision 1250 : DIFF_REVNUM_NONEXISTENT, 1251 right_source->revision, 1252 prop_changes, 1253 left_props, right_props, 1254 ! wrote_header, dwi, scratch_pool)); 1255 1256 return SVN_NO_ERROR; 1257} 1258 1259/* An svn_diff_tree_processor_t callback. */ 1260static svn_error_t * 1261diff_file_deleted(const char *relpath, 1262 const svn_diff_source_t *left_source, 1263 const char *left_file, 1264 /*const*/ apr_hash_t *left_props, 1265 void *file_baton, 1266 const struct svn_diff_tree_processor_t *processor, 1267 apr_pool_t *scratch_pool) 1268{ 1269 diff_writer_info_t *dwi = processor->baton; 1270 1271 if (dwi->no_diff_deleted) 1272 { 1273 const char *index_path = relpath; 1274 1275 if (dwi->ddi.anchor) 1276 index_path = svn_dirent_join(dwi->ddi.anchor, relpath, 1277 scratch_pool); 1278 1279 SVN_ERR(print_diff_index_header(dwi->outstream, dwi->header_encoding, 1280 index_path, " (deleted)", 1281 scratch_pool)); 1282 } 1283 else 1284 { 1285 svn_boolean_t wrote_header = FALSE; 1286 1287 if (!dwi->empty_file) 1288 SVN_ERR(svn_io_open_unique_file3(NULL, &dwi->empty_file, 1289 NULL, svn_io_file_del_on_pool_cleanup, 1290 dwi->pool, scratch_pool)); 1291 1292 if (left_file) 1293 SVN_ERR(diff_content_changed(&wrote_header, relpath, 1294 left_file, dwi->empty_file, 1295 left_source->revision, 1296 DIFF_REVNUM_NONEXISTENT, 1297 left_props, 1298 NULL, 1299 svn_diff_op_deleted, FALSE, 1300 NULL, SVN_INVALID_REVNUM, 1301 dwi, 1302 scratch_pool)); 1303 1304 if (left_props && apr_hash_count(left_props)) 1305 { 1306 apr_array_header_t *prop_changes; 1307 1308 SVN_ERR(svn_prop_diffs(&prop_changes, apr_hash_make(scratch_pool), 1309 left_props, scratch_pool)); 1310 1311 SVN_ERR(diff_props_changed(relpath, 1312 left_source->revision, 1313 DIFF_REVNUM_NONEXISTENT, 1314 prop_changes, 1315 left_props, NULL, 1316 ! wrote_header, dwi, scratch_pool)); 1317 } 1318 } 1319 1320 return SVN_NO_ERROR; 1321} 1322 1323/* An svn_wc_diff_callbacks4_t function. */ 1324static svn_error_t * 1325diff_dir_changed(const char *relpath, 1326 const svn_diff_source_t *left_source, 1327 const svn_diff_source_t *right_source, 1328 /*const*/ apr_hash_t *left_props, 1329 /*const*/ apr_hash_t *right_props, 1330 const apr_array_header_t *prop_changes, 1331 void *dir_baton, 1332 const struct svn_diff_tree_processor_t *processor, 1333 apr_pool_t *scratch_pool) 1334{ 1335 diff_writer_info_t *dwi = processor->baton; 1336 1337 SVN_ERR(diff_props_changed(relpath, 1338 left_source->revision, 1339 right_source->revision, 1340 prop_changes, 1341 left_props, right_props, 1342 TRUE /* show_diff_header */, 1343 dwi, 1344 scratch_pool)); 1345 1346 return SVN_NO_ERROR; 1347} 1348 1349/* An svn_diff_tree_processor_t callback. */ 1350static svn_error_t * 1351diff_dir_added(const char *relpath, 1352 const svn_diff_source_t *copyfrom_source, 1353 const svn_diff_source_t *right_source, 1354 /*const*/ apr_hash_t *copyfrom_props, 1355 /*const*/ apr_hash_t *right_props, 1356 void *dir_baton, 1357 const struct svn_diff_tree_processor_t *processor, 1358 apr_pool_t *scratch_pool) 1359{ 1360 diff_writer_info_t *dwi = processor->baton; 1361 apr_hash_t *left_props; 1362 apr_array_header_t *prop_changes; 1363 1364 if (dwi->no_diff_added) 1365 return SVN_NO_ERROR; 1366 1367 if (copyfrom_source && !dwi->show_copies_as_adds) 1368 { 1369 left_props = copyfrom_props ? copyfrom_props 1370 : apr_hash_make(scratch_pool); 1371 } 1372 else 1373 { 1374 left_props = apr_hash_make(scratch_pool); 1375 copyfrom_source = NULL; 1376 } 1377 1378 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, 1379 scratch_pool)); 1380 1381 return svn_error_trace(diff_props_changed(relpath, 1382 copyfrom_source ? copyfrom_source->revision 1383 : DIFF_REVNUM_NONEXISTENT, 1384 right_source->revision, 1385 prop_changes, 1386 left_props, right_props, 1387 TRUE /* show_diff_header */, 1388 dwi, 1389 scratch_pool)); 1390} 1391 1392/* An svn_diff_tree_processor_t callback. */ 1393static svn_error_t * 1394diff_dir_deleted(const char *relpath, 1395 const svn_diff_source_t *left_source, 1396 /*const*/ apr_hash_t *left_props, 1397 void *dir_baton, 1398 const struct svn_diff_tree_processor_t *processor, 1399 apr_pool_t *scratch_pool) 1400{ 1401 diff_writer_info_t *dwi = processor->baton; 1402 apr_array_header_t *prop_changes; 1403 apr_hash_t *right_props; 1404 1405 if (dwi->no_diff_deleted) 1406 return SVN_NO_ERROR; 1407 1408 right_props = apr_hash_make(scratch_pool); 1409 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, 1410 left_props, scratch_pool)); 1411 1412 SVN_ERR(diff_props_changed(relpath, 1413 left_source->revision, 1414 DIFF_REVNUM_NONEXISTENT, 1415 prop_changes, 1416 left_props, right_props, 1417 TRUE /* show_diff_header */, 1418 dwi, 1419 scratch_pool)); 1420 1421 return SVN_NO_ERROR; 1422} 1423 1424/*-----------------------------------------------------------------*/ 1425 1426/** The logic behind 'svn diff' and 'svn merge'. */ 1427 1428 1429/* Hi! This is a comment left behind by Karl, and Ben is too afraid 1430 to erase it at this time, because he's not fully confident that all 1431 this knowledge has been grokked yet. 1432 1433 There are five cases: 1434 1. path is not a URL and start_revision != end_revision 1435 2. path is not a URL and start_revision == end_revision 1436 3. path is a URL and start_revision != end_revision 1437 4. path is a URL and start_revision == end_revision 1438 5. path is not a URL and no revisions given 1439 1440 With only one distinct revision the working copy provides the 1441 other. When path is a URL there is no working copy. Thus 1442 1443 1: compare repository versions for URL coresponding to working copy 1444 2: compare working copy against repository version 1445 3: compare repository versions for URL 1446 4: nothing to do. 1447 5: compare working copy against text-base 1448 1449 Case 4 is not as stupid as it looks, for example it may occur if 1450 the user specifies two dates that resolve to the same revision. */ 1451 1452 1453/** Check if paths PATH_OR_URL1 and PATH_OR_URL2 are urls and if the 1454 * revisions REVISION1 and REVISION2 are local. If PEG_REVISION is not 1455 * unspecified, ensure that at least one of the two revisions is not 1456 * BASE or WORKING. 1457 * If PATH_OR_URL1 can only be found in the repository, set *IS_REPOS1 1458 * to TRUE. If PATH_OR_URL2 can only be found in the repository, set 1459 * *IS_REPOS2 to TRUE. */ 1460static svn_error_t * 1461check_paths(svn_boolean_t *is_repos1, 1462 svn_boolean_t *is_repos2, 1463 const char *path_or_url1, 1464 const char *path_or_url2, 1465 const svn_opt_revision_t *revision1, 1466 const svn_opt_revision_t *revision2, 1467 const svn_opt_revision_t *peg_revision) 1468{ 1469 svn_boolean_t is_local_rev1, is_local_rev2; 1470 1471 /* Verify our revision arguments in light of the paths. */ 1472 if ((revision1->kind == svn_opt_revision_unspecified) 1473 || (revision2->kind == svn_opt_revision_unspecified)) 1474 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, 1475 _("Not all required revisions are specified")); 1476 1477 /* Revisions can be said to be local or remote. 1478 * BASE and WORKING are local revisions. */ 1479 is_local_rev1 = 1480 ((revision1->kind == svn_opt_revision_base) 1481 || (revision1->kind == svn_opt_revision_working)); 1482 is_local_rev2 = 1483 ((revision2->kind == svn_opt_revision_base) 1484 || (revision2->kind == svn_opt_revision_working)); 1485 1486 if (peg_revision->kind != svn_opt_revision_unspecified && 1487 is_local_rev1 && is_local_rev2) 1488 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, 1489 _("At least one revision must be something other " 1490 "than BASE or WORKING when diffing a URL")); 1491 1492 /* Working copy paths with non-local revisions get turned into 1493 URLs. We don't do that here, though. We simply record that it 1494 needs to be done, which is information that helps us choose our 1495 diff helper function. */ 1496 *is_repos1 = ! is_local_rev1 || svn_path_is_url(path_or_url1); 1497 *is_repos2 = ! is_local_rev2 || svn_path_is_url(path_or_url2); 1498 1499 return SVN_NO_ERROR; 1500} 1501 1502/* Raise an error if the diff target URL does not exist at REVISION. 1503 * If REVISION does not equal OTHER_REVISION, mention both revisions in 1504 * the error message. Use RA_SESSION to contact the repository. 1505 * Use POOL for temporary allocations. */ 1506static svn_error_t * 1507check_diff_target_exists(const char *url, 1508 svn_revnum_t revision, 1509 svn_revnum_t other_revision, 1510 svn_ra_session_t *ra_session, 1511 apr_pool_t *pool) 1512{ 1513 svn_node_kind_t kind; 1514 const char *session_url; 1515 1516 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool)); 1517 1518 if (strcmp(url, session_url) != 0) 1519 SVN_ERR(svn_ra_reparent(ra_session, url, pool)); 1520 1521 SVN_ERR(svn_ra_check_path(ra_session, "", revision, &kind, pool)); 1522 if (kind == svn_node_none) 1523 { 1524 if (revision == other_revision) 1525 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1526 _("Diff target '%s' was not found in the " 1527 "repository at revision '%ld'"), 1528 url, revision); 1529 else 1530 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1531 _("Diff target '%s' was not found in the " 1532 "repository at revision '%ld' or '%ld'"), 1533 url, revision, other_revision); 1534 } 1535 1536 if (strcmp(url, session_url) != 0) 1537 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool)); 1538 1539 return SVN_NO_ERROR; 1540} 1541 1542/** Prepare a repos repos diff between PATH_OR_URL1 and 1543 * PATH_OR_URL2@PEG_REVISION, in the revision range REVISION1:REVISION2. 1544 * 1545 * Return the resolved URL and peg revision pairs in *URL1, *REV1 and in 1546 * *URL2, *REV2. 1547 * 1548 * Return suitable anchor URL and target pairs in *ANCHOR1, *TARGET1 and 1549 * in *ANCHOR2, *TARGET2, corresponding to *URL1 and *URL2. 1550 * 1551 * (The choice of anchor URLs here appears to be: start with *URL1, *URL2; 1552 * then take the parent dir on both sides, unless either of *URL1 or *URL2 1553 * is the repository root or the parent dir of *URL1 is unreadable.) 1554 * 1555 * Set *KIND1 and *KIND2 to the node kinds of *URL1 and *URL2, and verify 1556 * that at least one of the diff targets exists. 1557 * 1558 * Set *RA_SESSION to an RA session parented at the URL *ANCHOR1. 1559 * 1560 * Use client context CTX. Do all allocations in POOL. */ 1561static svn_error_t * 1562diff_prepare_repos_repos(const char **url1, 1563 const char **url2, 1564 svn_revnum_t *rev1, 1565 svn_revnum_t *rev2, 1566 const char **anchor1, 1567 const char **anchor2, 1568 const char **target1, 1569 const char **target2, 1570 svn_node_kind_t *kind1, 1571 svn_node_kind_t *kind2, 1572 svn_ra_session_t **ra_session, 1573 svn_client_ctx_t *ctx, 1574 const char *path_or_url1, 1575 const char *path_or_url2, 1576 const svn_opt_revision_t *revision1, 1577 const svn_opt_revision_t *revision2, 1578 const svn_opt_revision_t *peg_revision, 1579 apr_pool_t *pool) 1580{ 1581 const char *local_abspath1 = NULL; 1582 const char *local_abspath2 = NULL; 1583 const char *repos_root_url; 1584 const char *wri_abspath = NULL; 1585 svn_client__pathrev_t *resolved1; 1586 svn_client__pathrev_t *resolved2 = NULL; 1587 enum svn_opt_revision_kind peg_kind = peg_revision->kind; 1588 1589 if (!svn_path_is_url(path_or_url2)) 1590 { 1591 SVN_ERR(svn_dirent_get_absolute(&local_abspath2, path_or_url2, pool)); 1592 SVN_ERR(svn_wc__node_get_url(url2, ctx->wc_ctx, local_abspath2, 1593 pool, pool)); 1594 wri_abspath = local_abspath2; 1595 } 1596 else 1597 *url2 = apr_pstrdup(pool, path_or_url2); 1598 1599 if (!svn_path_is_url(path_or_url1)) 1600 { 1601 SVN_ERR(svn_dirent_get_absolute(&local_abspath1, path_or_url1, pool)); 1602 wri_abspath = local_abspath1; 1603 } 1604 1605 SVN_ERR(svn_client_open_ra_session2(ra_session, *url2, wri_abspath, 1606 ctx, pool, pool)); 1607 1608 /* If we are performing a pegged diff, we need to find out what our 1609 actual URLs will be. */ 1610 if (peg_kind != svn_opt_revision_unspecified 1611 || path_or_url1 == path_or_url2 1612 || local_abspath2) 1613 { 1614 svn_error_t *err; 1615 1616 err = svn_client__resolve_rev_and_url(&resolved2, 1617 *ra_session, path_or_url2, 1618 peg_revision, revision2, 1619 ctx, pool); 1620 if (err) 1621 { 1622 if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES 1623 && err->apr_err != SVN_ERR_FS_NOT_FOUND) 1624 return svn_error_trace(err); 1625 1626 svn_error_clear(err); 1627 resolved2 = NULL; 1628 } 1629 } 1630 else 1631 resolved2 = NULL; 1632 1633 if (peg_kind != svn_opt_revision_unspecified 1634 || path_or_url1 == path_or_url2 1635 || local_abspath1) 1636 { 1637 svn_error_t *err; 1638 1639 err = svn_client__resolve_rev_and_url(&resolved1, 1640 *ra_session, path_or_url1, 1641 peg_revision, revision1, 1642 ctx, pool); 1643 if (err) 1644 { 1645 if (err->apr_err != SVN_ERR_CLIENT_UNRELATED_RESOURCES 1646 && err->apr_err != SVN_ERR_FS_NOT_FOUND) 1647 return svn_error_trace(err); 1648 1649 svn_error_clear(err); 1650 resolved1 = NULL; 1651 } 1652 } 1653 else 1654 resolved1 = NULL; 1655 1656 if (resolved1) 1657 { 1658 *url1 = resolved1->url; 1659 *rev1 = resolved1->rev; 1660 } 1661 else 1662 { 1663 /* It would be nice if we could just return an error when resolving a 1664 location fails... But in many such cases we prefer diffing against 1665 an not existing location to show adds od removes (see issue #4153) */ 1666 1667 if (resolved2 1668 && (peg_kind != svn_opt_revision_unspecified 1669 || path_or_url1 == path_or_url2)) 1670 *url1 = resolved2->url; 1671 else if (! local_abspath1) 1672 *url1 = path_or_url1; 1673 else 1674 SVN_ERR(svn_wc__node_get_url(url1, ctx->wc_ctx, local_abspath1, 1675 pool, pool)); 1676 1677 SVN_ERR(svn_client__get_revision_number(rev1, NULL, ctx->wc_ctx, 1678 local_abspath1 /* may be NULL */, 1679 *ra_session, revision1, pool)); 1680 } 1681 1682 if (resolved2) 1683 { 1684 *url2 = resolved2->url; 1685 *rev2 = resolved2->rev; 1686 } 1687 else 1688 { 1689 /* It would be nice if we could just return an error when resolving a 1690 location fails... But in many such cases we prefer diffing against 1691 an not existing location to show adds od removes (see issue #4153) */ 1692 1693 if (resolved1 1694 && (peg_kind != svn_opt_revision_unspecified 1695 || path_or_url1 == path_or_url2)) 1696 *url2 = resolved1->url; 1697 /* else keep url2 */ 1698 1699 SVN_ERR(svn_client__get_revision_number(rev2, NULL, ctx->wc_ctx, 1700 local_abspath2 /* may be NULL */, 1701 *ra_session, revision2, pool)); 1702 } 1703 1704 /* Resolve revision and get path kind for the second target. */ 1705 SVN_ERR(svn_ra_reparent(*ra_session, *url2, pool)); 1706 SVN_ERR(svn_ra_check_path(*ra_session, "", *rev2, kind2, pool)); 1707 1708 /* Do the same for the first target. */ 1709 SVN_ERR(svn_ra_reparent(*ra_session, *url1, pool)); 1710 SVN_ERR(svn_ra_check_path(*ra_session, "", *rev1, kind1, pool)); 1711 1712 /* Either both URLs must exist at their respective revisions, 1713 * or one of them may be missing from one side of the diff. */ 1714 if (*kind1 == svn_node_none && *kind2 == svn_node_none) 1715 { 1716 if (strcmp(*url1, *url2) == 0) 1717 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1718 _("Diff target '%s' was not found in the " 1719 "repository at revisions '%ld' and '%ld'"), 1720 *url1, *rev1, *rev2); 1721 else 1722 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1723 _("Diff targets '%s' and '%s' were not found " 1724 "in the repository at revisions '%ld' and " 1725 "'%ld'"), 1726 *url1, *url2, *rev1, *rev2); 1727 } 1728 else if (*kind1 == svn_node_none) 1729 SVN_ERR(check_diff_target_exists(*url1, *rev2, *rev1, *ra_session, pool)); 1730 else if (*kind2 == svn_node_none) 1731 SVN_ERR(check_diff_target_exists(*url2, *rev1, *rev2, *ra_session, pool)); 1732 1733 SVN_ERR(svn_ra_get_repos_root2(*ra_session, &repos_root_url, pool)); 1734 1735 /* Choose useful anchors and targets for our two URLs. */ 1736 *anchor1 = *url1; 1737 *anchor2 = *url2; 1738 *target1 = ""; 1739 *target2 = ""; 1740 1741 /* If none of the targets is the repository root open the parent directory 1742 to allow describing replacement of the target itself */ 1743 if (strcmp(*url1, repos_root_url) != 0 1744 && strcmp(*url2, repos_root_url) != 0) 1745 { 1746 svn_node_kind_t ignored_kind; 1747 svn_error_t *err; 1748 1749 svn_uri_split(anchor1, target1, *url1, pool); 1750 svn_uri_split(anchor2, target2, *url2, pool); 1751 1752 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool)); 1753 1754 /* We might not have the necessary rights to read the root now. 1755 (It is ok to pass a revision here where the node doesn't exist) */ 1756 err = svn_ra_check_path(*ra_session, "", *rev1, &ignored_kind, pool); 1757 1758 if (err && (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN 1759 || err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED)) 1760 { 1761 svn_error_clear(err); 1762 1763 /* Ok, lets undo the reparent... 1764 1765 We can't report replacements this way, but at least we can 1766 report changes on the descendants */ 1767 1768 *anchor1 = svn_path_url_add_component2(*anchor1, *target1, pool); 1769 *anchor2 = svn_path_url_add_component2(*anchor2, *target2, pool); 1770 *target1 = ""; 1771 *target2 = ""; 1772 1773 SVN_ERR(svn_ra_reparent(*ra_session, *anchor1, pool)); 1774 } 1775 else 1776 SVN_ERR(err); 1777 } 1778 1779 return SVN_NO_ERROR; 1780} 1781 1782/* A Theoretical Note From Ben, regarding do_diff(). 1783 1784 This function is really svn_client_diff7(). If you read the public 1785 API description for svn_client_diff7(), it sounds quite Grand. It 1786 sounds really generalized and abstract and beautiful: that it will 1787 diff any two paths, be they working-copy paths or URLs, at any two 1788 revisions. 1789 1790 Now, the *reality* is that we have exactly three 'tools' for doing 1791 diffing, and thus this routine is built around the use of the three 1792 tools. Here they are, for clarity: 1793 1794 - svn_wc_diff: assumes both paths are the same wcpath. 1795 compares wcpath@BASE vs. wcpath@WORKING 1796 1797 - svn_wc_get_diff_editor: compares some URL@REV vs. wcpath@WORKING 1798 1799 - svn_client__get_diff_editor: compares some URL1@REV1 vs. URL2@REV2 1800 1801 Since Subversion 1.8 we also have a variant of svn_wc_diff called 1802 svn_client__arbitrary_nodes_diff, that allows handling WORKING-WORKING 1803 comparisons between nodes in the working copy. 1804 1805 So the truth of the matter is, if the caller's arguments can't be 1806 pigeonholed into one of these use-cases, we currently bail with a 1807 friendly apology. 1808 1809 Perhaps someday a brave soul will truly make svn_client_diff7() 1810 perfectly general. For now, we live with the 90% case. Certainly, 1811 the commandline client only calls this function in legal ways. 1812 When there are other users of svn_client.h, maybe this will become 1813 a more pressing issue. 1814 */ 1815 1816/* Return a "you can't do that" error, optionally wrapping another 1817 error CHILD_ERR. */ 1818static svn_error_t * 1819unsupported_diff_error(svn_error_t *child_err) 1820{ 1821 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, child_err, 1822 _("Sorry, svn_client_diff7 was called in a way " 1823 "that is not yet supported")); 1824} 1825 1826/* Perform a diff between two working-copy paths. 1827 1828 PATH1 and PATH2 are both working copy paths. REVISION1 and 1829 REVISION2 are their respective revisions. 1830 1831 For now, require PATH1=PATH2, REVISION1='base', REVISION2='working', 1832 otherwise return an error. 1833 1834 Anchor DIFF_PROCESSOR at the requested diff targets. 1835 1836 All other options are the same as those passed to svn_client_diff7(). */ 1837static svn_error_t * 1838diff_wc_wc(const char *path1, 1839 const svn_opt_revision_t *revision1, 1840 const char *path2, 1841 const svn_opt_revision_t *revision2, 1842 svn_depth_t depth, 1843 svn_boolean_t ignore_ancestry, 1844 const apr_array_header_t *changelists, 1845 const svn_diff_tree_processor_t *diff_processor, 1846 svn_client_ctx_t *ctx, 1847 apr_pool_t *result_pool, 1848 apr_pool_t *scratch_pool) 1849{ 1850 const char *abspath1; 1851 1852 SVN_ERR_ASSERT(! svn_path_is_url(path1)); 1853 SVN_ERR_ASSERT(! svn_path_is_url(path2)); 1854 1855 SVN_ERR(svn_dirent_get_absolute(&abspath1, path1, scratch_pool)); 1856 1857 /* Currently we support only the case where path1 and path2 are the 1858 same path. */ 1859 if ((strcmp(path1, path2) != 0) 1860 || (! ((revision1->kind == svn_opt_revision_base) 1861 && (revision2->kind == svn_opt_revision_working)))) 1862 return unsupported_diff_error( 1863 svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 1864 _("A non-URL diff at this time must be either from " 1865 "a path's base to the same path's working version " 1866 "or between the working versions of two paths" 1867 ))); 1868 1869 SVN_ERR(svn_wc__diff7(TRUE, 1870 ctx->wc_ctx, abspath1, depth, 1871 ignore_ancestry, changelists, 1872 diff_processor, 1873 ctx->cancel_func, ctx->cancel_baton, 1874 result_pool, scratch_pool)); 1875 return SVN_NO_ERROR; 1876} 1877 1878/* Perform a diff between two repository paths. 1879 1880 PATH_OR_URL1 and PATH_OR_URL2 may be either URLs or the working copy paths. 1881 REVISION1 and REVISION2 are their respective revisions. 1882 If PEG_REVISION is specified, PATH_OR_URL2 is the path at the peg revision, 1883 and the actual two paths compared are determined by following copy 1884 history from PATH_OR_URL2. 1885 1886 If DDI is null, anchor the DIFF_PROCESSOR at the requested diff 1887 targets. (This case is used by diff-summarize.) 1888 1889 If DDI is non-null: Set DDI->orig_path_* to the two diff target URLs as 1890 resolved at the given revisions; set DDI->anchor to an anchor WC path 1891 if either of PATH_OR_URL* is given as a WC path, else to null; set 1892 DDI->session_relpath to the repository-relpath of the anchor URL for 1893 DDI->orig_path_1. Anchor the DIFF_PROCESSOR at the anchor chosen 1894 for the underlying diff implementation if the target on either side 1895 is a file, else at the actual requested targets. 1896 1897 (The choice of WC anchor implementated here for DDI->anchor appears to 1898 be: choose PATH_OR_URL2 (if it's a WC path) or else PATH_OR_URL1 (if 1899 it's a WC path); then take its parent dir unless both resolved URLs 1900 refer to directories.) 1901 1902 (For the choice of URL anchor for DDI->session_relpath, see 1903 diff_prepare_repos_repos().) 1904 1905 ### Bizarre anchoring. TODO: always anchor DIFF_PROCESSOR at the 1906 requested targets. 1907 1908 All other options are the same as those passed to svn_client_diff7(). */ 1909static svn_error_t * 1910diff_repos_repos(struct diff_driver_info_t *ddi, 1911 const char *path_or_url1, 1912 const char *path_or_url2, 1913 const svn_opt_revision_t *revision1, 1914 const svn_opt_revision_t *revision2, 1915 const svn_opt_revision_t *peg_revision, 1916 svn_depth_t depth, 1917 svn_boolean_t ignore_ancestry, 1918 svn_boolean_t text_deltas, 1919 const svn_diff_tree_processor_t *diff_processor, 1920 svn_client_ctx_t *ctx, 1921 apr_pool_t *result_pool, 1922 apr_pool_t *scratch_pool) 1923{ 1924 svn_ra_session_t *extra_ra_session; 1925 1926 const svn_ra_reporter3_t *reporter; 1927 void *reporter_baton; 1928 1929 const svn_delta_editor_t *diff_editor; 1930 void *diff_edit_baton; 1931 1932 const char *url1; 1933 const char *url2; 1934 svn_revnum_t rev1; 1935 svn_revnum_t rev2; 1936 svn_node_kind_t kind1; 1937 svn_node_kind_t kind2; 1938 const char *anchor1; 1939 const char *anchor2; 1940 const char *target1; 1941 const char *target2; 1942 svn_ra_session_t *ra_session; 1943 1944 /* Prepare info for the repos repos diff. */ 1945 SVN_ERR(diff_prepare_repos_repos(&url1, &url2, &rev1, &rev2, 1946 &anchor1, &anchor2, &target1, &target2, 1947 &kind1, &kind2, &ra_session, 1948 ctx, path_or_url1, path_or_url2, 1949 revision1, revision2, peg_revision, 1950 scratch_pool)); 1951 1952 /* Set up the repos_diff editor on BASE_PATH, if available. 1953 Otherwise, we just use "". */ 1954 1955 if (ddi) 1956 { 1957 /* Get actual URLs. */ 1958 ddi->orig_path_1 = url1; 1959 ddi->orig_path_2 = url2; 1960 1961 /* This should be moved to the diff writer 1962 - path_or_url are provided by the caller 1963 - target1 is available as *root_relpath 1964 - (kind1 != svn_node_dir || kind2 != svn_node_dir) = !*root_is_dir */ 1965 1966 if (!svn_path_is_url(path_or_url2)) 1967 ddi->anchor = path_or_url2; 1968 else if (!svn_path_is_url(path_or_url1)) 1969 ddi->anchor = path_or_url1; 1970 else 1971 ddi->anchor = NULL; 1972 1973 if (*target1 && ddi->anchor 1974 && (kind1 != svn_node_dir || kind2 != svn_node_dir)) 1975 ddi->anchor = svn_dirent_dirname(ddi->anchor, result_pool); 1976 } 1977 1978 /* The repository can bring in a new working copy, but not delete 1979 everything. Luckily our new diff handler can just be reversed. */ 1980 if (kind2 == svn_node_none) 1981 { 1982 const char *str_tmp; 1983 svn_revnum_t rev_tmp; 1984 1985 str_tmp = url2; 1986 url2 = url1; 1987 url1 = str_tmp; 1988 1989 rev_tmp = rev2; 1990 rev2 = rev1; 1991 rev1 = rev_tmp; 1992 1993 str_tmp = anchor2; 1994 anchor2 = anchor1; 1995 anchor1 = str_tmp; 1996 1997 str_tmp = target2; 1998 target2 = target1; 1999 target1 = str_tmp; 2000 2001 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, 2002 scratch_pool); 2003 } 2004 2005 /* Filter the first path component using a filter processor, until we fixed 2006 the diff processing to handle this directly */ 2007 if (!ddi) 2008 { 2009 diff_processor = svn_diff__tree_processor_filter_create( 2010 diff_processor, target1, scratch_pool); 2011 } 2012 else if ((kind1 != svn_node_file && kind2 != svn_node_file) 2013 && target1[0] != '\0') 2014 { 2015 diff_processor = svn_diff__tree_processor_filter_create( 2016 diff_processor, target1, scratch_pool); 2017 } 2018 2019 /* Now, we open an extra RA session to the correct anchor 2020 location for URL1. This is used during the editor calls to fetch file 2021 contents. */ 2022 SVN_ERR(svn_ra__dup_session(&extra_ra_session, ra_session, anchor1, 2023 scratch_pool, scratch_pool)); 2024 2025 if (ddi) 2026 { 2027 const char *repos_root_url; 2028 const char *session_url; 2029 2030 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, 2031 scratch_pool)); 2032 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, 2033 scratch_pool)); 2034 2035 ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url, 2036 session_url, 2037 result_pool); 2038 } 2039 2040 SVN_ERR(svn_client__get_diff_editor2( 2041 &diff_editor, &diff_edit_baton, 2042 extra_ra_session, depth, 2043 rev1, 2044 text_deltas, 2045 diff_processor, 2046 ctx->cancel_func, ctx->cancel_baton, 2047 scratch_pool)); 2048 2049 /* We want to switch our txn into URL2 */ 2050 SVN_ERR(svn_ra_do_diff3(ra_session, &reporter, &reporter_baton, 2051 rev2, target1, 2052 depth, ignore_ancestry, text_deltas, 2053 url2, diff_editor, diff_edit_baton, scratch_pool)); 2054 2055 /* Drive the reporter; do the diff. */ 2056 SVN_ERR(reporter->set_path(reporter_baton, "", rev1, 2057 svn_depth_infinity, 2058 FALSE, NULL, 2059 scratch_pool)); 2060 2061 return svn_error_trace( 2062 reporter->finish_report(reporter_baton, scratch_pool)); 2063} 2064 2065/* Perform a diff between a repository path and a working-copy path. 2066 2067 PATH_OR_URL1 may be either a URL or a working copy path. PATH2 is a 2068 working copy path. REVISION1 is the revision of URL1. If PEG_REVISION1 2069 is specified, then PATH_OR_URL1 is the path in the peg revision, and the 2070 actual repository path to be compared is determined by following copy 2071 history. 2072 2073 REVISION_KIND2 specifies which revision should be reported from the 2074 working copy (BASE or WORKING) 2075 2076 If REVERSE is TRUE, the diff will be reported in reverse. 2077 2078 If DDI is null, anchor the DIFF_PROCESSOR at the requested diff 2079 targets. (This case is used by diff-summarize.) 2080 2081 If DDI is non-null: Set DDI->orig_path_* to the URLs of the two diff 2082 targets as resolved at the given revisions; set DDI->anchor to a WC path 2083 anchor for PATH2; set DDI->session_relpath to the repository-relpath of 2084 the URL of that same anchor WC path. 2085 2086 All other options are the same as those passed to svn_client_diff7(). */ 2087static svn_error_t * 2088diff_repos_wc(struct diff_driver_info_t *ddi, 2089 const char *path_or_url1, 2090 const svn_opt_revision_t *revision1, 2091 const svn_opt_revision_t *peg_revision1, 2092 const char *path2, 2093 enum svn_opt_revision_kind revision2_kind, 2094 svn_boolean_t reverse, 2095 svn_depth_t depth, 2096 svn_boolean_t ignore_ancestry, 2097 const apr_array_header_t *changelists, 2098 const svn_diff_tree_processor_t *diff_processor, 2099 svn_client_ctx_t *ctx, 2100 apr_pool_t *result_pool, 2101 apr_pool_t *scratch_pool) 2102{ 2103 const char *anchor, *anchor_url, *target; 2104 svn_ra_session_t *ra_session; 2105 svn_depth_t diff_depth; 2106 const svn_ra_reporter3_t *reporter; 2107 void *reporter_baton; 2108 const svn_delta_editor_t *diff_editor; 2109 void *diff_edit_baton; 2110 svn_boolean_t rev2_is_base = (revision2_kind == svn_opt_revision_base); 2111 svn_boolean_t server_supports_depth; 2112 const char *abspath_or_url1; 2113 const char *abspath2; 2114 const char *anchor_abspath; 2115 svn_boolean_t is_copy; 2116 svn_revnum_t cf_revision; 2117 const char *cf_repos_relpath; 2118 const char *cf_repos_root_url; 2119 svn_depth_t cf_depth; 2120 const char *copy_root_abspath; 2121 const char *target_url; 2122 svn_client__pathrev_t *loc1; 2123 2124 SVN_ERR_ASSERT(! svn_path_is_url(path2)); 2125 2126 if (!svn_path_is_url(path_or_url1)) 2127 { 2128 SVN_ERR(svn_dirent_get_absolute(&abspath_or_url1, path_or_url1, 2129 scratch_pool)); 2130 } 2131 else 2132 { 2133 abspath_or_url1 = path_or_url1; 2134 } 2135 2136 SVN_ERR(svn_dirent_get_absolute(&abspath2, path2, scratch_pool)); 2137 2138 /* Check if our diff target is a copied node. */ 2139 SVN_ERR(svn_wc__node_get_origin(&is_copy, 2140 &cf_revision, 2141 &cf_repos_relpath, 2142 &cf_repos_root_url, 2143 NULL, &cf_depth, 2144 ©_root_abspath, 2145 ctx->wc_ctx, abspath2, 2146 FALSE, scratch_pool, scratch_pool)); 2147 2148 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc1, 2149 path_or_url1, abspath2, 2150 peg_revision1, revision1, 2151 ctx, scratch_pool)); 2152 2153 if (revision2_kind == svn_opt_revision_base || !is_copy) 2154 { 2155 /* Convert path_or_url1 to a URL to feed to do_diff. */ 2156 SVN_ERR(svn_wc_get_actual_target2(&anchor, &target, ctx->wc_ctx, path2, 2157 scratch_pool, scratch_pool)); 2158 2159 /* Handle the ugly case where target is ".." */ 2160 if (*target && !svn_path_is_single_path_component(target)) 2161 { 2162 anchor = svn_dirent_join(anchor, target, scratch_pool); 2163 target = ""; 2164 } 2165 2166 /* Fetch the URL of the anchor directory. */ 2167 SVN_ERR(svn_dirent_get_absolute(&anchor_abspath, anchor, scratch_pool)); 2168 SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath, 2169 scratch_pool, scratch_pool)); 2170 SVN_ERR_ASSERT(anchor_url != NULL); 2171 2172 target_url = NULL; 2173 } 2174 else /* is_copy && revision2->kind != svn_opt_revision_base */ 2175 { 2176#if 0 2177 svn_node_kind_t kind; 2178#endif 2179 /* ### Ugly hack ahead ### 2180 * 2181 * We're diffing a locally copied/moved node. 2182 * Describe the copy source to the reporter instead of the copy itself. 2183 * Doing the latter would generate a single add_directory() call to the 2184 * diff editor which results in an unexpected diff (the copy would 2185 * be shown as deleted). 2186 * 2187 * ### But if we will receive any real changes from the repositor we 2188 * will most likely fail to apply them as the wc diff editor assumes 2189 * that we have the data to which the change applies in BASE... 2190 */ 2191 2192 target_url = svn_path_url_add_component2(cf_repos_root_url, 2193 cf_repos_relpath, 2194 scratch_pool); 2195 2196#if 0 2197 /*SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, abspath2, FALSE, FALSE, 2198 scratch_pool)); 2199 2200 if (kind != svn_node_dir 2201 || strcmp(copy_root_abspath, abspath2) != 0) */ 2202#endif 2203 { 2204 /* We are looking at a subdirectory of the repository, 2205 We can describe the parent directory as the anchor.. 2206 2207 ### This 'appears to work', but that is really dumb luck 2208 ### for the simple cases in the test suite */ 2209 anchor_abspath = svn_dirent_dirname(abspath2, scratch_pool); 2210 anchor_url = svn_path_url_add_component2(cf_repos_root_url, 2211 svn_relpath_dirname( 2212 cf_repos_relpath, 2213 scratch_pool), 2214 scratch_pool); 2215 target = svn_dirent_basename(abspath2, NULL); 2216 anchor = svn_dirent_dirname(path2, scratch_pool); 2217 } 2218#if 0 2219 else 2220 { 2221 /* This code, while ok can't be enabled without causing test 2222 * failures. The repository will send some changes against 2223 * BASE for nodes that don't have BASE... 2224 */ 2225 anchor_abspath = abspath2; 2226 anchor_url = svn_path_url_add_component2(cf_repos_root_url, 2227 cf_repos_relpath, 2228 scratch_pool); 2229 anchor = path2; 2230 target = ""; 2231 } 2232#endif 2233 } 2234 2235 SVN_ERR(svn_ra_reparent(ra_session, anchor_url, scratch_pool)); 2236 2237 if (ddi) 2238 { 2239 const char *repos_root_url; 2240 2241 ddi->anchor = anchor; 2242 2243 if (!reverse) 2244 { 2245 ddi->orig_path_1 = apr_pstrdup(result_pool, loc1->url); 2246 ddi->orig_path_2 = 2247 svn_path_url_add_component2(anchor_url, target, result_pool); 2248 } 2249 else 2250 { 2251 ddi->orig_path_1 = 2252 svn_path_url_add_component2(anchor_url, target, result_pool); 2253 ddi->orig_path_2 = apr_pstrdup(result_pool, loc1->url); 2254 } 2255 2256 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, 2257 scratch_pool)); 2258 2259 ddi->session_relpath = svn_uri_skip_ancestor(repos_root_url, 2260 anchor_url, 2261 result_pool); 2262 } 2263 else 2264 { 2265 diff_processor = svn_diff__tree_processor_filter_create( 2266 diff_processor, target, scratch_pool); 2267 } 2268 2269 if (reverse) 2270 diff_processor = svn_diff__tree_processor_reverse_create(diff_processor, scratch_pool); 2271 2272 /* Use the diff editor to generate the diff. */ 2273 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, 2274 SVN_RA_CAPABILITY_DEPTH, scratch_pool)); 2275 SVN_ERR(svn_wc__get_diff_editor(&diff_editor, &diff_edit_baton, 2276 ctx->wc_ctx, 2277 anchor_abspath, 2278 target, 2279 depth, 2280 ignore_ancestry, 2281 rev2_is_base, 2282 reverse, 2283 server_supports_depth, 2284 changelists, 2285 diff_processor, 2286 ctx->cancel_func, ctx->cancel_baton, 2287 scratch_pool, scratch_pool)); 2288 2289 if (depth != svn_depth_infinity) 2290 diff_depth = depth; 2291 else 2292 diff_depth = svn_depth_unknown; 2293 2294 /* Tell the RA layer we want a delta to change our txn to URL1 */ 2295 SVN_ERR(svn_ra_do_diff3(ra_session, 2296 &reporter, &reporter_baton, 2297 loc1->rev, 2298 target, 2299 diff_depth, 2300 ignore_ancestry, 2301 TRUE, /* text_deltas */ 2302 loc1->url, 2303 diff_editor, diff_edit_baton, 2304 scratch_pool)); 2305 2306 if (is_copy && revision2_kind != svn_opt_revision_base) 2307 { 2308 /* Report the copy source. */ 2309 if (cf_depth == svn_depth_unknown) 2310 cf_depth = svn_depth_infinity; 2311 2312 /* Reporting the in-wc revision as r0, makes the repository send 2313 everything as added, which avoids using BASE for pristine information, 2314 which is not there (or unrelated) for a copy */ 2315 2316 SVN_ERR(reporter->set_path(reporter_baton, "", 2317 ignore_ancestry ? 0 : cf_revision, 2318 cf_depth, FALSE, NULL, scratch_pool)); 2319 2320 if (*target) 2321 SVN_ERR(reporter->link_path(reporter_baton, target, 2322 target_url, 2323 ignore_ancestry ? 0 : cf_revision, 2324 cf_depth, FALSE, NULL, scratch_pool)); 2325 2326 /* Finish the report to generate the diff. */ 2327 SVN_ERR(reporter->finish_report(reporter_baton, scratch_pool)); 2328 } 2329 else 2330 { 2331 /* Create a txn mirror of path2; the diff editor will print 2332 diffs in reverse. :-) */ 2333 SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, abspath2, 2334 reporter, reporter_baton, 2335 FALSE, depth, TRUE, 2336 (! server_supports_depth), 2337 FALSE, 2338 ctx->cancel_func, ctx->cancel_baton, 2339 NULL, NULL, /* notification is N/A */ 2340 scratch_pool)); 2341 } 2342 2343 return SVN_NO_ERROR; 2344} 2345 2346/* Run diff on shelf SHELF_NAME, if it exists. 2347 */ 2348static svn_error_t * 2349diff_shelf(const char *shelf_name, 2350 const char *target_abspath, 2351 svn_depth_t depth, 2352 svn_boolean_t ignore_ancestry, 2353 const svn_diff_tree_processor_t *diff_processor, 2354 svn_client_ctx_t *ctx, 2355 apr_pool_t *scratch_pool) 2356{ 2357 svn_error_t *err; 2358 svn_client__shelf_t *shelf; 2359 svn_client__shelf_version_t *shelf_version; 2360 const char *wc_relpath; 2361 2362 err = svn_client__shelf_open_existing(&shelf, 2363 shelf_name, target_abspath, 2364 ctx, scratch_pool); 2365 if (err && err->apr_err == SVN_ERR_ILLEGAL_TARGET) 2366 { 2367 svn_error_clear(err); 2368 return SVN_NO_ERROR; 2369 } 2370 else 2371 SVN_ERR(err); 2372 2373 SVN_ERR(svn_client__shelf_version_open(&shelf_version, 2374 shelf, shelf->max_version, 2375 scratch_pool, scratch_pool)); 2376 wc_relpath = svn_dirent_skip_ancestor(shelf->wc_root_abspath, target_abspath); 2377 SVN_ERR(svn_client__shelf_diff(shelf_version, wc_relpath, 2378 depth, ignore_ancestry, 2379 diff_processor, scratch_pool)); 2380 SVN_ERR(svn_client__shelf_close(shelf, scratch_pool)); 2381 2382 return SVN_NO_ERROR; 2383} 2384 2385/* Run diff on all shelves named in CHANGELISTS by a changelist name 2386 * of the form "svn:shelf:SHELF_NAME", if they exist. 2387 */ 2388static svn_error_t * 2389diff_shelves(const apr_array_header_t *changelists, 2390 const char *target_abspath, 2391 svn_depth_t depth, 2392 svn_boolean_t ignore_ancestry, 2393 const svn_diff_tree_processor_t *diff_processor, 2394 svn_client_ctx_t *ctx, 2395 apr_pool_t *scratch_pool) 2396{ 2397 static const char PREFIX[] = "svn:shelf:"; 2398 static const int PREFIX_LEN = 10; 2399 int i; 2400 2401 if (! changelists) 2402 return SVN_NO_ERROR; 2403 for (i = 0; i < changelists->nelts; i++) 2404 { 2405 const char *cl = APR_ARRAY_IDX(changelists, i, const char *); 2406 2407 if (strncmp(cl, PREFIX, PREFIX_LEN) == 0) 2408 { 2409 const char *shelf_name = cl + PREFIX_LEN; 2410 2411 SVN_ERR(diff_shelf(shelf_name, target_abspath, 2412 depth, ignore_ancestry, 2413 diff_processor, ctx, scratch_pool)); 2414 } 2415 } 2416 2417 return SVN_NO_ERROR; 2418} 2419 2420 2421/* This is basically just the guts of svn_client_diff[_summarize][_peg]6(). */ 2422static svn_error_t * 2423do_diff(diff_driver_info_t *ddi, 2424 const char *path_or_url1, 2425 const char *path_or_url2, 2426 const svn_opt_revision_t *revision1, 2427 const svn_opt_revision_t *revision2, 2428 const svn_opt_revision_t *peg_revision, 2429 svn_boolean_t no_peg_revision, 2430 svn_depth_t depth, 2431 svn_boolean_t ignore_ancestry, 2432 const apr_array_header_t *changelists, 2433 svn_boolean_t text_deltas, 2434 const svn_diff_tree_processor_t *diff_processor, 2435 svn_client_ctx_t *ctx, 2436 apr_pool_t *result_pool, 2437 apr_pool_t *scratch_pool) 2438{ 2439 svn_boolean_t is_repos1; 2440 svn_boolean_t is_repos2; 2441 2442 /* Check if paths/revisions are urls/local. */ 2443 SVN_ERR(check_paths(&is_repos1, &is_repos2, path_or_url1, path_or_url2, 2444 revision1, revision2, peg_revision)); 2445 2446 if (is_repos1) 2447 { 2448 if (is_repos2) 2449 { 2450 /* Ignores changelists. */ 2451 SVN_ERR(diff_repos_repos(ddi, 2452 path_or_url1, path_or_url2, 2453 revision1, revision2, 2454 peg_revision, depth, ignore_ancestry, 2455 text_deltas, 2456 diff_processor, ctx, 2457 result_pool, scratch_pool)); 2458 } 2459 else /* path_or_url2 is a working copy path */ 2460 { 2461 SVN_ERR(diff_repos_wc(ddi, 2462 path_or_url1, revision1, 2463 no_peg_revision ? revision1 2464 : peg_revision, 2465 path_or_url2, revision2->kind, 2466 FALSE, depth, 2467 ignore_ancestry, changelists, 2468 diff_processor, ctx, 2469 result_pool, scratch_pool)); 2470 } 2471 } 2472 else /* path_or_url1 is a working copy path */ 2473 { 2474 if (is_repos2) 2475 { 2476 SVN_ERR(diff_repos_wc(ddi, 2477 path_or_url2, revision2, 2478 no_peg_revision ? revision2 2479 : peg_revision, 2480 path_or_url1, 2481 revision1->kind, 2482 TRUE, depth, 2483 ignore_ancestry, changelists, 2484 diff_processor, ctx, 2485 result_pool, scratch_pool)); 2486 } 2487 else /* path_or_url2 is a working copy path */ 2488 { 2489 if (revision1->kind == svn_opt_revision_working 2490 && revision2->kind == svn_opt_revision_working) 2491 { 2492 const char *abspath1; 2493 const char *abspath2; 2494 2495 SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, 2496 scratch_pool)); 2497 SVN_ERR(svn_dirent_get_absolute(&abspath2, path_or_url2, 2498 scratch_pool)); 2499 2500 if (ddi) 2501 { 2502 svn_node_kind_t kind1, kind2; 2503 2504 SVN_ERR(svn_io_check_resolved_path(abspath1, &kind1, 2505 scratch_pool)); 2506 SVN_ERR(svn_io_check_resolved_path(abspath2, &kind2, 2507 scratch_pool)); 2508 if (kind1 == svn_node_dir && kind2 == svn_node_dir) 2509 { 2510 ddi->anchor = ""; 2511 } 2512 else 2513 { 2514 ddi->anchor = svn_dirent_basename(abspath1, NULL); 2515 } 2516 ddi->orig_path_1 = path_or_url1; 2517 ddi->orig_path_2 = path_or_url2; 2518 } 2519 2520 /* Ignores changelists, ignore_ancestry */ 2521 SVN_ERR(svn_client__arbitrary_nodes_diff(abspath1, abspath2, 2522 depth, 2523 diff_processor, 2524 ctx, scratch_pool)); 2525 } 2526 else 2527 { 2528 if (ddi) 2529 { 2530 ddi->anchor = path_or_url1; 2531 ddi->orig_path_1 = path_or_url1; 2532 ddi->orig_path_2 = path_or_url2; 2533 } 2534 2535 { 2536 const char *abspath1; 2537 2538 SVN_ERR(svn_dirent_get_absolute(&abspath1, path_or_url1, 2539 scratch_pool)); 2540 SVN_ERR(diff_shelves(changelists, abspath1, 2541 depth, ignore_ancestry, 2542 diff_processor, ctx, scratch_pool)); 2543 } 2544 SVN_ERR(diff_wc_wc(path_or_url1, revision1, 2545 path_or_url2, revision2, 2546 depth, ignore_ancestry, changelists, 2547 diff_processor, ctx, 2548 result_pool, scratch_pool)); 2549 } 2550 } 2551 } 2552 2553 return SVN_NO_ERROR; 2554} 2555 2556/* Initialize DWI.diff_cmd and DWI.options, 2557 * according to OPTIONS and CONFIG. CONFIG and OPTIONS may be null. 2558 * Allocate the fields in RESULT_POOL, which should be at least as long-lived 2559 * as the pool DWI itself is allocated in. 2560 */ 2561static svn_error_t * 2562create_diff_writer_info(diff_writer_info_t *dwi, 2563 const apr_array_header_t *options, 2564 apr_hash_t *config, apr_pool_t *result_pool) 2565{ 2566 const char *diff_cmd = NULL; 2567 2568 /* See if there is a diff command and/or diff arguments. */ 2569 if (config) 2570 { 2571 svn_config_t *cfg = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG); 2572 svn_config_get(cfg, &diff_cmd, SVN_CONFIG_SECTION_HELPERS, 2573 SVN_CONFIG_OPTION_DIFF_CMD, NULL); 2574 if (options == NULL) 2575 { 2576 const char *diff_extensions; 2577 svn_config_get(cfg, &diff_extensions, SVN_CONFIG_SECTION_HELPERS, 2578 SVN_CONFIG_OPTION_DIFF_EXTENSIONS, NULL); 2579 if (diff_extensions) 2580 options = svn_cstring_split(diff_extensions, " \t\n\r", TRUE, 2581 result_pool); 2582 } 2583 } 2584 2585 if (options == NULL) 2586 options = apr_array_make(result_pool, 0, sizeof(const char *)); 2587 2588 if (diff_cmd) 2589 SVN_ERR(svn_path_cstring_to_utf8(&dwi->diff_cmd, diff_cmd, 2590 result_pool)); 2591 else 2592 dwi->diff_cmd = NULL; 2593 2594 /* If there was a command, arrange options to pass to it. */ 2595 if (dwi->diff_cmd) 2596 { 2597 const char **argv = NULL; 2598 int argc = options->nelts; 2599 if (argc) 2600 { 2601 int i; 2602 argv = apr_palloc(result_pool, argc * sizeof(char *)); 2603 for (i = 0; i < argc; i++) 2604 SVN_ERR(svn_utf_cstring_to_utf8(&argv[i], 2605 APR_ARRAY_IDX(options, i, const char *), result_pool)); 2606 } 2607 dwi->options.for_external.argv = argv; 2608 dwi->options.for_external.argc = argc; 2609 } 2610 else /* No command, so arrange options for internal invocation instead. */ 2611 { 2612 dwi->options.for_internal = svn_diff_file_options_create(result_pool); 2613 SVN_ERR(svn_diff_file_options_parse(dwi->options.for_internal, 2614 options, result_pool)); 2615 } 2616 2617 return SVN_NO_ERROR; 2618} 2619 2620/* Set up *DIFF_PROCESSOR and *DDI for normal and git-style diffs (but not 2621 * summary diffs). 2622 */ 2623static svn_error_t * 2624get_diff_processor(svn_diff_tree_processor_t **diff_processor, 2625 struct diff_driver_info_t **ddi, 2626 const apr_array_header_t *options, 2627 const char *relative_to_dir, 2628 svn_boolean_t no_diff_added, 2629 svn_boolean_t no_diff_deleted, 2630 svn_boolean_t show_copies_as_adds, 2631 svn_boolean_t ignore_content_type, 2632 svn_boolean_t ignore_properties, 2633 svn_boolean_t properties_only, 2634 svn_boolean_t use_git_diff_format, 2635 svn_boolean_t pretty_print_mergeinfo, 2636 const char *header_encoding, 2637 svn_stream_t *outstream, 2638 svn_stream_t *errstream, 2639 svn_client_ctx_t *ctx, 2640 apr_pool_t *pool) 2641{ 2642 diff_writer_info_t *dwi = apr_pcalloc(pool, sizeof(*dwi)); 2643 svn_diff_tree_processor_t *processor; 2644 2645 /* setup callback and baton */ 2646 2647 SVN_ERR(create_diff_writer_info(dwi, options, 2648 ctx->config, pool)); 2649 dwi->pool = pool; 2650 dwi->outstream = outstream; 2651 dwi->errstream = errstream; 2652 dwi->header_encoding = header_encoding; 2653 2654 dwi->force_binary = ignore_content_type; 2655 dwi->ignore_properties = ignore_properties; 2656 dwi->properties_only = properties_only; 2657 dwi->relative_to_dir = relative_to_dir; 2658 dwi->use_git_diff_format = use_git_diff_format; 2659 dwi->no_diff_added = no_diff_added; 2660 dwi->no_diff_deleted = no_diff_deleted; 2661 dwi->show_copies_as_adds = show_copies_as_adds; 2662 dwi->pretty_print_mergeinfo = pretty_print_mergeinfo; 2663 2664 dwi->cancel_func = ctx->cancel_func; 2665 dwi->cancel_baton = ctx->cancel_baton; 2666 2667 dwi->ddi.wc_ctx = ctx->wc_ctx; 2668 dwi->ddi.session_relpath = NULL; 2669 dwi->ddi.anchor = NULL; 2670 2671 processor = svn_diff__tree_processor_create(dwi, pool); 2672 2673 processor->dir_added = diff_dir_added; 2674 processor->dir_changed = diff_dir_changed; 2675 processor->dir_deleted = diff_dir_deleted; 2676 2677 processor->file_added = diff_file_added; 2678 processor->file_changed = diff_file_changed; 2679 processor->file_deleted = diff_file_deleted; 2680 2681 *diff_processor = processor; 2682 *ddi = &dwi->ddi; 2683 return SVN_NO_ERROR; 2684} 2685 2686svn_error_t * 2687svn_client__get_diff_writer_svn( 2688 svn_diff_tree_processor_t **diff_processor, 2689 const char *anchor, 2690 const char *orig_path_1, 2691 const char *orig_path_2, 2692 const apr_array_header_t *options, 2693 const char *relative_to_dir, 2694 svn_boolean_t no_diff_added, 2695 svn_boolean_t no_diff_deleted, 2696 svn_boolean_t show_copies_as_adds, 2697 svn_boolean_t ignore_content_type, 2698 svn_boolean_t ignore_properties, 2699 svn_boolean_t properties_only, 2700 svn_boolean_t pretty_print_mergeinfo, 2701 const char *header_encoding, 2702 svn_stream_t *outstream, 2703 svn_stream_t *errstream, 2704 svn_client_ctx_t *ctx, 2705 apr_pool_t *pool) 2706{ 2707 struct diff_driver_info_t *ddi; 2708 2709 SVN_ERR(get_diff_processor(diff_processor, &ddi, 2710 options, 2711 relative_to_dir, 2712 no_diff_added, 2713 no_diff_deleted, 2714 show_copies_as_adds, 2715 ignore_content_type, 2716 ignore_properties, 2717 properties_only, 2718 FALSE /*use_git_diff_format*/, 2719 pretty_print_mergeinfo, 2720 header_encoding, 2721 outstream, errstream, 2722 ctx, pool)); 2723 ddi->anchor = anchor; 2724 ddi->orig_path_1 = orig_path_1; 2725 ddi->orig_path_2 = orig_path_2; 2726 return SVN_NO_ERROR; 2727} 2728 2729/*----------------------------------------------------------------------- */ 2730 2731/*** Public Interfaces. ***/ 2732 2733/* Display context diffs between two PATH/REVISION pairs. Each of 2734 these inputs will be one of the following: 2735 2736 - a repository URL at a given revision. 2737 - a working copy path, ignoring local mods. 2738 - a working copy path, including local mods. 2739 2740 We can establish a matrix that shows the nine possible types of 2741 diffs we expect to support. 2742 2743 2744 ` . DST || URL:rev | WC:base | WC:working | 2745 ` . || | | | 2746 SRC ` . || | | | 2747 ============++============+============+============+ 2748 URL:rev || (*) | (*) | (*) | 2749 || | | | 2750 || | | | 2751 || | | | 2752 ------------++------------+------------+------------+ 2753 WC:base || (*) | | 2754 || | New svn_wc_diff which | 2755 || | is smart enough to | 2756 || | handle two WC paths | 2757 ------------++------------+ and their related + 2758 WC:working || (*) | text-bases and working | 2759 || | files. This operation | 2760 || | is entirely local. | 2761 || | | 2762 ------------++------------+------------+------------+ 2763 * These cases require server communication. 2764*/ 2765svn_error_t * 2766svn_client_diff7(const apr_array_header_t *options, 2767 const char *path_or_url1, 2768 const svn_opt_revision_t *revision1, 2769 const char *path_or_url2, 2770 const svn_opt_revision_t *revision2, 2771 const char *relative_to_dir, 2772 svn_depth_t depth, 2773 svn_boolean_t ignore_ancestry, 2774 svn_boolean_t no_diff_added, 2775 svn_boolean_t no_diff_deleted, 2776 svn_boolean_t show_copies_as_adds, 2777 svn_boolean_t ignore_content_type, 2778 svn_boolean_t ignore_properties, 2779 svn_boolean_t properties_only, 2780 svn_boolean_t use_git_diff_format, 2781 svn_boolean_t pretty_print_mergeinfo, 2782 const char *header_encoding, 2783 svn_stream_t *outstream, 2784 svn_stream_t *errstream, 2785 const apr_array_header_t *changelists, 2786 svn_client_ctx_t *ctx, 2787 apr_pool_t *pool) 2788{ 2789 svn_opt_revision_t peg_revision; 2790 svn_diff_tree_processor_t *diff_processor; 2791 struct diff_driver_info_t *ddi; 2792 2793 if (ignore_properties && properties_only) 2794 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 2795 _("Cannot ignore properties and show only " 2796 "properties at the same time")); 2797 2798 /* We will never do a pegged diff from here. */ 2799 peg_revision.kind = svn_opt_revision_unspecified; 2800 2801 /* --show-copies-as-adds and --git imply --notice-ancestry */ 2802 if (show_copies_as_adds || use_git_diff_format) 2803 ignore_ancestry = FALSE; 2804 2805 SVN_ERR(get_diff_processor(&diff_processor, &ddi, 2806 options, 2807 relative_to_dir, 2808 no_diff_added, 2809 no_diff_deleted, 2810 show_copies_as_adds, 2811 ignore_content_type, 2812 ignore_properties, 2813 properties_only, 2814 use_git_diff_format, 2815 pretty_print_mergeinfo, 2816 header_encoding, 2817 outstream, errstream, 2818 ctx, pool)); 2819 2820 return svn_error_trace(do_diff(ddi, 2821 path_or_url1, path_or_url2, 2822 revision1, revision2, 2823 &peg_revision, TRUE /* no_peg_revision */, 2824 depth, ignore_ancestry, changelists, 2825 TRUE /* text_deltas */, 2826 diff_processor, ctx, pool, pool)); 2827} 2828 2829svn_error_t * 2830svn_client_diff_peg7(const apr_array_header_t *options, 2831 const char *path_or_url, 2832 const svn_opt_revision_t *peg_revision, 2833 const svn_opt_revision_t *start_revision, 2834 const svn_opt_revision_t *end_revision, 2835 const char *relative_to_dir, 2836 svn_depth_t depth, 2837 svn_boolean_t ignore_ancestry, 2838 svn_boolean_t no_diff_added, 2839 svn_boolean_t no_diff_deleted, 2840 svn_boolean_t show_copies_as_adds, 2841 svn_boolean_t ignore_content_type, 2842 svn_boolean_t ignore_properties, 2843 svn_boolean_t properties_only, 2844 svn_boolean_t use_git_diff_format, 2845 svn_boolean_t pretty_print_mergeinfo, 2846 const char *header_encoding, 2847 svn_stream_t *outstream, 2848 svn_stream_t *errstream, 2849 const apr_array_header_t *changelists, 2850 svn_client_ctx_t *ctx, 2851 apr_pool_t *pool) 2852{ 2853 svn_diff_tree_processor_t *diff_processor; 2854 struct diff_driver_info_t *ddi; 2855 2856 if (ignore_properties && properties_only) 2857 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 2858 _("Cannot ignore properties and show only " 2859 "properties at the same time")); 2860 2861 /* --show-copies-as-adds and --git imply --notice-ancestry */ 2862 if (show_copies_as_adds || use_git_diff_format) 2863 ignore_ancestry = FALSE; 2864 2865 SVN_ERR(get_diff_processor(&diff_processor, &ddi, 2866 options, 2867 relative_to_dir, 2868 no_diff_added, 2869 no_diff_deleted, 2870 show_copies_as_adds, 2871 ignore_content_type, 2872 ignore_properties, 2873 properties_only, 2874 use_git_diff_format, 2875 pretty_print_mergeinfo, 2876 header_encoding, 2877 outstream, errstream, 2878 ctx, pool)); 2879 2880 return svn_error_trace(do_diff(ddi, 2881 path_or_url, path_or_url, 2882 start_revision, end_revision, 2883 peg_revision, FALSE /* no_peg_revision */, 2884 depth, ignore_ancestry, changelists, 2885 TRUE /* text_deltas */, 2886 diff_processor, ctx, pool, pool)); 2887} 2888 2889svn_error_t * 2890svn_client_diff_summarize2(const char *path_or_url1, 2891 const svn_opt_revision_t *revision1, 2892 const char *path_or_url2, 2893 const svn_opt_revision_t *revision2, 2894 svn_depth_t depth, 2895 svn_boolean_t ignore_ancestry, 2896 const apr_array_header_t *changelists, 2897 svn_client_diff_summarize_func_t summarize_func, 2898 void *summarize_baton, 2899 svn_client_ctx_t *ctx, 2900 apr_pool_t *pool) 2901{ 2902 svn_diff_tree_processor_t *diff_processor; 2903 svn_opt_revision_t peg_revision; 2904 2905 /* We will never do a pegged diff from here. */ 2906 peg_revision.kind = svn_opt_revision_unspecified; 2907 2908 SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor, 2909 summarize_func, summarize_baton, 2910 pool, pool)); 2911 2912 return svn_error_trace(do_diff(NULL, 2913 path_or_url1, path_or_url2, 2914 revision1, revision2, 2915 &peg_revision, TRUE /* no_peg_revision */, 2916 depth, ignore_ancestry, changelists, 2917 FALSE /* text_deltas */, 2918 diff_processor, ctx, pool, pool)); 2919} 2920 2921svn_error_t * 2922svn_client_diff_summarize_peg2(const char *path_or_url, 2923 const svn_opt_revision_t *peg_revision, 2924 const svn_opt_revision_t *start_revision, 2925 const svn_opt_revision_t *end_revision, 2926 svn_depth_t depth, 2927 svn_boolean_t ignore_ancestry, 2928 const apr_array_header_t *changelists, 2929 svn_client_diff_summarize_func_t summarize_func, 2930 void *summarize_baton, 2931 svn_client_ctx_t *ctx, 2932 apr_pool_t *pool) 2933{ 2934 svn_diff_tree_processor_t *diff_processor; 2935 2936 SVN_ERR(svn_client__get_diff_summarize_callbacks(&diff_processor, 2937 summarize_func, summarize_baton, 2938 pool, pool)); 2939 2940 return svn_error_trace(do_diff(NULL, 2941 path_or_url, path_or_url, 2942 start_revision, end_revision, 2943 peg_revision, FALSE /* no_peg_revision */, 2944 depth, ignore_ancestry, changelists, 2945 FALSE /* text_deltas */, 2946 diff_processor, ctx, pool, pool)); 2947} 2948 2949