editor.c revision 362181
1/* 2 * editor.c: Editor for modifying FS transactions 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 <apr_pools.h> 25 26#include "svn_types.h" 27#include "svn_error.h" 28#include "svn_pools.h" 29#include "svn_fs.h" 30#include "svn_props.h" 31#include "svn_path.h" 32 33#include "svn_private_config.h" 34 35#include "fs-loader.h" 36 37#include "private/svn_fspath.h" 38#include "private/svn_fs_private.h" 39#include "private/svn_editor.h" 40 41 42struct edit_baton { 43 /* The transaction associated with this editor. */ 44 svn_fs_txn_t *txn; 45 46 /* Has this editor been completed? */ 47 svn_boolean_t completed; 48 49 /* We sometimes need the cancellation beyond what svn_editor_t provides */ 50 svn_cancel_func_t cancel_func; 51 void *cancel_baton; 52 53 /* The pool that the txn lives within. When we create a ROOT, it will 54 be allocated within a subpool of this. The root will be closed in 55 complete/abort and that subpool will be destroyed. 56 57 This pool SHOULD NOT be used for any allocations. */ 58 apr_pool_t *txn_pool; 59 60 /* This is the root from the txn. Use get_root() to fetch/create this 61 member as appropriate. */ 62 svn_fs_root_t *root; 63}; 64 65#define FSPATH(relpath, pool) apr_pstrcat(pool, "/", relpath, SVN_VA_NULL) 66 67static svn_error_t * 68get_root(svn_fs_root_t **root, 69 struct edit_baton *eb) 70{ 71 if (eb->root == NULL) 72 SVN_ERR(svn_fs_txn_root(&eb->root, eb->txn, eb->txn_pool)); 73 *root = eb->root; 74 return SVN_NO_ERROR; 75} 76 77 78/* Apply each property in PROPS to the node at FSPATH in ROOT. */ 79static svn_error_t * 80add_new_props(svn_fs_root_t *root, 81 const char *fspath, 82 apr_hash_t *props, 83 apr_pool_t *scratch_pool) 84{ 85 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 86 apr_hash_index_t *hi; 87 88 /* ### it would be nice to have svn_fs_set_node_props(). but since we 89 ### don't... add each property to the node. this is a new node, so 90 ### we don't need to worry about deleting props. just adding. */ 91 92 for (hi = apr_hash_first(scratch_pool, props); hi; 93 hi = apr_hash_next(hi)) 94 { 95 const char *name = apr_hash_this_key(hi); 96 const svn_string_t *value = apr_hash_this_val(hi); 97 98 svn_pool_clear(iterpool); 99 100 SVN_ERR(svn_fs_change_node_prop(root, fspath, name, value, iterpool)); 101 } 102 103 svn_pool_destroy(iterpool); 104 return SVN_NO_ERROR; 105} 106 107 108static svn_error_t * 109alter_props(svn_fs_root_t *root, 110 const char *fspath, 111 apr_hash_t *props, 112 apr_pool_t *scratch_pool) 113{ 114 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 115 apr_hash_t *old_props; 116 apr_array_header_t *propdiffs; 117 int i; 118 119 SVN_ERR(svn_fs_node_proplist(&old_props, root, fspath, scratch_pool)); 120 121 SVN_ERR(svn_prop_diffs(&propdiffs, props, old_props, scratch_pool)); 122 123 for (i = 0; i < propdiffs->nelts; ++i) 124 { 125 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t); 126 127 svn_pool_clear(iterpool); 128 129 /* Add, change, or delete properties. */ 130 SVN_ERR(svn_fs_change_node_prop(root, fspath, prop->name, prop->value, 131 iterpool)); 132 } 133 134 svn_pool_destroy(iterpool); 135 return SVN_NO_ERROR; 136} 137 138 139static svn_error_t * 140set_text(svn_fs_root_t *root, 141 const char *fspath, 142 const svn_checksum_t *checksum, 143 svn_stream_t *contents, 144 svn_cancel_func_t cancel_func, 145 void *cancel_baton, 146 apr_pool_t *scratch_pool) 147{ 148 svn_stream_t *fs_contents; 149 150 /* ### We probably don't have an MD5 checksum, so no digest is available 151 ### for svn_fs_apply_text() to validate. It would be nice to have an 152 ### FS API that takes our CHECKSUM/CONTENTS pair (and PROPS!). */ 153 SVN_ERR(svn_fs_apply_text(&fs_contents, root, fspath, 154 NULL /* result_checksum */, 155 scratch_pool)); 156 SVN_ERR(svn_stream_copy3(contents, fs_contents, 157 cancel_func, cancel_baton, 158 scratch_pool)); 159 160 return SVN_NO_ERROR; 161} 162 163 164/* The caller wants to modify REVISION of FSPATH. Is that allowed? */ 165static svn_error_t * 166can_modify(svn_fs_root_t *txn_root, 167 const char *fspath, 168 svn_revnum_t revision, 169 apr_pool_t *scratch_pool) 170{ 171 svn_revnum_t created_rev; 172 173 /* Out-of-dateness check: compare the created-rev of the node 174 in the txn against the created-rev of FSPATH. */ 175 SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, fspath, 176 scratch_pool)); 177 178 /* Uncommitted nodes (eg. a descendant of a copy/move destination) 179 have no (committed) revision number. Let the caller go ahead and 180 modify these nodes. 181 182 Note: strictly speaking, they might be performing an "illegal" edit 183 in certain cases, but let's just assume they're Good Little Boys. 184 185 If CREATED_REV is invalid, that means it's already mutable in the 186 txn, which means it has already passed this out-of-dateness check. 187 (Usually, this happens when looking at a parent directory of an 188 already-modified node) */ 189 if (!SVN_IS_VALID_REVNUM(created_rev)) 190 return SVN_NO_ERROR; 191 192 /* If the node is immutable (has a revision), then the caller should 193 have supplied a valid revision number [that they expect to change]. 194 The checks further below will determine the out-of-dateness of the 195 specified revision. */ 196 /* ### ugh. descendants of copy/move destinations carry along 197 ### their original immutable state and (thus) a valid CREATED_REV. 198 ### but they are logically uncommitted, so the caller will pass 199 ### SVN_INVALID_REVNUM. (technically, the caller could provide 200 ### ORIGINAL_REV, but that is semantically incorrect for the Ev2 201 ### API). 202 ### 203 ### for now, we will assume the caller knows what they are doing 204 ### and an invalid revision implies such a descendant. in the 205 ### future, we could examine the ancestor chain looking for a 206 ### copy/move-here node and allow the modification (and the 207 ### converse: if no such ancestor, the caller must specify the 208 ### correct/intended revision to modify). 209 */ 210#if 1 211 if (!SVN_IS_VALID_REVNUM(revision)) 212 return SVN_NO_ERROR; 213#else 214 if (!SVN_IS_VALID_REVNUM(revision)) 215 /* ### use a custom error code? */ 216 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 217 _("Revision for modifying '%s' is required"), 218 fspath); 219#endif 220 221 if (revision < created_rev) 222 { 223 /* We asked to change a node that is *older* than what we found 224 in the transaction. The client is out of date. */ 225 return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL, 226 _("'%s' is out of date; try updating"), 227 fspath); 228 } 229 230 if (revision > created_rev) 231 { 232 /* We asked to change a node that is *newer* than what we found 233 in the transaction. Given that the transaction was based off 234 of 'youngest', then either: 235 - the caller asked to modify a future node 236 - the caller has committed more revisions since this txn 237 was constructed, and is asking to modify a node in one 238 of those new revisions. 239 In either case, the node may not have changed in those new 240 revisions; use the node's ID to determine this case. */ 241 svn_fs_root_t *rev_root; 242 svn_fs_node_relation_t relation; 243 244 /* Get the ID from the future/new revision. */ 245 SVN_ERR(svn_fs_revision_root(&rev_root, svn_fs_root_fs(txn_root), 246 revision, scratch_pool)); 247 SVN_ERR(svn_fs_node_relation(&relation, txn_root, fspath, rev_root, 248 fspath, scratch_pool)); 249 svn_fs_close_root(rev_root); 250 251 /* Has the target node changed in the future? */ 252 if (relation != svn_fs_node_unchanged) 253 { 254 /* Restarting the commit will base the txn on the future/new 255 revision, allowing the modification at REVISION. */ 256 /* ### use a custom error code */ 257 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, 258 _("'%s' has been modified since the " 259 "commit began (restart the commit)"), 260 fspath); 261 } 262 } 263 264 return SVN_NO_ERROR; 265} 266 267 268/* Can we create a node at FSPATH in TXN_ROOT? If something already exists 269 at that path, then the client MAY be out of date. We then have to see if 270 the path was created/modified in this transaction. IOW, it is new and 271 can be replaced without problem. 272 273 Note: the editor protocol disallows double-modifications. This is to 274 ensure somebody does not accidentally overwrite another file due to 275 being out-of-date. */ 276static svn_error_t * 277can_create(svn_fs_root_t *txn_root, 278 const char *fspath, 279 apr_pool_t *scratch_pool) 280{ 281 svn_node_kind_t kind; 282 const char *cur_fspath; 283 284 SVN_ERR(svn_fs_check_path(&kind, txn_root, fspath, scratch_pool)); 285 if (kind == svn_node_none) 286 return SVN_NO_ERROR; 287 288 /* ### I'm not sure if this works perfectly. We might have an ancestor 289 ### that was modified as a result of a change on a cousin. We might 290 ### misinterpret that as a *-here node which brought along this 291 ### child. Need to write a test to verify. We may also be able to 292 ### test the ancestor to determine if it has been *-here in this 293 ### txn, or just a simple modification. */ 294 295 /* Are any of the parents copied/moved-here? */ 296 for (cur_fspath = fspath; 297 strlen(cur_fspath) > 1; /* not the root */ 298 cur_fspath = svn_fspath__dirname(cur_fspath, scratch_pool)) 299 { 300 svn_revnum_t created_rev; 301 302 SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, cur_fspath, 303 scratch_pool)); 304 if (!SVN_IS_VALID_REVNUM(created_rev)) 305 { 306 /* The node has no created revision, meaning it is uncommitted. 307 Thus, it was created in this transaction, or it has already 308 been modified in some way (implying it has already passed a 309 modification check. */ 310 /* ### verify the node has been *-here ?? */ 311 return SVN_NO_ERROR; 312 } 313 } 314 315 return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL, 316 _("'%s' already exists, so may be out" 317 " of date; try updating"), 318 fspath); 319} 320 321 322/* This implements svn_editor_cb_add_directory_t */ 323static svn_error_t * 324add_directory_cb(void *baton, 325 const char *relpath, 326 const apr_array_header_t *children, 327 apr_hash_t *props, 328 svn_revnum_t replaces_rev, 329 apr_pool_t *scratch_pool) 330{ 331 struct edit_baton *eb = baton; 332 const char *fspath = FSPATH(relpath, scratch_pool); 333 svn_fs_root_t *root; 334 335 /* Note: we ignore CHILDREN. We have no "incomplete" state to worry about, 336 so we don't need to be aware of what children will be created. */ 337 338 SVN_ERR(get_root(&root, eb)); 339 340 if (SVN_IS_VALID_REVNUM(replaces_rev)) 341 { 342 SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool)); 343 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool)); 344 } 345 else 346 { 347 SVN_ERR(can_create(root, fspath, scratch_pool)); 348 } 349 350 SVN_ERR(svn_fs_make_dir(root, fspath, scratch_pool)); 351 SVN_ERR(add_new_props(root, fspath, props, scratch_pool)); 352 353 return SVN_NO_ERROR; 354} 355 356 357/* This implements svn_editor_cb_add_file_t */ 358static svn_error_t * 359add_file_cb(void *baton, 360 const char *relpath, 361 const svn_checksum_t *checksum, 362 svn_stream_t *contents, 363 apr_hash_t *props, 364 svn_revnum_t replaces_rev, 365 apr_pool_t *scratch_pool) 366{ 367 struct edit_baton *eb = baton; 368 const char *fspath = FSPATH(relpath, scratch_pool); 369 svn_fs_root_t *root; 370 371 SVN_ERR(get_root(&root, eb)); 372 373 if (SVN_IS_VALID_REVNUM(replaces_rev)) 374 { 375 SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool)); 376 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool)); 377 } 378 else 379 { 380 SVN_ERR(can_create(root, fspath, scratch_pool)); 381 } 382 383 SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool)); 384 385 SVN_ERR(set_text(root, fspath, checksum, contents, 386 eb->cancel_func, eb->cancel_baton, scratch_pool)); 387 SVN_ERR(add_new_props(root, fspath, props, scratch_pool)); 388 389 return SVN_NO_ERROR; 390} 391 392 393/* This implements svn_editor_cb_add_symlink_t */ 394static svn_error_t * 395add_symlink_cb(void *baton, 396 const char *relpath, 397 const char *target, 398 apr_hash_t *props, 399 svn_revnum_t replaces_rev, 400 apr_pool_t *scratch_pool) 401{ 402 struct edit_baton *eb = baton; 403 const char *fspath = FSPATH(relpath, scratch_pool); 404 svn_fs_root_t *root; 405 406 SVN_ERR(get_root(&root, eb)); 407 408 if (SVN_IS_VALID_REVNUM(replaces_rev)) 409 { 410 SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool)); 411 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool)); 412 } 413 else 414 { 415 SVN_ERR(can_create(root, fspath, scratch_pool)); 416 } 417 418 /* ### we probably need to construct a file with specific contents 419 ### (until the FS grows some symlink APIs) */ 420#if 0 421 SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool)); 422 SVN_ERR(svn_fs_apply_text(&fs_contents, root, fspath, 423 NULL /* result_checksum */, 424 scratch_pool)); 425 /* ### SVN_ERR(svn_stream_printf(fs_contents, ..., scratch_pool)); */ 426 apr_hash_set(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING, 427 SVN_PROP_SPECIAL_VALUE); 428 429 SVN_ERR(add_new_props(root, fspath, props, scratch_pool)); 430#endif 431 432 SVN__NOT_IMPLEMENTED(); 433} 434 435 436/* This implements svn_editor_cb_add_absent_t */ 437static svn_error_t * 438add_absent_cb(void *baton, 439 const char *relpath, 440 svn_node_kind_t kind, 441 svn_revnum_t replaces_rev, 442 apr_pool_t *scratch_pool) 443{ 444 /* This is a programming error. Code should not attempt to create these 445 kinds of nodes within the FS. */ 446 /* ### use a custom error code */ 447 return svn_error_create( 448 SVN_ERR_UNSUPPORTED_FEATURE, NULL, 449 _("The filesystem does not support 'absent' nodes")); 450} 451 452 453/* This implements svn_editor_cb_alter_directory_t */ 454static svn_error_t * 455alter_directory_cb(void *baton, 456 const char *relpath, 457 svn_revnum_t revision, 458 const apr_array_header_t *children, 459 apr_hash_t *props, 460 apr_pool_t *scratch_pool) 461{ 462 struct edit_baton *eb = baton; 463 const char *fspath = FSPATH(relpath, scratch_pool); 464 svn_fs_root_t *root; 465 466 /* Note: we ignore CHILDREN. We have no "incomplete" state to worry about, 467 so we don't need to be aware of what children will be created. */ 468 469 SVN_ERR(get_root(&root, eb)); 470 SVN_ERR(can_modify(root, fspath, revision, scratch_pool)); 471 472 if (props) 473 SVN_ERR(alter_props(root, fspath, props, scratch_pool)); 474 475 return SVN_NO_ERROR; 476} 477 478 479/* This implements svn_editor_cb_alter_file_t */ 480static svn_error_t * 481alter_file_cb(void *baton, 482 const char *relpath, 483 svn_revnum_t revision, 484 const svn_checksum_t *checksum, 485 svn_stream_t *contents, 486 apr_hash_t *props, 487 apr_pool_t *scratch_pool) 488{ 489 struct edit_baton *eb = baton; 490 const char *fspath = FSPATH(relpath, scratch_pool); 491 svn_fs_root_t *root; 492 493 SVN_ERR(get_root(&root, eb)); 494 SVN_ERR(can_modify(root, fspath, revision, scratch_pool)); 495 496 if (contents != NULL) 497 { 498 SVN_ERR_ASSERT(checksum != NULL); 499 SVN_ERR(set_text(root, fspath, checksum, contents, 500 eb->cancel_func, eb->cancel_baton, scratch_pool)); 501 } 502 503 if (props != NULL) 504 { 505 SVN_ERR(alter_props(root, fspath, props, scratch_pool)); 506 } 507 508 return SVN_NO_ERROR; 509} 510 511 512/* This implements svn_editor_cb_alter_symlink_t */ 513static svn_error_t * 514alter_symlink_cb(void *baton, 515 const char *relpath, 516 svn_revnum_t revision, 517 const char *target, 518 apr_hash_t *props, 519 apr_pool_t *scratch_pool) 520{ 521 struct edit_baton *eb = baton; 522 523 SVN_UNUSED(eb); 524 SVN__NOT_IMPLEMENTED(); 525} 526 527 528/* This implements svn_editor_cb_delete_t */ 529static svn_error_t * 530delete_cb(void *baton, 531 const char *relpath, 532 svn_revnum_t revision, 533 apr_pool_t *scratch_pool) 534{ 535 struct edit_baton *eb = baton; 536 const char *fspath = FSPATH(relpath, scratch_pool); 537 svn_fs_root_t *root; 538 539 SVN_ERR(get_root(&root, eb)); 540 SVN_ERR(can_modify(root, fspath, revision, scratch_pool)); 541 542 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool)); 543 544 return SVN_NO_ERROR; 545} 546 547 548/* This implements svn_editor_cb_copy_t */ 549static svn_error_t * 550copy_cb(void *baton, 551 const char *src_relpath, 552 svn_revnum_t src_revision, 553 const char *dst_relpath, 554 svn_revnum_t replaces_rev, 555 apr_pool_t *scratch_pool) 556{ 557 struct edit_baton *eb = baton; 558 const char *src_fspath = FSPATH(src_relpath, scratch_pool); 559 const char *dst_fspath = FSPATH(dst_relpath, scratch_pool); 560 svn_fs_root_t *root; 561 svn_fs_root_t *src_root; 562 563 SVN_ERR(get_root(&root, eb)); 564 565 /* Check if we can we replace the maybe-specified destination (revision). */ 566 if (SVN_IS_VALID_REVNUM(replaces_rev)) 567 { 568 SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool)); 569 SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool)); 570 } 571 else 572 { 573 SVN_ERR(can_create(root, dst_fspath, scratch_pool)); 574 } 575 576 SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision, 577 scratch_pool)); 578 SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool)); 579 svn_fs_close_root(src_root); 580 581 return SVN_NO_ERROR; 582} 583 584 585/* This implements svn_editor_cb_move_t */ 586static svn_error_t * 587move_cb(void *baton, 588 const char *src_relpath, 589 svn_revnum_t src_revision, 590 const char *dst_relpath, 591 svn_revnum_t replaces_rev, 592 apr_pool_t *scratch_pool) 593{ 594 struct edit_baton *eb = baton; 595 const char *src_fspath = FSPATH(src_relpath, scratch_pool); 596 const char *dst_fspath = FSPATH(dst_relpath, scratch_pool); 597 svn_fs_root_t *root; 598 svn_fs_root_t *src_root; 599 600 SVN_ERR(get_root(&root, eb)); 601 602 /* Check if we delete the specified source (revision), and can we replace 603 the maybe-specified destination (revision). */ 604 SVN_ERR(can_modify(root, src_fspath, src_revision, scratch_pool)); 605 if (SVN_IS_VALID_REVNUM(replaces_rev)) 606 { 607 SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool)); 608 SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool)); 609 } 610 else 611 { 612 SVN_ERR(can_create(root, dst_fspath, scratch_pool)); 613 } 614 615 /* ### would be nice to have svn_fs_move() */ 616 617 /* Copy the src to the dst. */ 618 SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision, 619 scratch_pool)); 620 SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool)); 621 svn_fs_close_root(src_root); 622 623 /* Notice: we're deleting the src repos path from the dst root. */ 624 SVN_ERR(svn_fs_delete(root, src_fspath, scratch_pool)); 625 626 return SVN_NO_ERROR; 627} 628 629 630/* This implements svn_editor_cb_complete_t */ 631static svn_error_t * 632complete_cb(void *baton, 633 apr_pool_t *scratch_pool) 634{ 635 struct edit_baton *eb = baton; 636 637 /* Watch out for a following call to svn_fs_editor_commit(). Note that 638 we are likely here because svn_fs_editor_commit() was called, and it 639 invoked svn_editor_complete(). */ 640 eb->completed = TRUE; 641 642 if (eb->root != NULL) 643 { 644 svn_fs_close_root(eb->root); 645 eb->root = NULL; 646 } 647 648 return SVN_NO_ERROR; 649} 650 651 652/* This implements svn_editor_cb_abort_t */ 653static svn_error_t * 654abort_cb(void *baton, 655 apr_pool_t *scratch_pool) 656{ 657 struct edit_baton *eb = baton; 658 svn_error_t *err; 659 660 /* Don't allow a following call to svn_fs_editor_commit(). */ 661 eb->completed = TRUE; 662 663 if (eb->root != NULL) 664 { 665 svn_fs_close_root(eb->root); 666 eb->root = NULL; 667 } 668 669 /* ### should we examine the error and attempt svn_fs_purge_txn() ? */ 670 err = svn_fs_abort_txn(eb->txn, scratch_pool); 671 672 /* For safety, clear the now-useless txn. */ 673 eb->txn = NULL; 674 675 return svn_error_trace(err); 676} 677 678 679static svn_error_t * 680make_editor(svn_editor_t **editor, 681 svn_fs_txn_t *txn, 682 svn_cancel_func_t cancel_func, 683 void *cancel_baton, 684 apr_pool_t *result_pool, 685 apr_pool_t *scratch_pool) 686{ 687 static const svn_editor_cb_many_t editor_cbs = { 688 add_directory_cb, 689 add_file_cb, 690 add_symlink_cb, 691 add_absent_cb, 692 alter_directory_cb, 693 alter_file_cb, 694 alter_symlink_cb, 695 delete_cb, 696 copy_cb, 697 move_cb, 698 complete_cb, 699 abort_cb 700 }; 701 struct edit_baton *eb = apr_pcalloc(result_pool, sizeof(*eb)); 702 703 eb->txn = txn; 704 eb->cancel_func = cancel_func; 705 eb->cancel_baton = cancel_baton; 706 eb->txn_pool = result_pool; 707 708 SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton, 709 result_pool, scratch_pool)); 710 SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool)); 711 712 return SVN_NO_ERROR; 713} 714 715 716svn_error_t * 717svn_fs__editor_create(svn_editor_t **editor, 718 const char **txn_name, 719 svn_fs_t *fs, 720 apr_uint32_t flags, 721 svn_cancel_func_t cancel_func, 722 void *cancel_baton, 723 apr_pool_t *result_pool, 724 apr_pool_t *scratch_pool) 725{ 726 svn_revnum_t revision; 727 svn_fs_txn_t *txn; 728 729 SVN_ERR(svn_fs_youngest_rev(&revision, fs, scratch_pool)); 730 SVN_ERR(svn_fs_begin_txn2(&txn, fs, revision, flags, result_pool)); 731 SVN_ERR(svn_fs_txn_name(txn_name, txn, result_pool)); 732 return svn_error_trace(make_editor(editor, txn, 733 cancel_func, cancel_baton, 734 result_pool, scratch_pool)); 735} 736 737 738svn_error_t * 739svn_fs__editor_create_for(svn_editor_t **editor, 740 svn_fs_t *fs, 741 const char *txn_name, 742 svn_cancel_func_t cancel_func, 743 void *cancel_baton, 744 apr_pool_t *result_pool, 745 apr_pool_t *scratch_pool) 746{ 747 svn_fs_txn_t *txn; 748 749 SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, result_pool)); 750 return svn_error_trace(make_editor(editor, txn, 751 cancel_func, cancel_baton, 752 result_pool, scratch_pool)); 753} 754 755 756svn_error_t * 757svn_fs__editor_commit(svn_revnum_t *revision, 758 svn_error_t **post_commit_err, 759 const char **conflict_path, 760 svn_editor_t *editor, 761 apr_pool_t *result_pool, 762 apr_pool_t *scratch_pool) 763{ 764 struct edit_baton *eb = svn_editor_get_baton(editor); 765 const char *inner_conflict_path; 766 svn_error_t *err = NULL; 767 768 /* make sure people are using the correct sequencing. */ 769 if (eb->completed) 770 return svn_error_create(SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION, 771 NULL, NULL); 772 773 *revision = SVN_INVALID_REVNUM; 774 *post_commit_err = NULL; 775 *conflict_path = NULL; 776 777 /* Clean up internal resources (eg. eb->root). This also allows the 778 editor infrastructure to know this editor is "complete". */ 779 err = svn_editor_complete(editor); 780 if (err) 781 { 782 svn_fs_txn_t *txn = eb->txn; 783 784 eb->txn = NULL; 785 return svn_error_trace(svn_error_compose_create( 786 err, 787 svn_fs_abort_txn(txn, scratch_pool))); 788 } 789 790 /* Note: docco for svn_fs_commit_txn() states that CONFLICT_PATH will 791 be allocated in the txn's pool. But it lies. Regardless, we want 792 it placed into RESULT_POOL. */ 793 794 err = svn_fs_commit_txn(&inner_conflict_path, 795 revision, 796 eb->txn, 797 scratch_pool); 798 if (SVN_IS_VALID_REVNUM(*revision)) 799 { 800 if (err) 801 { 802 /* Case 3. ERR is a post-commit (cleanup) error. */ 803 804 /* Pass responsibility via POST_COMMIT_ERR. */ 805 *post_commit_err = err; 806 err = SVN_NO_ERROR; 807 } 808 /* else: Case 1. */ 809 } 810 else 811 { 812 SVN_ERR_ASSERT(err != NULL); 813 if (err->apr_err == SVN_ERR_FS_CONFLICT) 814 { 815 /* Case 2. */ 816 817 /* Copy this into the correct pool (see note above). */ 818 *conflict_path = apr_pstrdup(result_pool, inner_conflict_path); 819 820 /* Return success. The caller should inspect CONFLICT_PATH to 821 determine this particular case. */ 822 svn_error_clear(err); 823 err = SVN_NO_ERROR; 824 } 825 /* else: Case 4. */ 826 827 /* Abort the TXN. Nobody wants to use it. */ 828 /* ### should we examine the error and attempt svn_fs_purge_txn() ? */ 829 err = svn_error_compose_create( 830 err, 831 svn_fs_abort_txn(eb->txn, scratch_pool)); 832 } 833 834 /* For safety, clear the now-useless txn. */ 835 eb->txn = NULL; 836 837 return svn_error_trace(err); 838} 839