ra_plugin.c revision 362181
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 **redirect_url, 558 const char *repos_URL, 559 const svn_ra_callbacks2_t *callbacks, 560 void *callback_baton, 561 svn_auth_baton_t *auth_baton, 562 apr_hash_t *config, 563 apr_pool_t *result_pool, 564 apr_pool_t *scratch_pool) 565{ 566 const char *client_string; 567 svn_ra_local__session_baton_t *sess; 568 const char *fs_path; 569 static volatile svn_atomic_t cache_init_state = 0; 570 apr_pool_t *pool = result_pool; 571 572 /* Initialise the FSFS memory cache size. We can only do this once 573 so one CONFIG will win the race and all others will be ignored 574 silently. */ 575 SVN_ERR(svn_atomic__init_once(&cache_init_state, cache_init, config, pool)); 576 577 /* We don't support redirections in ra-local. */ 578 if (corrected_url) 579 *corrected_url = NULL; 580 if (redirect_url) 581 *redirect_url = NULL; 582 583 /* Allocate and stash the session_sess args we have already. */ 584 sess = apr_pcalloc(pool, sizeof(*sess)); 585 sess->callbacks = callbacks; 586 sess->callback_baton = callback_baton; 587 sess->auth_baton = auth_baton; 588 589 /* Look through the URL, figure out which part points to the 590 repository, and which part is the path *within* the 591 repository. */ 592 SVN_ERR(svn_ra_local__split_URL(&(sess->repos), 593 &(sess->repos_url), 594 &fs_path, 595 repos_URL, 596 session->pool)); 597 sess->fs_path = svn_stringbuf_create(fs_path, session->pool); 598 599 /* Cache the filesystem object from the repos here for 600 convenience. */ 601 sess->fs = svn_repos_fs(sess->repos); 602 603 /* Ignore FS warnings. */ 604 svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL); 605 606 /* Cache the repository UUID as well */ 607 SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool)); 608 609 /* Be sure username is NULL so we know to look it up / ask for it */ 610 sess->username = NULL; 611 612 if (sess->callbacks->get_client_string != NULL) 613 SVN_ERR(sess->callbacks->get_client_string(sess->callback_baton, 614 &client_string, pool)); 615 else 616 client_string = NULL; 617 618 if (client_string) 619 sess->useragent = apr_pstrcat(pool, USER_AGENT " ", 620 client_string, SVN_VA_NULL); 621 else 622 sess->useragent = USER_AGENT; 623 624 session->priv = sess; 625 return SVN_NO_ERROR; 626} 627 628static svn_error_t * 629svn_ra_local__dup_session(svn_ra_session_t *new_session, 630 svn_ra_session_t *session, 631 const char *new_session_url, 632 apr_pool_t *result_pool, 633 apr_pool_t *scratch_pool) 634{ 635 svn_ra_local__session_baton_t *old_sess = session->priv; 636 svn_ra_local__session_baton_t *new_sess; 637 const char *fs_path; 638 639 /* Allocate and stash the session_sess args we have already. */ 640 new_sess = apr_pcalloc(result_pool, sizeof(*new_sess)); 641 new_sess->callbacks = old_sess->callbacks; 642 new_sess->callback_baton = old_sess->callback_baton; 643 644 /* ### Re-use existing FS handle? */ 645 646 /* Reuse existing code */ 647 SVN_ERR(svn_ra_local__split_URL(&(new_sess->repos), 648 &(new_sess->repos_url), 649 &fs_path, 650 new_session_url, 651 result_pool)); 652 653 new_sess->fs_path = svn_stringbuf_create(fs_path, result_pool); 654 655 /* Cache the filesystem object from the repos here for 656 convenience. */ 657 new_sess->fs = svn_repos_fs(new_sess->repos); 658 659 /* Ignore FS warnings. */ 660 svn_fs_set_warning_func(new_sess->fs, ignore_warnings, NULL); 661 662 /* Cache the repository UUID as well */ 663 new_sess->uuid = apr_pstrdup(result_pool, old_sess->uuid); 664 665 new_sess->username = old_sess->username 666 ? apr_pstrdup(result_pool, old_sess->username) 667 : NULL; 668 669 new_sess->useragent = apr_pstrdup(result_pool, old_sess->useragent); 670 new_session->priv = new_sess; 671 672 return SVN_NO_ERROR; 673} 674 675static svn_error_t * 676svn_ra_local__reparent(svn_ra_session_t *session, 677 const char *url, 678 apr_pool_t *pool) 679{ 680 svn_ra_local__session_baton_t *sess = session->priv; 681 const char *relpath = svn_uri_skip_ancestor(sess->repos_url, url, pool); 682 683 /* If the new URL isn't the same as our repository root URL, then 684 let's ensure that it's some child of it. */ 685 if (! relpath) 686 return svn_error_createf 687 (SVN_ERR_RA_ILLEGAL_URL, NULL, 688 _("URL '%s' is not a child of the session's repository root " 689 "URL '%s'"), url, sess->repos_url); 690 691 /* Update our FS_PATH sess member to point to our new 692 relative-URL-turned-absolute-filesystem-path. */ 693 svn_stringbuf_set(sess->fs_path, 694 svn_fspath__canonicalize(relpath, pool)); 695 696 return SVN_NO_ERROR; 697} 698 699static svn_error_t * 700svn_ra_local__get_session_url(svn_ra_session_t *session, 701 const char **url, 702 apr_pool_t *pool) 703{ 704 svn_ra_local__session_baton_t *sess = session->priv; 705 *url = svn_path_url_add_component2(sess->repos_url, 706 sess->fs_path->data + 1, 707 pool); 708 return SVN_NO_ERROR; 709} 710 711static svn_error_t * 712svn_ra_local__get_latest_revnum(svn_ra_session_t *session, 713 svn_revnum_t *latest_revnum, 714 apr_pool_t *pool) 715{ 716 svn_ra_local__session_baton_t *sess = session->priv; 717 return svn_fs_youngest_rev(latest_revnum, sess->fs, pool); 718} 719 720static svn_error_t * 721svn_ra_local__get_file_revs(svn_ra_session_t *session, 722 const char *path, 723 svn_revnum_t start, 724 svn_revnum_t end, 725 svn_boolean_t include_merged_revisions, 726 svn_file_rev_handler_t handler, 727 void *handler_baton, 728 apr_pool_t *pool) 729{ 730 svn_ra_local__session_baton_t *sess = session->priv; 731 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 732 return svn_repos_get_file_revs2(sess->repos, abs_path, start, end, 733 include_merged_revisions, NULL, NULL, 734 handler, handler_baton, pool); 735} 736 737static svn_error_t * 738svn_ra_local__get_dated_revision(svn_ra_session_t *session, 739 svn_revnum_t *revision, 740 apr_time_t tm, 741 apr_pool_t *pool) 742{ 743 svn_ra_local__session_baton_t *sess = session->priv; 744 return svn_repos_dated_revision(revision, sess->repos, tm, pool); 745} 746 747 748static svn_error_t * 749svn_ra_local__change_rev_prop(svn_ra_session_t *session, 750 svn_revnum_t rev, 751 const char *name, 752 const svn_string_t *const *old_value_p, 753 const svn_string_t *value, 754 apr_pool_t *pool) 755{ 756 svn_ra_local__session_baton_t *sess = session->priv; 757 758 SVN_ERR(get_username(session, pool)); 759 return svn_repos_fs_change_rev_prop4(sess->repos, rev, sess->username, 760 name, old_value_p, value, TRUE, TRUE, 761 NULL, NULL, pool); 762} 763 764static svn_error_t * 765svn_ra_local__get_uuid(svn_ra_session_t *session, 766 const char **uuid, 767 apr_pool_t *pool) 768{ 769 svn_ra_local__session_baton_t *sess = session->priv; 770 *uuid = sess->uuid; 771 return SVN_NO_ERROR; 772} 773 774static svn_error_t * 775svn_ra_local__get_repos_root(svn_ra_session_t *session, 776 const char **url, 777 apr_pool_t *pool) 778{ 779 svn_ra_local__session_baton_t *sess = session->priv; 780 *url = sess->repos_url; 781 return SVN_NO_ERROR; 782} 783 784static svn_error_t * 785svn_ra_local__rev_proplist(svn_ra_session_t *session, 786 svn_revnum_t rev, 787 apr_hash_t **props, 788 apr_pool_t *pool) 789{ 790 svn_ra_local__session_baton_t *sess = session->priv; 791 return svn_repos_fs_revision_proplist(props, sess->repos, rev, 792 NULL, NULL, pool); 793} 794 795static svn_error_t * 796svn_ra_local__rev_prop(svn_ra_session_t *session, 797 svn_revnum_t rev, 798 const char *name, 799 svn_string_t **value, 800 apr_pool_t *pool) 801{ 802 svn_ra_local__session_baton_t *sess = session->priv; 803 return svn_repos_fs_revision_prop(value, sess->repos, rev, name, 804 NULL, NULL, pool); 805} 806 807struct ccw_baton 808{ 809 svn_commit_callback2_t original_callback; 810 void *original_baton; 811 812 svn_ra_session_t *session; 813}; 814 815/* Wrapper which populates the repos_root field of the commit_info struct */ 816static svn_error_t * 817commit_callback_wrapper(const svn_commit_info_t *commit_info, 818 void *baton, 819 apr_pool_t *scratch_pool) 820{ 821 struct ccw_baton *ccwb = baton; 822 svn_commit_info_t *ci = svn_commit_info_dup(commit_info, scratch_pool); 823 824 SVN_ERR(svn_ra_local__get_repos_root(ccwb->session, &ci->repos_root, 825 scratch_pool)); 826 827 return svn_error_trace(ccwb->original_callback(ci, ccwb->original_baton, 828 scratch_pool)); 829} 830 831 832/* The repository layer does not correctly fill in REPOS_ROOT in 833 commit_info, as it doesn't know the url that is used to access 834 it. This hooks the callback to fill in the missing pieces. */ 835static void 836remap_commit_callback(svn_commit_callback2_t *callback, 837 void **callback_baton, 838 svn_ra_session_t *session, 839 svn_commit_callback2_t original_callback, 840 void *original_baton, 841 apr_pool_t *result_pool) 842{ 843 if (original_callback == NULL) 844 { 845 *callback = NULL; 846 *callback_baton = NULL; 847 } 848 else 849 { 850 /* Allocate this in RESULT_POOL, since the callback will be called 851 long after this function has returned. */ 852 struct ccw_baton *ccwb = apr_palloc(result_pool, sizeof(*ccwb)); 853 854 ccwb->session = session; 855 ccwb->original_callback = original_callback; 856 ccwb->original_baton = original_baton; 857 858 *callback = commit_callback_wrapper; 859 *callback_baton = ccwb; 860 } 861} 862 863static svn_error_t * 864svn_ra_local__get_commit_editor(svn_ra_session_t *session, 865 const svn_delta_editor_t **editor, 866 void **edit_baton, 867 apr_hash_t *revprop_table, 868 svn_commit_callback2_t callback, 869 void *callback_baton, 870 apr_hash_t *lock_tokens, 871 svn_boolean_t keep_locks, 872 apr_pool_t *pool) 873{ 874 svn_ra_local__session_baton_t *sess = session->priv; 875 struct deltify_etc_baton *deb = apr_palloc(pool, sizeof(*deb)); 876 877 /* Set repos_root_url in commit info */ 878 remap_commit_callback(&callback, &callback_baton, session, 879 callback, callback_baton, pool); 880 881 /* Prepare the baton for deltify_etc() */ 882 deb->fs = sess->fs; 883 deb->repos = sess->repos; 884 deb->fspath_base = sess->fs_path->data; 885 if (! keep_locks) 886 deb->lock_tokens = lock_tokens; 887 else 888 deb->lock_tokens = NULL; 889 deb->commit_cb = callback; 890 deb->commit_baton = callback_baton; 891 892 SVN_ERR(get_username(session, pool)); 893 894 /* If there are lock tokens to add, do so. */ 895 SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens, 896 session->pool, pool)); 897 898 /* Copy the revprops table so we can add the username. */ 899 revprop_table = apr_hash_copy(pool, revprop_table); 900 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR, 901 svn_string_create(sess->username, pool)); 902 svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION, 903 svn_string_create(SVN_VER_NUMBER, pool)); 904 svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT, 905 svn_string_create(sess->useragent, pool)); 906 907 /* Get the repos commit-editor */ 908 return svn_repos_get_commit_editor5 909 (editor, edit_baton, sess->repos, NULL, 910 svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data, 911 revprop_table, deltify_etc, deb, NULL, NULL, pool); 912} 913 914 915/* Implements svn_repos_mergeinfo_receiver_t. 916 * It add MERGEINFO for PATH to the svn_mergeinfo_catalog_t BATON. 917 */ 918static svn_error_t * 919mergeinfo_receiver(const char *path, 920 svn_mergeinfo_t mergeinfo, 921 void *baton, 922 apr_pool_t *scratch_pool) 923{ 924 svn_mergeinfo_catalog_t catalog = baton; 925 apr_pool_t *result_pool = apr_hash_pool_get(catalog); 926 apr_size_t len = strlen(path); 927 928 apr_hash_set(catalog, 929 apr_pstrmemdup(result_pool, path, len), 930 len, 931 svn_mergeinfo_dup(mergeinfo, result_pool)); 932 933 return SVN_NO_ERROR; 934} 935 936 937static svn_error_t * 938svn_ra_local__get_mergeinfo(svn_ra_session_t *session, 939 svn_mergeinfo_catalog_t *catalog, 940 const apr_array_header_t *paths, 941 svn_revnum_t revision, 942 svn_mergeinfo_inheritance_t inherit, 943 svn_boolean_t include_descendants, 944 apr_pool_t *pool) 945{ 946 svn_ra_local__session_baton_t *sess = session->priv; 947 svn_mergeinfo_catalog_t tmp_catalog = svn_hash__make(pool); 948 int i; 949 apr_array_header_t *abs_paths = 950 apr_array_make(pool, 0, sizeof(const char *)); 951 952 for (i = 0; i < paths->nelts; i++) 953 { 954 const char *relative_path = APR_ARRAY_IDX(paths, i, const char *); 955 APR_ARRAY_PUSH(abs_paths, const char *) = 956 svn_fspath__join(sess->fs_path->data, relative_path, pool); 957 } 958 959 SVN_ERR(svn_repos_fs_get_mergeinfo2(sess->repos, abs_paths, revision, 960 inherit, include_descendants, 961 NULL, NULL, 962 mergeinfo_receiver, tmp_catalog, 963 pool)); 964 if (apr_hash_count(tmp_catalog) > 0) 965 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog, 966 tmp_catalog, 967 sess->fs_path->data, 968 pool)); 969 else 970 *catalog = NULL; 971 972 return SVN_NO_ERROR; 973} 974 975 976static svn_error_t * 977svn_ra_local__do_update(svn_ra_session_t *session, 978 const svn_ra_reporter3_t **reporter, 979 void **report_baton, 980 svn_revnum_t update_revision, 981 const char *update_target, 982 svn_depth_t depth, 983 svn_boolean_t send_copyfrom_args, 984 svn_boolean_t ignore_ancestry, 985 const svn_delta_editor_t *update_editor, 986 void *update_baton, 987 apr_pool_t *result_pool, 988 apr_pool_t *scratch_pool) 989{ 990 return make_reporter(session, 991 reporter, 992 report_baton, 993 update_revision, 994 update_target, 995 NULL, 996 TRUE, 997 depth, 998 send_copyfrom_args, 999 ignore_ancestry, 1000 update_editor, 1001 update_baton, 1002 result_pool, scratch_pool); 1003} 1004 1005 1006static svn_error_t * 1007svn_ra_local__do_switch(svn_ra_session_t *session, 1008 const svn_ra_reporter3_t **reporter, 1009 void **report_baton, 1010 svn_revnum_t update_revision, 1011 const char *update_target, 1012 svn_depth_t depth, 1013 const char *switch_url, 1014 svn_boolean_t send_copyfrom_args, 1015 svn_boolean_t ignore_ancestry, 1016 const svn_delta_editor_t *update_editor, 1017 void *update_baton, 1018 apr_pool_t *result_pool, 1019 apr_pool_t *scratch_pool) 1020{ 1021 return make_reporter(session, 1022 reporter, 1023 report_baton, 1024 update_revision, 1025 update_target, 1026 switch_url, 1027 TRUE /* text_deltas */, 1028 depth, 1029 send_copyfrom_args, 1030 ignore_ancestry, 1031 update_editor, 1032 update_baton, 1033 result_pool, scratch_pool); 1034} 1035 1036 1037static svn_error_t * 1038svn_ra_local__do_status(svn_ra_session_t *session, 1039 const svn_ra_reporter3_t **reporter, 1040 void **report_baton, 1041 const char *status_target, 1042 svn_revnum_t revision, 1043 svn_depth_t depth, 1044 const svn_delta_editor_t *status_editor, 1045 void *status_baton, 1046 apr_pool_t *pool) 1047{ 1048 return make_reporter(session, 1049 reporter, 1050 report_baton, 1051 revision, 1052 status_target, 1053 NULL, 1054 FALSE, 1055 depth, 1056 FALSE, 1057 FALSE, 1058 status_editor, 1059 status_baton, 1060 pool, pool); 1061} 1062 1063 1064static svn_error_t * 1065svn_ra_local__do_diff(svn_ra_session_t *session, 1066 const svn_ra_reporter3_t **reporter, 1067 void **report_baton, 1068 svn_revnum_t update_revision, 1069 const char *update_target, 1070 svn_depth_t depth, 1071 svn_boolean_t ignore_ancestry, 1072 svn_boolean_t text_deltas, 1073 const char *switch_url, 1074 const svn_delta_editor_t *update_editor, 1075 void *update_baton, 1076 apr_pool_t *pool) 1077{ 1078 return make_reporter(session, 1079 reporter, 1080 report_baton, 1081 update_revision, 1082 update_target, 1083 switch_url, 1084 text_deltas, 1085 depth, 1086 FALSE, 1087 ignore_ancestry, 1088 update_editor, 1089 update_baton, 1090 pool, pool); 1091} 1092 1093 1094struct log_baton 1095{ 1096 svn_ra_local__session_baton_t *sess; 1097 svn_log_entry_receiver_t real_cb; 1098 void *real_baton; 1099}; 1100 1101static svn_error_t * 1102log_receiver_wrapper(void *baton, 1103 svn_log_entry_t *log_entry, 1104 apr_pool_t *pool) 1105{ 1106 struct log_baton *b = baton; 1107 svn_ra_local__session_baton_t *sess = b->sess; 1108 1109 if (sess->callbacks->cancel_func) 1110 SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton)); 1111 1112 /* For consistency with the other RA layers, replace an empty 1113 changed-paths hash with a NULL one. 1114 1115 ### Should this be done by svn_ra_get_log2() instead, then? */ 1116 if ((log_entry->changed_paths2) 1117 && (apr_hash_count(log_entry->changed_paths2) == 0)) 1118 { 1119 log_entry->changed_paths = NULL; 1120 log_entry->changed_paths2 = NULL; 1121 } 1122 1123 return b->real_cb(b->real_baton, log_entry, pool); 1124} 1125 1126 1127static svn_error_t * 1128svn_ra_local__get_log(svn_ra_session_t *session, 1129 const apr_array_header_t *paths, 1130 svn_revnum_t start, 1131 svn_revnum_t end, 1132 int limit, 1133 svn_boolean_t discover_changed_paths, 1134 svn_boolean_t strict_node_history, 1135 svn_boolean_t include_merged_revisions, 1136 const apr_array_header_t *revprops, 1137 svn_log_entry_receiver_t receiver, 1138 void *receiver_baton, 1139 apr_pool_t *pool) 1140{ 1141 svn_ra_local__session_baton_t *sess = session->priv; 1142 struct log_baton lb; 1143 apr_array_header_t *abs_paths = 1144 apr_array_make(pool, 0, sizeof(const char *)); 1145 1146 if (paths) 1147 { 1148 int i; 1149 1150 for (i = 0; i < paths->nelts; i++) 1151 { 1152 const char *relative_path = APR_ARRAY_IDX(paths, i, const char *); 1153 APR_ARRAY_PUSH(abs_paths, const char *) = 1154 svn_fspath__join(sess->fs_path->data, relative_path, pool); 1155 } 1156 } 1157 1158 lb.real_cb = receiver; 1159 lb.real_baton = receiver_baton; 1160 lb.sess = sess; 1161 receiver = log_receiver_wrapper; 1162 receiver_baton = &lb; 1163 1164 return svn_repos__get_logs_compat(sess->repos, 1165 abs_paths, 1166 start, 1167 end, 1168 limit, 1169 discover_changed_paths, 1170 strict_node_history, 1171 include_merged_revisions, 1172 revprops, 1173 NULL, NULL, 1174 receiver, 1175 receiver_baton, 1176 pool); 1177} 1178 1179 1180static svn_error_t * 1181svn_ra_local__do_check_path(svn_ra_session_t *session, 1182 const char *path, 1183 svn_revnum_t revision, 1184 svn_node_kind_t *kind, 1185 apr_pool_t *pool) 1186{ 1187 svn_ra_local__session_baton_t *sess = session->priv; 1188 svn_fs_root_t *root; 1189 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1190 1191 if (! SVN_IS_VALID_REVNUM(revision)) 1192 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool)); 1193 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1194 return svn_fs_check_path(kind, root, abs_path, pool); 1195} 1196 1197 1198static svn_error_t * 1199svn_ra_local__stat(svn_ra_session_t *session, 1200 const char *path, 1201 svn_revnum_t revision, 1202 svn_dirent_t **dirent, 1203 apr_pool_t *pool) 1204{ 1205 svn_ra_local__session_baton_t *sess = session->priv; 1206 svn_fs_root_t *root; 1207 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1208 1209 if (! SVN_IS_VALID_REVNUM(revision)) 1210 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool)); 1211 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1212 1213 return svn_repos_stat(dirent, root, abs_path, pool); 1214} 1215 1216 1217 1218 1219/* Obtain the properties for a node, including its 'entry props */ 1220static svn_error_t * 1221get_node_props(apr_hash_t **props, 1222 svn_fs_root_t *root, 1223 const char *path, 1224 const char *uuid, 1225 apr_pool_t *result_pool, 1226 apr_pool_t *scratch_pool) 1227{ 1228 svn_revnum_t cmt_rev; 1229 const char *cmt_date, *cmt_author; 1230 1231 /* Create a hash with props attached to the fs node. */ 1232 SVN_ERR(svn_fs_node_proplist(props, root, path, result_pool)); 1233 1234 /* Now add some non-tweakable metadata to the hash as well... */ 1235 1236 /* The so-called 'entryprops' with info about CR & friends. */ 1237 SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date, 1238 &cmt_author, root, path, 1239 scratch_pool)); 1240 1241 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, 1242 svn_string_createf(result_pool, "%ld", cmt_rev)); 1243 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, cmt_date ? 1244 svn_string_create(cmt_date, result_pool) : NULL); 1245 svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, cmt_author ? 1246 svn_string_create(cmt_author, result_pool) : NULL); 1247 svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, 1248 svn_string_create(uuid, result_pool)); 1249 1250 /* We have no 'wcprops' in ra_local, but might someday. */ 1251 1252 return SVN_NO_ERROR; 1253} 1254 1255 1256/* Getting just one file. */ 1257static svn_error_t * 1258svn_ra_local__get_file(svn_ra_session_t *session, 1259 const char *path, 1260 svn_revnum_t revision, 1261 svn_stream_t *stream, 1262 svn_revnum_t *fetched_rev, 1263 apr_hash_t **props, 1264 apr_pool_t *pool) 1265{ 1266 svn_fs_root_t *root; 1267 svn_stream_t *contents; 1268 svn_revnum_t youngest_rev; 1269 svn_ra_local__session_baton_t *sess = session->priv; 1270 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1271 svn_node_kind_t node_kind; 1272 1273 /* Open the revision's root. */ 1274 if (! SVN_IS_VALID_REVNUM(revision)) 1275 { 1276 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool)); 1277 SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool)); 1278 if (fetched_rev != NULL) 1279 *fetched_rev = youngest_rev; 1280 } 1281 else 1282 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1283 1284 SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, pool)); 1285 if (node_kind == svn_node_none) 1286 { 1287 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1288 _("'%s' path not found"), abs_path); 1289 } 1290 else if (node_kind != svn_node_file) 1291 { 1292 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, 1293 _("'%s' is not a file"), abs_path); 1294 } 1295 1296 if (stream) 1297 { 1298 /* Get a stream representing the file's contents. */ 1299 SVN_ERR(svn_fs_file_contents(&contents, root, abs_path, pool)); 1300 1301 /* Now push data from the fs stream back at the caller's stream. 1302 Note that this particular RA layer does not computing a 1303 checksum as we go, and confirming it against the repository's 1304 checksum when done. That's because it calls 1305 svn_fs_file_contents() directly, which already checks the 1306 stored checksum, and all we're doing here is writing bytes in 1307 a loop. Truly, Nothing Can Go Wrong :-). But RA layers that 1308 go over a network should confirm the checksum. 1309 1310 Note: we are not supposed to close the passed-in stream, so 1311 disown the thing. 1312 */ 1313 SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(stream, pool), 1314 sess->callbacks 1315 ? sess->callbacks->cancel_func : NULL, 1316 sess->callback_baton, 1317 pool)); 1318 } 1319 1320 /* Handle props if requested. */ 1321 if (props) 1322 SVN_ERR(get_node_props(props, root, abs_path, sess->uuid, pool, pool)); 1323 1324 return SVN_NO_ERROR; 1325} 1326 1327 1328 1329/* Getting a directory's entries */ 1330static svn_error_t * 1331svn_ra_local__get_dir(svn_ra_session_t *session, 1332 apr_hash_t **dirents, 1333 svn_revnum_t *fetched_rev, 1334 apr_hash_t **props, 1335 const char *path, 1336 svn_revnum_t revision, 1337 apr_uint32_t dirent_fields, 1338 apr_pool_t *pool) 1339{ 1340 svn_fs_root_t *root; 1341 svn_revnum_t youngest_rev; 1342 apr_hash_t *entries; 1343 apr_hash_index_t *hi; 1344 svn_ra_local__session_baton_t *sess = session->priv; 1345 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1346 1347 /* Open the revision's root. */ 1348 if (! SVN_IS_VALID_REVNUM(revision)) 1349 { 1350 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool)); 1351 SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool)); 1352 if (fetched_rev != NULL) 1353 *fetched_rev = youngest_rev; 1354 } 1355 else 1356 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1357 1358 if (dirents) 1359 { 1360 apr_pool_t *iterpool = svn_pool_create(pool); 1361 /* Get the dir's entries. */ 1362 SVN_ERR(svn_fs_dir_entries(&entries, root, abs_path, pool)); 1363 1364 /* Loop over the fs dirents, and build a hash of general 1365 svn_dirent_t's. */ 1366 *dirents = apr_hash_make(pool); 1367 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 1368 { 1369 const void *key; 1370 void *val; 1371 const char *datestring, *entryname, *fullpath; 1372 svn_fs_dirent_t *fs_entry; 1373 svn_dirent_t *entry = svn_dirent_create(pool); 1374 1375 svn_pool_clear(iterpool); 1376 1377 apr_hash_this(hi, &key, NULL, &val); 1378 entryname = (const char *) key; 1379 fs_entry = (svn_fs_dirent_t *) val; 1380 1381 fullpath = svn_dirent_join(abs_path, entryname, iterpool); 1382 1383 if (dirent_fields & SVN_DIRENT_KIND) 1384 { 1385 /* node kind */ 1386 entry->kind = fs_entry->kind; 1387 } 1388 1389 if (dirent_fields & SVN_DIRENT_SIZE) 1390 { 1391 /* size */ 1392 if (fs_entry->kind == svn_node_dir) 1393 entry->size = SVN_INVALID_FILESIZE; 1394 else 1395 SVN_ERR(svn_fs_file_length(&(entry->size), root, 1396 fullpath, iterpool)); 1397 } 1398 1399 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 1400 { 1401 /* has_props? */ 1402 SVN_ERR(svn_fs_node_has_props(&entry->has_props, 1403 root, fullpath, 1404 iterpool)); 1405 } 1406 1407 if ((dirent_fields & SVN_DIRENT_TIME) 1408 || (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 1409 || (dirent_fields & SVN_DIRENT_CREATED_REV)) 1410 { 1411 /* created_rev & friends */ 1412 SVN_ERR(svn_repos_get_committed_info(&(entry->created_rev), 1413 &datestring, 1414 &(entry->last_author), 1415 root, fullpath, iterpool)); 1416 if (datestring) 1417 SVN_ERR(svn_time_from_cstring(&(entry->time), datestring, 1418 pool)); 1419 if (entry->last_author) 1420 entry->last_author = apr_pstrdup(pool, entry->last_author); 1421 } 1422 1423 /* Store. */ 1424 svn_hash_sets(*dirents, entryname, entry); 1425 } 1426 svn_pool_destroy(iterpool); 1427 } 1428 1429 /* Handle props if requested. */ 1430 if (props) 1431 SVN_ERR(get_node_props(props, root, abs_path, sess->uuid, pool, pool)); 1432 1433 return SVN_NO_ERROR; 1434} 1435 1436 1437static svn_error_t * 1438svn_ra_local__get_locations(svn_ra_session_t *session, 1439 apr_hash_t **locations, 1440 const char *path, 1441 svn_revnum_t peg_revision, 1442 const apr_array_header_t *location_revisions, 1443 apr_pool_t *pool) 1444{ 1445 svn_ra_local__session_baton_t *sess = session->priv; 1446 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1447 return svn_repos_trace_node_locations(sess->fs, locations, abs_path, 1448 peg_revision, location_revisions, 1449 NULL, NULL, pool); 1450} 1451 1452 1453static svn_error_t * 1454svn_ra_local__get_location_segments(svn_ra_session_t *session, 1455 const char *path, 1456 svn_revnum_t peg_revision, 1457 svn_revnum_t start_rev, 1458 svn_revnum_t end_rev, 1459 svn_location_segment_receiver_t receiver, 1460 void *receiver_baton, 1461 apr_pool_t *pool) 1462{ 1463 svn_ra_local__session_baton_t *sess = session->priv; 1464 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1465 return svn_repos_node_location_segments(sess->repos, abs_path, 1466 peg_revision, start_rev, end_rev, 1467 receiver, receiver_baton, 1468 NULL, NULL, pool); 1469} 1470 1471struct lock_baton_t { 1472 svn_ra_lock_callback_t lock_func; 1473 void *lock_baton; 1474 const char *fs_path; 1475 svn_boolean_t is_lock; 1476 svn_error_t *cb_err; 1477}; 1478 1479/* Implements svn_fs_lock_callback_t. Used by svn_ra_local__lock and 1480 svn_ra_local__unlock to forward to supplied callback and record any 1481 callback error. */ 1482static svn_error_t * 1483lock_cb(void *lock_baton, 1484 const char *path, 1485 const svn_lock_t *lock, 1486 svn_error_t *fs_err, 1487 apr_pool_t *pool) 1488{ 1489 struct lock_baton_t *b = lock_baton; 1490 1491 if (b && !b->cb_err && b->lock_func) 1492 { 1493 path = svn_fspath__skip_ancestor(b->fs_path, path); 1494 b->cb_err = b->lock_func(b->lock_baton, path, b->is_lock, lock, fs_err, 1495 pool); 1496 } 1497 1498 return SVN_NO_ERROR; 1499} 1500 1501static svn_error_t * 1502svn_ra_local__lock(svn_ra_session_t *session, 1503 apr_hash_t *path_revs, 1504 const char *comment, 1505 svn_boolean_t force, 1506 svn_ra_lock_callback_t lock_func, 1507 void *lock_baton, 1508 apr_pool_t *pool) 1509{ 1510 svn_ra_local__session_baton_t *sess = session->priv; 1511 apr_hash_t *targets = apr_hash_make(pool); 1512 apr_hash_index_t *hi; 1513 svn_error_t *err; 1514 struct lock_baton_t baton = {0}; 1515 1516 /* A username is absolutely required to lock a path. */ 1517 SVN_ERR(get_username(session, pool)); 1518 1519 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 1520 { 1521 const char *abs_path = svn_fspath__join(sess->fs_path->data, 1522 apr_hash_this_key(hi), pool); 1523 svn_revnum_t current_rev = *(svn_revnum_t *)apr_hash_this_val(hi); 1524 svn_fs_lock_target_t *target = svn_fs_lock_target_create(NULL, 1525 current_rev, 1526 pool); 1527 1528 svn_hash_sets(targets, abs_path, target); 1529 } 1530 1531 baton.lock_func = lock_func; 1532 baton.lock_baton = lock_baton; 1533 baton.fs_path = sess->fs_path->data; 1534 baton.is_lock = TRUE; 1535 baton.cb_err = SVN_NO_ERROR; 1536 1537 err = svn_repos_fs_lock_many(sess->repos, targets, comment, 1538 FALSE /* not DAV comment */, 1539 0 /* no expiration */, force, 1540 lock_cb, &baton, 1541 pool, pool); 1542 1543 if (err && baton.cb_err) 1544 svn_error_compose(err, baton.cb_err); 1545 else if (!err) 1546 err = baton.cb_err; 1547 1548 return svn_error_trace(err); 1549} 1550 1551 1552static svn_error_t * 1553svn_ra_local__unlock(svn_ra_session_t *session, 1554 apr_hash_t *path_tokens, 1555 svn_boolean_t force, 1556 svn_ra_lock_callback_t lock_func, 1557 void *lock_baton, 1558 apr_pool_t *pool) 1559{ 1560 svn_ra_local__session_baton_t *sess = session->priv; 1561 apr_hash_t *targets = apr_hash_make(pool); 1562 apr_hash_index_t *hi; 1563 svn_error_t *err; 1564 struct lock_baton_t baton = {0}; 1565 1566 /* A username is absolutely required to unlock a path. */ 1567 SVN_ERR(get_username(session, pool)); 1568 1569 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 1570 { 1571 const char *abs_path = svn_fspath__join(sess->fs_path->data, 1572 apr_hash_this_key(hi), pool); 1573 const char *token = apr_hash_this_val(hi); 1574 1575 svn_hash_sets(targets, abs_path, token); 1576 } 1577 1578 baton.lock_func = lock_func; 1579 baton.lock_baton = lock_baton; 1580 baton.fs_path = sess->fs_path->data; 1581 baton.is_lock = FALSE; 1582 baton.cb_err = SVN_NO_ERROR; 1583 1584 err = svn_repos_fs_unlock_many(sess->repos, targets, force, lock_cb, &baton, 1585 pool, pool); 1586 1587 if (err && baton.cb_err) 1588 svn_error_compose(err, baton.cb_err); 1589 else if (!err) 1590 err = baton.cb_err; 1591 1592 return svn_error_trace(err); 1593} 1594 1595 1596 1597static svn_error_t * 1598svn_ra_local__get_lock(svn_ra_session_t *session, 1599 svn_lock_t **lock, 1600 const char *path, 1601 apr_pool_t *pool) 1602{ 1603 svn_ra_local__session_baton_t *sess = session->priv; 1604 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1605 return svn_fs_get_lock(lock, sess->fs, abs_path, pool); 1606} 1607 1608 1609 1610static svn_error_t * 1611svn_ra_local__get_locks(svn_ra_session_t *session, 1612 apr_hash_t **locks, 1613 const char *path, 1614 svn_depth_t depth, 1615 apr_pool_t *pool) 1616{ 1617 svn_ra_local__session_baton_t *sess = session->priv; 1618 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1619 1620 /* Kinda silly to call the repos wrapper, since we have no authz 1621 func to give it. But heck, why not. */ 1622 return svn_repos_fs_get_locks2(locks, sess->repos, abs_path, depth, 1623 NULL, NULL, pool); 1624} 1625 1626 1627static svn_error_t * 1628svn_ra_local__replay(svn_ra_session_t *session, 1629 svn_revnum_t revision, 1630 svn_revnum_t low_water_mark, 1631 svn_boolean_t send_deltas, 1632 const svn_delta_editor_t *editor, 1633 void *edit_baton, 1634 apr_pool_t *pool) 1635{ 1636 svn_ra_local__session_baton_t *sess = session->priv; 1637 svn_fs_root_t *root; 1638 1639 SVN_ERR(svn_fs_revision_root(&root, svn_repos_fs(sess->repos), 1640 revision, pool)); 1641 return svn_repos_replay2(root, sess->fs_path->data, low_water_mark, 1642 send_deltas, editor, edit_baton, NULL, NULL, 1643 pool); 1644} 1645 1646 1647static svn_error_t * 1648svn_ra_local__replay_range(svn_ra_session_t *session, 1649 svn_revnum_t start_revision, 1650 svn_revnum_t end_revision, 1651 svn_revnum_t low_water_mark, 1652 svn_boolean_t send_deltas, 1653 svn_ra_replay_revstart_callback_t revstart_func, 1654 svn_ra_replay_revfinish_callback_t revfinish_func, 1655 void *replay_baton, 1656 apr_pool_t *pool) 1657{ 1658 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL); 1659} 1660 1661 1662static svn_error_t * 1663svn_ra_local__has_capability(svn_ra_session_t *session, 1664 svn_boolean_t *has, 1665 const char *capability, 1666 apr_pool_t *pool) 1667{ 1668 svn_ra_local__session_baton_t *sess = session->priv; 1669 1670 if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0 1671 || strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0 1672 || strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0 1673 || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0 1674 || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0 1675 || strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0 1676 || strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0 1677 || strcmp(capability, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) == 0 1678 || strcmp(capability, SVN_RA_CAPABILITY_LIST) == 0 1679 ) 1680 { 1681 *has = TRUE; 1682 } 1683 else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0) 1684 { 1685 /* With mergeinfo, the code's capabilities may not reflect the 1686 repository's, so inquire further. */ 1687 SVN_ERR(svn_repos_has_capability(sess->repos, has, 1688 SVN_REPOS_CAPABILITY_MERGEINFO, 1689 pool)); 1690 } 1691 else /* Don't know any other capabilities, so error. */ 1692 { 1693 return svn_error_createf 1694 (SVN_ERR_UNKNOWN_CAPABILITY, NULL, 1695 _("Don't know anything about capability '%s'"), capability); 1696 } 1697 1698 return SVN_NO_ERROR; 1699} 1700 1701static svn_error_t * 1702svn_ra_local__get_deleted_rev(svn_ra_session_t *session, 1703 const char *path, 1704 svn_revnum_t peg_revision, 1705 svn_revnum_t end_revision, 1706 svn_revnum_t *revision_deleted, 1707 apr_pool_t *pool) 1708{ 1709 svn_ra_local__session_baton_t *sess = session->priv; 1710 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1711 1712 SVN_ERR(svn_repos_deleted_rev(sess->fs, 1713 abs_path, 1714 peg_revision, 1715 end_revision, 1716 revision_deleted, 1717 pool)); 1718 1719 return SVN_NO_ERROR; 1720} 1721 1722static svn_error_t * 1723svn_ra_local__get_inherited_props(svn_ra_session_t *session, 1724 apr_array_header_t **iprops, 1725 const char *path, 1726 svn_revnum_t revision, 1727 apr_pool_t *result_pool, 1728 apr_pool_t *scratch_pool) 1729{ 1730 svn_fs_root_t *root; 1731 svn_ra_local__session_baton_t *sess = session->priv; 1732 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, 1733 scratch_pool); 1734 svn_node_kind_t node_kind; 1735 1736 /* Open the revision's root. */ 1737 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, scratch_pool)); 1738 1739 SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, scratch_pool)); 1740 if (node_kind == svn_node_none) 1741 { 1742 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1743 _("'%s' path not found"), abs_path); 1744 } 1745 1746 return svn_error_trace( 1747 svn_repos_fs_get_inherited_props(iprops, root, abs_path, 1748 NULL /* propname */, 1749 NULL, NULL /* auth */, 1750 result_pool, scratch_pool)); 1751} 1752 1753static svn_error_t * 1754svn_ra_local__register_editor_shim_callbacks(svn_ra_session_t *session, 1755 svn_delta_shim_callbacks_t *callbacks) 1756{ 1757 /* This is currenly a no-op, since we don't provide our own editor, just 1758 use the one the libsvn_repos hands back to us. */ 1759 return SVN_NO_ERROR; 1760} 1761 1762 1763static svn_error_t * 1764svn_ra_local__get_commit_ev2(svn_editor_t **editor, 1765 svn_ra_session_t *session, 1766 apr_hash_t *revprops, 1767 svn_commit_callback2_t commit_cb, 1768 void *commit_baton, 1769 apr_hash_t *lock_tokens, 1770 svn_boolean_t keep_locks, 1771 svn_ra__provide_base_cb_t provide_base_cb, 1772 svn_ra__provide_props_cb_t provide_props_cb, 1773 svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb, 1774 void *cb_baton, 1775 svn_cancel_func_t cancel_func, 1776 void *cancel_baton, 1777 apr_pool_t *result_pool, 1778 apr_pool_t *scratch_pool) 1779{ 1780 svn_ra_local__session_baton_t *sess = session->priv; 1781 struct deltify_etc_baton *deb = apr_palloc(result_pool, sizeof(*deb)); 1782 1783 remap_commit_callback(&commit_cb, &commit_baton, session, 1784 commit_cb, commit_baton, result_pool); 1785 1786 /* NOTE: the RA callbacks are ignored. We pass everything directly to 1787 the REPOS editor. */ 1788 1789 /* Prepare the baton for deltify_etc() */ 1790 deb->fs = sess->fs; 1791 deb->repos = sess->repos; 1792 deb->fspath_base = sess->fs_path->data; 1793 if (! keep_locks) 1794 deb->lock_tokens = lock_tokens; 1795 else 1796 deb->lock_tokens = NULL; 1797 deb->commit_cb = commit_cb; 1798 deb->commit_baton = commit_baton; 1799 1800 /* Ensure there is a username (and an FS access context) associated with 1801 the session and its FS handle. */ 1802 SVN_ERR(get_username(session, scratch_pool)); 1803 1804 /* If there are lock tokens to add, do so. */ 1805 SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens, 1806 session->pool, scratch_pool)); 1807 1808 /* Copy the REVPROPS and insert the author/username. */ 1809 revprops = apr_hash_copy(scratch_pool, revprops); 1810 svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR, 1811 svn_string_create(sess->username, scratch_pool)); 1812 1813 return svn_error_trace(svn_repos__get_commit_ev2( 1814 editor, sess->repos, NULL /* authz */, 1815 NULL /* authz_repos_name */, NULL /* authz_user */, 1816 revprops, 1817 deltify_etc, deb, cancel_func, cancel_baton, 1818 result_pool, scratch_pool)); 1819} 1820 1821/* Trivially forward repos-layer callbacks to RA-layer callbacks. 1822 * Their signatures are the same. */ 1823typedef struct dirent_receiver_baton_t 1824{ 1825 svn_ra_dirent_receiver_t receiver; 1826 void *receiver_baton; 1827} dirent_receiver_baton_t; 1828 1829static svn_error_t * 1830dirent_receiver(const char *rel_path, 1831 svn_dirent_t *dirent, 1832 void *baton, 1833 apr_pool_t *pool) 1834{ 1835 dirent_receiver_baton_t *b = baton; 1836 return b->receiver(rel_path, dirent, b->receiver_baton, pool); 1837} 1838 1839static svn_error_t * 1840svn_ra_local__list(svn_ra_session_t *session, 1841 const char *path, 1842 svn_revnum_t revision, 1843 const apr_array_header_t *patterns, 1844 svn_depth_t depth, 1845 apr_uint32_t dirent_fields, 1846 svn_ra_dirent_receiver_t receiver, 1847 void *receiver_baton, 1848 apr_pool_t *pool) 1849{ 1850 svn_ra_local__session_baton_t *sess = session->priv; 1851 svn_fs_root_t *root; 1852 svn_boolean_t path_info_only = (dirent_fields & ~SVN_DIRENT_KIND) == 0; 1853 1854 dirent_receiver_baton_t baton; 1855 baton.receiver = receiver; 1856 baton.receiver_baton = receiver_baton; 1857 1858 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1859 path = svn_dirent_join(sess->fs_path->data, path, pool); 1860 return svn_error_trace(svn_repos_list(root, path, patterns, depth, 1861 path_info_only, NULL, NULL, 1862 dirent_receiver, &baton, 1863 sess->callbacks 1864 ? sess->callbacks->cancel_func 1865 : NULL, 1866 sess->callback_baton, pool)); 1867} 1868 1869/*----------------------------------------------------------------*/ 1870 1871static const svn_version_t * 1872ra_local_version(void) 1873{ 1874 SVN_VERSION_BODY; 1875} 1876 1877/** The ra_vtable **/ 1878 1879static const svn_ra__vtable_t ra_local_vtable = 1880{ 1881 ra_local_version, 1882 svn_ra_local__get_description, 1883 svn_ra_local__get_schemes, 1884 svn_ra_local__open, 1885 svn_ra_local__dup_session, 1886 svn_ra_local__reparent, 1887 svn_ra_local__get_session_url, 1888 svn_ra_local__get_latest_revnum, 1889 svn_ra_local__get_dated_revision, 1890 svn_ra_local__change_rev_prop, 1891 svn_ra_local__rev_proplist, 1892 svn_ra_local__rev_prop, 1893 svn_ra_local__get_commit_editor, 1894 svn_ra_local__get_file, 1895 svn_ra_local__get_dir, 1896 svn_ra_local__get_mergeinfo, 1897 svn_ra_local__do_update, 1898 svn_ra_local__do_switch, 1899 svn_ra_local__do_status, 1900 svn_ra_local__do_diff, 1901 svn_ra_local__get_log, 1902 svn_ra_local__do_check_path, 1903 svn_ra_local__stat, 1904 svn_ra_local__get_uuid, 1905 svn_ra_local__get_repos_root, 1906 svn_ra_local__get_locations, 1907 svn_ra_local__get_location_segments, 1908 svn_ra_local__get_file_revs, 1909 svn_ra_local__lock, 1910 svn_ra_local__unlock, 1911 svn_ra_local__get_lock, 1912 svn_ra_local__get_locks, 1913 svn_ra_local__replay, 1914 svn_ra_local__has_capability, 1915 svn_ra_local__replay_range, 1916 svn_ra_local__get_deleted_rev, 1917 svn_ra_local__get_inherited_props, 1918 NULL /* set_svn_ra_open */, 1919 svn_ra_local__list , 1920 svn_ra_local__register_editor_shim_callbacks, 1921 svn_ra_local__get_commit_ev2, 1922 NULL /* replay_range_ev2 */ 1923}; 1924 1925 1926/*----------------------------------------------------------------*/ 1927 1928/** The One Public Routine, called by libsvn_ra **/ 1929 1930svn_error_t * 1931svn_ra_local__init(const svn_version_t *loader_version, 1932 const svn_ra__vtable_t **vtable, 1933 apr_pool_t *pool) 1934{ 1935 static const svn_version_checklist_t checklist[] = 1936 { 1937 { "svn_subr", svn_subr_version }, 1938 { "svn_delta", svn_delta_version }, 1939 { "svn_repos", svn_repos_version }, 1940 { "svn_fs", svn_fs_version }, 1941 { NULL, NULL } 1942 }; 1943 1944 1945 /* Simplified version check to make sure we can safely use the 1946 VTABLE parameter. The RA loader does a more exhaustive check. */ 1947 if (loader_version->major != SVN_VER_MAJOR) 1948 return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL, 1949 _("Unsupported RA loader version (%d) for " 1950 "ra_local"), 1951 loader_version->major); 1952 1953 SVN_ERR(svn_ver_check_list2(ra_local_version(), checklist, svn_ver_equal)); 1954 1955#ifndef SVN_LIBSVN_RA_LINKS_RA_LOCAL 1956 /* This means the library was loaded as a DSO, so use the DSO pool. */ 1957 SVN_ERR(svn_fs_initialize(svn_dso__pool())); 1958#endif 1959 1960 *vtable = &ra_local_vtable; 1961 1962 return SVN_NO_ERROR; 1963} 1964 1965/* Compatibility wrapper for the 1.1 and before API. */ 1966#define NAME "ra_local" 1967#define DESCRIPTION RA_LOCAL_DESCRIPTION 1968#define VTBL ra_local_vtable 1969#define INITFUNC svn_ra_local__init 1970#define COMPAT_INITFUNC svn_ra_local_init 1971#include "../libsvn_ra/wrapper_template.h" 1972