commit.c revision 289166
1231200Smm/* commit.c --- editor for committing changes to a filesystem. 2231200Smm * 3231200Smm * ==================================================================== 4231200Smm * Licensed to the Apache Software Foundation (ASF) under one 5231200Smm * or more contributor license agreements. See the NOTICE file 6231200Smm * distributed with this work for additional information 7231200Smm * regarding copyright ownership. The ASF licenses this file 8231200Smm * to you under the Apache License, Version 2.0 (the 9231200Smm * "License"); you may not use this file except in compliance 10231200Smm * with the License. You may obtain a copy of the License at 11231200Smm * 12231200Smm * http://www.apache.org/licenses/LICENSE-2.0 13231200Smm * 14231200Smm * Unless required by applicable law or agreed to in writing, 15231200Smm * software distributed under the License is distributed on an 16231200Smm * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17231200Smm * KIND, either express or implied. See the License for the 18231200Smm * specific language governing permissions and limitations 19231200Smm * under the License. 20231200Smm * ==================================================================== 21231200Smm */ 22231200Smm 23231200Smm 24231200Smm#include <string.h> 25238856Smm 26231200Smm#include <apr_pools.h> 27358090Smm#include <apr_file_io.h> 28231200Smm 29231200Smm#include "svn_hash.h" 30231200Smm#include "svn_compat.h" 31231200Smm#include "svn_pools.h" 32231200Smm#include "svn_error.h" 33231200Smm#include "svn_dirent_uri.h" 34231200Smm#include "svn_path.h" 35302295Smm#include "svn_delta.h" 36238856Smm#include "svn_fs.h" 37238856Smm#include "svn_repos.h" 38231200Smm#include "svn_checksum.h" 39231200Smm#include "svn_ctype.h" 40231200Smm#include "svn_props.h" 41231200Smm#include "svn_mergeinfo.h" 42231200Smm#include "svn_private_config.h" 43231200Smm 44231200Smm#include "repos.h" 45231200Smm 46231200Smm#include "private/svn_fspath.h" 47231200Smm#include "private/svn_fs_private.h" 48231200Smm#include "private/svn_repos_private.h" 49231200Smm#include "private/svn_editor.h" 50231200Smm 51231200Smm 52231200Smm 53231200Smm/*** Editor batons. ***/ 54231200Smm 55231200Smmstruct edit_baton 56231200Smm{ 57231200Smm apr_pool_t *pool; 58231200Smm 59231200Smm /** Supplied when the editor is created: **/ 60231200Smm 61231200Smm /* Revision properties to set for this commit. */ 62231200Smm apr_hash_t *revprop_table; 63231200Smm 64231200Smm /* Callback to run when the commit is done. */ 65231200Smm svn_commit_callback2_t commit_callback; 66231200Smm void *commit_callback_baton; 67231200Smm 68231200Smm /* Callback to check authorizations on paths. */ 69231200Smm svn_repos_authz_callback_t authz_callback; 70231200Smm void *authz_baton; 71231200Smm 72231200Smm /* The already-open svn repository to commit to. */ 73353377Smm svn_repos_t *repos; 74231200Smm 75231200Smm /* URL to the root of the open repository. */ 76231200Smm const char *repos_url; 77231200Smm 78231200Smm /* The name of the repository (here for convenience). */ 79231200Smm const char *repos_name; 80231200Smm 81231200Smm /* The filesystem associated with the REPOS above (here for 82231200Smm convenience). */ 83231200Smm svn_fs_t *fs; 84231200Smm 85231200Smm /* Location in fs where the edit will begin. */ 86231200Smm const char *base_path; 87231200Smm 88231200Smm /* Does this set of interfaces 'own' the commit transaction? */ 89231200Smm svn_boolean_t txn_owner; 90231200Smm 91231200Smm /* svn transaction associated with this edit (created in 92231200Smm open_root, or supplied by the public API caller). */ 93231200Smm svn_fs_txn_t *txn; 94231200Smm 95231200Smm /** Filled in during open_root: **/ 96231200Smm 97231200Smm /* The name of the transaction. */ 98231200Smm const char *txn_name; 99231200Smm 100231200Smm /* The object representing the root directory of the svn txn. */ 101231200Smm svn_fs_root_t *txn_root; 102231200Smm 103231200Smm /* Avoid aborting an fs transaction more than once */ 104302001Smm svn_boolean_t txn_aborted; 105302001Smm 106302001Smm /** Filled in when the edit is closed: **/ 107302001Smm 108302001Smm /* The new revision created by this commit. */ 109302001Smm svn_revnum_t *new_rev; 110231200Smm 111231200Smm /* The date (according to the repository) of this commit. */ 112231200Smm const char **committed_date; 113231200Smm 114231200Smm /* The author (also according to the repository) of this commit. */ 115231200Smm const char **committed_author; 116231200Smm}; 117231200Smm 118231200Smm 119231200Smmstruct dir_baton 120231200Smm{ 121231200Smm struct edit_baton *edit_baton; 122231200Smm struct dir_baton *parent; 123231200Smm const char *path; /* the -absolute- path to this dir in the fs */ 124231200Smm svn_revnum_t base_rev; /* the revision I'm based on */ 125231200Smm svn_boolean_t was_copied; /* was this directory added with history? */ 126231200Smm apr_pool_t *pool; /* my personal pool, in which I am allocated. */ 127231200Smm}; 128231200Smm 129231200Smm 130231200Smmstruct file_baton 131302001Smm{ 132231200Smm struct edit_baton *edit_baton; 133231200Smm const char *path; /* the -absolute- path to this file in the fs */ 134231200Smm}; 135231200Smm 136231200Smm 137231200Smmstruct ev2_baton 138231200Smm{ 139231200Smm /* The repository we are editing. */ 140231200Smm svn_repos_t *repos; 141353377Smm 142231200Smm /* The authz baton for checks; NULL to skip authz. */ 143302001Smm svn_authz_t *authz; 144231200Smm 145231200Smm /* The repository name and user for performing authz checks. */ 146231200Smm const char *authz_repos_name; 147231200Smm const char *authz_user; 148231200Smm 149231200Smm /* Callback to provide info about the committed revision. */ 150231200Smm svn_commit_callback2_t commit_cb; 151231200Smm void *commit_baton; 152231200Smm 153231200Smm /* The FS txn editor */ 154231200Smm svn_editor_t *inner; 155231200Smm 156231200Smm /* The name of the open transaction (so we know what to commit) */ 157231200Smm const char *txn_name; 158231200Smm}; 159231200Smm 160231200Smm 161231200Smm/* Create and return a generic out-of-dateness error. */ 162231200Smmstatic svn_error_t * 163231200Smmout_of_date(const char *path, svn_node_kind_t kind) 164231200Smm{ 165231200Smm return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, 166231200Smm (kind == svn_node_dir 167231200Smm ? _("Directory '%s' is out of date") 168231200Smm : kind == svn_node_file 169231200Smm ? _("File '%s' is out of date") 170231200Smm : _("'%s' is out of date")), 171231200Smm path); 172231200Smm} 173358090Smm 174358090Smm 175358090Smmstatic svn_error_t * 176358090Smminvoke_commit_cb(svn_commit_callback2_t commit_cb, 177358090Smm void *commit_baton, 178358090Smm svn_fs_t *fs, 179358090Smm svn_revnum_t revision, 180358090Smm const char *post_commit_errstr, 181358090Smm apr_pool_t *scratch_pool) 182358090Smm{ 183358090Smm /* FS interface returns non-const values. */ 184358090Smm /* const */ svn_string_t *date; 185358090Smm /* const */ svn_string_t *author; 186231200Smm svn_commit_info_t *commit_info; 187231200Smm 188231200Smm if (commit_cb == NULL) 189231200Smm return SVN_NO_ERROR; 190358090Smm 191358090Smm SVN_ERR(svn_fs_revision_prop(&date, fs, revision, SVN_PROP_REVISION_DATE, 192358090Smm scratch_pool)); 193231200Smm SVN_ERR(svn_fs_revision_prop(&author, fs, revision, 194358090Smm SVN_PROP_REVISION_AUTHOR, 195358090Smm scratch_pool)); 196358090Smm 197358090Smm commit_info = svn_create_commit_info(scratch_pool); 198358090Smm 199358090Smm /* fill up the svn_commit_info structure */ 200358090Smm commit_info->revision = revision; 201358090Smm commit_info->date = date ? date->data : NULL; 202358090Smm commit_info->author = author ? author->data : NULL; 203358090Smm commit_info->post_commit_err = post_commit_errstr; 204358090Smm 205358090Smm return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool)); 206358090Smm} 207358090Smm 208358090Smm 209358090Smm 210358090Smm/* If EDITOR_BATON contains a valid authz callback, verify that the 211358090Smm REQUIRED access to PATH in ROOT is authorized. Return an error 212358090Smm appropriate for throwing out of the commit editor with SVN_ERR. If 213358090Smm no authz callback is present in EDITOR_BATON, then authorize all 214358090Smm paths. Use POOL for temporary allocation only. */ 215358090Smmstatic svn_error_t * 216358090Smmcheck_authz(struct edit_baton *editor_baton, const char *path, 217358090Smm svn_fs_root_t *root, svn_repos_authz_access_t required, 218358090Smm apr_pool_t *pool) 219358090Smm{ 220358090Smm if (editor_baton->authz_callback) 221358090Smm { 222358090Smm svn_boolean_t allowed; 223358090Smm 224358090Smm SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path, 225358090Smm editor_baton->authz_baton, pool)); 226358090Smm if (!allowed) 227358090Smm return svn_error_create(required & svn_authz_write ? 228358090Smm SVN_ERR_AUTHZ_UNWRITABLE : 229358090Smm SVN_ERR_AUTHZ_UNREADABLE, 230358090Smm NULL, "Access denied"); 231358090Smm } 232358090Smm 233358090Smm return SVN_NO_ERROR; 234358090Smm} 235358090Smm 236358090Smm 237358090Smm/* Return a directory baton allocated in POOL which represents 238358090Smm FULL_PATH, which is the immediate directory child of the directory 239358090Smm represented by PARENT_BATON. EDIT_BATON is the commit editor 240358090Smm baton. WAS_COPIED reveals whether or not this directory is the 241358090Smm result of a copy operation. BASE_REVISION is the base revision of 242358090Smm the directory. */ 243231200Smmstatic struct dir_baton * 244231200Smmmake_dir_baton(struct edit_baton *edit_baton, 245231200Smm struct dir_baton *parent_baton, 246231200Smm const char *full_path, 247358090Smm svn_boolean_t was_copied, 248358090Smm svn_revnum_t base_revision, 249358090Smm apr_pool_t *pool) 250358090Smm{ 251358090Smm struct dir_baton *db; 252358090Smm db = apr_pcalloc(pool, sizeof(*db)); 253358090Smm db->edit_baton = edit_baton; 254358090Smm db->parent = parent_baton; 255358090Smm db->pool = pool; 256358090Smm db->path = full_path; 257358090Smm db->was_copied = was_copied; 258362134Smm db->base_rev = base_revision; 259362134Smm return db; 260358090Smm} 261358090Smm 262358090Smm/* This function is the shared guts of add_file() and add_directory(), 263358090Smm which see for the meanings of the parameters. The only extra 264358090Smm parameter here is IS_DIR, which is TRUE when adding a directory, 265358090Smm and FALSE when adding a file. */ 266358090Smmstatic svn_error_t * 267358090Smmadd_file_or_directory(const char *path, 268358090Smm void *parent_baton, 269358090Smm const char *copy_path, 270358090Smm svn_revnum_t copy_revision, 271358090Smm svn_boolean_t is_dir, 272358090Smm apr_pool_t *pool, 273358090Smm void **return_baton) 274358090Smm{ 275358090Smm struct dir_baton *pb = parent_baton; 276358090Smm struct edit_baton *eb = pb->edit_baton; 277231200Smm apr_pool_t *subpool = svn_pool_create(pool); 278358090Smm svn_boolean_t was_copied = FALSE; 279358090Smm const char *full_path; 280358090Smm 281231200Smm /* Reject paths which contain control characters (related to issue #4340). */ 282358090Smm SVN_ERR(svn_path_check_valid(path, pool)); 283231200Smm 284358090Smm full_path = svn_fspath__join(eb->base_path, 285358090Smm svn_relpath_canonicalize(path, pool), pool); 286358090Smm 287231200Smm /* Sanity check. */ 288358090Smm if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision))) 289358090Smm return svn_error_createf 290358090Smm (SVN_ERR_FS_GENERAL, NULL, 291358090Smm _("Got source path but no source revision for '%s'"), full_path); 292358090Smm 293358090Smm if (copy_path) 294231200Smm { 295231200Smm const char *fs_path; 296231200Smm svn_fs_root_t *copy_root; 297231200Smm svn_node_kind_t kind; 298231200Smm size_t repos_url_len; 299353377Smm svn_repos_authz_access_t required; 300353377Smm 301231200Smm /* Copy requires recursive write access to the destination path 302231200Smm and write access to the parent path. */ 303353377Smm required = svn_authz_write | (is_dir ? svn_authz_recursive : 0); 304353377Smm SVN_ERR(check_authz(eb, full_path, eb->txn_root, 305231200Smm required, subpool)); 306231200Smm SVN_ERR(check_authz(eb, pb->path, eb->txn_root, 307353377Smm svn_authz_write, subpool)); 308353377Smm 309231200Smm /* Check PATH in our transaction. Make sure it does not exist 310231200Smm unless its parent directory was copied (in which case, the 311353377Smm thing might have been copied in as well), else return an 312353377Smm out-of-dateness error. */ 313231200Smm SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool)); 314231200Smm if ((kind != svn_node_none) && (! pb->was_copied)) 315353377Smm return svn_error_trace(out_of_date(full_path, kind)); 316353377Smm 317231200Smm /* For now, require that the url come from the same repository 318231200Smm that this commit is operating on. */ 319353377Smm copy_path = svn_path_uri_decode(copy_path, subpool); 320353377Smm repos_url_len = strlen(eb->repos_url); 321231200Smm if (strncmp(copy_path, eb->repos_url, repos_url_len) != 0) 322231200Smm return svn_error_createf 323231200Smm (SVN_ERR_FS_GENERAL, NULL, 324231200Smm _("Source url '%s' is from different repository"), copy_path); 325231200Smm 326231200Smm fs_path = apr_pstrdup(subpool, copy_path + repos_url_len); 327231200Smm 328231200Smm /* Now use the "fs_path" as an absolute path within the 329231200Smm repository to make the copy from. */ 330231200Smm SVN_ERR(svn_fs_revision_root(©_root, eb->fs, 331231200Smm copy_revision, subpool)); 332231200Smm 333231200Smm /* Copy also requires (recursive) read access to the source */ 334231200Smm required = svn_authz_read | (is_dir ? svn_authz_recursive : 0); 335231200Smm SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool)); 336231200Smm 337231200Smm SVN_ERR(svn_fs_copy(copy_root, fs_path, 338231200Smm eb->txn_root, full_path, subpool)); 339231200Smm was_copied = TRUE; 340231200Smm } 341231200Smm else 342231200Smm { 343231200Smm /* No ancestry given, just make a new directory or empty file. 344231200Smm Note that we don't perform an existence check here like the 345231200Smm copy-from case does -- that's because svn_fs_make_*() 346231200Smm already errors out if the file already exists. Verify write 347231200Smm access to the full path and to the parent. */ 348231200Smm SVN_ERR(check_authz(eb, full_path, eb->txn_root, 349231200Smm svn_authz_write, subpool)); 350231200Smm SVN_ERR(check_authz(eb, pb->path, eb->txn_root, 351231200Smm svn_authz_write, subpool)); 352231200Smm if (is_dir) 353231200Smm SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool)); 354231200Smm else 355231200Smm SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool)); 356231200Smm } 357231200Smm 358302001Smm /* Cleanup our temporary subpool. */ 359231200Smm svn_pool_destroy(subpool); 360231200Smm 361231200Smm /* Build a new child baton. */ 362231200Smm if (is_dir) 363231200Smm { 364231200Smm *return_baton = make_dir_baton(eb, pb, full_path, was_copied, 365231200Smm SVN_INVALID_REVNUM, pool); 366231200Smm } 367231200Smm else 368353377Smm { 369231200Smm struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb)); 370231200Smm new_fb->edit_baton = eb; 371231200Smm new_fb->path = full_path; 372231200Smm *return_baton = new_fb; 373231200Smm } 374231200Smm 375231200Smm return SVN_NO_ERROR; 376231200Smm} 377231200Smm 378231200Smm 379231200Smm 380231200Smm/*** Editor functions ***/ 381231200Smm 382231200Smmstatic svn_error_t * 383231200Smmopen_root(void *edit_baton, 384231200Smm svn_revnum_t base_revision, 385231200Smm apr_pool_t *pool, 386231200Smm void **root_baton) 387231200Smm{ 388231200Smm struct dir_baton *dirb; 389231200Smm struct edit_baton *eb = edit_baton; 390231200Smm svn_revnum_t youngest; 391231200Smm 392353377Smm /* Ignore BASE_REVISION. We always build our transaction against 393231200Smm HEAD. However, we will keep it in our dir baton for out of 394231200Smm dateness checks. */ 395231200Smm SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool)); 396231200Smm 397231200Smm /* Unless we've been instructed to use a specific transaction, we'll 398231200Smm make our own. */ 399231200Smm if (eb->txn_owner) 400231200Smm { 401231200Smm SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn), 402231200Smm eb->repos, 403231200Smm youngest, 404231200Smm eb->revprop_table, 405231200Smm eb->pool)); 406231200Smm } 407231200Smm else /* Even if we aren't the owner of the transaction, we might 408231200Smm have been instructed to set some properties. */ 409231200Smm { 410231200Smm apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table, 411231200Smm pool); 412231200Smm SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool)); 413231200Smm } 414231200Smm SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool)); 415231200Smm SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool)); 416231200Smm 417231200Smm /* Create a root dir baton. The `base_path' field is an -absolute- 418231200Smm path in the filesystem, upon which all further editor paths are 419231200Smm based. */ 420231200Smm dirb = apr_pcalloc(pool, sizeof(*dirb)); 421231200Smm dirb->edit_baton = edit_baton; 422231200Smm dirb->parent = NULL; 423231200Smm dirb->pool = pool; 424231200Smm dirb->was_copied = FALSE; 425231200Smm dirb->path = apr_pstrdup(pool, eb->base_path); 426231200Smm dirb->base_rev = base_revision; 427231200Smm 428231200Smm *root_baton = dirb; 429231200Smm return SVN_NO_ERROR; 430231200Smm} 431231200Smm 432231200Smm 433231200Smm 434231200Smmstatic svn_error_t * 435231200Smmdelete_entry(const char *path, 436231200Smm svn_revnum_t revision, 437231200Smm void *parent_baton, 438231200Smm apr_pool_t *pool) 439231200Smm{ 440231200Smm struct dir_baton *parent = parent_baton; 441231200Smm struct edit_baton *eb = parent->edit_baton; 442231200Smm svn_node_kind_t kind; 443231200Smm svn_revnum_t cr_rev; 444231200Smm svn_repos_authz_access_t required = svn_authz_write; 445231200Smm const char *full_path; 446231200Smm 447231200Smm full_path = svn_fspath__join(eb->base_path, 448231200Smm svn_relpath_canonicalize(path, pool), pool); 449231200Smm 450231200Smm /* Check PATH in our transaction. */ 451231200Smm SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool)); 452231200Smm 453231200Smm /* Deletion requires a recursive write access, as well as write 454231200Smm access to the parent directory. */ 455231200Smm if (kind == svn_node_dir) 456231200Smm required |= svn_authz_recursive; 457231200Smm SVN_ERR(check_authz(eb, full_path, eb->txn_root, 458231200Smm required, pool)); 459231200Smm SVN_ERR(check_authz(eb, parent->path, eb->txn_root, 460231200Smm svn_authz_write, pool)); 461231200Smm 462231200Smm /* If PATH doesn't exist in the txn, the working copy is out of date. */ 463231200Smm if (kind == svn_node_none) 464231200Smm return svn_error_trace(out_of_date(full_path, kind)); 465231200Smm 466231200Smm /* Now, make sure we're deleting the node we *think* we're 467231200Smm deleting, else return an out-of-dateness error. */ 468231200Smm SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool)); 469231200Smm if (SVN_IS_VALID_REVNUM(revision) && (revision < cr_rev)) 470231200Smm return svn_error_trace(out_of_date(full_path, kind)); 471231200Smm 472231200Smm /* This routine is a mindless wrapper. We call svn_fs_delete() 473231200Smm because that will delete files and recursively delete 474231200Smm directories. */ 475231200Smm return svn_fs_delete(eb->txn_root, full_path, pool); 476231200Smm} 477231200Smm 478231200Smm 479231200Smmstatic svn_error_t * 480231200Smmadd_directory(const char *path, 481231200Smm void *parent_baton, 482231200Smm const char *copy_path, 483231200Smm svn_revnum_t copy_revision, 484231200Smm apr_pool_t *pool, 485231200Smm void **child_baton) 486231200Smm{ 487231200Smm return add_file_or_directory(path, parent_baton, copy_path, copy_revision, 488231200Smm TRUE /* is_dir */, pool, child_baton); 489231200Smm} 490231200Smm 491231200Smm 492231200Smmstatic svn_error_t * 493231200Smmopen_directory(const char *path, 494231200Smm void *parent_baton, 495231200Smm svn_revnum_t base_revision, 496231200Smm apr_pool_t *pool, 497231200Smm void **child_baton) 498231200Smm{ 499231200Smm struct dir_baton *pb = parent_baton; 500358090Smm struct edit_baton *eb = pb->edit_baton; 501353377Smm svn_node_kind_t kind; 502358090Smm const char *full_path; 503358090Smm 504358090Smm full_path = svn_fspath__join(eb->base_path, 505358090Smm svn_relpath_canonicalize(path, pool), pool); 506358090Smm 507358090Smm /* Check PATH in our transaction. If it does not exist, 508358090Smm return a 'Path not present' error. */ 509358090Smm SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool)); 510358090Smm if (kind == svn_node_none) 511358090Smm return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, 512358090Smm _("Path '%s' not present"), 513358090Smm path); 514358090Smm 515358090Smm /* Build a new dir baton for this directory. */ 516358090Smm *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied, 517358090Smm base_revision, pool); 518358090Smm return SVN_NO_ERROR; 519358090Smm} 520358090Smm 521358090Smm 522358090Smmstatic svn_error_t * 523358090Smmapply_textdelta(void *file_baton, 524358090Smm const char *base_checksum, 525358090Smm apr_pool_t *pool, 526358090Smm svn_txdelta_window_handler_t *handler, 527353377Smm void **handler_baton) 528358090Smm{ 529353377Smm struct file_baton *fb = file_baton; 530358090Smm 531358090Smm /* Check for write authorization. */ 532358090Smm SVN_ERR(check_authz(fb->edit_baton, fb->path, 533358090Smm fb->edit_baton->txn_root, 534358090Smm svn_authz_write, pool)); 535358090Smm 536358090Smm return svn_fs_apply_textdelta(handler, handler_baton, 537358090Smm fb->edit_baton->txn_root, 538358090Smm fb->path, 539358090Smm base_checksum, 540358090Smm NULL, 541358090Smm pool); 542358090Smm} 543358090Smm 544358090Smm 545358090Smmstatic svn_error_t * 546358090Smmadd_file(const char *path, 547358090Smm void *parent_baton, 548353377Smm const char *copy_path, 549358090Smm svn_revnum_t copy_revision, 550358090Smm apr_pool_t *pool, 551358090Smm void **file_baton) 552358090Smm{ 553358090Smm return add_file_or_directory(path, parent_baton, copy_path, copy_revision, 554358090Smm FALSE /* is_dir */, pool, file_baton); 555358090Smm} 556358090Smm 557358090Smm 558358090Smmstatic svn_error_t * 559358090Smmopen_file(const char *path, 560358090Smm void *parent_baton, 561358090Smm svn_revnum_t base_revision, 562358090Smm apr_pool_t *pool, 563358090Smm void **file_baton) 564358090Smm{ 565358090Smm struct file_baton *new_fb; 566358090Smm struct dir_baton *pb = parent_baton; 567358090Smm struct edit_baton *eb = pb->edit_baton; 568358090Smm svn_revnum_t cr_rev; 569358090Smm apr_pool_t *subpool = svn_pool_create(pool); 570358090Smm const char *full_path; 571358090Smm 572358090Smm full_path = svn_fspath__join(eb->base_path, 573358090Smm svn_relpath_canonicalize(path, pool), pool); 574358090Smm 575358090Smm /* Check for read authorization. */ 576358090Smm SVN_ERR(check_authz(eb, full_path, eb->txn_root, 577358090Smm svn_authz_read, subpool)); 578358090Smm 579358090Smm /* Get this node's creation revision (doubles as an existence check). */ 580358090Smm SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, 581358090Smm subpool)); 582358090Smm 583358090Smm /* If the node our caller has is an older revision number than the 584358090Smm one in our transaction, return an out-of-dateness error. */ 585358090Smm if (SVN_IS_VALID_REVNUM(base_revision) && (base_revision < cr_rev)) 586358090Smm return svn_error_trace(out_of_date(full_path, svn_node_file)); 587358090Smm 588358090Smm /* Build a new file baton */ 589358090Smm new_fb = apr_pcalloc(pool, sizeof(*new_fb)); 590358090Smm new_fb->edit_baton = eb; 591358090Smm new_fb->path = full_path; 592358090Smm 593358090Smm *file_baton = new_fb; 594358090Smm 595358090Smm /* Destory the work subpool. */ 596358090Smm svn_pool_destroy(subpool); 597358090Smm 598358090Smm return SVN_NO_ERROR; 599358090Smm} 600358090Smm 601358090Smm 602358090Smmstatic svn_error_t * 603358090Smmchange_file_prop(void *file_baton, 604358090Smm const char *name, 605302001Smm const svn_string_t *value, 606302001Smm apr_pool_t *pool) 607302001Smm{ 608302001Smm struct file_baton *fb = file_baton; 609302001Smm struct edit_baton *eb = fb->edit_baton; 610302001Smm 611302001Smm /* Check for write authorization. */ 612302001Smm SVN_ERR(check_authz(eb, fb->path, eb->txn_root, 613302001Smm svn_authz_write, pool)); 614302001Smm 615353377Smm return svn_repos_fs_change_node_prop(eb->txn_root, fb->path, 616353377Smm name, value, pool); 617353377Smm} 618353377Smm 619353377Smm 620353377Smmstatic svn_error_t * 621353377Smmclose_file(void *file_baton, 622353377Smm const char *text_digest, 623353377Smm apr_pool_t *pool) 624358090Smm{ 625358090Smm struct file_baton *fb = file_baton; 626358090Smm 627358090Smm if (text_digest) 628358090Smm { 629358090Smm svn_checksum_t *checksum; 630358090Smm svn_checksum_t *text_checksum; 631358090Smm 632358090Smm SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 633358090Smm fb->edit_baton->txn_root, fb->path, 634358090Smm TRUE, pool)); 635358090Smm SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, 636358090Smm text_digest, pool)); 637358090Smm 638302001Smm if (!svn_checksum_match(text_checksum, checksum)) 639302001Smm return svn_checksum_mismatch_err(text_checksum, checksum, pool, 640302001Smm _("Checksum mismatch for resulting fulltext\n(%s)"), 641302001Smm fb->path); 642302001Smm } 643302001Smm 644302001Smm return SVN_NO_ERROR; 645302001Smm} 646358090Smm 647358090Smm 648302001Smmstatic svn_error_t * 649302001Smmchange_dir_prop(void *dir_baton, 650302001Smm const char *name, 651302001Smm const svn_string_t *value, 652302001Smm apr_pool_t *pool) 653302001Smm{ 654302001Smm struct dir_baton *db = dir_baton; 655302001Smm struct edit_baton *eb = db->edit_baton; 656302001Smm 657302001Smm /* Check for write authorization. */ 658302001Smm SVN_ERR(check_authz(eb, db->path, eb->txn_root, 659302001Smm svn_authz_write, pool)); 660302001Smm 661302001Smm if (SVN_IS_VALID_REVNUM(db->base_rev)) 662302001Smm { 663302001Smm /* Subversion rule: propchanges can only happen on a directory 664302001Smm which is up-to-date. */ 665302001Smm svn_revnum_t created_rev; 666302001Smm SVN_ERR(svn_fs_node_created_rev(&created_rev, 667302001Smm eb->txn_root, db->path, pool)); 668302001Smm 669302001Smm if (db->base_rev < created_rev) 670231200Smm return svn_error_trace(out_of_date(db->path, svn_node_dir)); 671302001Smm } 672231200Smm 673231200Smm return svn_repos_fs_change_node_prop(eb->txn_root, db->path, 674231200Smm name, value, pool); 675231200Smm} 676231200Smm 677231200Smmconst char * 678231200Smmsvn_repos__post_commit_error_str(svn_error_t *err, 679231200Smm apr_pool_t *pool) 680231200Smm{ 681231200Smm svn_error_t *hook_err1, *hook_err2; 682231200Smm const char *msg; 683231200Smm 684231200Smm if (! err) 685231200Smm return _("(no error)"); 686231200Smm 687231200Smm err = svn_error_purge_tracing(err); 688302001Smm 689231200Smm /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped 690231200Smm error from the post-commit script, if any, and hook_err2 should 691231200Smm be the original error, but be defensive and handle a case where 692231200Smm SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */ 693231200Smm hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED); 694231200Smm if (hook_err1 && hook_err1->child) 695231200Smm hook_err2 = hook_err1->child; 696231200Smm else 697353377Smm hook_err2 = hook_err1; 698353377Smm 699231200Smm /* This implementation counts on svn_repos_fs_commit_txn() and 700231200Smm libsvn_repos/commit.c:complete_cb() returning 701231200Smm svn_fs_commit_txn() as the parent error with a child 702231200Smm SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error. If the parent error 703231200Smm is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error 704231200Smm in svn_fs_commit_txn(). 705231200Smm 706231200Smm The post-commit hook error message is already self describing, so 707231200Smm it can be dropped into an error message without any additional 708231200Smm text. */ 709 if (hook_err1) 710 { 711 if (err == hook_err1) 712 { 713 if (hook_err2->message) 714 msg = apr_pstrdup(pool, hook_err2->message); 715 else 716 msg = _("post-commit hook failed with no error message."); 717 } 718 else 719 { 720 msg = hook_err2->message 721 ? apr_pstrdup(pool, hook_err2->message) 722 : _("post-commit hook failed with no error message."); 723 msg = apr_psprintf( 724 pool, 725 _("post commit FS processing had error:\n%s\n%s"), 726 err->message ? err->message : _("(no error message)"), 727 msg); 728 } 729 } 730 else 731 { 732 msg = apr_psprintf(pool, 733 _("post commit FS processing had error:\n%s"), 734 err->message ? err->message 735 : _("(no error message)")); 736 } 737 738 return msg; 739} 740 741static svn_error_t * 742close_edit(void *edit_baton, 743 apr_pool_t *pool) 744{ 745 struct edit_baton *eb = edit_baton; 746 svn_revnum_t new_revision = SVN_INVALID_REVNUM; 747 svn_error_t *err; 748 const char *conflict; 749 const char *post_commit_err = NULL; 750 751 /* If no transaction has been created (ie. if open_root wasn't 752 called before close_edit), abort the operation here with an 753 error. */ 754 if (! eb->txn) 755 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, 756 "No valid transaction supplied to close_edit"); 757 758 /* Commit. */ 759 err = svn_repos_fs_commit_txn(&conflict, eb->repos, 760 &new_revision, eb->txn, pool); 761 762 if (SVN_IS_VALID_REVNUM(new_revision)) 763 { 764 /* The actual commit succeeded, i.e. the transaction does no longer 765 exist and we can't use txn_root for conflict resolution etc. 766 767 Since close_edit is supposed to release resources, do it now. */ 768 if (eb->txn_root) 769 svn_fs_close_root(eb->txn_root); 770 771 if (err) 772 { 773 /* If the error was in post-commit, then the commit itself 774 succeeded. In which case, save the post-commit warning 775 (to be reported back to the client, who will probably 776 display it as a warning) and clear the error. */ 777 post_commit_err = svn_repos__post_commit_error_str(err, pool); 778 svn_error_clear(err); 779 } 780 781 /* Make sure a future abort doesn't perform 782 any work. This may occur if the commit 783 callback returns an error! */ 784 785 eb->txn = NULL; 786 eb->txn_root = NULL; 787 } 788 else 789 { 790 /* ### todo: we should check whether it really was a conflict, 791 and return the conflict info if so? */ 792 793 /* If the commit failed, it's *probably* due to a conflict -- 794 that is, the txn being out-of-date. The filesystem gives us 795 the ability to continue diddling the transaction and try 796 again; but let's face it: that's not how the cvs or svn works 797 from a user interface standpoint. Thus we don't make use of 798 this fs feature (for now, at least.) 799 800 So, in a nutshell: svn commits are an all-or-nothing deal. 801 Each commit creates a new fs txn which either succeeds or is 802 aborted completely. No second chances; the user simply 803 needs to update and commit again :) */ 804 805 eb->txn_aborted = TRUE; 806 807 return svn_error_trace( 808 svn_error_compose_create(err, 809 svn_fs_abort_txn(eb->txn, pool))); 810 } 811 812 /* At this point, the post-commit error has been converted to a string. 813 That information will be passed to a callback, if provided. If the 814 callback invocation fails in some way, that failure is returned here. 815 IOW, the post-commit error information is low priority compared to 816 other gunk here. */ 817 818 /* Pass new revision information to the caller's callback. */ 819 return svn_error_trace(invoke_commit_cb(eb->commit_callback, 820 eb->commit_callback_baton, 821 eb->repos->fs, 822 new_revision, 823 post_commit_err, 824 pool)); 825} 826 827 828static svn_error_t * 829abort_edit(void *edit_baton, 830 apr_pool_t *pool) 831{ 832 struct edit_baton *eb = edit_baton; 833 if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted) 834 return SVN_NO_ERROR; 835 836 eb->txn_aborted = TRUE; 837 838 /* Since abort_edit is supposed to release resources, do it now. */ 839 if (eb->txn_root) 840 svn_fs_close_root(eb->txn_root); 841 842 return svn_error_trace(svn_fs_abort_txn(eb->txn, pool)); 843} 844 845 846static svn_error_t * 847fetch_props_func(apr_hash_t **props, 848 void *baton, 849 const char *path, 850 svn_revnum_t base_revision, 851 apr_pool_t *result_pool, 852 apr_pool_t *scratch_pool) 853{ 854 struct edit_baton *eb = baton; 855 svn_fs_root_t *fs_root; 856 svn_error_t *err; 857 858 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, 859 svn_fs_txn_base_revision(eb->txn), 860 scratch_pool)); 861 err = svn_fs_node_proplist(props, fs_root, path, result_pool); 862 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 863 { 864 svn_error_clear(err); 865 *props = apr_hash_make(result_pool); 866 return SVN_NO_ERROR; 867 } 868 else if (err) 869 return svn_error_trace(err); 870 871 return SVN_NO_ERROR; 872} 873 874static svn_error_t * 875fetch_kind_func(svn_node_kind_t *kind, 876 void *baton, 877 const char *path, 878 svn_revnum_t base_revision, 879 apr_pool_t *scratch_pool) 880{ 881 struct edit_baton *eb = baton; 882 svn_fs_root_t *fs_root; 883 884 if (!SVN_IS_VALID_REVNUM(base_revision)) 885 base_revision = svn_fs_txn_base_revision(eb->txn); 886 887 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 888 889 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); 890 891 return SVN_NO_ERROR; 892} 893 894static svn_error_t * 895fetch_base_func(const char **filename, 896 void *baton, 897 const char *path, 898 svn_revnum_t base_revision, 899 apr_pool_t *result_pool, 900 apr_pool_t *scratch_pool) 901{ 902 struct edit_baton *eb = baton; 903 svn_stream_t *contents; 904 svn_stream_t *file_stream; 905 const char *tmp_filename; 906 svn_fs_root_t *fs_root; 907 svn_error_t *err; 908 909 if (!SVN_IS_VALID_REVNUM(base_revision)) 910 base_revision = svn_fs_txn_base_revision(eb->txn); 911 912 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 913 914 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); 915 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 916 { 917 svn_error_clear(err); 918 *filename = NULL; 919 return SVN_NO_ERROR; 920 } 921 else if (err) 922 return svn_error_trace(err); 923 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, 924 svn_io_file_del_on_pool_cleanup, 925 scratch_pool, scratch_pool)); 926 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); 927 928 *filename = apr_pstrdup(result_pool, tmp_filename); 929 930 return SVN_NO_ERROR; 931} 932 933 934 935/*** Public interfaces. ***/ 936 937svn_error_t * 938svn_repos_get_commit_editor5(const svn_delta_editor_t **editor, 939 void **edit_baton, 940 svn_repos_t *repos, 941 svn_fs_txn_t *txn, 942 const char *repos_url, 943 const char *base_path, 944 apr_hash_t *revprop_table, 945 svn_commit_callback2_t commit_callback, 946 void *commit_baton, 947 svn_repos_authz_callback_t authz_callback, 948 void *authz_baton, 949 apr_pool_t *pool) 950{ 951 svn_delta_editor_t *e; 952 apr_pool_t *subpool = svn_pool_create(pool); 953 struct edit_baton *eb; 954 svn_delta_shim_callbacks_t *shim_callbacks = 955 svn_delta_shim_callbacks_default(pool); 956 957 /* Do a global authz access lookup. Users with no write access 958 whatsoever to the repository don't get a commit editor. */ 959 if (authz_callback) 960 { 961 svn_boolean_t allowed; 962 963 SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL, 964 authz_baton, pool)); 965 if (!allowed) 966 return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL, 967 "Not authorized to open a commit editor."); 968 } 969 970 /* Allocate the structures. */ 971 e = svn_delta_default_editor(pool); 972 eb = apr_pcalloc(subpool, sizeof(*eb)); 973 974 /* Set up the editor. */ 975 e->open_root = open_root; 976 e->delete_entry = delete_entry; 977 e->add_directory = add_directory; 978 e->open_directory = open_directory; 979 e->change_dir_prop = change_dir_prop; 980 e->add_file = add_file; 981 e->open_file = open_file; 982 e->close_file = close_file; 983 e->apply_textdelta = apply_textdelta; 984 e->change_file_prop = change_file_prop; 985 e->close_edit = close_edit; 986 e->abort_edit = abort_edit; 987 988 /* Set up the edit baton. */ 989 eb->pool = subpool; 990 eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool); 991 eb->commit_callback = commit_callback; 992 eb->commit_callback_baton = commit_baton; 993 eb->authz_callback = authz_callback; 994 eb->authz_baton = authz_baton; 995 eb->base_path = svn_fspath__canonicalize(base_path, subpool); 996 eb->repos = repos; 997 eb->repos_url = repos_url; 998 eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool), 999 subpool); 1000 eb->fs = svn_repos_fs(repos); 1001 eb->txn = txn; 1002 eb->txn_owner = txn == NULL; 1003 1004 *edit_baton = eb; 1005 *editor = e; 1006 1007 shim_callbacks->fetch_props_func = fetch_props_func; 1008 shim_callbacks->fetch_kind_func = fetch_kind_func; 1009 shim_callbacks->fetch_base_func = fetch_base_func; 1010 shim_callbacks->fetch_baton = eb; 1011 1012 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, 1013 eb->repos_url, eb->base_path, 1014 shim_callbacks, pool, pool)); 1015 1016 return SVN_NO_ERROR; 1017} 1018 1019 1020#if 0 1021static svn_error_t * 1022ev2_check_authz(const struct ev2_baton *eb, 1023 const char *relpath, 1024 svn_repos_authz_access_t required, 1025 apr_pool_t *scratch_pool) 1026{ 1027 const char *fspath; 1028 svn_boolean_t allowed; 1029 1030 if (eb->authz == NULL) 1031 return SVN_NO_ERROR; 1032 1033 if (relpath) 1034 fspath = apr_pstrcat(scratch_pool, "/", relpath, NULL); 1035 else 1036 fspath = NULL; 1037 1038 SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath, 1039 eb->authz_user, required, 1040 &allowed, scratch_pool)); 1041 if (!allowed) 1042 return svn_error_create(required & svn_authz_write 1043 ? SVN_ERR_AUTHZ_UNWRITABLE 1044 : SVN_ERR_AUTHZ_UNREADABLE, 1045 NULL, "Access denied"); 1046 1047 return SVN_NO_ERROR; 1048} 1049#endif 1050 1051 1052/* This implements svn_editor_cb_add_directory_t */ 1053static svn_error_t * 1054add_directory_cb(void *baton, 1055 const char *relpath, 1056 const apr_array_header_t *children, 1057 apr_hash_t *props, 1058 svn_revnum_t replaces_rev, 1059 apr_pool_t *scratch_pool) 1060{ 1061 struct ev2_baton *eb = baton; 1062 1063 SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props, 1064 replaces_rev)); 1065 return SVN_NO_ERROR; 1066} 1067 1068 1069/* This implements svn_editor_cb_add_file_t */ 1070static svn_error_t * 1071add_file_cb(void *baton, 1072 const char *relpath, 1073 const svn_checksum_t *checksum, 1074 svn_stream_t *contents, 1075 apr_hash_t *props, 1076 svn_revnum_t replaces_rev, 1077 apr_pool_t *scratch_pool) 1078{ 1079 struct ev2_baton *eb = baton; 1080 1081 SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props, 1082 replaces_rev)); 1083 return SVN_NO_ERROR; 1084} 1085 1086 1087/* This implements svn_editor_cb_add_symlink_t */ 1088static svn_error_t * 1089add_symlink_cb(void *baton, 1090 const char *relpath, 1091 const char *target, 1092 apr_hash_t *props, 1093 svn_revnum_t replaces_rev, 1094 apr_pool_t *scratch_pool) 1095{ 1096 struct ev2_baton *eb = baton; 1097 1098 SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props, 1099 replaces_rev)); 1100 return SVN_NO_ERROR; 1101} 1102 1103 1104/* This implements svn_editor_cb_add_absent_t */ 1105static svn_error_t * 1106add_absent_cb(void *baton, 1107 const char *relpath, 1108 svn_node_kind_t kind, 1109 svn_revnum_t replaces_rev, 1110 apr_pool_t *scratch_pool) 1111{ 1112 struct ev2_baton *eb = baton; 1113 1114 SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev)); 1115 return SVN_NO_ERROR; 1116} 1117 1118 1119/* This implements svn_editor_cb_alter_directory_t */ 1120static svn_error_t * 1121alter_directory_cb(void *baton, 1122 const char *relpath, 1123 svn_revnum_t revision, 1124 const apr_array_header_t *children, 1125 apr_hash_t *props, 1126 apr_pool_t *scratch_pool) 1127{ 1128 struct ev2_baton *eb = baton; 1129 1130 SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision, 1131 children, props)); 1132 return SVN_NO_ERROR; 1133} 1134 1135 1136/* This implements svn_editor_cb_alter_file_t */ 1137static svn_error_t * 1138alter_file_cb(void *baton, 1139 const char *relpath, 1140 svn_revnum_t revision, 1141 apr_hash_t *props, 1142 const svn_checksum_t *checksum, 1143 svn_stream_t *contents, 1144 apr_pool_t *scratch_pool) 1145{ 1146 struct ev2_baton *eb = baton; 1147 1148 SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, props, 1149 checksum, contents)); 1150 return SVN_NO_ERROR; 1151} 1152 1153 1154/* This implements svn_editor_cb_alter_symlink_t */ 1155static svn_error_t * 1156alter_symlink_cb(void *baton, 1157 const char *relpath, 1158 svn_revnum_t revision, 1159 apr_hash_t *props, 1160 const char *target, 1161 apr_pool_t *scratch_pool) 1162{ 1163 struct ev2_baton *eb = baton; 1164 1165 SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, props, 1166 target)); 1167 return SVN_NO_ERROR; 1168} 1169 1170 1171/* This implements svn_editor_cb_delete_t */ 1172static svn_error_t * 1173delete_cb(void *baton, 1174 const char *relpath, 1175 svn_revnum_t revision, 1176 apr_pool_t *scratch_pool) 1177{ 1178 struct ev2_baton *eb = baton; 1179 1180 SVN_ERR(svn_editor_delete(eb->inner, relpath, revision)); 1181 return SVN_NO_ERROR; 1182} 1183 1184 1185/* This implements svn_editor_cb_copy_t */ 1186static svn_error_t * 1187copy_cb(void *baton, 1188 const char *src_relpath, 1189 svn_revnum_t src_revision, 1190 const char *dst_relpath, 1191 svn_revnum_t replaces_rev, 1192 apr_pool_t *scratch_pool) 1193{ 1194 struct ev2_baton *eb = baton; 1195 1196 SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath, 1197 replaces_rev)); 1198 return SVN_NO_ERROR; 1199} 1200 1201 1202/* This implements svn_editor_cb_move_t */ 1203static svn_error_t * 1204move_cb(void *baton, 1205 const char *src_relpath, 1206 svn_revnum_t src_revision, 1207 const char *dst_relpath, 1208 svn_revnum_t replaces_rev, 1209 apr_pool_t *scratch_pool) 1210{ 1211 struct ev2_baton *eb = baton; 1212 1213 SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath, 1214 replaces_rev)); 1215 return SVN_NO_ERROR; 1216} 1217 1218 1219/* This implements svn_editor_cb_rotate_t */ 1220static svn_error_t * 1221rotate_cb(void *baton, 1222 const apr_array_header_t *relpaths, 1223 const apr_array_header_t *revisions, 1224 apr_pool_t *scratch_pool) 1225{ 1226 struct ev2_baton *eb = baton; 1227 1228 SVN_ERR(svn_editor_rotate(eb->inner, relpaths, revisions)); 1229 return SVN_NO_ERROR; 1230} 1231 1232 1233/* This implements svn_editor_cb_complete_t */ 1234static svn_error_t * 1235complete_cb(void *baton, 1236 apr_pool_t *scratch_pool) 1237{ 1238 struct ev2_baton *eb = baton; 1239 svn_revnum_t revision; 1240 svn_error_t *post_commit_err; 1241 const char *conflict_path; 1242 svn_error_t *err; 1243 const char *post_commit_errstr; 1244 apr_hash_t *hooks_env; 1245 1246 /* Parse the hooks-env file (if any). */ 1247 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path, 1248 scratch_pool, scratch_pool)); 1249 1250 /* The transaction has been fully edited. Let the pre-commit hook 1251 have a look at the thing. */ 1252 SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env, 1253 eb->txn_name, scratch_pool)); 1254 1255 /* Hook is done. Let's do the actual commit. */ 1256 SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path, 1257 eb->inner, scratch_pool, scratch_pool)); 1258 1259 /* Did a conflict occur during the commit process? */ 1260 if (conflict_path != NULL) 1261 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, 1262 _("Conflict at '%s'"), conflict_path); 1263 1264 /* Since did not receive an error during the commit process, and no 1265 conflict was specified... we committed a revision. Run the hooks. 1266 Other errors may have occurred within the FS (specified by the 1267 POST_COMMIT_ERR localvar), but we need to run the hooks. */ 1268 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); 1269 err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision, 1270 eb->txn_name, scratch_pool); 1271 if (err) 1272 err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err, 1273 _("Commit succeeded, but post-commit hook failed")); 1274 1275 /* Combine the FS errors with the hook errors, and stringify. */ 1276 err = svn_error_compose_create(post_commit_err, err); 1277 if (err) 1278 { 1279 post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool); 1280 svn_error_clear(err); 1281 } 1282 else 1283 { 1284 post_commit_errstr = NULL; 1285 } 1286 1287 return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton, 1288 eb->repos->fs, revision, 1289 post_commit_errstr, 1290 scratch_pool)); 1291} 1292 1293 1294/* This implements svn_editor_cb_abort_t */ 1295static svn_error_t * 1296abort_cb(void *baton, 1297 apr_pool_t *scratch_pool) 1298{ 1299 struct ev2_baton *eb = baton; 1300 1301 SVN_ERR(svn_editor_abort(eb->inner)); 1302 return SVN_NO_ERROR; 1303} 1304 1305 1306static svn_error_t * 1307apply_revprops(svn_fs_t *fs, 1308 const char *txn_name, 1309 apr_hash_t *revprops, 1310 apr_pool_t *scratch_pool) 1311{ 1312 svn_fs_txn_t *txn; 1313 const apr_array_header_t *revprops_array; 1314 1315 /* The FS editor has a TXN inside it, but we can't access it. Open another 1316 based on the TXN_NAME. */ 1317 SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool)); 1318 1319 /* Validate and apply the revision properties. */ 1320 revprops_array = svn_prop_hash_to_array(revprops, scratch_pool); 1321 SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool)); 1322 1323 /* ### do we need to force the txn to close, or is it enough to wait 1324 ### for the pool to be cleared? */ 1325 return SVN_NO_ERROR; 1326} 1327 1328 1329svn_error_t * 1330svn_repos__get_commit_ev2(svn_editor_t **editor, 1331 svn_repos_t *repos, 1332 svn_authz_t *authz, 1333 const char *authz_repos_name, 1334 const char *authz_user, 1335 apr_hash_t *revprops, 1336 svn_commit_callback2_t commit_cb, 1337 void *commit_baton, 1338 svn_cancel_func_t cancel_func, 1339 void *cancel_baton, 1340 apr_pool_t *result_pool, 1341 apr_pool_t *scratch_pool) 1342{ 1343 static const svn_editor_cb_many_t editor_cbs = { 1344 add_directory_cb, 1345 add_file_cb, 1346 add_symlink_cb, 1347 add_absent_cb, 1348 alter_directory_cb, 1349 alter_file_cb, 1350 alter_symlink_cb, 1351 delete_cb, 1352 copy_cb, 1353 move_cb, 1354 rotate_cb, 1355 complete_cb, 1356 abort_cb 1357 }; 1358 struct ev2_baton *eb; 1359 const svn_string_t *author; 1360 apr_hash_t *hooks_env; 1361 1362 /* Parse the hooks-env file (if any). */ 1363 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, 1364 scratch_pool, scratch_pool)); 1365 1366 /* Can the user modify the repository at all? */ 1367 /* ### check against AUTHZ. */ 1368 1369 author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR); 1370 1371 eb = apr_palloc(result_pool, sizeof(*eb)); 1372 eb->repos = repos; 1373 eb->authz = authz; 1374 eb->authz_repos_name = authz_repos_name; 1375 eb->authz_user = authz_user; 1376 eb->commit_cb = commit_cb; 1377 eb->commit_baton = commit_baton; 1378 1379 SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name, 1380 repos->fs, SVN_FS_TXN_CHECK_LOCKS, 1381 cancel_func, cancel_baton, 1382 result_pool, scratch_pool)); 1383 1384 /* The TXN has been created. Go ahead and apply all revision properties. */ 1385 SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool)); 1386 1387 /* Okay... some access is allowed. Let's run the start-commit hook. */ 1388 SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env, 1389 author ? author->data : NULL, 1390 repos->client_capabilities, 1391 eb->txn_name, scratch_pool)); 1392 1393 /* Wrap the FS editor within our editor. */ 1394 SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton, 1395 result_pool, scratch_pool)); 1396 SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool)); 1397 1398 return SVN_NO_ERROR; 1399} 1400