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 199static svn_error_t * 200not_a_dir_error(const char *role, 201 const char *path) 202{ 203 return svn_error_createf 204 (SVN_ERR_FS_NOT_DIRECTORY, 0, 205 "Invalid %s directory '%s'", 206 role, path ? path : "(null)"); 207} 208 209 210/* Public interface to computing directory deltas. */ 211svn_error_t * 212svn_repos_dir_delta2(svn_fs_root_t *src_root, 213 const char *src_parent_dir, 214 const char *src_entry, 215 svn_fs_root_t *tgt_root, 216 const char *tgt_fullpath, 217 const svn_delta_editor_t *editor, 218 void *edit_baton, 219 svn_repos_authz_func_t authz_read_func, 220 void *authz_read_baton, 221 svn_boolean_t text_deltas, 222 svn_depth_t depth, 223 svn_boolean_t entry_props, 224 svn_boolean_t ignore_ancestry, 225 apr_pool_t *pool) 226{ 227 void *root_baton = NULL; 228 struct context c; 229 const char *src_fullpath; 230 const svn_fs_id_t *src_id, *tgt_id; 231 svn_node_kind_t src_kind, tgt_kind; 232 svn_revnum_t rootrev; 233 int distance; 234 const char *authz_root_path; 235 236 /* SRC_PARENT_DIR must be valid. */ 237 if (src_parent_dir) 238 src_parent_dir = svn_relpath_canonicalize(src_parent_dir, pool); 239 else 240 return not_a_dir_error("source parent", src_parent_dir); 241 242 /* TGT_FULLPATH must be valid. */ 243 if (tgt_fullpath) 244 tgt_fullpath = svn_relpath_canonicalize(tgt_fullpath, pool); 245 else 246 return svn_error_create(SVN_ERR_FS_PATH_SYNTAX, 0, 247 _("Invalid target path")); 248 249 if (depth == svn_depth_exclude) 250 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, 251 _("Delta depth 'exclude' not supported")); 252 253 /* Calculate the fs path implicitly used for editor->open_root, so 254 we can do an authz check on that path first. */ 255 if (*src_entry) 256 authz_root_path = svn_relpath_dirname(tgt_fullpath, pool); 257 else 258 authz_root_path = tgt_fullpath; 259 260 /* Construct the full path of the source item. */ 261 src_fullpath = svn_relpath_join(src_parent_dir, src_entry, pool); 262 263 /* Get the node kinds for the source and target paths. */ 264 SVN_ERR(svn_fs_check_path(&tgt_kind, tgt_root, tgt_fullpath, pool)); 265 SVN_ERR(svn_fs_check_path(&src_kind, src_root, src_fullpath, pool)); 266 267 /* If neither of our paths exists, we don't really have anything to do. */ 268 if ((tgt_kind == svn_node_none) && (src_kind == svn_node_none)) 269 goto cleanup; 270 271 /* If either the source or the target is a non-directory, we 272 require that a SRC_ENTRY be supplied. */ 273 if ((! *src_entry) && ((src_kind != svn_node_dir) 274 || tgt_kind != svn_node_dir)) 275 return svn_error_create 276 (SVN_ERR_FS_PATH_SYNTAX, 0, 277 _("Invalid editor anchoring; at least one of the " 278 "input paths is not a directory and there was no source entry")); 279 280 /* Set the global target revision if one can be determined. */ 281 if (svn_fs_is_revision_root(tgt_root)) 282 { 283 SVN_ERR(editor->set_target_revision 284 (edit_baton, svn_fs_revision_root_revision(tgt_root), pool)); 285 } 286 else if (svn_fs_is_txn_root(tgt_root)) 287 { 288 SVN_ERR(editor->set_target_revision 289 (edit_baton, svn_fs_txn_root_base_revision(tgt_root), pool)); 290 } 291 292 /* Setup our pseudo-global structure here. We need these variables 293 throughout the deltafication process, so pass them around by 294 reference to all the helper functions. */ 295 c.editor = editor; 296 c.source_root = src_root; 297 c.target_root = tgt_root; 298 c.authz_read_func = authz_read_func; 299 c.authz_read_baton = authz_read_baton; 300 c.text_deltas = text_deltas; 301 c.entry_props = entry_props; 302 c.ignore_ancestry = ignore_ancestry; 303 304 /* Get our editor root's revision. */ 305 rootrev = get_path_revision(src_root, src_parent_dir, pool); 306 307 /* If one or the other of our paths doesn't exist, we have to handle 308 those cases specially. */ 309 if (tgt_kind == svn_node_none) 310 { 311 /* Caller thinks that target still exists, but it doesn't. 312 So transform their source path to "nothing" by deleting it. */ 313 SVN_ERR(authz_root_check(tgt_root, authz_root_path, 314 authz_read_func, authz_read_baton, pool)); 315 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 316 SVN_ERR(delete(&c, root_baton, src_entry, pool)); 317 goto cleanup; 318 } 319 if (src_kind == svn_node_none) 320 { 321 /* The source path no longer exists, but the target does. 322 So transform "nothing" into "something" by adding. */ 323 SVN_ERR(authz_root_check(tgt_root, authz_root_path, 324 authz_read_func, authz_read_baton, pool)); 325 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 326 SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath, 327 src_entry, tgt_kind, pool)); 328 goto cleanup; 329 } 330 331 /* Get and compare the node IDs for the source and target. */ 332 SVN_ERR(svn_fs_node_id(&tgt_id, tgt_root, tgt_fullpath, pool)); 333 SVN_ERR(svn_fs_node_id(&src_id, src_root, src_fullpath, pool)); 334 distance = svn_fs_compare_ids(src_id, tgt_id); 335 336 if (distance == 0) 337 { 338 /* They are the same node! No-op (you gotta love those). */ 339 goto cleanup; 340 } 341 else if (*src_entry) 342 { 343 /* If the nodes have different kinds, we must delete the one and 344 add the other. Also, if they are completely unrelated and 345 our caller is interested in relatedness, we do the same thing. */ 346 if ((src_kind != tgt_kind) 347 || ((distance == -1) && (! ignore_ancestry))) 348 { 349 SVN_ERR(authz_root_check(tgt_root, authz_root_path, 350 authz_read_func, authz_read_baton, pool)); 351 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 352 SVN_ERR(delete(&c, root_baton, src_entry, pool)); 353 SVN_ERR(add_file_or_dir(&c, root_baton, depth, tgt_fullpath, 354 src_entry, tgt_kind, pool)); 355 } 356 /* Otherwise, we just replace the one with the other. */ 357 else 358 { 359 SVN_ERR(authz_root_check(tgt_root, authz_root_path, 360 authz_read_func, authz_read_baton, pool)); 361 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 362 SVN_ERR(replace_file_or_dir(&c, root_baton, depth, src_fullpath, 363 tgt_fullpath, src_entry, 364 tgt_kind, pool)); 365 } 366 } 367 else 368 { 369 /* There is no entry given, so delta the whole parent directory. */ 370 SVN_ERR(authz_root_check(tgt_root, authz_root_path, 371 authz_read_func, authz_read_baton, pool)); 372 SVN_ERR(editor->open_root(edit_baton, rootrev, pool, &root_baton)); 373 SVN_ERR(delta_dirs(&c, root_baton, depth, src_fullpath, 374 tgt_fullpath, "", pool)); 375 } 376 377 cleanup: 378 379 /* Make sure we close the root directory if we opened one above. */ 380 if (root_baton) 381 SVN_ERR(editor->close_directory(root_baton, pool)); 382 383 /* Close the edit. */ 384 return editor->close_edit(edit_baton, pool); 385} 386 387 388/* Retrieving the base revision from the path/revision hash. */ 389 390 391static svn_revnum_t 392get_path_revision(svn_fs_root_t *root, 393 const char *path, 394 apr_pool_t *pool) 395{ 396 svn_revnum_t revision; 397 svn_error_t *err; 398 399 /* Easy out -- if ROOT is a revision root, we can use the revision 400 that it's a root of. */ 401 if (svn_fs_is_revision_root(root)) 402 return svn_fs_revision_root_revision(root); 403 404 /* Else, this must be a transaction root, so ask the filesystem in 405 what revision this path was created. */ 406 if ((err = svn_fs_node_created_rev(&revision, root, path, pool))) 407 { 408 revision = SVN_INVALID_REVNUM; 409 svn_error_clear(err); 410 } 411 412 /* If we don't get back a valid revision, this path is mutable in 413 the transaction. We should probably examine the node on which it 414 is based, doable by querying for the node-id of the path, and 415 then examining that node-id's predecessor. ### This predecessor 416 determination isn't exposed via the FS public API right now, so 417 for now, we'll just return the SVN_INVALID_REVNUM. */ 418 return revision; 419} 420 421 422/* proplist_change_fn_t property changing functions. */ 423 424 425/* Call the directory property-setting function of C->editor to set 426 the property NAME to given VALUE on the OBJECT passed to this 427 function. */ 428static svn_error_t * 429change_dir_prop(struct context *c, 430 void *object, 431 const char *name, 432 const svn_string_t *value, 433 apr_pool_t *pool) 434{ 435 return c->editor->change_dir_prop(object, name, value, pool); 436} 437 438 439/* Call the file property-setting function of C->editor to set the 440 property NAME to given VALUE on the OBJECT passed to this 441 function. */ 442static svn_error_t * 443change_file_prop(struct context *c, 444 void *object, 445 const char *name, 446 const svn_string_t *value, 447 apr_pool_t *pool) 448{ 449 return c->editor->change_file_prop(object, name, value, pool); 450} 451 452 453 454 455/* Constructing deltas for properties of files and directories. */ 456 457 458/* Generate the appropriate property editing calls to turn the 459 properties of SOURCE_PATH into those of TARGET_PATH. If 460 SOURCE_PATH is NULL, this is an add, so assume the target starts 461 with no properties. Pass OBJECT on to the editor function wrapper 462 CHANGE_FN. */ 463static svn_error_t * 464delta_proplists(struct context *c, 465 const char *source_path, 466 const char *target_path, 467 proplist_change_fn_t *change_fn, 468 void *object, 469 apr_pool_t *pool) 470{ 471 apr_hash_t *s_props = 0; 472 apr_hash_t *t_props = 0; 473 apr_pool_t *subpool; 474 apr_array_header_t *prop_diffs; 475 int i; 476 477 SVN_ERR_ASSERT(target_path); 478 479 /* Make a subpool for local allocations. */ 480 subpool = svn_pool_create(pool); 481 482 /* If we're supposed to send entry props for all non-deleted items, 483 here we go! */ 484 if (c->entry_props) 485 { 486 svn_revnum_t committed_rev = SVN_INVALID_REVNUM; 487 svn_string_t *cr_str = NULL; 488 svn_string_t *committed_date = NULL; 489 svn_string_t *last_author = NULL; 490 491 /* Get the CR and two derivative props. ### check for error returns. */ 492 SVN_ERR(svn_fs_node_created_rev(&committed_rev, c->target_root, 493 target_path, subpool)); 494 if (SVN_IS_VALID_REVNUM(committed_rev)) 495 { 496 svn_fs_t *fs = svn_fs_root_fs(c->target_root); 497 apr_hash_t *r_props; 498 const char *uuid; 499 500 /* Transmit the committed-rev. */ 501 cr_str = svn_string_createf(subpool, "%ld", 502 committed_rev); 503 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_REV, 504 cr_str, subpool)); 505 506 SVN_ERR(svn_fs_revision_proplist(&r_props, fs, committed_rev, 507 pool)); 508 509 /* Transmit the committed-date. */ 510 committed_date = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE); 511 if (committed_date || source_path) 512 { 513 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_COMMITTED_DATE, 514 committed_date, subpool)); 515 } 516 517 /* Transmit the last-author. */ 518 last_author = svn_hash_gets(r_props, SVN_PROP_REVISION_AUTHOR); 519 if (last_author || source_path) 520 { 521 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_LAST_AUTHOR, 522 last_author, subpool)); 523 } 524 525 /* Transmit the UUID. */ 526 SVN_ERR(svn_fs_get_uuid(fs, &uuid, subpool)); 527 SVN_ERR(change_fn(c, object, SVN_PROP_ENTRY_UUID, 528 svn_string_create(uuid, subpool), 529 subpool)); 530 } 531 } 532 533 if (source_path) 534 { 535 svn_boolean_t changed; 536 537 /* Is this deltification worth our time? */ 538 SVN_ERR(svn_fs_props_changed(&changed, c->target_root, target_path, 539 c->source_root, source_path, subpool)); 540 if (! changed) 541 goto cleanup; 542 543 /* If so, go ahead and get the source path's properties. */ 544 SVN_ERR(svn_fs_node_proplist(&s_props, c->source_root, 545 source_path, subpool)); 546 } 547 else 548 { 549 s_props = apr_hash_make(subpool); 550 } 551 552 /* Get the target path's properties */ 553 SVN_ERR(svn_fs_node_proplist(&t_props, c->target_root, 554 target_path, subpool)); 555 556 /* Now transmit the differences. */ 557 SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, subpool)); 558 for (i = 0; i < prop_diffs->nelts; i++) 559 { 560 const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t); 561 SVN_ERR(change_fn(c, object, pc->name, pc->value, subpool)); 562 } 563 564 cleanup: 565 /* Destroy local subpool. */ 566 svn_pool_destroy(subpool); 567 568 return SVN_NO_ERROR; 569} 570 571 572 573 574/* Constructing deltas for file contents. */ 575 576 577/* Change the contents of FILE_BATON in C->editor, according to the 578 text delta from DELTA_STREAM. Pass BASE_CHECKSUM along to 579 C->editor->apply_textdelta. */ 580static svn_error_t * 581send_text_delta(struct context *c, 582 void *file_baton, 583 const char *base_checksum, 584 svn_txdelta_stream_t *delta_stream, 585 apr_pool_t *pool) 586{ 587 svn_txdelta_window_handler_t delta_handler; 588 void *delta_handler_baton; 589 590 /* Get a handler that will apply the delta to the file. */ 591 SVN_ERR(c->editor->apply_textdelta 592 (file_baton, base_checksum, pool, 593 &delta_handler, &delta_handler_baton)); 594 595 if (c->text_deltas && delta_stream) 596 { 597 /* Deliver the delta stream to the file. */ 598 return svn_txdelta_send_txstream(delta_stream, 599 delta_handler, 600 delta_handler_baton, 601 pool); 602 } 603 else 604 { 605 /* The caller doesn't want text delta data. Just send a single 606 NULL window. */ 607 return delta_handler(NULL, delta_handler_baton); 608 } 609} 610 611svn_error_t * 612svn_repos__compare_files(svn_boolean_t *changed_p, 613 svn_fs_root_t *root1, 614 const char *path1, 615 svn_fs_root_t *root2, 616 const char *path2, 617 apr_pool_t *pool) 618{ 619 svn_filesize_t size1, size2; 620 svn_checksum_t *checksum1, *checksum2; 621 svn_stream_t *stream1, *stream2; 622 svn_boolean_t same; 623 624 /* If the filesystem claims the things haven't changed, then they 625 haven't changed. */ 626 SVN_ERR(svn_fs_contents_changed(changed_p, root1, path1, 627 root2, path2, pool)); 628 if (!*changed_p) 629 return SVN_NO_ERROR; 630 631 /* If the SHA1 checksums match for these things, we'll claim they 632 have the same contents. (We don't give quite as much weight to 633 MD5 checksums.) */ 634 SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_sha1, 635 root1, path1, FALSE, pool)); 636 SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_sha1, 637 root2, path2, FALSE, pool)); 638 if (checksum1 && checksum2) 639 { 640 *changed_p = !svn_checksum_match(checksum1, checksum2); 641 return SVN_NO_ERROR; 642 } 643 644 /* From this point on, our default answer is "Nothing's changed". */ 645 *changed_p = FALSE; 646 647 /* Different filesizes means the contents are different. */ 648 SVN_ERR(svn_fs_file_length(&size1, root1, path1, pool)); 649 SVN_ERR(svn_fs_file_length(&size2, root2, path2, pool)); 650 if (size1 != size2) 651 { 652 *changed_p = TRUE; 653 return SVN_NO_ERROR; 654 } 655 656 /* Different MD5 checksums means the contents are different. */ 657 SVN_ERR(svn_fs_file_checksum(&checksum1, svn_checksum_md5, root1, path1, 658 FALSE, pool)); 659 SVN_ERR(svn_fs_file_checksum(&checksum2, svn_checksum_md5, root2, path2, 660 FALSE, pool)); 661 if (! svn_checksum_match(checksum1, checksum2)) 662 { 663 *changed_p = TRUE; 664 return SVN_NO_ERROR; 665 } 666 667 /* And finally, different contents means the ... uh ... contents are 668 different. */ 669 SVN_ERR(svn_fs_file_contents(&stream1, root1, path1, pool)); 670 SVN_ERR(svn_fs_file_contents(&stream2, root2, path2, pool)); 671 SVN_ERR(svn_stream_contents_same2(&same, stream1, stream2, pool)); 672 *changed_p = !same; 673 674 return SVN_NO_ERROR; 675} 676 677 678/* Make the appropriate edits on FILE_BATON to change its contents and 679 properties from those in SOURCE_PATH to those in TARGET_PATH. */ 680static svn_error_t * 681delta_files(struct context *c, 682 void *file_baton, 683 const char *source_path, 684 const char *target_path, 685 apr_pool_t *pool) 686{ 687 apr_pool_t *subpool; 688 svn_boolean_t changed = TRUE; 689 690 SVN_ERR_ASSERT(target_path); 691 692 /* Make a subpool for local allocations. */ 693 subpool = svn_pool_create(pool); 694 695 /* Compare the files' property lists. */ 696 SVN_ERR(delta_proplists(c, source_path, target_path, 697 change_file_prop, file_baton, subpool)); 698 699 if (source_path) 700 { 701 /* Is this delta calculation worth our time? If we are ignoring 702 ancestry, then our editor implementor isn't concerned by the 703 theoretical differences between "has contents which have not 704 changed with respect to" and "has the same actual contents 705 as". We'll do everything we can to avoid transmitting even 706 an empty text-delta in that case. */ 707 if (c->ignore_ancestry) 708 SVN_ERR(svn_repos__compare_files(&changed, 709 c->target_root, target_path, 710 c->source_root, source_path, 711 subpool)); 712 else 713 SVN_ERR(svn_fs_contents_changed(&changed, 714 c->target_root, target_path, 715 c->source_root, source_path, 716 subpool)); 717 } 718 else 719 { 720 /* If there isn't a source path, this is an add, which 721 necessarily has textual mods. */ 722 } 723 724 /* If there is a change, and the context indicates that we should 725 care about it, then hand it off to a delta stream. */ 726 if (changed) 727 { 728 svn_txdelta_stream_t *delta_stream = NULL; 729 svn_checksum_t *source_checksum; 730 const char *source_hex_digest = NULL; 731 732 if (c->text_deltas) 733 { 734 /* Get a delta stream turning an empty file into one having 735 TARGET_PATH's contents. */ 736 SVN_ERR(svn_fs_get_file_delta_stream 737 (&delta_stream, 738 source_path ? c->source_root : NULL, 739 source_path ? source_path : NULL, 740 c->target_root, target_path, subpool)); 741 } 742 743 if (source_path) 744 { 745 SVN_ERR(svn_fs_file_checksum(&source_checksum, svn_checksum_md5, 746 c->source_root, source_path, TRUE, 747 subpool)); 748 749 source_hex_digest = svn_checksum_to_cstring(source_checksum, 750 subpool); 751 } 752 753 SVN_ERR(send_text_delta(c, file_baton, source_hex_digest, 754 delta_stream, subpool)); 755 } 756 757 /* Cleanup. */ 758 svn_pool_destroy(subpool); 759 760 return SVN_NO_ERROR; 761} 762 763 764 765 766/* Generic directory deltafication routines. */ 767 768 769/* Emit a delta to delete the entry named TARGET_ENTRY from DIR_BATON. */ 770static svn_error_t * 771delete(struct context *c, 772 void *dir_baton, 773 const char *edit_path, 774 apr_pool_t *pool) 775{ 776 return c->editor->delete_entry(edit_path, SVN_INVALID_REVNUM, 777 dir_baton, pool); 778} 779 780 781/* If authorized, emit a delta to create the entry named TARGET_ENTRY 782 at the location EDIT_PATH. If not authorized, indicate that 783 EDIT_PATH is absent. Pass DIR_BATON through to editor functions 784 that require it. DEPTH is the depth from this point downward. */ 785static svn_error_t * 786add_file_or_dir(struct context *c, void *dir_baton, 787 svn_depth_t depth, 788 const char *target_path, 789 const char *edit_path, 790 svn_node_kind_t tgt_kind, 791 apr_pool_t *pool) 792{ 793 struct context *context = c; 794 svn_boolean_t allowed; 795 796 SVN_ERR_ASSERT(target_path && edit_path); 797 798 if (c->authz_read_func) 799 { 800 SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path, 801 c->authz_read_baton, pool)); 802 if (!allowed) 803 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool); 804 } 805 806 if (tgt_kind == svn_node_dir) 807 { 808 void *subdir_baton; 809 810 SVN_ERR(context->editor->add_directory(edit_path, dir_baton, NULL, 811 SVN_INVALID_REVNUM, pool, 812 &subdir_baton)); 813 SVN_ERR(delta_dirs(context, subdir_baton, MAYBE_DEMOTE_DEPTH(depth), 814 NULL, target_path, edit_path, pool)); 815 return context->editor->close_directory(subdir_baton, pool); 816 } 817 else 818 { 819 void *file_baton; 820 svn_checksum_t *checksum; 821 822 SVN_ERR(context->editor->add_file(edit_path, dir_baton, 823 NULL, SVN_INVALID_REVNUM, pool, 824 &file_baton)); 825 SVN_ERR(delta_files(context, file_baton, NULL, target_path, pool)); 826 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 827 context->target_root, target_path, 828 TRUE, pool)); 829 return context->editor->close_file 830 (file_baton, svn_checksum_to_cstring(checksum, pool), pool); 831 } 832} 833 834 835/* If authorized, emit a delta to modify EDIT_PATH with the changes 836 from SOURCE_PATH to TARGET_PATH. If not authorized, indicate that 837 EDIT_PATH is absent. Pass DIR_BATON through to editor functions 838 that require it. DEPTH is the depth from this point downward. */ 839static svn_error_t * 840replace_file_or_dir(struct context *c, 841 void *dir_baton, 842 svn_depth_t depth, 843 const char *source_path, 844 const char *target_path, 845 const char *edit_path, 846 svn_node_kind_t tgt_kind, 847 apr_pool_t *pool) 848{ 849 svn_revnum_t base_revision = SVN_INVALID_REVNUM; 850 svn_boolean_t allowed; 851 852 SVN_ERR_ASSERT(target_path && source_path && edit_path); 853 854 if (c->authz_read_func) 855 { 856 SVN_ERR(c->authz_read_func(&allowed, c->target_root, target_path, 857 c->authz_read_baton, pool)); 858 if (!allowed) 859 return absent_file_or_dir(c, dir_baton, edit_path, tgt_kind, pool); 860 } 861 862 /* Get the base revision for the entry from the hash. */ 863 base_revision = get_path_revision(c->source_root, source_path, pool); 864 865 if (tgt_kind == svn_node_dir) 866 { 867 void *subdir_baton; 868 869 SVN_ERR(c->editor->open_directory(edit_path, dir_baton, 870 base_revision, pool, 871 &subdir_baton)); 872 SVN_ERR(delta_dirs(c, subdir_baton, MAYBE_DEMOTE_DEPTH(depth), 873 source_path, target_path, edit_path, pool)); 874 return c->editor->close_directory(subdir_baton, pool); 875 } 876 else 877 { 878 void *file_baton; 879 svn_checksum_t *checksum; 880 881 SVN_ERR(c->editor->open_file(edit_path, dir_baton, base_revision, 882 pool, &file_baton)); 883 SVN_ERR(delta_files(c, file_baton, source_path, target_path, pool)); 884 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 885 c->target_root, target_path, TRUE, 886 pool)); 887 return c->editor->close_file 888 (file_baton, svn_checksum_to_cstring(checksum, pool), pool); 889 } 890} 891 892 893/* In directory DIR_BATON, indicate that EDIT_PATH (relative to the 894 edit root) is absent by invoking C->editor->absent_directory or 895 C->editor->absent_file (depending on TGT_KIND). */ 896static svn_error_t * 897absent_file_or_dir(struct context *c, 898 void *dir_baton, 899 const char *edit_path, 900 svn_node_kind_t tgt_kind, 901 apr_pool_t *pool) 902{ 903 SVN_ERR_ASSERT(edit_path); 904 905 if (tgt_kind == svn_node_dir) 906 return c->editor->absent_directory(edit_path, dir_baton, pool); 907 else 908 return c->editor->absent_file(edit_path, dir_baton, pool); 909} 910 911 912/* Emit deltas to turn SOURCE_PATH into TARGET_PATH. Assume that 913 DIR_BATON represents the directory we're constructing to the editor 914 in the context C. */ 915static svn_error_t * 916delta_dirs(struct context *c, 917 void *dir_baton, 918 svn_depth_t depth, 919 const char *source_path, 920 const char *target_path, 921 const char *edit_path, 922 apr_pool_t *pool) 923{ 924 apr_hash_t *s_entries = 0, *t_entries = 0; 925 apr_hash_index_t *hi; 926 apr_pool_t *subpool; 927 928 SVN_ERR_ASSERT(target_path); 929 930 /* Compare the property lists. */ 931 SVN_ERR(delta_proplists(c, source_path, target_path, 932 change_dir_prop, dir_baton, pool)); 933 934 /* Get the list of entries in each of source and target. */ 935 SVN_ERR(svn_fs_dir_entries(&t_entries, c->target_root, 936 target_path, pool)); 937 if (source_path) 938 SVN_ERR(svn_fs_dir_entries(&s_entries, c->source_root, 939 source_path, pool)); 940 941 /* Make a subpool for local allocations. */ 942 subpool = svn_pool_create(pool); 943 944 /* Loop over the hash of entries in the target, searching for its 945 partner in the source. If we find the matching partner entry, 946 use editor calls to replace the one in target with a new version 947 if necessary, then remove that entry from the source entries 948 hash. If we can't find a related node in the source, we use 949 editor calls to add the entry as a new item in the target. 950 Having handled all the entries that exist in target, any entries 951 still remaining the source entries hash represent entries that no 952 longer exist in target. Use editor calls to delete those entries 953 from the target tree. */ 954 for (hi = apr_hash_first(pool, t_entries); hi; hi = apr_hash_next(hi)) 955 { 956 const svn_fs_dirent_t *s_entry, *t_entry; 957 const void *key; 958 void *val; 959 apr_ssize_t klen; 960 const char *t_fullpath; 961 const char *e_fullpath; 962 const char *s_fullpath; 963 svn_node_kind_t tgt_kind; 964 965 /* Clear out our subpool for the next iteration... */ 966 svn_pool_clear(subpool); 967 968 /* KEY is the entry name in target, VAL the dirent */ 969 apr_hash_this(hi, &key, &klen, &val); 970 t_entry = val; 971 tgt_kind = t_entry->kind; 972 t_fullpath = svn_relpath_join(target_path, t_entry->name, subpool); 973 e_fullpath = svn_relpath_join(edit_path, t_entry->name, subpool); 974 975 /* Can we find something with the same name in the source 976 entries hash? */ 977 if (s_entries && ((s_entry = apr_hash_get(s_entries, key, klen)) != 0)) 978 { 979 svn_node_kind_t src_kind; 980 981 s_fullpath = svn_relpath_join(source_path, t_entry->name, subpool); 982 src_kind = s_entry->kind; 983 984 if (depth == svn_depth_infinity 985 || src_kind != svn_node_dir 986 || (src_kind == svn_node_dir 987 && depth == svn_depth_immediates)) 988 { 989 /* Use svn_fs_compare_ids() to compare our current 990 source and target ids. 991 992 0: means they are the same id, and this is a noop. 993 -1: means they are unrelated, so we have to delete the 994 old one and add the new one. 995 1: means the nodes are related through ancestry, so go 996 ahead and do the replace directly. */ 997 int distance = svn_fs_compare_ids(s_entry->id, t_entry->id); 998 if (distance == 0) 999 { 1000 /* no-op */ 1001 } 1002 else if ((src_kind != tgt_kind) 1003 || ((distance == -1) && (! c->ignore_ancestry))) 1004 { 1005 SVN_ERR(delete(c, dir_baton, e_fullpath, subpool)); 1006 SVN_ERR(add_file_or_dir(c, dir_baton, 1007 MAYBE_DEMOTE_DEPTH(depth), 1008 t_fullpath, e_fullpath, tgt_kind, 1009 subpool)); 1010 } 1011 else 1012 { 1013 SVN_ERR(replace_file_or_dir(c, dir_baton, 1014 MAYBE_DEMOTE_DEPTH(depth), 1015 s_fullpath, t_fullpath, 1016 e_fullpath, tgt_kind, 1017 subpool)); 1018 } 1019 } 1020 1021 /* Remove the entry from the source_hash. */ 1022 svn_hash_sets(s_entries, key, NULL); 1023 } 1024 else 1025 { 1026 if (depth == svn_depth_infinity 1027 || tgt_kind != svn_node_dir 1028 || (tgt_kind == svn_node_dir 1029 && depth == svn_depth_immediates)) 1030 { 1031 SVN_ERR(add_file_or_dir(c, dir_baton, 1032 MAYBE_DEMOTE_DEPTH(depth), 1033 t_fullpath, e_fullpath, tgt_kind, 1034 subpool)); 1035 } 1036 } 1037 } 1038 1039 /* All that is left in the source entries hash are things that need 1040 to be deleted. Delete them. */ 1041 if (s_entries) 1042 { 1043 for (hi = apr_hash_first(pool, s_entries); hi; hi = apr_hash_next(hi)) 1044 { 1045 const svn_fs_dirent_t *s_entry; 1046 void *val; 1047 const char *e_fullpath; 1048 svn_node_kind_t src_kind; 1049 1050 /* Clear out our subpool for the next iteration... */ 1051 svn_pool_clear(subpool); 1052 1053 /* KEY is the entry name in source, VAL the dirent */ 1054 apr_hash_this(hi, NULL, NULL, &val); 1055 s_entry = val; 1056 src_kind = s_entry->kind; 1057 e_fullpath = svn_relpath_join(edit_path, s_entry->name, subpool); 1058 1059 /* Do we actually want to delete the dir if we're non-recursive? */ 1060 if (depth == svn_depth_infinity 1061 || src_kind != svn_node_dir 1062 || (src_kind == svn_node_dir 1063 && depth == svn_depth_immediates)) 1064 { 1065 SVN_ERR(delete(c, dir_baton, e_fullpath, subpool)); 1066 } 1067 } 1068 } 1069 1070 /* Destroy local allocation subpool. */ 1071 svn_pool_destroy(subpool); 1072 1073 return SVN_NO_ERROR; 1074} 1075