/* Create a repository with a filesystem based on OPTS in a subdir NAME, * commit the standard Greek tree as revision 1, and set *REPOS_URL to * the URL we will use to access it. * * ### This always returns a file: URL. We should upgrade this to use the * test suite's specified URL scheme instead. */ static svn_error_t * create_greek_repos(const char **repos_url, const char *name, const svn_test_opts_t *opts, apr_pool_t *pool) { svn_repos_t *repos; svn_revnum_t committed_rev; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; /* Create a filesytem and repository. */ SVN_ERR(svn_test__create_repos(&repos, name, opts, pool)); /* Prepare and commit a txn containing the Greek tree. */ SVN_ERR(svn_fs_begin_txn2(&txn, svn_repos_fs(repos), 0 /* rev */, 0 /* flags */, pool)); SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); SVN_ERR(svn_repos_fs_commit_txn(NULL, repos, &committed_rev, txn, pool)); SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(committed_rev)); SVN_ERR(svn_uri_get_file_url_from_dirent(repos_url, name, pool)); return SVN_NO_ERROR; }
/* ** Append the specified lock(s) to the set of locks on this resource. ** ** If "make_indirect" is true (non-zero), then the specified lock(s) ** should be converted to an indirect lock (if it is a direct lock) ** before appending. Note that the conversion to an indirect lock does ** not alter the passed-in lock -- the change is internal the ** append_locks function. ** ** Multiple locks are specified using the lock->next links. */ static dav_error * append_locks(dav_lockdb *lockdb, const dav_resource *resource, int make_indirect, const dav_lock *lock) { dav_lockdb_private *info = lockdb->info; svn_lock_t *slock; svn_error_t *serr; dav_error *derr; /* If the resource's fs path is unreadable, we don't allow a lock to be created on it. */ if (! dav_svn__allow_read_resource(resource, SVN_INVALID_REVNUM, resource->pool)) return dav_svn__new_error(resource->pool, HTTP_FORBIDDEN, DAV_ERR_LOCK_SAVE_LOCK, "Path is not accessible."); if (lock->next) return dav_svn__new_error(resource->pool, HTTP_BAD_REQUEST, DAV_ERR_LOCK_SAVE_LOCK, "Tried to attach multiple locks to a resource."); /* RFC2518bis (section 7.4) doesn't require us to support 'lock-null' resources at all. Instead, it asks that we treat 'LOCK nonexistentURL' as a PUT (followed by a LOCK) of a 0-byte file. */ if (! resource->exists) { svn_revnum_t rev, new_rev; svn_fs_txn_t *txn; svn_fs_root_t *txn_root; const char *conflict_msg; dav_svn_repos *repos = resource->info->repos; apr_hash_t *revprop_table = apr_hash_make(resource->pool); apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING, svn_string_create(repos->username, resource->pool)); if (resource->info->repos->is_svn_client) return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, DAV_ERR_LOCK_SAVE_LOCK, "Subversion clients may not lock " "nonexistent paths."); else if (! resource->info->repos->autoversioning) return dav_svn__new_error(resource->pool, HTTP_METHOD_NOT_ALLOWED, DAV_ERR_LOCK_SAVE_LOCK, "Attempted to lock non-existent path; " "turn on autoversioning first."); /* Commit a 0-byte file: */ if ((serr = svn_fs_youngest_rev(&rev, repos->fs, resource->pool))) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Could not determine youngest revision", resource->pool); if ((serr = svn_repos_fs_begin_txn_for_commit2(&txn, repos->repos, rev, revprop_table, resource->pool))) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Could not begin a transaction", resource->pool); if ((serr = svn_fs_txn_root(&txn_root, txn, resource->pool))) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Could not begin a transaction", resource->pool); if ((serr = svn_fs_make_file(txn_root, resource->info->repos_path, resource->pool))) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Could not create empty file.", resource->pool); if ((serr = dav_svn__attach_auto_revprops(txn, resource->info->repos_path, resource->pool))) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Could not create empty file.", resource->pool); serr = svn_repos_fs_commit_txn(&conflict_msg, repos->repos, &new_rev, txn, resource->pool); if (SVN_IS_VALID_REVNUM(new_rev)) { /* ### Log an error in post commit FS processing? */ svn_error_clear(serr); } else { svn_error_clear(svn_fs_abort_txn(txn, resource->pool)); if (serr) return dav_svn__convert_err(serr, HTTP_CONFLICT, apr_psprintf(resource->pool, "Conflict when " "committing '%s'.", conflict_msg), resource->pool); else return dav_svn__new_error(resource->pool, HTTP_INTERNAL_SERVER_ERROR, 0, "Commit failed but there was no error " "provided."); } } /* Convert the dav_lock into an svn_lock_t. */ derr = dav_lock_to_svn_lock(&slock, lock, resource->info->repos_path, info, resource->info->repos->is_svn_client, resource->pool); if (derr) return derr; /* Now use the svn_lock_t to actually perform the lock. */ serr = svn_repos_fs_lock(&slock, resource->info->repos->repos, slock->path, slock->token, slock->comment, slock->is_dav_comment, slock->expiration_date, info->working_revnum, info->lock_steal, resource->pool); if (serr && serr->apr_err == SVN_ERR_FS_NO_USER) { svn_error_clear(serr); return dav_svn__new_error(resource->pool, HTTP_UNAUTHORIZED, DAV_ERR_LOCK_SAVE_LOCK, "Anonymous lock creation is not allowed."); } else if (serr) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, "Failed to create new lock.", resource->pool); /* A standard webdav LOCK response doesn't include any information about the creation date. We send it in a custom header, so that svn clients can fill in svn_lock_t->creation_date. A generic DAV client should just ignore the header. */ apr_table_setn(info->r->headers_out, SVN_DAV_CREATIONDATE_HEADER, svn_time_to_cstring(slock->creation_date, resource->pool)); /* A standard webdav LOCK response doesn't include any information about the owner of the lock. ('DAV:owner' has nothing to do with authorization, it's just a comment that we map to svn_lock_t->comment.) We send the owner in a custom header, so that svn clients can fill in svn_lock_t->owner. A generic DAV client should just ignore the header. */ apr_table_setn(info->r->headers_out, SVN_DAV_LOCK_OWNER_HEADER, slock->owner); /* Log the locking as a 'high-level' action. */ dav_svn__operational_log(resource->info, svn_log__lock_one_path(slock->path, info->lock_steal, resource->info->r->pool)); return 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)) { 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); err = SVN_NO_ERROR; } } 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))); } /* Pass new revision information to the caller's callback. */ { svn_string_t *date, *author; svn_commit_info_t *commit_info; /* Even if there was a post-commit hook failure, it's more serious if one of the calls here fails, so we explicitly check for errors here, while saving the possible post-commit error for later. */ err = svn_fs_revision_prop(&date, svn_repos_fs(eb->repos), new_revision, SVN_PROP_REVISION_DATE, pool); if (! err) { err = svn_fs_revision_prop(&author, svn_repos_fs(eb->repos), new_revision, SVN_PROP_REVISION_AUTHOR, pool); } if ((! err) && eb->commit_callback) { commit_info = svn_create_commit_info(pool); /* fill up the svn_commit_info structure */ commit_info->revision = new_revision; commit_info->date = date ? date->data : NULL; commit_info->author = author ? author->data : NULL; commit_info->post_commit_err = post_commit_err; err = (*eb->commit_callback)(commit_info, eb->commit_callback_baton, pool); } } return svn_error_trace(err); }
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)); }