Ejemplo n.º 1
0
/* 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 edit_baton *eb = baton;
    const char *src_fspath = FSPATH(src_relpath, scratch_pool);
    const char *dst_fspath = FSPATH(dst_relpath, scratch_pool);
    svn_fs_root_t *root;
    svn_fs_root_t *src_root;

    SVN_ERR(get_root(&root, eb));

    /* Check if we can we replace the maybe-specified destination (revision).  */
    if (SVN_IS_VALID_REVNUM(replaces_rev))
    {
        SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
        SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
    }
    else
    {
        SVN_ERR(can_create(root, dst_fspath, scratch_pool));
    }

    SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
                                 scratch_pool));
    SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
    svn_fs_close_root(src_root);

    return SVN_NO_ERROR;
}
Ejemplo n.º 2
0
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));
}
Ejemplo n.º 3
0
/* This implements svn_editor_cb_complete_t */
static svn_error_t *
complete_cb(void *baton,
            apr_pool_t *scratch_pool)
{
    struct edit_baton *eb = baton;

    /* Watch out for a following call to svn_fs_editor_commit(). Note that
       we are likely here because svn_fs_editor_commit() was called, and it
       invoked svn_editor_complete().  */
    eb->completed = TRUE;

    if (eb->root != NULL)
    {
        svn_fs_close_root(eb->root);
        eb->root = NULL;
    }

    return SVN_NO_ERROR;
}
Ejemplo n.º 4
0
/* 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 edit_baton *eb = baton;
    const char *src_fspath = FSPATH(src_relpath, scratch_pool);
    const char *dst_fspath = FSPATH(dst_relpath, scratch_pool);
    svn_fs_root_t *root;
    svn_fs_root_t *src_root;

    SVN_ERR(get_root(&root, eb));

    /* Check if we delete the specified source (revision), and can we replace
       the maybe-specified destination (revision).  */
    SVN_ERR(can_modify(root, src_fspath, src_revision, scratch_pool));
    if (SVN_IS_VALID_REVNUM(replaces_rev))
    {
        SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
        SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
    }
    else
    {
        SVN_ERR(can_create(root, dst_fspath, scratch_pool));
    }

    /* ### would be nice to have svn_fs_move()  */

    /* Copy the src to the dst. */
    SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
                                 scratch_pool));
    SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
    svn_fs_close_root(src_root);

    /* Notice: we're deleting the src repos path from the dst root. */
    SVN_ERR(svn_fs_delete(root, src_fspath, scratch_pool));

    return SVN_NO_ERROR;
}
Ejemplo n.º 5
0
/* This implements svn_editor_cb_abort_t */
static svn_error_t *
abort_cb(void *baton,
         apr_pool_t *scratch_pool)
{
    struct edit_baton *eb = baton;
    svn_error_t *err;

    /* Don't allow a following call to svn_fs_editor_commit().  */
    eb->completed = TRUE;

    if (eb->root != NULL)
    {
        svn_fs_close_root(eb->root);
        eb->root = NULL;
    }

    /* ### should we examine the error and attempt svn_fs_purge_txn() ?  */
    err = svn_fs_abort_txn(eb->txn, scratch_pool);

    /* For safety, clear the now-useless txn.  */
    eb->txn = NULL;

    return svn_error_trace(err);
}
Ejemplo n.º 6
0
/* The caller wants to modify REVISION of FSPATH. Is that allowed?  */
static svn_error_t *
can_modify(svn_fs_root_t *txn_root,
           const char *fspath,
           svn_revnum_t revision,
           apr_pool_t *scratch_pool)
{
    svn_revnum_t created_rev;

    /* Out-of-dateness check:  compare the created-rev of the node
       in the txn against the created-rev of FSPATH.  */
    SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, fspath,
                                    scratch_pool));

    /* Uncommitted nodes (eg. a descendent of a copy/move/rotate destination)
       have no (committed) revision number. Let the caller go ahead and
       modify these nodes.

       Note: strictly speaking, they might be performing an "illegal" edit
       in certain cases, but let's just assume they're Good Little Boys.

       If CREATED_REV is invalid, that means it's already mutable in the
       txn, which means it has already passed this out-of-dateness check.
       (Usually, this happens when looking at a parent directory of an
       already-modified node)  */
    if (!SVN_IS_VALID_REVNUM(created_rev))
        return SVN_NO_ERROR;

    /* If the node is immutable (has a revision), then the caller should
       have supplied a valid revision number [that they expect to change].
       The checks further below will determine the out-of-dateness of the
       specified revision.  */
    /* ### ugh. descendents of copy/move/rotate destinations carry along
       ### their original immutable state and (thus) a valid CREATED_REV.
       ### but they are logically uncommitted, so the caller will pass
       ### SVN_INVALID_REVNUM. (technically, the caller could provide
       ### ORIGINAL_REV, but that is semantically incorrect for the Ev2
       ### API).
       ###
       ### for now, we will assume the caller knows what they are doing
       ### and an invalid revision implies such a descendent. in the
       ### future, we could examine the ancestor chain looking for a
       ### copy/move/rotate-here node and allow the modification (and the
       ### converse: if no such ancestor, the caller must specify the
       ### correct/intended revision to modify).
    */
#if 1
    if (!SVN_IS_VALID_REVNUM(revision))
        return SVN_NO_ERROR;
#else
    if (!SVN_IS_VALID_REVNUM(revision))
        /* ### use a custom error code?  */
        return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
                                 _("Revision for modifying '%s' is required"),
                                 fspath);
#endif

    if (revision < created_rev)
    {
        /* We asked to change a node that is *older* than what we found
           in the transaction. The client is out of date.  */
        return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
                                 _("'%s' is out of date; try updating"),
                                 fspath);
    }

    if (revision > created_rev)
    {
        /* We asked to change a node that is *newer* than what we found
           in the transaction. Given that the transaction was based off
           of 'youngest', then either:
           - the caller asked to modify a future node
           - the caller has committed more revisions since this txn
           was constructed, and is asking to modify a node in one
           of those new revisions.
           In either case, the node may not have changed in those new
           revisions; use the node's ID to determine this case.  */
        const svn_fs_id_t *txn_noderev_id;
        svn_fs_root_t *rev_root;
        const svn_fs_id_t *new_noderev_id;

        /* The ID of the node that we would be modifying in the txn  */
        SVN_ERR(svn_fs_node_id(&txn_noderev_id, txn_root, fspath,
                               scratch_pool));

        /* Get the ID from the future/new revision.  */
        SVN_ERR(svn_fs_revision_root(&rev_root, svn_fs_root_fs(txn_root),
                                     revision, scratch_pool));
        SVN_ERR(svn_fs_node_id(&new_noderev_id, rev_root, fspath,
                               scratch_pool));
        svn_fs_close_root(rev_root);

        /* Has the target node changed in the future?  */
        if (svn_fs_compare_ids(txn_noderev_id, new_noderev_id) != 0)
        {
            /* Restarting the commit will base the txn on the future/new
               revision, allowing the modification at REVISION.  */
            /* ### use a custom error code  */
            return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
                                     _("'%s' has been modified since the "
                                       "commit began (restart the commit)"),
                                     fspath);
        }
    }

    return SVN_NO_ERROR;
}
Ejemplo n.º 7
0
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));
}