| /* commit.c --- editor for committing changes to a filesystem. |
| * |
| * ==================================================================== |
| * 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 <string.h> |
| |
| #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" |
| #include "svn_dirent_uri.h" |
| #include "svn_path.h" |
| #include "svn_delta.h" |
| #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 "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" |
| |
| |
| |
| /*** Editor batons. ***/ |
| |
| struct edit_baton |
| { |
| apr_pool_t *pool; |
| |
| /** Supplied when the editor is created: **/ |
| |
| /* Revision properties to set for this commit. */ |
| apr_hash_t *revprop_table; |
| |
| /* Callback to run when the commit is done. */ |
| svn_commit_callback2_t commit_callback; |
| void *commit_callback_baton; |
| |
| /* Callback to check authorizations on paths. */ |
| svn_repos_authz_callback_t authz_callback; |
| void *authz_baton; |
| |
| /* The already-open svn repository to commit to. */ |
| svn_repos_t *repos; |
| |
| /* URL to the root of the open repository. */ |
| const char *repos_url_decoded; |
| |
| /* The name of the repository (here for convenience). */ |
| const char *repos_name; |
| |
| /* The filesystem associated with the REPOS above (here for |
| convenience). */ |
| svn_fs_t *fs; |
| |
| /* Location in fs where the edit will begin. */ |
| const char *base_path; |
| |
| /* Does this set of interfaces 'own' the commit transaction? */ |
| svn_boolean_t txn_owner; |
| |
| /* svn transaction associated with this edit (created in |
| open_root, or supplied by the public API caller). */ |
| svn_fs_txn_t *txn; |
| |
| /** Filled in during open_root: **/ |
| |
| /* The name of the transaction. */ |
| const char *txn_name; |
| |
| /* The object representing the root directory of the svn txn. */ |
| svn_fs_root_t *txn_root; |
| |
| /* Avoid aborting an fs transaction more than once */ |
| svn_boolean_t txn_aborted; |
| |
| /** Filled in when the edit is closed: **/ |
| |
| /* The new revision created by this commit. */ |
| svn_revnum_t *new_rev; |
| |
| /* The date (according to the repository) of this commit. */ |
| const char **committed_date; |
| |
| /* The author (also according to the repository) of this commit. */ |
| const char **committed_author; |
| }; |
| |
| |
| struct dir_baton |
| { |
| struct edit_baton *edit_baton; |
| struct dir_baton *parent; |
| const char *path; /* the -absolute- path to this dir in the fs */ |
| svn_revnum_t base_rev; /* the revision I'm based on */ |
| svn_boolean_t was_copied; /* was this directory added with history? */ |
| apr_pool_t *pool; /* my personal pool, in which I am allocated. */ |
| svn_boolean_t checked_write; /* TRUE after successful write check */ |
| }; |
| |
| |
| struct file_baton |
| { |
| struct edit_baton *edit_baton; |
| const char *path; /* the -absolute- path to this file in the fs */ |
| svn_boolean_t checked_write; /* TRUE after successful write check */ |
| }; |
| |
| |
| 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 * |
| out_of_date(const char *path, svn_node_kind_t kind) |
| { |
| return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, |
| (kind == svn_node_dir |
| ? _("Directory '%s' is out of date") |
| : kind == svn_node_file |
| ? _("File '%s' is out of date") |
| : _("'%s' is out of date")), |
| path); |
| } |
| |
| /* Perform an out of date check for base_rev against created rev, |
| and a sanity check of base_rev. */ |
| static svn_error_t * |
| check_out_of_date(struct edit_baton *eb, |
| const char *path, |
| svn_node_kind_t kind, |
| svn_revnum_t base_rev, |
| svn_revnum_t created_rev) |
| { |
| if (base_rev < created_rev) |
| { |
| return out_of_date(path, kind); |
| } |
| else if (base_rev > created_rev) |
| { |
| if (base_rev > svn_fs_txn_base_revision(eb->txn)) |
| return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, |
| _("No such revision %ld"), |
| base_rev); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| 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; |
| apr_hash_t *revprops; |
| |
| if (commit_cb == NULL) |
| return SVN_NO_ERROR; |
| |
| SVN_ERR(svn_fs_revision_proplist2(&revprops, fs, revision, |
| TRUE, scratch_pool, scratch_pool)); |
| |
| date = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE); |
| author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR); |
| |
| 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; |
| /* commit_info->repos_root is not set by the repos layer, only by RA layers */ |
| |
| 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 |
| appropriate for throwing out of the commit editor with SVN_ERR. If |
| no authz callback is present in EDITOR_BATON, then authorize all |
| paths. Use POOL for temporary allocation only. */ |
| static svn_error_t * |
| check_authz(struct edit_baton *editor_baton, const char *path, |
| svn_fs_root_t *root, svn_repos_authz_access_t required, |
| apr_pool_t *pool) |
| { |
| if (editor_baton->authz_callback) |
| { |
| svn_boolean_t allowed; |
| |
| SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path, |
| editor_baton->authz_baton, 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; |
| } |
| |
| |
| /* Return a directory baton allocated in POOL which represents |
| FULL_PATH, which is the immediate directory child of the directory |
| represented by PARENT_BATON. EDIT_BATON is the commit editor |
| baton. WAS_COPIED reveals whether or not this directory is the |
| result of a copy operation. BASE_REVISION is the base revision of |
| the directory. */ |
| static struct dir_baton * |
| make_dir_baton(struct edit_baton *edit_baton, |
| struct dir_baton *parent_baton, |
| const char *full_path, |
| svn_boolean_t was_copied, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *db; |
| db = apr_pcalloc(pool, sizeof(*db)); |
| db->edit_baton = edit_baton; |
| db->parent = parent_baton; |
| db->pool = pool; |
| db->path = full_path; |
| db->was_copied = was_copied; |
| db->base_rev = base_revision; |
| 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, |
| and FALSE when adding a file. |
| |
| COPY_PATH must be a full URL, not a relative path. */ |
| static svn_error_t * |
| add_file_or_directory(const char *path, |
| void *parent_baton, |
| const char *copy_path, |
| svn_revnum_t copy_revision, |
| svn_boolean_t is_dir, |
| apr_pool_t *pool, |
| void **return_baton) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct edit_baton *eb = pb->edit_baton; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| svn_boolean_t was_copied = FALSE; |
| const char *full_path, *canonicalized_path; |
| |
| /* Reject paths which contain control characters (related to issue #4340). */ |
| SVN_ERR(svn_path_check_valid(path, pool)); |
| |
| SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, |
| pool, pool)); |
| full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); |
| |
| /* Sanity check. */ |
| if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision))) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, NULL, |
| _("Got source path but no source revision for '%s'"), full_path); |
| |
| if (copy_path) |
| { |
| const char *fs_path; |
| svn_fs_root_t *copy_root; |
| svn_node_kind_t kind; |
| svn_repos_authz_access_t required; |
| |
| /* Copy requires recursive write access to the destination path |
| and write access to the parent path. */ |
| required = svn_authz_write | (is_dir ? svn_authz_recursive : 0); |
| SVN_ERR(check_authz(eb, full_path, eb->txn_root, |
| required, subpool)); |
| SVN_ERR(check_authz(eb, pb->path, eb->txn_root, |
| svn_authz_write, subpool)); |
| |
| /* Check PATH in our transaction. Make sure it does not exist |
| unless its parent directory was copied (in which case, the |
| thing might have been copied in as well), else return an |
| 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 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. */ |
| copy_path = svn_path_uri_decode(copy_path, subpool); |
| fs_path = svn_cstring_skip_prefix(copy_path, eb->repos_url_decoded); |
| if (!fs_path) |
| return svn_error_createf |
| (SVN_ERR_FS_GENERAL, NULL, |
| _("Source url '%s' is from different repository"), copy_path); |
| |
| /* Now use the "fs_path" as an absolute path within the |
| repository to make the copy from. */ |
| SVN_ERR(svn_fs_revision_root(©_root, eb->fs, |
| copy_revision, subpool)); |
| |
| /* Copy also requires (recursive) read access to the source */ |
| required = svn_authz_read | (is_dir ? svn_authz_recursive : 0); |
| SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool)); |
| |
| SVN_ERR(svn_fs_copy(copy_root, fs_path, |
| eb->txn_root, full_path, subpool)); |
| was_copied = TRUE; |
| } |
| else |
| { |
| /* No ancestry given, just make a new directory or empty file. |
| Note that we don't perform an existence check here like the |
| copy-from case does -- that's because svn_fs_make_*() |
| already errors out if the file already exists. Verify write |
| access to the full path and to the parent. */ |
| SVN_ERR(check_authz(eb, full_path, eb->txn_root, |
| svn_authz_write, subpool)); |
| SVN_ERR(check_authz(eb, pb->path, eb->txn_root, |
| svn_authz_write, subpool)); |
| if (is_dir) |
| SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool)); |
| else |
| SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool)); |
| } |
| |
| /* Cleanup our temporary subpool. */ |
| svn_pool_destroy(subpool); |
| |
| /* Build a new child baton. */ |
| if (is_dir) |
| { |
| struct dir_baton *new_db = make_dir_baton(eb, pb, full_path, was_copied, |
| SVN_INVALID_REVNUM, pool); |
| |
| new_db->checked_write = TRUE; /* Just created */ |
| *return_baton = new_db; |
| } |
| else |
| { |
| struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb)); |
| new_fb->edit_baton = eb; |
| new_fb->path = full_path; |
| new_fb->checked_write = TRUE; /* Just created */ |
| *return_baton = new_fb; |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /*** Editor functions ***/ |
| |
| static svn_error_t * |
| open_root(void *edit_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **root_baton) |
| { |
| struct dir_baton *dirb; |
| struct edit_baton *eb = edit_baton; |
| svn_revnum_t youngest; |
| |
| /* We always build our transaction against HEAD. However, we will |
| sanity-check BASE_REVISION and keep it in our dir baton for out |
| of dateness checks. */ |
| SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool)); |
| |
| if (base_revision > youngest) |
| return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, |
| _("No such revision %ld (HEAD is %ld)"), |
| base_revision, youngest); |
| |
| /* Unless we've been instructed to use a specific transaction, we'll |
| make our own. */ |
| if (eb->txn_owner) |
| { |
| SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn), |
| eb->repos, |
| youngest, |
| eb->revprop_table, |
| eb->pool)); |
| } |
| else /* Even if we aren't the owner of the transaction, we might |
| have been instructed to set some properties. */ |
| { |
| apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table, |
| pool); |
| SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool)); |
| } |
| SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool)); |
| SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool)); |
| |
| /* Create a root dir baton. The `base_path' field is an -absolute- |
| path in the filesystem, upon which all further editor paths are |
| based. */ |
| dirb = apr_pcalloc(pool, sizeof(*dirb)); |
| dirb->edit_baton = edit_baton; |
| dirb->parent = NULL; |
| dirb->pool = pool; |
| dirb->was_copied = FALSE; |
| dirb->path = apr_pstrdup(pool, eb->base_path); |
| dirb->base_rev = base_revision; |
| |
| *root_baton = dirb; |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| static svn_error_t * |
| delete_entry(const char *path, |
| svn_revnum_t revision, |
| void *parent_baton, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *parent = parent_baton; |
| struct edit_baton *eb = parent->edit_baton; |
| svn_node_kind_t kind; |
| svn_repos_authz_access_t required = svn_authz_write; |
| const char *full_path, *canonicalized_path; |
| |
| SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, |
| pool, pool)); |
| full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); |
| |
| /* Check PATH in our transaction. */ |
| SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool)); |
| |
| /* Deletion requires a recursive write access, as well as write |
| access to the parent directory. */ |
| if (kind == svn_node_dir) |
| required |= svn_authz_recursive; |
| SVN_ERR(check_authz(eb, full_path, eb->txn_root, |
| required, pool)); |
| SVN_ERR(check_authz(eb, parent->path, eb->txn_root, |
| svn_authz_write, pool)); |
| |
| /* If PATH doesn't exist in the txn, the working copy is out of date. */ |
| if (kind == svn_node_none) |
| 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. */ |
| if (SVN_IS_VALID_REVNUM(revision)) |
| { |
| svn_revnum_t cr_rev; |
| |
| SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool)); |
| SVN_ERR(check_out_of_date(eb, full_path, kind, revision, cr_rev)); |
| } |
| |
| /* This routine is a mindless wrapper. We call svn_fs_delete() |
| because that will delete files and recursively delete |
| directories. */ |
| return svn_error_trace(svn_fs_delete(eb->txn_root, full_path, pool)); |
| } |
| |
| |
| static svn_error_t * |
| add_directory(const char *path, |
| void *parent_baton, |
| const char *copy_path, |
| svn_revnum_t copy_revision, |
| apr_pool_t *pool, |
| void **child_baton) |
| { |
| return add_file_or_directory(path, parent_baton, copy_path, copy_revision, |
| TRUE /* is_dir */, pool, child_baton); |
| } |
| |
| |
| static svn_error_t * |
| open_directory(const char *path, |
| void *parent_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **child_baton) |
| { |
| struct dir_baton *pb = parent_baton; |
| struct edit_baton *eb = pb->edit_baton; |
| svn_node_kind_t kind; |
| const char *full_path, *canonicalized_path; |
| |
| SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, |
| pool, pool)); |
| full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); |
| |
| /* Check PATH in our transaction. If it does not exist, |
| return a 'Path not present' error. */ |
| SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool)); |
| if (kind == svn_node_none) |
| return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, |
| _("Path '%s' not present"), |
| path); |
| |
| /* Build a new dir baton for this directory. */ |
| *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied, |
| base_revision, pool); |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| apply_textdelta(void *file_baton, |
| const char *base_checksum, |
| apr_pool_t *pool, |
| svn_txdelta_window_handler_t *handler, |
| void **handler_baton) |
| { |
| struct file_baton *fb = file_baton; |
| struct edit_baton *eb = fb->edit_baton; |
| |
| if (!fb->checked_write) |
| { |
| /* Check for write authorization. */ |
| SVN_ERR(check_authz(eb, fb->path, eb->txn_root, |
| svn_authz_write, pool)); |
| fb->checked_write = TRUE; |
| } |
| |
| return svn_error_trace( |
| svn_fs_apply_textdelta(handler, handler_baton, |
| eb->txn_root, |
| fb->path, |
| base_checksum, |
| NULL, |
| pool)); |
| } |
| |
| |
| static svn_error_t * |
| add_file(const char *path, |
| void *parent_baton, |
| const char *copy_path, |
| svn_revnum_t copy_revision, |
| apr_pool_t *pool, |
| void **file_baton) |
| { |
| return add_file_or_directory(path, parent_baton, copy_path, copy_revision, |
| FALSE /* is_dir */, pool, file_baton); |
| } |
| |
| |
| static svn_error_t * |
| open_file(const char *path, |
| void *parent_baton, |
| svn_revnum_t base_revision, |
| apr_pool_t *pool, |
| void **file_baton) |
| { |
| struct file_baton *new_fb; |
| struct dir_baton *pb = parent_baton; |
| struct edit_baton *eb = pb->edit_baton; |
| svn_revnum_t cr_rev; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| const char *full_path, *canonicalized_path; |
| |
| SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, |
| pool, pool)); |
| full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); |
| |
| /* Check for read authorization. */ |
| SVN_ERR(check_authz(eb, full_path, eb->txn_root, |
| svn_authz_read, subpool)); |
| |
| /* Get this node's creation revision (doubles as an existence check). */ |
| SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, |
| subpool)); |
| |
| /* 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)) |
| SVN_ERR(check_out_of_date(eb, full_path, svn_node_file, |
| base_revision, cr_rev)); |
| |
| /* Build a new file baton */ |
| new_fb = apr_pcalloc(pool, sizeof(*new_fb)); |
| new_fb->edit_baton = eb; |
| new_fb->path = full_path; |
| |
| *file_baton = new_fb; |
| |
| /* Destroy the work subpool. */ |
| svn_pool_destroy(subpool); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| change_file_prop(void *file_baton, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| struct file_baton *fb = file_baton; |
| struct edit_baton *eb = fb->edit_baton; |
| |
| if (!fb->checked_write) |
| { |
| /* Check for write authorization. */ |
| SVN_ERR(check_authz(eb, fb->path, eb->txn_root, |
| svn_authz_write, pool)); |
| fb->checked_write = TRUE; |
| } |
| |
| return svn_repos_fs_change_node_prop(eb->txn_root, fb->path, |
| name, value, pool); |
| } |
| |
| |
| static svn_error_t * |
| close_file(void *file_baton, |
| const char *text_digest, |
| apr_pool_t *pool) |
| { |
| struct file_baton *fb = file_baton; |
| |
| if (text_digest) |
| { |
| svn_checksum_t *checksum; |
| svn_checksum_t *text_checksum; |
| |
| SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, |
| fb->edit_baton->txn_root, fb->path, |
| TRUE, pool)); |
| SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, |
| text_digest, pool)); |
| |
| if (!svn_checksum_match(text_checksum, checksum)) |
| return svn_checksum_mismatch_err(text_checksum, checksum, pool, |
| _("Checksum mismatch for resulting fulltext\n(%s)"), |
| fb->path); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| change_dir_prop(void *dir_baton, |
| const char *name, |
| const svn_string_t *value, |
| apr_pool_t *pool) |
| { |
| struct dir_baton *db = dir_baton; |
| struct edit_baton *eb = db->edit_baton; |
| |
| /* Check for write authorization. */ |
| if (!db->checked_write) |
| { |
| SVN_ERR(check_authz(eb, db->path, eb->txn_root, |
| svn_authz_write, pool)); |
| |
| if (SVN_IS_VALID_REVNUM(db->base_rev)) |
| { |
| /* Subversion rule: propchanges can only happen on a directory |
| which is up-to-date. */ |
| svn_revnum_t created_rev; |
| SVN_ERR(svn_fs_node_created_rev(&created_rev, |
| eb->txn_root, db->path, pool)); |
| |
| SVN_ERR(check_out_of_date(eb, db->path, svn_node_dir, |
| db->base_rev, created_rev)); |
| } |
| |
| db->checked_write = TRUE; /* Skip on further prop changes */ |
| } |
| |
| return svn_repos_fs_change_node_prop(eb->txn_root, db->path, |
| name, value, pool); |
| } |
| |
| const char * |
| svn_repos__post_commit_error_str(svn_error_t *err, |
| apr_pool_t *pool) |
| { |
| svn_error_t *hook_err1, *hook_err2; |
| const char *msg; |
| |
| if (! err) |
| return _("(no error)"); |
| |
| err = svn_error_purge_tracing(err); |
| |
| /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped |
| error from the post-commit script, if any, and hook_err2 should |
| be the original error, but be defensive and handle a case where |
| SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */ |
| hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED); |
| if (hook_err1 && hook_err1->child) |
| hook_err2 = hook_err1->child; |
| else |
| hook_err2 = hook_err1; |
| |
| /* 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 |
| in svn_fs_commit_txn(). |
| |
| The post-commit hook error message is already self describing, so |
| it can be dropped into an error message without any additional |
| text. */ |
| if (hook_err1) |
| { |
| if (err == hook_err1) |
| { |
| if (hook_err2->message) |
| msg = apr_pstrdup(pool, hook_err2->message); |
| else |
| msg = _("post-commit hook failed with no error message."); |
| } |
| else |
| { |
| msg = hook_err2->message |
| ? apr_pstrdup(pool, hook_err2->message) |
| : _("post-commit hook failed with no error message."); |
| msg = apr_psprintf( |
| pool, |
| _("post commit FS processing had error:\n%s\n%s"), |
| err->message ? err->message : _("(no error message)"), |
| msg); |
| } |
| } |
| else |
| { |
| msg = apr_psprintf(pool, |
| _("post commit FS processing had error:\n%s"), |
| err->message ? err->message |
| : _("(no error message)")); |
| } |
| |
| return msg; |
| } |
| |
| static svn_error_t * |
| close_edit(void *edit_baton, |
| apr_pool_t *pool) |
| { |
| struct edit_baton *eb = edit_baton; |
| svn_revnum_t new_revision = SVN_INVALID_REVNUM; |
| svn_error_t *err; |
| const char *conflict; |
| const char *post_commit_err = NULL; |
| |
| /* If no transaction has been created (ie. if open_root wasn't |
| called before close_edit), abort the operation here with an |
| error. */ |
| if (! eb->txn) |
| return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, |
| "No valid transaction supplied to close_edit"); |
| |
| /* Commit. */ |
| err = svn_repos_fs_commit_txn(&conflict, eb->repos, |
| &new_revision, eb->txn, pool); |
| |
| 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 |
| succeeded. In which case, save the post-commit warning |
| (to be reported back to the client, who will probably |
| display it as a warning) and clear the error. */ |
| post_commit_err = svn_repos__post_commit_error_str(err, pool); |
| svn_error_clear(err); |
| } |
| |
| /* 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 |
| { |
| /* ### todo: we should check whether it really was a conflict, |
| and return the conflict info if so? */ |
| |
| /* If the commit failed, it's *probably* due to a conflict -- |
| that is, the txn being out-of-date. The filesystem gives us |
| the ability to continue diddling the transaction and try |
| again; but let's face it: that's not how the cvs or svn works |
| from a user interface standpoint. Thus we don't make use of |
| this fs feature (for now, at least.) |
| |
| So, in a nutshell: svn commits are an all-or-nothing deal. |
| Each commit creates a new fs txn which either succeeds or is |
| aborted completely. No second chances; the user simply |
| needs to update and commit again :) */ |
| |
| eb->txn_aborted = TRUE; |
| |
| return svn_error_trace( |
| svn_error_compose_create(err, |
| svn_fs_abort_txn(eb->txn, pool))); |
| } |
| |
| /* 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. */ |
| |
| /* 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)); |
| } |
| |
| |
| static svn_error_t * |
| abort_edit(void *edit_baton, |
| apr_pool_t *pool) |
| { |
| struct edit_baton *eb = edit_baton; |
| if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted) |
| return SVN_NO_ERROR; |
| |
| 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. ***/ |
| |
| svn_error_t * |
| svn_repos_get_commit_editor5(const svn_delta_editor_t **editor, |
| void **edit_baton, |
| svn_repos_t *repos, |
| svn_fs_txn_t *txn, |
| const char *repos_url_decoded, |
| const char *base_path, |
| apr_hash_t *revprop_table, |
| svn_commit_callback2_t commit_callback, |
| void *commit_baton, |
| svn_repos_authz_callback_t authz_callback, |
| void *authz_baton, |
| apr_pool_t *pool) |
| { |
| 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); |
| const char *repos_url = svn_path_uri_encode(repos_url_decoded, pool); |
| |
| /* Do a global authz access lookup. Users with no write access |
| whatsoever to the repository don't get a commit editor. */ |
| if (authz_callback) |
| { |
| svn_boolean_t allowed; |
| |
| SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL, |
| authz_baton, pool)); |
| if (!allowed) |
| return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL, |
| "Not authorized to open a commit editor."); |
| } |
| |
| /* Allocate the structures. */ |
| e = svn_delta_default_editor(pool); |
| eb = apr_pcalloc(subpool, sizeof(*eb)); |
| |
| /* Set up the editor. */ |
| e->open_root = open_root; |
| e->delete_entry = delete_entry; |
| e->add_directory = add_directory; |
| e->open_directory = open_directory; |
| e->change_dir_prop = change_dir_prop; |
| e->add_file = add_file; |
| e->open_file = open_file; |
| e->close_file = close_file; |
| e->apply_textdelta = apply_textdelta; |
| e->change_file_prop = change_file_prop; |
| e->close_edit = close_edit; |
| e->abort_edit = abort_edit; |
| |
| /* Set up the edit baton. */ |
| eb->pool = subpool; |
| eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool); |
| 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); |
| eb->repos = repos; |
| eb->repos_url_decoded = repos_url_decoded; |
| eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool), |
| subpool); |
| eb->fs = svn_repos_fs(repos); |
| eb->txn = txn; |
| eb->txn_owner = txn == NULL; |
| |
| *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, |
| 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, SVN_VA_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, |
| const svn_checksum_t *checksum, |
| svn_stream_t *contents, |
| apr_hash_t *props, |
| apr_pool_t *scratch_pool) |
| { |
| struct ev2_baton *eb = baton; |
| |
| SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, |
| checksum, contents, props)); |
| 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, |
| const char *target, |
| apr_hash_t *props, |
| apr_pool_t *scratch_pool) |
| { |
| struct ev2_baton *eb = baton; |
| |
| SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, |
| target, props)); |
| 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_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, |
| 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; |
| } |