repos_diff.c revision 362181
1/* 2 * repos_diff.c -- The diff editor for comparing two repository versions 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/* This code uses an editor driven by a tree delta between two 25 * repository revisions (REV1 and REV2). For each file encountered in 26 * the delta the editor constructs two temporary files, one for each 27 * revision. This necessitates a separate request for the REV1 version 28 * of the file when the delta shows the file being modified or 29 * deleted. Files that are added by the delta do not require a 30 * separate request, the REV1 version is empty and the delta is 31 * sufficient to construct the REV2 version. When both versions of 32 * each file have been created the diff callback is invoked to display 33 * the difference between the two files. */ 34 35#include <apr_uri.h> 36#include <apr_md5.h> 37#include <assert.h> 38 39#include "svn_checksum.h" 40#include "svn_hash.h" 41#include "svn_wc.h" 42#include "svn_pools.h" 43#include "svn_dirent_uri.h" 44#include "svn_path.h" 45#include "svn_io.h" 46#include "svn_props.h" 47#include "svn_private_config.h" 48 49#include "client.h" 50 51#include "private/svn_subr_private.h" 52#include "private/svn_wc_private.h" 53#include "private/svn_editor.h" 54#include "private/svn_sorts_private.h" 55 56/* Overall crawler editor baton. */ 57struct edit_baton { 58 /* The passed depth */ 59 svn_depth_t depth; 60 61 /* The result processor */ 62 const svn_diff_tree_processor_t *processor; 63 64 /* RA_SESSION is the open session for making requests to the RA layer */ 65 svn_ra_session_t *ra_session; 66 67 /* The rev1 from the '-r Rev1:Rev2' command line option */ 68 svn_revnum_t revision; 69 70 /* The rev2 from the '-r Rev1:Rev2' option, specifically set by 71 set_target_revision(). */ 72 svn_revnum_t target_revision; 73 74 /* The path to a temporary empty file used for add/delete 75 differences. The path is cached here so that it can be reused, 76 since all empty files are the same. */ 77 const char *empty_file; 78 79 /* Empty hash used for adds. */ 80 apr_hash_t *empty_hash; 81 82 /* Whether to report text deltas */ 83 svn_boolean_t text_deltas; 84 85 /* A callback used to see if the client wishes to cancel the running 86 operation. */ 87 svn_cancel_func_t cancel_func; 88 89 /* A baton to pass to the cancellation callback. */ 90 void *cancel_baton; 91 92 apr_pool_t *pool; 93}; 94 95typedef struct deleted_path_notify_t 96{ 97 svn_node_kind_t kind; 98 svn_wc_notify_action_t action; 99 svn_wc_notify_state_t state; 100 svn_boolean_t tree_conflicted; 101} deleted_path_notify_t; 102 103/* Directory level baton. 104 */ 105struct dir_baton { 106 /* Gets set if the directory is added rather than replaced/unchanged. */ 107 svn_boolean_t added; 108 109 /* Gets set if this operation caused a tree-conflict on this directory 110 * (does not show tree-conflicts persisting from before this operation). */ 111 svn_boolean_t tree_conflicted; 112 113 /* If TRUE, this node is skipped entirely. 114 * This is used to skip all children of a tree-conflicted 115 * directory without setting TREE_CONFLICTED to TRUE everywhere. */ 116 svn_boolean_t skip; 117 118 /* If TRUE, all children of this directory are skipped. */ 119 svn_boolean_t skip_children; 120 121 /* The path of the directory within the repository */ 122 const char *path; 123 124 /* The baton for the parent directory, or null if this is the root of the 125 hierarchy to be compared. */ 126 struct dir_baton *parent_baton; 127 128 /* The overall crawler editor baton. */ 129 struct edit_baton *edit_baton; 130 131 /* A cache of any property changes (svn_prop_t) received for this dir. */ 132 apr_array_header_t *propchanges; 133 134 /* Boolean indicating whether a node property was changed */ 135 svn_boolean_t has_propchange; 136 137 /* Baton for svn_diff_tree_processor_t */ 138 void *pdb; 139 svn_diff_source_t *left_source; 140 svn_diff_source_t *right_source; 141 142 /* The pool passed in by add_dir, open_dir, or open_root. 143 Also, the pool this dir baton is allocated in. */ 144 apr_pool_t *pool; 145 146 /* Base revision of directory. */ 147 svn_revnum_t base_revision; 148 149 /* Number of users of baton. Its pool will be destroyed 0 */ 150 int users; 151}; 152 153/* File level baton. 154 */ 155struct file_baton { 156 /* Reference to parent baton */ 157 struct dir_baton *parent_baton; 158 159 /* Gets set if the file is added rather than replaced. */ 160 svn_boolean_t added; 161 162 /* Gets set if this operation caused a tree-conflict on this file 163 * (does not show tree-conflicts persisting from before this operation). */ 164 svn_boolean_t tree_conflicted; 165 166 /* If TRUE, this node is skipped entirely. 167 * This is currently used to skip all children of a tree-conflicted 168 * directory. */ 169 svn_boolean_t skip; 170 171 /* The path of the file within the repository */ 172 const char *path; 173 174 /* The path and APR file handle to the temporary file that contains the 175 first repository version. Also, the pristine-property list of 176 this file. */ 177 const char *path_start_revision; 178 apr_hash_t *pristine_props; 179 svn_revnum_t base_revision; 180 181 /* The path and APR file handle to the temporary file that contains the 182 second repository version. These fields are set when processing 183 textdelta and file deletion, and will be NULL if there's no 184 textual difference between the two revisions. */ 185 const char *path_end_revision; 186 187 /* APPLY_HANDLER/APPLY_BATON represent the delta application baton. */ 188 svn_txdelta_window_handler_t apply_handler; 189 void *apply_baton; 190 191 /* The overall crawler editor baton. */ 192 struct edit_baton *edit_baton; 193 194 /* Holds the checksum of the start revision file */ 195 svn_checksum_t *start_md5_checksum; 196 197 /* Holds the resulting md5 digest of a textdelta transform */ 198 unsigned char result_digest[APR_MD5_DIGESTSIZE]; 199 svn_checksum_t *result_md5_checksum; 200 201 /* A cache of any property changes (svn_prop_t) received for this file. */ 202 apr_array_header_t *propchanges; 203 204 /* Boolean indicating whether a node property was changed */ 205 svn_boolean_t has_propchange; 206 207 /* Baton for svn_diff_tree_processor_t */ 208 void *pfb; 209 svn_diff_source_t *left_source; 210 svn_diff_source_t *right_source; 211 212 /* The pool passed in by add_file or open_file. 213 Also, the pool this file_baton is allocated in. */ 214 apr_pool_t *pool; 215}; 216 217/* Create a new directory baton for PATH in POOL. ADDED is set if 218 * this directory is being added rather than replaced. PARENT_BATON is 219 * the baton of the parent directory (or NULL if this is the root of 220 * the comparison hierarchy). The directory and its parent may or may 221 * not exist in the working copy. EDIT_BATON is the overall crawler 222 * editor baton. 223 */ 224static struct dir_baton * 225make_dir_baton(const char *path, 226 struct dir_baton *parent_baton, 227 struct edit_baton *edit_baton, 228 svn_boolean_t added, 229 svn_revnum_t base_revision, 230 apr_pool_t *result_pool) 231{ 232 apr_pool_t *dir_pool = svn_pool_create(result_pool); 233 struct dir_baton *dir_baton = apr_pcalloc(dir_pool, sizeof(*dir_baton)); 234 235 dir_baton->parent_baton = parent_baton; 236 dir_baton->edit_baton = edit_baton; 237 dir_baton->added = added; 238 dir_baton->tree_conflicted = FALSE; 239 dir_baton->skip = FALSE; 240 dir_baton->skip_children = FALSE; 241 dir_baton->pool = dir_pool; 242 dir_baton->path = apr_pstrdup(dir_pool, path); 243 dir_baton->propchanges = apr_array_make(dir_pool, 8, sizeof(svn_prop_t)); 244 dir_baton->base_revision = base_revision; 245 dir_baton->users++; 246 247 if (parent_baton) 248 parent_baton->users++; 249 250 return dir_baton; 251} 252 253/* New function. Called by everyone who has a reference when done */ 254static svn_error_t * 255release_dir(struct dir_baton *db) 256{ 257 assert(db->users > 0); 258 259 db->users--; 260 if (db->users) 261 return SVN_NO_ERROR; 262 263 { 264 struct dir_baton *pb = db->parent_baton; 265 266 svn_pool_destroy(db->pool); 267 268 if (pb != NULL) 269 SVN_ERR(release_dir(pb)); 270 } 271 272 return SVN_NO_ERROR; 273} 274 275/* Create a new file baton for PATH in POOL, which is a child of 276 * directory PARENT_PATH. ADDED is set if this file is being added 277 * rather than replaced. EDIT_BATON is a pointer to the global edit 278 * baton. 279 */ 280static struct file_baton * 281make_file_baton(const char *path, 282 struct dir_baton *parent_baton, 283 svn_boolean_t added, 284 apr_pool_t *result_pool) 285{ 286 apr_pool_t *file_pool = svn_pool_create(result_pool); 287 struct file_baton *file_baton = apr_pcalloc(file_pool, sizeof(*file_baton)); 288 289 file_baton->parent_baton = parent_baton; 290 file_baton->edit_baton = parent_baton->edit_baton; 291 file_baton->added = added; 292 file_baton->tree_conflicted = FALSE; 293 file_baton->skip = FALSE; 294 file_baton->pool = file_pool; 295 file_baton->path = apr_pstrdup(file_pool, path); 296 file_baton->propchanges = apr_array_make(file_pool, 8, sizeof(svn_prop_t)); 297 file_baton->base_revision = parent_baton->edit_baton->revision; 298 299 parent_baton->users++; 300 301 return file_baton; 302} 303 304/* Get revision FB->base_revision of the file described by FB from the 305 * repository, through FB->edit_baton->ra_session. 306 * 307 * Unless PROPS_ONLY is true: 308 * Set FB->path_start_revision to the path of a new temporary file containing 309 * the file's text. 310 * Set FB->start_md5_checksum to that file's MD-5 checksum. 311 * Install a pool cleanup handler on FB->pool to delete the file. 312 * 313 * Always: 314 * Set FB->pristine_props to a new hash containing the file's properties. 315 * 316 * Allocate all results in FB->pool. 317 */ 318static svn_error_t * 319get_file_from_ra(struct file_baton *fb, 320 svn_boolean_t props_only, 321 apr_pool_t *scratch_pool) 322{ 323 if (! props_only) 324 { 325 svn_stream_t *fstream; 326 327 SVN_ERR(svn_stream_open_unique(&fstream, &(fb->path_start_revision), 328 NULL, svn_io_file_del_on_pool_cleanup, 329 fb->pool, scratch_pool)); 330 331 fstream = svn_stream_checksummed2(fstream, NULL, &fb->start_md5_checksum, 332 svn_checksum_md5, TRUE, fb->pool); 333 334 /* Retrieve the file and its properties */ 335 SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session, 336 fb->path, 337 fb->base_revision, 338 fstream, NULL, 339 &(fb->pristine_props), 340 fb->pool)); 341 SVN_ERR(svn_stream_close(fstream)); 342 } 343 else 344 { 345 SVN_ERR(svn_ra_get_file(fb->edit_baton->ra_session, 346 fb->path, 347 fb->base_revision, 348 NULL, NULL, 349 &(fb->pristine_props), 350 fb->pool)); 351 } 352 353 return SVN_NO_ERROR; 354} 355 356/* Remove every no-op property change from CHANGES: that is, remove every 357 entry in which the target value is the same as the value of the 358 corresponding property in PRISTINE_PROPS. 359 360 Issue #3657 'dav update report handler in skelta mode can cause 361 spurious conflicts'. When communicating with the repository via ra_serf, 362 the change_dir_prop and change_file_prop svn_delta_editor_t 363 callbacks are called (obviously) when a directory or file property has 364 changed between the start and end of the edit. Less obvious however, 365 is that these callbacks may be made describing *all* of the properties 366 on FILE_BATON->PATH when using the DAV providers, not just the change(s). 367 (Specifically ra_serf does it for diff/merge/update/switch). 368 369 This means that the change_[file|dir]_prop svn_delta_editor_t callbacks 370 may be made where there are no property changes (i.e. a noop change of 371 NAME from VALUE to VALUE). Normally this is harmless, but during a 372 merge it can result in spurious conflicts if the WC's pristine property 373 NAME has a value other than VALUE. In an ideal world the mod_dav_svn 374 update report handler, when in 'skelta' mode and describing changes to 375 a path on which a property has changed, wouldn't ask the client to later 376 fetch all properties and figure out what has changed itself. The server 377 already knows which properties have changed! 378 379 Regardless, such a change is not yet implemented, and even when it is, 380 the client should DTRT with regard to older servers which behave this 381 way. Hence this little hack: We populate FILE_BATON->PROPCHANGES only 382 with *actual* property changes. 383 384 See https://issues.apache.org/jira/browse/SVN-3657#desc9 and 385 http://svn.haxx.se/dev/archive-2010-08/0351.shtml for more details. 386 */ 387static svn_error_t * 388remove_non_prop_changes(apr_hash_t *pristine_props, 389 apr_array_header_t *changes) 390{ 391 int i; 392 393 /* For added nodes, there is nothing to filter. */ 394 if (apr_hash_count(pristine_props) == 0) 395 return SVN_NO_ERROR; 396 397 for (i = 0; i < changes->nelts; i++) 398 { 399 svn_prop_t *change = &APR_ARRAY_IDX(changes, i, svn_prop_t); 400 401 if (change->value) 402 { 403 const svn_string_t *old_val = svn_hash_gets(pristine_props, 404 change->name); 405 406 if (old_val && svn_string_compare(old_val, change->value)) 407 { 408 /* Remove the matching change and re-check the current index */ 409 SVN_ERR(svn_sort__array_delete2(changes, i, 1)); 410 i--; 411 } 412 } 413 } 414 return SVN_NO_ERROR; 415} 416 417/* Get the empty file associated with the edit baton. This is cached so 418 * that it can be reused, all empty files are the same. 419 */ 420static svn_error_t * 421get_empty_file(struct edit_baton *eb, 422 const char **empty_file_path) 423{ 424 /* Create the file if it does not exist */ 425 /* Note that we tried to use /dev/null in r857294, but 426 that won't work on Windows: it's impossible to stat NUL */ 427 if (!eb->empty_file) 428 SVN_ERR(svn_io_open_unique_file3(NULL, &(eb->empty_file), NULL, 429 svn_io_file_del_on_pool_cleanup, 430 eb->pool, eb->pool)); 431 432 *empty_file_path = eb->empty_file; 433 434 return SVN_NO_ERROR; 435} 436 437/* An svn_delta_editor_t function. */ 438static svn_error_t * 439set_target_revision(void *edit_baton, 440 svn_revnum_t target_revision, 441 apr_pool_t *pool) 442{ 443 struct edit_baton *eb = edit_baton; 444 445 eb->target_revision = target_revision; 446 return SVN_NO_ERROR; 447} 448 449/* An svn_delta_editor_t function. The root of the comparison hierarchy */ 450static svn_error_t * 451open_root(void *edit_baton, 452 svn_revnum_t base_revision, 453 apr_pool_t *pool, 454 void **root_baton) 455{ 456 struct edit_baton *eb = edit_baton; 457 struct dir_baton *db = make_dir_baton("", NULL, eb, FALSE, base_revision, 458 eb->pool); 459 460 db->left_source = svn_diff__source_create(eb->revision, db->pool); 461 db->right_source = svn_diff__source_create(eb->target_revision, db->pool); 462 463 SVN_ERR(eb->processor->dir_opened(&db->pdb, 464 &db->skip, 465 &db->skip_children, 466 "", 467 db->left_source, 468 db->right_source, 469 NULL, 470 NULL, 471 eb->processor, 472 db->pool, 473 db->pool /* scratch_pool */)); 474 475 *root_baton = db; 476 return SVN_NO_ERROR; 477} 478 479/* Compare a file being deleted against an empty file. 480 */ 481static svn_error_t * 482diff_deleted_file(const char *path, 483 struct dir_baton *db, 484 apr_pool_t *scratch_pool) 485{ 486 struct edit_baton *eb = db->edit_baton; 487 struct file_baton *fb = make_file_baton(path, db, FALSE, scratch_pool); 488 svn_boolean_t skip = FALSE; 489 svn_diff_source_t *left_source = svn_diff__source_create(eb->revision, 490 scratch_pool); 491 492 if (eb->cancel_func) 493 SVN_ERR(eb->cancel_func(eb->cancel_baton)); 494 495 SVN_ERR(eb->processor->file_opened(&fb->pfb, &skip, path, 496 left_source, 497 NULL /* right_source */, 498 NULL /* copyfrom_source */, 499 db->pdb, 500 eb->processor, 501 scratch_pool, scratch_pool)); 502 503 if (eb->cancel_func) 504 SVN_ERR(eb->cancel_func(eb->cancel_baton)); 505 506 if (skip) 507 return SVN_NO_ERROR; 508 509 SVN_ERR(get_file_from_ra(fb, ! eb->text_deltas, scratch_pool)); 510 511 SVN_ERR(eb->processor->file_deleted(fb->path, 512 left_source, 513 fb->path_start_revision, 514 fb->pristine_props, 515 fb->pfb, 516 eb->processor, 517 scratch_pool)); 518 519 return SVN_NO_ERROR; 520} 521 522/* Recursively walk tree rooted at DIR (at EB->revision) in the repository, 523 * reporting all children as deleted. Part of a workaround for issue 2333. 524 * 525 * DIR is a repository path relative to the URL in EB->ra_session. EB is 526 * the overall crawler editor baton. EB->revision must be a valid revision 527 * number, not SVN_INVALID_REVNUM. Use EB->cancel_func (if not null) with 528 * EB->cancel_baton for cancellation. 529 */ 530/* ### TODO: Handle depth. */ 531static svn_error_t * 532diff_deleted_dir(const char *path, 533 struct dir_baton *pb, 534 apr_pool_t *scratch_pool) 535{ 536 struct edit_baton *eb = pb->edit_baton; 537 struct dir_baton *db; 538 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 539 svn_boolean_t skip = FALSE; 540 svn_boolean_t skip_children = FALSE; 541 apr_hash_t *dirents = NULL; 542 apr_hash_t *left_props = NULL; 543 svn_diff_source_t *left_source = svn_diff__source_create(eb->revision, 544 scratch_pool); 545 db = make_dir_baton(path, pb, pb->edit_baton, FALSE, SVN_INVALID_REVNUM, 546 scratch_pool); 547 548 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(eb->revision)); 549 550 if (eb->cancel_func) 551 SVN_ERR(eb->cancel_func(eb->cancel_baton)); 552 553 SVN_ERR(eb->processor->dir_opened(&db->pdb, &skip, &skip_children, 554 path, 555 left_source, 556 NULL /* right_source */, 557 NULL /* copyfrom_source */, 558 pb->pdb, 559 eb->processor, 560 scratch_pool, iterpool)); 561 562 if (!skip || !skip_children) 563 SVN_ERR(svn_ra_get_dir2(eb->ra_session, 564 skip_children ? NULL : &dirents, 565 NULL, 566 skip ? NULL : &left_props, 567 path, 568 eb->revision, 569 SVN_DIRENT_KIND, 570 scratch_pool)); 571 572 /* The "old" dir will be skipped by the repository report. If required, 573 * crawl it recursively, diffing each file against the empty file. This 574 * is a workaround for issue 2333 "'svn diff URL1 URL2' not reverse of 575 * 'svn diff URL2 URL1'". */ 576 if (! skip_children) 577 { 578 apr_hash_index_t *hi; 579 580 for (hi = apr_hash_first(scratch_pool, dirents); hi; 581 hi = apr_hash_next(hi)) 582 { 583 const char *child_path; 584 const char *name = apr_hash_this_key(hi); 585 svn_dirent_t *dirent = apr_hash_this_val(hi); 586 587 svn_pool_clear(iterpool); 588 589 child_path = svn_relpath_join(path, name, iterpool); 590 591 if (dirent->kind == svn_node_file) 592 { 593 SVN_ERR(diff_deleted_file(child_path, db, iterpool)); 594 } 595 else if (dirent->kind == svn_node_dir) 596 { 597 SVN_ERR(diff_deleted_dir(child_path, db, iterpool)); 598 } 599 } 600 } 601 602 if (! skip) 603 { 604 SVN_ERR(eb->processor->dir_deleted(path, 605 left_source, 606 left_props, 607 db->pdb, 608 eb->processor, 609 scratch_pool)); 610 } 611 612 SVN_ERR(release_dir(db)); 613 614 svn_pool_destroy(iterpool); 615 return SVN_NO_ERROR; 616} 617 618/* An svn_delta_editor_t function. */ 619static svn_error_t * 620delete_entry(const char *path, 621 svn_revnum_t base_revision, 622 void *parent_baton, 623 apr_pool_t *pool) 624{ 625 struct dir_baton *pb = parent_baton; 626 struct edit_baton *eb = pb->edit_baton; 627 svn_node_kind_t kind; 628 apr_pool_t *scratch_pool; 629 630 /* Process skips. */ 631 if (pb->skip_children) 632 return SVN_NO_ERROR; 633 634 scratch_pool = svn_pool_create(eb->pool); 635 636 /* We need to know if this is a directory or a file */ 637 SVN_ERR(svn_ra_check_path(eb->ra_session, path, eb->revision, &kind, 638 scratch_pool)); 639 640 switch (kind) 641 { 642 case svn_node_file: 643 { 644 SVN_ERR(diff_deleted_file(path, pb, scratch_pool)); 645 break; 646 } 647 case svn_node_dir: 648 { 649 SVN_ERR(diff_deleted_dir(path, pb, scratch_pool)); 650 break; 651 } 652 default: 653 break; 654 } 655 656 svn_pool_destroy(scratch_pool); 657 658 return SVN_NO_ERROR; 659} 660 661/* An svn_delta_editor_t function. */ 662static svn_error_t * 663add_directory(const char *path, 664 void *parent_baton, 665 const char *copyfrom_path, 666 svn_revnum_t copyfrom_revision, 667 apr_pool_t *pool, 668 void **child_baton) 669{ 670 struct dir_baton *pb = parent_baton; 671 struct edit_baton *eb = pb->edit_baton; 672 struct dir_baton *db; 673 674 /* ### TODO: support copyfrom? */ 675 676 db = make_dir_baton(path, pb, eb, TRUE, SVN_INVALID_REVNUM, pb->pool); 677 *child_baton = db; 678 679 /* Skip *everything* within a newly tree-conflicted directory, 680 * and directories the children of which should be skipped. */ 681 if (pb->skip_children) 682 { 683 db->skip = TRUE; 684 db->skip_children = TRUE; 685 return SVN_NO_ERROR; 686 } 687 688 db->right_source = svn_diff__source_create(eb->target_revision, 689 db->pool); 690 691 SVN_ERR(eb->processor->dir_opened(&db->pdb, 692 &db->skip, 693 &db->skip_children, 694 db->path, 695 NULL, 696 db->right_source, 697 NULL /* copyfrom_source */, 698 pb->pdb, 699 eb->processor, 700 db->pool, db->pool)); 701 702 return SVN_NO_ERROR; 703} 704 705/* An svn_delta_editor_t function. */ 706static svn_error_t * 707open_directory(const char *path, 708 void *parent_baton, 709 svn_revnum_t base_revision, 710 apr_pool_t *pool, 711 void **child_baton) 712{ 713 struct dir_baton *pb = parent_baton; 714 struct edit_baton *eb = pb->edit_baton; 715 struct dir_baton *db; 716 717 db = make_dir_baton(path, pb, eb, FALSE, base_revision, pb->pool); 718 719 *child_baton = db; 720 721 /* Process Skips. */ 722 if (pb->skip_children) 723 { 724 db->skip = TRUE; 725 db->skip_children = TRUE; 726 return SVN_NO_ERROR; 727 } 728 729 db->left_source = svn_diff__source_create(eb->revision, db->pool); 730 db->right_source = svn_diff__source_create(eb->target_revision, db->pool); 731 732 SVN_ERR(eb->processor->dir_opened(&db->pdb, 733 &db->skip, &db->skip_children, 734 path, 735 db->left_source, 736 db->right_source, 737 NULL /* copyfrom */, 738 pb ? pb->pdb : NULL, 739 eb->processor, 740 db->pool, db->pool)); 741 742 return SVN_NO_ERROR; 743} 744 745 746/* An svn_delta_editor_t function. */ 747static svn_error_t * 748add_file(const char *path, 749 void *parent_baton, 750 const char *copyfrom_path, 751 svn_revnum_t copyfrom_revision, 752 apr_pool_t *pool, 753 void **file_baton) 754{ 755 struct dir_baton *pb = parent_baton; 756 struct edit_baton *eb = pb->edit_baton; 757 struct file_baton *fb; 758 759 /* ### TODO: support copyfrom? */ 760 761 fb = make_file_baton(path, pb, TRUE, pb->pool); 762 *file_baton = fb; 763 764 /* Process Skips. */ 765 if (pb->skip_children) 766 { 767 fb->skip = TRUE; 768 return SVN_NO_ERROR; 769 } 770 771 fb->pristine_props = pb->edit_baton->empty_hash; 772 773 fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool); 774 775 SVN_ERR(eb->processor->file_opened(&fb->pfb, 776 &fb->skip, 777 path, 778 NULL, 779 fb->right_source, 780 NULL /* copy source */, 781 pb->pdb, 782 eb->processor, 783 fb->pool, fb->pool)); 784 785 return SVN_NO_ERROR; 786} 787 788/* An svn_delta_editor_t function. */ 789static svn_error_t * 790open_file(const char *path, 791 void *parent_baton, 792 svn_revnum_t base_revision, 793 apr_pool_t *pool, 794 void **file_baton) 795{ 796 struct dir_baton *pb = parent_baton; 797 struct file_baton *fb; 798 struct edit_baton *eb = pb->edit_baton; 799 fb = make_file_baton(path, pb, FALSE, pb->pool); 800 *file_baton = fb; 801 802 /* Process Skips. */ 803 if (pb->skip_children) 804 { 805 fb->skip = TRUE; 806 return SVN_NO_ERROR; 807 } 808 809 fb->base_revision = base_revision; 810 811 fb->left_source = svn_diff__source_create(eb->revision, fb->pool); 812 fb->right_source = svn_diff__source_create(eb->target_revision, fb->pool); 813 814 SVN_ERR(eb->processor->file_opened(&fb->pfb, 815 &fb->skip, 816 path, 817 fb->left_source, 818 fb->right_source, 819 NULL /* copy source */, 820 pb->pdb, 821 eb->processor, 822 fb->pool, fb->pool)); 823 824 return SVN_NO_ERROR; 825} 826 827/* Do the work of applying the text delta. */ 828static svn_error_t * 829window_handler(svn_txdelta_window_t *window, 830 void *window_baton) 831{ 832 struct file_baton *fb = window_baton; 833 834 SVN_ERR(fb->apply_handler(window, fb->apply_baton)); 835 836 if (!window) 837 { 838 fb->result_md5_checksum = svn_checksum__from_digest_md5( 839 fb->result_digest, 840 fb->pool); 841 } 842 843 return SVN_NO_ERROR; 844} 845 846/* Implements svn_stream_lazyopen_func_t. */ 847static svn_error_t * 848lazy_open_source(svn_stream_t **stream, 849 void *baton, 850 apr_pool_t *result_pool, 851 apr_pool_t *scratch_pool) 852{ 853 struct file_baton *fb = baton; 854 855 SVN_ERR(svn_stream_open_readonly(stream, fb->path_start_revision, 856 result_pool, scratch_pool)); 857 858 return SVN_NO_ERROR; 859} 860 861/* Implements svn_stream_lazyopen_func_t. */ 862static svn_error_t * 863lazy_open_result(svn_stream_t **stream, 864 void *baton, 865 apr_pool_t *result_pool, 866 apr_pool_t *scratch_pool) 867{ 868 struct file_baton *fb = baton; 869 870 SVN_ERR(svn_stream_open_unique(stream, &fb->path_end_revision, NULL, 871 svn_io_file_del_on_pool_cleanup, 872 result_pool, scratch_pool)); 873 874 return SVN_NO_ERROR; 875} 876 877/* An svn_delta_editor_t function. */ 878static svn_error_t * 879apply_textdelta(void *file_baton, 880 const char *base_md5_digest, 881 apr_pool_t *pool, 882 svn_txdelta_window_handler_t *handler, 883 void **handler_baton) 884{ 885 struct file_baton *fb = file_baton; 886 svn_stream_t *src_stream; 887 svn_stream_t *result_stream; 888 apr_pool_t *scratch_pool = fb->pool; 889 890 /* Skip *everything* within a newly tree-conflicted directory. */ 891 if (fb->skip) 892 { 893 *handler = svn_delta_noop_window_handler; 894 *handler_baton = NULL; 895 return SVN_NO_ERROR; 896 } 897 898 /* If we're not sending file text, then ignore any that we receive. */ 899 if (! fb->edit_baton->text_deltas) 900 { 901 /* Supply valid paths to indicate there is a text change. */ 902 SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_start_revision)); 903 SVN_ERR(get_empty_file(fb->edit_baton, &fb->path_end_revision)); 904 905 *handler = svn_delta_noop_window_handler; 906 *handler_baton = NULL; 907 908 return SVN_NO_ERROR; 909 } 910 911 /* We need the expected pristine file, so go get it */ 912 if (!fb->added) 913 SVN_ERR(get_file_from_ra(fb, FALSE, scratch_pool)); 914 else 915 SVN_ERR(get_empty_file(fb->edit_baton, &(fb->path_start_revision))); 916 917 SVN_ERR_ASSERT(fb->path_start_revision != NULL); 918 919 if (base_md5_digest != NULL) 920 { 921 svn_checksum_t *base_md5_checksum; 922 923 SVN_ERR(svn_checksum_parse_hex(&base_md5_checksum, svn_checksum_md5, 924 base_md5_digest, scratch_pool)); 925 926 if (!svn_checksum_match(base_md5_checksum, fb->start_md5_checksum)) 927 return svn_error_trace(svn_checksum_mismatch_err( 928 base_md5_checksum, 929 fb->start_md5_checksum, 930 scratch_pool, 931 _("Base checksum mismatch for '%s'"), 932 fb->path)); 933 } 934 935 /* Open the file to be used as the base for second revision */ 936 src_stream = svn_stream_lazyopen_create(lazy_open_source, fb, TRUE, 937 scratch_pool); 938 939 /* Open the file that will become the second revision after applying the 940 text delta, it starts empty */ 941 result_stream = svn_stream_lazyopen_create(lazy_open_result, fb, TRUE, 942 scratch_pool); 943 944 svn_txdelta_apply(src_stream, 945 result_stream, 946 fb->result_digest, 947 fb->path, fb->pool, 948 &(fb->apply_handler), &(fb->apply_baton)); 949 950 *handler = window_handler; 951 *handler_baton = file_baton; 952 953 return SVN_NO_ERROR; 954} 955 956/* An svn_delta_editor_t function. When the file is closed we have a temporary 957 * file containing a pristine version of the repository file. This can 958 * be compared against the working copy. 959 * 960 * ### Ignore TEXT_CHECKSUM for now. Someday we can use it to verify 961 * ### the integrity of the file being diffed. Done efficiently, this 962 * ### would probably involve calculating the checksum as the data is 963 * ### received, storing the final checksum in the file_baton, and 964 * ### comparing against it here. 965 */ 966static svn_error_t * 967close_file(void *file_baton, 968 const char *expected_md5_digest, 969 apr_pool_t *pool) 970{ 971 struct file_baton *fb = file_baton; 972 struct dir_baton *pb = fb->parent_baton; 973 struct edit_baton *eb = fb->edit_baton; 974 apr_pool_t *scratch_pool; 975 976 /* Skip *everything* within a newly tree-conflicted directory. */ 977 if (fb->skip) 978 { 979 svn_pool_destroy(fb->pool); 980 SVN_ERR(release_dir(pb)); 981 return SVN_NO_ERROR; 982 } 983 984 scratch_pool = fb->pool; 985 986 if (expected_md5_digest && eb->text_deltas) 987 { 988 svn_checksum_t *expected_md5_checksum; 989 990 SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5, 991 expected_md5_digest, scratch_pool)); 992 993 if (!svn_checksum_match(expected_md5_checksum, fb->result_md5_checksum)) 994 return svn_error_trace(svn_checksum_mismatch_err( 995 expected_md5_checksum, 996 fb->result_md5_checksum, 997 pool, 998 _("Checksum mismatch for '%s'"), 999 fb->path)); 1000 } 1001 1002 if (fb->added || fb->path_end_revision || fb->has_propchange) 1003 { 1004 apr_hash_t *right_props; 1005 1006 if (!fb->added && !fb->pristine_props) 1007 { 1008 /* We didn't receive a text change, so we have no pristine props. 1009 Retrieve just the props now. */ 1010 SVN_ERR(get_file_from_ra(fb, TRUE, scratch_pool)); 1011 } 1012 1013 if (fb->pristine_props) 1014 SVN_ERR(remove_non_prop_changes(fb->pristine_props, fb->propchanges)); 1015 1016 right_props = svn_prop__patch(fb->pristine_props, fb->propchanges, 1017 fb->pool); 1018 1019 if (fb->added) 1020 SVN_ERR(eb->processor->file_added(fb->path, 1021 NULL /* copyfrom_src */, 1022 fb->right_source, 1023 NULL /* copyfrom_file */, 1024 fb->path_end_revision, 1025 NULL /* copyfrom_props */, 1026 right_props, 1027 fb->pfb, 1028 eb->processor, 1029 fb->pool)); 1030 else 1031 SVN_ERR(eb->processor->file_changed(fb->path, 1032 fb->left_source, 1033 fb->right_source, 1034 fb->path_end_revision 1035 ? fb->path_start_revision 1036 : NULL, 1037 fb->path_end_revision, 1038 fb->pristine_props, 1039 right_props, 1040 (fb->path_end_revision != NULL), 1041 fb->propchanges, 1042 fb->pfb, 1043 eb->processor, 1044 fb->pool)); 1045 } 1046 1047 svn_pool_destroy(fb->pool); /* Destroy file and scratch pool */ 1048 1049 SVN_ERR(release_dir(pb)); 1050 1051 return SVN_NO_ERROR; 1052} 1053 1054/* Report any accumulated prop changes via the 'dir_props_changed' callback, 1055 * and then call the 'dir_closed' callback. Notify about any deleted paths 1056 * within this directory that have not already been notified, and then about 1057 * this directory itself (unless it was added, in which case the notification 1058 * was done at that time). 1059 * 1060 * An svn_delta_editor_t function. */ 1061static svn_error_t * 1062close_directory(void *dir_baton, 1063 apr_pool_t *pool) 1064{ 1065 struct dir_baton *db = dir_baton; 1066 struct edit_baton *eb = db->edit_baton; 1067 apr_pool_t *scratch_pool; 1068 apr_hash_t *pristine_props; 1069 svn_boolean_t send_changed = FALSE; 1070 1071 scratch_pool = db->pool; 1072 1073 if ((db->has_propchange || db->added) && !db->skip) 1074 { 1075 if (db->added) 1076 { 1077 pristine_props = eb->empty_hash; 1078 } 1079 else 1080 { 1081 SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, &pristine_props, 1082 db->path, db->base_revision, 0, scratch_pool)); 1083 } 1084 1085 if (db->propchanges->nelts > 0) 1086 { 1087 SVN_ERR(remove_non_prop_changes(pristine_props, db->propchanges)); 1088 } 1089 1090 if (db->propchanges->nelts > 0 || db->added) 1091 { 1092 apr_hash_t *right_props; 1093 1094 right_props = svn_prop__patch(pristine_props, db->propchanges, 1095 scratch_pool); 1096 1097 if (db->added) 1098 { 1099 SVN_ERR(eb->processor->dir_added(db->path, 1100 NULL /* copyfrom */, 1101 db->right_source, 1102 NULL /* copyfrom props */, 1103 right_props, 1104 db->pdb, 1105 eb->processor, 1106 db->pool)); 1107 } 1108 else 1109 { 1110 SVN_ERR(eb->processor->dir_changed(db->path, 1111 db->left_source, 1112 db->right_source, 1113 pristine_props, 1114 right_props, 1115 db->propchanges, 1116 db->pdb, 1117 eb->processor, 1118 db->pool)); 1119 } 1120 1121 send_changed = TRUE; /* Skip dir_closed */ 1122 } 1123 } 1124 1125 if (! db->skip && !send_changed) 1126 { 1127 SVN_ERR(eb->processor->dir_closed(db->path, 1128 db->left_source, 1129 db->right_source, 1130 db->pdb, 1131 eb->processor, 1132 db->pool)); 1133 } 1134 SVN_ERR(release_dir(db)); 1135 1136 return SVN_NO_ERROR; 1137} 1138 1139 1140/* Record a prop change, which we will report later in close_file(). 1141 * 1142 * An svn_delta_editor_t function. */ 1143static svn_error_t * 1144change_file_prop(void *file_baton, 1145 const char *name, 1146 const svn_string_t *value, 1147 apr_pool_t *pool) 1148{ 1149 struct file_baton *fb = file_baton; 1150 svn_prop_t *propchange; 1151 svn_prop_kind_t propkind; 1152 1153 /* Skip *everything* within a newly tree-conflicted directory. */ 1154 if (fb->skip) 1155 return SVN_NO_ERROR; 1156 1157 propkind = svn_property_kind2(name); 1158 if (propkind == svn_prop_wc_kind) 1159 return SVN_NO_ERROR; 1160 else if (propkind == svn_prop_regular_kind) 1161 fb->has_propchange = TRUE; 1162 1163 propchange = apr_array_push(fb->propchanges); 1164 propchange->name = apr_pstrdup(fb->pool, name); 1165 propchange->value = svn_string_dup(value, fb->pool); 1166 1167 return SVN_NO_ERROR; 1168} 1169 1170/* Make a note of this prop change, to be reported when the dir is closed. 1171 * 1172 * An svn_delta_editor_t function. */ 1173static svn_error_t * 1174change_dir_prop(void *dir_baton, 1175 const char *name, 1176 const svn_string_t *value, 1177 apr_pool_t *pool) 1178{ 1179 struct dir_baton *db = dir_baton; 1180 svn_prop_t *propchange; 1181 svn_prop_kind_t propkind; 1182 1183 /* Skip *everything* within a newly tree-conflicted directory. */ 1184 if (db->skip) 1185 return SVN_NO_ERROR; 1186 1187 propkind = svn_property_kind2(name); 1188 if (propkind == svn_prop_wc_kind) 1189 return SVN_NO_ERROR; 1190 else if (propkind == svn_prop_regular_kind) 1191 db->has_propchange = TRUE; 1192 1193 propchange = apr_array_push(db->propchanges); 1194 propchange->name = apr_pstrdup(db->pool, name); 1195 propchange->value = svn_string_dup(value, db->pool); 1196 1197 return SVN_NO_ERROR; 1198} 1199 1200 1201/* An svn_delta_editor_t function. */ 1202static svn_error_t * 1203close_edit(void *edit_baton, 1204 apr_pool_t *pool) 1205{ 1206 struct edit_baton *eb = edit_baton; 1207 1208 svn_pool_destroy(eb->pool); 1209 1210 return SVN_NO_ERROR; 1211} 1212 1213/* Notify that the node at PATH is 'missing'. 1214 * An svn_delta_editor_t function. */ 1215static svn_error_t * 1216absent_directory(const char *path, 1217 void *parent_baton, 1218 apr_pool_t *pool) 1219{ 1220 struct dir_baton *pb = parent_baton; 1221 struct edit_baton *eb = pb->edit_baton; 1222 1223 SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool)); 1224 1225 return SVN_NO_ERROR; 1226} 1227 1228 1229/* Notify that the node at PATH is 'missing'. 1230 * An svn_delta_editor_t function. */ 1231static svn_error_t * 1232absent_file(const char *path, 1233 void *parent_baton, 1234 apr_pool_t *pool) 1235{ 1236 struct dir_baton *pb = parent_baton; 1237 struct edit_baton *eb = pb->edit_baton; 1238 1239 SVN_ERR(eb->processor->node_absent(path, pb->pdb, eb->processor, pool)); 1240 1241 return SVN_NO_ERROR; 1242} 1243 1244static svn_error_t * 1245fetch_kind_func(svn_node_kind_t *kind, 1246 void *baton, 1247 const char *path, 1248 svn_revnum_t base_revision, 1249 apr_pool_t *scratch_pool) 1250{ 1251 struct edit_baton *eb = baton; 1252 1253 if (!SVN_IS_VALID_REVNUM(base_revision)) 1254 base_revision = eb->revision; 1255 1256 SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind, 1257 scratch_pool)); 1258 1259 return SVN_NO_ERROR; 1260} 1261 1262static svn_error_t * 1263fetch_props_func(apr_hash_t **props, 1264 void *baton, 1265 const char *path, 1266 svn_revnum_t base_revision, 1267 apr_pool_t *result_pool, 1268 apr_pool_t *scratch_pool) 1269{ 1270 struct edit_baton *eb = baton; 1271 svn_node_kind_t node_kind; 1272 1273 if (!SVN_IS_VALID_REVNUM(base_revision)) 1274 base_revision = eb->revision; 1275 1276 SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind, 1277 scratch_pool)); 1278 1279 if (node_kind == svn_node_file) 1280 { 1281 SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision, 1282 NULL, NULL, props, result_pool)); 1283 } 1284 else if (node_kind == svn_node_dir) 1285 { 1286 apr_array_header_t *tmp_props; 1287 1288 SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path, 1289 base_revision, 0 /* Dirent fields */, 1290 result_pool)); 1291 tmp_props = svn_prop_hash_to_array(*props, result_pool); 1292 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, 1293 result_pool)); 1294 *props = svn_prop_array_to_hash(tmp_props, result_pool); 1295 } 1296 else 1297 { 1298 *props = apr_hash_make(result_pool); 1299 } 1300 1301 return SVN_NO_ERROR; 1302} 1303 1304static svn_error_t * 1305fetch_base_func(const char **filename, 1306 void *baton, 1307 const char *path, 1308 svn_revnum_t base_revision, 1309 apr_pool_t *result_pool, 1310 apr_pool_t *scratch_pool) 1311{ 1312 struct edit_baton *eb = baton; 1313 svn_stream_t *fstream; 1314 svn_error_t *err; 1315 1316 if (!SVN_IS_VALID_REVNUM(base_revision)) 1317 base_revision = eb->revision; 1318 1319 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, 1320 svn_io_file_del_on_pool_cleanup, 1321 result_pool, scratch_pool)); 1322 1323 err = svn_ra_get_file(eb->ra_session, path, base_revision, 1324 fstream, NULL, NULL, scratch_pool); 1325 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 1326 { 1327 svn_error_clear(err); 1328 SVN_ERR(svn_stream_close(fstream)); 1329 1330 *filename = NULL; 1331 return SVN_NO_ERROR; 1332 } 1333 else if (err) 1334 return svn_error_trace(err); 1335 1336 SVN_ERR(svn_stream_close(fstream)); 1337 1338 return SVN_NO_ERROR; 1339} 1340 1341/* Create a repository diff editor and baton. */ 1342svn_error_t * 1343svn_client__get_diff_editor2(const svn_delta_editor_t **editor, 1344 void **edit_baton, 1345 svn_ra_session_t *ra_session, 1346 svn_depth_t depth, 1347 svn_revnum_t revision, 1348 svn_boolean_t text_deltas, 1349 const svn_diff_tree_processor_t *processor, 1350 svn_cancel_func_t cancel_func, 1351 void *cancel_baton, 1352 apr_pool_t *result_pool) 1353{ 1354 apr_pool_t *editor_pool = svn_pool_create(result_pool); 1355 svn_delta_editor_t *tree_editor = svn_delta_default_editor(editor_pool); 1356 struct edit_baton *eb = apr_pcalloc(editor_pool, sizeof(*eb)); 1357 svn_delta_shim_callbacks_t *shim_callbacks = 1358 svn_delta_shim_callbacks_default(editor_pool); 1359 1360 eb->pool = editor_pool; 1361 eb->depth = depth; 1362 1363 eb->processor = processor; 1364 1365 eb->ra_session = ra_session; 1366 1367 eb->revision = revision; 1368 eb->target_revision = SVN_INVALID_REVNUM; 1369 eb->empty_file = NULL; 1370 eb->empty_hash = apr_hash_make(eb->pool); 1371 eb->text_deltas = text_deltas; 1372 eb->cancel_func = cancel_func; 1373 eb->cancel_baton = cancel_baton; 1374 1375 tree_editor->set_target_revision = set_target_revision; 1376 tree_editor->open_root = open_root; 1377 tree_editor->delete_entry = delete_entry; 1378 tree_editor->add_directory = add_directory; 1379 tree_editor->open_directory = open_directory; 1380 tree_editor->add_file = add_file; 1381 tree_editor->open_file = open_file; 1382 tree_editor->apply_textdelta = apply_textdelta; 1383 tree_editor->close_file = close_file; 1384 tree_editor->close_directory = close_directory; 1385 tree_editor->change_file_prop = change_file_prop; 1386 tree_editor->change_dir_prop = change_dir_prop; 1387 tree_editor->close_edit = close_edit; 1388 tree_editor->absent_directory = absent_directory; 1389 tree_editor->absent_file = absent_file; 1390 1391 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, 1392 tree_editor, eb, 1393 editor, edit_baton, 1394 eb->pool)); 1395 1396 shim_callbacks->fetch_kind_func = fetch_kind_func; 1397 shim_callbacks->fetch_props_func = fetch_props_func; 1398 shim_callbacks->fetch_base_func = fetch_base_func; 1399 shim_callbacks->fetch_baton = eb; 1400 1401 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, 1402 NULL, NULL, shim_callbacks, 1403 result_pool, result_pool)); 1404 1405 return SVN_NO_ERROR; 1406} 1407