ra.c revision 299742
1/* 2 * ra.c : routines for interacting with the RA layer 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 25 26#include <apr_pools.h> 27 28#include "svn_error.h" 29#include "svn_hash.h" 30#include "svn_pools.h" 31#include "svn_string.h" 32#include "svn_sorts.h" 33#include "svn_ra.h" 34#include "svn_client.h" 35#include "svn_dirent_uri.h" 36#include "svn_path.h" 37#include "svn_props.h" 38#include "svn_mergeinfo.h" 39#include "client.h" 40#include "mergeinfo.h" 41 42#include "svn_private_config.h" 43#include "private/svn_wc_private.h" 44#include "private/svn_client_private.h" 45#include "private/svn_sorts_private.h" 46 47 48/* This is the baton that we pass svn_ra_open3(), and is associated with 49 the callback table we provide to RA. */ 50typedef struct callback_baton_t 51{ 52 /* Holds the directory that corresponds to the REPOS_URL at svn_ra_open3() 53 time. When callbacks specify a relative path, they are joined with 54 this base directory. */ 55 const char *base_dir_abspath; 56 57 /* TEMPORARY: Is 'base_dir_abspath' a versioned path? cmpilato 58 suspects that the commit-to-multiple-disjoint-working-copies 59 code is getting this all wrong, sometimes passing an unversioned 60 (or versioned in a foreign wc) path here which sorta kinda 61 happens to work most of the time but is ultimately incorrect. */ 62 svn_boolean_t base_dir_isversioned; 63 64 /* Used as wri_abspath for obtaining access to the pristine store */ 65 const char *wcroot_abspath; 66 67 /* An array of svn_client_commit_item3_t * structures, present only 68 during working copy commits. */ 69 const apr_array_header_t *commit_items; 70 71 /* A client context. */ 72 svn_client_ctx_t *ctx; 73 74 /* Last progress reported by progress callback. */ 75 apr_off_t last_progress; 76} callback_baton_t; 77 78 79 80static svn_error_t * 81open_tmp_file(apr_file_t **fp, 82 void *callback_baton, 83 apr_pool_t *pool) 84{ 85 return svn_error_trace(svn_io_open_unique_file3(fp, NULL, NULL, 86 svn_io_file_del_on_pool_cleanup, 87 pool, pool)); 88} 89 90 91/* This implements the 'svn_ra_get_wc_prop_func_t' interface. */ 92static svn_error_t * 93get_wc_prop(void *baton, 94 const char *relpath, 95 const char *name, 96 const svn_string_t **value, 97 apr_pool_t *pool) 98{ 99 callback_baton_t *cb = baton; 100 const char *local_abspath = NULL; 101 svn_error_t *err; 102 103 *value = NULL; 104 105 /* If we have a list of commit_items, search through that for a 106 match for this relative URL. */ 107 if (cb->commit_items) 108 { 109 int i; 110 for (i = 0; i < cb->commit_items->nelts; i++) 111 { 112 svn_client_commit_item3_t *item 113 = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *); 114 115 if (! strcmp(relpath, item->session_relpath)) 116 { 117 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path)); 118 local_abspath = item->path; 119 break; 120 } 121 } 122 123 /* Commits can only query relpaths in the commit_items list 124 since the commit driver traverses paths as they are, or will 125 be, in the repository. Non-commits query relpaths in the 126 working copy. */ 127 if (! local_abspath) 128 return SVN_NO_ERROR; 129 } 130 131 /* If we don't have a base directory, then there are no properties. */ 132 else if (cb->base_dir_abspath == NULL) 133 return SVN_NO_ERROR; 134 135 else 136 local_abspath = svn_dirent_join(cb->base_dir_abspath, relpath, pool); 137 138 err = svn_wc_prop_get2(value, cb->ctx->wc_ctx, local_abspath, name, 139 pool, pool); 140 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) 141 { 142 svn_error_clear(err); 143 err = NULL; 144 } 145 return svn_error_trace(err); 146} 147 148/* This implements the 'svn_ra_push_wc_prop_func_t' interface. */ 149static svn_error_t * 150push_wc_prop(void *baton, 151 const char *relpath, 152 const char *name, 153 const svn_string_t *value, 154 apr_pool_t *pool) 155{ 156 callback_baton_t *cb = baton; 157 int i; 158 159 /* If we're committing, search through the commit_items list for a 160 match for this relative URL. */ 161 if (! cb->commit_items) 162 return svn_error_createf 163 (SVN_ERR_UNSUPPORTED_FEATURE, NULL, 164 _("Attempt to set wcprop '%s' on '%s' in a non-commit operation"), 165 name, svn_dirent_local_style(relpath, pool)); 166 167 for (i = 0; i < cb->commit_items->nelts; i++) 168 { 169 svn_client_commit_item3_t *item 170 = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *); 171 172 if (strcmp(relpath, item->session_relpath) == 0) 173 { 174 apr_pool_t *changes_pool = item->incoming_prop_changes->pool; 175 svn_prop_t *prop = apr_palloc(changes_pool, sizeof(*prop)); 176 177 prop->name = apr_pstrdup(changes_pool, name); 178 if (value) 179 prop->value = svn_string_dup(value, changes_pool); 180 else 181 prop->value = NULL; 182 183 /* Buffer the propchange to take effect during the 184 post-commit process. */ 185 APR_ARRAY_PUSH(item->incoming_prop_changes, svn_prop_t *) = prop; 186 return SVN_NO_ERROR; 187 } 188 } 189 190 return SVN_NO_ERROR; 191} 192 193 194/* This implements the 'svn_ra_set_wc_prop_func_t' interface. */ 195static svn_error_t * 196set_wc_prop(void *baton, 197 const char *path, 198 const char *name, 199 const svn_string_t *value, 200 apr_pool_t *pool) 201{ 202 callback_baton_t *cb = baton; 203 const char *local_abspath; 204 205 local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool); 206 207 /* We pass 1 for the 'force' parameter here. Since the property is 208 coming from the repository, we definitely want to accept it. 209 Ideally, we'd raise a conflict if, say, the received property is 210 svn:eol-style yet the file has a locally added svn:mime-type 211 claiming that it's binary. Probably the repository is still 212 right, but the conflict would remind the user to make sure. 213 Unfortunately, we don't have a clean mechanism for doing that 214 here, so we just set the property and hope for the best. */ 215 return svn_error_trace(svn_wc_prop_set4(cb->ctx->wc_ctx, local_abspath, 216 name, 217 value, svn_depth_empty, 218 TRUE /* skip_checks */, 219 NULL /* changelist_filter */, 220 NULL, NULL /* cancellation */, 221 NULL, NULL /* notification */, 222 pool)); 223} 224 225 226/* This implements the `svn_ra_invalidate_wc_props_func_t' interface. */ 227static svn_error_t * 228invalidate_wc_props(void *baton, 229 const char *path, 230 const char *prop_name, 231 apr_pool_t *pool) 232{ 233 callback_baton_t *cb = baton; 234 const char *local_abspath; 235 236 local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool); 237 238 /* It's easier just to clear the whole dav_cache than to remove 239 individual items from it recursively like this. And since we 240 know that the RA providers that ship with Subversion only 241 invalidate the one property they use the most from this cache, 242 and that we're intentionally trying to get away from the use of 243 the cache altogether anyway, there's little to lose in wiping the 244 whole cache. Is it the most well-behaved approach to take? Not 245 so much. We choose not to care. */ 246 return svn_error_trace(svn_wc__node_clear_dav_cache_recursive( 247 cb->ctx->wc_ctx, local_abspath, pool)); 248} 249 250 251/* This implements the `svn_ra_get_wc_contents_func_t' interface. */ 252static svn_error_t * 253get_wc_contents(void *baton, 254 svn_stream_t **contents, 255 const svn_checksum_t *checksum, 256 apr_pool_t *pool) 257{ 258 callback_baton_t *cb = baton; 259 260 if (! cb->wcroot_abspath) 261 { 262 *contents = NULL; 263 return SVN_NO_ERROR; 264 } 265 266 return svn_error_trace( 267 svn_wc__get_pristine_contents_by_checksum(contents, 268 cb->ctx->wc_ctx, 269 cb->wcroot_abspath, 270 checksum, 271 pool, pool)); 272} 273 274 275static svn_error_t * 276cancel_callback(void *baton) 277{ 278 callback_baton_t *b = baton; 279 return svn_error_trace((b->ctx->cancel_func)(b->ctx->cancel_baton)); 280} 281 282 283static svn_error_t * 284get_client_string(void *baton, 285 const char **name, 286 apr_pool_t *pool) 287{ 288 callback_baton_t *b = baton; 289 *name = apr_pstrdup(pool, b->ctx->client_name); 290 return SVN_NO_ERROR; 291} 292 293/* Implements svn_ra_progress_notify_func_t. Accumulates progress information 294 * for different RA sessions and reports total progress to caller. */ 295static void 296progress_func(apr_off_t progress, 297 apr_off_t total, 298 void *baton, 299 apr_pool_t *pool) 300{ 301 callback_baton_t *b = baton; 302 svn_client_ctx_t *public_ctx = b->ctx; 303 svn_client__private_ctx_t *private_ctx = 304 svn_client__get_private_ctx(public_ctx); 305 306 private_ctx->total_progress += (progress - b->last_progress); 307 b->last_progress = progress; 308 309 if (public_ctx->progress_func) 310 { 311 /* All RA implementations currently provide -1 for total. So it doesn't 312 make sense to develop some complex logic to combine total across all 313 RA sessions. */ 314 public_ctx->progress_func(private_ctx->total_progress, -1, 315 public_ctx->progress_baton, pool); 316 } 317} 318 319#define SVN_CLIENT__MAX_REDIRECT_ATTEMPTS 3 /* ### TODO: Make configurable. */ 320 321svn_error_t * 322svn_client__open_ra_session_internal(svn_ra_session_t **ra_session, 323 const char **corrected_url, 324 const char *base_url, 325 const char *base_dir_abspath, 326 const apr_array_header_t *commit_items, 327 svn_boolean_t write_dav_props, 328 svn_boolean_t read_dav_props, 329 svn_client_ctx_t *ctx, 330 apr_pool_t *result_pool, 331 apr_pool_t *scratch_pool) 332{ 333 svn_ra_callbacks2_t *cbtable; 334 callback_baton_t *cb = apr_pcalloc(result_pool, sizeof(*cb)); 335 const char *uuid = NULL; 336 337 SVN_ERR_ASSERT(!write_dav_props || read_dav_props); 338 SVN_ERR_ASSERT(!read_dav_props || base_dir_abspath != NULL); 339 SVN_ERR_ASSERT(base_dir_abspath == NULL 340 || svn_dirent_is_absolute(base_dir_abspath)); 341 342 SVN_ERR(svn_ra_create_callbacks(&cbtable, result_pool)); 343 cbtable->open_tmp_file = open_tmp_file; 344 cbtable->get_wc_prop = read_dav_props ? get_wc_prop : NULL; 345 cbtable->set_wc_prop = (write_dav_props && read_dav_props) 346 ? set_wc_prop : NULL; 347 cbtable->push_wc_prop = commit_items ? push_wc_prop : NULL; 348 cbtable->invalidate_wc_props = (write_dav_props && read_dav_props) 349 ? invalidate_wc_props : NULL; 350 cbtable->auth_baton = ctx->auth_baton; /* new-style */ 351 cbtable->progress_func = progress_func; 352 cbtable->progress_baton = cb; 353 cbtable->cancel_func = ctx->cancel_func ? cancel_callback : NULL; 354 cbtable->get_client_string = get_client_string; 355 if (base_dir_abspath) 356 cbtable->get_wc_contents = get_wc_contents; 357 cbtable->check_tunnel_func = ctx->check_tunnel_func; 358 cbtable->open_tunnel_func = ctx->open_tunnel_func; 359 cbtable->tunnel_baton = ctx->tunnel_baton; 360 361 cb->commit_items = commit_items; 362 cb->ctx = ctx; 363 364 if (base_dir_abspath && (read_dav_props || write_dav_props)) 365 { 366 svn_error_t *err = svn_wc__node_get_repos_info(NULL, NULL, NULL, &uuid, 367 ctx->wc_ctx, 368 base_dir_abspath, 369 result_pool, 370 scratch_pool); 371 372 if (err && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY 373 || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND 374 || err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)) 375 { 376 svn_error_clear(err); 377 uuid = NULL; 378 } 379 else 380 { 381 SVN_ERR(err); 382 cb->base_dir_isversioned = TRUE; 383 } 384 cb->base_dir_abspath = apr_pstrdup(result_pool, base_dir_abspath); 385 } 386 387 if (base_dir_abspath) 388 { 389 svn_error_t *err = svn_wc__get_wcroot(&cb->wcroot_abspath, 390 ctx->wc_ctx, base_dir_abspath, 391 result_pool, scratch_pool); 392 393 if (err) 394 { 395 if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY 396 && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND 397 && err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED) 398 return svn_error_trace(err); 399 400 svn_error_clear(err); 401 cb->wcroot_abspath = NULL; 402 } 403 } 404 405 /* If the caller allows for auto-following redirections, and the 406 RA->open() call above reveals a CORRECTED_URL, try the new URL. 407 We'll do this in a loop up to some maximum number follow-and-retry 408 attempts. */ 409 if (corrected_url) 410 { 411 apr_hash_t *attempted = apr_hash_make(scratch_pool); 412 int attempts_left = SVN_CLIENT__MAX_REDIRECT_ATTEMPTS; 413 414 *corrected_url = NULL; 415 while (attempts_left--) 416 { 417 const char *corrected = NULL; 418 419 /* Try to open the RA session. If this is our last attempt, 420 don't accept corrected URLs from the RA provider. */ 421 SVN_ERR(svn_ra_open4(ra_session, 422 attempts_left == 0 ? NULL : &corrected, 423 base_url, uuid, cbtable, cb, ctx->config, 424 result_pool)); 425 426 /* No error and no corrected URL? We're done here. */ 427 if (! corrected) 428 break; 429 430 /* Notify the user that a redirect is being followed. */ 431 if (ctx->notify_func2 != NULL) 432 { 433 svn_wc_notify_t *notify = 434 svn_wc_create_notify_url(corrected, 435 svn_wc_notify_url_redirect, 436 scratch_pool); 437 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 438 } 439 440 /* Our caller will want to know what our final corrected URL was. */ 441 *corrected_url = corrected; 442 443 /* Make sure we've not attempted this URL before. */ 444 if (svn_hash_gets(attempted, corrected)) 445 return svn_error_createf(SVN_ERR_CLIENT_CYCLE_DETECTED, NULL, 446 _("Redirect cycle detected for URL '%s'"), 447 corrected); 448 449 /* Remember this CORRECTED_URL so we don't wind up in a loop. */ 450 svn_hash_sets(attempted, corrected, (void *)1); 451 base_url = corrected; 452 } 453 } 454 else 455 { 456 SVN_ERR(svn_ra_open4(ra_session, NULL, base_url, 457 uuid, cbtable, cb, ctx->config, result_pool)); 458 } 459 460 return SVN_NO_ERROR; 461} 462#undef SVN_CLIENT__MAX_REDIRECT_ATTEMPTS 463 464 465svn_error_t * 466svn_client_open_ra_session2(svn_ra_session_t **session, 467 const char *url, 468 const char *wri_abspath, 469 svn_client_ctx_t *ctx, 470 apr_pool_t *result_pool, 471 apr_pool_t *scratch_pool) 472{ 473 return svn_error_trace( 474 svn_client__open_ra_session_internal(session, NULL, url, 475 wri_abspath, NULL, 476 FALSE, FALSE, 477 ctx, result_pool, 478 scratch_pool)); 479} 480 481svn_error_t * 482svn_client__resolve_rev_and_url(svn_client__pathrev_t **resolved_loc_p, 483 svn_ra_session_t *ra_session, 484 const char *path_or_url, 485 const svn_opt_revision_t *peg_revision, 486 const svn_opt_revision_t *revision, 487 svn_client_ctx_t *ctx, 488 apr_pool_t *pool) 489{ 490 svn_opt_revision_t peg_rev = *peg_revision; 491 svn_opt_revision_t start_rev = *revision; 492 const char *url; 493 svn_revnum_t rev; 494 495 /* Default revisions: peg -> working or head; operative -> peg. */ 496 SVN_ERR(svn_opt_resolve_revisions(&peg_rev, &start_rev, 497 svn_path_is_url(path_or_url), 498 TRUE /* notice_local_mods */, 499 pool)); 500 501 /* Run the history function to get the object's (possibly 502 different) url in REVISION. */ 503 SVN_ERR(svn_client__repos_locations(&url, &rev, NULL, NULL, 504 ra_session, path_or_url, &peg_rev, 505 &start_rev, NULL, ctx, pool)); 506 507 SVN_ERR(svn_client__pathrev_create_with_session(resolved_loc_p, 508 ra_session, rev, url, pool)); 509 return SVN_NO_ERROR; 510} 511 512svn_error_t * 513svn_client__ra_session_from_path2(svn_ra_session_t **ra_session_p, 514 svn_client__pathrev_t **resolved_loc_p, 515 const char *path_or_url, 516 const char *base_dir_abspath, 517 const svn_opt_revision_t *peg_revision, 518 const svn_opt_revision_t *revision, 519 svn_client_ctx_t *ctx, 520 apr_pool_t *pool) 521{ 522 svn_ra_session_t *ra_session; 523 const char *initial_url; 524 const char *corrected_url; 525 svn_client__pathrev_t *resolved_loc; 526 const char *wri_abspath; 527 528 SVN_ERR(svn_client_url_from_path2(&initial_url, path_or_url, ctx, pool, 529 pool)); 530 if (! initial_url) 531 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, 532 _("'%s' has no URL"), path_or_url); 533 534 if (base_dir_abspath) 535 wri_abspath = base_dir_abspath; 536 else if (!svn_path_is_url(path_or_url)) 537 SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url, pool)); 538 else 539 wri_abspath = NULL; 540 541 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 542 initial_url, 543 wri_abspath, 544 NULL /* commit_items */, 545 base_dir_abspath != NULL, 546 base_dir_abspath != NULL, 547 ctx, pool, pool)); 548 549 /* If we got a CORRECTED_URL, we'll want to refer to that as the 550 URL-ized form of PATH_OR_URL from now on. */ 551 if (corrected_url && svn_path_is_url(path_or_url)) 552 path_or_url = corrected_url; 553 554 SVN_ERR(svn_client__resolve_rev_and_url(&resolved_loc, ra_session, 555 path_or_url, peg_revision, revision, 556 ctx, pool)); 557 558 /* Make the session point to the real URL. */ 559 SVN_ERR(svn_ra_reparent(ra_session, resolved_loc->url, pool)); 560 561 *ra_session_p = ra_session; 562 if (resolved_loc_p) 563 *resolved_loc_p = resolved_loc; 564 565 return SVN_NO_ERROR; 566} 567 568 569svn_error_t * 570svn_client__ensure_ra_session_url(const char **old_session_url, 571 svn_ra_session_t *ra_session, 572 const char *session_url, 573 apr_pool_t *pool) 574{ 575 SVN_ERR(svn_ra_get_session_url(ra_session, old_session_url, pool)); 576 if (! session_url) 577 SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_url, pool)); 578 if (strcmp(*old_session_url, session_url) != 0) 579 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool)); 580 return SVN_NO_ERROR; 581} 582 583 584 585/*** Repository Locations ***/ 586 587struct gls_receiver_baton_t 588{ 589 apr_array_header_t *segments; 590 svn_client_ctx_t *ctx; 591 apr_pool_t *pool; 592}; 593 594static svn_error_t * 595gls_receiver(svn_location_segment_t *segment, 596 void *baton, 597 apr_pool_t *pool) 598{ 599 struct gls_receiver_baton_t *b = baton; 600 APR_ARRAY_PUSH(b->segments, svn_location_segment_t *) = 601 svn_location_segment_dup(segment, b->pool); 602 if (b->ctx->cancel_func) 603 SVN_ERR((b->ctx->cancel_func)(b->ctx->cancel_baton)); 604 return SVN_NO_ERROR; 605} 606 607/* A qsort-compatible function which sorts svn_location_segment_t's 608 based on their revision range covering, resulting in ascending 609 (oldest-to-youngest) ordering. */ 610static int 611compare_segments(const void *a, const void *b) 612{ 613 const svn_location_segment_t *a_seg 614 = *((const svn_location_segment_t * const *) a); 615 const svn_location_segment_t *b_seg 616 = *((const svn_location_segment_t * const *) b); 617 if (a_seg->range_start == b_seg->range_start) 618 return 0; 619 return (a_seg->range_start < b_seg->range_start) ? -1 : 1; 620} 621 622svn_error_t * 623svn_client__repos_location_segments(apr_array_header_t **segments, 624 svn_ra_session_t *ra_session, 625 const char *url, 626 svn_revnum_t peg_revision, 627 svn_revnum_t start_revision, 628 svn_revnum_t end_revision, 629 svn_client_ctx_t *ctx, 630 apr_pool_t *pool) 631{ 632 struct gls_receiver_baton_t gls_receiver_baton; 633 const char *old_session_url; 634 svn_error_t *err; 635 636 *segments = apr_array_make(pool, 8, sizeof(svn_location_segment_t *)); 637 gls_receiver_baton.segments = *segments; 638 gls_receiver_baton.ctx = ctx; 639 gls_receiver_baton.pool = pool; 640 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, 641 url, pool)); 642 err = svn_ra_get_location_segments(ra_session, "", peg_revision, 643 start_revision, end_revision, 644 gls_receiver, &gls_receiver_baton, 645 pool); 646 SVN_ERR(svn_error_compose_create( 647 err, svn_ra_reparent(ra_session, old_session_url, pool))); 648 svn_sort__array(*segments, compare_segments); 649 return SVN_NO_ERROR; 650} 651 652/* Set *START_URL and *END_URL to the URLs that the object URL@PEG_REVNUM 653 * had in revisions START_REVNUM and END_REVNUM. Return an error if the 654 * node cannot be traced back to one of the requested revisions. 655 * 656 * START_URL and/or END_URL may be NULL if not wanted. START_REVNUM and 657 * END_REVNUM must be valid revision numbers except that END_REVNUM may 658 * be SVN_INVALID_REVNUM if END_URL is NULL. 659 * 660 * YOUNGEST_REV is the already retrieved youngest revision of the ra session, 661 * but can be SVN_INVALID_REVNUM if the value is not already retrieved. 662 * 663 * RA_SESSION is an open RA session parented at URL. 664 */ 665static svn_error_t * 666repos_locations(const char **start_url, 667 const char **end_url, 668 svn_ra_session_t *ra_session, 669 const char *url, 670 svn_revnum_t peg_revnum, 671 svn_revnum_t start_revnum, 672 svn_revnum_t end_revnum, 673 svn_revnum_t youngest_rev, 674 apr_pool_t *result_pool, 675 apr_pool_t *scratch_pool) 676{ 677 const char *repos_url, *start_path, *end_path; 678 apr_array_header_t *revs; 679 apr_hash_t *rev_locs; 680 681 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(peg_revnum)); 682 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_revnum)); 683 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_revnum) || end_url == NULL); 684 685 /* Avoid a network request in the common easy case. */ 686 if (start_revnum == peg_revnum 687 && (end_revnum == peg_revnum || end_revnum == SVN_INVALID_REVNUM)) 688 { 689 if (start_url) 690 *start_url = apr_pstrdup(result_pool, url); 691 if (end_url) 692 *end_url = apr_pstrdup(result_pool, url); 693 return SVN_NO_ERROR; 694 } 695 696 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, scratch_pool)); 697 698 /* Handle another common case: The repository root can't move */ 699 if (! strcmp(repos_url, url)) 700 { 701 if (! SVN_IS_VALID_REVNUM(youngest_rev)) 702 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest_rev, 703 scratch_pool)); 704 705 if (start_revnum > youngest_rev 706 || (SVN_IS_VALID_REVNUM(end_revnum) && (end_revnum > youngest_rev))) 707 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 708 _("No such revision %ld"), 709 (start_revnum > youngest_rev) 710 ? start_revnum : end_revnum); 711 712 if (start_url) 713 *start_url = apr_pstrdup(result_pool, repos_url); 714 if (end_url) 715 *end_url = apr_pstrdup(result_pool, repos_url); 716 return SVN_NO_ERROR; 717 } 718 719 revs = apr_array_make(scratch_pool, 2, sizeof(svn_revnum_t)); 720 APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum; 721 if (end_revnum != start_revnum && end_revnum != SVN_INVALID_REVNUM) 722 APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum; 723 724 SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum, 725 revs, scratch_pool)); 726 727 /* We'd better have all the paths we were looking for! */ 728 if (start_url) 729 { 730 start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(svn_revnum_t)); 731 if (! start_path) 732 return svn_error_createf 733 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, 734 _("Unable to find repository location for '%s' in revision %ld"), 735 url, start_revnum); 736 *start_url = svn_path_url_add_component2(repos_url, start_path + 1, 737 result_pool); 738 } 739 740 if (end_url) 741 { 742 end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t)); 743 if (! end_path) 744 return svn_error_createf 745 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, 746 _("The location for '%s' for revision %ld does not exist in the " 747 "repository or refers to an unrelated object"), 748 url, end_revnum); 749 750 *end_url = svn_path_url_add_component2(repos_url, end_path + 1, 751 result_pool); 752 } 753 754 return SVN_NO_ERROR; 755} 756 757svn_error_t * 758svn_client__repos_location(svn_client__pathrev_t **op_loc_p, 759 svn_ra_session_t *ra_session, 760 const svn_client__pathrev_t *peg_loc, 761 svn_revnum_t op_revnum, 762 svn_client_ctx_t *ctx, 763 apr_pool_t *result_pool, 764 apr_pool_t *scratch_pool) 765{ 766 const char *old_session_url; 767 const char *op_url; 768 svn_error_t *err; 769 770 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, 771 peg_loc->url, scratch_pool)); 772 err = repos_locations(&op_url, NULL, ra_session, 773 peg_loc->url, peg_loc->rev, 774 op_revnum, SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, 775 result_pool, scratch_pool); 776 SVN_ERR(svn_error_compose_create( 777 err, svn_ra_reparent(ra_session, old_session_url, scratch_pool))); 778 779 *op_loc_p = svn_client__pathrev_create(peg_loc->repos_root_url, 780 peg_loc->repos_uuid, 781 op_revnum, op_url, result_pool); 782 return SVN_NO_ERROR; 783} 784 785svn_error_t * 786svn_client__repos_locations(const char **start_url, 787 svn_revnum_t *start_revision, 788 const char **end_url, 789 svn_revnum_t *end_revision, 790 svn_ra_session_t *ra_session, 791 const char *path, 792 const svn_opt_revision_t *revision, 793 const svn_opt_revision_t *start, 794 const svn_opt_revision_t *end, 795 svn_client_ctx_t *ctx, 796 apr_pool_t *pool) 797{ 798 const char *url; 799 const char *local_abspath_or_url; 800 svn_revnum_t peg_revnum = SVN_INVALID_REVNUM; 801 svn_revnum_t start_revnum, end_revnum; 802 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM; 803 apr_pool_t *subpool = svn_pool_create(pool); 804 805 /* Ensure that we are given some real revision data to work with. 806 (It's okay if the END is unspecified -- in that case, we'll just 807 set it to the same thing as START.) */ 808 if (revision->kind == svn_opt_revision_unspecified 809 || start->kind == svn_opt_revision_unspecified) 810 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); 811 812 if (end == NULL) 813 { 814 static const svn_opt_revision_t unspecified_rev 815 = { svn_opt_revision_unspecified, { 0 } }; 816 817 end = &unspecified_rev; 818 } 819 820 /* Determine LOCAL_ABSPATH_OR_URL, URL, and possibly PEG_REVNUM. 821 If we are looking at the working version of a WC path that is scheduled 822 as a copy, then we need to use the copy-from URL and peg revision. */ 823 if (! svn_path_is_url(path)) 824 { 825 SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, subpool)); 826 827 if (revision->kind == svn_opt_revision_working) 828 { 829 const char *repos_root_url; 830 const char *repos_relpath; 831 svn_boolean_t is_copy; 832 833 SVN_ERR(svn_wc__node_get_origin(&is_copy, &peg_revnum, &repos_relpath, 834 &repos_root_url, NULL, NULL, NULL, 835 ctx->wc_ctx, local_abspath_or_url, 836 FALSE, subpool, subpool)); 837 838 if (repos_relpath) 839 url = svn_path_url_add_component2(repos_root_url, repos_relpath, 840 pool); 841 else 842 url = NULL; 843 844 if (url && is_copy && ra_session) 845 { 846 const char *session_url; 847 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, 848 subpool)); 849 850 if (strcmp(session_url, url) != 0) 851 { 852 /* We can't use the caller provided RA session now :( */ 853 ra_session = NULL; 854 } 855 } 856 } 857 else 858 url = NULL; 859 860 if (! url) 861 SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, 862 local_abspath_or_url, pool, subpool)); 863 864 if (!url) 865 { 866 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, 867 _("'%s' has no URL"), 868 svn_dirent_local_style(path, pool)); 869 } 870 } 871 else 872 { 873 local_abspath_or_url = path; 874 url = path; 875 } 876 877 /* ### We should be smarter here. If the callers just asks for BASE and 878 WORKING revisions, we should already have the correct URLs, so we 879 don't need to do anything more here in that case. */ 880 881 /* Open a RA session to this URL if we don't have one already. */ 882 if (! ra_session) 883 SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL, 884 ctx, subpool, subpool)); 885 886 /* Resolve the opt_revision_ts. */ 887 if (peg_revnum == SVN_INVALID_REVNUM) 888 SVN_ERR(svn_client__get_revision_number(&peg_revnum, &youngest_rev, 889 ctx->wc_ctx, local_abspath_or_url, 890 ra_session, revision, pool)); 891 892 SVN_ERR(svn_client__get_revision_number(&start_revnum, &youngest_rev, 893 ctx->wc_ctx, local_abspath_or_url, 894 ra_session, start, pool)); 895 if (end->kind == svn_opt_revision_unspecified) 896 end_revnum = start_revnum; 897 else 898 SVN_ERR(svn_client__get_revision_number(&end_revnum, &youngest_rev, 899 ctx->wc_ctx, local_abspath_or_url, 900 ra_session, end, pool)); 901 902 /* Set the output revision variables. */ 903 if (start_revision) 904 { 905 *start_revision = start_revnum; 906 } 907 if (end_revision && end->kind != svn_opt_revision_unspecified) 908 { 909 *end_revision = end_revnum; 910 } 911 912 SVN_ERR(repos_locations(start_url, end_url, 913 ra_session, url, peg_revnum, 914 start_revnum, end_revnum, youngest_rev, 915 pool, subpool)); 916 svn_pool_destroy(subpool); 917 return SVN_NO_ERROR; 918} 919 920svn_error_t * 921svn_client__calc_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p, 922 const svn_client__pathrev_t *loc1, 923 apr_hash_t *history1, 924 svn_boolean_t has_rev_zero_history1, 925 const svn_client__pathrev_t *loc2, 926 apr_hash_t *history2, 927 svn_boolean_t has_rev_zero_history2, 928 apr_pool_t *result_pool, 929 apr_pool_t *scratch_pool) 930{ 931 apr_hash_index_t *hi; 932 svn_revnum_t yc_revision = SVN_INVALID_REVNUM; 933 const char *yc_relpath = NULL; 934 935 if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0) 936 { 937 *ancestor_p = NULL; 938 return SVN_NO_ERROR; 939 } 940 941 /* Loop through the first location's history, check for overlapping 942 paths and ranges in the second location's history, and 943 remembering the youngest matching location. */ 944 for (hi = apr_hash_first(scratch_pool, history1); hi; hi = apr_hash_next(hi)) 945 { 946 const char *path = apr_hash_this_key(hi); 947 apr_ssize_t path_len = apr_hash_this_key_len(hi); 948 svn_rangelist_t *ranges1 = apr_hash_this_val(hi); 949 svn_rangelist_t *ranges2, *common; 950 951 ranges2 = apr_hash_get(history2, path, path_len); 952 if (ranges2) 953 { 954 /* We have a path match. Now, did our two histories share 955 any revisions at that path? */ 956 SVN_ERR(svn_rangelist_intersect(&common, ranges1, ranges2, 957 TRUE, scratch_pool)); 958 if (common->nelts) 959 { 960 svn_merge_range_t *yc_range = 961 APR_ARRAY_IDX(common, common->nelts - 1, svn_merge_range_t *); 962 if ((! SVN_IS_VALID_REVNUM(yc_revision)) 963 || (yc_range->end > yc_revision)) 964 { 965 yc_revision = yc_range->end; 966 yc_relpath = path + 1; 967 } 968 } 969 } 970 } 971 972 /* It's possible that PATH_OR_URL1 and PATH_OR_URL2's only common 973 history is revision 0. */ 974 if (!yc_relpath && has_rev_zero_history1 && has_rev_zero_history2) 975 { 976 yc_relpath = ""; 977 yc_revision = 0; 978 } 979 980 if (yc_relpath) 981 { 982 *ancestor_p = svn_client__pathrev_create_with_relpath( 983 loc1->repos_root_url, loc1->repos_uuid, 984 yc_revision, yc_relpath, result_pool); 985 } 986 else 987 { 988 *ancestor_p = NULL; 989 } 990 return SVN_NO_ERROR; 991} 992 993svn_error_t * 994svn_client__get_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p, 995 const svn_client__pathrev_t *loc1, 996 const svn_client__pathrev_t *loc2, 997 svn_ra_session_t *session, 998 svn_client_ctx_t *ctx, 999 apr_pool_t *result_pool, 1000 apr_pool_t *scratch_pool) 1001{ 1002 apr_pool_t *sesspool = NULL; 1003 apr_hash_t *history1, *history2; 1004 svn_boolean_t has_rev_zero_history1; 1005 svn_boolean_t has_rev_zero_history2; 1006 1007 if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0) 1008 { 1009 *ancestor_p = NULL; 1010 return SVN_NO_ERROR; 1011 } 1012 1013 /* Open an RA session for the two locations. */ 1014 if (session == NULL) 1015 { 1016 sesspool = svn_pool_create(scratch_pool); 1017 SVN_ERR(svn_client_open_ra_session2(&session, loc1->url, NULL, ctx, 1018 sesspool, sesspool)); 1019 } 1020 1021 /* We're going to cheat and use history-as-mergeinfo because it 1022 saves us a bunch of annoying custom data comparisons and such. */ 1023 SVN_ERR(svn_client__get_history_as_mergeinfo(&history1, 1024 &has_rev_zero_history1, 1025 loc1, 1026 SVN_INVALID_REVNUM, 1027 SVN_INVALID_REVNUM, 1028 session, ctx, scratch_pool)); 1029 SVN_ERR(svn_client__get_history_as_mergeinfo(&history2, 1030 &has_rev_zero_history2, 1031 loc2, 1032 SVN_INVALID_REVNUM, 1033 SVN_INVALID_REVNUM, 1034 session, ctx, scratch_pool)); 1035 /* Close the ra session if we opened one. */ 1036 if (sesspool) 1037 svn_pool_destroy(sesspool); 1038 1039 SVN_ERR(svn_client__calc_youngest_common_ancestor(ancestor_p, 1040 loc1, history1, 1041 has_rev_zero_history1, 1042 loc2, history2, 1043 has_rev_zero_history2, 1044 result_pool, 1045 scratch_pool)); 1046 1047 return SVN_NO_ERROR; 1048} 1049 1050struct ra_ev2_baton { 1051 /* The working copy context, from the client context. */ 1052 svn_wc_context_t *wc_ctx; 1053 1054 /* For a given REPOS_RELPATH, provide a LOCAL_ABSPATH that represents 1055 that repository node. */ 1056 apr_hash_t *relpath_map; 1057}; 1058 1059 1060svn_error_t * 1061svn_client__ra_provide_base(svn_stream_t **contents, 1062 svn_revnum_t *revision, 1063 void *baton, 1064 const char *repos_relpath, 1065 apr_pool_t *result_pool, 1066 apr_pool_t *scratch_pool) 1067{ 1068 struct ra_ev2_baton *reb = baton; 1069 const char *local_abspath; 1070 svn_error_t *err; 1071 1072 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath); 1073 if (!local_abspath) 1074 { 1075 *contents = NULL; 1076 return SVN_NO_ERROR; 1077 } 1078 1079 err = svn_wc_get_pristine_contents2(contents, reb->wc_ctx, local_abspath, 1080 result_pool, scratch_pool); 1081 if (err) 1082 { 1083 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 1084 return svn_error_trace(err); 1085 1086 svn_error_clear(err); 1087 *contents = NULL; 1088 return SVN_NO_ERROR; 1089 } 1090 1091 if (*contents != NULL) 1092 { 1093 /* The pristine contents refer to the BASE, or to the pristine of 1094 a copy/move to this location. Fetch the correct revision. */ 1095 SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL, 1096 NULL, 1097 reb->wc_ctx, local_abspath, FALSE, 1098 scratch_pool, scratch_pool)); 1099 } 1100 1101 return SVN_NO_ERROR; 1102} 1103 1104 1105svn_error_t * 1106svn_client__ra_provide_props(apr_hash_t **props, 1107 svn_revnum_t *revision, 1108 void *baton, 1109 const char *repos_relpath, 1110 apr_pool_t *result_pool, 1111 apr_pool_t *scratch_pool) 1112{ 1113 struct ra_ev2_baton *reb = baton; 1114 const char *local_abspath; 1115 svn_error_t *err; 1116 1117 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath); 1118 if (!local_abspath) 1119 { 1120 *props = NULL; 1121 return SVN_NO_ERROR; 1122 } 1123 1124 err = svn_wc_get_pristine_props(props, reb->wc_ctx, local_abspath, 1125 result_pool, scratch_pool); 1126 if (err) 1127 { 1128 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 1129 return svn_error_trace(err); 1130 1131 svn_error_clear(err); 1132 *props = NULL; 1133 return SVN_NO_ERROR; 1134 } 1135 1136 if (*props != NULL) 1137 { 1138 /* The pristine props refer to the BASE, or to the pristine props of 1139 a copy/move to this location. Fetch the correct revision. */ 1140 SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL, 1141 NULL, 1142 reb->wc_ctx, local_abspath, FALSE, 1143 scratch_pool, scratch_pool)); 1144 } 1145 1146 return SVN_NO_ERROR; 1147} 1148 1149 1150svn_error_t * 1151svn_client__ra_get_copysrc_kind(svn_node_kind_t *kind, 1152 void *baton, 1153 const char *repos_relpath, 1154 svn_revnum_t src_revision, 1155 apr_pool_t *scratch_pool) 1156{ 1157 struct ra_ev2_baton *reb = baton; 1158 const char *local_abspath; 1159 1160 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath); 1161 if (!local_abspath) 1162 { 1163 *kind = svn_node_unknown; 1164 return SVN_NO_ERROR; 1165 } 1166 1167 /* ### what to do with SRC_REVISION? */ 1168 1169 SVN_ERR(svn_wc_read_kind2(kind, reb->wc_ctx, local_abspath, 1170 FALSE, FALSE, scratch_pool)); 1171 1172 return SVN_NO_ERROR; 1173} 1174 1175 1176void * 1177svn_client__ra_make_cb_baton(svn_wc_context_t *wc_ctx, 1178 apr_hash_t *relpath_map, 1179 apr_pool_t *result_pool) 1180{ 1181 struct ra_ev2_baton *reb = apr_palloc(result_pool, sizeof(*reb)); 1182 1183 SVN_ERR_ASSERT_NO_RETURN(wc_ctx != NULL); 1184 SVN_ERR_ASSERT_NO_RETURN(relpath_map != NULL); 1185 1186 reb->wc_ctx = wc_ctx; 1187 reb->relpath_map = relpath_map; 1188 1189 return reb; 1190} 1191