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