1251881Speter/* commit.c --- editor for committing changes to a filesystem. 2251881Speter * 3251881Speter * ==================================================================== 4251881Speter * Licensed to the Apache Software Foundation (ASF) under one 5251881Speter * or more contributor license agreements. See the NOTICE file 6251881Speter * distributed with this work for additional information 7251881Speter * regarding copyright ownership. The ASF licenses this file 8251881Speter * to you under the Apache License, Version 2.0 (the 9251881Speter * "License"); you may not use this file except in compliance 10251881Speter * with the License. You may obtain a copy of the License at 11251881Speter * 12251881Speter * http://www.apache.org/licenses/LICENSE-2.0 13251881Speter * 14251881Speter * Unless required by applicable law or agreed to in writing, 15251881Speter * software distributed under the License is distributed on an 16251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17251881Speter * KIND, either express or implied. See the License for the 18251881Speter * specific language governing permissions and limitations 19251881Speter * under the License. 20251881Speter * ==================================================================== 21251881Speter */ 22251881Speter 23251881Speter 24251881Speter#include <string.h> 25251881Speter 26251881Speter#include <apr_pools.h> 27251881Speter#include <apr_file_io.h> 28251881Speter 29251881Speter#include "svn_hash.h" 30251881Speter#include "svn_compat.h" 31251881Speter#include "svn_pools.h" 32251881Speter#include "svn_error.h" 33251881Speter#include "svn_dirent_uri.h" 34251881Speter#include "svn_path.h" 35251881Speter#include "svn_delta.h" 36251881Speter#include "svn_fs.h" 37251881Speter#include "svn_repos.h" 38251881Speter#include "svn_checksum.h" 39251881Speter#include "svn_ctype.h" 40251881Speter#include "svn_props.h" 41251881Speter#include "svn_mergeinfo.h" 42251881Speter#include "svn_private_config.h" 43251881Speter 44251881Speter#include "repos.h" 45251881Speter 46251881Speter#include "private/svn_fspath.h" 47251881Speter#include "private/svn_fs_private.h" 48251881Speter#include "private/svn_repos_private.h" 49251881Speter#include "private/svn_editor.h" 50251881Speter 51251881Speter 52251881Speter 53251881Speter/*** Editor batons. ***/ 54251881Speter 55251881Speterstruct edit_baton 56251881Speter{ 57251881Speter apr_pool_t *pool; 58251881Speter 59251881Speter /** Supplied when the editor is created: **/ 60251881Speter 61251881Speter /* Revision properties to set for this commit. */ 62251881Speter apr_hash_t *revprop_table; 63251881Speter 64251881Speter /* Callback to run when the commit is done. */ 65251881Speter svn_commit_callback2_t commit_callback; 66251881Speter void *commit_callback_baton; 67251881Speter 68251881Speter /* Callback to check authorizations on paths. */ 69251881Speter svn_repos_authz_callback_t authz_callback; 70251881Speter void *authz_baton; 71251881Speter 72251881Speter /* The already-open svn repository to commit to. */ 73251881Speter svn_repos_t *repos; 74251881Speter 75251881Speter /* URL to the root of the open repository. */ 76251881Speter const char *repos_url; 77251881Speter 78251881Speter /* The name of the repository (here for convenience). */ 79251881Speter const char *repos_name; 80251881Speter 81251881Speter /* The filesystem associated with the REPOS above (here for 82251881Speter convenience). */ 83251881Speter svn_fs_t *fs; 84251881Speter 85251881Speter /* Location in fs where the edit will begin. */ 86251881Speter const char *base_path; 87251881Speter 88251881Speter /* Does this set of interfaces 'own' the commit transaction? */ 89251881Speter svn_boolean_t txn_owner; 90251881Speter 91251881Speter /* svn transaction associated with this edit (created in 92251881Speter open_root, or supplied by the public API caller). */ 93251881Speter svn_fs_txn_t *txn; 94251881Speter 95251881Speter /** Filled in during open_root: **/ 96251881Speter 97251881Speter /* The name of the transaction. */ 98251881Speter const char *txn_name; 99251881Speter 100251881Speter /* The object representing the root directory of the svn txn. */ 101251881Speter svn_fs_root_t *txn_root; 102251881Speter 103251881Speter /* Avoid aborting an fs transaction more than once */ 104251881Speter svn_boolean_t txn_aborted; 105251881Speter 106251881Speter /** Filled in when the edit is closed: **/ 107251881Speter 108251881Speter /* The new revision created by this commit. */ 109251881Speter svn_revnum_t *new_rev; 110251881Speter 111251881Speter /* The date (according to the repository) of this commit. */ 112251881Speter const char **committed_date; 113251881Speter 114251881Speter /* The author (also according to the repository) of this commit. */ 115251881Speter const char **committed_author; 116251881Speter}; 117251881Speter 118251881Speter 119251881Speterstruct dir_baton 120251881Speter{ 121251881Speter struct edit_baton *edit_baton; 122251881Speter struct dir_baton *parent; 123251881Speter const char *path; /* the -absolute- path to this dir in the fs */ 124251881Speter svn_revnum_t base_rev; /* the revision I'm based on */ 125251881Speter svn_boolean_t was_copied; /* was this directory added with history? */ 126251881Speter apr_pool_t *pool; /* my personal pool, in which I am allocated. */ 127251881Speter}; 128251881Speter 129251881Speter 130251881Speterstruct file_baton 131251881Speter{ 132251881Speter struct edit_baton *edit_baton; 133251881Speter const char *path; /* the -absolute- path to this file in the fs */ 134251881Speter}; 135251881Speter 136251881Speter 137251881Speterstruct ev2_baton 138251881Speter{ 139251881Speter /* The repository we are editing. */ 140251881Speter svn_repos_t *repos; 141251881Speter 142251881Speter /* The authz baton for checks; NULL to skip authz. */ 143251881Speter svn_authz_t *authz; 144251881Speter 145251881Speter /* The repository name and user for performing authz checks. */ 146251881Speter const char *authz_repos_name; 147251881Speter const char *authz_user; 148251881Speter 149251881Speter /* Callback to provide info about the committed revision. */ 150251881Speter svn_commit_callback2_t commit_cb; 151251881Speter void *commit_baton; 152251881Speter 153251881Speter /* The FS txn editor */ 154251881Speter svn_editor_t *inner; 155251881Speter 156251881Speter /* The name of the open transaction (so we know what to commit) */ 157251881Speter const char *txn_name; 158251881Speter}; 159251881Speter 160251881Speter 161251881Speter/* Create and return a generic out-of-dateness error. */ 162251881Speterstatic svn_error_t * 163251881Speterout_of_date(const char *path, svn_node_kind_t kind) 164251881Speter{ 165251881Speter return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, 166251881Speter (kind == svn_node_dir 167251881Speter ? _("Directory '%s' is out of date") 168251881Speter : kind == svn_node_file 169251881Speter ? _("File '%s' is out of date") 170251881Speter : _("'%s' is out of date")), 171251881Speter path); 172251881Speter} 173251881Speter 174251881Speter 175251881Speterstatic svn_error_t * 176251881Speterinvoke_commit_cb(svn_commit_callback2_t commit_cb, 177251881Speter void *commit_baton, 178251881Speter svn_fs_t *fs, 179251881Speter svn_revnum_t revision, 180251881Speter const char *post_commit_errstr, 181251881Speter apr_pool_t *scratch_pool) 182251881Speter{ 183251881Speter /* FS interface returns non-const values. */ 184251881Speter /* const */ svn_string_t *date; 185251881Speter /* const */ svn_string_t *author; 186251881Speter svn_commit_info_t *commit_info; 187251881Speter 188251881Speter if (commit_cb == NULL) 189251881Speter return SVN_NO_ERROR; 190251881Speter 191251881Speter SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE, 192251881Speter scratch_pool)); 193251881Speter SVN_ERR(svn_fs_revision_prop(&author, fs, revision, 194251881Speter SVN_PROP_REVISION_AUTHOR, 195251881Speter scratch_pool)); 196251881Speter 197251881Speter commit_info = svn_create_commit_info(scratch_pool); 198251881Speter 199251881Speter /* fill up the svn_commit_info structure */ 200251881Speter commit_info->revision = revision; 201251881Speter commit_info->date = date ? date->data : NULL; 202251881Speter commit_info->author = author ? author->data : NULL; 203251881Speter commit_info->post_commit_err = post_commit_errstr; 204251881Speter 205251881Speter return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool)); 206251881Speter} 207251881Speter 208251881Speter 209251881Speter 210251881Speter/* If EDITOR_BATON contains a valid authz callback, verify that the 211251881Speter REQUIRED access to PATH in ROOT is authorized. Return an error 212251881Speter appropriate for throwing out of the commit editor with SVN_ERR. If 213251881Speter no authz callback is present in EDITOR_BATON, then authorize all 214251881Speter paths. Use POOL for temporary allocation only. */ 215251881Speterstatic svn_error_t * 216251881Spetercheck_authz(struct edit_baton *editor_baton, const char *path, 217251881Speter svn_fs_root_t *root, svn_repos_authz_access_t required, 218251881Speter apr_pool_t *pool) 219251881Speter{ 220251881Speter if (editor_baton->authz_callback) 221251881Speter { 222251881Speter svn_boolean_t allowed; 223251881Speter 224251881Speter SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path, 225251881Speter editor_baton->authz_baton, pool)); 226251881Speter if (!allowed) 227251881Speter return svn_error_create(required & svn_authz_write ? 228251881Speter SVN_ERR_AUTHZ_UNWRITABLE : 229251881Speter SVN_ERR_AUTHZ_UNREADABLE, 230251881Speter NULL, "Access denied"); 231251881Speter } 232251881Speter 233251881Speter return SVN_NO_ERROR; 234251881Speter} 235251881Speter 236251881Speter 237251881Speter/* Return a directory baton allocated in POOL which represents 238251881Speter FULL_PATH, which is the immediate directory child of the directory 239251881Speter represented by PARENT_BATON. EDIT_BATON is the commit editor 240251881Speter baton. WAS_COPIED reveals whether or not this directory is the 241251881Speter result of a copy operation. BASE_REVISION is the base revision of 242251881Speter the directory. */ 243251881Speterstatic struct dir_baton * 244251881Spetermake_dir_baton(struct edit_baton *edit_baton, 245251881Speter struct dir_baton *parent_baton, 246251881Speter const char *full_path, 247251881Speter svn_boolean_t was_copied, 248251881Speter svn_revnum_t base_revision, 249251881Speter apr_pool_t *pool) 250251881Speter{ 251251881Speter struct dir_baton *db; 252251881Speter db = apr_pcalloc(pool, sizeof(*db)); 253251881Speter db->edit_baton = edit_baton; 254251881Speter db->parent = parent_baton; 255251881Speter db->pool = pool; 256251881Speter db->path = full_path; 257251881Speter db->was_copied = was_copied; 258251881Speter db->base_rev = base_revision; 259251881Speter return db; 260251881Speter} 261251881Speter 262251881Speter/* This function is the shared guts of add_file() and add_directory(), 263251881Speter which see for the meanings of the parameters. The only extra 264251881Speter parameter here is IS_DIR, which is TRUE when adding a directory, 265251881Speter and FALSE when adding a file. */ 266251881Speterstatic svn_error_t * 267251881Speteradd_file_or_directory(const char *path, 268251881Speter void *parent_baton, 269251881Speter const char *copy_path, 270251881Speter svn_revnum_t copy_revision, 271251881Speter svn_boolean_t is_dir, 272251881Speter apr_pool_t *pool, 273251881Speter void **return_baton) 274251881Speter{ 275251881Speter struct dir_baton *pb = parent_baton; 276251881Speter struct edit_baton *eb = pb->edit_baton; 277251881Speter apr_pool_t *subpool = svn_pool_create(pool); 278251881Speter svn_boolean_t was_copied = FALSE; 279251881Speter const char *full_path; 280251881Speter 281251881Speter /* Reject paths which contain control characters (related to issue #4340). */ 282251881Speter SVN_ERR(svn_path_check_valid(path, pool)); 283251881Speter 284251881Speter full_path = svn_fspath__join(eb->base_path, 285251881Speter svn_relpath_canonicalize(path, pool), pool); 286251881Speter 287251881Speter /* Sanity check. */ 288251881Speter if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision))) 289251881Speter return svn_error_createf 290251881Speter (SVN_ERR_FS_GENERAL, NULL, 291251881Speter _("Got source path but no source revision for '%s'"), full_path); 292251881Speter 293251881Speter if (copy_path) 294251881Speter { 295251881Speter const char *fs_path; 296251881Speter svn_fs_root_t *copy_root; 297251881Speter svn_node_kind_t kind; 298251881Speter size_t repos_url_len; 299251881Speter svn_repos_authz_access_t required; 300251881Speter 301251881Speter /* Copy requires recursive write access to the destination path 302251881Speter and write access to the parent path. */ 303251881Speter required = svn_authz_write | (is_dir ? svn_authz_recursive : 0); 304251881Speter SVN_ERR(check_authz(eb, full_path, eb->txn_root, 305251881Speter required, subpool)); 306251881Speter SVN_ERR(check_authz(eb, pb->path, eb->txn_root, 307251881Speter svn_authz_write, subpool)); 308251881Speter 309251881Speter /* Check PATH in our transaction. Make sure it does not exist 310251881Speter unless its parent directory was copied (in which case, the 311251881Speter thing might have been copied in as well), else return an 312251881Speter out-of-dateness error. */ 313251881Speter SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool)); 314251881Speter if ((kind != svn_node_none) && (! pb->was_copied)) 315251881Speter return svn_error_trace(out_of_date(full_path, kind)); 316251881Speter 317251881Speter /* For now, require that the url come from the same repository 318251881Speter that this commit is operating on. */ 319251881Speter copy_path = svn_path_uri_decode(copy_path, subpool); 320251881Speter repos_url_len = strlen(eb->repos_url); 321251881Speter if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0) 322251881Speter return svn_error_createf 323251881Speter (SVN_ERR_FS_GENERAL, NULL, 324251881Speter _("Source url '%s' is from different repository"), copy_path); 325251881Speter 326251881Speter fs_path = apr_pstrdup(subpool, copy_path + repos_url_len); 327251881Speter 328251881Speter /* Now use the "fs_path" as an absolute path within the 329251881Speter repository to make the copy from. */ 330251881Speter SVN_ERR(svn_fs_revision_root(©_root, eb->fs, 331251881Speter copy_revision, subpool)); 332251881Speter 333251881Speter /* Copy also requires (recursive) read access to the source */ 334251881Speter required = svn_authz_read | (is_dir ? svn_authz_recursive : 0); 335251881Speter SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool)); 336251881Speter 337251881Speter SVN_ERR(svn_fs_copy(copy_root, fs_path, 338251881Speter eb->txn_root, full_path, subpool)); 339251881Speter was_copied = TRUE; 340251881Speter } 341251881Speter else 342251881Speter { 343251881Speter /* No ancestry given, just make a new directory or empty file. 344251881Speter Note that we don't perform an existence check here like the 345251881Speter copy-from case does -- that's because svn_fs_make_*() 346251881Speter already errors out if the file already exists. Verify write 347251881Speter access to the full path and to the parent. */ 348251881Speter SVN_ERR(check_authz(eb, full_path, eb->txn_root, 349251881Speter svn_authz_write, subpool)); 350251881Speter SVN_ERR(check_authz(eb, pb->path, eb->txn_root, 351251881Speter svn_authz_write, subpool)); 352251881Speter if (is_dir) 353251881Speter SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool)); 354251881Speter else 355251881Speter SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool)); 356251881Speter } 357251881Speter 358251881Speter /* Cleanup our temporary subpool. */ 359251881Speter svn_pool_destroy(subpool); 360251881Speter 361251881Speter /* Build a new child baton. */ 362251881Speter if (is_dir) 363251881Speter { 364251881Speter *return_baton = make_dir_baton(eb, pb, full_path, was_copied, 365251881Speter SVN_INVALID_REVNUM, pool); 366251881Speter } 367251881Speter else 368251881Speter { 369251881Speter struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb)); 370251881Speter new_fb->edit_baton = eb; 371251881Speter new_fb->path = full_path; 372251881Speter *return_baton = new_fb; 373251881Speter } 374251881Speter 375251881Speter return SVN_NO_ERROR; 376251881Speter} 377251881Speter 378251881Speter 379251881Speter 380251881Speter/*** Editor functions ***/ 381251881Speter 382251881Speterstatic svn_error_t * 383251881Speteropen_root(void *edit_baton, 384251881Speter svn_revnum_t base_revision, 385251881Speter apr_pool_t *pool, 386251881Speter void **root_baton) 387251881Speter{ 388251881Speter struct dir_baton *dirb; 389251881Speter struct edit_baton *eb = edit_baton; 390251881Speter svn_revnum_t youngest; 391251881Speter 392251881Speter /* Ignore BASE_REVISION. We always build our transaction against 393251881Speter HEAD. However, we will keep it in our dir baton for out of 394251881Speter dateness checks. */ 395251881Speter SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool)); 396251881Speter 397251881Speter /* Unless we've been instructed to use a specific transaction, we'll 398251881Speter make our own. */ 399251881Speter if (eb->txn_owner) 400251881Speter { 401251881Speter SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn), 402251881Speter eb->repos, 403251881Speter youngest, 404251881Speter eb->revprop_table, 405251881Speter eb->pool)); 406251881Speter } 407251881Speter else /* Even if we aren't the owner of the transaction, we might 408251881Speter have been instructed to set some properties. */ 409251881Speter { 410251881Speter apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table, 411251881Speter pool); 412251881Speter SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool)); 413251881Speter } 414251881Speter SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool)); 415251881Speter SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool)); 416251881Speter 417251881Speter /* Create a root dir baton. The `base_path' field is an -absolute- 418251881Speter path in the filesystem, upon which all further editor paths are 419251881Speter based. */ 420251881Speter dirb = apr_pcalloc(pool, sizeof(*dirb)); 421251881Speter dirb->edit_baton = edit_baton; 422251881Speter dirb->parent = NULL; 423251881Speter dirb->pool = pool; 424251881Speter dirb->was_copied = FALSE; 425251881Speter dirb->path = apr_pstrdup(pool, eb->base_path); 426251881Speter dirb->base_rev = base_revision; 427251881Speter 428251881Speter *root_baton = dirb; 429251881Speter return SVN_NO_ERROR; 430251881Speter} 431251881Speter 432251881Speter 433251881Speter 434251881Speterstatic svn_error_t * 435251881Speterdelete_entry(const char *path, 436251881Speter svn_revnum_t revision, 437251881Speter void *parent_baton, 438251881Speter apr_pool_t *pool) 439251881Speter{ 440251881Speter struct dir_baton *parent = parent_baton; 441251881Speter struct edit_baton *eb = parent->edit_baton; 442251881Speter svn_node_kind_t kind; 443251881Speter svn_revnum_t cr_rev; 444251881Speter svn_repos_authz_access_t required = svn_authz_write; 445251881Speter const char *full_path; 446251881Speter 447251881Speter full_path = svn_fspath__join(eb->base_path, 448251881Speter svn_relpath_canonicalize(path, pool), pool); 449251881Speter 450251881Speter /* Check PATH in our transaction. */ 451251881Speter SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool)); 452251881Speter 453251881Speter /* Deletion requires a recursive write access, as well as write 454251881Speter access to the parent directory. */ 455251881Speter if (kind == svn_node_dir) 456251881Speter required |= svn_authz_recursive; 457251881Speter SVN_ERR(check_authz(eb, full_path, eb->txn_root, 458251881Speter required, pool)); 459251881Speter SVN_ERR(check_authz(eb, parent->path, eb->txn_root, 460251881Speter svn_authz_write, pool)); 461251881Speter 462251881Speter /* If PATH doesn't exist in the txn, the working copy is out of date. */ 463251881Speter if (kind == svn_node_none) 464251881Speter return svn_error_trace(out_of_date(full_path, kind)); 465251881Speter 466251881Speter /* Now, make sure we're deleting the node we *think* we're 467251881Speter deleting, else return an out-of-dateness error. */ 468251881Speter SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool)); 469251881Speter if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev)) 470251881Speter return svn_error_trace(out_of_date(full_path, kind)); 471251881Speter 472251881Speter /* This routine is a mindless wrapper. We call svn_fs_delete() 473251881Speter because that will delete files and recursively delete 474251881Speter directories. */ 475251881Speter return svn_fs_delete(eb->txn_root, full_path, pool); 476251881Speter} 477251881Speter 478251881Speter 479251881Speterstatic svn_error_t * 480251881Speteradd_directory(const char *path, 481251881Speter void *parent_baton, 482251881Speter const char *copy_path, 483251881Speter svn_revnum_t copy_revision, 484251881Speter apr_pool_t *pool, 485251881Speter void **child_baton) 486251881Speter{ 487251881Speter return add_file_or_directory(path, parent_baton, copy_path, copy_revision, 488251881Speter TRUE /* is_dir */, pool, child_baton); 489251881Speter} 490251881Speter 491251881Speter 492251881Speterstatic svn_error_t * 493251881Speteropen_directory(const char *path, 494251881Speter void *parent_baton, 495251881Speter svn_revnum_t base_revision, 496251881Speter apr_pool_t *pool, 497251881Speter void **child_baton) 498251881Speter{ 499251881Speter struct dir_baton *pb = parent_baton; 500251881Speter struct edit_baton *eb = pb->edit_baton; 501251881Speter svn_node_kind_t kind; 502251881Speter const char *full_path; 503251881Speter 504251881Speter full_path = svn_fspath__join(eb->base_path, 505251881Speter svn_relpath_canonicalize(path, pool), pool); 506251881Speter 507251881Speter /* Check PATH in our transaction. If it does not exist, 508251881Speter return a 'Path not present' error. */ 509251881Speter SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool)); 510251881Speter if (kind == svn_node_none) 511251881Speter return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, 512251881Speter _("Path '%s' not present"), 513251881Speter path); 514251881Speter 515251881Speter /* Build a new dir baton for this directory. */ 516251881Speter *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied, 517251881Speter base_revision, pool); 518251881Speter return SVN_NO_ERROR; 519251881Speter} 520251881Speter 521251881Speter 522251881Speterstatic svn_error_t * 523251881Speterapply_textdelta(void *file_baton, 524251881Speter const char *base_checksum, 525251881Speter apr_pool_t *pool, 526251881Speter svn_txdelta_window_handler_t *handler, 527251881Speter void **handler_baton) 528251881Speter{ 529251881Speter struct file_baton *fb = file_baton; 530251881Speter 531251881Speter /* Check for write authorization. */ 532251881Speter SVN_ERR(check_authz(fb->edit_baton, fb->path, 533251881Speter fb->edit_baton->txn_root, 534251881Speter svn_authz_write, pool)); 535251881Speter 536251881Speter return svn_fs_apply_textdelta(handler, handler_baton, 537251881Speter fb->edit_baton->txn_root, 538251881Speter fb->path, 539251881Speter base_checksum, 540251881Speter NULL, 541251881Speter pool); 542251881Speter} 543251881Speter 544251881Speter 545251881Speterstatic svn_error_t * 546251881Speteradd_file(const char *path, 547251881Speter void *parent_baton, 548251881Speter const char *copy_path, 549251881Speter svn_revnum_t copy_revision, 550251881Speter apr_pool_t *pool, 551251881Speter void **file_baton) 552251881Speter{ 553251881Speter return add_file_or_directory(path, parent_baton, copy_path, copy_revision, 554251881Speter FALSE /* is_dir */, pool, file_baton); 555251881Speter} 556251881Speter 557251881Speter 558251881Speterstatic svn_error_t * 559251881Speteropen_file(const char *path, 560251881Speter void *parent_baton, 561251881Speter svn_revnum_t base_revision, 562251881Speter apr_pool_t *pool, 563251881Speter void **file_baton) 564251881Speter{ 565251881Speter struct file_baton *new_fb; 566251881Speter struct dir_baton *pb = parent_baton; 567251881Speter struct edit_baton *eb = pb->edit_baton; 568251881Speter svn_revnum_t cr_rev; 569251881Speter apr_pool_t *subpool = svn_pool_create(pool); 570251881Speter const char *full_path; 571251881Speter 572251881Speter full_path = svn_fspath__join(eb->base_path, 573251881Speter svn_relpath_canonicalize(path, pool), pool); 574251881Speter 575251881Speter /* Check for read authorization. */ 576251881Speter SVN_ERR(check_authz(eb, full_path, eb->txn_root, 577251881Speter svn_authz_read, subpool)); 578251881Speter 579251881Speter /* Get this node's creation revision (doubles as an existence check). */ 580251881Speter SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, 581251881Speter subpool)); 582251881Speter 583251881Speter /* If the node our caller has is an older revision number than the 584251881Speter one in our transaction, return an out-of-dateness error. */ 585251881Speter if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev)) 586251881Speter return svn_error_trace(out_of_date(full_path, svn_node_file)); 587251881Speter 588251881Speter /* Build a new file baton */ 589251881Speter new_fb = apr_pcalloc(pool, sizeof(*new_fb)); 590251881Speter new_fb->edit_baton = eb; 591251881Speter new_fb->path = full_path; 592251881Speter 593251881Speter *file_baton = new_fb; 594251881Speter 595251881Speter /* Destory the work subpool. */ 596251881Speter svn_pool_destroy(subpool); 597251881Speter 598251881Speter return SVN_NO_ERROR; 599251881Speter} 600251881Speter 601251881Speter 602251881Speterstatic svn_error_t * 603251881Speterchange_file_prop(void *file_baton, 604251881Speter const char *name, 605251881Speter const svn_string_t *value, 606251881Speter apr_pool_t *pool) 607251881Speter{ 608251881Speter struct file_baton *fb = file_baton; 609251881Speter struct edit_baton *eb = fb->edit_baton; 610251881Speter 611251881Speter /* Check for write authorization. */ 612251881Speter SVN_ERR(check_authz(eb, fb->path, eb->txn_root, 613251881Speter svn_authz_write, pool)); 614251881Speter 615251881Speter return svn_repos_fs_change_node_prop(eb->txn_root, fb->path, 616251881Speter name, value, pool); 617251881Speter} 618251881Speter 619251881Speter 620251881Speterstatic svn_error_t * 621251881Speterclose_file(void *file_baton, 622251881Speter const char *text_digest, 623251881Speter apr_pool_t *pool) 624251881Speter{ 625251881Speter struct file_baton *fb = file_baton; 626251881Speter 627251881Speter if (text_digest) 628251881Speter { 629251881Speter svn_checksum_t *checksum; 630251881Speter svn_checksum_t *text_checksum; 631251881Speter 632251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 633251881Speter fb->edit_baton->txn_root, fb->path, 634251881Speter TRUE, pool)); 635251881Speter SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, 636251881Speter text_digest, pool)); 637251881Speter 638251881Speter if (!svn_checksum_match(text_checksum, checksum)) 639251881Speter return svn_checksum_mismatch_err(text_checksum, checksum, pool, 640251881Speter _("Checksum mismatch for resulting fulltext\n(%s)"), 641251881Speter fb->path); 642251881Speter } 643251881Speter 644251881Speter return SVN_NO_ERROR; 645251881Speter} 646251881Speter 647251881Speter 648251881Speterstatic svn_error_t * 649251881Speterchange_dir_prop(void *dir_baton, 650251881Speter const char *name, 651251881Speter const svn_string_t *value, 652251881Speter apr_pool_t *pool) 653251881Speter{ 654251881Speter struct dir_baton *db = dir_baton; 655251881Speter struct edit_baton *eb = db->edit_baton; 656251881Speter 657251881Speter /* Check for write authorization. */ 658251881Speter SVN_ERR(check_authz(eb, db->path, eb->txn_root, 659251881Speter svn_authz_write, pool)); 660251881Speter 661251881Speter if (SVN_IS_VALID_REVNUM(db->base_rev)) 662251881Speter { 663251881Speter /* Subversion rule: propchanges can only happen on a directory 664251881Speter which is up-to-date. */ 665251881Speter svn_revnum_t created_rev; 666251881Speter SVN_ERR(svn_fs_node_created_rev(&created_rev, 667251881Speter eb->txn_root, db->path, pool)); 668251881Speter 669251881Speter if (db->base_rev < created_rev) 670251881Speter return svn_error_trace(out_of_date(db->path, svn_node_dir)); 671251881Speter } 672251881Speter 673251881Speter return svn_repos_fs_change_node_prop(eb->txn_root, db->path, 674251881Speter name, value, pool); 675251881Speter} 676251881Speter 677251881Speterconst char * 678251881Spetersvn_repos__post_commit_error_str(svn_error_t *err, 679251881Speter apr_pool_t *pool) 680251881Speter{ 681251881Speter svn_error_t *hook_err1, *hook_err2; 682251881Speter const char *msg; 683251881Speter 684251881Speter if (! err) 685251881Speter return _("(no error)"); 686251881Speter 687251881Speter err = svn_error_purge_tracing(err); 688251881Speter 689251881Speter /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped 690251881Speter error from the post-commit script, if any, and hook_err2 should 691251881Speter be the original error, but be defensive and handle a case where 692251881Speter SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */ 693251881Speter hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED); 694251881Speter if (hook_err1 && hook_err1->child) 695251881Speter hook_err2 = hook_err1->child; 696251881Speter else 697251881Speter hook_err2 = hook_err1; 698251881Speter 699251881Speter /* This implementation counts on svn_repos_fs_commit_txn() and 700251881Speter libsvn_repos/commit.c:complete_cb() returning 701251881Speter svn_fs_commit_txn() as the parent error with a child 702251881Speter SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error. If the parent error 703251881Speter is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error 704251881Speter in svn_fs_commit_txn(). 705251881Speter 706251881Speter The post-commit hook error message is already self describing, so 707251881Speter it can be dropped into an error message without any additional 708251881Speter text. */ 709251881Speter if (hook_err1) 710251881Speter { 711251881Speter if (err == hook_err1) 712251881Speter { 713251881Speter if (hook_err2->message) 714251881Speter msg = apr_pstrdup(pool, hook_err2->message); 715251881Speter else 716251881Speter msg = _("post-commit hook failed with no error message."); 717251881Speter } 718251881Speter else 719251881Speter { 720251881Speter msg = hook_err2->message 721251881Speter ? apr_pstrdup(pool, hook_err2->message) 722251881Speter : _("post-commit hook failed with no error message."); 723251881Speter msg = apr_psprintf( 724251881Speter pool, 725251881Speter _("post commit FS processing had error:\n%s\n%s"), 726251881Speter err->message ? err->message : _("(no error message)"), 727251881Speter msg); 728251881Speter } 729251881Speter } 730251881Speter else 731251881Speter { 732251881Speter msg = apr_psprintf(pool, 733251881Speter _("post commit FS processing had error:\n%s"), 734251881Speter err->message ? err->message 735251881Speter : _("(no error message)")); 736251881Speter } 737251881Speter 738251881Speter return msg; 739251881Speter} 740251881Speter 741251881Speterstatic svn_error_t * 742251881Speterclose_edit(void *edit_baton, 743251881Speter apr_pool_t *pool) 744251881Speter{ 745251881Speter struct edit_baton *eb = edit_baton; 746251881Speter svn_revnum_t new_revision = SVN_INVALID_REVNUM; 747251881Speter svn_error_t *err; 748251881Speter const char *conflict; 749251881Speter const char *post_commit_err = NULL; 750251881Speter 751251881Speter /* If no transaction has been created (ie. if open_root wasn't 752251881Speter called before close_edit), abort the operation here with an 753251881Speter error. */ 754251881Speter if (! eb->txn) 755251881Speter return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, 756251881Speter "No valid transaction supplied to close_edit"); 757251881Speter 758251881Speter /* Commit. */ 759251881Speter err = svn_repos_fs_commit_txn(&conflict, eb->repos, 760251881Speter &new_revision, eb->txn, pool); 761251881Speter 762251881Speter if (SVN_IS_VALID_REVNUM(new_revision)) 763251881Speter { 764251881Speter if (err) 765251881Speter { 766251881Speter /* If the error was in post-commit, then the commit itself 767251881Speter succeeded. In which case, save the post-commit warning 768251881Speter (to be reported back to the client, who will probably 769251881Speter display it as a warning) and clear the error. */ 770251881Speter post_commit_err = svn_repos__post_commit_error_str(err, pool); 771251881Speter svn_error_clear(err); 772251881Speter } 773251881Speter } 774251881Speter else 775251881Speter { 776251881Speter /* ### todo: we should check whether it really was a conflict, 777251881Speter and return the conflict info if so? */ 778251881Speter 779251881Speter /* If the commit failed, it's *probably* due to a conflict -- 780251881Speter that is, the txn being out-of-date. The filesystem gives us 781251881Speter the ability to continue diddling the transaction and try 782251881Speter again; but let's face it: that's not how the cvs or svn works 783251881Speter from a user interface standpoint. Thus we don't make use of 784251881Speter this fs feature (for now, at least.) 785251881Speter 786251881Speter So, in a nutshell: svn commits are an all-or-nothing deal. 787251881Speter Each commit creates a new fs txn which either succeeds or is 788251881Speter aborted completely. No second chances; the user simply 789251881Speter needs to update and commit again :) */ 790251881Speter 791251881Speter eb->txn_aborted = TRUE; 792251881Speter 793251881Speter return svn_error_trace( 794251881Speter svn_error_compose_create(err, 795251881Speter svn_fs_abort_txn(eb->txn, pool))); 796251881Speter } 797251881Speter 798251881Speter /* At this point, the post-commit error has been converted to a string. 799251881Speter That information will be passed to a callback, if provided. If the 800251881Speter callback invocation fails in some way, that failure is returned here. 801251881Speter IOW, the post-commit error information is low priority compared to 802251881Speter other gunk here. */ 803251881Speter 804251881Speter /* Pass new revision information to the caller's callback. */ 805251881Speter return svn_error_trace(invoke_commit_cb(eb->commit_callback, 806251881Speter eb->commit_callback_baton, 807251881Speter eb->repos->fs, 808251881Speter new_revision, 809251881Speter post_commit_err, 810251881Speter pool)); 811251881Speter} 812251881Speter 813251881Speter 814251881Speterstatic svn_error_t * 815251881Speterabort_edit(void *edit_baton, 816251881Speter apr_pool_t *pool) 817251881Speter{ 818251881Speter struct edit_baton *eb = edit_baton; 819251881Speter if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted) 820251881Speter return SVN_NO_ERROR; 821251881Speter 822251881Speter eb->txn_aborted = TRUE; 823251881Speter 824251881Speter return svn_error_trace(svn_fs_abort_txn(eb->txn, pool)); 825251881Speter} 826251881Speter 827251881Speter 828251881Speterstatic svn_error_t * 829251881Speterfetch_props_func(apr_hash_t **props, 830251881Speter void *baton, 831251881Speter const char *path, 832251881Speter svn_revnum_t base_revision, 833251881Speter apr_pool_t *result_pool, 834251881Speter apr_pool_t *scratch_pool) 835251881Speter{ 836251881Speter struct edit_baton *eb = baton; 837251881Speter svn_fs_root_t *fs_root; 838251881Speter svn_error_t *err; 839251881Speter 840251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, 841251881Speter svn_fs_txn_base_revision(eb->txn), 842251881Speter scratch_pool)); 843251881Speter err = svn_fs_node_proplist(props, fs_root, path, result_pool); 844251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 845251881Speter { 846251881Speter svn_error_clear(err); 847251881Speter *props = apr_hash_make(result_pool); 848251881Speter return SVN_NO_ERROR; 849251881Speter } 850251881Speter else if (err) 851251881Speter return svn_error_trace(err); 852251881Speter 853251881Speter return SVN_NO_ERROR; 854251881Speter} 855251881Speter 856251881Speterstatic svn_error_t * 857251881Speterfetch_kind_func(svn_node_kind_t *kind, 858251881Speter void *baton, 859251881Speter const char *path, 860251881Speter svn_revnum_t base_revision, 861251881Speter apr_pool_t *scratch_pool) 862251881Speter{ 863251881Speter struct edit_baton *eb = baton; 864251881Speter svn_fs_root_t *fs_root; 865251881Speter 866251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 867251881Speter base_revision = svn_fs_txn_base_revision(eb->txn); 868251881Speter 869251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 870251881Speter 871251881Speter SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); 872251881Speter 873251881Speter return SVN_NO_ERROR; 874251881Speter} 875251881Speter 876251881Speterstatic svn_error_t * 877251881Speterfetch_base_func(const char **filename, 878251881Speter void *baton, 879251881Speter const char *path, 880251881Speter svn_revnum_t base_revision, 881251881Speter apr_pool_t *result_pool, 882251881Speter apr_pool_t *scratch_pool) 883251881Speter{ 884251881Speter struct edit_baton *eb = baton; 885251881Speter svn_stream_t *contents; 886251881Speter svn_stream_t *file_stream; 887251881Speter const char *tmp_filename; 888251881Speter svn_fs_root_t *fs_root; 889251881Speter svn_error_t *err; 890251881Speter 891251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 892251881Speter base_revision = svn_fs_txn_base_revision(eb->txn); 893251881Speter 894251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 895251881Speter 896251881Speter err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); 897251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 898251881Speter { 899251881Speter svn_error_clear(err); 900251881Speter *filename = NULL; 901251881Speter return SVN_NO_ERROR; 902251881Speter } 903251881Speter else if (err) 904251881Speter return svn_error_trace(err); 905251881Speter SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, 906251881Speter svn_io_file_del_on_pool_cleanup, 907251881Speter scratch_pool, scratch_pool)); 908251881Speter SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); 909251881Speter 910251881Speter *filename = apr_pstrdup(result_pool, tmp_filename); 911251881Speter 912251881Speter return SVN_NO_ERROR; 913251881Speter} 914251881Speter 915251881Speter 916251881Speter 917251881Speter/*** Public interfaces. ***/ 918251881Speter 919251881Spetersvn_error_t * 920251881Spetersvn_repos_get_commit_editor5(const svn_delta_editor_t **editor, 921251881Speter void **edit_baton, 922251881Speter svn_repos_t *repos, 923251881Speter svn_fs_txn_t *txn, 924251881Speter const char *repos_url, 925251881Speter const char *base_path, 926251881Speter apr_hash_t *revprop_table, 927251881Speter svn_commit_callback2_t commit_callback, 928251881Speter void *commit_baton, 929251881Speter svn_repos_authz_callback_t authz_callback, 930251881Speter void *authz_baton, 931251881Speter apr_pool_t *pool) 932251881Speter{ 933251881Speter svn_delta_editor_t *e; 934251881Speter apr_pool_t *subpool = svn_pool_create(pool); 935251881Speter struct edit_baton *eb; 936251881Speter svn_delta_shim_callbacks_t *shim_callbacks = 937251881Speter svn_delta_shim_callbacks_default(pool); 938251881Speter 939251881Speter /* Do a global authz access lookup. Users with no write access 940251881Speter whatsoever to the repository don't get a commit editor. */ 941251881Speter if (authz_callback) 942251881Speter { 943251881Speter svn_boolean_t allowed; 944251881Speter 945251881Speter SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL, 946251881Speter authz_baton, pool)); 947251881Speter if (!allowed) 948251881Speter return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL, 949251881Speter "Not authorized to open a commit editor."); 950251881Speter } 951251881Speter 952251881Speter /* Allocate the structures. */ 953251881Speter e = svn_delta_default_editor(pool); 954251881Speter eb = apr_pcalloc(subpool, sizeof(*eb)); 955251881Speter 956251881Speter /* Set up the editor. */ 957251881Speter e->open_root = open_root; 958251881Speter e->delete_entry = delete_entry; 959251881Speter e->add_directory = add_directory; 960251881Speter e->open_directory = open_directory; 961251881Speter e->change_dir_prop = change_dir_prop; 962251881Speter e->add_file = add_file; 963251881Speter e->open_file = open_file; 964251881Speter e->close_file = close_file; 965251881Speter e->apply_textdelta = apply_textdelta; 966251881Speter e->change_file_prop = change_file_prop; 967251881Speter e->close_edit = close_edit; 968251881Speter e->abort_edit = abort_edit; 969251881Speter 970251881Speter /* Set up the edit baton. */ 971251881Speter eb->pool = subpool; 972251881Speter eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool); 973251881Speter eb->commit_callback = commit_callback; 974251881Speter eb->commit_callback_baton = commit_baton; 975251881Speter eb->authz_callback = authz_callback; 976251881Speter eb->authz_baton = authz_baton; 977251881Speter eb->base_path = svn_fspath__canonicalize(base_path, subpool); 978251881Speter eb->repos = repos; 979251881Speter eb->repos_url = repos_url; 980251881Speter eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool), 981251881Speter subpool); 982251881Speter eb->fs = svn_repos_fs(repos); 983251881Speter eb->txn = txn; 984251881Speter eb->txn_owner = txn == NULL; 985251881Speter 986251881Speter *edit_baton = eb; 987251881Speter *editor = e; 988251881Speter 989251881Speter shim_callbacks->fetch_props_func = fetch_props_func; 990251881Speter shim_callbacks->fetch_kind_func = fetch_kind_func; 991251881Speter shim_callbacks->fetch_base_func = fetch_base_func; 992251881Speter shim_callbacks->fetch_baton = eb; 993251881Speter 994251881Speter SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, 995251881Speter eb->repos_url, eb->base_path, 996251881Speter shim_callbacks, pool, pool)); 997251881Speter 998251881Speter return SVN_NO_ERROR; 999251881Speter} 1000251881Speter 1001251881Speter 1002251881Speter#if 0 1003251881Speterstatic svn_error_t * 1004251881Speterev2_check_authz(const struct ev2_baton *eb, 1005251881Speter const char *relpath, 1006251881Speter svn_repos_authz_access_t required, 1007251881Speter apr_pool_t *scratch_pool) 1008251881Speter{ 1009251881Speter const char *fspath; 1010251881Speter svn_boolean_t allowed; 1011251881Speter 1012251881Speter if (eb->authz == NULL) 1013251881Speter return SVN_NO_ERROR; 1014251881Speter 1015251881Speter if (relpath) 1016251881Speter fspath = apr_pstrcat(scratch_pool, "/", relpath, NULL); 1017251881Speter else 1018251881Speter fspath = NULL; 1019251881Speter 1020251881Speter SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath, 1021251881Speter eb->authz_user, required, 1022251881Speter &allowed, scratch_pool)); 1023251881Speter if (!allowed) 1024251881Speter return svn_error_create(required & svn_authz_write 1025251881Speter ? SVN_ERR_AUTHZ_UNWRITABLE 1026251881Speter : SVN_ERR_AUTHZ_UNREADABLE, 1027251881Speter NULL, "Access denied"); 1028251881Speter 1029251881Speter return SVN_NO_ERROR; 1030251881Speter} 1031251881Speter#endif 1032251881Speter 1033251881Speter 1034251881Speter/* This implements svn_editor_cb_add_directory_t */ 1035251881Speterstatic svn_error_t * 1036251881Speteradd_directory_cb(void *baton, 1037251881Speter const char *relpath, 1038251881Speter const apr_array_header_t *children, 1039251881Speter apr_hash_t *props, 1040251881Speter svn_revnum_t replaces_rev, 1041251881Speter apr_pool_t *scratch_pool) 1042251881Speter{ 1043251881Speter struct ev2_baton *eb = baton; 1044251881Speter 1045251881Speter SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props, 1046251881Speter replaces_rev)); 1047251881Speter return SVN_NO_ERROR; 1048251881Speter} 1049251881Speter 1050251881Speter 1051251881Speter/* This implements svn_editor_cb_add_file_t */ 1052251881Speterstatic svn_error_t * 1053251881Speteradd_file_cb(void *baton, 1054251881Speter const char *relpath, 1055251881Speter const svn_checksum_t *checksum, 1056251881Speter svn_stream_t *contents, 1057251881Speter apr_hash_t *props, 1058251881Speter svn_revnum_t replaces_rev, 1059251881Speter apr_pool_t *scratch_pool) 1060251881Speter{ 1061251881Speter struct ev2_baton *eb = baton; 1062251881Speter 1063251881Speter SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props, 1064251881Speter replaces_rev)); 1065251881Speter return SVN_NO_ERROR; 1066251881Speter} 1067251881Speter 1068251881Speter 1069251881Speter/* This implements svn_editor_cb_add_symlink_t */ 1070251881Speterstatic svn_error_t * 1071251881Speteradd_symlink_cb(void *baton, 1072251881Speter const char *relpath, 1073251881Speter const char *target, 1074251881Speter apr_hash_t *props, 1075251881Speter svn_revnum_t replaces_rev, 1076251881Speter apr_pool_t *scratch_pool) 1077251881Speter{ 1078251881Speter struct ev2_baton *eb = baton; 1079251881Speter 1080251881Speter SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props, 1081251881Speter replaces_rev)); 1082251881Speter return SVN_NO_ERROR; 1083251881Speter} 1084251881Speter 1085251881Speter 1086251881Speter/* This implements svn_editor_cb_add_absent_t */ 1087251881Speterstatic svn_error_t * 1088251881Speteradd_absent_cb(void *baton, 1089251881Speter const char *relpath, 1090251881Speter svn_node_kind_t kind, 1091251881Speter svn_revnum_t replaces_rev, 1092251881Speter apr_pool_t *scratch_pool) 1093251881Speter{ 1094251881Speter struct ev2_baton *eb = baton; 1095251881Speter 1096251881Speter SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev)); 1097251881Speter return SVN_NO_ERROR; 1098251881Speter} 1099251881Speter 1100251881Speter 1101251881Speter/* This implements svn_editor_cb_alter_directory_t */ 1102251881Speterstatic svn_error_t * 1103251881Speteralter_directory_cb(void *baton, 1104251881Speter const char *relpath, 1105251881Speter svn_revnum_t revision, 1106251881Speter const apr_array_header_t *children, 1107251881Speter apr_hash_t *props, 1108251881Speter apr_pool_t *scratch_pool) 1109251881Speter{ 1110251881Speter struct ev2_baton *eb = baton; 1111251881Speter 1112251881Speter SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision, 1113251881Speter children, props)); 1114251881Speter return SVN_NO_ERROR; 1115251881Speter} 1116251881Speter 1117251881Speter 1118251881Speter/* This implements svn_editor_cb_alter_file_t */ 1119251881Speterstatic svn_error_t * 1120251881Speteralter_file_cb(void *baton, 1121251881Speter const char *relpath, 1122251881Speter svn_revnum_t revision, 1123251881Speter apr_hash_t *props, 1124251881Speter const svn_checksum_t *checksum, 1125251881Speter svn_stream_t *contents, 1126251881Speter apr_pool_t *scratch_pool) 1127251881Speter{ 1128251881Speter struct ev2_baton *eb = baton; 1129251881Speter 1130251881Speter SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, props, 1131251881Speter checksum, contents)); 1132251881Speter return SVN_NO_ERROR; 1133251881Speter} 1134251881Speter 1135251881Speter 1136251881Speter/* This implements svn_editor_cb_alter_symlink_t */ 1137251881Speterstatic svn_error_t * 1138251881Speteralter_symlink_cb(void *baton, 1139251881Speter const char *relpath, 1140251881Speter svn_revnum_t revision, 1141251881Speter apr_hash_t *props, 1142251881Speter const char *target, 1143251881Speter apr_pool_t *scratch_pool) 1144251881Speter{ 1145251881Speter struct ev2_baton *eb = baton; 1146251881Speter 1147251881Speter SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, props, 1148251881Speter target)); 1149251881Speter return SVN_NO_ERROR; 1150251881Speter} 1151251881Speter 1152251881Speter 1153251881Speter/* This implements svn_editor_cb_delete_t */ 1154251881Speterstatic svn_error_t * 1155251881Speterdelete_cb(void *baton, 1156251881Speter const char *relpath, 1157251881Speter svn_revnum_t revision, 1158251881Speter apr_pool_t *scratch_pool) 1159251881Speter{ 1160251881Speter struct ev2_baton *eb = baton; 1161251881Speter 1162251881Speter SVN_ERR(svn_editor_delete(eb->inner, relpath, revision)); 1163251881Speter return SVN_NO_ERROR; 1164251881Speter} 1165251881Speter 1166251881Speter 1167251881Speter/* This implements svn_editor_cb_copy_t */ 1168251881Speterstatic svn_error_t * 1169251881Spetercopy_cb(void *baton, 1170251881Speter const char *src_relpath, 1171251881Speter svn_revnum_t src_revision, 1172251881Speter const char *dst_relpath, 1173251881Speter svn_revnum_t replaces_rev, 1174251881Speter apr_pool_t *scratch_pool) 1175251881Speter{ 1176251881Speter struct ev2_baton *eb = baton; 1177251881Speter 1178251881Speter SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath, 1179251881Speter replaces_rev)); 1180251881Speter return SVN_NO_ERROR; 1181251881Speter} 1182251881Speter 1183251881Speter 1184251881Speter/* This implements svn_editor_cb_move_t */ 1185251881Speterstatic svn_error_t * 1186251881Spetermove_cb(void *baton, 1187251881Speter const char *src_relpath, 1188251881Speter svn_revnum_t src_revision, 1189251881Speter const char *dst_relpath, 1190251881Speter svn_revnum_t replaces_rev, 1191251881Speter apr_pool_t *scratch_pool) 1192251881Speter{ 1193251881Speter struct ev2_baton *eb = baton; 1194251881Speter 1195251881Speter SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath, 1196251881Speter replaces_rev)); 1197251881Speter return SVN_NO_ERROR; 1198251881Speter} 1199251881Speter 1200251881Speter 1201251881Speter/* This implements svn_editor_cb_rotate_t */ 1202251881Speterstatic svn_error_t * 1203251881Speterrotate_cb(void *baton, 1204251881Speter const apr_array_header_t *relpaths, 1205251881Speter const apr_array_header_t *revisions, 1206251881Speter apr_pool_t *scratch_pool) 1207251881Speter{ 1208251881Speter struct ev2_baton *eb = baton; 1209251881Speter 1210251881Speter SVN_ERR(svn_editor_rotate(eb->inner, relpaths, revisions)); 1211251881Speter return SVN_NO_ERROR; 1212251881Speter} 1213251881Speter 1214251881Speter 1215251881Speter/* This implements svn_editor_cb_complete_t */ 1216251881Speterstatic svn_error_t * 1217251881Spetercomplete_cb(void *baton, 1218251881Speter apr_pool_t *scratch_pool) 1219251881Speter{ 1220251881Speter struct ev2_baton *eb = baton; 1221251881Speter svn_revnum_t revision; 1222251881Speter svn_error_t *post_commit_err; 1223251881Speter const char *conflict_path; 1224251881Speter svn_error_t *err; 1225251881Speter const char *post_commit_errstr; 1226251881Speter apr_hash_t *hooks_env; 1227251881Speter 1228251881Speter /* Parse the hooks-env file (if any). */ 1229251881Speter SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path, 1230251881Speter scratch_pool, scratch_pool)); 1231251881Speter 1232251881Speter /* The transaction has been fully edited. Let the pre-commit hook 1233251881Speter have a look at the thing. */ 1234251881Speter SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env, 1235251881Speter eb->txn_name, scratch_pool)); 1236251881Speter 1237251881Speter /* Hook is done. Let's do the actual commit. */ 1238251881Speter SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path, 1239251881Speter eb->inner, scratch_pool, scratch_pool)); 1240251881Speter 1241251881Speter /* Did a conflict occur during the commit process? */ 1242251881Speter if (conflict_path != NULL) 1243251881Speter return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, 1244251881Speter _("Conflict at '%s'"), conflict_path); 1245251881Speter 1246251881Speter /* Since did not receive an error during the commit process, and no 1247251881Speter conflict was specified... we committed a revision. Run the hooks. 1248251881Speter Other errors may have occurred within the FS (specified by the 1249251881Speter POST_COMMIT_ERR localvar), but we need to run the hooks. */ 1250251881Speter SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); 1251251881Speter err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision, 1252251881Speter eb->txn_name, scratch_pool); 1253251881Speter if (err) 1254251881Speter err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err, 1255251881Speter _("Commit succeeded, but post-commit hook failed")); 1256251881Speter 1257251881Speter /* Combine the FS errors with the hook errors, and stringify. */ 1258251881Speter err = svn_error_compose_create(post_commit_err, err); 1259251881Speter if (err) 1260251881Speter { 1261251881Speter post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool); 1262251881Speter svn_error_clear(err); 1263251881Speter } 1264251881Speter else 1265251881Speter { 1266251881Speter post_commit_errstr = NULL; 1267251881Speter } 1268251881Speter 1269251881Speter return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton, 1270251881Speter eb->repos->fs, revision, 1271251881Speter post_commit_errstr, 1272251881Speter scratch_pool)); 1273251881Speter} 1274251881Speter 1275251881Speter 1276251881Speter/* This implements svn_editor_cb_abort_t */ 1277251881Speterstatic svn_error_t * 1278251881Speterabort_cb(void *baton, 1279251881Speter apr_pool_t *scratch_pool) 1280251881Speter{ 1281251881Speter struct ev2_baton *eb = baton; 1282251881Speter 1283251881Speter SVN_ERR(svn_editor_abort(eb->inner)); 1284251881Speter return SVN_NO_ERROR; 1285251881Speter} 1286251881Speter 1287251881Speter 1288251881Speterstatic svn_error_t * 1289251881Speterapply_revprops(svn_fs_t *fs, 1290251881Speter const char *txn_name, 1291251881Speter apr_hash_t *revprops, 1292251881Speter apr_pool_t *scratch_pool) 1293251881Speter{ 1294251881Speter svn_fs_txn_t *txn; 1295251881Speter const apr_array_header_t *revprops_array; 1296251881Speter 1297251881Speter /* The FS editor has a TXN inside it, but we can't access it. Open another 1298251881Speter based on the TXN_NAME. */ 1299251881Speter SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool)); 1300251881Speter 1301251881Speter /* Validate and apply the revision properties. */ 1302251881Speter revprops_array = svn_prop_hash_to_array(revprops, scratch_pool); 1303251881Speter SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool)); 1304251881Speter 1305251881Speter /* ### do we need to force the txn to close, or is it enough to wait 1306251881Speter ### for the pool to be cleared? */ 1307251881Speter return SVN_NO_ERROR; 1308251881Speter} 1309251881Speter 1310251881Speter 1311251881Spetersvn_error_t * 1312251881Spetersvn_repos__get_commit_ev2(svn_editor_t **editor, 1313251881Speter svn_repos_t *repos, 1314251881Speter svn_authz_t *authz, 1315251881Speter const char *authz_repos_name, 1316251881Speter const char *authz_user, 1317251881Speter apr_hash_t *revprops, 1318251881Speter svn_commit_callback2_t commit_cb, 1319251881Speter void *commit_baton, 1320251881Speter svn_cancel_func_t cancel_func, 1321251881Speter void *cancel_baton, 1322251881Speter apr_pool_t *result_pool, 1323251881Speter apr_pool_t *scratch_pool) 1324251881Speter{ 1325251881Speter static const svn_editor_cb_many_t editor_cbs = { 1326251881Speter add_directory_cb, 1327251881Speter add_file_cb, 1328251881Speter add_symlink_cb, 1329251881Speter add_absent_cb, 1330251881Speter alter_directory_cb, 1331251881Speter alter_file_cb, 1332251881Speter alter_symlink_cb, 1333251881Speter delete_cb, 1334251881Speter copy_cb, 1335251881Speter move_cb, 1336251881Speter rotate_cb, 1337251881Speter complete_cb, 1338251881Speter abort_cb 1339251881Speter }; 1340251881Speter struct ev2_baton *eb; 1341251881Speter const svn_string_t *author; 1342251881Speter apr_hash_t *hooks_env; 1343251881Speter 1344251881Speter /* Parse the hooks-env file (if any). */ 1345251881Speter SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, 1346251881Speter scratch_pool, scratch_pool)); 1347251881Speter 1348251881Speter /* Can the user modify the repository at all? */ 1349251881Speter /* ### check against AUTHZ. */ 1350251881Speter 1351251881Speter author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR); 1352251881Speter 1353251881Speter eb = apr_palloc(result_pool, sizeof(*eb)); 1354251881Speter eb->repos = repos; 1355251881Speter eb->authz = authz; 1356251881Speter eb->authz_repos_name = authz_repos_name; 1357251881Speter eb->authz_user = authz_user; 1358251881Speter eb->commit_cb = commit_cb; 1359251881Speter eb->commit_baton = commit_baton; 1360251881Speter 1361251881Speter SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name, 1362251881Speter repos->fs, SVN_FS_TXN_CHECK_LOCKS, 1363251881Speter cancel_func, cancel_baton, 1364251881Speter result_pool, scratch_pool)); 1365251881Speter 1366251881Speter /* The TXN has been created. Go ahead and apply all revision properties. */ 1367251881Speter SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool)); 1368251881Speter 1369251881Speter /* Okay... some access is allowed. Let's run the start-commit hook. */ 1370251881Speter SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env, 1371251881Speter author ? author->data : NULL, 1372251881Speter repos->client_capabilities, 1373251881Speter eb->txn_name, scratch_pool)); 1374251881Speter 1375251881Speter /* Wrap the FS editor within our editor. */ 1376251881Speter SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton, 1377251881Speter result_pool, scratch_pool)); 1378251881Speter SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool)); 1379251881Speter 1380251881Speter return SVN_NO_ERROR; 1381251881Speter} 1382