2 * ra.c : routines for interacting with the RA layer
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
26 #include <apr_pools.h>
28 #include "svn_error.h"
30 #include "svn_pools.h"
31 #include "svn_string.h"
32 #include "svn_sorts.h"
34 #include "svn_client.h"
35 #include "svn_dirent_uri.h"
37 #include "svn_props.h"
38 #include "svn_mergeinfo.h"
40 #include "mergeinfo.h"
42 #include "svn_private_config.h"
43 #include "private/svn_wc_private.h"
44 #include "private/svn_client_private.h"
47 /* This is the baton that we pass svn_ra_open3(), and is associated with
48 the callback table we provide to RA. */
49 typedef struct callback_baton_t
51 /* Holds the directory that corresponds to the REPOS_URL at svn_ra_open3()
52 time. When callbacks specify a relative path, they are joined with
53 this base directory. */
54 const char *base_dir_abspath;
56 /* TEMPORARY: Is 'base_dir_abspath' a versioned path? cmpilato
57 suspects that the commit-to-multiple-disjoint-working-copies
58 code is getting this all wrong, sometimes passing an unversioned
59 (or versioned in a foreign wc) path here which sorta kinda
60 happens to work most of the time but is ultimately incorrect. */
61 svn_boolean_t base_dir_isversioned;
63 /* Used as wri_abspath for obtaining access to the pristine store */
64 const char *wcroot_abspath;
66 /* An array of svn_client_commit_item3_t * structures, present only
67 during working copy commits. */
68 const apr_array_header_t *commit_items;
70 /* A client context. */
71 svn_client_ctx_t *ctx;
78 open_tmp_file(apr_file_t **fp,
82 return svn_error_trace(svn_io_open_unique_file3(fp, NULL, NULL,
83 svn_io_file_del_on_pool_cleanup,
88 /* This implements the 'svn_ra_get_wc_prop_func_t' interface. */
90 get_wc_prop(void *baton,
93 const svn_string_t **value,
96 callback_baton_t *cb = baton;
97 const char *local_abspath = NULL;
102 /* If we have a list of commit_items, search through that for a
103 match for this relative URL. */
104 if (cb->commit_items)
107 for (i = 0; i < cb->commit_items->nelts; i++)
109 svn_client_commit_item3_t *item
110 = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
112 if (! strcmp(relpath, item->session_relpath))
114 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
115 local_abspath = item->path;
120 /* Commits can only query relpaths in the commit_items list
121 since the commit driver traverses paths as they are, or will
122 be, in the repository. Non-commits query relpaths in the
128 /* If we don't have a base directory, then there are no properties. */
129 else if (cb->base_dir_abspath == NULL)
133 local_abspath = svn_dirent_join(cb->base_dir_abspath, relpath, pool);
135 err = svn_wc_prop_get2(value, cb->ctx->wc_ctx, local_abspath, name,
137 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
139 svn_error_clear(err);
142 return svn_error_trace(err);
145 /* This implements the 'svn_ra_push_wc_prop_func_t' interface. */
147 push_wc_prop(void *baton,
150 const svn_string_t *value,
153 callback_baton_t *cb = baton;
156 /* If we're committing, search through the commit_items list for a
157 match for this relative URL. */
158 if (! cb->commit_items)
159 return svn_error_createf
160 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
161 _("Attempt to set wcprop '%s' on '%s' in a non-commit operation"),
162 name, svn_dirent_local_style(relpath, pool));
164 for (i = 0; i < cb->commit_items->nelts; i++)
166 svn_client_commit_item3_t *item
167 = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
169 if (strcmp(relpath, item->session_relpath) == 0)
171 apr_pool_t *changes_pool = item->incoming_prop_changes->pool;
172 svn_prop_t *prop = apr_palloc(changes_pool, sizeof(*prop));
174 prop->name = apr_pstrdup(changes_pool, name);
176 prop->value = svn_string_dup(value, changes_pool);
180 /* Buffer the propchange to take effect during the
181 post-commit process. */
182 APR_ARRAY_PUSH(item->incoming_prop_changes, svn_prop_t *) = prop;
191 /* This implements the 'svn_ra_set_wc_prop_func_t' interface. */
193 set_wc_prop(void *baton,
196 const svn_string_t *value,
199 callback_baton_t *cb = baton;
200 const char *local_abspath;
202 local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool);
204 /* We pass 1 for the 'force' parameter here. Since the property is
205 coming from the repository, we definitely want to accept it.
206 Ideally, we'd raise a conflict if, say, the received property is
207 svn:eol-style yet the file has a locally added svn:mime-type
208 claiming that it's binary. Probably the repository is still
209 right, but the conflict would remind the user to make sure.
210 Unfortunately, we don't have a clean mechanism for doing that
211 here, so we just set the property and hope for the best. */
212 return svn_error_trace(svn_wc_prop_set4(cb->ctx->wc_ctx, local_abspath,
214 value, svn_depth_empty,
215 TRUE /* skip_checks */,
216 NULL /* changelist_filter */,
217 NULL, NULL /* cancellation */,
218 NULL, NULL /* notification */,
223 /* This implements the `svn_ra_invalidate_wc_props_func_t' interface. */
225 invalidate_wc_props(void *baton,
227 const char *prop_name,
230 callback_baton_t *cb = baton;
231 const char *local_abspath;
233 local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool);
235 /* It's easier just to clear the whole dav_cache than to remove
236 individual items from it recursively like this. And since we
237 know that the RA providers that ship with Subversion only
238 invalidate the one property they use the most from this cache,
239 and that we're intentionally trying to get away from the use of
240 the cache altogether anyway, there's little to lose in wiping the
241 whole cache. Is it the most well-behaved approach to take? Not
242 so much. We choose not to care. */
243 return svn_error_trace(svn_wc__node_clear_dav_cache_recursive(
244 cb->ctx->wc_ctx, local_abspath, pool));
248 /* This implements the `svn_ra_get_wc_contents_func_t' interface. */
250 get_wc_contents(void *baton,
251 svn_stream_t **contents,
252 const svn_checksum_t *checksum,
255 callback_baton_t *cb = baton;
257 if (! cb->wcroot_abspath)
263 return svn_error_trace(
264 svn_wc__get_pristine_contents_by_checksum(contents,
273 cancel_callback(void *baton)
275 callback_baton_t *b = baton;
276 return svn_error_trace((b->ctx->cancel_func)(b->ctx->cancel_baton));
281 get_client_string(void *baton,
285 callback_baton_t *b = baton;
286 *name = apr_pstrdup(pool, b->ctx->client_name);
291 #define SVN_CLIENT__MAX_REDIRECT_ATTEMPTS 3 /* ### TODO: Make configurable. */
294 svn_client__open_ra_session_internal(svn_ra_session_t **ra_session,
295 const char **corrected_url,
296 const char *base_url,
297 const char *base_dir_abspath,
298 const apr_array_header_t *commit_items,
299 svn_boolean_t write_dav_props,
300 svn_boolean_t read_dav_props,
301 svn_client_ctx_t *ctx,
302 apr_pool_t *result_pool,
303 apr_pool_t *scratch_pool)
305 svn_ra_callbacks2_t *cbtable;
306 callback_baton_t *cb = apr_pcalloc(result_pool, sizeof(*cb));
307 const char *uuid = NULL;
309 SVN_ERR_ASSERT(!write_dav_props || read_dav_props);
310 SVN_ERR_ASSERT(!read_dav_props || base_dir_abspath != NULL);
311 SVN_ERR_ASSERT(base_dir_abspath == NULL
312 || svn_dirent_is_absolute(base_dir_abspath));
314 SVN_ERR(svn_ra_create_callbacks(&cbtable, result_pool));
315 cbtable->open_tmp_file = open_tmp_file;
316 cbtable->get_wc_prop = read_dav_props ? get_wc_prop : NULL;
317 cbtable->set_wc_prop = (write_dav_props && read_dav_props)
318 ? set_wc_prop : NULL;
319 cbtable->push_wc_prop = commit_items ? push_wc_prop : NULL;
320 cbtable->invalidate_wc_props = (write_dav_props && read_dav_props)
321 ? invalidate_wc_props : NULL;
322 cbtable->auth_baton = ctx->auth_baton; /* new-style */
323 cbtable->progress_func = ctx->progress_func;
324 cbtable->progress_baton = ctx->progress_baton;
325 cbtable->cancel_func = ctx->cancel_func ? cancel_callback : NULL;
326 cbtable->get_client_string = get_client_string;
327 if (base_dir_abspath)
328 cbtable->get_wc_contents = get_wc_contents;
330 cb->commit_items = commit_items;
333 if (base_dir_abspath && (read_dav_props || write_dav_props))
335 svn_error_t *err = svn_wc__node_get_repos_info(NULL, NULL, NULL, &uuid,
341 if (err && (err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY
342 || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND
343 || err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED))
345 svn_error_clear(err);
351 cb->base_dir_isversioned = TRUE;
353 cb->base_dir_abspath = apr_pstrdup(result_pool, base_dir_abspath);
356 if (base_dir_abspath)
358 svn_error_t *err = svn_wc__get_wcroot(&cb->wcroot_abspath,
359 ctx->wc_ctx, base_dir_abspath,
360 result_pool, scratch_pool);
364 if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY
365 && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
366 && err->apr_err != SVN_ERR_WC_UPGRADE_REQUIRED)
367 return svn_error_trace(err);
369 svn_error_clear(err);
370 cb->wcroot_abspath = NULL;
374 /* If the caller allows for auto-following redirections, and the
375 RA->open() call above reveals a CORRECTED_URL, try the new URL.
376 We'll do this in a loop up to some maximum number follow-and-retry
380 apr_hash_t *attempted = apr_hash_make(scratch_pool);
381 int attempts_left = SVN_CLIENT__MAX_REDIRECT_ATTEMPTS;
383 *corrected_url = NULL;
384 while (attempts_left--)
386 const char *corrected = NULL;
388 /* Try to open the RA session. If this is our last attempt,
389 don't accept corrected URLs from the RA provider. */
390 SVN_ERR(svn_ra_open4(ra_session,
391 attempts_left == 0 ? NULL : &corrected,
392 base_url, uuid, cbtable, cb, ctx->config,
395 /* No error and no corrected URL? We're done here. */
399 /* Notify the user that a redirect is being followed. */
400 if (ctx->notify_func2 != NULL)
402 svn_wc_notify_t *notify =
403 svn_wc_create_notify_url(corrected,
404 svn_wc_notify_url_redirect,
406 (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
409 /* Our caller will want to know what our final corrected URL was. */
410 *corrected_url = corrected;
412 /* Make sure we've not attempted this URL before. */
413 if (svn_hash_gets(attempted, corrected))
414 return svn_error_createf(SVN_ERR_CLIENT_CYCLE_DETECTED, NULL,
415 _("Redirect cycle detected for URL '%s'"),
418 /* Remember this CORRECTED_URL so we don't wind up in a loop. */
419 svn_hash_sets(attempted, corrected, (void *)1);
420 base_url = corrected;
425 SVN_ERR(svn_ra_open4(ra_session, NULL, base_url,
426 uuid, cbtable, cb, ctx->config, result_pool));
431 #undef SVN_CLIENT__MAX_REDIRECT_ATTEMPTS
435 svn_client_open_ra_session2(svn_ra_session_t **session,
437 const char *wri_abspath,
438 svn_client_ctx_t *ctx,
439 apr_pool_t *result_pool,
440 apr_pool_t *scratch_pool)
442 return svn_error_trace(
443 svn_client__open_ra_session_internal(session, NULL, url,
451 svn_client__resolve_rev_and_url(svn_client__pathrev_t **resolved_loc_p,
452 svn_ra_session_t *ra_session,
453 const char *path_or_url,
454 const svn_opt_revision_t *peg_revision,
455 const svn_opt_revision_t *revision,
456 svn_client_ctx_t *ctx,
459 svn_opt_revision_t peg_rev = *peg_revision;
460 svn_opt_revision_t start_rev = *revision;
464 /* Default revisions: peg -> working or head; operative -> peg. */
465 SVN_ERR(svn_opt_resolve_revisions(&peg_rev, &start_rev,
466 svn_path_is_url(path_or_url),
467 TRUE /* notice_local_mods */,
470 /* Run the history function to get the object's (possibly
471 different) url in REVISION. */
472 SVN_ERR(svn_client__repos_locations(&url, &rev, NULL, NULL,
473 ra_session, path_or_url, &peg_rev,
474 &start_rev, NULL, ctx, pool));
476 SVN_ERR(svn_client__pathrev_create_with_session(resolved_loc_p,
477 ra_session, rev, url, pool));
482 svn_client__ra_session_from_path2(svn_ra_session_t **ra_session_p,
483 svn_client__pathrev_t **resolved_loc_p,
484 const char *path_or_url,
485 const char *base_dir_abspath,
486 const svn_opt_revision_t *peg_revision,
487 const svn_opt_revision_t *revision,
488 svn_client_ctx_t *ctx,
491 svn_ra_session_t *ra_session;
492 const char *initial_url;
493 const char *corrected_url;
494 svn_client__pathrev_t *resolved_loc;
495 const char *wri_abspath;
497 SVN_ERR(svn_client_url_from_path2(&initial_url, path_or_url, ctx, pool,
500 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
501 _("'%s' has no URL"), path_or_url);
503 if (base_dir_abspath)
504 wri_abspath = base_dir_abspath;
505 else if (!svn_path_is_url(path_or_url))
506 SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url, pool));
510 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
513 NULL /* commit_items */,
514 base_dir_abspath != NULL,
515 base_dir_abspath != NULL,
518 /* If we got a CORRECTED_URL, we'll want to refer to that as the
519 URL-ized form of PATH_OR_URL from now on. */
520 if (corrected_url && svn_path_is_url(path_or_url))
521 path_or_url = corrected_url;
523 SVN_ERR(svn_client__resolve_rev_and_url(&resolved_loc, ra_session,
524 path_or_url, peg_revision, revision,
527 /* Make the session point to the real URL. */
528 SVN_ERR(svn_ra_reparent(ra_session, resolved_loc->url, pool));
530 *ra_session_p = ra_session;
532 *resolved_loc_p = resolved_loc;
539 svn_client__ensure_ra_session_url(const char **old_session_url,
540 svn_ra_session_t *ra_session,
541 const char *session_url,
544 SVN_ERR(svn_ra_get_session_url(ra_session, old_session_url, pool));
546 SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_url, pool));
547 if (strcmp(*old_session_url, session_url) != 0)
548 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
554 /*** Repository Locations ***/
556 struct gls_receiver_baton_t
558 apr_array_header_t *segments;
559 svn_client_ctx_t *ctx;
564 gls_receiver(svn_location_segment_t *segment,
568 struct gls_receiver_baton_t *b = baton;
569 APR_ARRAY_PUSH(b->segments, svn_location_segment_t *) =
570 svn_location_segment_dup(segment, b->pool);
571 if (b->ctx->cancel_func)
572 SVN_ERR((b->ctx->cancel_func)(b->ctx->cancel_baton));
576 /* A qsort-compatible function which sorts svn_location_segment_t's
577 based on their revision range covering, resulting in ascending
578 (oldest-to-youngest) ordering. */
580 compare_segments(const void *a, const void *b)
582 const svn_location_segment_t *a_seg
583 = *((const svn_location_segment_t * const *) a);
584 const svn_location_segment_t *b_seg
585 = *((const svn_location_segment_t * const *) b);
586 if (a_seg->range_start == b_seg->range_start)
588 return (a_seg->range_start < b_seg->range_start) ? -1 : 1;
592 svn_client__repos_location_segments(apr_array_header_t **segments,
593 svn_ra_session_t *ra_session,
595 svn_revnum_t peg_revision,
596 svn_revnum_t start_revision,
597 svn_revnum_t end_revision,
598 svn_client_ctx_t *ctx,
601 struct gls_receiver_baton_t gls_receiver_baton;
602 const char *old_session_url;
605 *segments = apr_array_make(pool, 8, sizeof(svn_location_segment_t *));
606 gls_receiver_baton.segments = *segments;
607 gls_receiver_baton.ctx = ctx;
608 gls_receiver_baton.pool = pool;
609 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
611 err = svn_ra_get_location_segments(ra_session, "", peg_revision,
612 start_revision, end_revision,
613 gls_receiver, &gls_receiver_baton,
615 SVN_ERR(svn_error_compose_create(
616 err, svn_ra_reparent(ra_session, old_session_url, pool)));
617 qsort((*segments)->elts, (*segments)->nelts,
618 (*segments)->elt_size, compare_segments);
622 /* Set *START_URL and *END_URL to the URLs that the object URL@PEG_REVNUM
623 * had in revisions START_REVNUM and END_REVNUM. Return an error if the
624 * node cannot be traced back to one of the requested revisions.
626 * START_URL and/or END_URL may be NULL if not wanted. START_REVNUM and
627 * END_REVNUM must be valid revision numbers except that END_REVNUM may
628 * be SVN_INVALID_REVNUM if END_URL is NULL.
630 * RA_SESSION is an open RA session parented at URL.
633 repos_locations(const char **start_url,
634 const char **end_url,
635 svn_ra_session_t *ra_session,
637 svn_revnum_t peg_revnum,
638 svn_revnum_t start_revnum,
639 svn_revnum_t end_revnum,
640 apr_pool_t *result_pool,
641 apr_pool_t *scratch_pool)
643 const char *repos_url, *start_path, *end_path;
644 apr_array_header_t *revs;
645 apr_hash_t *rev_locs;
647 SVN_ERR_ASSERT(peg_revnum != SVN_INVALID_REVNUM);
648 SVN_ERR_ASSERT(start_revnum != SVN_INVALID_REVNUM);
649 SVN_ERR_ASSERT(end_revnum != SVN_INVALID_REVNUM || end_url == NULL);
651 /* Avoid a network request in the common easy case. */
652 if (start_revnum == peg_revnum
653 && (end_revnum == peg_revnum || end_revnum == SVN_INVALID_REVNUM))
656 *start_url = apr_pstrdup(result_pool, url);
658 *end_url = apr_pstrdup(result_pool, url);
662 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, scratch_pool));
664 revs = apr_array_make(scratch_pool, 2, sizeof(svn_revnum_t));
665 APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum;
666 if (end_revnum != start_revnum && end_revnum != SVN_INVALID_REVNUM)
667 APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum;
669 SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum,
670 revs, scratch_pool));
672 /* We'd better have all the paths we were looking for! */
675 start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(svn_revnum_t));
677 return svn_error_createf
678 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
679 _("Unable to find repository location for '%s' in revision %ld"),
681 *start_url = svn_path_url_add_component2(repos_url, start_path + 1,
687 end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t));
689 return svn_error_createf
690 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
691 _("The location for '%s' for revision %ld does not exist in the "
692 "repository or refers to an unrelated object"),
695 *end_url = svn_path_url_add_component2(repos_url, end_path + 1,
703 svn_client__repos_location(svn_client__pathrev_t **op_loc_p,
704 svn_ra_session_t *ra_session,
705 const svn_client__pathrev_t *peg_loc,
706 svn_revnum_t op_revnum,
707 svn_client_ctx_t *ctx,
708 apr_pool_t *result_pool,
709 apr_pool_t *scratch_pool)
711 const char *old_session_url;
715 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
716 peg_loc->url, scratch_pool));
717 err = repos_locations(&op_url, NULL, ra_session,
718 peg_loc->url, peg_loc->rev,
719 op_revnum, SVN_INVALID_REVNUM,
720 result_pool, scratch_pool);
721 SVN_ERR(svn_error_compose_create(
722 err, svn_ra_reparent(ra_session, old_session_url, scratch_pool)));
724 *op_loc_p = svn_client__pathrev_create(peg_loc->repos_root_url,
726 op_revnum, op_url, result_pool);
731 svn_client__repos_locations(const char **start_url,
732 svn_revnum_t *start_revision,
733 const char **end_url,
734 svn_revnum_t *end_revision,
735 svn_ra_session_t *ra_session,
737 const svn_opt_revision_t *revision,
738 const svn_opt_revision_t *start,
739 const svn_opt_revision_t *end,
740 svn_client_ctx_t *ctx,
744 const char *local_abspath_or_url;
745 svn_revnum_t peg_revnum = SVN_INVALID_REVNUM;
746 svn_revnum_t start_revnum, end_revnum;
747 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
748 apr_pool_t *subpool = svn_pool_create(pool);
750 /* Ensure that we are given some real revision data to work with.
751 (It's okay if the END is unspecified -- in that case, we'll just
752 set it to the same thing as START.) */
753 if (revision->kind == svn_opt_revision_unspecified
754 || start->kind == svn_opt_revision_unspecified)
755 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
759 static const svn_opt_revision_t unspecified_rev
760 = { svn_opt_revision_unspecified, { 0 } };
762 end = &unspecified_rev;
765 /* Determine LOCAL_ABSPATH_OR_URL, URL, and possibly PEG_REVNUM.
766 If we are looking at the working version of a WC path that is scheduled
767 as a copy, then we need to use the copy-from URL and peg revision. */
768 if (! svn_path_is_url(path))
770 SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, subpool));
772 if (revision->kind == svn_opt_revision_working)
774 const char *repos_root_url;
775 const char *repos_relpath;
776 svn_boolean_t is_copy;
778 SVN_ERR(svn_wc__node_get_origin(&is_copy, &peg_revnum, &repos_relpath,
779 &repos_root_url, NULL, NULL,
780 ctx->wc_ctx, local_abspath_or_url,
781 FALSE, subpool, subpool));
784 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
789 if (url && is_copy && ra_session)
791 const char *session_url;
792 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url,
795 if (strcmp(session_url, url) != 0)
797 /* We can't use the caller provided RA session now :( */
806 SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx,
807 local_abspath_or_url, pool, subpool));
811 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
812 _("'%s' has no URL"),
813 svn_dirent_local_style(path, pool));
818 local_abspath_or_url = path;
822 /* ### We should be smarter here. If the callers just asks for BASE and
823 WORKING revisions, we should already have the correct URLs, so we
824 don't need to do anything more here in that case. */
826 /* Open a RA session to this URL if we don't have one already. */
828 SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
829 ctx, subpool, subpool));
831 /* Resolve the opt_revision_ts. */
832 if (peg_revnum == SVN_INVALID_REVNUM)
833 SVN_ERR(svn_client__get_revision_number(&peg_revnum, &youngest_rev,
834 ctx->wc_ctx, local_abspath_or_url,
835 ra_session, revision, pool));
837 SVN_ERR(svn_client__get_revision_number(&start_revnum, &youngest_rev,
838 ctx->wc_ctx, local_abspath_or_url,
839 ra_session, start, pool));
840 if (end->kind == svn_opt_revision_unspecified)
841 end_revnum = start_revnum;
843 SVN_ERR(svn_client__get_revision_number(&end_revnum, &youngest_rev,
844 ctx->wc_ctx, local_abspath_or_url,
845 ra_session, end, pool));
847 /* Set the output revision variables. */
850 *start_revision = start_revnum;
852 if (end_revision && end->kind != svn_opt_revision_unspecified)
854 *end_revision = end_revnum;
857 SVN_ERR(repos_locations(start_url, end_url,
858 ra_session, url, peg_revnum,
859 start_revnum, end_revnum,
861 svn_pool_destroy(subpool);
866 svn_client__calc_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p,
867 const svn_client__pathrev_t *loc1,
868 apr_hash_t *history1,
869 svn_boolean_t has_rev_zero_history1,
870 const svn_client__pathrev_t *loc2,
871 apr_hash_t *history2,
872 svn_boolean_t has_rev_zero_history2,
873 apr_pool_t *result_pool,
874 apr_pool_t *scratch_pool)
876 apr_hash_index_t *hi;
877 svn_revnum_t yc_revision = SVN_INVALID_REVNUM;
878 const char *yc_relpath = NULL;
880 if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0)
886 /* Loop through the first location's history, check for overlapping
887 paths and ranges in the second location's history, and
888 remembering the youngest matching location. */
889 for (hi = apr_hash_first(scratch_pool, history1); hi; hi = apr_hash_next(hi))
891 const char *path = svn__apr_hash_index_key(hi);
892 apr_ssize_t path_len = svn__apr_hash_index_klen(hi);
893 svn_rangelist_t *ranges1 = svn__apr_hash_index_val(hi);
894 svn_rangelist_t *ranges2, *common;
896 ranges2 = apr_hash_get(history2, path, path_len);
899 /* We have a path match. Now, did our two histories share
900 any revisions at that path? */
901 SVN_ERR(svn_rangelist_intersect(&common, ranges1, ranges2,
902 TRUE, scratch_pool));
905 svn_merge_range_t *yc_range =
906 APR_ARRAY_IDX(common, common->nelts - 1, svn_merge_range_t *);
907 if ((! SVN_IS_VALID_REVNUM(yc_revision))
908 || (yc_range->end > yc_revision))
910 yc_revision = yc_range->end;
911 yc_relpath = path + 1;
917 /* It's possible that PATH_OR_URL1 and PATH_OR_URL2's only common
918 history is revision 0. */
919 if (!yc_relpath && has_rev_zero_history1 && has_rev_zero_history2)
927 *ancestor_p = svn_client__pathrev_create_with_relpath(
928 loc1->repos_root_url, loc1->repos_uuid,
929 yc_revision, yc_relpath, result_pool);
939 svn_client__get_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p,
940 const svn_client__pathrev_t *loc1,
941 const svn_client__pathrev_t *loc2,
942 svn_ra_session_t *session,
943 svn_client_ctx_t *ctx,
944 apr_pool_t *result_pool,
945 apr_pool_t *scratch_pool)
947 apr_pool_t *sesspool = NULL;
948 apr_hash_t *history1, *history2;
949 svn_boolean_t has_rev_zero_history1;
950 svn_boolean_t has_rev_zero_history2;
952 if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0)
958 /* Open an RA session for the two locations. */
961 sesspool = svn_pool_create(scratch_pool);
962 SVN_ERR(svn_client_open_ra_session2(&session, loc1->url, NULL, ctx,
963 sesspool, sesspool));
966 /* We're going to cheat and use history-as-mergeinfo because it
967 saves us a bunch of annoying custom data comparisons and such. */
968 SVN_ERR(svn_client__get_history_as_mergeinfo(&history1,
969 &has_rev_zero_history1,
973 session, ctx, scratch_pool));
974 SVN_ERR(svn_client__get_history_as_mergeinfo(&history2,
975 &has_rev_zero_history2,
979 session, ctx, scratch_pool));
980 /* Close the ra session if we opened one. */
982 svn_pool_destroy(sesspool);
984 SVN_ERR(svn_client__calc_youngest_common_ancestor(ancestor_p,
986 has_rev_zero_history1,
988 has_rev_zero_history2,
996 svn_client__youngest_common_ancestor(const char **ancestor_url,
997 svn_revnum_t *ancestor_rev,
998 const char *path_or_url1,
999 const svn_opt_revision_t *revision1,
1000 const char *path_or_url2,
1001 const svn_opt_revision_t *revision2,
1002 svn_client_ctx_t *ctx,
1003 apr_pool_t *result_pool,
1004 apr_pool_t *scratch_pool)
1006 apr_pool_t *sesspool = svn_pool_create(scratch_pool);
1007 svn_ra_session_t *session;
1008 svn_client__pathrev_t *loc1, *loc2, *ancestor;
1010 /* Resolve the two locations */
1011 SVN_ERR(svn_client__ra_session_from_path2(&session, &loc1,
1013 revision1, revision1,
1015 SVN_ERR(svn_client__resolve_rev_and_url(&loc2, session,
1016 path_or_url2, revision2, revision2,
1017 ctx, scratch_pool));
1019 SVN_ERR(svn_client__get_youngest_common_ancestor(
1020 &ancestor, loc1, loc2, session, ctx, result_pool, scratch_pool));
1024 *ancestor_url = ancestor->url;
1025 *ancestor_rev = ancestor->rev;
1029 *ancestor_url = NULL;
1030 *ancestor_rev = SVN_INVALID_REVNUM;
1032 svn_pool_destroy(sesspool);
1033 return SVN_NO_ERROR;
1037 struct ra_ev2_baton {
1038 /* The working copy context, from the client context. */
1039 svn_wc_context_t *wc_ctx;
1041 /* For a given REPOS_RELPATH, provide a LOCAL_ABSPATH that represents
1042 that repository node. */
1043 apr_hash_t *relpath_map;
1048 svn_client__ra_provide_base(svn_stream_t **contents,
1049 svn_revnum_t *revision,
1051 const char *repos_relpath,
1052 apr_pool_t *result_pool,
1053 apr_pool_t *scratch_pool)
1055 struct ra_ev2_baton *reb = baton;
1056 const char *local_abspath;
1059 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1063 return SVN_NO_ERROR;
1066 err = svn_wc_get_pristine_contents2(contents, reb->wc_ctx, local_abspath,
1067 result_pool, scratch_pool);
1070 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1071 return svn_error_trace(err);
1073 svn_error_clear(err);
1075 return SVN_NO_ERROR;
1078 if (*contents != NULL)
1080 /* The pristine contents refer to the BASE, or to the pristine of
1081 a copy/move to this location. Fetch the correct revision. */
1082 SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL,
1083 reb->wc_ctx, local_abspath, FALSE,
1084 scratch_pool, scratch_pool));
1087 return SVN_NO_ERROR;
1092 svn_client__ra_provide_props(apr_hash_t **props,
1093 svn_revnum_t *revision,
1095 const char *repos_relpath,
1096 apr_pool_t *result_pool,
1097 apr_pool_t *scratch_pool)
1099 struct ra_ev2_baton *reb = baton;
1100 const char *local_abspath;
1103 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1107 return SVN_NO_ERROR;
1110 err = svn_wc_get_pristine_props(props, reb->wc_ctx, local_abspath,
1111 result_pool, scratch_pool);
1114 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1115 return svn_error_trace(err);
1117 svn_error_clear(err);
1119 return SVN_NO_ERROR;
1124 /* The pristine props refer to the BASE, or to the pristine props of
1125 a copy/move to this location. Fetch the correct revision. */
1126 SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL,
1127 reb->wc_ctx, local_abspath, FALSE,
1128 scratch_pool, scratch_pool));
1131 return SVN_NO_ERROR;
1136 svn_client__ra_get_copysrc_kind(svn_node_kind_t *kind,
1138 const char *repos_relpath,
1139 svn_revnum_t src_revision,
1140 apr_pool_t *scratch_pool)
1142 struct ra_ev2_baton *reb = baton;
1143 const char *local_abspath;
1145 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1148 *kind = svn_node_unknown;
1149 return SVN_NO_ERROR;
1152 /* ### what to do with SRC_REVISION? */
1154 SVN_ERR(svn_wc_read_kind2(kind, reb->wc_ctx, local_abspath,
1155 FALSE, FALSE, scratch_pool));
1157 return SVN_NO_ERROR;
1162 svn_client__ra_make_cb_baton(svn_wc_context_t *wc_ctx,
1163 apr_hash_t *relpath_map,
1164 apr_pool_t *result_pool)
1166 struct ra_ev2_baton *reb = apr_palloc(result_pool, sizeof(*reb));
1168 SVN_ERR_ASSERT_NO_RETURN(wc_ctx != NULL);
1169 SVN_ERR_ASSERT_NO_RETURN(relpath_map != NULL);
1171 reb->wc_ctx = wc_ctx;
1172 reb->relpath_map = relpath_map;