/* * editor.c : editing trees of versioned resources * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ #include #include "svn_types.h" #include "svn_error.h" #include "svn_pools.h" #include "svn_dirent_uri.h" #include "private/svn_editor.h" #ifdef SVN_DEBUG /* This enables runtime checks of the editor API constraints. This may introduce additional memory and runtime overhead, and should not be used in production builds. ### Remove before release? ### Disabled for now. If I call svn_editor_alter_directory(A) then svn_editor_add_file(A/f) the latter fails on SHOULD_ALLOW_ADD. If I modify svn_editor_alter_directory to MARK_ALLOW_ADD(child) then if I call svn_editor_alter_directory(A) followed by svn_editor_alter_directory(A/B/C) the latter fails on VERIFY_PARENT_MAY_EXIST. */ #if 0 #define ENABLE_ORDERING_CHECK #endif #endif struct svn_editor_t { void *baton; /* Standard cancellation function. Called before each callback. */ svn_cancel_func_t cancel_func; void *cancel_baton; /* Our callback functions match that of the set-many structure, so just use that. */ svn_editor_cb_many_t funcs; /* This pool is used as the scratch_pool for all callbacks. */ apr_pool_t *scratch_pool; #ifdef ENABLE_ORDERING_CHECK svn_boolean_t within_callback; apr_hash_t *pending_incomplete_children; apr_hash_t *completed_nodes; svn_boolean_t finished; apr_pool_t *state_pool; #endif }; #ifdef ENABLE_ORDERING_CHECK #define START_CALLBACK(editor) \ do { \ svn_editor_t *editor__tmp_e = (editor); \ SVN_ERR_ASSERT(!editor__tmp_e->within_callback); \ editor__tmp_e->within_callback = TRUE; \ } while (0) #define END_CALLBACK(editor) ((editor)->within_callback = FALSE) /* Marker to indicate no further changes are allowed on this node. */ static const int marker_done = 0; #define MARKER_DONE (&marker_done) /* Marker indicating that add_* may be called for this path, or that it can be the destination of a copy or move. For copy/move, the path will switch to MARKER_ALLOW_ALTER, to enable further tweaks. */ static const int marker_allow_add = 0; #define MARKER_ALLOW_ADD (&marker_allow_add) /* Marker indicating that alter_* may be called for this path. */ static const int marker_allow_alter = 0; #define MARKER_ALLOW_ALTER (&marker_allow_alter) /* Just like MARKER_DONE, but also indicates that the node was created via add_directory(). This allows us to verify that the CHILDREN param was comprehensive. */ static const int marker_added_dir = 0; #define MARKER_ADDED_DIR (&marker_added_dir) #define MARK_FINISHED(editor) ((editor)->finished = TRUE) #define SHOULD_NOT_BE_FINISHED(editor) SVN_ERR_ASSERT(!(editor)->finished) #define CLEAR_INCOMPLETE(editor, relpath) \ svn_hash_sets((editor)->pending_incomplete_children, relpath, NULL); #define MARK_RELPATH(editor, relpath, value) \ svn_hash_sets((editor)->completed_nodes, \ apr_pstrdup((editor)->state_pool, relpath), value) #define MARK_COMPLETED(editor, relpath) \ MARK_RELPATH(editor, relpath, MARKER_DONE) #define SHOULD_NOT_BE_COMPLETED(editor, relpath) \ SVN_ERR_ASSERT(svn_hash_gets((editor)->completed_nodes, relpath) == NULL) #define MARK_ALLOW_ADD(editor, relpath) \ MARK_RELPATH(editor, relpath, MARKER_ALLOW_ADD) #define SHOULD_ALLOW_ADD(editor, relpath) \ SVN_ERR_ASSERT(allow_either(editor, relpath, MARKER_ALLOW_ADD, NULL)) #define MARK_ALLOW_ALTER(editor, relpath) \ MARK_RELPATH(editor, relpath, MARKER_ALLOW_ALTER) #define SHOULD_ALLOW_ALTER(editor, relpath) \ SVN_ERR_ASSERT(allow_either(editor, relpath, MARKER_ALLOW_ALTER, NULL)) #define MARK_ADDED_DIR(editor, relpath) \ MARK_RELPATH(editor, relpath, MARKER_ADDED_DIR) #define CHECK_UNKNOWN_CHILD(editor, relpath) \ SVN_ERR_ASSERT(check_unknown_child(editor, relpath)) /* When a child is changed in some way, mark the parent directory as needing to be "stable" (no future structural changes). IOW, only allow "alter" on the parent. Prevents parent-add/delete/move after any child operation. */ #define MARK_PARENT_STABLE(editor, relpath) \ mark_parent_stable(editor, relpath) /* If the parent is MARKER_ALLOW_ADD, then it has been moved-away, and we know it does not exist. All other cases: it might exist. */ #define VERIFY_PARENT_MAY_EXIST(editor, relpath) \ SVN_ERR_ASSERT(svn_hash_gets((editor)->completed_nodes, \ svn_relpath_dirname(relpath, \ (editor)->scratch_pool)) \ != MARKER_ALLOW_ADD) /* If the parent is MARKER_ADDED_DIR, then we should not be deleting children(*). If the parent is MARKER_ALLOW_ADD, then it has been moved-away, so children cannot exist. That leaves MARKER_DONE, MARKER_ALLOW_ALTER, and NULL as possible values. Just assert that we didn't get either of the bad ones. (*) if the child as added via add_*(), then it would have been marked as completed and delete/move-away already test against completed nodes. This test is to beware of trying to delete "children" that are not actually (and can't possibly be) present. */ #define CHILD_DELETIONS_ALLOWED(editor, relpath) \ SVN_ERR_ASSERT(!allow_either(editor, \ svn_relpath_dirname(relpath, \ (editor)->scratch_pool), \ MARKER_ADDED_DIR, MARKER_ALLOW_ADD)) static svn_boolean_t allow_either(const svn_editor_t *editor, const char *relpath, const void *marker1, const void *marker2) { void *value = svn_hash_gets(editor->completed_nodes, relpath); return value == marker1 || value == marker2; } static svn_boolean_t check_unknown_child(const svn_editor_t *editor, const char *relpath) { const char *parent; /* If we already know about the new child, then exit early. */ if (svn_hash_gets(editor->pending_incomplete_children, relpath) != NULL) return TRUE; parent = svn_relpath_dirname(relpath, editor->scratch_pool); /* Was this parent created via svn_editor_add_directory() ? */ if (svn_hash_gets(editor->completed_nodes, parent) == MARKER_ADDED_DIR) { /* Whoops. This child should have been listed in that add call, and placed into ->pending_incomplete_children. */ return FALSE; } /* The parent was not added in this drive. */ return TRUE; } static void mark_parent_stable(const svn_editor_t *editor, const char *relpath) { const char *parent = svn_relpath_dirname(relpath, editor->scratch_pool); const void *marker = svn_hash_gets(editor->completed_nodes, parent); /* If RELPATH has already been marked (to disallow adds, or that it has been fully-completed), then do nothing. */ if (marker == MARKER_ALLOW_ALTER || marker == MARKER_DONE || marker == MARKER_ADDED_DIR) return; /* If the marker is MARKER_ALLOW_ADD, then that means the parent was moved away. There is no way to work on a child. That should have been tested before we got here by VERIFY_PARENT_MAY_EXIST(). */ SVN_ERR_ASSERT_NO_RETURN(marker != MARKER_ALLOW_ADD); /* MARKER is NULL. Upgrade it to MARKER_ALLOW_ALTER. */ MARK_RELPATH(editor, parent, MARKER_ALLOW_ALTER); } #else /* Be wary with the definition of these macros so that we don't end up with "statement with no effect" warnings. Obviously, this depends upon particular usage, which is easy to verify. */ #define START_CALLBACK(editor) /* empty */ #define END_CALLBACK(editor) /* empty */ #define MARK_FINISHED(editor) /* empty */ #define SHOULD_NOT_BE_FINISHED(editor) /* empty */ #define CLEAR_INCOMPLETE(editor, relpath) /* empty */ #define MARK_COMPLETED(editor, relpath) /* empty */ #define SHOULD_NOT_BE_COMPLETED(editor, relpath) /* empty */ #define MARK_ALLOW_ADD(editor, relpath) /* empty */ #define SHOULD_ALLOW_ADD(editor, relpath) /* empty */ #define MARK_ALLOW_ALTER(editor, relpath) /* empty */ #define SHOULD_ALLOW_ALTER(editor, relpath) /* empty */ #define MARK_ADDED_DIR(editor, relpath) /* empty */ #define CHECK_UNKNOWN_CHILD(editor, relpath) /* empty */ #define MARK_PARENT_STABLE(editor, relpath) /* empty */ #define VERIFY_PARENT_MAY_EXIST(editor, relpath) /* empty */ #define CHILD_DELETIONS_ALLOWED(editor, relpath) /* empty */ #endif /* ENABLE_ORDERING_CHECK */ svn_error_t * svn_editor_create(svn_editor_t **editor, void *editor_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { *editor = apr_pcalloc(result_pool, sizeof(**editor)); (*editor)->baton = editor_baton; (*editor)->cancel_func = cancel_func; (*editor)->cancel_baton = cancel_baton; (*editor)->scratch_pool = svn_pool_create(result_pool); #ifdef ENABLE_ORDERING_CHECK (*editor)->pending_incomplete_children = apr_hash_make(result_pool); (*editor)->completed_nodes = apr_hash_make(result_pool); (*editor)->finished = FALSE; (*editor)->state_pool = result_pool; #endif return SVN_NO_ERROR; } void * svn_editor_get_baton(const svn_editor_t *editor) { return editor->baton; } svn_error_t * svn_editor_setcb_add_directory(svn_editor_t *editor, svn_editor_cb_add_directory_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_add_directory = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_add_file(svn_editor_t *editor, svn_editor_cb_add_file_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_add_file = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_add_symlink(svn_editor_t *editor, svn_editor_cb_add_symlink_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_add_symlink = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_add_absent(svn_editor_t *editor, svn_editor_cb_add_absent_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_add_absent = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_alter_directory(svn_editor_t *editor, svn_editor_cb_alter_directory_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_alter_directory = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_alter_file(svn_editor_t *editor, svn_editor_cb_alter_file_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_alter_file = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_alter_symlink(svn_editor_t *editor, svn_editor_cb_alter_symlink_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_alter_symlink = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_delete(svn_editor_t *editor, svn_editor_cb_delete_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_delete = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_copy(svn_editor_t *editor, svn_editor_cb_copy_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_copy = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_move(svn_editor_t *editor, svn_editor_cb_move_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_move = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_rotate(svn_editor_t *editor, svn_editor_cb_rotate_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_rotate = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_complete(svn_editor_t *editor, svn_editor_cb_complete_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_complete = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_abort(svn_editor_t *editor, svn_editor_cb_abort_t callback, apr_pool_t *scratch_pool) { editor->funcs.cb_abort = callback; return SVN_NO_ERROR; } svn_error_t * svn_editor_setcb_many(svn_editor_t *editor, const svn_editor_cb_many_t *many, apr_pool_t *scratch_pool) { #define COPY_CALLBACK(NAME) if (many->NAME) editor->funcs.NAME = many->NAME COPY_CALLBACK(cb_add_directory); COPY_CALLBACK(cb_add_file); COPY_CALLBACK(cb_add_symlink); COPY_CALLBACK(cb_add_absent); COPY_CALLBACK(cb_alter_directory); COPY_CALLBACK(cb_alter_file); COPY_CALLBACK(cb_alter_symlink); COPY_CALLBACK(cb_delete); COPY_CALLBACK(cb_copy); COPY_CALLBACK(cb_move); COPY_CALLBACK(cb_rotate); COPY_CALLBACK(cb_complete); COPY_CALLBACK(cb_abort); #undef COPY_CALLBACK return SVN_NO_ERROR; } static svn_error_t * check_cancel(svn_editor_t *editor) { svn_error_t *err = NULL; if (editor->cancel_func) { START_CALLBACK(editor); err = editor->cancel_func(editor->cancel_baton); END_CALLBACK(editor); } return svn_error_trace(err); } svn_error_t * svn_editor_add_directory(svn_editor_t *editor, const char *relpath, const apr_array_header_t *children, apr_hash_t *props, svn_revnum_t replaces_rev) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); SVN_ERR_ASSERT(children != NULL); SVN_ERR_ASSERT(props != NULL); /* ### validate children are just basenames? */ SHOULD_NOT_BE_FINISHED(editor); SHOULD_ALLOW_ADD(editor, relpath); VERIFY_PARENT_MAY_EXIST(editor, relpath); CHECK_UNKNOWN_CHILD(editor, relpath); SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_add_directory) { START_CALLBACK(editor); err = editor->funcs.cb_add_directory(editor->baton, relpath, children, props, replaces_rev, editor->scratch_pool); END_CALLBACK(editor); } MARK_ADDED_DIR(editor, relpath); MARK_PARENT_STABLE(editor, relpath); CLEAR_INCOMPLETE(editor, relpath); #ifdef ENABLE_ORDERING_CHECK { int i; for (i = 0; i < children->nelts; i++) { const char *child_basename = APR_ARRAY_IDX(children, i, const char *); const char *child = svn_relpath_join(relpath, child_basename, editor->state_pool); svn_hash_sets(editor->pending_incomplete_children, child, ""); } } #endif svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_add_file(svn_editor_t *editor, const char *relpath, const svn_checksum_t *checksum, svn_stream_t *contents, apr_hash_t *props, svn_revnum_t replaces_rev) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); SVN_ERR_ASSERT(checksum != NULL && checksum->kind == SVN_EDITOR_CHECKSUM_KIND); SVN_ERR_ASSERT(contents != NULL); SVN_ERR_ASSERT(props != NULL); SHOULD_NOT_BE_FINISHED(editor); SHOULD_ALLOW_ADD(editor, relpath); VERIFY_PARENT_MAY_EXIST(editor, relpath); CHECK_UNKNOWN_CHILD(editor, relpath); SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_add_file) { START_CALLBACK(editor); err = editor->funcs.cb_add_file(editor->baton, relpath, checksum, contents, props, replaces_rev, editor->scratch_pool); END_CALLBACK(editor); } MARK_COMPLETED(editor, relpath); MARK_PARENT_STABLE(editor, relpath); CLEAR_INCOMPLETE(editor, relpath); svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_add_symlink(svn_editor_t *editor, const char *relpath, const char *target, apr_hash_t *props, svn_revnum_t replaces_rev) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); SVN_ERR_ASSERT(props != NULL); SHOULD_NOT_BE_FINISHED(editor); SHOULD_ALLOW_ADD(editor, relpath); VERIFY_PARENT_MAY_EXIST(editor, relpath); CHECK_UNKNOWN_CHILD(editor, relpath); SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_add_symlink) { START_CALLBACK(editor); err = editor->funcs.cb_add_symlink(editor->baton, relpath, target, props, replaces_rev, editor->scratch_pool); END_CALLBACK(editor); } MARK_COMPLETED(editor, relpath); MARK_PARENT_STABLE(editor, relpath); CLEAR_INCOMPLETE(editor, relpath); svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_add_absent(svn_editor_t *editor, const char *relpath, svn_node_kind_t kind, svn_revnum_t replaces_rev) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); SHOULD_NOT_BE_FINISHED(editor); SHOULD_ALLOW_ADD(editor, relpath); VERIFY_PARENT_MAY_EXIST(editor, relpath); CHECK_UNKNOWN_CHILD(editor, relpath); SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_add_absent) { START_CALLBACK(editor); err = editor->funcs.cb_add_absent(editor->baton, relpath, kind, replaces_rev, editor->scratch_pool); END_CALLBACK(editor); } MARK_COMPLETED(editor, relpath); MARK_PARENT_STABLE(editor, relpath); CLEAR_INCOMPLETE(editor, relpath); svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_alter_directory(svn_editor_t *editor, const char *relpath, svn_revnum_t revision, const apr_array_header_t *children, apr_hash_t *props) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); SVN_ERR_ASSERT(children != NULL || props != NULL); /* ### validate children are just basenames? */ SHOULD_NOT_BE_FINISHED(editor); SHOULD_ALLOW_ALTER(editor, relpath); VERIFY_PARENT_MAY_EXIST(editor, relpath); SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_alter_directory) { START_CALLBACK(editor); err = editor->funcs.cb_alter_directory(editor->baton, relpath, revision, children, props, editor->scratch_pool); END_CALLBACK(editor); } MARK_COMPLETED(editor, relpath); MARK_PARENT_STABLE(editor, relpath); #ifdef ENABLE_ORDERING_CHECK /* ### this is not entirely correct. we probably need to adjust the ### check_unknown_child() function for this scenario. */ #if 0 { int i; for (i = 0; i < children->nelts; i++) { const char *child_basename = APR_ARRAY_IDX(children, i, const char *); const char *child = svn_relpath_join(relpath, child_basename, editor->state_pool); apr_hash_set(editor->pending_incomplete_children, child, APR_HASH_KEY_STRING, ""); /* Perhaps MARK_ALLOW_ADD(editor, child); ? */ } } #endif #endif svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_alter_file(svn_editor_t *editor, const char *relpath, svn_revnum_t revision, apr_hash_t *props, const svn_checksum_t *checksum, svn_stream_t *contents) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); SVN_ERR_ASSERT((checksum != NULL && contents != NULL) || (checksum == NULL && contents == NULL)); SVN_ERR_ASSERT(props != NULL || checksum != NULL); if (checksum) SVN_ERR_ASSERT(checksum->kind == SVN_EDITOR_CHECKSUM_KIND); SHOULD_NOT_BE_FINISHED(editor); SHOULD_ALLOW_ALTER(editor, relpath); VERIFY_PARENT_MAY_EXIST(editor, relpath); SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_alter_file) { START_CALLBACK(editor); err = editor->funcs.cb_alter_file(editor->baton, relpath, revision, props, checksum, contents, editor->scratch_pool); END_CALLBACK(editor); } MARK_COMPLETED(editor, relpath); MARK_PARENT_STABLE(editor, relpath); svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_alter_symlink(svn_editor_t *editor, const char *relpath, svn_revnum_t revision, apr_hash_t *props, const char *target) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); SVN_ERR_ASSERT(props != NULL || target != NULL); SHOULD_NOT_BE_FINISHED(editor); SHOULD_ALLOW_ALTER(editor, relpath); VERIFY_PARENT_MAY_EXIST(editor, relpath); SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_alter_symlink) { START_CALLBACK(editor); err = editor->funcs.cb_alter_symlink(editor->baton, relpath, revision, props, target, editor->scratch_pool); END_CALLBACK(editor); } MARK_COMPLETED(editor, relpath); MARK_PARENT_STABLE(editor, relpath); svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_delete(svn_editor_t *editor, const char *relpath, svn_revnum_t revision) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); SHOULD_NOT_BE_FINISHED(editor); SHOULD_NOT_BE_COMPLETED(editor, relpath); VERIFY_PARENT_MAY_EXIST(editor, relpath); CHILD_DELETIONS_ALLOWED(editor, relpath); SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_delete) { START_CALLBACK(editor); err = editor->funcs.cb_delete(editor->baton, relpath, revision, editor->scratch_pool); END_CALLBACK(editor); } MARK_COMPLETED(editor, relpath); MARK_PARENT_STABLE(editor, relpath); svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_copy(svn_editor_t *editor, const char *src_relpath, svn_revnum_t src_revision, const char *dst_relpath, svn_revnum_t replaces_rev) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath)); SVN_ERR_ASSERT(svn_relpath_is_canonical(dst_relpath)); SHOULD_NOT_BE_FINISHED(editor); SHOULD_ALLOW_ADD(editor, dst_relpath); VERIFY_PARENT_MAY_EXIST(editor, src_relpath); VERIFY_PARENT_MAY_EXIST(editor, dst_relpath); SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_copy) { START_CALLBACK(editor); err = editor->funcs.cb_copy(editor->baton, src_relpath, src_revision, dst_relpath, replaces_rev, editor->scratch_pool); END_CALLBACK(editor); } MARK_ALLOW_ALTER(editor, dst_relpath); MARK_PARENT_STABLE(editor, dst_relpath); CLEAR_INCOMPLETE(editor, dst_relpath); svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_move(svn_editor_t *editor, const char *src_relpath, svn_revnum_t src_revision, const char *dst_relpath, svn_revnum_t replaces_rev) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath)); SVN_ERR_ASSERT(svn_relpath_is_canonical(dst_relpath)); SHOULD_NOT_BE_FINISHED(editor); SHOULD_NOT_BE_COMPLETED(editor, src_relpath); SHOULD_ALLOW_ADD(editor, dst_relpath); VERIFY_PARENT_MAY_EXIST(editor, src_relpath); CHILD_DELETIONS_ALLOWED(editor, src_relpath); VERIFY_PARENT_MAY_EXIST(editor, dst_relpath); SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_move) { START_CALLBACK(editor); err = editor->funcs.cb_move(editor->baton, src_relpath, src_revision, dst_relpath, replaces_rev, editor->scratch_pool); END_CALLBACK(editor); } MARK_ALLOW_ADD(editor, src_relpath); MARK_PARENT_STABLE(editor, src_relpath); MARK_ALLOW_ALTER(editor, dst_relpath); MARK_PARENT_STABLE(editor, dst_relpath); CLEAR_INCOMPLETE(editor, dst_relpath); svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_rotate(svn_editor_t *editor, const apr_array_header_t *relpaths, const apr_array_header_t *revisions) { svn_error_t *err = SVN_NO_ERROR; SHOULD_NOT_BE_FINISHED(editor); #ifdef ENABLE_ORDERING_CHECK { int i; for (i = 0; i < relpaths->nelts; i++) { const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *); SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath)); SHOULD_NOT_BE_COMPLETED(editor, relpath); VERIFY_PARENT_MAY_EXIST(editor, relpath); CHILD_DELETIONS_ALLOWED(editor, relpath); } } #endif SVN_ERR(check_cancel(editor)); if (editor->funcs.cb_rotate) { START_CALLBACK(editor); err = editor->funcs.cb_rotate(editor->baton, relpaths, revisions, editor->scratch_pool); END_CALLBACK(editor); } #ifdef ENABLE_ORDERING_CHECK { int i; for (i = 0; i < relpaths->nelts; i++) { const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *); MARK_ALLOW_ALTER(editor, relpath); MARK_PARENT_STABLE(editor, relpath); } } #endif svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_complete(svn_editor_t *editor) { svn_error_t *err = SVN_NO_ERROR; SHOULD_NOT_BE_FINISHED(editor); #ifdef ENABLE_ORDERING_CHECK SVN_ERR_ASSERT(apr_hash_count(editor->pending_incomplete_children) == 0); #endif if (editor->funcs.cb_complete) { START_CALLBACK(editor); err = editor->funcs.cb_complete(editor->baton, editor->scratch_pool); END_CALLBACK(editor); } MARK_FINISHED(editor); svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); } svn_error_t * svn_editor_abort(svn_editor_t *editor) { svn_error_t *err = SVN_NO_ERROR; SHOULD_NOT_BE_FINISHED(editor); if (editor->funcs.cb_abort) { START_CALLBACK(editor); err = editor->funcs.cb_abort(editor->baton, editor->scratch_pool); END_CALLBACK(editor); } MARK_FINISHED(editor); svn_pool_clear(editor->scratch_pool); return svn_error_trace(err); }