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