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->callbacks->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->callbacks->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, (char *)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 (char *)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 1024 * 1024, /* process-local transfers 364 should be fast */ 365 result_pool)); 366 367 /* Wrap the report baton given us by the repos layer with our own 368 reporter baton. */ 369 *report_baton = make_reporter_baton(sess, rbaton, result_pool); 370 371 return SVN_NO_ERROR; 372} 373 374 375/*----------------------------------------------------------------*/ 376 377/*** Deltification stuff for get_commit_editor() ***/ 378 379struct deltify_etc_baton 380{ 381 svn_fs_t *fs; /* the fs to deltify in */ 382 svn_repos_t *repos; /* repos for unlocking */ 383 const char *fspath_base; /* fs-path part of split session URL */ 384 385 apr_hash_t *lock_tokens; /* tokens to unlock, if any */ 386 387 svn_commit_callback2_t commit_cb; /* the original callback */ 388 void *commit_baton; /* the original callback's baton */ 389}; 390 391/* This implements 'svn_commit_callback_t'. Its invokes the original 392 (wrapped) callback, but also does deltification on the new revision and 393 possibly unlocks committed paths. 394 BATON is 'struct deltify_etc_baton *'. */ 395static svn_error_t * 396deltify_etc(const svn_commit_info_t *commit_info, 397 void *baton, 398 apr_pool_t *scratch_pool) 399{ 400 struct deltify_etc_baton *deb = baton; 401 svn_error_t *err1 = SVN_NO_ERROR; 402 svn_error_t *err2; 403 404 /* Invoke the original callback first, in case someone's waiting to 405 know the revision number so they can go off and annotate an 406 issue or something. */ 407 if (deb->commit_cb) 408 err1 = deb->commit_cb(commit_info, deb->commit_baton, scratch_pool); 409 410 /* Maybe unlock the paths. */ 411 if (deb->lock_tokens) 412 { 413 apr_pool_t *iterpool = svn_pool_create(scratch_pool); 414 apr_hash_index_t *hi; 415 416 for (hi = apr_hash_first(scratch_pool, deb->lock_tokens); hi; 417 hi = apr_hash_next(hi)) 418 { 419 const void *relpath = svn__apr_hash_index_key(hi); 420 const char *token = svn__apr_hash_index_val(hi); 421 const char *fspath; 422 423 svn_pool_clear(iterpool); 424 425 fspath = svn_fspath__join(deb->fspath_base, relpath, iterpool); 426 427 /* We may get errors here if the lock was broken or stolen 428 after the commit succeeded. This is fine and should be 429 ignored. */ 430 svn_error_clear(svn_repos_fs_unlock(deb->repos, fspath, token, 431 FALSE, iterpool)); 432 } 433 434 svn_pool_destroy(iterpool); 435 } 436 437 /* But, deltification shouldn't be stopped just because someone's 438 random callback failed, so proceed unconditionally on to 439 deltification. */ 440 err2 = svn_fs_deltify_revision(deb->fs, commit_info->revision, scratch_pool); 441 442 return svn_error_compose_create(err1, err2); 443} 444 445 446/* If LOCK_TOKENS is not NULL, then copy all tokens into the access context 447 of FS. The tokens' paths will be prepended with FSPATH_BASE. 448 449 ACCESS_POOL must match (or exceed) the lifetime of the access context 450 that was associated with FS. Typically, this is the session pool. 451 452 Temporary allocations are made in SCRATCH_POOL. */ 453static svn_error_t * 454apply_lock_tokens(svn_fs_t *fs, 455 const char *fspath_base, 456 apr_hash_t *lock_tokens, 457 apr_pool_t *access_pool, 458 apr_pool_t *scratch_pool) 459{ 460 if (lock_tokens) 461 { 462 svn_fs_access_t *access_ctx; 463 464 SVN_ERR(svn_fs_get_access(&access_ctx, fs)); 465 466 /* If there is no access context, the filesystem will scream if a 467 lock is needed. */ 468 if (access_ctx) 469 { 470 apr_hash_index_t *hi; 471 472 /* Note: we have no use for an iterpool here since the data 473 within the loop is copied into ACCESS_POOL. */ 474 475 for (hi = apr_hash_first(scratch_pool, lock_tokens); hi; 476 hi = apr_hash_next(hi)) 477 { 478 const void *relpath = svn__apr_hash_index_key(hi); 479 const char *token = svn__apr_hash_index_val(hi); 480 const char *fspath; 481 482 /* The path needs to live as long as ACCESS_CTX. */ 483 fspath = svn_fspath__join(fspath_base, relpath, access_pool); 484 485 /* The token must live as long as ACCESS_CTX. */ 486 token = apr_pstrdup(access_pool, token); 487 488 SVN_ERR(svn_fs_access_add_lock_token2(access_ctx, fspath, 489 token)); 490 } 491 } 492 } 493 494 return SVN_NO_ERROR; 495} 496 497 498/*----------------------------------------------------------------*/ 499 500/*** The RA vtable routines ***/ 501 502#define RA_LOCAL_DESCRIPTION \ 503 N_("Module for accessing a repository on local disk.") 504 505static const char * 506svn_ra_local__get_description(apr_pool_t *pool) 507{ 508 return _(RA_LOCAL_DESCRIPTION); 509} 510 511static const char * const * 512svn_ra_local__get_schemes(apr_pool_t *pool) 513{ 514 static const char *schemes[] = { "file", NULL }; 515 516 return schemes; 517} 518 519/* Do nothing. 520 * 521 * Why is this acceptable? FS warnings used to be used for only 522 * two things: failures to close BDB repositories and failures to 523 * interact with memcached in FSFS (new in 1.6). In 1.5 and earlier, 524 * we did not call svn_fs_set_warning_func in ra_local, which means 525 * that any BDB-closing failure would have led to abort()s; the fact 526 * that this hasn't led to huge hues and cries makes it seem likely 527 * that this just doesn't happen that often, at least not through 528 * ra_local. And as far as memcached goes, it seems unlikely that 529 * somebody is going to go through the trouble of setting up and 530 * running memcached servers but then use ra_local access. So we 531 * ignore errors here, so that memcached can use the FS warnings API 532 * without crashing ra_local. 533 */ 534static void 535ignore_warnings(void *baton, 536 svn_error_t *err) 537{ 538#ifdef SVN_DEBUG 539 SVN_DBG(("Ignoring FS warning %d\n", err ? err->apr_err : 0)); 540#endif 541 return; 542} 543 544static svn_error_t * 545svn_ra_local__open(svn_ra_session_t *session, 546 const char **corrected_url, 547 const char *repos_URL, 548 const svn_ra_callbacks2_t *callbacks, 549 void *callback_baton, 550 apr_hash_t *config, 551 apr_pool_t *pool) 552{ 553 svn_ra_local__session_baton_t *sess; 554 const char *fs_path; 555 static volatile svn_atomic_t cache_init_state = 0; 556 557 /* Initialise the FSFS memory cache size. We can only do this once 558 so one CONFIG will win the race and all others will be ignored 559 silently. */ 560 SVN_ERR(svn_atomic__init_once(&cache_init_state, cache_init, config, pool)); 561 562 /* We don't support redirections in ra-local. */ 563 if (corrected_url) 564 *corrected_url = NULL; 565 566 /* Allocate and stash the session_sess args we have already. */ 567 sess = apr_pcalloc(pool, sizeof(*sess)); 568 sess->callbacks = callbacks; 569 sess->callback_baton = callback_baton; 570 571 /* Look through the URL, figure out which part points to the 572 repository, and which part is the path *within* the 573 repository. */ 574 SVN_ERR_W(svn_ra_local__split_URL(&(sess->repos), 575 &(sess->repos_url), 576 &fs_path, 577 repos_URL, 578 session->pool), 579 _("Unable to open an ra_local session to URL")); 580 sess->fs_path = svn_stringbuf_create(fs_path, session->pool); 581 582 /* Cache the filesystem object from the repos here for 583 convenience. */ 584 sess->fs = svn_repos_fs(sess->repos); 585 586 /* Ignore FS warnings. */ 587 svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL); 588 589 /* Cache the repository UUID as well */ 590 SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool)); 591 592 /* Be sure username is NULL so we know to look it up / ask for it */ 593 sess->username = NULL; 594 595 session->priv = sess; 596 return SVN_NO_ERROR; 597} 598 599static svn_error_t * 600svn_ra_local__reparent(svn_ra_session_t *session, 601 const char *url, 602 apr_pool_t *pool) 603{ 604 svn_ra_local__session_baton_t *sess = session->priv; 605 const char *relpath = svn_uri_skip_ancestor(sess->repos_url, url, pool); 606 607 /* If the new URL isn't the same as our repository root URL, then 608 let's ensure that it's some child of it. */ 609 if (! relpath) 610 return svn_error_createf 611 (SVN_ERR_RA_ILLEGAL_URL, NULL, 612 _("URL '%s' is not a child of the session's repository root " 613 "URL '%s'"), url, sess->repos_url); 614 615 /* Update our FS_PATH sess member to point to our new 616 relative-URL-turned-absolute-filesystem-path. */ 617 svn_stringbuf_set(sess->fs_path, 618 svn_fspath__canonicalize(relpath, pool)); 619 620 return SVN_NO_ERROR; 621} 622 623static svn_error_t * 624svn_ra_local__get_session_url(svn_ra_session_t *session, 625 const char **url, 626 apr_pool_t *pool) 627{ 628 svn_ra_local__session_baton_t *sess = session->priv; 629 *url = svn_path_url_add_component2(sess->repos_url, 630 sess->fs_path->data + 1, 631 pool); 632 return SVN_NO_ERROR; 633} 634 635static svn_error_t * 636svn_ra_local__get_latest_revnum(svn_ra_session_t *session, 637 svn_revnum_t *latest_revnum, 638 apr_pool_t *pool) 639{ 640 svn_ra_local__session_baton_t *sess = session->priv; 641 return svn_fs_youngest_rev(latest_revnum, sess->fs, pool); 642} 643 644static svn_error_t * 645svn_ra_local__get_file_revs(svn_ra_session_t *session, 646 const char *path, 647 svn_revnum_t start, 648 svn_revnum_t end, 649 svn_boolean_t include_merged_revisions, 650 svn_file_rev_handler_t handler, 651 void *handler_baton, 652 apr_pool_t *pool) 653{ 654 svn_ra_local__session_baton_t *sess = session->priv; 655 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 656 return svn_repos_get_file_revs2(sess->repos, abs_path, start, end, 657 include_merged_revisions, NULL, NULL, 658 handler, handler_baton, pool); 659} 660 661static svn_error_t * 662svn_ra_local__get_dated_revision(svn_ra_session_t *session, 663 svn_revnum_t *revision, 664 apr_time_t tm, 665 apr_pool_t *pool) 666{ 667 svn_ra_local__session_baton_t *sess = session->priv; 668 return svn_repos_dated_revision(revision, sess->repos, tm, pool); 669} 670 671 672static svn_error_t * 673svn_ra_local__change_rev_prop(svn_ra_session_t *session, 674 svn_revnum_t rev, 675 const char *name, 676 const svn_string_t *const *old_value_p, 677 const svn_string_t *value, 678 apr_pool_t *pool) 679{ 680 svn_ra_local__session_baton_t *sess = session->priv; 681 682 SVN_ERR(get_username(session, pool)); 683 return svn_repos_fs_change_rev_prop4(sess->repos, rev, sess->username, 684 name, old_value_p, value, TRUE, TRUE, 685 NULL, NULL, pool); 686} 687 688static svn_error_t * 689svn_ra_local__get_uuid(svn_ra_session_t *session, 690 const char **uuid, 691 apr_pool_t *pool) 692{ 693 svn_ra_local__session_baton_t *sess = session->priv; 694 *uuid = sess->uuid; 695 return SVN_NO_ERROR; 696} 697 698static svn_error_t * 699svn_ra_local__get_repos_root(svn_ra_session_t *session, 700 const char **url, 701 apr_pool_t *pool) 702{ 703 svn_ra_local__session_baton_t *sess = session->priv; 704 *url = sess->repos_url; 705 return SVN_NO_ERROR; 706} 707 708static svn_error_t * 709svn_ra_local__rev_proplist(svn_ra_session_t *session, 710 svn_revnum_t rev, 711 apr_hash_t **props, 712 apr_pool_t *pool) 713{ 714 svn_ra_local__session_baton_t *sess = session->priv; 715 return svn_repos_fs_revision_proplist(props, sess->repos, rev, 716 NULL, NULL, pool); 717} 718 719static svn_error_t * 720svn_ra_local__rev_prop(svn_ra_session_t *session, 721 svn_revnum_t rev, 722 const char *name, 723 svn_string_t **value, 724 apr_pool_t *pool) 725{ 726 svn_ra_local__session_baton_t *sess = session->priv; 727 return svn_repos_fs_revision_prop(value, sess->repos, rev, name, 728 NULL, NULL, pool); 729} 730 731static svn_error_t * 732svn_ra_local__get_commit_editor(svn_ra_session_t *session, 733 const svn_delta_editor_t **editor, 734 void **edit_baton, 735 apr_hash_t *revprop_table, 736 svn_commit_callback2_t callback, 737 void *callback_baton, 738 apr_hash_t *lock_tokens, 739 svn_boolean_t keep_locks, 740 apr_pool_t *pool) 741{ 742 svn_ra_local__session_baton_t *sess = session->priv; 743 struct deltify_etc_baton *deb = apr_palloc(pool, sizeof(*deb)); 744 745 /* Prepare the baton for deltify_etc() */ 746 deb->fs = sess->fs; 747 deb->repos = sess->repos; 748 deb->fspath_base = sess->fs_path->data; 749 if (! keep_locks) 750 deb->lock_tokens = lock_tokens; 751 else 752 deb->lock_tokens = NULL; 753 deb->commit_cb = callback; 754 deb->commit_baton = callback_baton; 755 756 SVN_ERR(get_username(session, pool)); 757 758 /* If there are lock tokens to add, do so. */ 759 SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens, 760 session->pool, pool)); 761 762 /* Copy the revprops table so we can add the username. */ 763 revprop_table = apr_hash_copy(pool, revprop_table); 764 svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR, 765 svn_string_create(sess->username, pool)); 766 svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION, 767 svn_string_create(SVN_VER_NUMBER, pool)); 768 769 /* Get the repos commit-editor */ 770 return svn_repos_get_commit_editor5 771 (editor, edit_baton, sess->repos, NULL, 772 svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data, 773 revprop_table, deltify_etc, deb, NULL, NULL, pool); 774} 775 776 777static svn_error_t * 778svn_ra_local__get_mergeinfo(svn_ra_session_t *session, 779 svn_mergeinfo_catalog_t *catalog, 780 const apr_array_header_t *paths, 781 svn_revnum_t revision, 782 svn_mergeinfo_inheritance_t inherit, 783 svn_boolean_t include_descendants, 784 apr_pool_t *pool) 785{ 786 svn_ra_local__session_baton_t *sess = session->priv; 787 svn_mergeinfo_catalog_t tmp_catalog; 788 int i; 789 apr_array_header_t *abs_paths = 790 apr_array_make(pool, 0, sizeof(const char *)); 791 792 for (i = 0; i < paths->nelts; i++) 793 { 794 const char *relative_path = APR_ARRAY_IDX(paths, i, const char *); 795 APR_ARRAY_PUSH(abs_paths, const char *) = 796 svn_fspath__join(sess->fs_path->data, relative_path, pool); 797 } 798 799 SVN_ERR(svn_repos_fs_get_mergeinfo(&tmp_catalog, sess->repos, abs_paths, 800 revision, inherit, include_descendants, 801 NULL, NULL, pool)); 802 if (apr_hash_count(tmp_catalog) > 0) 803 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog, 804 tmp_catalog, 805 sess->fs_path->data, 806 pool)); 807 else 808 *catalog = NULL; 809 810 return SVN_NO_ERROR; 811} 812 813 814static svn_error_t * 815svn_ra_local__do_update(svn_ra_session_t *session, 816 const svn_ra_reporter3_t **reporter, 817 void **report_baton, 818 svn_revnum_t update_revision, 819 const char *update_target, 820 svn_depth_t depth, 821 svn_boolean_t send_copyfrom_args, 822 svn_boolean_t ignore_ancestry, 823 const svn_delta_editor_t *update_editor, 824 void *update_baton, 825 apr_pool_t *result_pool, 826 apr_pool_t *scratch_pool) 827{ 828 return make_reporter(session, 829 reporter, 830 report_baton, 831 update_revision, 832 update_target, 833 NULL, 834 TRUE, 835 depth, 836 send_copyfrom_args, 837 ignore_ancestry, 838 update_editor, 839 update_baton, 840 result_pool, scratch_pool); 841} 842 843 844static svn_error_t * 845svn_ra_local__do_switch(svn_ra_session_t *session, 846 const svn_ra_reporter3_t **reporter, 847 void **report_baton, 848 svn_revnum_t update_revision, 849 const char *update_target, 850 svn_depth_t depth, 851 const char *switch_url, 852 svn_boolean_t send_copyfrom_args, 853 svn_boolean_t ignore_ancestry, 854 const svn_delta_editor_t *update_editor, 855 void *update_baton, 856 apr_pool_t *result_pool, 857 apr_pool_t *scratch_pool) 858{ 859 return make_reporter(session, 860 reporter, 861 report_baton, 862 update_revision, 863 update_target, 864 switch_url, 865 TRUE /* text_deltas */, 866 depth, 867 send_copyfrom_args, 868 ignore_ancestry, 869 update_editor, 870 update_baton, 871 result_pool, scratch_pool); 872} 873 874 875static svn_error_t * 876svn_ra_local__do_status(svn_ra_session_t *session, 877 const svn_ra_reporter3_t **reporter, 878 void **report_baton, 879 const char *status_target, 880 svn_revnum_t revision, 881 svn_depth_t depth, 882 const svn_delta_editor_t *status_editor, 883 void *status_baton, 884 apr_pool_t *pool) 885{ 886 return make_reporter(session, 887 reporter, 888 report_baton, 889 revision, 890 status_target, 891 NULL, 892 FALSE, 893 depth, 894 FALSE, 895 FALSE, 896 status_editor, 897 status_baton, 898 pool, pool); 899} 900 901 902static svn_error_t * 903svn_ra_local__do_diff(svn_ra_session_t *session, 904 const svn_ra_reporter3_t **reporter, 905 void **report_baton, 906 svn_revnum_t update_revision, 907 const char *update_target, 908 svn_depth_t depth, 909 svn_boolean_t ignore_ancestry, 910 svn_boolean_t text_deltas, 911 const char *switch_url, 912 const svn_delta_editor_t *update_editor, 913 void *update_baton, 914 apr_pool_t *pool) 915{ 916 return make_reporter(session, 917 reporter, 918 report_baton, 919 update_revision, 920 update_target, 921 switch_url, 922 text_deltas, 923 depth, 924 FALSE, 925 ignore_ancestry, 926 update_editor, 927 update_baton, 928 pool, pool); 929} 930 931 932struct log_baton 933{ 934 svn_ra_local__session_baton_t *sess; 935 svn_log_entry_receiver_t real_cb; 936 void *real_baton; 937}; 938 939static svn_error_t * 940log_receiver_wrapper(void *baton, 941 svn_log_entry_t *log_entry, 942 apr_pool_t *pool) 943{ 944 struct log_baton *b = baton; 945 svn_ra_local__session_baton_t *sess = b->sess; 946 947 if (sess->callbacks->cancel_func) 948 SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton)); 949 950 /* For consistency with the other RA layers, replace an empty 951 changed-paths hash with a NULL one. 952 953 ### Should this be done by svn_ra_get_log2() instead, then? */ 954 if ((log_entry->changed_paths2) 955 && (apr_hash_count(log_entry->changed_paths2) == 0)) 956 { 957 log_entry->changed_paths = NULL; 958 log_entry->changed_paths2 = NULL; 959 } 960 961 return b->real_cb(b->real_baton, log_entry, pool); 962} 963 964 965static svn_error_t * 966svn_ra_local__get_log(svn_ra_session_t *session, 967 const apr_array_header_t *paths, 968 svn_revnum_t start, 969 svn_revnum_t end, 970 int limit, 971 svn_boolean_t discover_changed_paths, 972 svn_boolean_t strict_node_history, 973 svn_boolean_t include_merged_revisions, 974 const apr_array_header_t *revprops, 975 svn_log_entry_receiver_t receiver, 976 void *receiver_baton, 977 apr_pool_t *pool) 978{ 979 svn_ra_local__session_baton_t *sess = session->priv; 980 struct log_baton lb; 981 apr_array_header_t *abs_paths = 982 apr_array_make(pool, 0, sizeof(const char *)); 983 984 if (paths) 985 { 986 int i; 987 988 for (i = 0; i < paths->nelts; i++) 989 { 990 const char *relative_path = APR_ARRAY_IDX(paths, i, const char *); 991 APR_ARRAY_PUSH(abs_paths, const char *) = 992 svn_fspath__join(sess->fs_path->data, relative_path, pool); 993 } 994 } 995 996 lb.real_cb = receiver; 997 lb.real_baton = receiver_baton; 998 lb.sess = sess; 999 receiver = log_receiver_wrapper; 1000 receiver_baton = &lb; 1001 1002 return svn_repos_get_logs4(sess->repos, 1003 abs_paths, 1004 start, 1005 end, 1006 limit, 1007 discover_changed_paths, 1008 strict_node_history, 1009 include_merged_revisions, 1010 revprops, 1011 NULL, NULL, 1012 receiver, 1013 receiver_baton, 1014 pool); 1015} 1016 1017 1018static svn_error_t * 1019svn_ra_local__do_check_path(svn_ra_session_t *session, 1020 const char *path, 1021 svn_revnum_t revision, 1022 svn_node_kind_t *kind, 1023 apr_pool_t *pool) 1024{ 1025 svn_ra_local__session_baton_t *sess = session->priv; 1026 svn_fs_root_t *root; 1027 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1028 1029 if (! SVN_IS_VALID_REVNUM(revision)) 1030 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool)); 1031 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1032 return svn_fs_check_path(kind, root, abs_path, pool); 1033} 1034 1035 1036static svn_error_t * 1037svn_ra_local__stat(svn_ra_session_t *session, 1038 const char *path, 1039 svn_revnum_t revision, 1040 svn_dirent_t **dirent, 1041 apr_pool_t *pool) 1042{ 1043 svn_ra_local__session_baton_t *sess = session->priv; 1044 svn_fs_root_t *root; 1045 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1046 1047 if (! SVN_IS_VALID_REVNUM(revision)) 1048 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool)); 1049 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1050 1051 return svn_repos_stat(dirent, root, abs_path, pool); 1052} 1053 1054 1055 1056 1057static svn_error_t * 1058get_node_props(apr_hash_t **props, 1059 apr_array_header_t **inherited_props, 1060 svn_ra_local__session_baton_t *sess, 1061 svn_fs_root_t *root, 1062 const char *path, 1063 apr_pool_t *result_pool, 1064 apr_pool_t *scratch_pool) 1065{ 1066 svn_revnum_t cmt_rev; 1067 const char *cmt_date, *cmt_author; 1068 1069 /* Create a hash with props attached to the fs node. */ 1070 if (props) 1071 { 1072 SVN_ERR(svn_fs_node_proplist(props, root, path, result_pool)); 1073 } 1074 1075 /* Get inherited properties if requested. */ 1076 if (inherited_props) 1077 { 1078 SVN_ERR(svn_repos_fs_get_inherited_props(inherited_props, root, path, 1079 NULL, NULL, NULL, 1080 result_pool, scratch_pool)); 1081 } 1082 1083 /* Now add some non-tweakable metadata to the hash as well... */ 1084 1085 if (props) 1086 { 1087 /* The so-called 'entryprops' with info about CR & friends. */ 1088 SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date, 1089 &cmt_author, root, path, 1090 scratch_pool)); 1091 1092 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV, 1093 svn_string_createf(result_pool, "%ld", cmt_rev)); 1094 svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, cmt_date ? 1095 svn_string_create(cmt_date, result_pool) :NULL); 1096 svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, cmt_author ? 1097 svn_string_create(cmt_author, result_pool) :NULL); 1098 svn_hash_sets(*props, SVN_PROP_ENTRY_UUID, 1099 svn_string_create(sess->uuid, result_pool)); 1100 1101 /* We have no 'wcprops' in ra_local, but might someday. */ 1102 } 1103 1104 return SVN_NO_ERROR; 1105} 1106 1107 1108/* Getting just one file. */ 1109static svn_error_t * 1110svn_ra_local__get_file(svn_ra_session_t *session, 1111 const char *path, 1112 svn_revnum_t revision, 1113 svn_stream_t *stream, 1114 svn_revnum_t *fetched_rev, 1115 apr_hash_t **props, 1116 apr_pool_t *pool) 1117{ 1118 svn_fs_root_t *root; 1119 svn_stream_t *contents; 1120 svn_revnum_t youngest_rev; 1121 svn_ra_local__session_baton_t *sess = session->priv; 1122 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1123 svn_node_kind_t node_kind; 1124 1125 /* Open the revision's root. */ 1126 if (! SVN_IS_VALID_REVNUM(revision)) 1127 { 1128 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool)); 1129 SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool)); 1130 if (fetched_rev != NULL) 1131 *fetched_rev = youngest_rev; 1132 } 1133 else 1134 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1135 1136 SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, pool)); 1137 if (node_kind == svn_node_none) 1138 { 1139 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1140 _("'%s' path not found"), abs_path); 1141 } 1142 else if (node_kind != svn_node_file) 1143 { 1144 return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL, 1145 _("'%s' is not a file"), abs_path); 1146 } 1147 1148 if (stream) 1149 { 1150 /* Get a stream representing the file's contents. */ 1151 SVN_ERR(svn_fs_file_contents(&contents, root, abs_path, pool)); 1152 1153 /* Now push data from the fs stream back at the caller's stream. 1154 Note that this particular RA layer does not computing a 1155 checksum as we go, and confirming it against the repository's 1156 checksum when done. That's because it calls 1157 svn_fs_file_contents() directly, which already checks the 1158 stored checksum, and all we're doing here is writing bytes in 1159 a loop. Truly, Nothing Can Go Wrong :-). But RA layers that 1160 go over a network should confirm the checksum. 1161 1162 Note: we are not supposed to close the passed-in stream, so 1163 disown the thing. 1164 */ 1165 SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(stream, pool), 1166 sess->callbacks 1167 ? sess->callbacks->cancel_func : NULL, 1168 sess->callback_baton, 1169 pool)); 1170 } 1171 1172 /* Handle props if requested. */ 1173 if (props) 1174 SVN_ERR(get_node_props(props, NULL, sess, root, abs_path, pool, pool)); 1175 1176 return SVN_NO_ERROR; 1177} 1178 1179 1180 1181/* Getting a directory's entries */ 1182static svn_error_t * 1183svn_ra_local__get_dir(svn_ra_session_t *session, 1184 apr_hash_t **dirents, 1185 svn_revnum_t *fetched_rev, 1186 apr_hash_t **props, 1187 const char *path, 1188 svn_revnum_t revision, 1189 apr_uint32_t dirent_fields, 1190 apr_pool_t *pool) 1191{ 1192 svn_fs_root_t *root; 1193 svn_revnum_t youngest_rev; 1194 apr_hash_t *entries; 1195 apr_hash_index_t *hi; 1196 svn_ra_local__session_baton_t *sess = session->priv; 1197 apr_pool_t *subpool; 1198 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1199 1200 /* Open the revision's root. */ 1201 if (! SVN_IS_VALID_REVNUM(revision)) 1202 { 1203 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool)); 1204 SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool)); 1205 if (fetched_rev != NULL) 1206 *fetched_rev = youngest_rev; 1207 } 1208 else 1209 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool)); 1210 1211 if (dirents) 1212 { 1213 /* Get the dir's entries. */ 1214 SVN_ERR(svn_fs_dir_entries(&entries, root, abs_path, pool)); 1215 1216 /* Loop over the fs dirents, and build a hash of general 1217 svn_dirent_t's. */ 1218 *dirents = apr_hash_make(pool); 1219 subpool = svn_pool_create(pool); 1220 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) 1221 { 1222 const void *key; 1223 void *val; 1224 apr_hash_t *prophash; 1225 const char *datestring, *entryname, *fullpath; 1226 svn_fs_dirent_t *fs_entry; 1227 svn_dirent_t *entry = svn_dirent_create(pool); 1228 1229 svn_pool_clear(subpool); 1230 1231 apr_hash_this(hi, &key, NULL, &val); 1232 entryname = (const char *) key; 1233 fs_entry = (svn_fs_dirent_t *) val; 1234 1235 fullpath = svn_dirent_join(abs_path, entryname, subpool); 1236 1237 if (dirent_fields & SVN_DIRENT_KIND) 1238 { 1239 /* node kind */ 1240 entry->kind = fs_entry->kind; 1241 } 1242 1243 if (dirent_fields & SVN_DIRENT_SIZE) 1244 { 1245 /* size */ 1246 if (entry->kind == svn_node_dir) 1247 entry->size = 0; 1248 else 1249 SVN_ERR(svn_fs_file_length(&(entry->size), root, 1250 fullpath, subpool)); 1251 } 1252 1253 if (dirent_fields & SVN_DIRENT_HAS_PROPS) 1254 { 1255 /* has_props? */ 1256 SVN_ERR(svn_fs_node_proplist(&prophash, root, fullpath, 1257 subpool)); 1258 entry->has_props = (apr_hash_count(prophash) != 0); 1259 } 1260 1261 if ((dirent_fields & SVN_DIRENT_TIME) 1262 || (dirent_fields & SVN_DIRENT_LAST_AUTHOR) 1263 || (dirent_fields & SVN_DIRENT_CREATED_REV)) 1264 { 1265 /* created_rev & friends */ 1266 SVN_ERR(svn_repos_get_committed_info(&(entry->created_rev), 1267 &datestring, 1268 &(entry->last_author), 1269 root, fullpath, subpool)); 1270 if (datestring) 1271 SVN_ERR(svn_time_from_cstring(&(entry->time), datestring, 1272 pool)); 1273 if (entry->last_author) 1274 entry->last_author = apr_pstrdup(pool, entry->last_author); 1275 } 1276 1277 /* Store. */ 1278 svn_hash_sets(*dirents, entryname, entry); 1279 } 1280 svn_pool_destroy(subpool); 1281 } 1282 1283 /* Handle props if requested. */ 1284 if (props) 1285 SVN_ERR(get_node_props(props, NULL, sess, root, abs_path, pool, pool)); 1286 1287 return SVN_NO_ERROR; 1288} 1289 1290 1291static svn_error_t * 1292svn_ra_local__get_locations(svn_ra_session_t *session, 1293 apr_hash_t **locations, 1294 const char *path, 1295 svn_revnum_t peg_revision, 1296 const apr_array_header_t *location_revisions, 1297 apr_pool_t *pool) 1298{ 1299 svn_ra_local__session_baton_t *sess = session->priv; 1300 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1301 return svn_repos_trace_node_locations(sess->fs, locations, abs_path, 1302 peg_revision, location_revisions, 1303 NULL, NULL, pool); 1304} 1305 1306 1307static svn_error_t * 1308svn_ra_local__get_location_segments(svn_ra_session_t *session, 1309 const char *path, 1310 svn_revnum_t peg_revision, 1311 svn_revnum_t start_rev, 1312 svn_revnum_t end_rev, 1313 svn_location_segment_receiver_t receiver, 1314 void *receiver_baton, 1315 apr_pool_t *pool) 1316{ 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 return svn_repos_node_location_segments(sess->repos, abs_path, 1320 peg_revision, start_rev, end_rev, 1321 receiver, receiver_baton, 1322 NULL, NULL, pool); 1323} 1324 1325 1326static svn_error_t * 1327svn_ra_local__lock(svn_ra_session_t *session, 1328 apr_hash_t *path_revs, 1329 const char *comment, 1330 svn_boolean_t force, 1331 svn_ra_lock_callback_t lock_func, 1332 void *lock_baton, 1333 apr_pool_t *pool) 1334{ 1335 svn_ra_local__session_baton_t *sess = session->priv; 1336 apr_hash_index_t *hi; 1337 apr_pool_t *iterpool = svn_pool_create(pool); 1338 1339 /* A username is absolutely required to lock a path. */ 1340 SVN_ERR(get_username(session, pool)); 1341 1342 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi)) 1343 { 1344 svn_lock_t *lock; 1345 const void *key; 1346 const char *path; 1347 void *val; 1348 svn_revnum_t *revnum; 1349 const char *abs_path; 1350 svn_error_t *err, *callback_err = NULL; 1351 1352 svn_pool_clear(iterpool); 1353 apr_hash_this(hi, &key, NULL, &val); 1354 path = key; 1355 revnum = val; 1356 1357 abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool); 1358 1359 /* This wrapper will call pre- and post-lock hooks. */ 1360 err = svn_repos_fs_lock(&lock, sess->repos, abs_path, NULL, comment, 1361 FALSE /* not DAV comment */, 1362 0 /* no expiration */, *revnum, force, 1363 iterpool); 1364 1365 if (err && !SVN_ERR_IS_LOCK_ERROR(err)) 1366 return err; 1367 1368 if (lock_func) 1369 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock, 1370 err, iterpool); 1371 1372 svn_error_clear(err); 1373 1374 if (callback_err) 1375 return callback_err; 1376 } 1377 1378 svn_pool_destroy(iterpool); 1379 1380 return SVN_NO_ERROR; 1381} 1382 1383 1384static svn_error_t * 1385svn_ra_local__unlock(svn_ra_session_t *session, 1386 apr_hash_t *path_tokens, 1387 svn_boolean_t force, 1388 svn_ra_lock_callback_t lock_func, 1389 void *lock_baton, 1390 apr_pool_t *pool) 1391{ 1392 svn_ra_local__session_baton_t *sess = session->priv; 1393 apr_hash_index_t *hi; 1394 apr_pool_t *iterpool = svn_pool_create(pool); 1395 1396 /* A username is absolutely required to unlock a path. */ 1397 SVN_ERR(get_username(session, pool)); 1398 1399 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi)) 1400 { 1401 const void *key; 1402 const char *path; 1403 void *val; 1404 const char *abs_path, *token; 1405 svn_error_t *err, *callback_err = NULL; 1406 1407 svn_pool_clear(iterpool); 1408 apr_hash_this(hi, &key, NULL, &val); 1409 path = key; 1410 /* Since we can't store NULL values in a hash, we turn "" to 1411 NULL here. */ 1412 if (strcmp(val, "") != 0) 1413 token = val; 1414 else 1415 token = NULL; 1416 1417 abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool); 1418 1419 /* This wrapper will call pre- and post-unlock hooks. */ 1420 err = svn_repos_fs_unlock(sess->repos, abs_path, token, force, 1421 iterpool); 1422 1423 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err)) 1424 return err; 1425 1426 if (lock_func) 1427 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, iterpool); 1428 1429 svn_error_clear(err); 1430 1431 if (callback_err) 1432 return callback_err; 1433 } 1434 1435 svn_pool_destroy(iterpool); 1436 1437 return SVN_NO_ERROR; 1438} 1439 1440 1441 1442static svn_error_t * 1443svn_ra_local__get_lock(svn_ra_session_t *session, 1444 svn_lock_t **lock, 1445 const char *path, 1446 apr_pool_t *pool) 1447{ 1448 svn_ra_local__session_baton_t *sess = session->priv; 1449 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1450 return svn_fs_get_lock(lock, sess->fs, abs_path, pool); 1451} 1452 1453 1454 1455static svn_error_t * 1456svn_ra_local__get_locks(svn_ra_session_t *session, 1457 apr_hash_t **locks, 1458 const char *path, 1459 svn_depth_t depth, 1460 apr_pool_t *pool) 1461{ 1462 svn_ra_local__session_baton_t *sess = session->priv; 1463 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1464 1465 /* Kinda silly to call the repos wrapper, since we have no authz 1466 func to give it. But heck, why not. */ 1467 return svn_repos_fs_get_locks2(locks, sess->repos, abs_path, depth, 1468 NULL, NULL, pool); 1469} 1470 1471 1472static svn_error_t * 1473svn_ra_local__replay(svn_ra_session_t *session, 1474 svn_revnum_t revision, 1475 svn_revnum_t low_water_mark, 1476 svn_boolean_t send_deltas, 1477 const svn_delta_editor_t *editor, 1478 void *edit_baton, 1479 apr_pool_t *pool) 1480{ 1481 svn_ra_local__session_baton_t *sess = session->priv; 1482 svn_fs_root_t *root; 1483 1484 SVN_ERR(svn_fs_revision_root(&root, svn_repos_fs(sess->repos), 1485 revision, pool)); 1486 return svn_repos_replay2(root, sess->fs_path->data, low_water_mark, 1487 send_deltas, editor, edit_baton, NULL, NULL, 1488 pool); 1489} 1490 1491 1492static svn_error_t * 1493svn_ra_local__replay_range(svn_ra_session_t *session, 1494 svn_revnum_t start_revision, 1495 svn_revnum_t end_revision, 1496 svn_revnum_t low_water_mark, 1497 svn_boolean_t send_deltas, 1498 svn_ra_replay_revstart_callback_t revstart_func, 1499 svn_ra_replay_revfinish_callback_t revfinish_func, 1500 void *replay_baton, 1501 apr_pool_t *pool) 1502{ 1503 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL); 1504} 1505 1506 1507static svn_error_t * 1508svn_ra_local__has_capability(svn_ra_session_t *session, 1509 svn_boolean_t *has, 1510 const char *capability, 1511 apr_pool_t *pool) 1512{ 1513 svn_ra_local__session_baton_t *sess = session->priv; 1514 1515 if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0 1516 || strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0 1517 || strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0 1518 || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0 1519 || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0 1520 || strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0 1521 || strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0 1522 || strcmp(capability, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) == 0 1523 ) 1524 { 1525 *has = TRUE; 1526 } 1527 else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0) 1528 { 1529 /* With mergeinfo, the code's capabilities may not reflect the 1530 repository's, so inquire further. */ 1531 SVN_ERR(svn_repos_has_capability(sess->repos, has, 1532 SVN_REPOS_CAPABILITY_MERGEINFO, 1533 pool)); 1534 } 1535 else /* Don't know any other capabilities, so error. */ 1536 { 1537 return svn_error_createf 1538 (SVN_ERR_UNKNOWN_CAPABILITY, NULL, 1539 _("Don't know anything about capability '%s'"), capability); 1540 } 1541 1542 return SVN_NO_ERROR; 1543} 1544 1545static svn_error_t * 1546svn_ra_local__get_deleted_rev(svn_ra_session_t *session, 1547 const char *path, 1548 svn_revnum_t peg_revision, 1549 svn_revnum_t end_revision, 1550 svn_revnum_t *revision_deleted, 1551 apr_pool_t *pool) 1552{ 1553 svn_ra_local__session_baton_t *sess = session->priv; 1554 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool); 1555 1556 SVN_ERR(svn_repos_deleted_rev(sess->fs, 1557 abs_path, 1558 peg_revision, 1559 end_revision, 1560 revision_deleted, 1561 pool)); 1562 1563 return SVN_NO_ERROR; 1564} 1565 1566static svn_error_t * 1567svn_ra_local__get_inherited_props(svn_ra_session_t *session, 1568 apr_array_header_t **iprops, 1569 const char *path, 1570 svn_revnum_t revision, 1571 apr_pool_t *result_pool, 1572 apr_pool_t *scratch_pool) 1573{ 1574 svn_fs_root_t *root; 1575 svn_revnum_t youngest_rev; 1576 svn_ra_local__session_baton_t *sess = session->priv; 1577 const char *abs_path = svn_fspath__join(sess->fs_path->data, path, 1578 scratch_pool); 1579 svn_node_kind_t node_kind; 1580 1581 /* Open the revision's root. */ 1582 if (! SVN_IS_VALID_REVNUM(revision)) 1583 { 1584 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, scratch_pool)); 1585 SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, 1586 scratch_pool)); 1587 } 1588 else 1589 { 1590 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, scratch_pool)); 1591 } 1592 1593 SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, scratch_pool)); 1594 if (node_kind == svn_node_none) 1595 { 1596 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1597 _("'%s' path not found"), abs_path); 1598 } 1599 1600 return svn_error_trace(get_node_props(NULL, iprops, sess, root, abs_path, 1601 result_pool, scratch_pool)); 1602} 1603 1604static svn_error_t * 1605svn_ra_local__register_editor_shim_callbacks(svn_ra_session_t *session, 1606 svn_delta_shim_callbacks_t *callbacks) 1607{ 1608 /* This is currenly a no-op, since we don't provide our own editor, just 1609 use the one the libsvn_repos hands back to us. */ 1610 return SVN_NO_ERROR; 1611} 1612 1613 1614static svn_error_t * 1615svn_ra_local__get_commit_ev2(svn_editor_t **editor, 1616 svn_ra_session_t *session, 1617 apr_hash_t *revprops, 1618 svn_commit_callback2_t commit_cb, 1619 void *commit_baton, 1620 apr_hash_t *lock_tokens, 1621 svn_boolean_t keep_locks, 1622 svn_ra__provide_base_cb_t provide_base_cb, 1623 svn_ra__provide_props_cb_t provide_props_cb, 1624 svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb, 1625 void *cb_baton, 1626 svn_cancel_func_t cancel_func, 1627 void *cancel_baton, 1628 apr_pool_t *result_pool, 1629 apr_pool_t *scratch_pool) 1630{ 1631 svn_ra_local__session_baton_t *sess = session->priv; 1632 struct deltify_etc_baton *deb = apr_palloc(result_pool, sizeof(*deb)); 1633 1634 /* NOTE: the RA callbacks are ignored. We pass everything directly to 1635 the REPOS editor. */ 1636 1637 /* Prepare the baton for deltify_etc() */ 1638 deb->fs = sess->fs; 1639 deb->repos = sess->repos; 1640 deb->fspath_base = sess->fs_path->data; 1641 if (! keep_locks) 1642 deb->lock_tokens = lock_tokens; 1643 else 1644 deb->lock_tokens = NULL; 1645 deb->commit_cb = commit_cb; 1646 deb->commit_baton = commit_baton; 1647 1648 /* Ensure there is a username (and an FS access context) associated with 1649 the session and its FS handle. */ 1650 SVN_ERR(get_username(session, scratch_pool)); 1651 1652 /* If there are lock tokens to add, do so. */ 1653 SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens, 1654 session->pool, scratch_pool)); 1655 1656 /* Copy the REVPROPS and insert the author/username. */ 1657 revprops = apr_hash_copy(scratch_pool, revprops); 1658 svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR, 1659 svn_string_create(sess->username, scratch_pool)); 1660 1661 return svn_error_trace(svn_repos__get_commit_ev2( 1662 editor, sess->repos, NULL /* authz */, 1663 NULL /* authz_repos_name */, NULL /* authz_user */, 1664 revprops, 1665 deltify_etc, deb, cancel_func, cancel_baton, 1666 result_pool, scratch_pool)); 1667} 1668 1669/*----------------------------------------------------------------*/ 1670 1671static const svn_version_t * 1672ra_local_version(void) 1673{ 1674 SVN_VERSION_BODY; 1675} 1676 1677/** The ra_vtable **/ 1678 1679static const svn_ra__vtable_t ra_local_vtable = 1680{ 1681 ra_local_version, 1682 svn_ra_local__get_description, 1683 svn_ra_local__get_schemes, 1684 svn_ra_local__open, 1685 svn_ra_local__reparent, 1686 svn_ra_local__get_session_url, 1687 svn_ra_local__get_latest_revnum, 1688 svn_ra_local__get_dated_revision, 1689 svn_ra_local__change_rev_prop, 1690 svn_ra_local__rev_proplist, 1691 svn_ra_local__rev_prop, 1692 svn_ra_local__get_commit_editor, 1693 svn_ra_local__get_file, 1694 svn_ra_local__get_dir, 1695 svn_ra_local__get_mergeinfo, 1696 svn_ra_local__do_update, 1697 svn_ra_local__do_switch, 1698 svn_ra_local__do_status, 1699 svn_ra_local__do_diff, 1700 svn_ra_local__get_log, 1701 svn_ra_local__do_check_path, 1702 svn_ra_local__stat, 1703 svn_ra_local__get_uuid, 1704 svn_ra_local__get_repos_root, 1705 svn_ra_local__get_locations, 1706 svn_ra_local__get_location_segments, 1707 svn_ra_local__get_file_revs, 1708 svn_ra_local__lock, 1709 svn_ra_local__unlock, 1710 svn_ra_local__get_lock, 1711 svn_ra_local__get_locks, 1712 svn_ra_local__replay, 1713 svn_ra_local__has_capability, 1714 svn_ra_local__replay_range, 1715 svn_ra_local__get_deleted_rev, 1716 svn_ra_local__register_editor_shim_callbacks, 1717 svn_ra_local__get_inherited_props, 1718 svn_ra_local__get_commit_ev2 1719}; 1720 1721 1722/*----------------------------------------------------------------*/ 1723 1724/** The One Public Routine, called by libsvn_ra **/ 1725 1726svn_error_t * 1727svn_ra_local__init(const svn_version_t *loader_version, 1728 const svn_ra__vtable_t **vtable, 1729 apr_pool_t *pool) 1730{ 1731 static const svn_version_checklist_t checklist[] = 1732 { 1733 { "svn_subr", svn_subr_version }, 1734 { "svn_delta", svn_delta_version }, 1735 { "svn_repos", svn_repos_version }, 1736 { "svn_fs", svn_fs_version }, 1737 { NULL, NULL } 1738 }; 1739 1740 1741 /* Simplified version check to make sure we can safely use the 1742 VTABLE parameter. The RA loader does a more exhaustive check. */ 1743 if (loader_version->major != SVN_VER_MAJOR) 1744 return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL, 1745 _("Unsupported RA loader version (%d) for " 1746 "ra_local"), 1747 loader_version->major); 1748 1749 SVN_ERR(svn_ver_check_list2(ra_local_version(), checklist, svn_ver_equal)); 1750 1751#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL 1752 /* This assumes that POOL was the pool used to load the dso. */ 1753 SVN_ERR(svn_fs_initialize(pool)); 1754#endif 1755 1756 *vtable = &ra_local_vtable; 1757 1758 return SVN_NO_ERROR; 1759} 1760 1761/* Compatibility wrapper for the 1.1 and before API. */ 1762#define NAME "ra_local" 1763#define DESCRIPTION RA_LOCAL_DESCRIPTION 1764#define VTBL ra_local_vtable 1765#define INITFUNC svn_ra_local__init 1766#define COMPAT_INITFUNC svn_ra_local_init 1767#include "../libsvn_ra/wrapper_template.h" 1768