diff options
Diffstat (limited to 'subversion/libsvn_repos/commit.c')
| -rw-r--r-- | subversion/libsvn_repos/commit.c | 1381 | 
1 files changed, 1381 insertions, 0 deletions
| diff --git a/subversion/libsvn_repos/commit.c b/subversion/libsvn_repos/commit.c new file mode 100644 index 000000000000..c4606ab4b7bd --- /dev/null +++ b/subversion/libsvn_repos/commit.c @@ -0,0 +1,1381 @@ +/* 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; + +  /* 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. */ +}; + + +struct file_baton +{ +  struct edit_baton *edit_baton; +  const char *path; /* the -absolute- path to this file in the fs */ +}; + + +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); +} + + +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 +   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.  */ +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; + +  /* 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); + +  /* 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; +      size_t repos_url_len; +      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); +      repos_url_len = strlen(eb->repos_url); +      if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0) +        return svn_error_createf +          (SVN_ERR_FS_GENERAL, NULL, +           _("Source url '%s' is from different repository"), copy_path); + +      fs_path = apr_pstrdup(subpool, copy_path + repos_url_len); + +      /* 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) +    { +      *return_baton = make_dir_baton(eb, pb, full_path, was_copied, +                                     SVN_INVALID_REVNUM, pool); +    } +  else +    { +      struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb)); +      new_fb->edit_baton = eb; +      new_fb->path = full_path; +      *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; + +  /* Ignore BASE_REVISION.  We always build our transaction against +     HEAD.  However, we will keep it in our dir baton for out of +     dateness checks.  */ +  SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool)); + +  /* 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_revnum_t cr_rev; +  svn_repos_authz_access_t required = svn_authz_write; +  const char *full_path; + +  full_path = svn_fspath__join(eb->base_path, +                               svn_relpath_canonicalize(path, pool), 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. */ +  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 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 +     directories.  */ +  return 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; + +  full_path = svn_fspath__join(eb->base_path, +                               svn_relpath_canonicalize(path, pool), 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; + +  /* Check for write authorization. */ +  SVN_ERR(check_authz(fb->edit_baton, fb->path, +                      fb->edit_baton->txn_root, +                      svn_authz_write, pool)); + +  return svn_fs_apply_textdelta(handler, handler_baton, +                                fb->edit_baton->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; + +  full_path = svn_fspath__join(eb->base_path, +                               svn_relpath_canonicalize(path, pool), 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) && (base_revision < cr_rev)) +    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)); +  new_fb->edit_baton = eb; +  new_fb->path = full_path; + +  *file_baton = new_fb; + +  /* Destory 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; + +  /* Check for write authorization. */ +  SVN_ERR(check_authz(eb, fb->path, eb->txn_root, +                      svn_authz_write, pool)); + +  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. */ +  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)); + +      if (db->base_rev < created_rev) +        return svn_error_trace(out_of_date(db->path, svn_node_dir)); +    } + +  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)) +    { +      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); +        } +    } +  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; + +  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, +                             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); + +  /* 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 = repos_url; +  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, +                                   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; +} | 
