delta.c revision 299742
1/* 2 * delta.c: an editor driver for expressing differences between two trees 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#include <apr_hash.h> 26 27#include "svn_hash.h" 28#include "svn_types.h" 29#include "svn_delta.h" 30#include "svn_fs.h" 31#include "svn_checksum.h" 32#include "svn_path.h" 33#include "svn_repos.h" 34#include "svn_pools.h" 35#include "svn_props.h" 36#include "svn_private_config.h" 37#include "repos.h" 38 39 40 41/* THINGS TODO: Currently the code herein gives only a slight nod to 42 fully supporting directory deltas that involve renames, copies, and 43 such. */ 44 45 46/* Some datatypes and declarations used throughout the file. */ 47 48 49/* Parameters which remain constant throughout a delta traversal. 50 At the top of the recursion, we initialize one of these structures. 51 Then we pass it down to every call. This way, functions invoked 52 deep in the recursion can get access to this traversal's global 53 parameters, without using global variables. */ 54struct context { 55 const svn_delta_editor_t *editor; 56 const char *edit_base_path; 57 svn_fs_root_t *source_root; 58 svn_fs_root_t *target_root; 59 svn_repos_authz_func_t authz_read_func; 60 void *authz_read_baton; 61 svn_boolean_t text_deltas; 62 svn_boolean_t entry_props; 63 svn_boolean_t ignore_ancestry; 64}; 65 66 67/* The type of a function that accepts changes to an object's property 68 list. OBJECT is the object whose properties are being changed. 69 NAME is the name of the property to change. VALUE is the new value 70 for the property, or zero if the property should be deleted. */ 71typedef svn_error_t *proplist_change_fn_t(struct context *c, 72 void *object, 73 const char *name, 74 const svn_string_t *value, 75 apr_pool_t *pool); 76 77 78 79/* Some prototypes for functions used throughout. See each individual 80 function for information about what it does. */ 81 82 83/* Retrieving the base revision from the path/revision hash. */ 84static svn_revnum_t get_path_revision(svn_fs_root_t *root, 85 const char *path, 86 apr_pool_t *pool); 87 88 89/* proplist_change_fn_t property changing functions. */ 90static svn_error_t *change_dir_prop(struct context *c, 91 void *object, 92 const char *path, 93 const svn_string_t *value, 94 apr_pool_t *pool); 95 96static svn_error_t *change_file_prop(struct context *c, 97 void *object, 98 const char *path, 99 const svn_string_t *value, 100 apr_pool_t *pool); 101 102 103/* Constructing deltas for properties of files and directories. */ 104static svn_error_t *delta_proplists(struct context *c, 105 const char *source_path, 106 const char *target_path, 107 proplist_change_fn_t *change_fn, 108 void *object, 109 apr_pool_t *pool); 110 111 112/* Constructing deltas for file constents. */ 113static svn_error_t *send_text_delta(struct context *c, 114 void *file_baton, 115 const char *base_checksum, 116 svn_txdelta_stream_t *delta_stream, 117 apr_pool_t *pool); 118 119static svn_error_t *delta_files(struct context *c, 120 void *file_baton, 121 const char *source_path, 122 const char *target_path, 123 apr_pool_t *pool); 124 125 126/* Generic directory deltafication routines. */ 127static svn_error_t *delete(struct context *c, 128 void *dir_baton, 129 const char *edit_path, 130 apr_pool_t *pool); 131 132static svn_error_t *add_file_or_dir(struct context *c, 133 void *dir_baton, 134 svn_depth_t depth, 135 const char *target_path, 136 const char *edit_path, 137 svn_node_kind_t tgt_kind, 138 apr_pool_t *pool); 139 140static svn_error_t *replace_file_or_dir(struct context *c, 141 void *dir_baton, 142 svn_depth_t depth, 143 const char *source_path, 144 const char *target_path, 145 const char *edit_path, 146 svn_node_kind_t tgt_kind, 147 apr_pool_t *pool); 148 149static svn_error_t *absent_file_or_dir(struct context *c, 150 void *dir_baton, 151 const char *edit_path, 152 svn_node_kind_t tgt_kind, 153 apr_pool_t *pool); 154 155static svn_error_t *delta_dirs(struct context *c, 156 void *dir_baton, 157 svn_depth_t depth, 158 const char *source_path, 159 const char *target_path, 160 const char *edit_path, 161 apr_pool_t *pool); 162 163 164 165#define MAYBE_DEMOTE_DEPTH(depth) \ 166 (((depth) == svn_depth_immediates || (depth) == svn_depth_files) \ 167 ? svn_depth_empty \ 168 : (depth)) 169 170 171/* Return the error 'SVN_ERR_AUTHZ_ROOT_UNREADABLE' if PATH in ROOT is 172 * unreadable according to AUTHZ_READ_FUNC with AUTHZ_READ_BATON. 173 * 174 * PATH should be the implicit root path of an editor drive, that is, 175 * the path used by editor->open_root(). 176 */ 177static svn_error_t * 178authz_root_check(svn_fs_root_t *root, 179 const char *path, 180 svn_repos_authz_func_t authz_read_func, 181 void *authz_read_baton, 182 apr_pool_t *pool) 183{ 184 svn_boolean_t allowed; 185 186 if (authz_read_func) 187 { 188 SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, pool)); 189 190 if (! allowed) 191 return svn_error_create(SVN_ERR_AUTHZ_ROOT_UNREADABLE, 0, 192 _("Unable to open root of edit")); 193 } 194 195 return SVN_NO_ERROR; 196} 197 198 199/* Public interface to computing directory deltas. */ 200svn_error_t * 201svn_repos_dir_delta2(svn_fs_root_t *src_root, 202 const char *src_parent_dir, 203 const char *src_entry, 204 svn_fs_root_t *tgt_root, 205 const char *tgt_fullpath, 206 const svn_delta_editor_t *editor, 207 void *edit_baton, 208 svn_repos_authz_func_t authz_read_func, 209 void *authz_read_baton, 210 svn_boolean_t text_deltas, 211 svn_depth_t depth, 212 svn_boolean_t entry_props, 213 svn_boolean_t ignore_ancestry, 214 apr_pool_t *pool) 215{ 216 void *root_baton = NULL; 217 struct context c; 218 const char *src_fullpath; 219 svn_node_kind_t src_kind, tgt_kind; 220 svn_revnum_t rootrev; 221 svn_fs_node_relation_t relation; 222 const char *authz_root_path; 223 224 /* SRC_PARENT_DIR must be valid. */ 225 if (src_parent_dir) 226 src_parent_dir = svn_relpath_canonicalize(src_parent_dir, pool); 227 else 228 return svn_error_create(SVN_ERR_FS_NOT_DIRECTORY, 0, 229 "Invalid source parent directory '(null)'"); 230 231 /* TGT_FULLPATH must be valid. */ 232 if (tgt_fullpath) 233 tgt_fullpath = svn_relpath_canonicalize(tgt_fullpath, pool); 234 else 235 return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, 0, 236 _("Invalid target path")); 237 238 if (depth == svn_depth_exclude) 239 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, 240 _("Delta depth 'exclude' not supported")); 241 242 /* Calculate the fs path implicitly used for editor->open_root, so 243 we can do an authz check on that path first. */ 244 if (*src_entry) 245 authz_root_path = svn_relpath_dirname(tgt_fullpath, pool); 246 else 247 authz_root_path = tgt_fullpath; 248 249 /* Construct the full path of the source item. */ 250 src_fullpath = svn_relpath_join(src_parent_dir, src_entry, pool); 251 252 /* Get the node kinds for the source and target paths. */ 253 SVN_ERR(svn_fs_check_path(&tgt_kind, tgt_root, tgt_fullpath, pool)); 254 SVN_ERR(svn_fs_check_path(&src_kind, src_root, src_fullpath, pool)); 255 256 /* If neither of our paths exists, we don't really have anything to do. */ 257 if ((tgt_kind == svn_node_none) && (src_kind == svn_node_none)) 258 goto cleanup; 259 260 /* If either the source or the target is a non-directory, we 261 require that a SRC_ENTRY be supplied. */ 262 if ((! *src_entry) && ((src_kind != svn_node_dir) 263 || tgt_kind != svn_node_dir)) 264 return svn_error_create 265 (SVN_ERR_FS_PATH_SYNTAX, 0, 266 _("Invalid editor anchoring; at least one of the " 267 "input paths is not a directory and there was no source entry")); 268 269 /* Set the global target revision if one can be determined. */ 270 if (svn_fs_is_revision_root(tgt_root)) 271 { 272 SVN_ERR(editor->set_target_revision 273 (edit_baton, svn_fs_revision_root_revision(tgt_root), pool)); 274 } 275 else if (svn_fs_is_txn_root(tgt_root)) 276 { 277 SVN_ERR(editor->set_target_revision 278 (edit_baton, svn_fs_txn_root_base_revision(tgt_root), pool)); 279 } 280 281 /* Setup our pseudo-global structure here. We need these variables 282 throughout the deltafication process, so pass them around by 283 reference to all the helper functions. */ 284 c.editor = editor; 285 c.source_root = src_root; 286 c.target_root = tgt_root; 287 c.authz_read_func = authz_read_func; 288 c.authz_read_baton = authz_read_baton; 289 c.text_deltas = text_deltas; 290 c.entry_props = entry_props; 291 c.ignore_ancestry = ignore_ancestry; 292 293 /* Get our editor root's revision. */ 294 rootrev = get_path_revision(src_root, src_parent_dir, pool); 295 296 /* If one or the other of our paths doesn't exist, we have to handle 297 those cases specially. */ 298 if (tgt_kind == svn_node_none) 299 { 300 /* Caller thinks that target still exists, but it doesn't. 301 So transform their source path to "nothing" by deleting it. */ 302 SVN_ERR(authz_root_check(tgt_root, authz_root_path, 303 authz_read_func, authz_read_baton, pool)); 304 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 305 SVN_ERR(delete(&c, root_baton, src_entry, pool)); 306 goto cleanup; 307 } 308 if (src_kind == svn_node_none) 309 { 310 /* The source path no longer exists, but the target does. 311 So transform "nothing" into "something" by adding. */ 312 SVN_ERR(authz_root_check(tgt_root, authz_root_path, 313 authz_read_func, authz_read_baton, pool)); 314 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 315 SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath, 316 src_entry, tgt_kind, pool)); 317 goto cleanup; 318 } 319 320 /* Get and compare the node IDs for the source and target. */ 321 SVN_ERR(svn_fs_node_relation(&relation, tgt_root, tgt_fullpath, 322 src_root, src_fullpath, pool)); 323 324 if (relation == svn_fs_node_unchanged) 325 { 326 /* They are the same node! No-op (you gotta love those). */ 327 goto cleanup; 328 } 329 else if (*src_entry) 330 { 331 /* If the nodes have different kinds, we must delete the one and 332 add the other. Also, if they are completely unrelated and 333 our caller is interested in relatedness, we do the same thing. */ 334 if ((src_kind != tgt_kind) 335 || ((relation == svn_fs_node_unrelated) && (! ignore_ancestry))) 336 { 337 SVN_ERR(authz_root_check(tgt_root, authz_root_path, 338 authz_read_func, authz_read_baton, pool)); 339 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 340 SVN_ERR(delete(&c, root_baton, src_entry, pool)); 341 SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath, 342 src_entry, tgt_kind, pool)); 343 } 344 /* Otherwise, we just replace the one with the other. */ 345 else 346 { 347 SVN_ERR(authz_root_check(tgt_root, authz_root_path, 348 authz_read_func, authz_read_baton, pool)); 349 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 350 SVN_ERR(replace_file_or_dir(&c, root_baton, depth, src_fullpath, 351 tgt_fullpath, src_entry, 352 tgt_kind, pool)); 353 } 354 } 355 else 356 { 357 /* There is no entry given, so delta the whole parent directory. */ 358 SVN_ERR(authz_root_check(tgt_root, authz_root_path, 359 authz_read_func, authz_read_baton, pool)); 360 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 361 SVN_ERR(delta_dirs(&c, root_baton, depth, src_fullpath, 362 tgt_fullpath, "", pool)); 363 } 364 365 cleanup: 366 367 /* Make sure we close the root directory if we opened one above. */ 368 if (root_baton) 369 SVN_ERR(editor->close_directory(root_baton, pool)); 370 371 /* Close the edit. */ 372 return editor->close_edit(edit_baton, pool); 373} 374 375 376/* Retrieving the base revision from the path/revision hash. */ 377 378 379static svn_revnum_t 380get_path_revision(svn_fs_root_t *root, 381 const char *path, 382 apr_pool_t *pool) 383{ 384 svn_revnum_t revision; 385 svn_error_t *err; 386 387 /* Easy out -- if ROOT is a revision root, we can use the revision 388 that it's a root of. */ 389 if (svn_fs_is_revision_root(root)) 390 return svn_fs_revision_root_revision(root); 391 392 /* Else, this must be a transaction root, so ask the filesystem in 393 what revision this path was created. */ 394 if ((err = svn_fs_node_created_rev(&revision, root, path, pool))) 395 { 396 revision = SVN_INVALID_REVNUM; 397 svn_error_clear(err); 398 } 399 400 /* If we don't get back a valid revision, this path is mutable in 401 the transaction. We should probably examine the node on which it 402 is based, doable by querying for the node-id of the path, and 403 then examining that node-id's predecessor. ### This predecessor 404 determination isn't exposed via the FS public API right now, so 405 for now, we'll just return the SVN_INVALID_REVNUM. */ 406 return revision; 407} 408 409 410/* proplist_change_fn_t property changing functions. */ 411 412 413/* Call the directory property-setting function of C->editor to set 414 the property NAME to given VALUE on the OBJECT passed to this 415 function. */ 416static svn_error_t * 417change_dir_prop(struct context *c, 418 void *object, 419 const char *name, 420 const svn_string_t *value, 421 apr_pool_t *pool) 422{ 423 return c->editor->change_dir_prop(object, name, value, pool); 424} 425 426 427/* Call the file property-setting function of C->editor to set the 428 property NAME to given VALUE on the OBJECT passed to this 429 function. */ 430static svn_error_t * 431change_file_prop(struct context *c, 432 void *object, 433 const char *name, 434 const svn_string_t *value, 435 apr_pool_t *pool) 436{ 437 return c->editor->change_file_prop(object, name, value, pool); 438} 439 440 441 442 443/* Constructing deltas for properties of files and directories. */ 444 445 446/* Generate the appropriate property editing calls to turn the 447 properties of SOURCE_PATH into those of TARGET_PATH. If 448 SOURCE_PATH is NULL, this is an add, so assume the target starts 449 with no properties. Pass OBJECT on to the editor function wrapper 450 CHANGE_FN. */ 451static svn_error_t * 452delta_proplists(struct context *c, 453 const char *source_path, 454 const char *target_path, 455 proplist_change_fn_t *change_fn, 456 void *object, 457 apr_pool_t *pool) 458{ 459 apr_hash_t *s_props = 0; 460 apr_hash_t *t_props = 0; 461 apr_pool_t *subpool; 462 apr_array_header_t *prop_diffs; 463 int i; 464 465 SVN_ERR_ASSERT(target_path); 466 467 /* Make a subpool for local allocations. */ 468 subpool = svn_pool_create(pool); 469 470 /* If we're supposed to send entry props for all non-deleted items, 471 here we go! */ 472 if (c->entry_props) 473 { 474 svn_revnum_t committed_rev = SVN_INVALID_REVNUM; 475 svn_string_t *cr_str = NULL; 476 svn_string_t *committed_date = NULL; 477 svn_string_t *last_author = NULL; 478 479 /* Get the CR and two derivative props. ### check for error returns. */ 480 SVN_ERR(svn_fs_node_created_rev(&committed_rev, c->target_root, 481 target_path, subpool)); 482 if (SVN_IS_VALID_REVNUM(committed_rev)) 483 { 484 svn_fs_t *fs = svn_fs_root_fs(c->target_root); 485 apr_hash_t *r_props; 486 const char *uuid; 487 488 /* Transmit the committed-rev. */ 489 cr_str = svn_string_createf(subpool, "%ld", 490 committed_rev); 491 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV, 492 cr_str, subpool)); 493 494 SVN_ERR(svn_fs_revision_proplist(&r_props, fs, committed_rev, 495 pool)); 496 497 /* Transmit the committed-date. */ 498 committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE); 499 if (committed_date || source_path) 500 { 501 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_DATE, 502 committed_date, subpool)); 503 } 504 505 /* Transmit the last-author. */ 506 last_author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR); 507 if (last_author || source_path) 508 { 509 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_LAST_AUTHOR, 510 last_author, subpool)); 511 } 512 513 /* Transmit the UUID. */ 514 SVN_ERR(svn_fs_get_uuid(fs, &uuid, subpool)); 515 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_UUID, 516 svn_string_create(uuid, subpool), 517 subpool)); 518 } 519 } 520 521 if (source_path) 522 { 523 svn_boolean_t changed; 524 525 /* Is this deltification worth our time? */ 526 SVN_ERR(svn_fs_props_different(&changed, c->target_root, target_path, 527 c->source_root, source_path, subpool)); 528 if (! changed) 529 goto cleanup; 530 531 /* If so, go ahead and get the source path's properties. */ 532 SVN_ERR(svn_fs_node_proplist(&s_props, c->source_root, 533 source_path, subpool)); 534 } 535 else 536 { 537 s_props = apr_hash_make(subpool); 538 } 539 540 /* Get the target path's properties */ 541 SVN_ERR(svn_fs_node_proplist(&t_props, c->target_root, 542 target_path, subpool)); 543 544 /* Now transmit the differences. */ 545 SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, subpool)); 546 for (i = 0; i < prop_diffs->nelts; i++) 547 { 548 const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t); 549 SVN_ERR(change_fn(c, object, pc->name, pc->value, subpool)); 550 } 551 552 cleanup: 553 /* Destroy local subpool. */ 554 svn_pool_destroy(subpool); 555 556 return SVN_NO_ERROR; 557} 558 559 560 561 562/* Constructing deltas for file contents. */ 563 564 565/* Change the contents of FILE_BATON in C->editor, according to the 566 text delta from DELTA_STREAM. Pass BASE_CHECKSUM along to 567 C->editor->apply_textdelta. */ 568static svn_error_t * 569send_text_delta(struct context *c, 570 void *file_baton, 571 const char *base_checksum, 572 svn_txdelta_stream_t *delta_stream, 573 apr_pool_t *pool) 574{ 575 svn_txdelta_window_handler_t delta_handler; 576 void *delta_handler_baton; 577 578 /* Get a handler that will apply the delta to the file. */ 579 SVN_ERR(c->editor->apply_textdelta 580 (file_baton, base_checksum, pool, 581 &delta_handler, &delta_handler_baton)); 582 583 if (c->text_deltas && delta_stream) 584 { 585 /* Deliver the delta stream to the file. */ 586 return svn_txdelta_send_txstream(delta_stream, 587 delta_handler, 588 delta_handler_baton, 589 pool); 590 } 591 else 592 { 593 /* The caller doesn't want text delta data. Just send a single 594 NULL window. */ 595 return delta_handler(NULL, delta_handler_baton); 596 } 597} 598 599svn_error_t * 600svn_repos__compare_files(svn_boolean_t *changed_p, 601 svn_fs_root_t *root1, 602 const char *path1, 603 svn_fs_root_t *root2, 604 const char *path2, 605 apr_pool_t *pool) 606{ 607 return svn_error_trace(svn_fs_contents_different(changed_p, root1, path1, 608 root2, path2, pool)); 609} 610 611 612/* Make the appropriate edits on FILE_BATON to change its contents and 613 properties from those in SOURCE_PATH to those in TARGET_PATH. */ 614static svn_error_t * 615delta_files(struct context *c, 616 void *file_baton, 617 const char *source_path, 618 const char *target_path, 619 apr_pool_t *pool) 620{ 621 apr_pool_t *subpool; 622 svn_boolean_t changed = TRUE; 623 624 SVN_ERR_ASSERT(target_path); 625 626 /* Make a subpool for local allocations. */ 627 subpool = svn_pool_create(pool); 628 629 /* Compare the files' property lists. */ 630 SVN_ERR(delta_proplists(c, source_path, target_path, 631 change_file_prop, file_baton, subpool)); 632 633 if (source_path) 634 { 635 SVN_ERR(svn_fs_contents_different(&changed, 636 c->target_root, target_path, 637 c->source_root, source_path, 638 subpool)); 639 } 640 else 641 { 642 /* If there isn't a source path, this is an add, which 643 necessarily has textual mods. */ 644 } 645 646 /* If there is a change, and the context indicates that we should 647 care about it, then hand it off to a delta stream. */ 648 if (changed) 649 { 650 svn_txdelta_stream_t *delta_stream = NULL; 651 svn_checksum_t *source_checksum; 652 const char *source_hex_digest = NULL; 653 654 if (c->text_deltas) 655 { 656 /* Get a delta stream turning an empty file into one having 657 TARGET_PATH's contents. */ 658 SVN_ERR(svn_fs_get_file_delta_stream 659 (&delta_stream, 660 source_path ? c->source_root : NULL, 661 source_path ? source_path : NULL, 662 c->target_root, target_path, subpool)); 663 } 664 665 if (source_path) 666 { 667 SVN_ERR(svn_fs_file_checksum(&source_checksum, svn_checksum_md5, 668 c->source_root, source_path, TRUE, 669 subpool)); 670 671 source_hex_digest = svn_checksum_to_cstring(source_checksum, 672 subpool); 673 } 674 675 SVN_ERR(send_text_delta(c, file_baton, source_hex_digest, 676 delta_stream, subpool)); 677 } 678 679 /* Cleanup. */ 680 svn_pool_destroy(subpool); 681 682 return SVN_NO_ERROR; 683} 684 685 686 687 688/* Generic directory deltafication routines. */ 689 690 691/* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */ 692static svn_error_t * 693delete(struct context *c, 694 void *dir_baton, 695 const char *edit_path, 696 apr_pool_t *pool) 697{ 698 return c->editor->delete_entry(edit_path, SVN_INVALID_REVNUM, 699 dir_baton, pool); 700} 701 702 703/* If authorized, emit a delta to create the entry named TARGET_ENTRY 704 at the location EDIT_PATH. If not authorized, indicate that 705 EDIT_PATH is absent. Pass DIR_BATON through to editor functions 706 that require it. DEPTH is the depth from this point downward. */ 707static svn_error_t * 708add_file_or_dir(struct context *c, void *dir_baton, 709 svn_depth_t depth, 710 const char *target_path, 711 const char *edit_path, 712 svn_node_kind_t tgt_kind, 713 apr_pool_t *pool) 714{ 715 struct context *context = c; 716 svn_boolean_t allowed; 717 718 SVN_ERR_ASSERT(target_path && edit_path); 719 720 if (c->authz_read_func) 721 { 722 SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path, 723 c->authz_read_baton, pool)); 724 if (!allowed) 725 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool); 726 } 727 728 if (tgt_kind == svn_node_dir) 729 { 730 void *subdir_baton; 731 732 SVN_ERR(context->editor->add_directory(edit_path, dir_baton, NULL, 733 SVN_INVALID_REVNUM, pool, 734 &subdir_baton)); 735 SVN_ERR(delta_dirs(context, subdir_baton, MAYBE_DEMOTE_DEPTH(depth), 736 NULL, target_path, edit_path, pool)); 737 return context->editor->close_directory(subdir_baton, pool); 738 } 739 else 740 { 741 void *file_baton; 742 svn_checksum_t *checksum; 743 744 SVN_ERR(context->editor->add_file(edit_path, dir_baton, 745 NULL, SVN_INVALID_REVNUM, pool, 746 &file_baton)); 747 SVN_ERR(delta_files(context, file_baton, NULL, target_path, pool)); 748 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 749 context->target_root, target_path, 750 TRUE, pool)); 751 return context->editor->close_file 752 (file_baton, svn_checksum_to_cstring(checksum, pool), pool); 753 } 754} 755 756 757/* If authorized, emit a delta to modify EDIT_PATH with the changes 758 from SOURCE_PATH to TARGET_PATH. If not authorized, indicate that 759 EDIT_PATH is absent. Pass DIR_BATON through to editor functions 760 that require it. DEPTH is the depth from this point downward. */ 761static svn_error_t * 762replace_file_or_dir(struct context *c, 763 void *dir_baton, 764 svn_depth_t depth, 765 const char *source_path, 766 const char *target_path, 767 const char *edit_path, 768 svn_node_kind_t tgt_kind, 769 apr_pool_t *pool) 770{ 771 svn_revnum_t base_revision = SVN_INVALID_REVNUM; 772 svn_boolean_t allowed; 773 774 SVN_ERR_ASSERT(target_path && source_path && edit_path); 775 776 if (c->authz_read_func) 777 { 778 SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path, 779 c->authz_read_baton, pool)); 780 if (!allowed) 781 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool); 782 } 783 784 /* Get the base revision for the entry from the hash. */ 785 base_revision = get_path_revision(c->source_root, source_path, pool); 786 787 if (tgt_kind == svn_node_dir) 788 { 789 void *subdir_baton; 790 791 SVN_ERR(c->editor->open_directory(edit_path, dir_baton, 792 base_revision, pool, 793 &subdir_baton)); 794 SVN_ERR(delta_dirs(c, subdir_baton, MAYBE_DEMOTE_DEPTH(depth), 795 source_path, target_path, edit_path, pool)); 796 return c->editor->close_directory(subdir_baton, pool); 797 } 798 else 799 { 800 void *file_baton; 801 svn_checksum_t *checksum; 802 803 SVN_ERR(c->editor->open_file(edit_path, dir_baton, base_revision, 804 pool, &file_baton)); 805 SVN_ERR(delta_files(c, file_baton, source_path, target_path, pool)); 806 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 807 c->target_root, target_path, TRUE, 808 pool)); 809 return c->editor->close_file 810 (file_baton, svn_checksum_to_cstring(checksum, pool), pool); 811 } 812} 813 814 815/* In directory DIR_BATON, indicate that EDIT_PATH (relative to the 816 edit root) is absent by invoking C->editor->absent_directory or 817 C->editor->absent_file (depending on TGT_KIND). */ 818static svn_error_t * 819absent_file_or_dir(struct context *c, 820 void *dir_baton, 821 const char *edit_path, 822 svn_node_kind_t tgt_kind, 823 apr_pool_t *pool) 824{ 825 SVN_ERR_ASSERT(edit_path); 826 827 if (tgt_kind == svn_node_dir) 828 return c->editor->absent_directory(edit_path, dir_baton, pool); 829 else 830 return c->editor->absent_file(edit_path, dir_baton, pool); 831} 832 833 834/* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that 835 DIR_BATON represents the directory we're constructing to the editor 836 in the context C. */ 837static svn_error_t * 838delta_dirs(struct context *c, 839 void *dir_baton, 840 svn_depth_t depth, 841 const char *source_path, 842 const char *target_path, 843 const char *edit_path, 844 apr_pool_t *pool) 845{ 846 apr_hash_t *s_entries = 0, *t_entries = 0; 847 apr_hash_index_t *hi; 848 apr_pool_t *subpool; 849 850 SVN_ERR_ASSERT(target_path); 851 852 /* Compare the property lists. */ 853 SVN_ERR(delta_proplists(c, source_path, target_path, 854 change_dir_prop, dir_baton, pool)); 855 856 /* Get the list of entries in each of source and target. */ 857 SVN_ERR(svn_fs_dir_entries(&t_entries, c->target_root, 858 target_path, pool)); 859 if (source_path) 860 SVN_ERR(svn_fs_dir_entries(&s_entries, c->source_root, 861 source_path, pool)); 862 863 /* Make a subpool for local allocations. */ 864 subpool = svn_pool_create(pool); 865 866 /* Loop over the hash of entries in the target, searching for its 867 partner in the source. If we find the matching partner entry, 868 use editor calls to replace the one in target with a new version 869 if necessary, then remove that entry from the source entries 870 hash. If we can't find a related node in the source, we use 871 editor calls to add the entry as a new item in the target. 872 Having handled all the entries that exist in target, any entries 873 still remaining the source entries hash represent entries that no 874 longer exist in target. Use editor calls to delete those entries 875 from the target tree. */ 876 for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi)) 877 { 878 const void *key = apr_hash_this_key(hi); 879 apr_ssize_t klen = apr_hash_this_key_len(hi); 880 const svn_fs_dirent_t *t_entry = apr_hash_this_val(hi); 881 const svn_fs_dirent_t *s_entry; 882 const char *t_fullpath; 883 const char *e_fullpath; 884 const char *s_fullpath; 885 svn_node_kind_t tgt_kind; 886 887 /* Clear out our subpool for the next iteration... */ 888 svn_pool_clear(subpool); 889 890 tgt_kind = t_entry->kind; 891 t_fullpath = svn_relpath_join(target_path, t_entry->name, subpool); 892 e_fullpath = svn_relpath_join(edit_path, t_entry->name, subpool); 893 894 /* Can we find something with the same name in the source 895 entries hash? */ 896 if (s_entries && ((s_entry = apr_hash_get(s_entries, key, klen)) != 0)) 897 { 898 svn_node_kind_t src_kind; 899 900 s_fullpath = svn_relpath_join(source_path, t_entry->name, subpool); 901 src_kind = s_entry->kind; 902 903 if (depth == svn_depth_infinity 904 || src_kind != svn_node_dir 905 || (src_kind == svn_node_dir 906 && depth == svn_depth_immediates)) 907 { 908 /* Use svn_fs_compare_ids() to compare our current 909 source and target ids. 910 911 0: means they are the same id, and this is a noop. 912 -1: means they are unrelated, so we have to delete the 913 old one and add the new one. 914 1: means the nodes are related through ancestry, so go 915 ahead and do the replace directly. */ 916 int distance = svn_fs_compare_ids(s_entry->id, t_entry->id); 917 if (distance == 0) 918 { 919 /* no-op */ 920 } 921 else if ((src_kind != tgt_kind) 922 || ((distance == -1) && (! c->ignore_ancestry))) 923 { 924 SVN_ERR(delete(c, dir_baton, e_fullpath, subpool)); 925 SVN_ERR(add_file_or_dir(c, dir_baton, 926 MAYBE_DEMOTE_DEPTH(depth), 927 t_fullpath, e_fullpath, tgt_kind, 928 subpool)); 929 } 930 else 931 { 932 SVN_ERR(replace_file_or_dir(c, dir_baton, 933 MAYBE_DEMOTE_DEPTH(depth), 934 s_fullpath, t_fullpath, 935 e_fullpath, tgt_kind, 936 subpool)); 937 } 938 } 939 940 /* Remove the entry from the source_hash. */ 941 svn_hash_sets(s_entries, key, NULL); 942 } 943 else 944 { 945 if (depth == svn_depth_infinity 946 || tgt_kind != svn_node_dir 947 || (tgt_kind == svn_node_dir 948 && depth == svn_depth_immediates)) 949 { 950 SVN_ERR(add_file_or_dir(c, dir_baton, 951 MAYBE_DEMOTE_DEPTH(depth), 952 t_fullpath, e_fullpath, tgt_kind, 953 subpool)); 954 } 955 } 956 } 957 958 /* All that is left in the source entries hash are things that need 959 to be deleted. Delete them. */ 960 if (s_entries) 961 { 962 for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi)) 963 { 964 const svn_fs_dirent_t *s_entry = apr_hash_this_val(hi); 965 const char *e_fullpath; 966 svn_node_kind_t src_kind; 967 968 /* Clear out our subpool for the next iteration... */ 969 svn_pool_clear(subpool); 970 971 src_kind = s_entry->kind; 972 e_fullpath = svn_relpath_join(edit_path, s_entry->name, subpool); 973 974 /* Do we actually want to delete the dir if we're non-recursive? */ 975 if (depth == svn_depth_infinity 976 || src_kind != svn_node_dir 977 || (src_kind == svn_node_dir 978 && depth == svn_depth_immediates)) 979 { 980 SVN_ERR(delete(c, dir_baton, e_fullpath, subpool)); 981 } 982 } 983 } 984 985 /* Destroy local allocation subpool. */ 986 svn_pool_destroy(subpool); 987 988 return SVN_NO_ERROR; 989} 990