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, try the new URL. 406 We'll do this in a loop up to some maximum number follow-and-retry 407 attempts. */ 408 if (corrected_url) 409 { 410 apr_hash_t *attempted = apr_hash_make(scratch_pool); 411 int attempts_left = SVN_CLIENT__MAX_REDIRECT_ATTEMPTS; 412 413 *corrected_url = NULL; 414 while (attempts_left--) 415 { 416 const char *corrected = NULL; /* canonicalized version */ 417 const char *redirect_url = NULL; /* non-canonicalized version */ 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_open5(ra_session, 422 attempts_left == 0 ? NULL : &corrected, 423 attempts_left == 0 ? NULL : &redirect_url, 424 base_url, uuid, cbtable, cb, ctx->config, 425 result_pool)); 426 427 /* No error and no corrected URL? We're done here. */ 428 if (! corrected) 429 break; 430 431 /* Notify the user that a redirect is being followed. */ 432 if (ctx->notify_func2 != NULL) 433 { 434 svn_wc_notify_t *notify = 435 svn_wc_create_notify_url(corrected, 436 svn_wc_notify_url_redirect, 437 scratch_pool); 438 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); 439 } 440 441 /* Our caller will want to know what our final corrected URL was. */ 442 *corrected_url = corrected; 443 444 /* Make sure we've not attempted this URL before. */ 445 if (svn_hash_gets(attempted, redirect_url)) 446 return svn_error_createf(SVN_ERR_CLIENT_CYCLE_DETECTED, NULL, 447 _("Redirect cycle detected for URL '%s'"), 448 redirect_url); 449 450 /* 451 * Remember this redirect URL so we don't wind up in a loop. 452 * 453 * Store the non-canonicalized version of the URL. The canonicalized 454 * version is insufficient for loop detection because we might not get 455 * an exact match against URLs used by the RA protocol-layer (the URL 456 * used by the protocol may contain trailing slashes, for example, 457 * which are stripped during canonicalization). 458 */ 459 svn_hash_sets(attempted, redirect_url, (void *)1); 460 461 base_url = corrected; 462 } 463 } 464 else 465 { 466 SVN_ERR(svn_ra_open5(ra_session, NULL, NULL, base_url, 467 uuid, cbtable, cb, ctx->config, result_pool)); 468 } 469 470 return SVN_NO_ERROR; 471} 472#undef SVN_CLIENT__MAX_REDIRECT_ATTEMPTS 473 474 475svn_error_t * 476svn_client_open_ra_session2(svn_ra_session_t **session, 477 const char *url, 478 const char *wri_abspath, 479 svn_client_ctx_t *ctx, 480 apr_pool_t *result_pool, 481 apr_pool_t *scratch_pool) 482{ 483 return svn_error_trace( 484 svn_client__open_ra_session_internal(session, NULL, url, 485 wri_abspath, NULL, 486 FALSE, FALSE, 487 ctx, result_pool, 488 scratch_pool)); 489} 490 491svn_error_t * 492svn_client__resolve_rev_and_url(svn_client__pathrev_t **resolved_loc_p, 493 svn_ra_session_t *ra_session, 494 const char *path_or_url, 495 const svn_opt_revision_t *peg_revision, 496 const svn_opt_revision_t *revision, 497 svn_client_ctx_t *ctx, 498 apr_pool_t *pool) 499{ 500 svn_opt_revision_t peg_rev = *peg_revision; 501 svn_opt_revision_t start_rev = *revision; 502 const char *url; 503 svn_revnum_t rev; 504 505 /* Default revisions: peg -> working or head; operative -> peg. */ 506 SVN_ERR(svn_opt_resolve_revisions(&peg_rev, &start_rev, 507 svn_path_is_url(path_or_url), 508 TRUE /* notice_local_mods */, 509 pool)); 510 511 /* Run the history function to get the object's (possibly 512 different) url in REVISION. */ 513 SVN_ERR(svn_client__repos_locations(&url, &rev, NULL, NULL, 514 ra_session, path_or_url, &peg_rev, 515 &start_rev, NULL, ctx, pool)); 516 517 SVN_ERR(svn_client__pathrev_create_with_session(resolved_loc_p, 518 ra_session, rev, url, pool)); 519 return SVN_NO_ERROR; 520} 521 522svn_error_t * 523svn_client__ra_session_from_path2(svn_ra_session_t **ra_session_p, 524 svn_client__pathrev_t **resolved_loc_p, 525 const char *path_or_url, 526 const char *base_dir_abspath, 527 const svn_opt_revision_t *peg_revision, 528 const svn_opt_revision_t *revision, 529 svn_client_ctx_t *ctx, 530 apr_pool_t *pool) 531{ 532 svn_ra_session_t *ra_session; 533 const char *initial_url; 534 const char *corrected_url; 535 svn_client__pathrev_t *resolved_loc; 536 const char *wri_abspath; 537 538 SVN_ERR(svn_client_url_from_path2(&initial_url, path_or_url, ctx, pool, 539 pool)); 540 if (! initial_url) 541 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, 542 _("'%s' has no URL"), path_or_url); 543 544 if (base_dir_abspath) 545 wri_abspath = base_dir_abspath; 546 else if (!svn_path_is_url(path_or_url)) 547 SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url, pool)); 548 else 549 wri_abspath = NULL; 550 551 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, 552 initial_url, 553 wri_abspath, 554 NULL /* commit_items */, 555 base_dir_abspath != NULL, 556 base_dir_abspath != NULL, 557 ctx, pool, pool)); 558 559 /* If we got a CORRECTED_URL, we'll want to refer to that as the 560 URL-ized form of PATH_OR_URL from now on. */ 561 if (corrected_url && svn_path_is_url(path_or_url)) 562 path_or_url = corrected_url; 563 564 SVN_ERR(svn_client__resolve_rev_and_url(&resolved_loc, ra_session, 565 path_or_url, peg_revision, revision, 566 ctx, pool)); 567 568 /* Make the session point to the real URL. */ 569 SVN_ERR(svn_ra_reparent(ra_session, resolved_loc->url, pool)); 570 571 *ra_session_p = ra_session; 572 if (resolved_loc_p) 573 *resolved_loc_p = resolved_loc; 574 575 return SVN_NO_ERROR; 576} 577 578 579svn_error_t * 580svn_client__ensure_ra_session_url(const char **old_session_url, 581 svn_ra_session_t *ra_session, 582 const char *session_url, 583 apr_pool_t *pool) 584{ 585 SVN_ERR(svn_ra_get_session_url(ra_session, old_session_url, pool)); 586 if (! session_url) 587 SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_url, pool)); 588 if (strcmp(*old_session_url, session_url) != 0) 589 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool)); 590 return SVN_NO_ERROR; 591} 592 593 594 595/*** Repository Locations ***/ 596 597struct gls_receiver_baton_t 598{ 599 apr_array_header_t *segments; 600 svn_client_ctx_t *ctx; 601 apr_pool_t *pool; 602}; 603 604static svn_error_t * 605gls_receiver(svn_location_segment_t *segment, 606 void *baton, 607 apr_pool_t *pool) 608{ 609 struct gls_receiver_baton_t *b = baton; 610 APR_ARRAY_PUSH(b->segments, svn_location_segment_t *) = 611 svn_location_segment_dup(segment, b->pool); 612 if (b->ctx->cancel_func) 613 SVN_ERR((b->ctx->cancel_func)(b->ctx->cancel_baton)); 614 return SVN_NO_ERROR; 615} 616 617/* A qsort-compatible function which sorts svn_location_segment_t's 618 based on their revision range covering, resulting in ascending 619 (oldest-to-youngest) ordering. */ 620static int 621compare_segments(const void *a, const void *b) 622{ 623 const svn_location_segment_t *a_seg 624 = *((const svn_location_segment_t * const *) a); 625 const svn_location_segment_t *b_seg 626 = *((const svn_location_segment_t * const *) b); 627 if (a_seg->range_start == b_seg->range_start) 628 return 0; 629 return (a_seg->range_start < b_seg->range_start) ? -1 : 1; 630} 631 632svn_error_t * 633svn_client__repos_location_segments(apr_array_header_t **segments, 634 svn_ra_session_t *ra_session, 635 const char *url, 636 svn_revnum_t peg_revision, 637 svn_revnum_t start_revision, 638 svn_revnum_t end_revision, 639 svn_client_ctx_t *ctx, 640 apr_pool_t *pool) 641{ 642 struct gls_receiver_baton_t gls_receiver_baton; 643 const char *old_session_url; 644 svn_error_t *err; 645 646 *segments = apr_array_make(pool, 8, sizeof(svn_location_segment_t *)); 647 gls_receiver_baton.segments = *segments; 648 gls_receiver_baton.ctx = ctx; 649 gls_receiver_baton.pool = pool; 650 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, 651 url, pool)); 652 err = svn_ra_get_location_segments(ra_session, "", peg_revision, 653 start_revision, end_revision, 654 gls_receiver, &gls_receiver_baton, 655 pool); 656 SVN_ERR(svn_error_compose_create( 657 err, svn_ra_reparent(ra_session, old_session_url, pool))); 658 svn_sort__array(*segments, compare_segments); 659 return SVN_NO_ERROR; 660} 661 662/* Set *START_URL and *END_URL to the URLs that the object URL@PEG_REVNUM 663 * had in revisions START_REVNUM and END_REVNUM. Return an error if the 664 * node cannot be traced back to one of the requested revisions. 665 * 666 * START_URL and/or END_URL may be NULL if not wanted. START_REVNUM and 667 * END_REVNUM must be valid revision numbers except that END_REVNUM may 668 * be SVN_INVALID_REVNUM if END_URL is NULL. 669 * 670 * YOUNGEST_REV is the already retrieved youngest revision of the ra session, 671 * but can be SVN_INVALID_REVNUM if the value is not already retrieved. 672 * 673 * RA_SESSION is an open RA session parented at URL. 674 */ 675static svn_error_t * 676repos_locations(const char **start_url, 677 const char **end_url, 678 svn_ra_session_t *ra_session, 679 const char *url, 680 svn_revnum_t peg_revnum, 681 svn_revnum_t start_revnum, 682 svn_revnum_t end_revnum, 683 svn_revnum_t youngest_rev, 684 apr_pool_t *result_pool, 685 apr_pool_t *scratch_pool) 686{ 687 const char *repos_url, *start_path, *end_path; 688 apr_array_header_t *revs; 689 apr_hash_t *rev_locs; 690 691 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(peg_revnum)); 692 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_revnum)); 693 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_revnum) || end_url == NULL); 694 695 /* Avoid a network request in the common easy case. */ 696 if (start_revnum == peg_revnum 697 && (end_revnum == peg_revnum || end_revnum == SVN_INVALID_REVNUM)) 698 { 699 if (start_url) 700 *start_url = apr_pstrdup(result_pool, url); 701 if (end_url) 702 *end_url = apr_pstrdup(result_pool, url); 703 return SVN_NO_ERROR; 704 } 705 706 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, scratch_pool)); 707 708 /* Handle another common case: The repository root can't move */ 709 if (! strcmp(repos_url, url)) 710 { 711 if (! SVN_IS_VALID_REVNUM(youngest_rev)) 712 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest_rev, 713 scratch_pool)); 714 715 if (start_revnum > youngest_rev 716 || (SVN_IS_VALID_REVNUM(end_revnum) && (end_revnum > youngest_rev))) 717 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 718 _("No such revision %ld"), 719 (start_revnum > youngest_rev) 720 ? start_revnum : end_revnum); 721 722 if (start_url) 723 *start_url = apr_pstrdup(result_pool, repos_url); 724 if (end_url) 725 *end_url = apr_pstrdup(result_pool, repos_url); 726 return SVN_NO_ERROR; 727 } 728 729 revs = apr_array_make(scratch_pool, 2, sizeof(svn_revnum_t)); 730 APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum; 731 if (end_revnum != start_revnum && end_revnum != SVN_INVALID_REVNUM) 732 APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum; 733 734 SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum, 735 revs, scratch_pool)); 736 737 /* We'd better have all the paths we were looking for! */ 738 if (start_url) 739 { 740 start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(start_revnum)); 741 if (! start_path) 742 return svn_error_createf 743 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, 744 _("Unable to find repository location for '%s' in revision %ld"), 745 url, start_revnum); 746 *start_url = svn_path_url_add_component2(repos_url, start_path + 1, 747 result_pool); 748 } 749 750 if (end_url) 751 { 752 end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(end_revnum)); 753 if (! end_path) 754 return svn_error_createf 755 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, 756 _("The location for '%s' for revision %ld does not exist in the " 757 "repository or refers to an unrelated object"), 758 url, end_revnum); 759 760 *end_url = svn_path_url_add_component2(repos_url, end_path + 1, 761 result_pool); 762 } 763 764 return SVN_NO_ERROR; 765} 766 767svn_error_t * 768svn_client__repos_location(svn_client__pathrev_t **op_loc_p, 769 svn_ra_session_t *ra_session, 770 const svn_client__pathrev_t *peg_loc, 771 svn_revnum_t op_revnum, 772 svn_client_ctx_t *ctx, 773 apr_pool_t *result_pool, 774 apr_pool_t *scratch_pool) 775{ 776 const char *old_session_url; 777 const char *op_url; 778 svn_error_t *err; 779 780 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session, 781 peg_loc->url, scratch_pool)); 782 err = repos_locations(&op_url, NULL, ra_session, 783 peg_loc->url, peg_loc->rev, 784 op_revnum, SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, 785 result_pool, scratch_pool); 786 SVN_ERR(svn_error_compose_create( 787 err, svn_ra_reparent(ra_session, old_session_url, scratch_pool))); 788 789 *op_loc_p = svn_client__pathrev_create(peg_loc->repos_root_url, 790 peg_loc->repos_uuid, 791 op_revnum, op_url, result_pool); 792 return SVN_NO_ERROR; 793} 794 795svn_error_t * 796svn_client__repos_locations(const char **start_url, 797 svn_revnum_t *start_revision, 798 const char **end_url, 799 svn_revnum_t *end_revision, 800 svn_ra_session_t *ra_session, 801 const char *path, 802 const svn_opt_revision_t *revision, 803 const svn_opt_revision_t *start, 804 const svn_opt_revision_t *end, 805 svn_client_ctx_t *ctx, 806 apr_pool_t *pool) 807{ 808 const char *url; 809 const char *local_abspath_or_url; 810 svn_revnum_t peg_revnum = SVN_INVALID_REVNUM; 811 svn_revnum_t start_revnum, end_revnum; 812 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM; 813 apr_pool_t *subpool = svn_pool_create(pool); 814 815 /* Ensure that we are given some real revision data to work with. 816 (It's okay if the END is unspecified -- in that case, we'll just 817 set it to the same thing as START.) */ 818 if (revision->kind == svn_opt_revision_unspecified 819 || start->kind == svn_opt_revision_unspecified) 820 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL); 821 822 if (end == NULL) 823 { 824 static const svn_opt_revision_t unspecified_rev 825 = { svn_opt_revision_unspecified, { 0 } }; 826 827 end = &unspecified_rev; 828 } 829 830 /* Determine LOCAL_ABSPATH_OR_URL, URL, and possibly PEG_REVNUM. 831 If we are looking at the working version of a WC path that is scheduled 832 as a copy, then we need to use the copy-from URL and peg revision. */ 833 if (! svn_path_is_url(path)) 834 { 835 SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, subpool)); 836 837 if (revision->kind == svn_opt_revision_working) 838 { 839 const char *repos_root_url; 840 const char *repos_relpath; 841 svn_boolean_t is_copy; 842 843 SVN_ERR(svn_wc__node_get_origin(&is_copy, &peg_revnum, &repos_relpath, 844 &repos_root_url, NULL, NULL, NULL, 845 ctx->wc_ctx, local_abspath_or_url, 846 FALSE, subpool, subpool)); 847 848 if (repos_relpath) 849 url = svn_path_url_add_component2(repos_root_url, repos_relpath, 850 pool); 851 else 852 url = NULL; 853 854 if (url && is_copy && ra_session) 855 { 856 const char *session_url; 857 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, 858 subpool)); 859 860 if (strcmp(session_url, url) != 0) 861 { 862 /* We can't use the caller provided RA session now :( */ 863 ra_session = NULL; 864 } 865 } 866 } 867 else 868 url = NULL; 869 870 if (! url) 871 SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, 872 local_abspath_or_url, pool, subpool)); 873 874 if (!url) 875 { 876 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, 877 _("'%s' has no URL"), 878 svn_dirent_local_style(path, pool)); 879 } 880 } 881 else 882 { 883 local_abspath_or_url = path; 884 url = path; 885 } 886 887 /* ### We should be smarter here. If the callers just asks for BASE and 888 WORKING revisions, we should already have the correct URLs, so we 889 don't need to do anything more here in that case. */ 890 891 /* Open a RA session to this URL if we don't have one already. */ 892 if (! ra_session) 893 SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL, 894 ctx, subpool, subpool)); 895 896 /* Resolve the opt_revision_ts. */ 897 if (peg_revnum == SVN_INVALID_REVNUM) 898 SVN_ERR(svn_client__get_revision_number(&peg_revnum, &youngest_rev, 899 ctx->wc_ctx, local_abspath_or_url, 900 ra_session, revision, pool)); 901 902 SVN_ERR(svn_client__get_revision_number(&start_revnum, &youngest_rev, 903 ctx->wc_ctx, local_abspath_or_url, 904 ra_session, start, pool)); 905 if (end->kind == svn_opt_revision_unspecified) 906 end_revnum = start_revnum; 907 else 908 SVN_ERR(svn_client__get_revision_number(&end_revnum, &youngest_rev, 909 ctx->wc_ctx, local_abspath_or_url, 910 ra_session, end, pool)); 911 912 /* Set the output revision variables. */ 913 if (start_revision) 914 { 915 *start_revision = start_revnum; 916 } 917 if (end_revision && end->kind != svn_opt_revision_unspecified) 918 { 919 *end_revision = end_revnum; 920 } 921 922 SVN_ERR(repos_locations(start_url, end_url, 923 ra_session, url, peg_revnum, 924 start_revnum, end_revnum, youngest_rev, 925 pool, subpool)); 926 svn_pool_destroy(subpool); 927 return SVN_NO_ERROR; 928} 929 930svn_error_t * 931svn_client__calc_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p, 932 const svn_client__pathrev_t *loc1, 933 apr_hash_t *history1, 934 svn_boolean_t has_rev_zero_history1, 935 const svn_client__pathrev_t *loc2, 936 apr_hash_t *history2, 937 svn_boolean_t has_rev_zero_history2, 938 apr_pool_t *result_pool, 939 apr_pool_t *scratch_pool) 940{ 941 apr_hash_index_t *hi; 942 svn_revnum_t yc_revision = SVN_INVALID_REVNUM; 943 const char *yc_relpath = NULL; 944 945 if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0) 946 { 947 *ancestor_p = NULL; 948 return SVN_NO_ERROR; 949 } 950 951 /* Loop through the first location's history, check for overlapping 952 paths and ranges in the second location's history, and 953 remembering the youngest matching location. */ 954 for (hi = apr_hash_first(scratch_pool, history1); hi; hi = apr_hash_next(hi)) 955 { 956 const char *path = apr_hash_this_key(hi); 957 apr_ssize_t path_len = apr_hash_this_key_len(hi); 958 svn_rangelist_t *ranges1 = apr_hash_this_val(hi); 959 svn_rangelist_t *ranges2, *common; 960 961 ranges2 = apr_hash_get(history2, path, path_len); 962 if (ranges2) 963 { 964 /* We have a path match. Now, did our two histories share 965 any revisions at that path? */ 966 SVN_ERR(svn_rangelist_intersect(&common, ranges1, ranges2, 967 TRUE, scratch_pool)); 968 if (common->nelts) 969 { 970 svn_merge_range_t *yc_range = 971 APR_ARRAY_IDX(common, common->nelts - 1, svn_merge_range_t *); 972 if ((! SVN_IS_VALID_REVNUM(yc_revision)) 973 || (yc_range->end > yc_revision)) 974 { 975 yc_revision = yc_range->end; 976 yc_relpath = path + 1; 977 } 978 } 979 } 980 } 981 982 /* It's possible that PATH_OR_URL1 and PATH_OR_URL2's only common 983 history is revision 0. */ 984 if (!yc_relpath && has_rev_zero_history1 && has_rev_zero_history2) 985 { 986 yc_relpath = ""; 987 yc_revision = 0; 988 } 989 990 if (yc_relpath) 991 { 992 *ancestor_p = svn_client__pathrev_create_with_relpath( 993 loc1->repos_root_url, loc1->repos_uuid, 994 yc_revision, yc_relpath, result_pool); 995 } 996 else 997 { 998 *ancestor_p = NULL; 999 } 1000 return SVN_NO_ERROR; 1001} 1002 1003svn_error_t * 1004svn_client__get_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p, 1005 const svn_client__pathrev_t *loc1, 1006 const svn_client__pathrev_t *loc2, 1007 svn_ra_session_t *session, 1008 svn_client_ctx_t *ctx, 1009 apr_pool_t *result_pool, 1010 apr_pool_t *scratch_pool) 1011{ 1012 apr_pool_t *sesspool = NULL; 1013 apr_hash_t *history1, *history2; 1014 svn_boolean_t has_rev_zero_history1; 1015 svn_boolean_t has_rev_zero_history2; 1016 1017 if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0) 1018 { 1019 *ancestor_p = NULL; 1020 return SVN_NO_ERROR; 1021 } 1022 1023 /* Open an RA session for the two locations. */ 1024 if (session == NULL) 1025 { 1026 sesspool = svn_pool_create(scratch_pool); 1027 SVN_ERR(svn_client_open_ra_session2(&session, loc1->url, NULL, ctx, 1028 sesspool, sesspool)); 1029 } 1030 1031 /* We're going to cheat and use history-as-mergeinfo because it 1032 saves us a bunch of annoying custom data comparisons and such. */ 1033 SVN_ERR(svn_client__get_history_as_mergeinfo(&history1, 1034 &has_rev_zero_history1, 1035 loc1, 1036 SVN_INVALID_REVNUM, 1037 SVN_INVALID_REVNUM, 1038 session, ctx, scratch_pool)); 1039 SVN_ERR(svn_client__get_history_as_mergeinfo(&history2, 1040 &has_rev_zero_history2, 1041 loc2, 1042 SVN_INVALID_REVNUM, 1043 SVN_INVALID_REVNUM, 1044 session, ctx, scratch_pool)); 1045 /* Close the ra session if we opened one. */ 1046 if (sesspool) 1047 svn_pool_destroy(sesspool); 1048 1049 SVN_ERR(svn_client__calc_youngest_common_ancestor(ancestor_p, 1050 loc1, history1, 1051 has_rev_zero_history1, 1052 loc2, history2, 1053 has_rev_zero_history2, 1054 result_pool, 1055 scratch_pool)); 1056 1057 return SVN_NO_ERROR; 1058} 1059 1060struct ra_ev2_baton { 1061 /* The working copy context, from the client context. */ 1062 svn_wc_context_t *wc_ctx; 1063 1064 /* For a given REPOS_RELPATH, provide a LOCAL_ABSPATH that represents 1065 that repository node. */ 1066 apr_hash_t *relpath_map; 1067}; 1068 1069 1070svn_error_t * 1071svn_client__ra_provide_base(svn_stream_t **contents, 1072 svn_revnum_t *revision, 1073 void *baton, 1074 const char *repos_relpath, 1075 apr_pool_t *result_pool, 1076 apr_pool_t *scratch_pool) 1077{ 1078 struct ra_ev2_baton *reb = baton; 1079 const char *local_abspath; 1080 svn_error_t *err; 1081 1082 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath); 1083 if (!local_abspath) 1084 { 1085 *contents = NULL; 1086 return SVN_NO_ERROR; 1087 } 1088 1089 err = svn_wc_get_pristine_contents2(contents, reb->wc_ctx, local_abspath, 1090 result_pool, scratch_pool); 1091 if (err) 1092 { 1093 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 1094 return svn_error_trace(err); 1095 1096 svn_error_clear(err); 1097 *contents = NULL; 1098 return SVN_NO_ERROR; 1099 } 1100 1101 if (*contents != NULL) 1102 { 1103 /* The pristine contents refer to the BASE, or to the pristine of 1104 a copy/move to this location. Fetch the correct revision. */ 1105 SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL, 1106 NULL, 1107 reb->wc_ctx, local_abspath, FALSE, 1108 scratch_pool, scratch_pool)); 1109 } 1110 1111 return SVN_NO_ERROR; 1112} 1113 1114 1115svn_error_t * 1116svn_client__ra_provide_props(apr_hash_t **props, 1117 svn_revnum_t *revision, 1118 void *baton, 1119 const char *repos_relpath, 1120 apr_pool_t *result_pool, 1121 apr_pool_t *scratch_pool) 1122{ 1123 struct ra_ev2_baton *reb = baton; 1124 const char *local_abspath; 1125 svn_error_t *err; 1126 1127 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath); 1128 if (!local_abspath) 1129 { 1130 *props = NULL; 1131 return SVN_NO_ERROR; 1132 } 1133 1134 err = svn_wc_get_pristine_props(props, reb->wc_ctx, local_abspath, 1135 result_pool, scratch_pool); 1136 if (err) 1137 { 1138 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 1139 return svn_error_trace(err); 1140 1141 svn_error_clear(err); 1142 *props = NULL; 1143 return SVN_NO_ERROR; 1144 } 1145 1146 if (*props != NULL) 1147 { 1148 /* The pristine props refer to the BASE, or to the pristine props of 1149 a copy/move to this location. Fetch the correct revision. */ 1150 SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL, 1151 NULL, 1152 reb->wc_ctx, local_abspath, FALSE, 1153 scratch_pool, scratch_pool)); 1154 } 1155 1156 return SVN_NO_ERROR; 1157} 1158 1159 1160svn_error_t * 1161svn_client__ra_get_copysrc_kind(svn_node_kind_t *kind, 1162 void *baton, 1163 const char *repos_relpath, 1164 svn_revnum_t src_revision, 1165 apr_pool_t *scratch_pool) 1166{ 1167 struct ra_ev2_baton *reb = baton; 1168 const char *local_abspath; 1169 1170 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath); 1171 if (!local_abspath) 1172 { 1173 *kind = svn_node_unknown; 1174 return SVN_NO_ERROR; 1175 } 1176 1177 /* ### what to do with SRC_REVISION? */ 1178 1179 SVN_ERR(svn_wc_read_kind2(kind, reb->wc_ctx, local_abspath, 1180 FALSE, FALSE, scratch_pool)); 1181 1182 return SVN_NO_ERROR; 1183} 1184 1185 1186void * 1187svn_client__ra_make_cb_baton(svn_wc_context_t *wc_ctx, 1188 apr_hash_t *relpath_map, 1189 apr_pool_t *result_pool) 1190{ 1191 struct ra_ev2_baton *reb = apr_palloc(result_pool, sizeof(*reb)); 1192 1193 SVN_ERR_ASSERT_NO_RETURN(wc_ctx != NULL); 1194 SVN_ERR_ASSERT_NO_RETURN(relpath_map != NULL); 1195 1196 reb->wc_ctx = wc_ctx; 1197 reb->relpath_map = relpath_map; 1198 1199 return reb; 1200} 1201