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