1/* commit.c --- editor for committing changes to a filesystem. 2 * 3 * ==================================================================== 4 * Licensed to the Apache Software Foundation (ASF) under one 5 * or more contributor license agreements. See the NOTICE file 6 * distributed with this work for additional information 7 * regarding copyright ownership. The ASF licenses this file 8 * to you under the Apache License, Version 2.0 (the 9 * "License"); you may not use this file except in compliance 10 * with the License. You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, 15 * software distributed under the License is distributed on an 16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17 * KIND, either express or implied. See the License for the 18 * specific language governing permissions and limitations 19 * under the License. 20 * ==================================================================== 21 */ 22 23 24#include <string.h> 25 26#include <apr_pools.h> 27#include <apr_file_io.h> 28 29#include "svn_hash.h" 30#include "svn_compat.h" 31#include "svn_pools.h" 32#include "svn_error.h" 33#include "svn_dirent_uri.h" 34#include "svn_path.h" 35#include "svn_delta.h" 36#include "svn_fs.h" 37#include "svn_repos.h" 38#include "svn_checksum.h" 39#include "svn_ctype.h" 40#include "svn_props.h" 41#include "svn_mergeinfo.h" 42#include "svn_private_config.h" 43 44#include "repos.h" 45 46#include "private/svn_fspath.h" 47#include "private/svn_fs_private.h" 48#include "private/svn_repos_private.h" 49#include "private/svn_editor.h" 50 51 52 53/*** Editor batons. ***/ 54 55struct edit_baton 56{ 57 apr_pool_t *pool; 58 59 /** Supplied when the editor is created: **/ 60 61 /* Revision properties to set for this commit. */ 62 apr_hash_t *revprop_table; 63 64 /* Callback to run when the commit is done. */ 65 svn_commit_callback2_t commit_callback; 66 void *commit_callback_baton; 67 68 /* Callback to check authorizations on paths. */ 69 svn_repos_authz_callback_t authz_callback; 70 void *authz_baton; 71 72 /* The already-open svn repository to commit to. */ 73 svn_repos_t *repos; 74 75 /* URL to the root of the open repository. */ 76 const char *repos_url_decoded; 77 78 /* The name of the repository (here for convenience). */ 79 const char *repos_name; 80 81 /* The filesystem associated with the REPOS above (here for 82 convenience). */ 83 svn_fs_t *fs; 84 85 /* Location in fs where the edit will begin. */ 86 const char *base_path; 87 88 /* Does this set of interfaces 'own' the commit transaction? */ 89 svn_boolean_t txn_owner; 90 91 /* svn transaction associated with this edit (created in 92 open_root, or supplied by the public API caller). */ 93 svn_fs_txn_t *txn; 94 95 /** Filled in during open_root: **/ 96 97 /* The name of the transaction. */ 98 const char *txn_name; 99 100 /* The object representing the root directory of the svn txn. */ 101 svn_fs_root_t *txn_root; 102 103 /* Avoid aborting an fs transaction more than once */ 104 svn_boolean_t txn_aborted; 105 106 /** Filled in when the edit is closed: **/ 107 108 /* The new revision created by this commit. */ 109 svn_revnum_t *new_rev; 110 111 /* The date (according to the repository) of this commit. */ 112 const char **committed_date; 113 114 /* The author (also according to the repository) of this commit. */ 115 const char **committed_author; 116}; 117 118 119struct dir_baton 120{ 121 struct edit_baton *edit_baton; 122 struct dir_baton *parent; 123 const char *path; /* the -absolute- path to this dir in the fs */ 124 svn_revnum_t base_rev; /* the revision I'm based on */ 125 svn_boolean_t was_copied; /* was this directory added with history? */ 126 apr_pool_t *pool; /* my personal pool, in which I am allocated. */ 127 svn_boolean_t checked_write; /* TRUE after successfull write check */ 128}; 129 130 131struct file_baton 132{ 133 struct edit_baton *edit_baton; 134 const char *path; /* the -absolute- path to this file in the fs */ 135 svn_boolean_t checked_write; /* TRUE after successfull write check */ 136}; 137 138 139struct ev2_baton 140{ 141 /* The repository we are editing. */ 142 svn_repos_t *repos; 143 144 /* The authz baton for checks; NULL to skip authz. */ 145 svn_authz_t *authz; 146 147 /* The repository name and user for performing authz checks. */ 148 const char *authz_repos_name; 149 const char *authz_user; 150 151 /* Callback to provide info about the committed revision. */ 152 svn_commit_callback2_t commit_cb; 153 void *commit_baton; 154 155 /* The FS txn editor */ 156 svn_editor_t *inner; 157 158 /* The name of the open transaction (so we know what to commit) */ 159 const char *txn_name; 160}; 161 162 163/* Create and return a generic out-of-dateness error. */ 164static svn_error_t * 165out_of_date(const char *path, svn_node_kind_t kind) 166{ 167 return svn_error_createf(SVN_ERR_FS_TXN_OUT_OF_DATE, NULL, 168 (kind == svn_node_dir 169 ? _("Directory '%s' is out of date") 170 : kind == svn_node_file 171 ? _("File '%s' is out of date") 172 : _("'%s' is out of date")), 173 path); 174} 175 176/* Perform an out of date check for base_rev against created rev, 177 and a sanity check of base_rev. */ 178static svn_error_t * 179check_out_of_date(struct edit_baton *eb, 180 const char *path, 181 svn_node_kind_t kind, 182 svn_revnum_t base_rev, 183 svn_revnum_t created_rev) 184{ 185 if (base_rev < created_rev) 186 { 187 return out_of_date(path, kind); 188 } 189 else if (base_rev > created_rev) 190 { 191 if (base_rev > svn_fs_txn_base_revision(eb->txn)) 192 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 193 _("No such revision %ld"), 194 base_rev); 195 } 196 197 return SVN_NO_ERROR; 198} 199 200 201static svn_error_t * 202invoke_commit_cb(svn_commit_callback2_t commit_cb, 203 void *commit_baton, 204 svn_fs_t *fs, 205 svn_revnum_t revision, 206 const char *post_commit_errstr, 207 apr_pool_t *scratch_pool) 208{ 209 /* FS interface returns non-const values. */ 210 /* const */ svn_string_t *date; 211 /* const */ svn_string_t *author; 212 svn_commit_info_t *commit_info; 213 apr_hash_t *revprops; 214 215 if (commit_cb == NULL) 216 return SVN_NO_ERROR; 217 218 SVN_ERR(svn_fs_revision_proplist2(&revprops, fs, revision, 219 TRUE, scratch_pool, scratch_pool)); 220 221 date = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE); 222 author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR); 223 224 commit_info = svn_create_commit_info(scratch_pool); 225 226 /* fill up the svn_commit_info structure */ 227 commit_info->revision = revision; 228 commit_info->date = date ? date->data : NULL; 229 commit_info->author = author ? author->data : NULL; 230 commit_info->post_commit_err = post_commit_errstr; 231 /* commit_info->repos_root is not set by the repos layer, only by RA layers */ 232 233 return svn_error_trace(commit_cb(commit_info, commit_baton, scratch_pool)); 234} 235 236 237 238/* If EDITOR_BATON contains a valid authz callback, verify that the 239 REQUIRED access to PATH in ROOT is authorized. Return an error 240 appropriate for throwing out of the commit editor with SVN_ERR. If 241 no authz callback is present in EDITOR_BATON, then authorize all 242 paths. Use POOL for temporary allocation only. */ 243static svn_error_t * 244check_authz(struct edit_baton *editor_baton, const char *path, 245 svn_fs_root_t *root, svn_repos_authz_access_t required, 246 apr_pool_t *pool) 247{ 248 if (editor_baton->authz_callback) 249 { 250 svn_boolean_t allowed; 251 252 SVN_ERR(editor_baton->authz_callback(required, &allowed, root, path, 253 editor_baton->authz_baton, pool)); 254 if (!allowed) 255 return svn_error_create(required & svn_authz_write ? 256 SVN_ERR_AUTHZ_UNWRITABLE : 257 SVN_ERR_AUTHZ_UNREADABLE, 258 NULL, "Access denied"); 259 } 260 261 return SVN_NO_ERROR; 262} 263 264 265/* Return a directory baton allocated in POOL which represents 266 FULL_PATH, which is the immediate directory child of the directory 267 represented by PARENT_BATON. EDIT_BATON is the commit editor 268 baton. WAS_COPIED reveals whether or not this directory is the 269 result of a copy operation. BASE_REVISION is the base revision of 270 the directory. */ 271static struct dir_baton * 272make_dir_baton(struct edit_baton *edit_baton, 273 struct dir_baton *parent_baton, 274 const char *full_path, 275 svn_boolean_t was_copied, 276 svn_revnum_t base_revision, 277 apr_pool_t *pool) 278{ 279 struct dir_baton *db; 280 db = apr_pcalloc(pool, sizeof(*db)); 281 db->edit_baton = edit_baton; 282 db->parent = parent_baton; 283 db->pool = pool; 284 db->path = full_path; 285 db->was_copied = was_copied; 286 db->base_rev = base_revision; 287 return db; 288} 289 290/* This function is the shared guts of add_file() and add_directory(), 291 which see for the meanings of the parameters. The only extra 292 parameter here is IS_DIR, which is TRUE when adding a directory, 293 and FALSE when adding a file. 294 295 COPY_PATH must be a full URL, not a relative path. */ 296static svn_error_t * 297add_file_or_directory(const char *path, 298 void *parent_baton, 299 const char *copy_path, 300 svn_revnum_t copy_revision, 301 svn_boolean_t is_dir, 302 apr_pool_t *pool, 303 void **return_baton) 304{ 305 struct dir_baton *pb = parent_baton; 306 struct edit_baton *eb = pb->edit_baton; 307 apr_pool_t *subpool = svn_pool_create(pool); 308 svn_boolean_t was_copied = FALSE; 309 const char *full_path, *canonicalized_path; 310 311 /* Reject paths which contain control characters (related to issue #4340). */ 312 SVN_ERR(svn_path_check_valid(path, pool)); 313 314 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, 315 pool, pool)); 316 full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); 317 318 /* Sanity check. */ 319 if (copy_path && (! SVN_IS_VALID_REVNUM(copy_revision))) 320 return svn_error_createf 321 (SVN_ERR_FS_GENERAL, NULL, 322 _("Got source path but no source revision for '%s'"), full_path); 323 324 if (copy_path) 325 { 326 const char *fs_path; 327 svn_fs_root_t *copy_root; 328 svn_node_kind_t kind; 329 svn_repos_authz_access_t required; 330 331 /* Copy requires recursive write access to the destination path 332 and write access to the parent path. */ 333 required = svn_authz_write | (is_dir ? svn_authz_recursive : 0); 334 SVN_ERR(check_authz(eb, full_path, eb->txn_root, 335 required, subpool)); 336 SVN_ERR(check_authz(eb, pb->path, eb->txn_root, 337 svn_authz_write, subpool)); 338 339 /* Check PATH in our transaction. Make sure it does not exist 340 unless its parent directory was copied (in which case, the 341 thing might have been copied in as well), else return an 342 out-of-dateness error. */ 343 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, subpool)); 344 if ((kind != svn_node_none) && (! pb->was_copied)) 345 return svn_error_trace(out_of_date(full_path, kind)); 346 347 /* For now, require that the url come from the same repository 348 that this commit is operating on. */ 349 copy_path = svn_path_uri_decode(copy_path, subpool); 350 fs_path = svn_cstring_skip_prefix(copy_path, eb->repos_url_decoded); 351 if (!fs_path) 352 return svn_error_createf 353 (SVN_ERR_FS_GENERAL, NULL, 354 _("Source url '%s' is from different repository"), copy_path); 355 356 /* Now use the "fs_path" as an absolute path within the 357 repository to make the copy from. */ 358 SVN_ERR(svn_fs_revision_root(©_root, eb->fs, 359 copy_revision, subpool)); 360 361 /* Copy also requires (recursive) read access to the source */ 362 required = svn_authz_read | (is_dir ? svn_authz_recursive : 0); 363 SVN_ERR(check_authz(eb, fs_path, copy_root, required, subpool)); 364 365 SVN_ERR(svn_fs_copy(copy_root, fs_path, 366 eb->txn_root, full_path, subpool)); 367 was_copied = TRUE; 368 } 369 else 370 { 371 /* No ancestry given, just make a new directory or empty file. 372 Note that we don't perform an existence check here like the 373 copy-from case does -- that's because svn_fs_make_*() 374 already errors out if the file already exists. Verify write 375 access to the full path and to the parent. */ 376 SVN_ERR(check_authz(eb, full_path, eb->txn_root, 377 svn_authz_write, subpool)); 378 SVN_ERR(check_authz(eb, pb->path, eb->txn_root, 379 svn_authz_write, subpool)); 380 if (is_dir) 381 SVN_ERR(svn_fs_make_dir(eb->txn_root, full_path, subpool)); 382 else 383 SVN_ERR(svn_fs_make_file(eb->txn_root, full_path, subpool)); 384 } 385 386 /* Cleanup our temporary subpool. */ 387 svn_pool_destroy(subpool); 388 389 /* Build a new child baton. */ 390 if (is_dir) 391 { 392 struct dir_baton *new_db = make_dir_baton(eb, pb, full_path, was_copied, 393 SVN_INVALID_REVNUM, pool); 394 395 new_db->checked_write = TRUE; /* Just created */ 396 *return_baton = new_db; 397 } 398 else 399 { 400 struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb)); 401 new_fb->edit_baton = eb; 402 new_fb->path = full_path; 403 new_fb->checked_write = TRUE; /* Just created */ 404 *return_baton = new_fb; 405 } 406 407 return SVN_NO_ERROR; 408} 409 410 411 412/*** Editor functions ***/ 413 414static svn_error_t * 415open_root(void *edit_baton, 416 svn_revnum_t base_revision, 417 apr_pool_t *pool, 418 void **root_baton) 419{ 420 struct dir_baton *dirb; 421 struct edit_baton *eb = edit_baton; 422 svn_revnum_t youngest; 423 424 /* We always build our transaction against HEAD. However, we will 425 sanity-check BASE_REVISION and keep it in our dir baton for out 426 of dateness checks. */ 427 SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool)); 428 429 if (base_revision > youngest) 430 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 431 _("No such revision %ld (HEAD is %ld)"), 432 base_revision, youngest); 433 434 /* Unless we've been instructed to use a specific transaction, we'll 435 make our own. */ 436 if (eb->txn_owner) 437 { 438 SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn), 439 eb->repos, 440 youngest, 441 eb->revprop_table, 442 eb->pool)); 443 } 444 else /* Even if we aren't the owner of the transaction, we might 445 have been instructed to set some properties. */ 446 { 447 apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table, 448 pool); 449 SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool)); 450 } 451 SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool)); 452 SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool)); 453 454 /* Create a root dir baton. The `base_path' field is an -absolute- 455 path in the filesystem, upon which all further editor paths are 456 based. */ 457 dirb = apr_pcalloc(pool, sizeof(*dirb)); 458 dirb->edit_baton = edit_baton; 459 dirb->parent = NULL; 460 dirb->pool = pool; 461 dirb->was_copied = FALSE; 462 dirb->path = apr_pstrdup(pool, eb->base_path); 463 dirb->base_rev = base_revision; 464 465 *root_baton = dirb; 466 return SVN_NO_ERROR; 467} 468 469 470 471static svn_error_t * 472delete_entry(const char *path, 473 svn_revnum_t revision, 474 void *parent_baton, 475 apr_pool_t *pool) 476{ 477 struct dir_baton *parent = parent_baton; 478 struct edit_baton *eb = parent->edit_baton; 479 svn_node_kind_t kind; 480 svn_repos_authz_access_t required = svn_authz_write; 481 const char *full_path, *canonicalized_path; 482 483 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, 484 pool, pool)); 485 full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); 486 487 /* Check PATH in our transaction. */ 488 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool)); 489 490 /* Deletion requires a recursive write access, as well as write 491 access to the parent directory. */ 492 if (kind == svn_node_dir) 493 required |= svn_authz_recursive; 494 SVN_ERR(check_authz(eb, full_path, eb->txn_root, 495 required, pool)); 496 SVN_ERR(check_authz(eb, parent->path, eb->txn_root, 497 svn_authz_write, pool)); 498 499 /* If PATH doesn't exist in the txn, the working copy is out of date. */ 500 if (kind == svn_node_none) 501 return svn_error_trace(out_of_date(full_path, kind)); 502 503 /* Now, make sure we're deleting the node we *think* we're 504 deleting, else return an out-of-dateness error. */ 505 if (SVN_IS_VALID_REVNUM(revision)) 506 { 507 svn_revnum_t cr_rev; 508 509 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, pool)); 510 SVN_ERR(check_out_of_date(eb, full_path, kind, revision, cr_rev)); 511 } 512 513 /* This routine is a mindless wrapper. We call svn_fs_delete() 514 because that will delete files and recursively delete 515 directories. */ 516 return svn_error_trace(svn_fs_delete(eb->txn_root, full_path, pool)); 517} 518 519 520static svn_error_t * 521add_directory(const char *path, 522 void *parent_baton, 523 const char *copy_path, 524 svn_revnum_t copy_revision, 525 apr_pool_t *pool, 526 void **child_baton) 527{ 528 return add_file_or_directory(path, parent_baton, copy_path, copy_revision, 529 TRUE /* is_dir */, pool, child_baton); 530} 531 532 533static svn_error_t * 534open_directory(const char *path, 535 void *parent_baton, 536 svn_revnum_t base_revision, 537 apr_pool_t *pool, 538 void **child_baton) 539{ 540 struct dir_baton *pb = parent_baton; 541 struct edit_baton *eb = pb->edit_baton; 542 svn_node_kind_t kind; 543 const char *full_path, *canonicalized_path; 544 545 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, 546 pool, pool)); 547 full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); 548 549 /* Check PATH in our transaction. If it does not exist, 550 return a 'Path not present' error. */ 551 SVN_ERR(svn_fs_check_path(&kind, eb->txn_root, full_path, pool)); 552 if (kind == svn_node_none) 553 return svn_error_createf(SVN_ERR_FS_NOT_DIRECTORY, NULL, 554 _("Path '%s' not present"), 555 path); 556 557 /* Build a new dir baton for this directory. */ 558 *child_baton = make_dir_baton(eb, pb, full_path, pb->was_copied, 559 base_revision, pool); 560 return SVN_NO_ERROR; 561} 562 563 564static svn_error_t * 565apply_textdelta(void *file_baton, 566 const char *base_checksum, 567 apr_pool_t *pool, 568 svn_txdelta_window_handler_t *handler, 569 void **handler_baton) 570{ 571 struct file_baton *fb = file_baton; 572 struct edit_baton *eb = fb->edit_baton; 573 574 if (!fb->checked_write) 575 { 576 /* Check for write authorization. */ 577 SVN_ERR(check_authz(eb, fb->path, eb->txn_root, 578 svn_authz_write, pool)); 579 fb->checked_write = TRUE; 580 } 581 582 return svn_error_trace( 583 svn_fs_apply_textdelta(handler, handler_baton, 584 eb->txn_root, 585 fb->path, 586 base_checksum, 587 NULL, 588 pool)); 589} 590 591 592static svn_error_t * 593add_file(const char *path, 594 void *parent_baton, 595 const char *copy_path, 596 svn_revnum_t copy_revision, 597 apr_pool_t *pool, 598 void **file_baton) 599{ 600 return add_file_or_directory(path, parent_baton, copy_path, copy_revision, 601 FALSE /* is_dir */, pool, file_baton); 602} 603 604 605static svn_error_t * 606open_file(const char *path, 607 void *parent_baton, 608 svn_revnum_t base_revision, 609 apr_pool_t *pool, 610 void **file_baton) 611{ 612 struct file_baton *new_fb; 613 struct dir_baton *pb = parent_baton; 614 struct edit_baton *eb = pb->edit_baton; 615 svn_revnum_t cr_rev; 616 apr_pool_t *subpool = svn_pool_create(pool); 617 const char *full_path, *canonicalized_path; 618 619 SVN_ERR(svn_relpath_canonicalize_safe(&canonicalized_path, NULL, path, 620 pool, pool)); 621 full_path = svn_fspath__join(eb->base_path, canonicalized_path, pool); 622 623 /* Check for read authorization. */ 624 SVN_ERR(check_authz(eb, full_path, eb->txn_root, 625 svn_authz_read, subpool)); 626 627 /* Get this node's creation revision (doubles as an existence check). */ 628 SVN_ERR(svn_fs_node_created_rev(&cr_rev, eb->txn_root, full_path, 629 subpool)); 630 631 /* If the node our caller has is an older revision number than the 632 one in our transaction, return an out-of-dateness error. */ 633 if (SVN_IS_VALID_REVNUM(base_revision)) 634 SVN_ERR(check_out_of_date(eb, full_path, svn_node_file, 635 base_revision, cr_rev)); 636 637 /* Build a new file baton */ 638 new_fb = apr_pcalloc(pool, sizeof(*new_fb)); 639 new_fb->edit_baton = eb; 640 new_fb->path = full_path; 641 642 *file_baton = new_fb; 643 644 /* Destory the work subpool. */ 645 svn_pool_destroy(subpool); 646 647 return SVN_NO_ERROR; 648} 649 650 651static svn_error_t * 652change_file_prop(void *file_baton, 653 const char *name, 654 const svn_string_t *value, 655 apr_pool_t *pool) 656{ 657 struct file_baton *fb = file_baton; 658 struct edit_baton *eb = fb->edit_baton; 659 660 if (!fb->checked_write) 661 { 662 /* Check for write authorization. */ 663 SVN_ERR(check_authz(eb, fb->path, eb->txn_root, 664 svn_authz_write, pool)); 665 fb->checked_write = TRUE; 666 } 667 668 return svn_repos_fs_change_node_prop(eb->txn_root, fb->path, 669 name, value, pool); 670} 671 672 673static svn_error_t * 674close_file(void *file_baton, 675 const char *text_digest, 676 apr_pool_t *pool) 677{ 678 struct file_baton *fb = file_baton; 679 680 if (text_digest) 681 { 682 svn_checksum_t *checksum; 683 svn_checksum_t *text_checksum; 684 685 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 686 fb->edit_baton->txn_root, fb->path, 687 TRUE, pool)); 688 SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, 689 text_digest, pool)); 690 691 if (!svn_checksum_match(text_checksum, checksum)) 692 return svn_checksum_mismatch_err(text_checksum, checksum, pool, 693 _("Checksum mismatch for resulting fulltext\n(%s)"), 694 fb->path); 695 } 696 697 return SVN_NO_ERROR; 698} 699 700 701static svn_error_t * 702change_dir_prop(void *dir_baton, 703 const char *name, 704 const svn_string_t *value, 705 apr_pool_t *pool) 706{ 707 struct dir_baton *db = dir_baton; 708 struct edit_baton *eb = db->edit_baton; 709 710 /* Check for write authorization. */ 711 if (!db->checked_write) 712 { 713 SVN_ERR(check_authz(eb, db->path, eb->txn_root, 714 svn_authz_write, pool)); 715 716 if (SVN_IS_VALID_REVNUM(db->base_rev)) 717 { 718 /* Subversion rule: propchanges can only happen on a directory 719 which is up-to-date. */ 720 svn_revnum_t created_rev; 721 SVN_ERR(svn_fs_node_created_rev(&created_rev, 722 eb->txn_root, db->path, pool)); 723 724 SVN_ERR(check_out_of_date(eb, db->path, svn_node_dir, 725 db->base_rev, created_rev)); 726 } 727 728 db->checked_write = TRUE; /* Skip on further prop changes */ 729 } 730 731 return svn_repos_fs_change_node_prop(eb->txn_root, db->path, 732 name, value, pool); 733} 734 735const char * 736svn_repos__post_commit_error_str(svn_error_t *err, 737 apr_pool_t *pool) 738{ 739 svn_error_t *hook_err1, *hook_err2; 740 const char *msg; 741 742 if (! err) 743 return _("(no error)"); 744 745 err = svn_error_purge_tracing(err); 746 747 /* hook_err1 is the SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped 748 error from the post-commit script, if any, and hook_err2 should 749 be the original error, but be defensive and handle a case where 750 SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED doesn't wrap an error. */ 751 hook_err1 = svn_error_find_cause(err, SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED); 752 if (hook_err1 && hook_err1->child) 753 hook_err2 = hook_err1->child; 754 else 755 hook_err2 = hook_err1; 756 757 /* This implementation counts on svn_repos_fs_commit_txn() and 758 libsvn_repos/commit.c:complete_cb() returning 759 svn_fs_commit_txn() as the parent error with a child 760 SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED error. If the parent error 761 is SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED then there was no error 762 in svn_fs_commit_txn(). 763 764 The post-commit hook error message is already self describing, so 765 it can be dropped into an error message without any additional 766 text. */ 767 if (hook_err1) 768 { 769 if (err == hook_err1) 770 { 771 if (hook_err2->message) 772 msg = apr_pstrdup(pool, hook_err2->message); 773 else 774 msg = _("post-commit hook failed with no error message."); 775 } 776 else 777 { 778 msg = hook_err2->message 779 ? apr_pstrdup(pool, hook_err2->message) 780 : _("post-commit hook failed with no error message."); 781 msg = apr_psprintf( 782 pool, 783 _("post commit FS processing had error:\n%s\n%s"), 784 err->message ? err->message : _("(no error message)"), 785 msg); 786 } 787 } 788 else 789 { 790 msg = apr_psprintf(pool, 791 _("post commit FS processing had error:\n%s"), 792 err->message ? err->message 793 : _("(no error message)")); 794 } 795 796 return msg; 797} 798 799static svn_error_t * 800close_edit(void *edit_baton, 801 apr_pool_t *pool) 802{ 803 struct edit_baton *eb = edit_baton; 804 svn_revnum_t new_revision = SVN_INVALID_REVNUM; 805 svn_error_t *err; 806 const char *conflict; 807 const char *post_commit_err = NULL; 808 809 /* If no transaction has been created (ie. if open_root wasn't 810 called before close_edit), abort the operation here with an 811 error. */ 812 if (! eb->txn) 813 return svn_error_create(SVN_ERR_REPOS_BAD_ARGS, NULL, 814 "No valid transaction supplied to close_edit"); 815 816 /* Commit. */ 817 err = svn_repos_fs_commit_txn(&conflict, eb->repos, 818 &new_revision, eb->txn, pool); 819 820 if (SVN_IS_VALID_REVNUM(new_revision)) 821 { 822 /* The actual commit succeeded, i.e. the transaction does no longer 823 exist and we can't use txn_root for conflict resolution etc. 824 825 Since close_edit is supposed to release resources, do it now. */ 826 if (eb->txn_root) 827 svn_fs_close_root(eb->txn_root); 828 829 if (err) 830 { 831 /* If the error was in post-commit, then the commit itself 832 succeeded. In which case, save the post-commit warning 833 (to be reported back to the client, who will probably 834 display it as a warning) and clear the error. */ 835 post_commit_err = svn_repos__post_commit_error_str(err, pool); 836 svn_error_clear(err); 837 } 838 839 /* Make sure a future abort doesn't perform 840 any work. This may occur if the commit 841 callback returns an error! */ 842 843 eb->txn = NULL; 844 eb->txn_root = NULL; 845 } 846 else 847 { 848 /* ### todo: we should check whether it really was a conflict, 849 and return the conflict info if so? */ 850 851 /* If the commit failed, it's *probably* due to a conflict -- 852 that is, the txn being out-of-date. The filesystem gives us 853 the ability to continue diddling the transaction and try 854 again; but let's face it: that's not how the cvs or svn works 855 from a user interface standpoint. Thus we don't make use of 856 this fs feature (for now, at least.) 857 858 So, in a nutshell: svn commits are an all-or-nothing deal. 859 Each commit creates a new fs txn which either succeeds or is 860 aborted completely. No second chances; the user simply 861 needs to update and commit again :) */ 862 863 eb->txn_aborted = TRUE; 864 865 return svn_error_trace( 866 svn_error_compose_create(err, 867 svn_fs_abort_txn(eb->txn, pool))); 868 } 869 870 /* At this point, the post-commit error has been converted to a string. 871 That information will be passed to a callback, if provided. If the 872 callback invocation fails in some way, that failure is returned here. 873 IOW, the post-commit error information is low priority compared to 874 other gunk here. */ 875 876 /* Pass new revision information to the caller's callback. */ 877 return svn_error_trace(invoke_commit_cb(eb->commit_callback, 878 eb->commit_callback_baton, 879 eb->repos->fs, 880 new_revision, 881 post_commit_err, 882 pool)); 883} 884 885 886static svn_error_t * 887abort_edit(void *edit_baton, 888 apr_pool_t *pool) 889{ 890 struct edit_baton *eb = edit_baton; 891 if ((! eb->txn) || (! eb->txn_owner) || eb->txn_aborted) 892 return SVN_NO_ERROR; 893 894 eb->txn_aborted = TRUE; 895 896 /* Since abort_edit is supposed to release resources, do it now. */ 897 if (eb->txn_root) 898 svn_fs_close_root(eb->txn_root); 899 900 return svn_error_trace(svn_fs_abort_txn(eb->txn, pool)); 901} 902 903 904static svn_error_t * 905fetch_props_func(apr_hash_t **props, 906 void *baton, 907 const char *path, 908 svn_revnum_t base_revision, 909 apr_pool_t *result_pool, 910 apr_pool_t *scratch_pool) 911{ 912 struct edit_baton *eb = baton; 913 svn_fs_root_t *fs_root; 914 svn_error_t *err; 915 916 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, 917 svn_fs_txn_base_revision(eb->txn), 918 scratch_pool)); 919 err = svn_fs_node_proplist(props, fs_root, path, result_pool); 920 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 921 { 922 svn_error_clear(err); 923 *props = apr_hash_make(result_pool); 924 return SVN_NO_ERROR; 925 } 926 else if (err) 927 return svn_error_trace(err); 928 929 return SVN_NO_ERROR; 930} 931 932static svn_error_t * 933fetch_kind_func(svn_node_kind_t *kind, 934 void *baton, 935 const char *path, 936 svn_revnum_t base_revision, 937 apr_pool_t *scratch_pool) 938{ 939 struct edit_baton *eb = baton; 940 svn_fs_root_t *fs_root; 941 942 if (!SVN_IS_VALID_REVNUM(base_revision)) 943 base_revision = svn_fs_txn_base_revision(eb->txn); 944 945 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 946 947 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); 948 949 return SVN_NO_ERROR; 950} 951 952static svn_error_t * 953fetch_base_func(const char **filename, 954 void *baton, 955 const char *path, 956 svn_revnum_t base_revision, 957 apr_pool_t *result_pool, 958 apr_pool_t *scratch_pool) 959{ 960 struct edit_baton *eb = baton; 961 svn_stream_t *contents; 962 svn_stream_t *file_stream; 963 const char *tmp_filename; 964 svn_fs_root_t *fs_root; 965 svn_error_t *err; 966 967 if (!SVN_IS_VALID_REVNUM(base_revision)) 968 base_revision = svn_fs_txn_base_revision(eb->txn); 969 970 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 971 972 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); 973 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 974 { 975 svn_error_clear(err); 976 *filename = NULL; 977 return SVN_NO_ERROR; 978 } 979 else if (err) 980 return svn_error_trace(err); 981 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, 982 svn_io_file_del_on_pool_cleanup, 983 scratch_pool, scratch_pool)); 984 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); 985 986 *filename = apr_pstrdup(result_pool, tmp_filename); 987 988 return SVN_NO_ERROR; 989} 990 991 992 993/*** Public interfaces. ***/ 994 995svn_error_t * 996svn_repos_get_commit_editor5(const svn_delta_editor_t **editor, 997 void **edit_baton, 998 svn_repos_t *repos, 999 svn_fs_txn_t *txn, 1000 const char *repos_url_decoded, 1001 const char *base_path, 1002 apr_hash_t *revprop_table, 1003 svn_commit_callback2_t commit_callback, 1004 void *commit_baton, 1005 svn_repos_authz_callback_t authz_callback, 1006 void *authz_baton, 1007 apr_pool_t *pool) 1008{ 1009 svn_delta_editor_t *e; 1010 apr_pool_t *subpool = svn_pool_create(pool); 1011 struct edit_baton *eb; 1012 svn_delta_shim_callbacks_t *shim_callbacks = 1013 svn_delta_shim_callbacks_default(pool); 1014 const char *repos_url = svn_path_uri_encode(repos_url_decoded, pool); 1015 1016 /* Do a global authz access lookup. Users with no write access 1017 whatsoever to the repository don't get a commit editor. */ 1018 if (authz_callback) 1019 { 1020 svn_boolean_t allowed; 1021 1022 SVN_ERR(authz_callback(svn_authz_write, &allowed, NULL, NULL, 1023 authz_baton, pool)); 1024 if (!allowed) 1025 return svn_error_create(SVN_ERR_AUTHZ_UNWRITABLE, NULL, 1026 "Not authorized to open a commit editor."); 1027 } 1028 1029 /* Allocate the structures. */ 1030 e = svn_delta_default_editor(pool); 1031 eb = apr_pcalloc(subpool, sizeof(*eb)); 1032 1033 /* Set up the editor. */ 1034 e->open_root = open_root; 1035 e->delete_entry = delete_entry; 1036 e->add_directory = add_directory; 1037 e->open_directory = open_directory; 1038 e->change_dir_prop = change_dir_prop; 1039 e->add_file = add_file; 1040 e->open_file = open_file; 1041 e->close_file = close_file; 1042 e->apply_textdelta = apply_textdelta; 1043 e->change_file_prop = change_file_prop; 1044 e->close_edit = close_edit; 1045 e->abort_edit = abort_edit; 1046 1047 /* Set up the edit baton. */ 1048 eb->pool = subpool; 1049 eb->revprop_table = svn_prop_hash_dup(revprop_table, subpool); 1050 eb->commit_callback = commit_callback; 1051 eb->commit_callback_baton = commit_baton; 1052 eb->authz_callback = authz_callback; 1053 eb->authz_baton = authz_baton; 1054 eb->base_path = svn_fspath__canonicalize(base_path, subpool); 1055 eb->repos = repos; 1056 eb->repos_url_decoded = repos_url_decoded; 1057 eb->repos_name = svn_dirent_basename(svn_repos_path(repos, subpool), 1058 subpool); 1059 eb->fs = svn_repos_fs(repos); 1060 eb->txn = txn; 1061 eb->txn_owner = txn == NULL; 1062 1063 *edit_baton = eb; 1064 *editor = e; 1065 1066 shim_callbacks->fetch_props_func = fetch_props_func; 1067 shim_callbacks->fetch_kind_func = fetch_kind_func; 1068 shim_callbacks->fetch_base_func = fetch_base_func; 1069 shim_callbacks->fetch_baton = eb; 1070 1071 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, 1072 repos_url, eb->base_path, 1073 shim_callbacks, pool, pool)); 1074 1075 return SVN_NO_ERROR; 1076} 1077 1078 1079#if 0 1080static svn_error_t * 1081ev2_check_authz(const struct ev2_baton *eb, 1082 const char *relpath, 1083 svn_repos_authz_access_t required, 1084 apr_pool_t *scratch_pool) 1085{ 1086 const char *fspath; 1087 svn_boolean_t allowed; 1088 1089 if (eb->authz == NULL) 1090 return SVN_NO_ERROR; 1091 1092 if (relpath) 1093 fspath = apr_pstrcat(scratch_pool, "/", relpath, SVN_VA_NULL); 1094 else 1095 fspath = NULL; 1096 1097 SVN_ERR(svn_repos_authz_check_access(eb->authz, eb->authz_repos_name, fspath, 1098 eb->authz_user, required, 1099 &allowed, scratch_pool)); 1100 if (!allowed) 1101 return svn_error_create(required & svn_authz_write 1102 ? SVN_ERR_AUTHZ_UNWRITABLE 1103 : SVN_ERR_AUTHZ_UNREADABLE, 1104 NULL, "Access denied"); 1105 1106 return SVN_NO_ERROR; 1107} 1108#endif 1109 1110 1111/* This implements svn_editor_cb_add_directory_t */ 1112static svn_error_t * 1113add_directory_cb(void *baton, 1114 const char *relpath, 1115 const apr_array_header_t *children, 1116 apr_hash_t *props, 1117 svn_revnum_t replaces_rev, 1118 apr_pool_t *scratch_pool) 1119{ 1120 struct ev2_baton *eb = baton; 1121 1122 SVN_ERR(svn_editor_add_directory(eb->inner, relpath, children, props, 1123 replaces_rev)); 1124 return SVN_NO_ERROR; 1125} 1126 1127 1128/* This implements svn_editor_cb_add_file_t */ 1129static svn_error_t * 1130add_file_cb(void *baton, 1131 const char *relpath, 1132 const svn_checksum_t *checksum, 1133 svn_stream_t *contents, 1134 apr_hash_t *props, 1135 svn_revnum_t replaces_rev, 1136 apr_pool_t *scratch_pool) 1137{ 1138 struct ev2_baton *eb = baton; 1139 1140 SVN_ERR(svn_editor_add_file(eb->inner, relpath, checksum, contents, props, 1141 replaces_rev)); 1142 return SVN_NO_ERROR; 1143} 1144 1145 1146/* This implements svn_editor_cb_add_symlink_t */ 1147static svn_error_t * 1148add_symlink_cb(void *baton, 1149 const char *relpath, 1150 const char *target, 1151 apr_hash_t *props, 1152 svn_revnum_t replaces_rev, 1153 apr_pool_t *scratch_pool) 1154{ 1155 struct ev2_baton *eb = baton; 1156 1157 SVN_ERR(svn_editor_add_symlink(eb->inner, relpath, target, props, 1158 replaces_rev)); 1159 return SVN_NO_ERROR; 1160} 1161 1162 1163/* This implements svn_editor_cb_add_absent_t */ 1164static svn_error_t * 1165add_absent_cb(void *baton, 1166 const char *relpath, 1167 svn_node_kind_t kind, 1168 svn_revnum_t replaces_rev, 1169 apr_pool_t *scratch_pool) 1170{ 1171 struct ev2_baton *eb = baton; 1172 1173 SVN_ERR(svn_editor_add_absent(eb->inner, relpath, kind, replaces_rev)); 1174 return SVN_NO_ERROR; 1175} 1176 1177 1178/* This implements svn_editor_cb_alter_directory_t */ 1179static svn_error_t * 1180alter_directory_cb(void *baton, 1181 const char *relpath, 1182 svn_revnum_t revision, 1183 const apr_array_header_t *children, 1184 apr_hash_t *props, 1185 apr_pool_t *scratch_pool) 1186{ 1187 struct ev2_baton *eb = baton; 1188 1189 SVN_ERR(svn_editor_alter_directory(eb->inner, relpath, revision, 1190 children, props)); 1191 return SVN_NO_ERROR; 1192} 1193 1194 1195/* This implements svn_editor_cb_alter_file_t */ 1196static svn_error_t * 1197alter_file_cb(void *baton, 1198 const char *relpath, 1199 svn_revnum_t revision, 1200 const svn_checksum_t *checksum, 1201 svn_stream_t *contents, 1202 apr_hash_t *props, 1203 apr_pool_t *scratch_pool) 1204{ 1205 struct ev2_baton *eb = baton; 1206 1207 SVN_ERR(svn_editor_alter_file(eb->inner, relpath, revision, 1208 checksum, contents, props)); 1209 return SVN_NO_ERROR; 1210} 1211 1212 1213/* This implements svn_editor_cb_alter_symlink_t */ 1214static svn_error_t * 1215alter_symlink_cb(void *baton, 1216 const char *relpath, 1217 svn_revnum_t revision, 1218 const char *target, 1219 apr_hash_t *props, 1220 apr_pool_t *scratch_pool) 1221{ 1222 struct ev2_baton *eb = baton; 1223 1224 SVN_ERR(svn_editor_alter_symlink(eb->inner, relpath, revision, 1225 target, props)); 1226 return SVN_NO_ERROR; 1227} 1228 1229 1230/* This implements svn_editor_cb_delete_t */ 1231static svn_error_t * 1232delete_cb(void *baton, 1233 const char *relpath, 1234 svn_revnum_t revision, 1235 apr_pool_t *scratch_pool) 1236{ 1237 struct ev2_baton *eb = baton; 1238 1239 SVN_ERR(svn_editor_delete(eb->inner, relpath, revision)); 1240 return SVN_NO_ERROR; 1241} 1242 1243 1244/* This implements svn_editor_cb_copy_t */ 1245static svn_error_t * 1246copy_cb(void *baton, 1247 const char *src_relpath, 1248 svn_revnum_t src_revision, 1249 const char *dst_relpath, 1250 svn_revnum_t replaces_rev, 1251 apr_pool_t *scratch_pool) 1252{ 1253 struct ev2_baton *eb = baton; 1254 1255 SVN_ERR(svn_editor_copy(eb->inner, src_relpath, src_revision, dst_relpath, 1256 replaces_rev)); 1257 return SVN_NO_ERROR; 1258} 1259 1260 1261/* This implements svn_editor_cb_move_t */ 1262static svn_error_t * 1263move_cb(void *baton, 1264 const char *src_relpath, 1265 svn_revnum_t src_revision, 1266 const char *dst_relpath, 1267 svn_revnum_t replaces_rev, 1268 apr_pool_t *scratch_pool) 1269{ 1270 struct ev2_baton *eb = baton; 1271 1272 SVN_ERR(svn_editor_move(eb->inner, src_relpath, src_revision, dst_relpath, 1273 replaces_rev)); 1274 return SVN_NO_ERROR; 1275} 1276 1277 1278/* This implements svn_editor_cb_complete_t */ 1279static svn_error_t * 1280complete_cb(void *baton, 1281 apr_pool_t *scratch_pool) 1282{ 1283 struct ev2_baton *eb = baton; 1284 svn_revnum_t revision; 1285 svn_error_t *post_commit_err; 1286 const char *conflict_path; 1287 svn_error_t *err; 1288 const char *post_commit_errstr; 1289 apr_hash_t *hooks_env; 1290 1291 /* Parse the hooks-env file (if any). */ 1292 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, eb->repos->hooks_env_path, 1293 scratch_pool, scratch_pool)); 1294 1295 /* The transaction has been fully edited. Let the pre-commit hook 1296 have a look at the thing. */ 1297 SVN_ERR(svn_repos__hooks_pre_commit(eb->repos, hooks_env, 1298 eb->txn_name, scratch_pool)); 1299 1300 /* Hook is done. Let's do the actual commit. */ 1301 SVN_ERR(svn_fs__editor_commit(&revision, &post_commit_err, &conflict_path, 1302 eb->inner, scratch_pool, scratch_pool)); 1303 1304 /* Did a conflict occur during the commit process? */ 1305 if (conflict_path != NULL) 1306 return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL, 1307 _("Conflict at '%s'"), conflict_path); 1308 1309 /* Since did not receive an error during the commit process, and no 1310 conflict was specified... we committed a revision. Run the hooks. 1311 Other errors may have occurred within the FS (specified by the 1312 POST_COMMIT_ERR localvar), but we need to run the hooks. */ 1313 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision)); 1314 err = svn_repos__hooks_post_commit(eb->repos, hooks_env, revision, 1315 eb->txn_name, scratch_pool); 1316 if (err) 1317 err = svn_error_create(SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err, 1318 _("Commit succeeded, but post-commit hook failed")); 1319 1320 /* Combine the FS errors with the hook errors, and stringify. */ 1321 err = svn_error_compose_create(post_commit_err, err); 1322 if (err) 1323 { 1324 post_commit_errstr = svn_repos__post_commit_error_str(err, scratch_pool); 1325 svn_error_clear(err); 1326 } 1327 else 1328 { 1329 post_commit_errstr = NULL; 1330 } 1331 1332 return svn_error_trace(invoke_commit_cb(eb->commit_cb, eb->commit_baton, 1333 eb->repos->fs, revision, 1334 post_commit_errstr, 1335 scratch_pool)); 1336} 1337 1338 1339/* This implements svn_editor_cb_abort_t */ 1340static svn_error_t * 1341abort_cb(void *baton, 1342 apr_pool_t *scratch_pool) 1343{ 1344 struct ev2_baton *eb = baton; 1345 1346 SVN_ERR(svn_editor_abort(eb->inner)); 1347 return SVN_NO_ERROR; 1348} 1349 1350 1351static svn_error_t * 1352apply_revprops(svn_fs_t *fs, 1353 const char *txn_name, 1354 apr_hash_t *revprops, 1355 apr_pool_t *scratch_pool) 1356{ 1357 svn_fs_txn_t *txn; 1358 const apr_array_header_t *revprops_array; 1359 1360 /* The FS editor has a TXN inside it, but we can't access it. Open another 1361 based on the TXN_NAME. */ 1362 SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, scratch_pool)); 1363 1364 /* Validate and apply the revision properties. */ 1365 revprops_array = svn_prop_hash_to_array(revprops, scratch_pool); 1366 SVN_ERR(svn_repos_fs_change_txn_props(txn, revprops_array, scratch_pool)); 1367 1368 /* ### do we need to force the txn to close, or is it enough to wait 1369 ### for the pool to be cleared? */ 1370 return SVN_NO_ERROR; 1371} 1372 1373 1374svn_error_t * 1375svn_repos__get_commit_ev2(svn_editor_t **editor, 1376 svn_repos_t *repos, 1377 svn_authz_t *authz, 1378 const char *authz_repos_name, 1379 const char *authz_user, 1380 apr_hash_t *revprops, 1381 svn_commit_callback2_t commit_cb, 1382 void *commit_baton, 1383 svn_cancel_func_t cancel_func, 1384 void *cancel_baton, 1385 apr_pool_t *result_pool, 1386 apr_pool_t *scratch_pool) 1387{ 1388 static const svn_editor_cb_many_t editor_cbs = { 1389 add_directory_cb, 1390 add_file_cb, 1391 add_symlink_cb, 1392 add_absent_cb, 1393 alter_directory_cb, 1394 alter_file_cb, 1395 alter_symlink_cb, 1396 delete_cb, 1397 copy_cb, 1398 move_cb, 1399 complete_cb, 1400 abort_cb 1401 }; 1402 struct ev2_baton *eb; 1403 const svn_string_t *author; 1404 apr_hash_t *hooks_env; 1405 1406 /* Parse the hooks-env file (if any). */ 1407 SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path, 1408 scratch_pool, scratch_pool)); 1409 1410 /* Can the user modify the repository at all? */ 1411 /* ### check against AUTHZ. */ 1412 1413 author = svn_hash_gets(revprops, SVN_PROP_REVISION_AUTHOR); 1414 1415 eb = apr_palloc(result_pool, sizeof(*eb)); 1416 eb->repos = repos; 1417 eb->authz = authz; 1418 eb->authz_repos_name = authz_repos_name; 1419 eb->authz_user = authz_user; 1420 eb->commit_cb = commit_cb; 1421 eb->commit_baton = commit_baton; 1422 1423 SVN_ERR(svn_fs__editor_create(&eb->inner, &eb->txn_name, 1424 repos->fs, SVN_FS_TXN_CHECK_LOCKS, 1425 cancel_func, cancel_baton, 1426 result_pool, scratch_pool)); 1427 1428 /* The TXN has been created. Go ahead and apply all revision properties. */ 1429 SVN_ERR(apply_revprops(repos->fs, eb->txn_name, revprops, scratch_pool)); 1430 1431 /* Okay... some access is allowed. Let's run the start-commit hook. */ 1432 SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env, 1433 author ? author->data : NULL, 1434 repos->client_capabilities, 1435 eb->txn_name, scratch_pool)); 1436 1437 /* Wrap the FS editor within our editor. */ 1438 SVN_ERR(svn_editor_create(editor, eb, cancel_func, cancel_baton, 1439 result_pool, scratch_pool)); 1440 SVN_ERR(svn_editor_setcb_many(*editor, &editor_cbs, scratch_pool)); 1441 1442 return SVN_NO_ERROR; 1443} 1444