1/* 2 * diff_local.c: comparing local trees with each other 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24/* ==================================================================== */ 25 26 27 28/*** Includes. ***/ 29 30#include <apr_strings.h> 31#include <apr_pools.h> 32#include <apr_hash.h> 33#include "svn_hash.h" 34#include "svn_types.h" 35#include "svn_wc.h" 36#include "svn_diff.h" 37#include "svn_client.h" 38#include "svn_string.h" 39#include "svn_error.h" 40#include "svn_dirent_uri.h" 41#include "svn_io.h" 42#include "svn_pools.h" 43#include "svn_props.h" 44#include "svn_sorts.h" 45#include "svn_subst.h" 46#include "client.h" 47 48#include "private/svn_sorts_private.h" 49#include "private/svn_wc_private.h" 50#include "private/svn_diff_tree.h" 51 52#include "svn_private_config.h" 53 54 55/* Try to get properties for LOCAL_ABSPATH and return them in the property 56 * hash *PROPS. If there are no properties because LOCAL_ABSPATH is not 57 * versioned, return an empty property hash. */ 58static svn_error_t * 59get_props(apr_hash_t **props, 60 const char *local_abspath, 61 svn_wc_context_t *wc_ctx, 62 apr_pool_t *result_pool, 63 apr_pool_t *scratch_pool) 64{ 65 svn_error_t *err; 66 67 err = svn_wc_prop_list2(props, wc_ctx, local_abspath, result_pool, 68 scratch_pool); 69 if (err) 70 { 71 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND || 72 err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY) 73 { 74 svn_error_clear(err); 75 *props = apr_hash_make(result_pool); 76 77 /* ### Apply autoprops, like 'svn add' would? */ 78 } 79 else 80 return svn_error_trace(err); 81 } 82 83 return SVN_NO_ERROR; 84} 85 86/* Forward declaration */ 87static svn_error_t * 88do_file_diff(const char *left_abspath, 89 const char *right_abspath, 90 const char *left_root_abspath, 91 const char *right_root_abspath, 92 svn_boolean_t left_only, 93 svn_boolean_t right_only, 94 void *parent_baton, 95 const svn_diff_tree_processor_t *diff_processor, 96 svn_client_ctx_t *ctx, 97 apr_pool_t *scratch_pool); 98 99/* Forward declaration */ 100static svn_error_t * 101do_dir_diff(const char *left_abspath, 102 const char *right_abspath, 103 const char *left_root_abspath, 104 const char *right_root_abspath, 105 svn_boolean_t left_only, 106 svn_boolean_t right_only, 107 svn_boolean_t left_before_right, 108 svn_depth_t depth, 109 void *parent_baton, 110 const svn_diff_tree_processor_t *diff_processor, 111 svn_client_ctx_t *ctx, 112 apr_pool_t *scratch_pool); 113 114/* Produce a diff of depth DEPTH between two arbitrary directories at 115 * LEFT_ABSPATH1 and RIGHT_ABSPATH2, using the provided diff callbacks 116 * to show file changes and, for versioned nodes, property changes. 117 * 118 * Report paths as relative from LEFT_ROOT_ABSPATH/RIGHT_ROOT_ABSPATH. 119 * 120 * If LEFT_ONLY is TRUE, only the left source exists (= everything will 121 * be reported as deleted). If RIGHT_ONLY is TRUE, only the right source 122 * exists (= everything will be reported as added). 123 * 124 * If LEFT_BEFORE_RIGHT is TRUE and left and right are unrelated, left is 125 * reported first. If false, right is reported first. (This is to allow 126 * producing a proper inverse diff). 127 * 128 * Walk the sources according to depth, and report with parent baton 129 * PARENT_BATON. */ 130static svn_error_t * 131inner_dir_diff(const char *left_abspath, 132 const char *right_abspath, 133 const char *left_root_abspath, 134 const char *right_root_abspath, 135 svn_boolean_t left_only, 136 svn_boolean_t right_only, 137 svn_boolean_t left_before_right, 138 svn_depth_t depth, 139 void *parent_baton, 140 const svn_diff_tree_processor_t *diff_processor, 141 svn_client_ctx_t *ctx, 142 apr_pool_t *scratch_pool) 143{ 144 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 145 apr_hash_t *left_dirents; 146 apr_hash_t *right_dirents; 147 apr_array_header_t *sorted_dirents; 148 svn_error_t *err; 149 svn_depth_t depth_below_here; 150 int i; 151 152 SVN_ERR_ASSERT(depth >= svn_depth_files && depth <= svn_depth_infinity); 153 154 if (!right_only) 155 { 156 err = svn_io_get_dirents3(&left_dirents, left_abspath, FALSE, 157 scratch_pool, iterpool); 158 159 if (err && (APR_STATUS_IS_ENOENT(err->apr_err) 160 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 161 { 162 svn_error_clear(err); 163 left_dirents = apr_hash_make(scratch_pool); 164 right_only = TRUE; 165 } 166 else 167 SVN_ERR(err); 168 } 169 else 170 left_dirents = apr_hash_make(scratch_pool); 171 172 if (!left_only) 173 { 174 err = svn_io_get_dirents3(&right_dirents, right_abspath, FALSE, 175 scratch_pool, iterpool); 176 177 if (err && (APR_STATUS_IS_ENOENT(err->apr_err) 178 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))) 179 { 180 svn_error_clear(err); 181 right_dirents = apr_hash_make(scratch_pool); 182 left_only = TRUE; 183 } 184 else 185 SVN_ERR(err); 186 } 187 else 188 right_dirents = apr_hash_make(scratch_pool); 189 190 if (left_only && right_only) 191 return SVN_NO_ERROR; /* Somebody deleted the directory?? */ 192 193 if (depth != svn_depth_infinity) 194 depth_below_here = svn_depth_empty; 195 else 196 depth_below_here = svn_depth_infinity; 197 198 sorted_dirents = svn_sort__hash(apr_hash_merge(iterpool, left_dirents, 199 right_dirents, NULL, NULL), 200 svn_sort_compare_items_as_paths, 201 scratch_pool); 202 203 for (i = 0; i < sorted_dirents->nelts; i++) 204 { 205 svn_sort__item_t* elt = &APR_ARRAY_IDX(sorted_dirents, i, svn_sort__item_t); 206 svn_io_dirent2_t *left_dirent; 207 svn_io_dirent2_t *right_dirent; 208 const char *child_left_abspath; 209 const char *child_right_abspath; 210 211 svn_pool_clear(iterpool); 212 213 if (ctx->cancel_func) 214 SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 215 216 if (svn_wc_is_adm_dir(elt->key, iterpool)) 217 continue; 218 219 left_dirent = right_only ? NULL : svn_hash_gets(left_dirents, elt->key); 220 right_dirent = left_only ? NULL : svn_hash_gets(right_dirents, elt->key); 221 222 child_left_abspath = svn_dirent_join(left_abspath, elt->key, iterpool); 223 child_right_abspath = svn_dirent_join(right_abspath, elt->key, iterpool); 224 225 if (((left_dirent == NULL) != (right_dirent == NULL)) 226 || (left_dirent->kind != right_dirent->kind)) 227 { 228 /* Report delete and/or add */ 229 if (left_dirent && left_before_right) 230 { 231 if (left_dirent->kind == svn_node_file) 232 SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath, 233 left_root_abspath, right_root_abspath, 234 TRUE, FALSE, parent_baton, 235 diff_processor, ctx, iterpool)); 236 else if (depth >= svn_depth_immediates) 237 SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath, 238 left_root_abspath, right_root_abspath, 239 TRUE, FALSE, left_before_right, 240 depth_below_here, parent_baton, 241 diff_processor, ctx, iterpool)); 242 } 243 244 if (right_dirent) 245 { 246 if (right_dirent->kind == svn_node_file) 247 SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath, 248 left_root_abspath, right_root_abspath, 249 FALSE, TRUE, parent_baton, 250 diff_processor, ctx, iterpool)); 251 else if (depth >= svn_depth_immediates) 252 SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath, 253 left_root_abspath, right_root_abspath, 254 FALSE, TRUE, left_before_right, 255 depth_below_here, parent_baton, 256 diff_processor, ctx, iterpool)); 257 } 258 259 if (left_dirent && !left_before_right) 260 { 261 if (left_dirent->kind == svn_node_file) 262 SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath, 263 left_root_abspath, right_root_abspath, 264 TRUE, FALSE, parent_baton, 265 diff_processor, ctx, iterpool)); 266 else if (depth >= svn_depth_immediates) 267 SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath, 268 left_root_abspath, right_root_abspath, 269 TRUE, FALSE, left_before_right, 270 depth_below_here, parent_baton, 271 diff_processor, ctx, iterpool)); 272 } 273 } 274 else if (left_dirent->kind == svn_node_file) 275 { 276 /* Perform file-file diff */ 277 SVN_ERR(do_file_diff(child_left_abspath, child_right_abspath, 278 left_root_abspath, right_root_abspath, 279 FALSE, FALSE, parent_baton, 280 diff_processor, ctx, iterpool)); 281 } 282 else if (depth >= svn_depth_immediates) 283 { 284 /* Perform dir-dir diff */ 285 SVN_ERR(do_dir_diff(child_left_abspath, child_right_abspath, 286 left_root_abspath, right_root_abspath, 287 FALSE, FALSE, left_before_right, 288 depth_below_here, parent_baton, 289 diff_processor, ctx, iterpool)); 290 } 291 } 292 293 return SVN_NO_ERROR; 294} 295 296/* Translates *LEFT_ABSPATH to a temporary file if PROPS specify that the 297 file needs translation. *LEFT_ABSPATH is updated to point to a file that 298 lives at least as long as RESULT_POOL when translation is necessary. 299 Otherwise the value is not updated */ 300static svn_error_t * 301translate_if_necessary(const char **local_abspath, 302 apr_hash_t *props, 303 svn_cancel_func_t cancel_func, 304 void *cancel_baton, 305 apr_pool_t *result_pool, 306 apr_pool_t *scratch_pool) 307{ 308 const svn_string_t *eol_style_val; 309 const svn_string_t *keywords_val; 310 svn_subst_eol_style_t eol_style; 311 const char *eol; 312 apr_hash_t *keywords; 313 svn_stream_t *contents; 314 svn_stream_t *dst; 315 316 /* if (svn_hash_gets(props, SVN_PROP_SPECIAL)) 317 ### TODO: Implement */ 318 319 eol_style_val = svn_hash_gets(props, SVN_PROP_EOL_STYLE); 320 keywords_val = svn_hash_gets(props, SVN_PROP_KEYWORDS); 321 322 if (eol_style_val) 323 svn_subst_eol_style_from_value(&eol_style, &eol, eol_style_val->data); 324 else 325 { 326 eol = NULL; 327 eol_style = svn_subst_eol_style_none; 328 } 329 330 if (keywords_val) 331 SVN_ERR(svn_subst_build_keywords3(&keywords, keywords_val->data, 332 APR_STRINGIFY(SVN_INVALID_REVNUM), 333 "", "", 0, "", scratch_pool)); 334 else 335 keywords = NULL; 336 337 if (!svn_subst_translation_required(eol_style, eol, keywords, FALSE, FALSE)) 338 return SVN_NO_ERROR; 339 340 SVN_ERR(svn_stream_open_readonly(&contents, *local_abspath, 341 scratch_pool, scratch_pool)); 342 343 SVN_ERR(svn_stream_open_unique(&dst, local_abspath, NULL, 344 svn_io_file_del_on_pool_cleanup, 345 result_pool, scratch_pool)); 346 347 dst = svn_subst_stream_translated(dst, eol, TRUE /* repair */, 348 keywords, FALSE /* expand */, 349 scratch_pool); 350 351 SVN_ERR(svn_stream_copy3(contents, dst, cancel_func, cancel_baton, 352 scratch_pool)); 353 354 return SVN_NO_ERROR; 355} 356 357/* Handles reporting of a file for inner_dir_diff */ 358static svn_error_t * 359do_file_diff(const char *left_abspath, 360 const char *right_abspath, 361 const char *left_root_abspath, 362 const char *right_root_abspath, 363 svn_boolean_t left_only, 364 svn_boolean_t right_only, 365 void *parent_baton, 366 const svn_diff_tree_processor_t *diff_processor, 367 svn_client_ctx_t *ctx, 368 apr_pool_t *scratch_pool) 369{ 370 const char *relpath; 371 svn_diff_source_t *left_source; 372 svn_diff_source_t *right_source; 373 svn_boolean_t skip = FALSE; 374 apr_hash_t *left_props; 375 apr_hash_t *right_props; 376 void *file_baton; 377 378 relpath = svn_dirent_skip_ancestor(left_root_abspath, left_abspath); 379 380 if (! right_only) 381 left_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); 382 else 383 left_source = NULL; 384 385 if (! left_only) 386 right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); 387 else 388 right_source = NULL; 389 390 SVN_ERR(diff_processor->file_opened(&file_baton, &skip, 391 relpath, 392 left_source, 393 right_source, 394 NULL /* copyfrom_source */, 395 parent_baton, 396 diff_processor, 397 scratch_pool, 398 scratch_pool)); 399 400 if (skip) 401 return SVN_NO_ERROR; 402 403 if (! right_only) 404 { 405 SVN_ERR(get_props(&left_props, left_abspath, ctx->wc_ctx, 406 scratch_pool, scratch_pool)); 407 408 /* We perform a mimetype detection to avoid diffing binary files 409 for textual changes.*/ 410 if (! svn_hash_gets(left_props, SVN_PROP_MIME_TYPE)) 411 { 412 const char *mime_type; 413 414 /* ### Use libmagic magic? */ 415 SVN_ERR(svn_io_detect_mimetype2(&mime_type, left_abspath, 416 ctx->mimetypes_map, scratch_pool)); 417 418 if (mime_type) 419 svn_hash_sets(left_props, SVN_PROP_MIME_TYPE, 420 svn_string_create(mime_type, scratch_pool)); 421 } 422 423 SVN_ERR(translate_if_necessary(&left_abspath, left_props, 424 ctx->cancel_func, ctx->cancel_baton, 425 scratch_pool, scratch_pool)); 426 } 427 else 428 left_props = NULL; 429 430 if (! left_only) 431 { 432 SVN_ERR(get_props(&right_props, right_abspath, ctx->wc_ctx, 433 scratch_pool, scratch_pool)); 434 435 /* We perform a mimetype detection to avoid diffing binary files 436 for textual changes.*/ 437 if (! svn_hash_gets(right_props, SVN_PROP_MIME_TYPE)) 438 { 439 const char *mime_type; 440 441 /* ### Use libmagic magic? */ 442 SVN_ERR(svn_io_detect_mimetype2(&mime_type, right_abspath, 443 ctx->mimetypes_map, scratch_pool)); 444 445 if (mime_type) 446 svn_hash_sets(right_props, SVN_PROP_MIME_TYPE, 447 svn_string_create(mime_type, scratch_pool)); 448 } 449 450 SVN_ERR(translate_if_necessary(&right_abspath, right_props, 451 ctx->cancel_func, ctx->cancel_baton, 452 scratch_pool, scratch_pool)); 453 454 } 455 else 456 right_props = NULL; 457 458 if (left_only) 459 { 460 SVN_ERR(diff_processor->file_deleted(relpath, 461 left_source, 462 left_abspath, 463 left_props, 464 file_baton, 465 diff_processor, 466 scratch_pool)); 467 } 468 else if (right_only) 469 { 470 SVN_ERR(diff_processor->file_added(relpath, 471 NULL /* copyfrom_source */, 472 right_source, 473 NULL /* copyfrom_file */, 474 right_abspath, 475 NULL /* copyfrom_props */, 476 right_props, 477 file_baton, 478 diff_processor, 479 scratch_pool)); 480 } 481 else 482 { 483 /* ### Perform diff -> close/changed */ 484 svn_boolean_t same; 485 apr_array_header_t *prop_changes; 486 487 SVN_ERR(svn_io_files_contents_same_p(&same, left_abspath, right_abspath, 488 scratch_pool)); 489 490 SVN_ERR(svn_prop_diffs(&prop_changes, right_props, left_props, 491 scratch_pool)); 492 493 if (!same || prop_changes->nelts > 0) 494 { 495 SVN_ERR(diff_processor->file_changed(relpath, 496 left_source, 497 right_source, 498 same ? NULL : left_abspath, 499 same ? NULL : right_abspath, 500 left_props, 501 right_props, 502 !same, 503 prop_changes, 504 file_baton, 505 diff_processor, 506 scratch_pool)); 507 } 508 else 509 { 510 SVN_ERR(diff_processor->file_closed(relpath, 511 left_source, 512 right_source, 513 file_baton, 514 diff_processor, 515 scratch_pool)); 516 } 517 } 518 return SVN_NO_ERROR; 519} 520 521 522/* Handles reporting of a directory and its children for inner_dir_diff */ 523static svn_error_t * 524do_dir_diff(const char *left_abspath, 525 const char *right_abspath, 526 const char *left_root_abspath, 527 const char *right_root_abspath, 528 svn_boolean_t left_only, 529 svn_boolean_t right_only, 530 svn_boolean_t left_before_right, 531 svn_depth_t depth, 532 void *parent_baton, 533 const svn_diff_tree_processor_t *diff_processor, 534 svn_client_ctx_t *ctx, 535 apr_pool_t *scratch_pool) 536{ 537 const char *relpath; 538 svn_diff_source_t *left_source; 539 svn_diff_source_t *right_source; 540 svn_boolean_t skip = FALSE; 541 svn_boolean_t skip_children = FALSE; 542 void *dir_baton; 543 apr_hash_t *left_props; 544 apr_hash_t *right_props; 545 546 relpath = svn_dirent_skip_ancestor(left_root_abspath, left_abspath); 547 548 if (! right_only) 549 { 550 left_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); 551 SVN_ERR(get_props(&left_props, left_abspath, ctx->wc_ctx, 552 scratch_pool, scratch_pool)); 553 } 554 else 555 { 556 left_source = NULL; 557 left_props = NULL; 558 } 559 560 if (! left_only) 561 { 562 right_source = svn_diff__source_create(SVN_INVALID_REVNUM, scratch_pool); 563 SVN_ERR(get_props(&right_props, right_abspath, ctx->wc_ctx, 564 scratch_pool, scratch_pool)); 565 } 566 else 567 { 568 right_source = NULL; 569 right_props = NULL; 570 } 571 572 SVN_ERR(diff_processor->dir_opened(&dir_baton, &skip, &skip_children, 573 relpath, 574 left_source, 575 right_source, 576 NULL /* copyfrom_source */, 577 parent_baton, 578 diff_processor, 579 scratch_pool, scratch_pool)); 580 581 if (!skip_children) 582 { 583 if (depth >= svn_depth_files) 584 SVN_ERR(inner_dir_diff(left_abspath, right_abspath, 585 left_root_abspath, right_root_abspath, 586 left_only, right_only, 587 left_before_right, depth, 588 dir_baton, 589 diff_processor, ctx, scratch_pool)); 590 } 591 else if (skip) 592 return SVN_NO_ERROR; 593 594 if (left_props && right_props) 595 { 596 apr_array_header_t *prop_diffs; 597 598 SVN_ERR(svn_prop_diffs(&prop_diffs, right_props, left_props, 599 scratch_pool)); 600 601 if (prop_diffs->nelts) 602 { 603 SVN_ERR(diff_processor->dir_changed(relpath, 604 left_source, 605 right_source, 606 left_props, 607 right_props, 608 prop_diffs, 609 dir_baton, 610 diff_processor, 611 scratch_pool)); 612 return SVN_NO_ERROR; 613 } 614 } 615 616 if (left_source && right_source) 617 { 618 SVN_ERR(diff_processor->dir_closed(relpath, 619 left_source, 620 right_source, 621 dir_baton, 622 diff_processor, 623 scratch_pool)); 624 } 625 else if (left_source) 626 { 627 SVN_ERR(diff_processor->dir_deleted(relpath, 628 left_source, 629 left_props, 630 dir_baton, 631 diff_processor, 632 scratch_pool)); 633 } 634 else 635 { 636 SVN_ERR(diff_processor->dir_added(relpath, 637 NULL /* copyfrom_source */, 638 right_source, 639 NULL /* copyfrom_props */, 640 right_props, 641 dir_baton, 642 diff_processor, 643 scratch_pool)); 644 } 645 646 return SVN_NO_ERROR; 647} 648 649svn_error_t * 650svn_client__arbitrary_nodes_diff(const char *left_abspath, 651 const char *right_abspath, 652 svn_depth_t depth, 653 const svn_diff_tree_processor_t *diff_processor, 654 svn_client_ctx_t *ctx, 655 apr_pool_t *scratch_pool) 656{ 657 svn_node_kind_t left_kind; 658 svn_node_kind_t right_kind; 659 const char *left_root_abspath = left_abspath; 660 const char *right_root_abspath = right_abspath; 661 svn_boolean_t left_before_right = TRUE; /* Future argument? */ 662 663 if (depth == svn_depth_unknown) 664 depth = svn_depth_infinity; 665 666 SVN_ERR(svn_io_check_resolved_path(left_abspath, &left_kind, scratch_pool)); 667 SVN_ERR(svn_io_check_resolved_path(right_abspath, &right_kind, scratch_pool)); 668 669 if (left_kind == svn_node_dir && right_kind == svn_node_dir) 670 { 671 SVN_ERR(do_dir_diff(left_abspath, right_abspath, 672 left_root_abspath, right_root_abspath, 673 FALSE, FALSE, left_before_right, 674 depth, NULL /* parent_baton */, 675 diff_processor, ctx, scratch_pool)); 676 } 677 else if (left_kind == svn_node_file && right_kind == svn_node_file) 678 { 679 SVN_ERR(do_file_diff(left_abspath, right_abspath, 680 left_root_abspath, right_root_abspath, 681 FALSE, FALSE, 682 NULL /* parent_baton */, 683 diff_processor, ctx, scratch_pool)); 684 } 685 else if (left_kind == svn_node_file || left_kind == svn_node_dir 686 || right_kind == svn_node_file || right_kind == svn_node_dir) 687 { 688 /* The root is added/deleted/replaced. Report delete and/or add. */ 689 if (left_before_right) 690 { 691 if (left_kind == svn_node_file) 692 SVN_ERR(do_file_diff(left_abspath, right_abspath, 693 left_root_abspath, right_root_abspath, 694 TRUE, FALSE, NULL /* parent_baton */, 695 diff_processor, ctx, scratch_pool)); 696 else if (left_kind == svn_node_dir) 697 SVN_ERR(do_dir_diff(left_abspath, right_abspath, 698 left_root_abspath, right_root_abspath, 699 TRUE, FALSE, left_before_right, 700 depth, NULL /* parent_baton */, 701 diff_processor, ctx, scratch_pool)); 702 } 703 704 if (right_kind == svn_node_file) 705 SVN_ERR(do_file_diff(left_abspath, right_abspath, 706 left_root_abspath, right_root_abspath, 707 FALSE, TRUE, NULL /* parent_baton */, 708 diff_processor, ctx, scratch_pool)); 709 else if (right_kind == svn_node_dir) 710 SVN_ERR(do_dir_diff(left_abspath, right_abspath, 711 left_root_abspath, right_root_abspath, 712 FALSE, TRUE, left_before_right, 713 depth, NULL /* parent_baton */, 714 diff_processor, ctx, scratch_pool)); 715 716 if (! left_before_right) 717 { 718 if (left_kind == svn_node_file) 719 SVN_ERR(do_file_diff(left_abspath, right_abspath, 720 left_root_abspath, right_root_abspath, 721 TRUE, FALSE, NULL /* parent_baton */, 722 diff_processor, ctx, scratch_pool)); 723 else if (left_kind == svn_node_dir) 724 SVN_ERR(do_dir_diff(left_abspath, right_abspath, 725 left_root_abspath, right_root_abspath, 726 TRUE, FALSE, left_before_right, 727 depth, NULL /* parent_baton */, 728 diff_processor, ctx, scratch_pool)); 729 } 730 } 731 else 732 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 733 _("'%s' is not a file or directory"), 734 svn_dirent_local_style( 735 (left_kind == svn_node_none) 736 ? left_abspath 737 : right_abspath, 738 scratch_pool)); 739 740 return SVN_NO_ERROR; 741} 742