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, SVN_VA_NULL)
68 get_root(svn_fs_root_t **root,
69 struct edit_baton *eb)
72 SVN_ERR(svn_fs_txn_root(&eb->root, eb->txn, eb->txn_pool));
78 /* Apply each property in PROPS to the node at FSPATH in ROOT. */
80 add_new_props(svn_fs_root_t *root,
83 apr_pool_t *scratch_pool)
85 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
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. */
92 for (hi = apr_hash_first(scratch_pool, props); hi;
93 hi = apr_hash_next(hi))
95 const char *name = apr_hash_this_key(hi);
96 const svn_string_t *value = apr_hash_this_val(hi);
98 svn_pool_clear(iterpool);
100 SVN_ERR(svn_fs_change_node_prop(root, fspath, name, value, iterpool));
103 svn_pool_destroy(iterpool);
109 alter_props(svn_fs_root_t *root,
112 apr_pool_t *scratch_pool)
114 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
115 apr_hash_t *old_props;
116 apr_array_header_t *propdiffs;
119 SVN_ERR(svn_fs_node_proplist(&old_props, root, fspath, scratch_pool));
121 SVN_ERR(svn_prop_diffs(&propdiffs, props, old_props, scratch_pool));
123 for (i = 0; i < propdiffs->nelts; ++i)
125 const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
127 svn_pool_clear(iterpool);
129 /* Add, change, or delete properties. */
130 SVN_ERR(svn_fs_change_node_prop(root, fspath, prop->name, prop->value,
134 svn_pool_destroy(iterpool);
140 set_text(svn_fs_root_t *root,
142 const svn_checksum_t *checksum,
143 svn_stream_t *contents,
144 svn_cancel_func_t cancel_func,
146 apr_pool_t *scratch_pool)
148 svn_stream_t *fs_contents;
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 */,
156 SVN_ERR(svn_stream_copy3(contents, fs_contents,
157 cancel_func, cancel_baton,
164 /* The caller wants to modify REVISION of FSPATH. Is that allowed? */
166 can_modify(svn_fs_root_t *txn_root,
168 svn_revnum_t revision,
169 apr_pool_t *scratch_pool)
171 svn_revnum_t created_rev;
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,
178 /* Uncommitted nodes (eg. a descendant of a copy/move destination)
179 have no (committed) revision number. Let the caller go ahead and
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.
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))
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
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).
211 if (!SVN_IS_VALID_REVNUM(revision))
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"),
221 if (revision < created_rev)
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"),
230 if (revision > created_rev)
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;
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);
251 /* Has the target node changed in the future? */
252 if (relation != svn_fs_node_unchanged)
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)"),
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.
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. */
277 can_create(svn_fs_root_t *txn_root,
279 apr_pool_t *scratch_pool)
281 svn_node_kind_t kind;
282 const char *cur_fspath;
284 SVN_ERR(svn_fs_check_path(&kind, txn_root, fspath, scratch_pool));
285 if (kind == svn_node_none)
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. */
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))
300 svn_revnum_t created_rev;
302 SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, cur_fspath,
304 if (!SVN_IS_VALID_REVNUM(created_rev))
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 ?? */
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"),
322 /* This implements svn_editor_cb_add_directory_t */
324 add_directory_cb(void *baton,
326 const apr_array_header_t *children,
328 svn_revnum_t replaces_rev,
329 apr_pool_t *scratch_pool)
331 struct edit_baton *eb = baton;
332 const char *fspath = FSPATH(relpath, scratch_pool);
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. */
338 SVN_ERR(get_root(&root, eb));
340 if (SVN_IS_VALID_REVNUM(replaces_rev))
342 SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
343 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
347 SVN_ERR(can_create(root, fspath, scratch_pool));
350 SVN_ERR(svn_fs_make_dir(root, fspath, scratch_pool));
351 SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
357 /* This implements svn_editor_cb_add_file_t */
359 add_file_cb(void *baton,
361 const svn_checksum_t *checksum,
362 svn_stream_t *contents,
364 svn_revnum_t replaces_rev,
365 apr_pool_t *scratch_pool)
367 struct edit_baton *eb = baton;
368 const char *fspath = FSPATH(relpath, scratch_pool);
371 SVN_ERR(get_root(&root, eb));
373 if (SVN_IS_VALID_REVNUM(replaces_rev))
375 SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
376 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
380 SVN_ERR(can_create(root, fspath, scratch_pool));
383 SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool));
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));
393 /* This implements svn_editor_cb_add_symlink_t */
395 add_symlink_cb(void *baton,
399 svn_revnum_t replaces_rev,
400 apr_pool_t *scratch_pool)
402 struct edit_baton *eb = baton;
403 const char *fspath = FSPATH(relpath, scratch_pool);
406 SVN_ERR(get_root(&root, eb));
408 if (SVN_IS_VALID_REVNUM(replaces_rev))
410 SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
411 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
415 SVN_ERR(can_create(root, fspath, scratch_pool));
418 /* ### we probably need to construct a file with specific contents
419 ### (until the FS grows some symlink APIs) */
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 */,
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);
429 SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
432 SVN__NOT_IMPLEMENTED();
436 /* This implements svn_editor_cb_add_absent_t */
438 add_absent_cb(void *baton,
440 svn_node_kind_t kind,
441 svn_revnum_t replaces_rev,
442 apr_pool_t *scratch_pool)
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"));
453 /* This implements svn_editor_cb_alter_directory_t */
455 alter_directory_cb(void *baton,
457 svn_revnum_t revision,
458 const apr_array_header_t *children,
460 apr_pool_t *scratch_pool)
462 struct edit_baton *eb = baton;
463 const char *fspath = FSPATH(relpath, scratch_pool);
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. */
469 SVN_ERR(get_root(&root, eb));
470 SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
473 SVN_ERR(alter_props(root, fspath, props, scratch_pool));
479 /* This implements svn_editor_cb_alter_file_t */
481 alter_file_cb(void *baton,
483 svn_revnum_t revision,
484 const svn_checksum_t *checksum,
485 svn_stream_t *contents,
487 apr_pool_t *scratch_pool)
489 struct edit_baton *eb = baton;
490 const char *fspath = FSPATH(relpath, scratch_pool);
493 SVN_ERR(get_root(&root, eb));
494 SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
496 if (contents != NULL)
498 SVN_ERR_ASSERT(checksum != NULL);
499 SVN_ERR(set_text(root, fspath, checksum, contents,
500 eb->cancel_func, eb->cancel_baton, scratch_pool));
505 SVN_ERR(alter_props(root, fspath, props, scratch_pool));
512 /* This implements svn_editor_cb_alter_symlink_t */
514 alter_symlink_cb(void *baton,
516 svn_revnum_t revision,
519 apr_pool_t *scratch_pool)
521 struct edit_baton *eb = baton;
524 SVN__NOT_IMPLEMENTED();
528 /* This implements svn_editor_cb_delete_t */
530 delete_cb(void *baton,
532 svn_revnum_t revision,
533 apr_pool_t *scratch_pool)
535 struct edit_baton *eb = baton;
536 const char *fspath = FSPATH(relpath, scratch_pool);
539 SVN_ERR(get_root(&root, eb));
540 SVN_ERR(can_modify(root, fspath, revision, scratch_pool));
542 SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
548 /* This implements svn_editor_cb_copy_t */
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)
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);
561 svn_fs_root_t *src_root;
563 SVN_ERR(get_root(&root, eb));
565 /* Check if we can we replace the maybe-specified destination (revision). */
566 if (SVN_IS_VALID_REVNUM(replaces_rev))
568 SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
569 SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
573 SVN_ERR(can_create(root, dst_fspath, scratch_pool));
576 SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
578 SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
579 svn_fs_close_root(src_root);
585 /* This implements svn_editor_cb_move_t */
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)
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);
598 svn_fs_root_t *src_root;
600 SVN_ERR(get_root(&root, eb));
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))
607 SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
608 SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
612 SVN_ERR(can_create(root, dst_fspath, scratch_pool));
615 /* ### would be nice to have svn_fs_move() */
617 /* Copy the src to the dst. */
618 SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
620 SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
621 svn_fs_close_root(src_root);
623 /* Notice: we're deleting the src repos path from the dst root. */
624 SVN_ERR(svn_fs_delete(root, src_fspath, scratch_pool));
630 /* This implements svn_editor_cb_complete_t */
632 complete_cb(void *baton,
633 apr_pool_t *scratch_pool)
635 struct edit_baton *eb = baton;
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;
642 if (eb->root != NULL)
644 svn_fs_close_root(eb->root);
652 /* This implements svn_editor_cb_abort_t */
654 abort_cb(void *baton,
655 apr_pool_t *scratch_pool)
657 struct edit_baton *eb = baton;
660 /* Don't allow a following call to svn_fs_editor_commit(). */
661 eb->completed = TRUE;
663 if (eb->root != NULL)
665 svn_fs_close_root(eb->root);
669 /* ### should we examine the error and attempt svn_fs_purge_txn() ? */
670 err = svn_fs_abort_txn(eb->txn, scratch_pool);
672 /* For safety, clear the now-useless txn. */
675 return svn_error_trace(err);
680 make_editor(svn_editor_t **editor,
682 svn_cancel_func_t cancel_func,
684 apr_pool_t *result_pool,
685 apr_pool_t *scratch_pool)
687 static const svn_editor_cb_many_t editor_cbs = {
701 struct edit_baton *eb = apr_pcalloc(result_pool, sizeof(*eb));
704 eb->cancel_func = cancel_func;
705 eb->cancel_baton = cancel_baton;
706 eb->txn_pool = result_pool;
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));
717 svn_fs__editor_create(svn_editor_t **editor,
718 const char **txn_name,
721 svn_cancel_func_t cancel_func,
723 apr_pool_t *result_pool,
724 apr_pool_t *scratch_pool)
726 svn_revnum_t revision;
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));
739 svn_fs__editor_create_for(svn_editor_t **editor,
741 const char *txn_name,
742 svn_cancel_func_t cancel_func,
744 apr_pool_t *result_pool,
745 apr_pool_t *scratch_pool)
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));
757 svn_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)
764 struct edit_baton *eb = svn_editor_get_baton(editor);
765 const char *inner_conflict_path;
766 svn_error_t *err = NULL;
768 /* make sure people are using the correct sequencing. */
770 return svn_error_create(SVN_ERR_FS_INCORRECT_EDITOR_COMPLETION,
773 *revision = SVN_INVALID_REVNUM;
774 *post_commit_err = NULL;
775 *conflict_path = NULL;
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);
782 svn_fs_txn_t *txn = eb->txn;
785 return svn_error_trace(svn_error_compose_create(
787 svn_fs_abort_txn(txn, scratch_pool)));
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. */
794 err = svn_fs_commit_txn(&inner_conflict_path,
798 if (SVN_IS_VALID_REVNUM(*revision))
802 /* Case 3. ERR is a post-commit (cleanup) error. */
804 /* Pass responsibility via POST_COMMIT_ERR. */
805 *post_commit_err = err;
812 SVN_ERR_ASSERT(err != NULL);
813 if (err->apr_err == SVN_ERR_FS_CONFLICT)
817 /* Copy this into the correct pool (see note above). */
818 *conflict_path = apr_pstrdup(result_pool, inner_conflict_path);
820 /* Return success. The caller should inspect CONFLICT_PATH to
821 determine this particular case. */
822 svn_error_clear(err);
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(
831 svn_fs_abort_txn(eb->txn, scratch_pool));
834 /* For safety, clear the now-useless txn. */
837 return svn_error_trace(err);