/* 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; }
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)); }
/* 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; }
/* 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; }
/* 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); }
/* 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; }
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)); }