diff options
Diffstat (limited to 'subversion/libsvn_repos/commit.c')
-rw-r--r-- | subversion/libsvn_repos/commit.c | 636 |
1 files changed, 589 insertions, 47 deletions
diff --git a/subversion/libsvn_repos/commit.c b/subversion/libsvn_repos/commit.c index c8032e3..22cf873 100644 --- a/subversion/libsvn_repos/commit.c +++ b/subversion/libsvn_repos/commit.c @@ -26,6 +26,7 @@ #include <apr_pools.h> #include <apr_file_io.h> +#include "svn_hash.h" #include "svn_compat.h" #include "svn_pools.h" #include "svn_error.h" @@ -35,12 +36,17 @@ #include "svn_fs.h" #include "svn_repos.h" #include "svn_checksum.h" +#include "svn_ctype.h" #include "svn_props.h" #include "svn_mergeinfo.h" -#include "repos.h" #include "svn_private_config.h" + +#include "repos.h" + #include "private/svn_fspath.h" +#include "private/svn_fs_private.h" #include "private/svn_repos_private.h" +#include "private/svn_editor.h" @@ -128,6 +134,29 @@ struct file_baton }; +struct ev2_baton +{ + /* The repository we are editing. */ + svn_repos_t *repos; + + /* The authz baton for checks; NULL to skip authz. */ + svn_authz_t *authz; + + /* The repository name and user for performing authz checks. */ + const char *authz_repos_name; + const char *authz_user; + + /* Callback to provide info about the committed revision. */ + svn_commit_callback2_t commit_cb; + void *commit_baton; + + /* The FS txn editor */ + svn_editor_t *inner; + + /* The name of the open transaction (so we know what to commit) */ + const char *txn_name; +}; + /* Create and return a generic out-of-dateness error. */ static svn_error_t * @@ -143,6 +172,40 @@ out_of_date(const char *path, svn_node_kind_t kind) } +static svn_error_t * +invoke_commit_cb(svn_commit_callback2_t commit_cb, + void *commit_baton, + svn_fs_t *fs, + svn_revnum_t revision, + const char *post_commit_errstr, + apr_pool_t *scratch_pool) +{ + /* FS interface returns non-const values. */ + /* const */ svn_string_t *date; + /* const */ svn_string_t *author; + svn_commit_info_t *commit_info; + + if (commit_cb == NULL) + return SVN_NO_ERROR; + + SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE, + scratch_pool)); + SVN_ERR(svn_fs_revision_prop(&author, fs, revision, + SVN_PROP_REVISION_AUTHOR, + scratch_pool)); + + commit_info = svn_create_commit_info(scratch_pool); + + /* fill up the svn_commit_info structure */ + commit_info->revision = revision; + commit_info->date = date ? date->data : NULL; + commit_info->author = author ? author->data : NULL; + commit_info->post_commit_err = post_commit_errstr; + + return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool)); +} + + /* If EDITOR_BATON contains a valid authz callback, verify that the REQUIRED access to PATH in ROOT is authorized. Return an error @@ -196,7 +259,6 @@ make_dir_baton(struct edit_baton *edit_baton, return db; } - /* This function is the shared guts of add_file() and add_directory(), which see for the meanings of the parameters. The only extra parameter here is IS_DIR, which is TRUE when adding a directory, @@ -216,6 +278,9 @@ add_file_or_directory(const char *path, svn_boolean_t was_copied = FALSE; const char *full_path; + /* Reject paths which contain control characters (related to issue #4340). */ + SVN_ERR(svn_path_check_valid(path, pool)); + full_path = svn_fspath__join(eb->base_path, svn_relpath_canonicalize(path, pool), pool); @@ -247,7 +312,7 @@ add_file_or_directory(const char *path, out-of-dateness error. */ SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool)); if ((kind != svn_node_none) && (! pb->was_copied)) - return out_of_date(full_path, kind); + return svn_error_trace(out_of_date(full_path, kind)); /* For now, require that the url come from the same repository that this commit is operating on. */ @@ -396,13 +461,13 @@ delete_entry(const char *path, /* If PATH doesn't exist in the txn, the working copy is out of date. */ if (kind == svn_node_none) - return out_of_date(full_path, kind); + return svn_error_trace(out_of_date(full_path, kind)); /* Now, make sure we're deleting the node we *think* we're deleting, else return an out-of-dateness error. */ SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool)); if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev)) - return out_of_date(full_path, kind); + return svn_error_trace(out_of_date(full_path, kind)); /* This routine is a mindless wrapper. We call svn_fs_delete() because that will delete files and recursively delete @@ -518,7 +583,7 @@ open_file(const char *path, /* If the node our caller has is an older revision number than the one in our transaction, return an out-of-dateness error. */ if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev)) - return out_of_date(full_path, svn_node_file); + return svn_error_trace(out_of_date(full_path, svn_node_file)); /* Build a new file baton */ new_fb = apr_pcalloc(pool, sizeof(*new_fb)); @@ -602,7 +667,7 @@ change_dir_prop(void *dir_baton, eb->txn_root, db->path, pool)); if (db->base_rev < created_rev) - return out_of_date(db->path, svn_node_dir); + return svn_error_trace(out_of_date(db->path, svn_node_dir)); } return svn_repos_fs_change_node_prop(eb->txn_root, db->path, @@ -631,7 +696,8 @@ svn_repos__post_commit_error_str(svn_error_t *err, else hook_err2 = hook_err1; - /* This implementation counts on svn_repos_fs_commit_txn() returning + /* This implementation counts on svn_repos_fs_commit_txn() and + libsvn_repos/commit.c:complete_cb() returning svn_fs_commit_txn() as the parent error with a child SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error. If the parent error is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error @@ -695,6 +761,13 @@ close_edit(void *edit_baton, if (SVN_IS_VALID_REVNUM(new_revision)) { + /* The actual commit succeeded, i.e. the transaction does no longer + exist and we can't use txn_root for conflict resolution etc. + + Since close_edit is supposed to release resources, do it now. */ + if (eb->txn_root) + svn_fs_close_root(eb->txn_root); + if (err) { /* If the error was in post-commit, then the commit itself @@ -703,8 +776,14 @@ close_edit(void *edit_baton, display it as a warning) and clear the error. */ post_commit_err = svn_repos__post_commit_error_str(err, pool); svn_error_clear(err); - err = SVN_NO_ERROR; } + + /* Make sure a future abort doesn't perform + any work. This may occur if the commit + callback returns an error! */ + + eb->txn = NULL; + eb->txn_root = NULL; } else { @@ -730,41 +809,19 @@ close_edit(void *edit_baton, svn_fs_abort_txn(eb->txn, pool))); } - /* Pass new revision information to the caller's callback. */ - { - svn_string_t *date, *author; - svn_commit_info_t *commit_info; + /* At this point, the post-commit error has been converted to a string. + That information will be passed to a callback, if provided. If the + callback invocation fails in some way, that failure is returned here. + IOW, the post-commit error information is low priority compared to + other gunk here. */ - /* Even if there was a post-commit hook failure, it's more serious - if one of the calls here fails, so we explicitly check for errors - here, while saving the possible post-commit error for later. */ - - err = svn_fs_revision_prop(&date, svn_repos_fs(eb->repos), - new_revision, SVN_PROP_REVISION_DATE, - pool); - if (! err) - { - err = svn_fs_revision_prop(&author, svn_repos_fs(eb->repos), - new_revision, SVN_PROP_REVISION_AUTHOR, - pool); - } - - if ((! err) && eb->commit_callback) - { - commit_info = svn_create_commit_info(pool); - - /* fill up the svn_commit_info structure */ - commit_info->revision = new_revision; - commit_info->date = date ? date->data : NULL; - commit_info->author = author ? author->data : NULL; - commit_info->post_commit_err = post_commit_err; - err = (*eb->commit_callback)(commit_info, - eb->commit_callback_baton, - pool); - } - } - - return svn_error_trace(err); + /* Pass new revision information to the caller's callback. */ + return svn_error_trace(invoke_commit_cb(eb->commit_callback, + eb->commit_callback_baton, + eb->repos->fs, + new_revision, + post_commit_err, + pool)); } @@ -778,10 +835,102 @@ abort_edit(void *edit_baton, eb->txn_aborted = TRUE; + /* Since abort_edit is supposed to release resources, do it now. */ + if (eb->txn_root) + svn_fs_close_root(eb->txn_root); + return svn_error_trace(svn_fs_abort_txn(eb->txn, pool)); } +static svn_error_t * +fetch_props_func(apr_hash_t **props, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = baton; + svn_fs_root_t *fs_root; + svn_error_t *err; + + SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, + svn_fs_txn_base_revision(eb->txn), + scratch_pool)); + err = svn_fs_node_proplist(props, fs_root, path, result_pool); + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + *props = apr_hash_make(result_pool); + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + + return SVN_NO_ERROR; +} + +static svn_error_t * +fetch_kind_func(svn_node_kind_t *kind, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = baton; + svn_fs_root_t *fs_root; + + if (!SVN_IS_VALID_REVNUM(base_revision)) + base_revision = svn_fs_txn_base_revision(eb->txn); + + SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); + + SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +fetch_base_func(const char **filename, + void *baton, + const char *path, + svn_revnum_t base_revision, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct edit_baton *eb = baton; + svn_stream_t *contents; + svn_stream_t *file_stream; + const char *tmp_filename; + svn_fs_root_t *fs_root; + svn_error_t *err; + + if (!SVN_IS_VALID_REVNUM(base_revision)) + base_revision = svn_fs_txn_base_revision(eb->txn); + + SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); + + err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); + if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) + { + svn_error_clear(err); + *filename = NULL; + return SVN_NO_ERROR; + } + else if (err) + return svn_error_trace(err); + SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, + svn_io_file_del_on_pool_cleanup, + scratch_pool, scratch_pool)); + SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); + + *filename = apr_pstrdup(result_pool, tmp_filename); + + return SVN_NO_ERROR; +} + + /*** Public interfaces. ***/ @@ -793,8 +942,8 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor, const char *repos_url, const char *base_path, apr_hash_t *revprop_table, - svn_commit_callback2_t callback, - void *callback_baton, + svn_commit_callback2_t commit_callback, + void *commit_baton, svn_repos_authz_callback_t authz_callback, void *authz_baton, apr_pool_t *pool) @@ -802,6 +951,8 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor, svn_delta_editor_t *e; apr_pool_t *subpool = svn_pool_create(pool); struct edit_baton *eb; + svn_delta_shim_callbacks_t *shim_callbacks = + svn_delta_shim_callbacks_default(pool); /* Do a global authz access lookup. Users with no write access whatsoever to the repository don't get a commit editor. */ @@ -837,8 +988,8 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor, /* Set up the edit baton. */ eb->pool = subpool; eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool); - eb->commit_callback = callback; - eb->commit_callback_baton = callback_baton; + eb->commit_callback = commit_callback; + eb->commit_callback_baton = commit_baton; eb->authz_callback = authz_callback; eb->authz_baton = authz_baton; eb->base_path = svn_fspath__canonicalize(base_path, subpool); @@ -853,5 +1004,396 @@ svn_repos_get_commit_editor5(const svn_delta_editor_t **editor, *edit_baton = eb; *editor = e; + shim_callbacks->fetch_props_func = fetch_props_func; + shim_callbacks->fetch_kind_func = fetch_kind_func; + shim_callbacks->fetch_base_func = fetch_base_func; + shim_callbacks->fetch_baton = eb; + + SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, + eb->repos_url, eb->base_path, + shim_callbacks, pool, pool)); + + return SVN_NO_ERROR; +} + + +#if 0 +static svn_error_t * +ev2_check_authz(const struct ev2_baton *eb, + const char *relpath, + svn_repos_authz_access_t required, + apr_pool_t *scratch_pool) +{ + const char *fspath; + svn_boolean_t allowed; + + if (eb->authz == NULL) + return SVN_NO_ERROR; + + if (relpath) + fspath = apr_pstrcat(scratch_pool, "/", relpath, NULL); + else + fspath = NULL; + + SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath, + eb->authz_user, required, + &allowed, scratch_pool)); + if (!allowed) + return svn_error_create(required & svn_authz_write + ? SVN_ERR_AUTHZ_UNWRITABLE + : SVN_ERR_AUTHZ_UNREADABLE, + NULL, "Access denied"); + + return SVN_NO_ERROR; +} +#endif + + +/* This implements svn_editor_cb_add_directory_t */ +static svn_error_t * +add_directory_cb(void *baton, + const char *relpath, + const apr_array_header_t *children, + apr_hash_t *props, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props, + replaces_rev)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_add_file_t */ +static svn_error_t * +add_file_cb(void *baton, + const char *relpath, + const svn_checksum_t *checksum, + svn_stream_t *contents, + apr_hash_t *props, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props, + replaces_rev)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_add_symlink_t */ +static svn_error_t * +add_symlink_cb(void *baton, + const char *relpath, + const char *target, + apr_hash_t *props, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props, + replaces_rev)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_add_absent_t */ +static svn_error_t * +add_absent_cb(void *baton, + const char *relpath, + svn_node_kind_t kind, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_alter_directory_t */ +static svn_error_t * +alter_directory_cb(void *baton, + const char *relpath, + svn_revnum_t revision, + const apr_array_header_t *children, + apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision, + children, props)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_alter_file_t */ +static svn_error_t * +alter_file_cb(void *baton, + const char *relpath, + svn_revnum_t revision, + apr_hash_t *props, + const svn_checksum_t *checksum, + svn_stream_t *contents, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, props, + checksum, contents)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_alter_symlink_t */ +static svn_error_t * +alter_symlink_cb(void *baton, + const char *relpath, + svn_revnum_t revision, + apr_hash_t *props, + const char *target, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, props, + target)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_delete_t */ +static svn_error_t * +delete_cb(void *baton, + const char *relpath, + svn_revnum_t revision, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_delete(eb->inner, relpath, revision)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_copy_t */ +static svn_error_t * +copy_cb(void *baton, + const char *src_relpath, + svn_revnum_t src_revision, + const char *dst_relpath, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath, + replaces_rev)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_move_t */ +static svn_error_t * +move_cb(void *baton, + const char *src_relpath, + svn_revnum_t src_revision, + const char *dst_relpath, + svn_revnum_t replaces_rev, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath, + replaces_rev)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_rotate_t */ +static svn_error_t * +rotate_cb(void *baton, + const apr_array_header_t *relpaths, + const apr_array_header_t *revisions, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_rotate(eb->inner, relpaths, revisions)); + return SVN_NO_ERROR; +} + + +/* This implements svn_editor_cb_complete_t */ +static svn_error_t * +complete_cb(void *baton, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + svn_revnum_t revision; + svn_error_t *post_commit_err; + const char *conflict_path; + svn_error_t *err; + const char *post_commit_errstr; + apr_hash_t *hooks_env; + + /* Parse the hooks-env file (if any). */ + SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path, + scratch_pool, scratch_pool)); + + /* The transaction has been fully edited. Let the pre-commit hook + have a look at the thing. */ + SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env, + eb->txn_name, scratch_pool)); + + /* Hook is done. Let's do the actual commit. */ + SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path, + eb->inner, scratch_pool, scratch_pool)); + + /* Did a conflict occur during the commit process? */ + if (conflict_path != NULL) + return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, + _("Conflict at '%s'"), conflict_path); + + /* Since did not receive an error during the commit process, and no + conflict was specified... we committed a revision. Run the hooks. + Other errors may have occurred within the FS (specified by the + POST_COMMIT_ERR localvar), but we need to run the hooks. */ + SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); + err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision, + eb->txn_name, scratch_pool); + if (err) + err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err, + _("Commit succeeded, but post-commit hook failed")); + + /* Combine the FS errors with the hook errors, and stringify. */ + err = svn_error_compose_create(post_commit_err, err); + if (err) + { + post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool); + svn_error_clear(err); + } + else + { + post_commit_errstr = NULL; + } + + return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton, + eb->repos->fs, revision, + post_commit_errstr, + scratch_pool)); +} + + +/* This implements svn_editor_cb_abort_t */ +static svn_error_t * +abort_cb(void *baton, + apr_pool_t *scratch_pool) +{ + struct ev2_baton *eb = baton; + + SVN_ERR(svn_editor_abort(eb->inner)); + return SVN_NO_ERROR; +} + + +static svn_error_t * +apply_revprops(svn_fs_t *fs, + const char *txn_name, + apr_hash_t *revprops, + apr_pool_t *scratch_pool) +{ + svn_fs_txn_t *txn; + const apr_array_header_t *revprops_array; + + /* The FS editor has a TXN inside it, but we can't access it. Open another + based on the TXN_NAME. */ + SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool)); + + /* Validate and apply the revision properties. */ + revprops_array = svn_prop_hash_to_array(revprops, scratch_pool); + SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool)); + + /* ### do we need to force the txn to close, or is it enough to wait + ### for the pool to be cleared? */ + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_repos__get_commit_ev2(svn_editor_t **editor, + svn_repos_t *repos, + svn_authz_t *authz, + const char *authz_repos_name, + const char *authz_user, + apr_hash_t *revprops, + svn_commit_callback2_t commit_cb, + void *commit_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + static const svn_editor_cb_many_t editor_cbs = { + add_directory_cb, + add_file_cb, + add_symlink_cb, + add_absent_cb, + alter_directory_cb, + alter_file_cb, + alter_symlink_cb, + delete_cb, + copy_cb, + move_cb, + rotate_cb, + complete_cb, + abort_cb + }; + struct ev2_baton *eb; + const svn_string_t *author; + apr_hash_t *hooks_env; + + /* Parse the hooks-env file (if any). */ + SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, + scratch_pool, scratch_pool)); + + /* Can the user modify the repository at all? */ + /* ### check against AUTHZ. */ + + author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR); + + eb = apr_palloc(result_pool, sizeof(*eb)); + eb->repos = repos; + eb->authz = authz; + eb->authz_repos_name = authz_repos_name; + eb->authz_user = authz_user; + eb->commit_cb = commit_cb; + eb->commit_baton = commit_baton; + + SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name, + repos->fs, SVN_FS_TXN_CHECK_LOCKS, + cancel_func, cancel_baton, + result_pool, scratch_pool)); + + /* The TXN has been created. Go ahead and apply all revision properties. */ + SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool)); + + /* Okay... some access is allowed. Let's run the start-commit hook. */ + SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env, + author ? author->data : NULL, + repos->client_capabilities, + eb->txn_name, scratch_pool)); + + /* Wrap the FS editor within our editor. */ + SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton, + result_pool, scratch_pool)); + SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool)); + return SVN_NO_ERROR; } |