2 * editor.c: Editor for modifying FS transactions
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 #include <apr_pools.h>
26 #include "svn_types.h"
27 #include "svn_error.h"
28 #include "svn_pools.h"
30 #include "svn_props.h"
33 #include "svn_private_config.h"
35 #include "fs-loader.h"
37 #include "private/svn_fspath.h"
38 #include "private/svn_fs_private.h"
39 #include "private/svn_editor.h"
43 /* The transaction associated with this editor. */
46 /* Has this editor been completed? */
47 svn_boolean_t completed;
49 /* We sometimes need the cancellation beyond what svn_editor_t provides */
50 svn_cancel_func_t cancel_func;
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.
57 This pool SHOULD NOT be used for any allocations. */
60 /* This is the root from the txn. Use get_root() to fetch/create this
61 member as appropriate. */
65 #define FSPATH(relpath, pool) apr_pstrcat(pool, "/", relpath, NULL)
66 #define UNUSED(x) ((void)(x))
70 get_root(svn_fs_root_t **root,
71 struct edit_baton *eb)
74 SVN_ERR(svn_fs_txn_root(&eb->root, eb->txn, eb->txn_pool));
80 /* Apply each property in PROPS to the node at FSPATH in ROOT. */
82 add_new_props(svn_fs_root_t *root,
85 apr_pool_t *scratch_pool)
87 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
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. */
94 for (hi = apr_hash_first(scratch_pool, props); hi;
95 hi = apr_hash_next(hi))
97 const char *name = svn__apr_hash_index_key(hi);
98 const svn_string_t *value = svn__apr_hash_index_val(hi);
100 svn_pool_clear(iterpool);
102 SVN_ERR(svn_fs_change_node_prop(root, fspath, name, value, iterpool));
105 svn_pool_destroy(iterpool);
111 alter_props(svn_fs_root_t *root,
114 apr_pool_t *scratch_pool)
116 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
117 apr_hash_t *old_props;
118 apr_array_header_t *propdiffs;
121 SVN_ERR(svn_fs_node_proplist(&old_props, root, fspath, scratch_pool));
123 SVN_ERR(svn_prop_diffs(&propdiffs, props, old_props, scratch_pool));
125 for (i = 0; i < propdiffs->nelts; ++i)
127 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
129 svn_pool_clear(iterpool);
131 /* Add, change, or delete properties. */
132 SVN_ERR(svn_fs_change_node_prop(root, fspath, prop->name, prop->value,
136 svn_pool_destroy(iterpool);
142 set_text(svn_fs_root_t *root,
144 const svn_checksum_t *checksum,
145 svn_stream_t *contents,
146 svn_cancel_func_t cancel_func,
148 apr_pool_t *scratch_pool)
150 svn_stream_t *fs_contents;
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 */,
158 SVN_ERR(svn_stream_copy3(contents, fs_contents,
159 cancel_func, cancel_baton,
166 /* The caller wants to modify REVISION of FSPATH. Is that allowed? */
168 can_modify(svn_fs_root_t *txn_root,
170 svn_revnum_t revision,
171 apr_pool_t *scratch_pool)
173 svn_revnum_t created_rev;
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,
180 /* Uncommitted nodes (eg. a descendent of a copy/move/rotate destination)
181 have no (committed) revision number. Let the caller go ahead and
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.
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))
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
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).
213 if (!SVN_IS_VALID_REVNUM(revision))
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"),
223 if (revision < created_rev)
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"),
232 if (revision > created_rev)
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;
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,
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,
256 svn_fs_close_root(rev_root);
258 /* Has the target node changed in the future? */
259 if (svn_fs_compare_ids(txn_noderev_id, new_noderev_id) != 0)
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)"),
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.
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. */
284 can_create(svn_fs_root_t *txn_root,
286 apr_pool_t *scratch_pool)
288 svn_node_kind_t kind;
289 const char *cur_fspath;
291 SVN_ERR(svn_fs_check_path(&kind, txn_root, fspath, scratch_pool));
292 if (kind == svn_node_none)
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. */
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))
307 svn_revnum_t created_rev;
309 SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, cur_fspath,
311 if (!SVN_IS_VALID_REVNUM(created_rev))
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 ?? */
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"),
329 /* This implements svn_editor_cb_add_directory_t */
331 add_directory_cb(void *baton,
333 const apr_array_header_t *children,
335 svn_revnum_t replaces_rev,
336 apr_pool_t *scratch_pool)
338 struct edit_baton *eb = baton;
339 const char *fspath = FSPATH(relpath, scratch_pool);
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. */
345 SVN_ERR(get_root(&root, eb));
347 if (SVN_IS_VALID_REVNUM(replaces_rev))
349 SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
350 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
354 SVN_ERR(can_create(root, fspath, scratch_pool));
357 SVN_ERR(svn_fs_make_dir(root, fspath, scratch_pool));
358 SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
364 /* This implements svn_editor_cb_add_file_t */
366 add_file_cb(void *baton,
368 const svn_checksum_t *checksum,
369 svn_stream_t *contents,
371 svn_revnum_t replaces_rev,
372 apr_pool_t *scratch_pool)
374 struct edit_baton *eb = baton;
375 const char *fspath = FSPATH(relpath, scratch_pool);
378 SVN_ERR(get_root(&root, eb));
380 if (SVN_IS_VALID_REVNUM(replaces_rev))
382 SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
383 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
387 SVN_ERR(can_create(root, fspath, scratch_pool));
390 SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool));
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));
400 /* This implements svn_editor_cb_add_symlink_t */
402 add_symlink_cb(void *baton,
406 svn_revnum_t replaces_rev,
407 apr_pool_t *scratch_pool)
409 struct edit_baton *eb = baton;
410 const char *fspath = FSPATH(relpath, scratch_pool);
413 SVN_ERR(get_root(&root, eb));
415 if (SVN_IS_VALID_REVNUM(replaces_rev))
417 SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
418 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
422 SVN_ERR(can_create(root, fspath, scratch_pool));
425 /* ### we probably need to construct a file with specific contents
426 ### (until the FS grows some symlink APIs) */
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 */,
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);
436 SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
439 SVN__NOT_IMPLEMENTED();
443 /* This implements svn_editor_cb_add_absent_t */
445 add_absent_cb(void *baton,
447 svn_node_kind_t kind,
448 svn_revnum_t replaces_rev,
449 apr_pool_t *scratch_pool)
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"));
460 /* This implements svn_editor_cb_alter_directory_t */
462 alter_directory_cb(void *baton,
464 svn_revnum_t revision,
465 const apr_array_header_t *children,
467 apr_pool_t *scratch_pool)
469 struct edit_baton *eb = baton;
470 const char *fspath = FSPATH(relpath, scratch_pool);
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. */
476 SVN_ERR(get_root(&root, eb));
477 SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
480 SVN_ERR(alter_props(root, fspath, props, scratch_pool));
486 /* This implements svn_editor_cb_alter_file_t */
488 alter_file_cb(void *baton,
490 svn_revnum_t revision,
492 const svn_checksum_t *checksum,
493 svn_stream_t *contents,
494 apr_pool_t *scratch_pool)
496 struct edit_baton *eb = baton;
497 const char *fspath = FSPATH(relpath, scratch_pool);
500 SVN_ERR(get_root(&root, eb));
501 SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
503 if (contents != NULL)
505 SVN_ERR_ASSERT(checksum != NULL);
506 SVN_ERR(set_text(root, fspath, checksum, contents,
507 eb->cancel_func, eb->cancel_baton, scratch_pool));
512 SVN_ERR(alter_props(root, fspath, props, scratch_pool));
519 /* This implements svn_editor_cb_alter_symlink_t */
521 alter_symlink_cb(void *baton,
523 svn_revnum_t revision,
526 apr_pool_t *scratch_pool)
528 struct edit_baton *eb = baton;
530 UNUSED(eb); SVN__NOT_IMPLEMENTED();
534 /* This implements svn_editor_cb_delete_t */
536 delete_cb(void *baton,
538 svn_revnum_t revision,
539 apr_pool_t *scratch_pool)
541 struct edit_baton *eb = baton;
542 const char *fspath = FSPATH(relpath, scratch_pool);
545 SVN_ERR(get_root(&root, eb));
546 SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
548 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
554 /* This implements svn_editor_cb_copy_t */
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)
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);
567 svn_fs_root_t *src_root;
569 SVN_ERR(get_root(&root, eb));
571 /* Check if we can we replace the maybe-specified destination (revision). */
572 if (SVN_IS_VALID_REVNUM(replaces_rev))
574 SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
575 SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
579 SVN_ERR(can_create(root, dst_fspath, scratch_pool));
582 SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
584 SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
585 svn_fs_close_root(src_root);
591 /* This implements svn_editor_cb_move_t */
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)
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);
604 svn_fs_root_t *src_root;
606 SVN_ERR(get_root(&root, eb));
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))
613 SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
614 SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
618 SVN_ERR(can_create(root, dst_fspath, scratch_pool));
621 /* ### would be nice to have svn_fs_move() */
623 /* Copy the src to the dst. */
624 SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
626 SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
627 svn_fs_close_root(src_root);
629 /* Notice: we're deleting the src repos path from the dst root. */
630 SVN_ERR(svn_fs_delete(root, src_fspath, scratch_pool));
636 /* This implements svn_editor_cb_rotate_t */
638 rotate_cb(void *baton,
639 const apr_array_header_t *relpaths,
640 const apr_array_header_t *revisions,
641 apr_pool_t *scratch_pool)
643 struct edit_baton *eb = baton;
645 UNUSED(eb); SVN__NOT_IMPLEMENTED();
649 /* This implements svn_editor_cb_complete_t */
651 complete_cb(void *baton,
652 apr_pool_t *scratch_pool)
654 struct edit_baton *eb = baton;
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;
661 if (eb->root != NULL)
663 svn_fs_close_root(eb->root);
671 /* This implements svn_editor_cb_abort_t */
673 abort_cb(void *baton,
674 apr_pool_t *scratch_pool)
676 struct edit_baton *eb = baton;
679 /* Don't allow a following call to svn_fs_editor_commit(). */
680 eb->completed = TRUE;
682 if (eb->root != NULL)
684 svn_fs_close_root(eb->root);
688 /* ### should we examine the error and attempt svn_fs_purge_txn() ? */
689 err = svn_fs_abort_txn(eb->txn, scratch_pool);
691 /* For safety, clear the now-useless txn. */
694 return svn_error_trace(err);
699 make_editor(svn_editor_t **editor,
701 svn_cancel_func_t cancel_func,
703 apr_pool_t *result_pool,
704 apr_pool_t *scratch_pool)
706 static const svn_editor_cb_many_t editor_cbs = {
721 struct edit_baton *eb = apr_pcalloc(result_pool, sizeof(*eb));
724 eb->cancel_func = cancel_func;
725 eb->cancel_baton = cancel_baton;
726 eb->txn_pool = result_pool;
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));
737 svn_fs__editor_create(svn_editor_t **editor,
738 const char **txn_name,
741 svn_cancel_func_t cancel_func,
743 apr_pool_t *result_pool,
744 apr_pool_t *scratch_pool)
746 svn_revnum_t revision;
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));
759 svn_fs__editor_create_for(svn_editor_t **editor,
761 const char *txn_name,
762 svn_cancel_func_t cancel_func,
764 apr_pool_t *result_pool,
765 apr_pool_t *scratch_pool)
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));
777 svn_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)
784 struct edit_baton *eb = svn_editor_get_baton(editor);
785 const char *inner_conflict_path;
786 svn_error_t *err = NULL;
788 /* make sure people are using the correct sequencing. */
790 return svn_error_create(SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION,
793 *revision = SVN_INVALID_REVNUM;
794 *post_commit_err = NULL;
795 *conflict_path = NULL;
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);
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. */
806 err = svn_fs_commit_txn(&inner_conflict_path,
810 if (SVN_IS_VALID_REVNUM(*revision))
814 /* Case 3. ERR is a post-commit (cleanup) error. */
816 /* Pass responsibility via POST_COMMIT_ERR. */
817 *post_commit_err = err;
824 SVN_ERR_ASSERT(err != NULL);
825 if (err->apr_err == SVN_ERR_FS_CONFLICT)
829 /* Copy this into the correct pool (see note above). */
830 *conflict_path = apr_pstrdup(result_pool, inner_conflict_path);
832 /* Return sucess. The caller should inspect CONFLICT_PATH to
833 determine this particular case. */
834 svn_error_clear(err);
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(
843 svn_fs_abort_txn(eb->txn, scratch_pool));
846 /* For safety, clear the now-useless txn. */
849 return svn_error_trace(err);