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