dump_editor.c revision 299742
1/* 2 * dump_editor.c: The svn_delta_editor_t editor used by svnrdump to 3 * dump revisions. 4 * 5 * ==================================================================== 6 * Licensed to the Apache Software Foundation (ASF) under one 7 * or more contributor license agreements. See the NOTICE file 8 * distributed with this work for additional information 9 * regarding copyright ownership. The ASF licenses this file 10 * to you under the Apache License, Version 2.0 (the 11 * "License"); you may not use this file except in compliance 12 * with the License. You may obtain a copy of the License at 13 * 14 * http://www.apache.org/licenses/LICENSE-2.0 15 * 16 * Unless required by applicable law or agreed to in writing, 17 * software distributed under the License is distributed on an 18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19 * KIND, either express or implied. See the License for the 20 * specific language governing permissions and limitations 21 * under the License. 22 * ==================================================================== 23 */ 24 25#include "svn_hash.h" 26#include "svn_pools.h" 27#include "svn_repos.h" 28#include "svn_path.h" 29#include "svn_props.h" 30#include "svn_subst.h" 31#include "svn_dirent_uri.h" 32 33#include "private/svn_repos_private.h" 34#include "private/svn_subr_private.h" 35#include "private/svn_dep_compat.h" 36#include "private/svn_editor.h" 37 38#include "svnrdump.h" 39#include <assert.h> 40 41#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) 42 43 44/* A directory baton used by all directory-related callback functions 45 * in the dump editor. */ 46struct dir_baton 47{ 48 struct dump_edit_baton *eb; 49 50 /* Pool for per-directory allocations */ 51 apr_pool_t *pool; 52 53 /* the path to this directory */ 54 const char *repos_relpath; /* a relpath */ 55 56 /* Copyfrom info for the node, if any. */ 57 const char *copyfrom_path; /* a relpath */ 58 svn_revnum_t copyfrom_rev; 59 60 /* Headers accumulated so far for this directory */ 61 svn_repos__dumpfile_headers_t *headers; 62 63 /* Properties which were modified during change_dir_prop. */ 64 apr_hash_t *props; 65 66 /* Properties which were deleted during change_dir_prop. */ 67 apr_hash_t *deleted_props; 68 69 /* Hash of paths that need to be deleted, though some -might- be 70 replaced. Maps const char * paths to this dir_baton. Note that 71 they're full paths, because that's what the editor driver gives 72 us, although they're all really within this directory. */ 73 apr_hash_t *deleted_entries; 74 75 /* Flag to trigger dumping props. */ 76 svn_boolean_t dump_props; 77}; 78 79/* A file baton used by all file-related callback functions in the dump 80 * editor */ 81struct file_baton 82{ 83 struct dump_edit_baton *eb; 84 85 /* Pool for per-file allocations */ 86 apr_pool_t *pool; 87 88 /* the path to this file */ 89 const char *repos_relpath; /* a relpath */ 90 91 /* Properties which were modified during change_file_prop. */ 92 apr_hash_t *props; 93 94 /* Properties which were deleted during change_file_prop. */ 95 apr_hash_t *deleted_props; 96 97 /* The checksum of the file the delta is being applied to */ 98 const char *base_checksum; 99 100 /* Copy state and source information (if any). */ 101 svn_boolean_t is_copy; 102 const char *copyfrom_path; 103 svn_revnum_t copyfrom_rev; 104 105 /* The action associate with this node. */ 106 enum svn_node_action action; 107 108 /* Flags to trigger dumping props and text. */ 109 svn_boolean_t dump_text; 110 svn_boolean_t dump_props; 111}; 112 113/* The baton used by the dump editor. */ 114struct dump_edit_baton { 115 /* The output stream we write the dumpfile to */ 116 svn_stream_t *stream; 117 118 /* A backdoor ra session to fetch additional information during the edit. */ 119 svn_ra_session_t *ra_session; 120 121 /* The repository relpath of the anchor of the editor when driven 122 via the RA update mechanism; NULL otherwise. (When the editor is 123 driven via the RA "replay" mechanism instead, the editor is 124 always anchored at the repository, we don't need to prepend an 125 anchor path to the dumped node paths, and open_root() doesn't 126 need to manufacture directory additions.) */ 127 const char *update_anchor_relpath; 128 129 /* Pool for per-revision allocations */ 130 apr_pool_t *pool; 131 132 /* Temporary file used for textdelta application along with its 133 absolute path; these two variables should be allocated in the 134 per-edit-session pool */ 135 const char *delta_abspath; 136 apr_file_t *delta_file; 137 138 /* The revision we're currently dumping. */ 139 svn_revnum_t current_revision; 140 141 /* The baton of the directory node whose block of 142 dump stream data has not been fully completed; NULL if there's no 143 such item. */ 144 struct dir_baton *pending_db; 145}; 146 147/* Make a directory baton to represent the directory at PATH (relative 148 * to the EDIT_BATON). 149 * 150 * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this 151 * directory should be compared for changes. If the copyfrom 152 * information is valid, the directory will be compared against its 153 * copy source. 154 * 155 * PB is the directory baton of this directory's parent, or NULL if 156 * this is the top-level directory of the edit. 157 * 158 * Perform all allocations in POOL. */ 159static struct dir_baton * 160make_dir_baton(const char *path, 161 const char *copyfrom_path, 162 svn_revnum_t copyfrom_rev, 163 void *edit_baton, 164 struct dir_baton *pb, 165 apr_pool_t *pool) 166{ 167 struct dump_edit_baton *eb = edit_baton; 168 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); 169 const char *repos_relpath; 170 171 /* Construct the full path of this node. */ 172 if (pb) 173 repos_relpath = svn_relpath_canonicalize(path, pool); 174 else 175 repos_relpath = ""; 176 177 /* Strip leading slash from copyfrom_path so that the path is 178 canonical and svn_relpath_join can be used */ 179 if (copyfrom_path) 180 copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool); 181 182 new_db->eb = eb; 183 new_db->pool = pool; 184 new_db->repos_relpath = repos_relpath; 185 new_db->copyfrom_path = copyfrom_path 186 ? svn_relpath_canonicalize(copyfrom_path, pool) 187 : NULL; 188 new_db->copyfrom_rev = copyfrom_rev; 189 new_db->headers = NULL; 190 new_db->props = apr_hash_make(pool); 191 new_db->deleted_props = apr_hash_make(pool); 192 new_db->deleted_entries = apr_hash_make(pool); 193 194 return new_db; 195} 196 197/* Make a file baton to represent the directory at PATH (relative to 198 * PB->eb). PB is the directory baton of this directory's parent, or 199 * NULL if this is the top-level directory of the edit. Perform all 200 * allocations in POOL. */ 201static struct file_baton * 202make_file_baton(const char *path, 203 struct dir_baton *pb, 204 apr_pool_t *pool) 205{ 206 struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb)); 207 208 new_fb->eb = pb->eb; 209 new_fb->pool = pool; 210 new_fb->repos_relpath = svn_relpath_canonicalize(path, pool); 211 new_fb->props = apr_hash_make(pool); 212 new_fb->deleted_props = apr_hash_make(pool); 213 new_fb->is_copy = FALSE; 214 new_fb->copyfrom_path = NULL; 215 new_fb->copyfrom_rev = SVN_INVALID_REVNUM; 216 new_fb->action = svn_node_action_change; 217 218 return new_fb; 219} 220 221/* Append to HEADERS the required headers, and set *CONTENT to the property 222 * content section, to represent the property delta of PROPS/DELETED_PROPS. 223 */ 224static svn_error_t * 225get_props_content(svn_repos__dumpfile_headers_t *headers, 226 svn_stringbuf_t **content, 227 apr_hash_t *props, 228 apr_hash_t *deleted_props, 229 apr_pool_t *result_pool, 230 apr_pool_t *scratch_pool) 231{ 232 svn_stream_t *content_stream; 233 apr_hash_t *normal_props; 234 235 *content = svn_stringbuf_create_empty(result_pool); 236 237 content_stream = svn_stream_from_stringbuf(*content, scratch_pool); 238 239 SVN_ERR(svn_rdump__normalize_props(&normal_props, props, scratch_pool)); 240 SVN_ERR(svn_hash_write_incremental(normal_props, deleted_props, 241 content_stream, "PROPS-END", 242 scratch_pool)); 243 SVN_ERR(svn_stream_close(content_stream)); 244 245 /* Prop-delta: true */ 246 svn_repos__dumpfile_header_push( 247 headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true"); 248 249 return SVN_NO_ERROR; 250} 251 252/* A special case of dump_node(), for a delete record. 253 * 254 * The only thing special about this version is it only writes one blank 255 * line, not two, after the headers. Why? Historical precedent for the 256 * case where a delete record is used as part of a (delete + add-with-history) 257 * in implementing a replacement. 258 */ 259static svn_error_t * 260dump_node_delete(svn_stream_t *stream, 261 const char *node_relpath, 262 apr_pool_t *pool) 263{ 264 svn_repos__dumpfile_headers_t *headers 265 = svn_repos__dumpfile_headers_create(pool); 266 267 assert(svn_relpath_is_canonical(node_relpath)); 268 269 /* Node-path: ... */ 270 svn_repos__dumpfile_header_push( 271 headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath); 272 273 /* Node-action: delete */ 274 svn_repos__dumpfile_header_push( 275 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); 276 277 SVN_ERR(svn_repos__dump_node_record(stream, headers, 278 NULL, FALSE, 0, /* props & text */ 279 FALSE /*content_length_always*/, pool)); 280 return SVN_NO_ERROR; 281} 282 283/* Set *HEADERS_P to contain some headers for the node at PATH of type KIND. 284 * 285 * ACTION describes what is happening to the node (see enum 286 * svn_node_action). 287 * 288 * If the node was itself copied, IS_COPY is TRUE and the 289 * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV. 290 * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this 291 * node is part of a copied subtree. 292 * 293 * Iff ACTION is svn_node_action_replace and IS_COPY, then first write a 294 * complete deletion record to the dump stream. 295 * 296 * If ACTION is svn_node_action_delete, then the node record will be 297 * complete. (The caller may want to write two blank lines after the 298 * header block.) 299 */ 300static svn_error_t * 301dump_node(svn_repos__dumpfile_headers_t **headers_p, 302 struct dump_edit_baton *eb, 303 const char *repos_relpath, 304 struct dir_baton *db, 305 struct file_baton *fb, 306 enum svn_node_action action, 307 svn_boolean_t is_copy, 308 const char *copyfrom_path, 309 svn_revnum_t copyfrom_rev, 310 apr_pool_t *pool) 311{ 312 const char *node_relpath = repos_relpath; 313 svn_repos__dumpfile_headers_t *headers 314 = svn_repos__dumpfile_headers_create(pool); 315 316 assert(svn_relpath_is_canonical(repos_relpath)); 317 assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path)); 318 assert(! (db && fb)); 319 320 /* Add the edit root relpath prefix if necessary. */ 321 if (eb->update_anchor_relpath) 322 node_relpath = svn_relpath_join(eb->update_anchor_relpath, 323 node_relpath, pool); 324 325 /* Node-path: ... */ 326 svn_repos__dumpfile_header_push( 327 headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath); 328 329 /* Node-kind: "file" | "dir" */ 330 if (fb) 331 svn_repos__dumpfile_header_push( 332 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file"); 333 else if (db) 334 svn_repos__dumpfile_header_push( 335 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir"); 336 337 338 /* Write the appropriate Node-action header */ 339 switch (action) 340 { 341 case svn_node_action_change: 342 /* We are here after a change_file_prop or change_dir_prop. They 343 set up whatever dump_props they needed to- nothing to 344 do here but print node action information. 345 346 Node-action: change. */ 347 svn_repos__dumpfile_header_push( 348 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change"); 349 break; 350 351 case svn_node_action_delete: 352 /* Node-action: delete */ 353 svn_repos__dumpfile_header_push( 354 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); 355 break; 356 357 case svn_node_action_replace: 358 if (! is_copy) 359 { 360 /* Node-action: replace */ 361 svn_repos__dumpfile_header_push( 362 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace"); 363 364 /* Wait for a change_*_prop to be called before dumping 365 anything */ 366 if (fb) 367 fb->dump_props = TRUE; 368 else if (db) 369 db->dump_props = TRUE; 370 break; 371 } 372 else 373 { 374 /* More complex case: is_copy is true, and copyfrom_path/ 375 copyfrom_rev are present: delete the original, and then re-add 376 it */ 377 /* ### Why not write a 'replace' record? Don't know. */ 378 379 /* ### Unusually, we end this 'delete' node record with only a single 380 blank line after the header block -- no extra blank line. */ 381 SVN_ERR(dump_node_delete(eb->stream, repos_relpath, pool)); 382 383 /* The remaining action is a non-replacing add-with-history */ 384 /* action = svn_node_action_add; */ 385 } 386 /* FALL THROUGH to 'add' */ 387 388 case svn_node_action_add: 389 /* Node-action: add */ 390 svn_repos__dumpfile_header_push( 391 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add"); 392 393 if (is_copy) 394 { 395 /* Node-copyfrom-rev / Node-copyfrom-path */ 396 svn_repos__dumpfile_header_pushf( 397 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", copyfrom_rev); 398 svn_repos__dumpfile_header_push( 399 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, copyfrom_path); 400 } 401 else 402 { 403 /* fb->dump_props (for files) is handled in close_file() 404 which is called immediately. 405 406 However, directories are not closed until all the work 407 inside them has been done; db->dump_props (for directories) 408 is handled (via dump_pending()) in all the functions that 409 can possibly be called after add_directory(): 410 411 - add_directory() 412 - open_directory() 413 - delete_entry() 414 - close_directory() 415 - add_file() 416 - open_file() 417 418 change_dir_prop() is a special case. */ 419 if (fb) 420 fb->dump_props = TRUE; 421 else if (db) 422 db->dump_props = TRUE; 423 } 424 425 break; 426 } 427 428 /* Return the headers so far. We don't necessarily have all the headers 429 yet -- there may be property-related and content length headers to 430 come, if this was not a 'delete' record. */ 431 *headers_p = headers; 432 return SVN_NO_ERROR; 433} 434 435static svn_error_t * 436dump_mkdir(struct dump_edit_baton *eb, 437 const char *repos_relpath, 438 apr_pool_t *pool) 439{ 440 svn_stringbuf_t *prop_content; 441 svn_repos__dumpfile_headers_t *headers 442 = svn_repos__dumpfile_headers_create(pool); 443 444 /* Node-path: ... */ 445 svn_repos__dumpfile_header_push( 446 headers, SVN_REPOS_DUMPFILE_NODE_PATH, repos_relpath); 447 448 /* Node-kind: dir */ 449 svn_repos__dumpfile_header_push( 450 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir"); 451 452 /* Node-action: add */ 453 svn_repos__dumpfile_header_push( 454 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add"); 455 456 /* Dump the (empty) property block. */ 457 SVN_ERR(get_props_content(headers, &prop_content, 458 apr_hash_make(pool), apr_hash_make(pool), 459 pool, pool)); 460 SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, prop_content, 461 FALSE, 0, FALSE /*content_length_always*/, 462 pool)); 463 464 /* Newlines to tie it all off. */ 465 SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); 466 467 return SVN_NO_ERROR; 468} 469 470/* Dump pending headers and properties for the directory EB->pending_db (if 471 * not null), to allow starting the dump of a child node */ 472static svn_error_t * 473dump_pending_dir(struct dump_edit_baton *eb, 474 apr_pool_t *scratch_pool) 475{ 476 struct dir_baton *db = eb->pending_db; 477 svn_stringbuf_t *prop_content = NULL; 478 479 if (! db) 480 return SVN_NO_ERROR; 481 482 /* Some pending properties to dump? */ 483 if (db->dump_props) 484 { 485 SVN_ERR(get_props_content(db->headers, &prop_content, 486 db->props, db->deleted_props, 487 scratch_pool, scratch_pool)); 488 } 489 SVN_ERR(svn_repos__dump_node_record(eb->stream, db->headers, prop_content, 490 FALSE, 0, FALSE /*content_length_always*/, 491 scratch_pool)); 492 493 /* No text is going to be dumped. Write a couple of newlines and 494 wait for the next node/ revision. */ 495 SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); 496 497 if (db->dump_props) 498 { 499 /* Cleanup so that data is never dumped twice. */ 500 apr_hash_clear(db->props); 501 apr_hash_clear(db->deleted_props); 502 db->dump_props = FALSE; 503 } 504 505 /* Anything that was pending is pending no longer. */ 506 eb->pending_db = NULL; 507 508 return SVN_NO_ERROR; 509} 510 511 512 513/*** Editor Function Implementations ***/ 514 515static svn_error_t * 516open_root(void *edit_baton, 517 svn_revnum_t base_revision, 518 apr_pool_t *pool, 519 void **root_baton) 520{ 521 struct dump_edit_baton *eb = edit_baton; 522 struct dir_baton *new_db = NULL; 523 524 /* Clear the per-revision pool after each revision */ 525 svn_pool_clear(eb->pool); 526 527 if (eb->update_anchor_relpath) 528 { 529 int i; 530 const char *parent_path = eb->update_anchor_relpath; 531 apr_array_header_t *dirs_to_add = 532 apr_array_make(pool, 4, sizeof(const char *)); 533 apr_pool_t *iterpool = svn_pool_create(pool); 534 535 while (! svn_path_is_empty(parent_path)) 536 { 537 APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path; 538 parent_path = svn_relpath_dirname(parent_path, pool); 539 } 540 541 for (i = dirs_to_add->nelts; i; --i) 542 { 543 const char *dir_to_add = 544 APR_ARRAY_IDX(dirs_to_add, i - 1, const char *); 545 546 svn_pool_clear(iterpool); 547 548 /* For parents of the source directory, we just manufacture 549 the adds ourselves. */ 550 if (i > 1) 551 { 552 SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool)); 553 } 554 else 555 { 556 /* ... but for the source directory itself, we'll defer 557 to letting the typical plumbing handle this task. */ 558 new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, 559 edit_baton, NULL, pool); 560 SVN_ERR(dump_node(&new_db->headers, 561 eb, new_db->repos_relpath, new_db, 562 NULL, svn_node_action_add, FALSE, 563 NULL, SVN_INVALID_REVNUM, pool)); 564 565 /* Remember that we've started but not yet finished 566 handling this directory. */ 567 eb->pending_db = new_db; 568 } 569 } 570 svn_pool_destroy(iterpool); 571 } 572 573 if (! new_db) 574 { 575 new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, 576 edit_baton, NULL, pool); 577 } 578 579 *root_baton = new_db; 580 return SVN_NO_ERROR; 581} 582 583static svn_error_t * 584delete_entry(const char *path, 585 svn_revnum_t revision, 586 void *parent_baton, 587 apr_pool_t *pool) 588{ 589 struct dir_baton *pb = parent_baton; 590 591 SVN_ERR(dump_pending_dir(pb->eb, pool)); 592 593 /* We don't dump this deletion immediate. Rather, we add this path 594 to the deleted_entries of the parent directory baton. That way, 595 we can tell (later) an addition from a replacement. All the real 596 deletions get handled in close_directory(). */ 597 svn_hash_sets(pb->deleted_entries, apr_pstrdup(pb->pool, path), pb); 598 599 return SVN_NO_ERROR; 600} 601 602static svn_error_t * 603add_directory(const char *path, 604 void *parent_baton, 605 const char *copyfrom_path, 606 svn_revnum_t copyfrom_rev, 607 apr_pool_t *pool, 608 void **child_baton) 609{ 610 struct dir_baton *pb = parent_baton; 611 void *was_deleted; 612 struct dir_baton *new_db; 613 svn_boolean_t is_copy; 614 615 SVN_ERR(dump_pending_dir(pb->eb, pool)); 616 617 new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, 618 pb, pb->pool); 619 620 /* This might be a replacement -- is the path already deleted? */ 621 was_deleted = svn_hash_gets(pb->deleted_entries, path); 622 623 /* Detect an add-with-history */ 624 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); 625 626 /* Dump the node */ 627 SVN_ERR(dump_node(&new_db->headers, 628 pb->eb, new_db->repos_relpath, new_db, NULL, 629 was_deleted ? svn_node_action_replace : svn_node_action_add, 630 is_copy, 631 is_copy ? new_db->copyfrom_path : NULL, 632 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, 633 pool)); 634 635 if (was_deleted) 636 /* Delete the path, it's now been dumped */ 637 svn_hash_sets(pb->deleted_entries, path, NULL); 638 639 /* Remember that we've started, but not yet finished handling this 640 directory. */ 641 pb->eb->pending_db = new_db; 642 643 *child_baton = new_db; 644 return SVN_NO_ERROR; 645} 646 647static svn_error_t * 648open_directory(const char *path, 649 void *parent_baton, 650 svn_revnum_t base_revision, 651 apr_pool_t *pool, 652 void **child_baton) 653{ 654 struct dir_baton *pb = parent_baton; 655 struct dir_baton *new_db; 656 const char *copyfrom_path = NULL; 657 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; 658 659 SVN_ERR(dump_pending_dir(pb->eb, pool)); 660 661 /* If the parent directory has explicit comparison path and rev, 662 record the same for this one. */ 663 if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev)) 664 { 665 copyfrom_path = svn_relpath_join(pb->copyfrom_path, 666 svn_relpath_basename(path, NULL), 667 pb->pool); 668 copyfrom_rev = pb->copyfrom_rev; 669 } 670 671 new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb, 672 pb->pool); 673 674 *child_baton = new_db; 675 return SVN_NO_ERROR; 676} 677 678static svn_error_t * 679close_directory(void *dir_baton, 680 apr_pool_t *pool) 681{ 682 struct dir_baton *db = dir_baton; 683 apr_hash_index_t *hi; 684 svn_boolean_t this_pending; 685 686 /* Remember if this directory is the one currently pending. */ 687 this_pending = (db->eb->pending_db == db); 688 689 SVN_ERR(dump_pending_dir(db->eb, pool)); 690 691 /* If this directory was pending, then dump_pending() should have 692 taken care of all the props and such. Of course, the only way 693 that would be the case is if this directory was added/replaced. 694 695 Otherwise, if stuff for this directory has already been written 696 out (at some point in the past, prior to our handling other 697 nodes), we might need to generate a second "change" record just 698 to carry the information we've since learned about the 699 directory. */ 700 if ((! this_pending) && (db->dump_props)) 701 { 702 SVN_ERR(dump_node(&db->headers, 703 db->eb, db->repos_relpath, db, NULL, 704 svn_node_action_change, FALSE, 705 NULL, SVN_INVALID_REVNUM, pool)); 706 db->eb->pending_db = db; 707 SVN_ERR(dump_pending_dir(db->eb, pool)); 708 } 709 710 /* Dump the deleted directory entries */ 711 for (hi = apr_hash_first(pool, db->deleted_entries); hi; 712 hi = apr_hash_next(hi)) 713 { 714 const char *path = apr_hash_this_key(hi); 715 716 SVN_ERR(dump_node_delete(db->eb->stream, path, pool)); 717 /* This deletion record is complete -- write an extra newline */ 718 SVN_ERR(svn_stream_puts(db->eb->stream, "\n")); 719 } 720 721 /* ### should be unnecessary */ 722 apr_hash_clear(db->deleted_entries); 723 724 return SVN_NO_ERROR; 725} 726 727static svn_error_t * 728add_file(const char *path, 729 void *parent_baton, 730 const char *copyfrom_path, 731 svn_revnum_t copyfrom_rev, 732 apr_pool_t *pool, 733 void **file_baton) 734{ 735 struct dir_baton *pb = parent_baton; 736 struct file_baton *fb; 737 void *was_deleted; 738 739 SVN_ERR(dump_pending_dir(pb->eb, pool)); 740 741 /* Make the file baton. */ 742 fb = make_file_baton(path, pb, pool); 743 744 /* This might be a replacement -- is the path already deleted? */ 745 was_deleted = svn_hash_gets(pb->deleted_entries, path); 746 747 /* Detect add-with-history. */ 748 if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev)) 749 { 750 fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool); 751 fb->copyfrom_rev = copyfrom_rev; 752 fb->is_copy = TRUE; 753 } 754 fb->action = was_deleted ? svn_node_action_replace : svn_node_action_add; 755 756 /* Delete the path, it's now been dumped. */ 757 if (was_deleted) 758 svn_hash_sets(pb->deleted_entries, path, NULL); 759 760 *file_baton = fb; 761 return SVN_NO_ERROR; 762} 763 764static svn_error_t * 765open_file(const char *path, 766 void *parent_baton, 767 svn_revnum_t ancestor_revision, 768 apr_pool_t *pool, 769 void **file_baton) 770{ 771 struct dir_baton *pb = parent_baton; 772 struct file_baton *fb; 773 774 SVN_ERR(dump_pending_dir(pb->eb, pool)); 775 776 /* Make the file baton. */ 777 fb = make_file_baton(path, pb, pool); 778 779 /* If the parent directory has explicit copyfrom path and rev, 780 record the same for this one. */ 781 if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev)) 782 { 783 fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path, 784 svn_relpath_basename(path, NULL), 785 pb->pool); 786 fb->copyfrom_rev = pb->copyfrom_rev; 787 } 788 789 *file_baton = fb; 790 return SVN_NO_ERROR; 791} 792 793static svn_error_t * 794change_dir_prop(void *parent_baton, 795 const char *name, 796 const svn_string_t *value, 797 apr_pool_t *pool) 798{ 799 struct dir_baton *db = parent_baton; 800 svn_boolean_t this_pending; 801 802 /* This directory is not pending, but something else is, so handle 803 the "something else". */ 804 this_pending = (db->eb->pending_db == db); 805 if (! this_pending) 806 SVN_ERR(dump_pending_dir(db->eb, pool)); 807 808 if (svn_property_kind2(name) != svn_prop_regular_kind) 809 return SVN_NO_ERROR; 810 811 if (value) 812 svn_hash_sets(db->props, 813 apr_pstrdup(db->pool, name), 814 svn_string_dup(value, db->pool)); 815 else 816 svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), ""); 817 818 /* Make sure we eventually output the props */ 819 db->dump_props = TRUE; 820 821 return SVN_NO_ERROR; 822} 823 824static svn_error_t * 825change_file_prop(void *file_baton, 826 const char *name, 827 const svn_string_t *value, 828 apr_pool_t *pool) 829{ 830 struct file_baton *fb = file_baton; 831 832 if (svn_property_kind2(name) != svn_prop_regular_kind) 833 return SVN_NO_ERROR; 834 835 if (value) 836 svn_hash_sets(fb->props, 837 apr_pstrdup(fb->pool, name), 838 svn_string_dup(value, fb->pool)); 839 else 840 svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), ""); 841 842 /* Dump the property headers and wait; close_file might need 843 to write text headers too depending on whether 844 apply_textdelta is called */ 845 fb->dump_props = TRUE; 846 847 return SVN_NO_ERROR; 848} 849 850static svn_error_t * 851apply_textdelta(void *file_baton, const char *base_checksum, 852 apr_pool_t *pool, 853 svn_txdelta_window_handler_t *handler, 854 void **handler_baton) 855{ 856 struct file_baton *fb = file_baton; 857 struct dump_edit_baton *eb = fb->eb; 858 svn_stream_t *delta_filestream; 859 860 /* Use a temporary file to measure the Text-content-length */ 861 delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool); 862 863 /* Prepare to write the delta to the delta_filestream */ 864 svn_txdelta_to_svndiff3(handler, handler_baton, 865 delta_filestream, 0, 866 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); 867 868 /* Record that there's text to be dumped, and its base checksum. */ 869 fb->dump_text = TRUE; 870 fb->base_checksum = apr_pstrdup(fb->pool, base_checksum); 871 872 return SVN_NO_ERROR; 873} 874 875static svn_error_t * 876close_file(void *file_baton, 877 const char *text_checksum, 878 apr_pool_t *pool) 879{ 880 struct file_baton *fb = file_baton; 881 struct dump_edit_baton *eb = fb->eb; 882 apr_finfo_t *info = apr_pcalloc(pool, sizeof(apr_finfo_t)); 883 svn_stringbuf_t *propstring = NULL; 884 svn_repos__dumpfile_headers_t *headers; 885 886 SVN_ERR(dump_pending_dir(eb, pool)); 887 888 /* Start dumping this node, by collecting some basic headers for it. */ 889 SVN_ERR(dump_node(&headers, eb, fb->repos_relpath, NULL, fb, 890 fb->action, fb->is_copy, fb->copyfrom_path, 891 fb->copyfrom_rev, pool)); 892 893 /* Some pending properties to dump? We'll dump just the headers for 894 now, then dump the actual propchange content only after dumping 895 the text headers too (if present). */ 896 if (fb->dump_props) 897 { 898 SVN_ERR(get_props_content(headers, &propstring, 899 fb->props, fb->deleted_props, 900 pool, pool)); 901 } 902 903 /* Dump the text headers */ 904 if (fb->dump_text) 905 { 906 apr_status_t err; 907 908 /* Text-delta: true */ 909 svn_repos__dumpfile_header_push( 910 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true"); 911 912 err = apr_file_info_get(info, APR_FINFO_SIZE, eb->delta_file); 913 if (err) 914 SVN_ERR(svn_error_wrap_apr(err, NULL)); 915 916 if (fb->base_checksum) 917 /* Text-delta-base-md5: */ 918 svn_repos__dumpfile_header_push( 919 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, fb->base_checksum); 920 921 /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */ 922 svn_repos__dumpfile_header_push( 923 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, text_checksum); 924 } 925 926 /* Dump the headers and props now */ 927 SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, propstring, 928 fb->dump_text, info->size, 929 FALSE /*content_length_always*/, 930 pool)); 931 932 if (fb->dump_props) 933 { 934 /* Cleanup */ 935 fb->dump_props = FALSE; 936 apr_hash_clear(fb->props); 937 apr_hash_clear(fb->deleted_props); 938 } 939 940 /* Dump the text */ 941 if (fb->dump_text) 942 { 943 /* Seek to the beginning of the delta file, map it to a stream, 944 and copy the stream to eb->stream. Then close the stream and 945 truncate the file so we can reuse it for the next textdelta 946 application. Note that the file isn't created, opened or 947 closed here */ 948 svn_stream_t *delta_filestream; 949 apr_off_t offset = 0; 950 951 SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool)); 952 delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool); 953 SVN_ERR(svn_stream_copy3(delta_filestream, eb->stream, NULL, NULL, pool)); 954 955 /* Cleanup */ 956 SVN_ERR(svn_stream_close(delta_filestream)); 957 SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool)); 958 } 959 960 /* Write a couple of blank lines for matching output with `svnadmin 961 dump` */ 962 SVN_ERR(svn_stream_puts(eb->stream, "\n\n")); 963 964 return SVN_NO_ERROR; 965} 966 967static svn_error_t * 968close_edit(void *edit_baton, apr_pool_t *pool) 969{ 970 return SVN_NO_ERROR; 971} 972 973static svn_error_t * 974fetch_base_func(const char **filename, 975 void *baton, 976 const char *path, 977 svn_revnum_t base_revision, 978 apr_pool_t *result_pool, 979 apr_pool_t *scratch_pool) 980{ 981 struct dump_edit_baton *eb = baton; 982 svn_stream_t *fstream; 983 svn_error_t *err; 984 985 if (path[0] == '/') 986 path += 1; 987 988 if (! SVN_IS_VALID_REVNUM(base_revision)) 989 base_revision = eb->current_revision - 1; 990 991 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, 992 svn_io_file_del_on_pool_cleanup, 993 result_pool, scratch_pool)); 994 995 err = svn_ra_get_file(eb->ra_session, path, base_revision, 996 fstream, NULL, NULL, scratch_pool); 997 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 998 { 999 svn_error_clear(err); 1000 SVN_ERR(svn_stream_close(fstream)); 1001 1002 *filename = NULL; 1003 return SVN_NO_ERROR; 1004 } 1005 else if (err) 1006 return svn_error_trace(err); 1007 1008 SVN_ERR(svn_stream_close(fstream)); 1009 1010 return SVN_NO_ERROR; 1011} 1012 1013static svn_error_t * 1014fetch_props_func(apr_hash_t **props, 1015 void *baton, 1016 const char *path, 1017 svn_revnum_t base_revision, 1018 apr_pool_t *result_pool, 1019 apr_pool_t *scratch_pool) 1020{ 1021 struct dump_edit_baton *eb = baton; 1022 svn_node_kind_t node_kind; 1023 1024 if (path[0] == '/') 1025 path += 1; 1026 1027 if (! SVN_IS_VALID_REVNUM(base_revision)) 1028 base_revision = eb->current_revision - 1; 1029 1030 SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind, 1031 scratch_pool)); 1032 1033 if (node_kind == svn_node_file) 1034 { 1035 SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision, 1036 NULL, NULL, props, result_pool)); 1037 } 1038 else if (node_kind == svn_node_dir) 1039 { 1040 apr_array_header_t *tmp_props; 1041 1042 SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path, 1043 base_revision, 0 /* Dirent fields */, 1044 result_pool)); 1045 tmp_props = svn_prop_hash_to_array(*props, result_pool); 1046 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, 1047 result_pool)); 1048 *props = svn_prop_array_to_hash(tmp_props, result_pool); 1049 } 1050 else 1051 { 1052 *props = apr_hash_make(result_pool); 1053 } 1054 1055 return SVN_NO_ERROR; 1056} 1057 1058static svn_error_t * 1059fetch_kind_func(svn_node_kind_t *kind, 1060 void *baton, 1061 const char *path, 1062 svn_revnum_t base_revision, 1063 apr_pool_t *scratch_pool) 1064{ 1065 struct dump_edit_baton *eb = baton; 1066 1067 if (path[0] == '/') 1068 path += 1; 1069 1070 if (! SVN_IS_VALID_REVNUM(base_revision)) 1071 base_revision = eb->current_revision - 1; 1072 1073 SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind, 1074 scratch_pool)); 1075 1076 return SVN_NO_ERROR; 1077} 1078 1079svn_error_t * 1080svn_rdump__get_dump_editor(const svn_delta_editor_t **editor, 1081 void **edit_baton, 1082 svn_revnum_t revision, 1083 svn_stream_t *stream, 1084 svn_ra_session_t *ra_session, 1085 const char *update_anchor_relpath, 1086 svn_cancel_func_t cancel_func, 1087 void *cancel_baton, 1088 apr_pool_t *pool) 1089{ 1090 struct dump_edit_baton *eb; 1091 svn_delta_editor_t *de; 1092 svn_delta_shim_callbacks_t *shim_callbacks = 1093 svn_delta_shim_callbacks_default(pool); 1094 1095 eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton)); 1096 eb->stream = stream; 1097 eb->ra_session = ra_session; 1098 eb->update_anchor_relpath = update_anchor_relpath; 1099 eb->current_revision = revision; 1100 eb->pending_db = NULL; 1101 1102 /* Create a special per-revision pool */ 1103 eb->pool = svn_pool_create(pool); 1104 1105 /* Open a unique temporary file for all textdelta applications in 1106 this edit session. The file is automatically closed and cleaned 1107 up when the edit session is done. */ 1108 SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath), 1109 NULL, svn_io_file_del_on_close, pool, pool)); 1110 1111 de = svn_delta_default_editor(pool); 1112 de->open_root = open_root; 1113 de->delete_entry = delete_entry; 1114 de->add_directory = add_directory; 1115 de->open_directory = open_directory; 1116 de->close_directory = close_directory; 1117 de->change_dir_prop = change_dir_prop; 1118 de->change_file_prop = change_file_prop; 1119 de->apply_textdelta = apply_textdelta; 1120 de->add_file = add_file; 1121 de->open_file = open_file; 1122 de->close_file = close_file; 1123 de->close_edit = close_edit; 1124 1125 /* Set the edit_baton and editor. */ 1126 *edit_baton = eb; 1127 *editor = de; 1128 1129 /* Wrap this editor in a cancellation editor. */ 1130 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, 1131 de, eb, editor, edit_baton, pool)); 1132 1133 shim_callbacks->fetch_base_func = fetch_base_func; 1134 shim_callbacks->fetch_props_func = fetch_props_func; 1135 shim_callbacks->fetch_kind_func = fetch_kind_func; 1136 shim_callbacks->fetch_baton = eb; 1137 1138 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, 1139 NULL, NULL, shim_callbacks, pool, pool)); 1140 1141 return SVN_NO_ERROR; 1142} 1143