ra_plugin.c revision 299742
1/* 2 * ra_plugin.c : the main RA module for local repository access 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24#include "ra_local.h" 25#include "svn_hash.h" 26#include "svn_ra.h" 27#include "svn_fs.h" 28#include "svn_delta.h" 29#include "svn_repos.h" 30#include "svn_pools.h" 31#include "svn_time.h" 32#include "svn_props.h" 33#include "svn_mergeinfo.h" 34#include "svn_path.h" 35#include "svn_version.h" 36#include "svn_cache_config.h" 37 38#include "svn_private_config.h" 39#include "../libsvn_ra/ra_loader.h" 40#include "private/svn_mergeinfo_private.h" 41#include "private/svn_repos_private.h" 42#include "private/svn_fspath.h" 43#include "private/svn_atomic.h" 44#include "private/svn_subr_private.h" 45 46#define APR_WANT_STRFUNC 47#include <apr_want.h> 48 49/*----------------------------------------------------------------*/ 50 51/*** Miscellaneous helper functions ***/ 52 53 54/* Pool cleanup handler: ensure that the access descriptor of the 55 filesystem (svn_fs_t *) DATA is set to NULL. */ 56static apr_status_t 57cleanup_access(void *data) 58{ 59 svn_error_t *serr; 60 svn_fs_t *fs = data; 61 62 serr = svn_fs_set_access(fs, NULL); 63 64 if (serr) 65 { 66 apr_status_t apr_err = serr->apr_err; 67 svn_error_clear(serr); 68 return apr_err; 69 } 70 71 return APR_SUCCESS; 72} 73 74 75/* Fetch a username for use with SESSION, and store it in SESSION->username. 76 * 77 * Allocate the username in SESSION->pool. Use SCRATCH_POOL for temporary 78 * allocations. */ 79static svn_error_t * 80get_username(svn_ra_session_t *session, 81 apr_pool_t *scratch_pool) 82{ 83 svn_ra_local__session_baton_t *sess = session->priv; 84 85 /* If we've already found the username don't ask for it again. */ 86 if (! sess->username) 87 { 88 /* Get a username somehow, so we have some svn:author property to 89 attach to a commit. */ 90 if (sess->auth_baton) 91 { 92 void *creds; 93 svn_auth_cred_username_t *username_creds; 94 svn_auth_iterstate_t *iterstate; 95 96 SVN_ERR(svn_auth_first_credentials(&creds, &iterstate, 97 SVN_AUTH_CRED_USERNAME, 98 sess->uuid, /* realmstring */ 99 sess->auth_baton, 100 scratch_pool)); 101 102 /* No point in calling next_creds(), since that assumes that the 103 first_creds() somehow failed to authenticate. But there's no 104 challenge going on, so we use whatever creds we get back on 105 the first try. */ 106 username_creds = creds; 107 if (username_creds && username_creds->username) 108 { 109 sess->username = apr_pstrdup(session->pool, 110 username_creds->username); 111 svn_error_clear(svn_auth_save_credentials(iterstate, 112 scratch_pool)); 113 } 114 else 115 sess->username = ""; 116 } 117 else 118 sess->username = ""; 119 } 120 121 /* If we have a real username, attach it to the filesystem so that it can 122 be used to validate locks. Even if there already is a user context 123 associated, it may contain irrelevant lock tokens, so always create a new. 124 */ 125 if (*sess->username) 126 { 127 svn_fs_access_t *access_ctx; 128 129 SVN_ERR(svn_fs_create_access(&access_ctx, sess->username, 130 session->pool)); 131 SVN_ERR(svn_fs_set_access(sess->fs, access_ctx)); 132 133 /* Make sure this context is disassociated when the pool gets 134 destroyed. */ 135 apr_pool_cleanup_register(session->pool, sess->fs, cleanup_access, 136 apr_pool_cleanup_null); 137 } 138 139 return SVN_NO_ERROR; 140} 141 142/* Implements an svn_atomic__init_once callback. Sets the FSFS memory 143 cache size. */ 144static svn_error_t * 145cache_init(void *baton, apr_pool_t *pool) 146{ 147 apr_hash_t *config_hash = baton; 148 svn_config_t *config = NULL; 149 const char *memory_cache_size_str; 150 151 if (config_hash) 152 config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG); 153 svn_config_get(config, &memory_cache_size_str, SVN_CONFIG_SECTION_MISCELLANY, 154 SVN_CONFIG_OPTION_MEMORY_CACHE_SIZE, NULL); 155 if (memory_cache_size_str) 156 { 157 apr_uint64_t memory_cache_size; 158 svn_cache_config_t settings = *svn_cache_config_get(); 159 160 SVN_ERR(svn_error_quick_wrap(svn_cstring_atoui64(&memory_cache_size, 161 memory_cache_size_str), 162 _("memory-cache-size invalid"))); 163 settings.cache_size = 1024 * 1024 * memory_cache_size; 164 svn_cache_config_set(&settings); 165 } 166 167 return SVN_NO_ERROR; 168} 169 170/*----------------------------------------------------------------*/ 171 172/*** The reporter vtable needed by do_update() and friends ***/ 173 174typedef struct reporter_baton_t 175{ 176 svn_ra_local__session_baton_t *sess; 177 void *report_baton; 178 179} reporter_baton_t; 180 181 182static void * 183make_reporter_baton(svn_ra_local__session_baton_t *sess, 184 void *report_baton, 185 apr_pool_t *pool) 186{ 187 reporter_baton_t *rbaton = apr_palloc(pool, sizeof(*rbaton)); 188 rbaton->sess = sess; 189 rbaton->report_baton = report_baton; 190 return rbaton; 191} 192 193 194static svn_error_t * 195reporter_set_path(void *reporter_baton, 196 const char *path, 197 svn_revnum_t revision, 198 svn_depth_t depth, 199 svn_boolean_t start_empty, 200 const char *lock_token, 201 apr_pool_t *pool) 202{ 203 reporter_baton_t *rbaton = reporter_baton; 204 return svn_repos_set_path3(rbaton->report_baton, path, 205 revision, depth, start_empty, lock_token, pool); 206} 207 208 209static svn_error_t * 210reporter_delete_path(void *reporter_baton, 211 const char *path, 212 apr_pool_t *pool) 213{ 214 reporter_baton_t *rbaton = reporter_baton; 215 return svn_repos_delete_path(rbaton->report_baton, path, pool); 216} 217 218 219static svn_error_t * 220reporter_link_path(void *reporter_baton, 221 const char *path, 222 const char *url, 223 svn_revnum_t revision, 224 svn_depth_t depth, 225 svn_boolean_t start_empty, 226 const char *lock_token, 227 apr_pool_t *pool) 228{ 229 reporter_baton_t *rbaton = reporter_baton; 230 const char *repos_url = rbaton->sess->repos_url; 231 const char *relpath = svn_uri_skip_ancestor(repos_url, url, pool); 232 const char *fs_path; 233 234 if (!relpath) 235 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 236 _("'%s'\n" 237 "is not the same repository as\n" 238 "'%s'"), url, rbaton->sess->repos_url); 239 240 /* Convert the relpath to an fspath */ 241 if (relpath[0] == '\0') 242 fs_path = "/"; 243 else 244 fs_path = apr_pstrcat(pool, "/", relpath, SVN_VA_NULL); 245 246 return svn_repos_link_path3(rbaton->report_baton, path, fs_path, revision, 247 depth, start_empty, lock_token, pool); 248} 249 250 251static svn_error_t * 252reporter_finish_report(void *reporter_baton, 253 apr_pool_t *pool) 254{ 255 reporter_baton_t *rbaton = reporter_baton; 256 return svn_repos_finish_report(rbaton->report_baton, pool); 257} 258 259 260static svn_error_t * 261reporter_abort_report(void *reporter_baton, 262 apr_pool_t *pool) 263{ 264 reporter_baton_t *rbaton = reporter_baton; 265 return svn_repos_abort_report(rbaton->report_baton, pool); 266} 267 268 269static const svn_ra_reporter3_t ra_local_reporter = 270{ 271 reporter_set_path, 272 reporter_delete_path, 273 reporter_link_path, 274 reporter_finish_report, 275 reporter_abort_report 276}; 277 278 279/* ... 280 * 281 * Wrap a cancellation editor using SESSION's cancellation function around 282 * the supplied EDITOR. ### Some callers (via svn_ra_do_update2() etc.) 283 * don't appear to know that we do this, and are supplying an editor that 284 * they have already wrapped with the same cancellation editor, so it ends 285 * up double-wrapped. 286 * 287 * Allocate @a *reporter and @a *report_baton in @a result_pool. Use 288 * @a scratch_pool for temporary allocations. 289 */ 290static svn_error_t * 291make_reporter(svn_ra_session_t *session, 292 const svn_ra_reporter3_t **reporter, 293 void **report_baton, 294 svn_revnum_t revision, 295 const char *target, 296 const char *other_url, 297 svn_boolean_t text_deltas, 298 svn_depth_t depth, 299 svn_boolean_t send_copyfrom_args, 300 svn_boolean_t ignore_ancestry, 301 const svn_delta_editor_t *editor, 302 void *edit_baton, 303 apr_pool_t *result_pool, 304 apr_pool_t *scratch_pool) 305{ 306 svn_ra_local__session_baton_t *sess = session->priv; 307 void *rbaton; 308 const char *other_fs_path = NULL; 309 310 /* Get the HEAD revision if one is not supplied. */ 311 if (! SVN_IS_VALID_REVNUM(revision)) 312 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, scratch_pool)); 313 314 /* If OTHER_URL was provided, validate it and convert it into a 315 regular filesystem path. */ 316 if (other_url) 317 { 318 const char *other_relpath 319 = svn_uri_skip_ancestor(sess->repos_url, other_url, scratch_pool); 320 321 /* Sanity check: the other_url better be in the same repository as 322 the original session url! */ 323 if (! other_relpath) 324 return svn_error_createf 325 (SVN_ERR_RA_ILLEGAL_URL, NULL, 326 _("'%s'\n" 327 "is not the same repository as\n" 328 "'%s'"), other_url, sess->repos_url); 329 330 other_fs_path = apr_pstrcat(scratch_pool, "/", other_relpath, 331 SVN_VA_NULL); 332 } 333 334 /* Pass back our reporter */ 335 *reporter = &ra_local_reporter; 336 337 SVN_ERR(get_username(session, scratch_pool)); 338 339 if (sess->callbacks) 340 SVN_ERR(svn_delta_get_cancellation_editor(sess->callbacks->cancel_func, 341 sess->callback_baton, 342 editor, 343 edit_baton, 344 &editor, 345 &edit_baton, 346 result_pool)); 347 348 /* Build a reporter baton. */ 349 SVN_ERR(svn_repos_begin_report3(&rbaton, 350 revision, 351 sess->repos, 352 sess->fs_path->data, 353 target, 354 other_fs_path, 355 text_deltas, 356 depth, 357 ignore_ancestry, 358 send_copyfrom_args, 359 editor, 360 edit_baton, 361 NULL, 362 NULL, 363 0, /* Disable zero-copy codepath, because 364 RA API users are unaware about the 365 zero-copy code path limitation (do 366 not access FSFS data structures 367 and, hence, caches). See notes 368 to svn_repos_begin_report3() for 369 additional details. */ 370 result_pool)); 371 372 /* Wrap the report baton given us by the repos layer with our own 373 reporter baton. */ 374 *report_baton = make_reporter_baton(sess, rbaton, result_pool); 375 376 return SVN_NO_ERROR; 377} 378 379 380/*----------------------------------------------------------------*/ 381 382/*** Deltification stuff for get_commit_editor() ***/ 383 384struct deltify_etc_baton 385{ 386 svn_fs_t *fs; /* the fs to deltify in */ 387 svn_repos_t *repos; /* repos for unlocking */ 388 const char *fspath_base; /* fs-path part of split session URL */ 389 390 apr_hash_t *lock_tokens; /* tokens to unlock, if any */ 391 392 svn_commit_callback2_t commit_cb; /* the original callback */ 393 void *commit_baton; /* the original callback's baton */ 394}; 395 396/* This implements 'svn_commit_callback_t'. Its invokes the original 397 (wrapped) callback, but also does deltification on the new revision and 398 possibly unlocks committed paths. 399 BATON is 'struct deltify_etc_baton *'. */ 400static svn_error_t * 401deltify_etc(const svn_commit_info_t *commit_info, 402 void *baton, 403 apr_pool_t *scratch_pool) 404{ 405 struct deltify_etc_baton *deb = baton; 406 svn_error_t *err1 = SVN_NO_ERROR; 407 svn_error_t *err2; 408 409 /* Invoke the original callback first, in case someone's waiting to 410 know the revision number so they can go off and annotate an 411 issue or something. */ 412 if (deb->commit_cb) 413 err1 = deb->commit_cb(commit_info, deb->commit_baton, scratch_pool); 414 415 /* Maybe unlock the paths. */ 416 if (deb->lock_tokens) 417 { 418 apr_pool_t *subpool = svn_pool_create(scratch_pool); 419 apr_hash_t *targets = apr_hash_make(subpool); 420 apr_hash_index_t *hi; 421 422 for (hi = apr_hash_first(subpool, deb->lock_tokens); hi; 423 hi = apr_hash_next(hi)) 424 { 425 const void *relpath = apr_hash_this_key(hi); 426 const char *token = apr_hash_this_val(hi); 427 const char *fspath; 428 429 fspath = svn_fspath__join(deb->fspath_base, relpath, subpool); 430 svn_hash_sets(targets, fspath, token); 431 } 432 433 /* We may get errors here if the lock was broken or stolen 434 after the commit succeeded. This is fine and should be 435 ignored. */ 436 svn_error_clear(svn_repos_fs_unlock_many(deb->repos, targets, FALSE, 437 NULL, NULL, 438 subpool, subpool)); 439 440 svn_pool_destroy(subpool); 441 } 442 443 /* But, deltification shouldn't be stopped just because someone's 444 random callback failed, so proceed unconditionally on to 445 deltification. */ 446 err2 = svn_fs_deltify_revision(deb->fs, commit_info->revision, scratch_pool); 447 448 return svn_error_compose_create(err1, err2); 449} 450 451 452/* If LOCK_TOKENS is not NULL, then copy all tokens into the access context 453 of FS. The tokens' paths will be prepended with FSPATH_BASE. 454 455 ACCESS_POOL must match (or exceed) the lifetime of the access context 456 that was associated with FS. Typically, this is the session pool. 457 458 Temporary allocations are made in SCRATCH_POOL. */ 459static svn_error_t * 460apply_lock_tokens(svn_fs_t *fs, 461 const char *fspath_base, 462 apr_hash_t *lock_tokens, 463 apr_pool_t *access_pool, 464 apr_pool_t *scratch_pool) 465{ 466 if (lock_tokens) 467 { 468 svn_fs_access_t *access_ctx; 469 470 SVN_ERR(svn_fs_get_access(&access_ctx, fs)); 471 472 /* If there is no access context, the filesystem will scream if a 473 lock is needed. */ 474 if (access_ctx) 475 { 476 apr_hash_index_t *hi; 477 478 /* Note: we have no use for an iterpool here since the data 479 within the loop is copied into ACCESS_POOL. */ 480 481 for (hi = apr_hash_first(scratch_pool, lock_tokens); hi; 482 hi = apr_hash_next(hi)) 483 { 484 const void *relpath = apr_hash_this_key(hi); 485 const char *token = apr_hash_this_val(hi); 486 const char *fspath; 487 488 /* The path needs to live as long as ACCESS_CTX. */ 489 fspath = svn_fspath__join(fspath_base, relpath, access_pool); 490 491 /* The token must live as long as ACCESS_CTX. */ 492 token = apr_pstrdup(access_pool, token); 493 494 SVN_ERR(svn_fs_access_add_lock_token2(access_ctx, fspath, 495 token)); 496 } 497 } 498 } 499 500 return SVN_NO_ERROR; 501} 502 503 504/*----------------------------------------------------------------*/ 505 506/*** The RA vtable routines ***/ 507 508#define RA_LOCAL_DESCRIPTION \ 509 N_("Module for accessing a repository on local disk.") 510 511static const char * 512svn_ra_local__get_description(apr_pool_t *pool) 513{ 514 return _(RA_LOCAL_DESCRIPTION); 515} 516 517static const char * const * 518svn_ra_local__get_schemes(apr_pool_t *pool) 519{ 520 static const char *schemes[] = { "file", NULL }; 521 522 return schemes; 523} 524 525/* Do nothing. 526 * 527 * Why is this acceptable? FS warnings used to be used for only 528 * two things: failures to close BDB repositories and failures to 529 * interact with memcached in FSFS (new in 1.6). In 1.5 and earlier, 530 * we did not call svn_fs_set_warning_func in ra_local, which means 531 * that any BDB-closing failure would have led to abort()s; the fact 532 * that this hasn't led to huge hues and cries makes it seem likely 533 * that this just doesn't happen that often, at least not through 534 * ra_local. And as far as memcached goes, it seems unlikely that 535 * somebody is going to go through the trouble of setting up and 536 * running memcached servers but then use ra_local access. So we 537 * ignore errors here, so that memcached can use the FS warnings API 538 * without crashing ra_local. 539 */ 540static void 541ignore_warnings(void *baton, 542 svn_error_t *err) 543{ 544#ifdef SVN_DEBUG 545 SVN_DBG(("Ignoring FS warning %s\n", 546 svn_error_symbolic_name(err ? err->apr_err : 0))); 547#endif 548 return; 549} 550 551#define USER_AGENT "SVN/" SVN_VER_NUMBER " (" SVN_BUILD_TARGET ")" \ 552 " ra_local" 553 554static svn_error_t * 555svn_ra_local__open(svn_ra_session_t *session, 556 const char **corrected_url, 557 const char *repos_URL, 558 const svn_ra_callbacks2_t *callbacks, 559 void *callback_baton, 560 svn_auth_baton_t *auth_baton, 561 apr_hash_t *config, 562 apr_pool_t *result_pool, 563 apr_pool_t *scratch_pool) 564{ 565 const char *client_string; 566 svn_ra_local__session_baton_t *sess; 567 const char *fs_path; 568 static volatile svn_atomic_t cache_init_state = 0; 569 apr_pool_t *pool = result_pool; 570 571 /* Initialise the FSFS memory cache size. We can only do this once 572 so one CONFIG will win the race and all others will be ignored 573 silently. */ 574 SVN_ERR(svn_atomic__init_once(&cache_init_state, cache_init, config, pool)); 575 576 /* We don't support redirections in ra-local. */ 577 if (corrected_url) 578 *corrected_url = NULL; 579 580 /* Allocate and stash the session_sess args we have already. */ 581 sess = apr_pcalloc(pool, sizeof(*sess)); 582 sess->callbacks = callbacks; 583 sess->callback_baton = callback_baton; 584 sess->auth_baton = auth_baton; 585 586 /* Look through the URL, figure out which part points to the 587 repository, and which part is the path *within* the 588 repository. */ 589 SVN_ERR(svn_ra_local__split_URL(&(sess->repos), 590 &(sess->repos_url), 591 &fs_path, 592 repos_URL, 593 session->pool)); 594 sess->fs_path = svn_stringbuf_create(fs_path, session->pool); 595 596 /* Cache the filesystem object from the repos here for 597 convenience. */ 598 sess->fs = svn_repos_fs(sess->repos); 599 600 /* Ignore FS warnings. */ 601 svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL); 602 603 /* Cache the repository UUID as well */ 604 SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool)); 605 606 /* Be sure username is NULL so we know to look it up / ask for it */ 607 sess->username = NULL; 608 609 if (sess->callbacks->get_client_string != NULL) 610 SVN_ERR(sess->callbacks->get_client_string(sess->callback_baton, 611 &client_string, pool)); 612 else 613 client_string = NULL; 614 615 if (client_string) 616 sess->useragent = apr_pstrcat(pool, USER_AGENT " ", 617 client_string, SVN_VA_NULL); 618 else 619 sess->useragent = USER_AGENT; 620 621 session->priv = sess; 622 return SVN_NO_ERROR; 623} 624 625static svn_error_t * 626svn_ra_local__dup_session(svn_ra_session_t *new_session, 627 svn_ra_session_t *session, 628 const char *new_session_url, 629 apr_pool_t *result_pool, 630 apr_pool_t *scratch_pool) 631{ 632 svn_ra_local__session_baton_t *old_sess = session->priv; 633 svn_ra_local__session_baton_t *new_sess; 634 const char *fs_path; 635 636 /* Allocate and stash the session_sess args we have already. */ 637 new_sess = apr_pcalloc(result_pool, sizeof(*new_sess)); 638 new_sess->callbacks = old_sess->callbacks; 639 new_sess->callback_baton = old_sess->callback_baton; 640 641 /* ### Re-use existing FS handle? */ 642 643 /* Reuse existing code */ 644 SVN_ERR(svn_ra_local__split_URL(&(new_sess->repos), 645 &(new_sess->repos_url), 646 &fs_path, 647 new_session_url, 648 result_pool)); 649 650 new_sess->fs_path = svn_stringbuf_create(fs_path, result_pool); 651 652 /* Cache the filesystem object from the repos here for 653 convenience. */ 654 new_sess->fs = svn_repos_fs(new_sess->repos); 655 656 /* Ignore FS warnings. */ 657 svn_fs_set_warning_func(new_sess->fs, ignore_warnings, NULL); 658 659 /* Cache the repository UUID as well */ 660 new_sess->uuid = apr_pstrdup(result_pool, old_sess->uuid); 661 662 new_sess->username = old_sess->username 663 ? apr_pstrdup(result_pool, old_sess->username) 664 : NULL; 665 666 new_sess->useragent = apr_pstrdup(result_pool, old_sess->useragent); 667 new_session->priv = new_sess; 668 669 return SVN_NO_ERROR; 670} 671 672static svn_error_t * 673svn_ra_local__reparent(svn_ra_session_t *session, 674 const char *url, 675 apr_pool_t *pool) 676{ 677 svn_ra_local__session_baton_t *sess = session->priv; 678 const char *relpath = svn_uri_skip_ancestor(sess->repos_url, url, pool); 679 680 /* If the new URL isn't the same as our repository root URL, then 681 let's ensure that it's some child of it. */ 682 if (! relpath) 683 return svn_error_createf 684 (SVN_ERR_RA_ILLEGAL_URL, NULL, 685 _("URL '%s' is not a child of the session's repository root " 686 "URL '%s'"), url, sess->repos_url); 687 688 /* Update our FS_PATH sess member to point to our new 689 relative-URL-turned-absolute-filesystem-path. */ 690 svn_stringbuf_set(sess->fs_path, 691 svn_fspath__canonicalize(relpath, pool)); 692 693 return SVN_NO_ERROR; 694} 695 696static svn_error_t * 697svn_ra_local__get_session_url(svn_ra_session_t *session, 698 const char **url, 699 apr_pool_t *pool) 700{ 701 svn_ra_local__session_baton_t *sess = session->priv; 702 *url = svn_path_url_add_component2(sess->repos_url, 703 sess->fs_path->data + 1, 704 pool); 705 return SVN_NO_ERROR; 706} 707 708static svn_error_t * 709svn_ra_local__get_latest_revnum(svn_ra_session_t *session, 710 svn_revnum_t *latest_revnum, 711 apr_pool_t *pool) 712{ 713 svn_ra_local__session_baton_t *sess = session->priv; 714 return svn_fs_youngest_rev(latest_revnum, sess->fs, pool); 715} 716 717static svn_error_t * 718svn_ra_local__get_file_revs(svn_ra_session_t *session, 719 const char *path, 720 svn_revnum_t start, 721 svn_revnum_t end, 722 svn_boolean_t include_merged_revisions, 723 svn_file_rev_handler_t handler, 724 void *handler_baton, 725 apr_pool_t *pool) 726{ 727 svn_ra_local__session_baton_t *sess = session->priv; 728 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 729 return svn_repos_get_file_revs2(sess->repos, abs_path, start, end, 730 include_merged_revisions, NULL, NULL, 731 handler, handler_baton, pool); 732} 733 734static svn_error_t * 735svn_ra_local__get_dated_revision(svn_ra_session_t *session, 736 svn_revnum_t *revision, 737 apr_time_t tm, 738 apr_pool_t *pool) 739{ 740 svn_ra_local__session_baton_t *sess = session->priv; 741 return svn_repos_dated_revision(revision, sess->repos, tm, pool); 742} 743 744 745static svn_error_t * 746svn_ra_local__change_rev_prop(svn_ra_session_t *session, 747 svn_revnum_t rev, 748 const char *name, 749 const svn_string_t *const *old_value_p, 750 const svn_string_t *value, 751 apr_pool_t *pool) 752{ 753 svn_ra_local__session_baton_t *sess = session->priv; 754 755 SVN_ERR(get_username(session, pool)); 756 return svn_repos_fs_change_rev_prop4(sess->repos, rev, sess->username, 757 name, old_value_p, value, TRUE, TRUE, 758 NULL, NULL, pool); 759} 760 761static svn_error_t * 762svn_ra_local__get_uuid(svn_ra_session_t *session, 763 const char **uuid, 764 apr_pool_t *pool) 765{ 766 svn_ra_local__session_baton_t *sess = session->priv; 767 *uuid = sess->uuid; 768 return SVN_NO_ERROR; 769} 770 771static svn_error_t * 772svn_ra_local__get_repos_root(svn_ra_session_t *session, 773 const char **url, 774 apr_pool_t *pool) 775{ 776 svn_ra_local__session_baton_t *sess = session->priv; 777 *url = sess->repos_url; 778 return SVN_NO_ERROR; 779} 780 781static svn_error_t * 782svn_ra_local__rev_proplist(svn_ra_session_t *session, 783 svn_revnum_t rev, 784 apr_hash_t **props, 785 apr_pool_t *pool) 786{ 787 svn_ra_local__session_baton_t *sess = session->priv; 788 return svn_repos_fs_revision_proplist(props, sess->repos, rev, 789 NULL, NULL, pool); 790} 791 792static svn_error_t * 793svn_ra_local__rev_prop(svn_ra_session_t *session, 794 svn_revnum_t rev, 795 const char *name, 796 svn_string_t **value, 797 apr_pool_t *pool) 798{ 799 svn_ra_local__session_baton_t *sess = session->priv; 800 return svn_repos_fs_revision_prop(value, sess->repos, rev, name, 801 NULL, NULL, pool); 802} 803 804struct ccw_baton 805{ 806 svn_commit_callback2_t original_callback; 807 void *original_baton; 808 809 svn_ra_session_t *session; 810}; 811 812/* Wrapper which populates the repos_root field of the commit_info struct */ 813static svn_error_t * 814commit_callback_wrapper(const svn_commit_info_t *commit_info, 815 void *baton, 816 apr_pool_t *scratch_pool) 817{ 818 struct ccw_baton *ccwb = baton; 819 svn_commit_info_t *ci = svn_commit_info_dup(commit_info, scratch_pool); 820 821 SVN_ERR(svn_ra_local__get_repos_root(ccwb->session, &ci->repos_root, 822 scratch_pool)); 823 824 return svn_error_trace(ccwb->original_callback(ci, ccwb->original_baton, 825 scratch_pool)); 826} 827 828 829/* The repository layer does not correctly fill in REPOS_ROOT in 830 commit_info, as it doesn't know the url that is used to access 831 it. This hooks the callback to fill in the missing pieces. */ 832static void 833remap_commit_callback(svn_commit_callback2_t *callback, 834 void **callback_baton, 835 svn_ra_session_t *session, 836 svn_commit_callback2_t original_callback, 837 void *original_baton, 838 apr_pool_t *result_pool) 839{ 840 if (original_callback == NULL) 841 { 842 *callback = NULL; 843 *callback_baton = NULL; 844 } 845 else 846 { 847 /* Allocate this in RESULT_POOL, since the callback will be called 848 long after this function has returned. */ 849 struct ccw_baton *ccwb = apr_palloc(result_pool, sizeof(*ccwb)); 850 851 ccwb->session = session; 852 ccwb->original_callback = original_callback; 853 ccwb->original_baton = original_baton; 854 855 *callback = commit_callback_wrapper; 856 *callback_baton = ccwb; 857 } 858} 859 860static svn_error_t * 861svn_ra_local__get_commit_editor(svn_ra_session_t *session, 862 const svn_delta_editor_t **editor, 863 void **edit_baton, 864 apr_hash_t *revprop_table, 865 svn_commit_callback2_t callback, 866 void *callback_baton, 867 apr_hash_t *lock_tokens, 868 svn_boolean_t keep_locks, 869 apr_pool_t *pool) 870{ 871 svn_ra_local__session_baton_t *sess = session->priv; 872 struct deltify_etc_baton *deb = apr_palloc(pool, sizeof(*deb)); 873 874 /* Set repos_root_url in commit info */ 875 remap_commit_callback(&callback, &callback_baton, session, 876 callback, callback_baton, pool); 877 878 /* Prepare the baton for deltify_etc() */ 879 deb->fs = sess->fs; 880 deb->repos = sess->repos; 881 deb->fspath_base = sess->fs_path->data; 882 if (! keep_locks) 883 deb->lock_tokens = lock_tokens; 884 else 885 deb->lock_tokens = NULL; 886 deb->commit_cb = callback; 887 deb->commit_baton = callback_baton; 888 889 SVN_ERR(get_username(session, pool)); 890 891 /* If there are lock tokens to add, do so. */ 892 SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens, 893 session->pool, pool)); 894 895 /* Copy the revprops table so we can add the username. */ 896 revprop_table = apr_hash_copy(pool, revprop_table); 897 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR, 898 svn_string_create(sess->username, pool)); 899 svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION, 900 svn_string_create(SVN_VER_NUMBER, pool)); 901 svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT, 902 svn_string_create(sess->useragent, pool)); 903 904 /* Get the repos commit-editor */ 905 return svn_repos_get_commit_editor5 906 (editor, edit_baton, sess->repos, NULL, 907 svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data, 908 revprop_table, deltify_etc, deb, NULL, NULL, pool); 909} 910 911 912static svn_error_t * 913svn_ra_local__get_mergeinfo(svn_ra_session_t *session, 914 svn_mergeinfo_catalog_t *catalog, 915 const apr_array_header_t *paths, 916 svn_revnum_t revision, 917 svn_mergeinfo_inheritance_t inherit, 918 svn_boolean_t include_descendants, 919 apr_pool_t *pool) 920{ 921 svn_ra_local__session_baton_t *sess = session->priv; 922 svn_mergeinfo_catalog_t tmp_catalog; 923 int i; 924 apr_array_header_t *abs_paths = 925 apr_array_make(pool, 0, sizeof(const char *)); 926 927 for (i = 0; i < paths->nelts; i++) 928 { 929 const char *relative_path = APR_ARRAY_IDX(paths, i, const char *); 930 APR_ARRAY_PUSH(abs_paths, const char *) = 931 svn_fspath__join(sess->fs_path->data, relative_path, pool); 932 } 933 934 SVN_ERR(svn_repos_fs_get_mergeinfo(&tmp_catalog, sess->repos, abs_paths, 935 revision, inherit, include_descendants, 936 NULL, NULL, pool)); 937 if (apr_hash_count(tmp_catalog) > 0) 938 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog, 939 tmp_catalog, 940 sess->fs_path->data, 941 pool)); 942 else 943 *catalog = NULL; 944 945 return SVN_NO_ERROR; 946} 947 948 949static svn_error_t * 950svn_ra_local__do_update(svn_ra_session_t *session, 951 const svn_ra_reporter3_t **reporter, 952 void **report_baton, 953 svn_revnum_t update_revision, 954 const char *update_target, 955 svn_depth_t depth, 956 svn_boolean_t send_copyfrom_args, 957 svn_boolean_t ignore_ancestry, 958 const svn_delta_editor_t *update_editor, 959 void *update_baton, 960 apr_pool_t *result_pool, 961 apr_pool_t *scratch_pool) 962{ 963 return make_reporter(session, 964 reporter, 965 report_baton, 966 update_revision, 967 update_target, 968 NULL, 969 TRUE, 970 depth, 971 send_copyfrom_args, 972 ignore_ancestry, 973 update_editor, 974 update_baton, 975 result_pool, scratch_pool); 976} 977 978 979static svn_error_t * 980svn_ra_local__do_switch(svn_ra_session_t *session, 981 const svn_ra_reporter3_t **reporter, 982 void **report_baton, 983 svn_revnum_t update_revision, 984 const char *update_target, 985 svn_depth_t depth, 986 const char *switch_url, 987 svn_boolean_t send_copyfrom_args, 988 svn_boolean_t ignore_ancestry, 989 const svn_delta_editor_t *update_editor, 990 void *update_baton, 991 apr_pool_t *result_pool, 992 apr_pool_t *scratch_pool) 993{ 994 return make_reporter(session, 995 reporter, 996 report_baton, 997 update_revision, 998 update_target, 999 switch_url, 1000 TRUE /* text_deltas */, 1001 depth, 1002 send_copyfrom_args, 1003 ignore_ancestry, 1004 update_editor, 1005 update_baton, 1006 result_pool, scratch_pool); 1007} 1008 1009 1010static svn_error_t * 1011svn_ra_local__do_status(svn_ra_session_t *session, 1012 const svn_ra_reporter3_t **reporter, 1013 void **report_baton, 1014 const char *status_target, 1015 svn_revnum_t revision, 1016 svn_depth_t depth, 1017 const svn_delta_editor_t *status_editor, 1018 void *status_baton, 1019 apr_pool_t *pool) 1020{ 1021 return make_reporter(session, 1022 reporter, 1023 report_baton, 1024 revision, 1025 status_target, 1026 NULL, 1027 FALSE, 1028 depth, 1029 FALSE, 1030 FALSE, 1031 status_editor, 1032 status_baton, 1033 pool, pool); 1034} 1035 1036 1037static svn_error_t * 1038svn_ra_local__do_diff(svn_ra_session_t *session, 1039 const svn_ra_reporter3_t **reporter, 1040 void **report_baton, 1041 svn_revnum_t update_revision, 1042 const char *update_target, 1043 svn_depth_t depth, 1044 svn_boolean_t ignore_ancestry, 1045 svn_boolean_t text_deltas, 1046 const char *switch_url, 1047 const svn_delta_editor_t *update_editor, 1048 void *update_baton, 1049 apr_pool_t *pool) 1050{ 1051 return make_reporter(session, 1052 reporter, 1053 report_baton, 1054 update_revision, 1055 update_target, 1056 switch_url, 1057 text_deltas, 1058 depth, 1059 FALSE, 1060 ignore_ancestry, 1061 update_editor, 1062 update_baton, 1063 pool, pool); 1064} 1065 1066 1067struct log_baton 1068{ 1069 svn_ra_local__session_baton_t *sess; 1070 svn_log_entry_receiver_t real_cb; 1071 void *real_baton; 1072}; 1073 1074static svn_error_t * 1075log_receiver_wrapper(void *baton, 1076 svn_log_entry_t *log_entry, 1077 apr_pool_t *pool) 1078{ 1079 struct log_baton *b = baton; 1080 svn_ra_local__session_baton_t *sess = b->sess; 1081 1082 if (sess->callbacks->cancel_func) 1083 SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton)); 1084 1085 /* For consistency with the other RA layers, replace an empty 1086 changed-paths hash with a NULL one. 1087 1088 ### Should this be done by svn_ra_get_log2() instead, then? */ 1089 if ((log_entry->changed_paths2) 1090 && (apr_hash_count(log_entry->changed_paths2) == 0)) 1091 { 1092 log_entry->changed_paths = NULL; 1093 log_entry->changed_paths2 = NULL; 1094 } 1095 1096 return b->real_cb(b->real_baton, log_entry, pool); 1097} 1098 1099 1100static svn_error_t * 1101svn_ra_local__get_log(svn_ra_session_t *session, 1102 const apr_array_header_t *paths, 1103 svn_revnum_t start, 1104 svn_revnum_t end, 1105 int limit, 1106 svn_boolean_t discover_changed_paths, 1107 svn_boolean_t strict_node_history, 1108 svn_boolean_t include_merged_revisions, 1109 const apr_array_header_t *revprops, 1110 svn_log_entry_receiver_t receiver, 1111 void *receiver_baton, 1112 apr_pool_t *pool) 1113{ 1114 svn_ra_local__session_baton_t *sess = session->priv; 1115 struct log_baton lb; 1116 apr_array_header_t *abs_paths = 1117 apr_array_make(pool, 0, sizeof(const char *)); 1118 1119 if (paths) 1120 { 1121 int i; 1122 1123 for (i = 0; i < paths->nelts; i++) 1124 { 1125 const char *relative_path = APR_ARRAY_IDX(paths, i, const char *); 1126 APR_ARRAY_PUSH(abs_paths, const char *) = 1127 svn_fspath__join(sess->fs_path->data, relative_path, pool); 1128 } 1129 } 1130 1131 lb.real_cb = receiver; 1132 lb.real_baton = receiver_baton; 1133 lb.sess = sess; 1134 receiver = log_receiver_wrapper; 1135 receiver_baton = &lb; 1136 1137 return svn_repos_get_logs4(sess->repos, 1138 abs_paths, 1139 start, 1140 end, 1141 limit, 1142 discover_changed_paths, 1143 strict_node_history, 1144 include_merged_revisions, 1145 revprops, 1146 NULL, NULL, 1147 receiver, 1148 receiver_baton, 1149 pool); 1150} 1151 1152 1153static svn_error_t * 1154svn_ra_local__do_check_path(svn_ra_session_t *session, 1155 const char *path, 1156 svn_revnum_t revision, 1157 svn_node_kind_t *kind, 1158 apr_pool_t *pool) 1159{ 1160 svn_ra_local__session_baton_t *sess = session->priv; 1161 svn_fs_root_t *root; 1162 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1163 1164 if (! SVN_IS_VALID_REVNUM(revision)) 1165 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool)); 1166 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1167 return svn_fs_check_path(kind, root, abs_path, pool); 1168} 1169 1170 1171static svn_error_t * 1172svn_ra_local__stat(svn_ra_session_t *session, 1173 const char *path, 1174 svn_revnum_t revision, 1175 svn_dirent_t **dirent, 1176 apr_pool_t *pool) 1177{ 1178 svn_ra_local__session_baton_t *sess = session->priv; 1179 svn_fs_root_t *root; 1180 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1181 1182 if (! SVN_IS_VALID_REVNUM(revision)) 1183 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool)); 1184 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1185 1186 return svn_repos_stat(dirent, root, abs_path, pool); 1187} 1188 1189 1190 1191 1192/* Obtain the properties for a node, including its 'entry props */ 1193static svn_error_t * 1194get_node_props(apr_hash_t **props, 1195 svn_fs_root_t *root, 1196 const char *path, 1197 const char *uuid, 1198 apr_pool_t *result_pool, 1199 apr_pool_t *scratch_pool) 1200{ 1201 svn_revnum_t cmt_rev; 1202 const char *cmt_date, *cmt_author; 1203 1204 /* Create a hash with props attached to the fs node. */ 1205 SVN_ERR(svn_fs_node_proplist(props, root, path, result_pool)); 1206 1207 /* Now add some non-tweakable metadata to the hash as well... */ 1208 1209 /* The so-called 'entryprops' with info about CR & friends. */ 1210 SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date, 1211 &cmt_author, root, path, 1212 scratch_pool)); 1213 1214 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, 1215 svn_string_createf(result_pool, "%ld", cmt_rev)); 1216 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, cmt_date ? 1217 svn_string_create(cmt_date, result_pool) : NULL); 1218 svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, cmt_author ? 1219 svn_string_create(cmt_author, result_pool) : NULL); 1220 svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, 1221 svn_string_create(uuid, result_pool)); 1222 1223 /* We have no 'wcprops' in ra_local, but might someday. */ 1224 1225 return SVN_NO_ERROR; 1226} 1227 1228 1229/* Getting just one file. */ 1230static svn_error_t * 1231svn_ra_local__get_file(svn_ra_session_t *session, 1232 const char *path, 1233 svn_revnum_t revision, 1234 svn_stream_t *stream, 1235 svn_revnum_t *fetched_rev, 1236 apr_hash_t **props, 1237 apr_pool_t *pool) 1238{ 1239 svn_fs_root_t *root; 1240 svn_stream_t *contents; 1241 svn_revnum_t youngest_rev; 1242 svn_ra_local__session_baton_t *sess = session->priv; 1243 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1244 svn_node_kind_t node_kind; 1245 1246 /* Open the revision's root. */ 1247 if (! SVN_IS_VALID_REVNUM(revision)) 1248 { 1249 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool)); 1250 SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool)); 1251 if (fetched_rev != NULL) 1252 *fetched_rev = youngest_rev; 1253 } 1254 else 1255 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1256 1257 SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, pool)); 1258 if (node_kind == svn_node_none) 1259 { 1260 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1261 _("'%s' path not found"), abs_path); 1262 } 1263 else if (node_kind != svn_node_file) 1264 { 1265 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, 1266 _("'%s' is not a file"), abs_path); 1267 } 1268 1269 if (stream) 1270 { 1271 /* Get a stream representing the file's contents. */ 1272 SVN_ERR(svn_fs_file_contents(&contents, root, abs_path, pool)); 1273 1274 /* Now push data from the fs stream back at the caller's stream. 1275 Note that this particular RA layer does not computing a 1276 checksum as we go, and confirming it against the repository's 1277 checksum when done. That's because it calls 1278 svn_fs_file_contents() directly, which already checks the 1279 stored checksum, and all we're doing here is writing bytes in 1280 a loop. Truly, Nothing Can Go Wrong :-). But RA layers that 1281 go over a network should confirm the checksum. 1282 1283 Note: we are not supposed to close the passed-in stream, so 1284 disown the thing. 1285 */ 1286 SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(stream, pool), 1287 sess->callbacks 1288 ? sess->callbacks->cancel_func : NULL, 1289 sess->callback_baton, 1290 pool)); 1291 } 1292 1293 /* Handle props if requested. */ 1294 if (props) 1295 SVN_ERR(get_node_props(props, root, abs_path, sess->uuid, pool, pool)); 1296 1297 return SVN_NO_ERROR; 1298} 1299 1300 1301 1302/* Getting a directory's entries */ 1303static svn_error_t * 1304svn_ra_local__get_dir(svn_ra_session_t *session, 1305 apr_hash_t **dirents, 1306 svn_revnum_t *fetched_rev, 1307 apr_hash_t **props, 1308 const char *path, 1309 svn_revnum_t revision, 1310 apr_uint32_t dirent_fields, 1311 apr_pool_t *pool) 1312{ 1313 svn_fs_root_t *root; 1314 svn_revnum_t youngest_rev; 1315 apr_hash_t *entries; 1316 apr_hash_index_t *hi; 1317 svn_ra_local__session_baton_t *sess = session->priv; 1318 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1319 1320 /* Open the revision's root. */ 1321 if (! SVN_IS_VALID_REVNUM(revision)) 1322 { 1323 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool)); 1324 SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool)); 1325 if (fetched_rev != NULL) 1326 *fetched_rev = youngest_rev; 1327 } 1328 else 1329 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1330 1331 if (dirents) 1332 { 1333 apr_pool_t *iterpool = svn_pool_create(pool); 1334 /* Get the dir's entries. */ 1335 SVN_ERR(svn_fs_dir_entries(&entries, root, abs_path, pool)); 1336 1337 /* Loop over the fs dirents, and build a hash of general 1338 svn_dirent_t's. */ 1339 *dirents = apr_hash_make(pool); 1340 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 1341 { 1342 const void *key; 1343 void *val; 1344 const char *datestring, *entryname, *fullpath; 1345 svn_fs_dirent_t *fs_entry; 1346 svn_dirent_t *entry = svn_dirent_create(pool); 1347 1348 svn_pool_clear(iterpool); 1349 1350 apr_hash_this(hi, &key, NULL, &val); 1351 entryname = (const char *) key; 1352 fs_entry = (svn_fs_dirent_t *) val; 1353 1354 fullpath = svn_dirent_join(abs_path, entryname, iterpool); 1355 1356 if (dirent_fields & SVN_DIRENT_KIND) 1357 { 1358 /* node kind */ 1359 entry->kind = fs_entry->kind; 1360 } 1361 1362 if (dirent_fields & SVN_DIRENT_SIZE) 1363 { 1364 /* size */ 1365 if (entry->kind == svn_node_dir) 1366 entry->size = 0; 1367 else 1368 SVN_ERR(svn_fs_file_length(&(entry->size), root, 1369 fullpath, iterpool)); 1370 } 1371 1372 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 1373 { 1374 /* has_props? */ 1375 SVN_ERR(svn_fs_node_has_props(&entry->has_props, 1376 root, fullpath, 1377 iterpool)); 1378 } 1379 1380 if ((dirent_fields & SVN_DIRENT_TIME) 1381 || (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 1382 || (dirent_fields & SVN_DIRENT_CREATED_REV)) 1383 { 1384 /* created_rev & friends */ 1385 SVN_ERR(svn_repos_get_committed_info(&(entry->created_rev), 1386 &datestring, 1387 &(entry->last_author), 1388 root, fullpath, iterpool)); 1389 if (datestring) 1390 SVN_ERR(svn_time_from_cstring(&(entry->time), datestring, 1391 pool)); 1392 if (entry->last_author) 1393 entry->last_author = apr_pstrdup(pool, entry->last_author); 1394 } 1395 1396 /* Store. */ 1397 svn_hash_sets(*dirents, entryname, entry); 1398 } 1399 svn_pool_destroy(iterpool); 1400 } 1401 1402 /* Handle props if requested. */ 1403 if (props) 1404 SVN_ERR(get_node_props(props, root, abs_path, sess->uuid, pool, pool)); 1405 1406 return SVN_NO_ERROR; 1407} 1408 1409 1410static svn_error_t * 1411svn_ra_local__get_locations(svn_ra_session_t *session, 1412 apr_hash_t **locations, 1413 const char *path, 1414 svn_revnum_t peg_revision, 1415 const apr_array_header_t *location_revisions, 1416 apr_pool_t *pool) 1417{ 1418 svn_ra_local__session_baton_t *sess = session->priv; 1419 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1420 return svn_repos_trace_node_locations(sess->fs, locations, abs_path, 1421 peg_revision, location_revisions, 1422 NULL, NULL, pool); 1423} 1424 1425 1426static svn_error_t * 1427svn_ra_local__get_location_segments(svn_ra_session_t *session, 1428 const char *path, 1429 svn_revnum_t peg_revision, 1430 svn_revnum_t start_rev, 1431 svn_revnum_t end_rev, 1432 svn_location_segment_receiver_t receiver, 1433 void *receiver_baton, 1434 apr_pool_t *pool) 1435{ 1436 svn_ra_local__session_baton_t *sess = session->priv; 1437 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1438 return svn_repos_node_location_segments(sess->repos, abs_path, 1439 peg_revision, start_rev, end_rev, 1440 receiver, receiver_baton, 1441 NULL, NULL, pool); 1442} 1443 1444struct lock_baton_t { 1445 svn_ra_lock_callback_t lock_func; 1446 void *lock_baton; 1447 const char *fs_path; 1448 svn_boolean_t is_lock; 1449 svn_error_t *cb_err; 1450}; 1451 1452/* Implements svn_fs_lock_callback_t. Used by svn_ra_local__lock and 1453 svn_ra_local__unlock to forward to supplied callback and record any 1454 callback error. */ 1455static svn_error_t * 1456lock_cb(void *lock_baton, 1457 const char *path, 1458 const svn_lock_t *lock, 1459 svn_error_t *fs_err, 1460 apr_pool_t *pool) 1461{ 1462 struct lock_baton_t *b = lock_baton; 1463 1464 if (b && !b->cb_err && b->lock_func) 1465 { 1466 path = svn_fspath__skip_ancestor(b->fs_path, path); 1467 b->cb_err = b->lock_func(b->lock_baton, path, b->is_lock, lock, fs_err, 1468 pool); 1469 } 1470 1471 return SVN_NO_ERROR; 1472} 1473 1474static svn_error_t * 1475svn_ra_local__lock(svn_ra_session_t *session, 1476 apr_hash_t *path_revs, 1477 const char *comment, 1478 svn_boolean_t force, 1479 svn_ra_lock_callback_t lock_func, 1480 void *lock_baton, 1481 apr_pool_t *pool) 1482{ 1483 svn_ra_local__session_baton_t *sess = session->priv; 1484 apr_hash_t *targets = apr_hash_make(pool); 1485 apr_hash_index_t *hi; 1486 svn_error_t *err; 1487 struct lock_baton_t baton = {0}; 1488 1489 /* A username is absolutely required to lock a path. */ 1490 SVN_ERR(get_username(session, pool)); 1491 1492 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 1493 { 1494 const char *abs_path = svn_fspath__join(sess->fs_path->data, 1495 apr_hash_this_key(hi), pool); 1496 svn_revnum_t current_rev = *(svn_revnum_t *)apr_hash_this_val(hi); 1497 svn_fs_lock_target_t *target = svn_fs_lock_target_create(NULL, 1498 current_rev, 1499 pool); 1500 1501 svn_hash_sets(targets, abs_path, target); 1502 } 1503 1504 baton.lock_func = lock_func; 1505 baton.lock_baton = lock_baton; 1506 baton.fs_path = sess->fs_path->data; 1507 baton.is_lock = TRUE; 1508 baton.cb_err = SVN_NO_ERROR; 1509 1510 err = svn_repos_fs_lock_many(sess->repos, targets, comment, 1511 FALSE /* not DAV comment */, 1512 0 /* no expiration */, force, 1513 lock_cb, &baton, 1514 pool, pool); 1515 1516 if (err && baton.cb_err) 1517 svn_error_compose(err, baton.cb_err); 1518 else if (!err) 1519 err = baton.cb_err; 1520 1521 return svn_error_trace(err); 1522} 1523 1524 1525static svn_error_t * 1526svn_ra_local__unlock(svn_ra_session_t *session, 1527 apr_hash_t *path_tokens, 1528 svn_boolean_t force, 1529 svn_ra_lock_callback_t lock_func, 1530 void *lock_baton, 1531 apr_pool_t *pool) 1532{ 1533 svn_ra_local__session_baton_t *sess = session->priv; 1534 apr_hash_t *targets = apr_hash_make(pool); 1535 apr_hash_index_t *hi; 1536 svn_error_t *err; 1537 struct lock_baton_t baton = {0}; 1538 1539 /* A username is absolutely required to unlock a path. */ 1540 SVN_ERR(get_username(session, pool)); 1541 1542 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 1543 { 1544 const char *abs_path = svn_fspath__join(sess->fs_path->data, 1545 apr_hash_this_key(hi), pool); 1546 const char *token = apr_hash_this_val(hi); 1547 1548 svn_hash_sets(targets, abs_path, token); 1549 } 1550 1551 baton.lock_func = lock_func; 1552 baton.lock_baton = lock_baton; 1553 baton.fs_path = sess->fs_path->data; 1554 baton.is_lock = FALSE; 1555 baton.cb_err = SVN_NO_ERROR; 1556 1557 err = svn_repos_fs_unlock_many(sess->repos, targets, force, lock_cb, &baton, 1558 pool, pool); 1559 1560 if (err && baton.cb_err) 1561 svn_error_compose(err, baton.cb_err); 1562 else if (!err) 1563 err = baton.cb_err; 1564 1565 return svn_error_trace(err); 1566} 1567 1568 1569 1570static svn_error_t * 1571svn_ra_local__get_lock(svn_ra_session_t *session, 1572 svn_lock_t **lock, 1573 const char *path, 1574 apr_pool_t *pool) 1575{ 1576 svn_ra_local__session_baton_t *sess = session->priv; 1577 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1578 return svn_fs_get_lock(lock, sess->fs, abs_path, pool); 1579} 1580 1581 1582 1583static svn_error_t * 1584svn_ra_local__get_locks(svn_ra_session_t *session, 1585 apr_hash_t **locks, 1586 const char *path, 1587 svn_depth_t depth, 1588 apr_pool_t *pool) 1589{ 1590 svn_ra_local__session_baton_t *sess = session->priv; 1591 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1592 1593 /* Kinda silly to call the repos wrapper, since we have no authz 1594 func to give it. But heck, why not. */ 1595 return svn_repos_fs_get_locks2(locks, sess->repos, abs_path, depth, 1596 NULL, NULL, pool); 1597} 1598 1599 1600static svn_error_t * 1601svn_ra_local__replay(svn_ra_session_t *session, 1602 svn_revnum_t revision, 1603 svn_revnum_t low_water_mark, 1604 svn_boolean_t send_deltas, 1605 const svn_delta_editor_t *editor, 1606 void *edit_baton, 1607 apr_pool_t *pool) 1608{ 1609 svn_ra_local__session_baton_t *sess = session->priv; 1610 svn_fs_root_t *root; 1611 1612 SVN_ERR(svn_fs_revision_root(&root, svn_repos_fs(sess->repos), 1613 revision, pool)); 1614 return svn_repos_replay2(root, sess->fs_path->data, low_water_mark, 1615 send_deltas, editor, edit_baton, NULL, NULL, 1616 pool); 1617} 1618 1619 1620static svn_error_t * 1621svn_ra_local__replay_range(svn_ra_session_t *session, 1622 svn_revnum_t start_revision, 1623 svn_revnum_t end_revision, 1624 svn_revnum_t low_water_mark, 1625 svn_boolean_t send_deltas, 1626 svn_ra_replay_revstart_callback_t revstart_func, 1627 svn_ra_replay_revfinish_callback_t revfinish_func, 1628 void *replay_baton, 1629 apr_pool_t *pool) 1630{ 1631 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL); 1632} 1633 1634 1635static svn_error_t * 1636svn_ra_local__has_capability(svn_ra_session_t *session, 1637 svn_boolean_t *has, 1638 const char *capability, 1639 apr_pool_t *pool) 1640{ 1641 svn_ra_local__session_baton_t *sess = session->priv; 1642 1643 if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0 1644 || strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0 1645 || strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0 1646 || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0 1647 || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0 1648 || strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0 1649 || strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0 1650 || strcmp(capability, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) == 0 1651 ) 1652 { 1653 *has = TRUE; 1654 } 1655 else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0) 1656 { 1657 /* With mergeinfo, the code's capabilities may not reflect the 1658 repository's, so inquire further. */ 1659 SVN_ERR(svn_repos_has_capability(sess->repos, has, 1660 SVN_REPOS_CAPABILITY_MERGEINFO, 1661 pool)); 1662 } 1663 else /* Don't know any other capabilities, so error. */ 1664 { 1665 return svn_error_createf 1666 (SVN_ERR_UNKNOWN_CAPABILITY, NULL, 1667 _("Don't know anything about capability '%s'"), capability); 1668 } 1669 1670 return SVN_NO_ERROR; 1671} 1672 1673static svn_error_t * 1674svn_ra_local__get_deleted_rev(svn_ra_session_t *session, 1675 const char *path, 1676 svn_revnum_t peg_revision, 1677 svn_revnum_t end_revision, 1678 svn_revnum_t *revision_deleted, 1679 apr_pool_t *pool) 1680{ 1681 svn_ra_local__session_baton_t *sess = session->priv; 1682 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1683 1684 SVN_ERR(svn_repos_deleted_rev(sess->fs, 1685 abs_path, 1686 peg_revision, 1687 end_revision, 1688 revision_deleted, 1689 pool)); 1690 1691 return SVN_NO_ERROR; 1692} 1693 1694static svn_error_t * 1695svn_ra_local__get_inherited_props(svn_ra_session_t *session, 1696 apr_array_header_t **iprops, 1697 const char *path, 1698 svn_revnum_t revision, 1699 apr_pool_t *result_pool, 1700 apr_pool_t *scratch_pool) 1701{ 1702 svn_fs_root_t *root; 1703 svn_revnum_t youngest_rev; 1704 svn_ra_local__session_baton_t *sess = session->priv; 1705 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, 1706 scratch_pool); 1707 svn_node_kind_t node_kind; 1708 1709 /* Open the revision's root. */ 1710 if (! SVN_IS_VALID_REVNUM(revision)) 1711 { 1712 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, scratch_pool)); 1713 SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, 1714 scratch_pool)); 1715 } 1716 else 1717 { 1718 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, scratch_pool)); 1719 } 1720 1721 SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, scratch_pool)); 1722 if (node_kind == svn_node_none) 1723 { 1724 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1725 _("'%s' path not found"), abs_path); 1726 } 1727 1728 return svn_error_trace( 1729 svn_repos_fs_get_inherited_props(iprops, root, abs_path, 1730 NULL /* propname */, 1731 NULL, NULL /* auth */, 1732 result_pool, scratch_pool)); 1733} 1734 1735static svn_error_t * 1736svn_ra_local__register_editor_shim_callbacks(svn_ra_session_t *session, 1737 svn_delta_shim_callbacks_t *callbacks) 1738{ 1739 /* This is currenly a no-op, since we don't provide our own editor, just 1740 use the one the libsvn_repos hands back to us. */ 1741 return SVN_NO_ERROR; 1742} 1743 1744 1745static svn_error_t * 1746svn_ra_local__get_commit_ev2(svn_editor_t **editor, 1747 svn_ra_session_t *session, 1748 apr_hash_t *revprops, 1749 svn_commit_callback2_t commit_cb, 1750 void *commit_baton, 1751 apr_hash_t *lock_tokens, 1752 svn_boolean_t keep_locks, 1753 svn_ra__provide_base_cb_t provide_base_cb, 1754 svn_ra__provide_props_cb_t provide_props_cb, 1755 svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb, 1756 void *cb_baton, 1757 svn_cancel_func_t cancel_func, 1758 void *cancel_baton, 1759 apr_pool_t *result_pool, 1760 apr_pool_t *scratch_pool) 1761{ 1762 svn_ra_local__session_baton_t *sess = session->priv; 1763 struct deltify_etc_baton *deb = apr_palloc(result_pool, sizeof(*deb)); 1764 1765 remap_commit_callback(&commit_cb, &commit_baton, session, 1766 commit_cb, commit_baton, result_pool); 1767 1768 /* NOTE: the RA callbacks are ignored. We pass everything directly to 1769 the REPOS editor. */ 1770 1771 /* Prepare the baton for deltify_etc() */ 1772 deb->fs = sess->fs; 1773 deb->repos = sess->repos; 1774 deb->fspath_base = sess->fs_path->data; 1775 if (! keep_locks) 1776 deb->lock_tokens = lock_tokens; 1777 else 1778 deb->lock_tokens = NULL; 1779 deb->commit_cb = commit_cb; 1780 deb->commit_baton = commit_baton; 1781 1782 /* Ensure there is a username (and an FS access context) associated with 1783 the session and its FS handle. */ 1784 SVN_ERR(get_username(session, scratch_pool)); 1785 1786 /* If there are lock tokens to add, do so. */ 1787 SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens, 1788 session->pool, scratch_pool)); 1789 1790 /* Copy the REVPROPS and insert the author/username. */ 1791 revprops = apr_hash_copy(scratch_pool, revprops); 1792 svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR, 1793 svn_string_create(sess->username, scratch_pool)); 1794 1795 return svn_error_trace(svn_repos__get_commit_ev2( 1796 editor, sess->repos, NULL /* authz */, 1797 NULL /* authz_repos_name */, NULL /* authz_user */, 1798 revprops, 1799 deltify_etc, deb, cancel_func, cancel_baton, 1800 result_pool, scratch_pool)); 1801} 1802 1803/*----------------------------------------------------------------*/ 1804 1805static const svn_version_t * 1806ra_local_version(void) 1807{ 1808 SVN_VERSION_BODY; 1809} 1810 1811/** The ra_vtable **/ 1812 1813static const svn_ra__vtable_t ra_local_vtable = 1814{ 1815 ra_local_version, 1816 svn_ra_local__get_description, 1817 svn_ra_local__get_schemes, 1818 svn_ra_local__open, 1819 svn_ra_local__dup_session, 1820 svn_ra_local__reparent, 1821 svn_ra_local__get_session_url, 1822 svn_ra_local__get_latest_revnum, 1823 svn_ra_local__get_dated_revision, 1824 svn_ra_local__change_rev_prop, 1825 svn_ra_local__rev_proplist, 1826 svn_ra_local__rev_prop, 1827 svn_ra_local__get_commit_editor, 1828 svn_ra_local__get_file, 1829 svn_ra_local__get_dir, 1830 svn_ra_local__get_mergeinfo, 1831 svn_ra_local__do_update, 1832 svn_ra_local__do_switch, 1833 svn_ra_local__do_status, 1834 svn_ra_local__do_diff, 1835 svn_ra_local__get_log, 1836 svn_ra_local__do_check_path, 1837 svn_ra_local__stat, 1838 svn_ra_local__get_uuid, 1839 svn_ra_local__get_repos_root, 1840 svn_ra_local__get_locations, 1841 svn_ra_local__get_location_segments, 1842 svn_ra_local__get_file_revs, 1843 svn_ra_local__lock, 1844 svn_ra_local__unlock, 1845 svn_ra_local__get_lock, 1846 svn_ra_local__get_locks, 1847 svn_ra_local__replay, 1848 svn_ra_local__has_capability, 1849 svn_ra_local__replay_range, 1850 svn_ra_local__get_deleted_rev, 1851 svn_ra_local__register_editor_shim_callbacks, 1852 svn_ra_local__get_inherited_props, 1853 svn_ra_local__get_commit_ev2 1854}; 1855 1856 1857/*----------------------------------------------------------------*/ 1858 1859/** The One Public Routine, called by libsvn_ra **/ 1860 1861svn_error_t * 1862svn_ra_local__init(const svn_version_t *loader_version, 1863 const svn_ra__vtable_t **vtable, 1864 apr_pool_t *pool) 1865{ 1866 static const svn_version_checklist_t checklist[] = 1867 { 1868 { "svn_subr", svn_subr_version }, 1869 { "svn_delta", svn_delta_version }, 1870 { "svn_repos", svn_repos_version }, 1871 { "svn_fs", svn_fs_version }, 1872 { NULL, NULL } 1873 }; 1874 1875 1876 /* Simplified version check to make sure we can safely use the 1877 VTABLE parameter. The RA loader does a more exhaustive check. */ 1878 if (loader_version->major != SVN_VER_MAJOR) 1879 return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL, 1880 _("Unsupported RA loader version (%d) for " 1881 "ra_local"), 1882 loader_version->major); 1883 1884 SVN_ERR(svn_ver_check_list2(ra_local_version(), checklist, svn_ver_equal)); 1885 1886#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL 1887 /* This means the library was loaded as a DSO, so use the DSO pool. */ 1888 SVN_ERR(svn_fs_initialize(svn_dso__pool())); 1889#endif 1890 1891 *vtable = &ra_local_vtable; 1892 1893 return SVN_NO_ERROR; 1894} 1895 1896/* Compatibility wrapper for the 1.1 and before API. */ 1897#define NAME "ra_local" 1898#define DESCRIPTION RA_LOCAL_DESCRIPTION 1899#define VTBL ra_local_vtable 1900#define INITFUNC svn_ra_local__init 1901#define COMPAT_INITFUNC svn_ra_local_init 1902#include "../libsvn_ra/wrapper_template.h" 1903