Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_client / ra.c
bloba270ee08b75ac1a988227a19ca029b9a12b7071d
1 /*
2 * ra.c : routines for interacting with the RA layer
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #include <apr_pools.h>
22 #include <assert.h>
24 #include "svn_error.h"
25 #include "svn_pools.h"
26 #include "svn_string.h"
27 #include "svn_sorts.h"
28 #include "svn_ra.h"
29 #include "svn_client.h"
30 #include "svn_path.h"
31 #include "svn_props.h"
32 #include "svn_mergeinfo.h"
33 #include "client.h"
34 #include "mergeinfo.h"
36 #include "svn_private_config.h"
37 #include "private/svn_wc_private.h"
41 static svn_error_t *
42 open_admin_tmp_file(apr_file_t **fp,
43 void *callback_baton,
44 apr_pool_t *pool)
46 svn_client__callback_baton_t *cb = callback_baton;
48 SVN_ERR(svn_wc_create_tmp_file2(fp, NULL, cb->base_dir,
49 svn_io_file_del_on_close, pool));
51 return SVN_NO_ERROR;
55 static svn_error_t *
56 open_tmp_file(apr_file_t **fp,
57 void *callback_baton,
58 apr_pool_t *pool)
60 svn_client__callback_baton_t *cb = callback_baton;
61 const char *truepath;
63 if (cb->base_dir && ! cb->read_only_wc)
64 truepath = apr_pstrdup(pool, cb->base_dir);
65 else
66 SVN_ERR(svn_io_temp_dir(&truepath, pool));
68 /* Tack on a made-up filename. */
69 truepath = svn_path_join(truepath, "tempfile", pool);
71 /* Open a unique file; use APR_DELONCLOSE. */
72 SVN_ERR(svn_io_open_unique_file2(fp, NULL, truepath, ".tmp",
73 svn_io_file_del_on_close, pool));
75 return SVN_NO_ERROR;
79 /* This implements the 'svn_ra_get_wc_prop_func_t' interface. */
80 static svn_error_t *
81 get_wc_prop(void *baton,
82 const char *relpath,
83 const char *name,
84 const svn_string_t **value,
85 apr_pool_t *pool)
87 svn_client__callback_baton_t *cb = baton;
89 *value = NULL;
91 /* If we have a list of commit_items, search through that for a
92 match for this relative URL. */
93 if (cb->commit_items)
95 int i;
96 for (i = 0; i < cb->commit_items->nelts; i++)
98 svn_client_commit_item3_t *item
99 = APR_ARRAY_IDX(cb->commit_items, i,
100 svn_client_commit_item3_t *);
101 if (! strcmp(relpath,
102 svn_path_uri_decode(item->url, pool)))
103 return svn_wc_prop_get(value, name, item->path, cb->base_access,
104 pool);
107 return SVN_NO_ERROR;
110 /* If we don't have a base directory, then there are no properties. */
111 else if (cb->base_dir == NULL)
112 return SVN_NO_ERROR;
114 return svn_wc_prop_get(value, name,
115 svn_path_join(cb->base_dir, relpath, pool),
116 cb->base_access, pool);
119 /* This implements the 'svn_ra_push_wc_prop_func_t' interface. */
120 static svn_error_t *
121 push_wc_prop(void *baton,
122 const char *relpath,
123 const char *name,
124 const svn_string_t *value,
125 apr_pool_t *pool)
127 svn_client__callback_baton_t *cb = baton;
128 int i;
130 /* If we're committing, search through the commit_items list for a
131 match for this relative URL. */
132 if (! cb->commit_items)
133 return svn_error_createf
134 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
135 _("Attempt to set wc property '%s' on '%s' in a non-commit operation"),
136 name, svn_path_local_style(relpath, pool));
138 for (i = 0; i < cb->commit_items->nelts; i++)
140 svn_client_commit_item3_t *item
141 = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
143 if (strcmp(relpath, svn_path_uri_decode(item->url, pool)) == 0)
145 apr_pool_t *cpool = item->incoming_prop_changes->pool;
146 svn_prop_t *prop = apr_palloc(cpool, sizeof(*prop));
148 prop->name = apr_pstrdup(cpool, name);
149 if (value)
151 prop->value
152 = svn_string_ncreate(value->data, value->len, cpool);
154 else
155 prop->value = NULL;
157 /* Buffer the propchange to take effect during the
158 post-commit process. */
159 APR_ARRAY_PUSH(item->incoming_prop_changes, svn_prop_t *) = prop;
160 return SVN_NO_ERROR;
164 return SVN_NO_ERROR;
168 /* This implements the 'svn_ra_set_wc_prop_func_t' interface. */
169 static svn_error_t *
170 set_wc_prop(void *baton,
171 const char *path,
172 const char *name,
173 const svn_string_t *value,
174 apr_pool_t *pool)
176 svn_client__callback_baton_t *cb = baton;
177 svn_wc_adm_access_t *adm_access;
178 const svn_wc_entry_t *entry;
179 const char *full_path = svn_path_join(cb->base_dir, path, pool);
181 SVN_ERR(svn_wc__entry_versioned(&entry, full_path, cb->base_access, FALSE,
182 pool));
184 SVN_ERR(svn_wc_adm_retrieve(&adm_access, cb->base_access,
185 (entry->kind == svn_node_dir
186 ? full_path
187 : svn_path_dirname(full_path, pool)),
188 pool));
190 /* We pass 1 for the 'force' parameter here. Since the property is
191 coming from the repository, we definitely want to accept it.
192 Ideally, we'd raise a conflict if, say, the received property is
193 svn:eol-style yet the file has a locally added svn:mime-type
194 claiming that it's binary. Probably the repository is still
195 right, but the conflict would remind the user to make sure.
196 Unfortunately, we don't have a clean mechanism for doing that
197 here, so we just set the property and hope for the best. */
198 return svn_wc_prop_set2(name, value, full_path, adm_access, TRUE, pool);
202 struct invalidate_wcprop_walk_baton
204 /* The wcprop to invalidate. */
205 const char *prop_name;
207 /* Access baton for the top of the walk. */
208 svn_wc_adm_access_t *base_access;
212 /* This implements the `found_entry' prototype in
213 `svn_wc_entry_callbacks_t'. */
214 static svn_error_t *
215 invalidate_wcprop_for_entry(const char *path,
216 const svn_wc_entry_t *entry,
217 void *walk_baton,
218 apr_pool_t *pool)
220 struct invalidate_wcprop_walk_baton *wb = walk_baton;
221 svn_wc_adm_access_t *entry_access;
223 SVN_ERR(svn_wc_adm_retrieve(&entry_access, wb->base_access,
224 ((entry->kind == svn_node_dir)
225 ? path
226 : svn_path_dirname(path, pool)),
227 pool));
228 /* It doesn't matter if we pass 0 or 1 for force here, since
229 property deletion is always permitted. */
230 return svn_wc_prop_set2(wb->prop_name, NULL, path, entry_access,
231 FALSE, pool);
235 /* This implements the `svn_ra_invalidate_wc_props_func_t' interface. */
236 static svn_error_t *
237 invalidate_wc_props(void *baton,
238 const char *path,
239 const char *prop_name,
240 apr_pool_t *pool)
242 svn_client__callback_baton_t *cb = baton;
243 svn_wc_entry_callbacks2_t walk_callbacks = { invalidate_wcprop_for_entry,
244 svn_client__default_walker_error_handler };
245 struct invalidate_wcprop_walk_baton wb;
246 svn_wc_adm_access_t *adm_access;
248 wb.base_access = cb->base_access;
249 wb.prop_name = prop_name;
251 path = svn_path_join(cb->base_dir, path, pool);
252 SVN_ERR(svn_wc_adm_probe_retrieve(&adm_access, cb->base_access, path,
253 pool));
254 SVN_ERR(svn_wc_walk_entries3(path, adm_access, &walk_callbacks, &wb,
255 svn_depth_infinity, FALSE,
256 cb->ctx->cancel_func, cb->ctx->cancel_baton,
257 pool));
259 return SVN_NO_ERROR;
263 static svn_error_t *
264 cancel_callback(void *baton)
266 svn_client__callback_baton_t *b = baton;
267 return (b->ctx->cancel_func)(b->ctx->cancel_baton);
271 static svn_error_t *
272 get_client_string(void *baton,
273 const char **name,
274 apr_pool_t *pool)
276 svn_client__callback_baton_t *b = baton;
277 *name = apr_pstrdup(pool, b->ctx->client_name);
278 return SVN_NO_ERROR;
281 svn_error_t *
282 svn_client__open_ra_session_internal(svn_ra_session_t **ra_session,
283 const char *base_url,
284 const char *base_dir,
285 svn_wc_adm_access_t *base_access,
286 apr_array_header_t *commit_items,
287 svn_boolean_t use_admin,
288 svn_boolean_t read_only_wc,
289 svn_client_ctx_t *ctx,
290 apr_pool_t *pool)
292 svn_ra_callbacks2_t *cbtable = apr_pcalloc(pool, sizeof(*cbtable));
293 svn_client__callback_baton_t *cb = apr_pcalloc(pool, sizeof(*cb));
295 cbtable->open_tmp_file = use_admin ? open_admin_tmp_file : open_tmp_file;
296 cbtable->get_wc_prop = use_admin ? get_wc_prop : NULL;
297 cbtable->set_wc_prop = read_only_wc ? NULL : set_wc_prop;
298 cbtable->push_wc_prop = commit_items ? push_wc_prop : NULL;
299 cbtable->invalidate_wc_props = read_only_wc ? NULL : invalidate_wc_props;
300 cbtable->auth_baton = ctx->auth_baton; /* new-style */
301 cbtable->progress_func = ctx->progress_func;
302 cbtable->progress_baton = ctx->progress_baton;
303 cbtable->cancel_func = ctx->cancel_func ? cancel_callback : NULL;
304 cbtable->get_client_string = get_client_string;
306 cb->base_dir = base_dir;
307 cb->base_access = base_access;
308 cb->read_only_wc = read_only_wc;
309 cb->pool = pool;
310 cb->commit_items = commit_items;
311 cb->ctx = ctx;
313 SVN_ERR(svn_ra_open2(ra_session, base_url, cbtable, cb,
314 ctx->config, pool));
316 return SVN_NO_ERROR;
319 svn_error_t *
320 svn_client_open_ra_session(svn_ra_session_t **session,
321 const char *url,
322 svn_client_ctx_t *ctx,
323 apr_pool_t *pool)
325 return svn_client__open_ra_session_internal(session, url, NULL, NULL, NULL,
326 FALSE, TRUE, ctx, pool);
330 svn_error_t *
331 svn_client_uuid_from_url(const char **uuid,
332 const char *url,
333 svn_client_ctx_t *ctx,
334 apr_pool_t *pool)
336 svn_ra_session_t *ra_session;
337 apr_pool_t *subpool = svn_pool_create(pool);
339 /* use subpool to create a temporary RA session */
340 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url,
341 NULL, /* no base dir */
342 NULL, NULL, FALSE, TRUE,
343 ctx, subpool));
345 SVN_ERR(svn_ra_get_uuid2(ra_session, uuid, pool));
347 /* destroy the RA session */
348 svn_pool_destroy(subpool);
350 return SVN_NO_ERROR;
354 svn_error_t *
355 svn_client_uuid_from_path(const char **uuid,
356 const char *path,
357 svn_wc_adm_access_t *adm_access,
358 svn_client_ctx_t *ctx,
359 apr_pool_t *pool)
361 const svn_wc_entry_t *entry;
363 SVN_ERR(svn_wc__entry_versioned(&entry, path, adm_access,
364 TRUE, /* show deleted */ pool));
366 if (entry->uuid)
368 *uuid = entry->uuid;
370 else if (entry->url)
372 /* fallback to using the network. */
373 SVN_ERR(svn_client_uuid_from_url(uuid, entry->url, ctx, pool));
375 else
377 /* Try the parent if it's the same working copy. It's not
378 entirely clear how this happens (possibly an old wc?) but it
379 has been triggered by TSVN, see
380 http://subversion.tigris.org/servlets/ReadMsg?list=dev&msgNo=101831
381 Message-ID: <877jgjtkus.fsf@debian2.lan> */
382 svn_boolean_t is_root;
383 SVN_ERR(svn_wc_is_wc_root(&is_root, path, adm_access, pool));
384 if (is_root)
385 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
386 _("'%s' has no URL"),
387 svn_path_local_style(path, pool));
388 else
389 return svn_client_uuid_from_path(uuid, svn_path_dirname(path, pool),
390 adm_access, ctx, pool);
393 return SVN_NO_ERROR;
400 svn_error_t *
401 svn_client__ra_session_from_path(svn_ra_session_t **ra_session_p,
402 svn_revnum_t *rev_p,
403 const char **url_p,
404 const char *path_or_url,
405 svn_wc_adm_access_t *base_access,
406 const svn_opt_revision_t *peg_revision_p,
407 const svn_opt_revision_t *revision,
408 svn_client_ctx_t *ctx,
409 apr_pool_t *pool)
411 svn_ra_session_t *ra_session;
412 const char *initial_url, *url, *base_dir = NULL;
413 svn_opt_revision_t *good_rev;
414 svn_opt_revision_t peg_revision, start_rev;
415 svn_opt_revision_t dead_end_rev;
416 svn_opt_revision_t *ignored_rev, *new_rev;
417 svn_revnum_t rev;
418 const char *ignored_url;
420 SVN_ERR(svn_client_url_from_path(&initial_url, path_or_url, pool));
421 if (! initial_url)
422 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
423 _("'%s' has no URL"), path_or_url);
425 start_rev = *revision;
426 peg_revision = *peg_revision_p;
427 SVN_ERR(svn_opt_resolve_revisions(&peg_revision, &start_rev,
428 svn_path_is_url(path_or_url),
429 TRUE,
430 pool));
432 if (base_access)
433 base_dir = svn_wc_adm_access_path(base_access);
435 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, initial_url,
436 base_dir, base_access, NULL,
437 base_access ? TRUE : FALSE,
438 FALSE, ctx, pool));
440 dead_end_rev.kind = svn_opt_revision_unspecified;
442 /* Run the history function to get the object's (possibly
443 different) url in REVISION. */
444 SVN_ERR(svn_client__repos_locations(&url, &new_rev,
445 &ignored_url, &ignored_rev,
446 ra_session,
447 path_or_url, &peg_revision,
448 /* search range: */
449 &start_rev, &dead_end_rev,
450 ctx, pool));
451 good_rev = (svn_opt_revision_t *)new_rev;
453 /* Make the session point to the real URL. */
454 SVN_ERR(svn_ra_reparent(ra_session, url, pool));
456 /* Resolve good_rev into a real revnum. */
457 if (good_rev->kind == svn_opt_revision_unspecified)
458 good_rev->kind = svn_opt_revision_head;
459 SVN_ERR(svn_client__get_revision_number(&rev, NULL, ra_session,
460 good_rev, url, pool));
462 *ra_session_p = ra_session;
463 *rev_p = rev;
464 *url_p = url;
466 return SVN_NO_ERROR;
470 svn_error_t *
471 svn_client__path_relative_to_session(const char **rel_path,
472 svn_ra_session_t *ra_session,
473 const char *url,
474 apr_pool_t *pool)
476 const char *session_url;
477 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
478 if (strcmp(session_url, url) == 0)
479 *rel_path = "";
480 else
481 *rel_path = svn_path_uri_decode(svn_path_is_child(session_url, url, pool),
482 pool);
483 return SVN_NO_ERROR;
487 svn_error_t *
488 svn_client__ensure_ra_session_url(const char **old_session_url,
489 svn_ra_session_t *ra_session,
490 const char *session_url,
491 apr_pool_t *pool)
493 *old_session_url = NULL;
494 SVN_ERR(svn_ra_get_session_url(ra_session, old_session_url, pool));
495 if (! session_url)
496 SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_url, pool));
497 if (strcmp(*old_session_url, session_url) != 0)
498 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
499 return SVN_NO_ERROR;
504 /*** Repository Locations ***/
506 struct gls_receiver_baton_t
508 apr_array_header_t *segments;
509 svn_client_ctx_t *ctx;
510 apr_pool_t *pool;
513 static svn_error_t *
514 gls_receiver(svn_location_segment_t *segment,
515 void *baton,
516 apr_pool_t *pool)
518 struct gls_receiver_baton_t *b = baton;
519 APR_ARRAY_PUSH(b->segments, svn_location_segment_t *) =
520 svn_location_segment_dup(segment, b->pool);
521 if (b->ctx->cancel_func)
522 SVN_ERR((b->ctx->cancel_func)(b->ctx->cancel_baton));
523 return SVN_NO_ERROR;
526 /* A qsort-compatible function which sorts svn_location_segment_t's
527 based on their revision range covering, resulting in ascending
528 (oldest-to-youngest) ordering. */
529 static int
530 compare_segments(const void *a, const void *b)
532 const svn_location_segment_t *a_seg
533 = *((const svn_location_segment_t * const *) a);
534 const svn_location_segment_t *b_seg
535 = *((const svn_location_segment_t * const *) b);
536 if (a_seg->range_start == b_seg->range_start)
537 return 0;
538 return (a_seg->range_start < b_seg->range_start) ? -1 : 1;
541 svn_error_t *
542 svn_client__repos_location_segments(apr_array_header_t **segments,
543 svn_ra_session_t *ra_session,
544 const char *path,
545 svn_revnum_t peg_revision,
546 svn_revnum_t start_revision,
547 svn_revnum_t end_revision,
548 svn_client_ctx_t *ctx,
549 apr_pool_t *pool)
551 struct gls_receiver_baton_t gls_receiver_baton;
552 *segments = apr_array_make(pool, 8, sizeof(svn_location_segment_t *));
553 gls_receiver_baton.segments = *segments;
554 gls_receiver_baton.ctx = ctx;
555 gls_receiver_baton.pool = pool;
556 SVN_ERR(svn_ra_get_location_segments(ra_session, path, peg_revision,
557 start_revision, end_revision,
558 gls_receiver, &gls_receiver_baton,
559 pool));
560 qsort((*segments)->elts, (*segments)->nelts,
561 (*segments)->elt_size, compare_segments);
562 return SVN_NO_ERROR;
566 svn_error_t *
567 svn_client__repos_locations(const char **start_url,
568 svn_opt_revision_t **start_revision,
569 const char **end_url,
570 svn_opt_revision_t **end_revision,
571 svn_ra_session_t *ra_session,
572 const char *path,
573 const svn_opt_revision_t *revision,
574 const svn_opt_revision_t *start,
575 const svn_opt_revision_t *end,
576 svn_client_ctx_t *ctx,
577 apr_pool_t *pool)
579 const char *repos_url;
580 const char *url;
581 const char *start_path = NULL;
582 const char *end_path = NULL;
583 svn_revnum_t peg_revnum = SVN_INVALID_REVNUM;
584 svn_revnum_t start_revnum, end_revnum;
585 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
586 apr_array_header_t *revs;
587 apr_hash_t *rev_locs;
588 apr_pool_t *subpool = svn_pool_create(pool);
590 /* Ensure that we are given some real revision data to work with.
591 (It's okay if the END is unspecified -- in that case, we'll just
592 set it to the same thing as START.) */
593 if (revision->kind == svn_opt_revision_unspecified
594 || start->kind == svn_opt_revision_unspecified)
595 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
597 /* Check to see if this is schedule add with history working copy
598 path. If it is, then we need to use the URL and peg revision of
599 the copyfrom information. */
600 if (! svn_path_is_url(path))
602 svn_wc_adm_access_t *adm_access;
603 const svn_wc_entry_t *entry;
604 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path,
605 FALSE, 0, ctx->cancel_func,
606 ctx->cancel_baton, pool));
607 SVN_ERR(svn_wc_entry(&entry, path, adm_access, FALSE, pool));
608 SVN_ERR(svn_wc_adm_close(adm_access));
609 if (entry->copyfrom_url && revision->kind == svn_opt_revision_working)
611 url = entry->copyfrom_url;
612 peg_revnum = entry->copyfrom_rev;
613 if (!entry->url || strcmp(entry->url, entry->copyfrom_url) != 0)
615 /* We can't use the caller provided RA session in this case */
616 ra_session = NULL;
619 else if (entry->url)
621 url = entry->url;
623 else
625 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
626 _("'%s' has no URL"),
627 svn_path_local_style(path, pool));
630 else
632 url = path;
635 /* ### We should be smarter here. If the callers just asks for BASE and
636 WORKING revisions, we should already have the correct URLs, so we
637 don't need to do anything more here in that case. */
639 /* Open a RA session to this URL if we don't have one already. */
640 if (! ra_session)
641 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, url, NULL,
642 NULL, NULL, FALSE, TRUE,
643 ctx, subpool));
645 /* Resolve the opt_revision_ts. */
646 if (peg_revnum == SVN_INVALID_REVNUM)
647 SVN_ERR(svn_client__get_revision_number(&peg_revnum, &youngest_rev,
648 ra_session, revision, path,
649 pool));
651 SVN_ERR(svn_client__get_revision_number(&start_revnum, &youngest_rev,
652 ra_session, start, path, pool));
653 if (end->kind == svn_opt_revision_unspecified)
654 end_revnum = start_revnum;
655 else
656 SVN_ERR(svn_client__get_revision_number(&end_revnum, &youngest_rev,
657 ra_session, end, path, pool));
659 /* Set the output revision variables. */
660 *start_revision = apr_pcalloc(pool, sizeof(**start_revision));
661 (*start_revision)->kind = svn_opt_revision_number;
662 (*start_revision)->value.number = start_revnum;
663 if (end->kind != svn_opt_revision_unspecified)
665 *end_revision = apr_pcalloc(pool, sizeof(**end_revision));
666 (*end_revision)->kind = svn_opt_revision_number;
667 (*end_revision)->value.number = end_revnum;
670 if (start_revnum == peg_revnum && end_revnum == peg_revnum)
672 /* Avoid a network request in the common easy case. */
673 *start_url = url;
674 if (end->kind != svn_opt_revision_unspecified)
675 *end_url = url;
676 svn_pool_destroy(subpool);
677 return SVN_NO_ERROR;
680 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, subpool));
682 revs = apr_array_make(subpool, 2, sizeof(svn_revnum_t));
683 APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum;
684 if (end_revnum != start_revnum)
685 APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum;
687 SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum,
688 revs, subpool));
690 /* We'd better have all the paths we were looking for! */
691 start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(svn_revnum_t));
692 if (! start_path)
693 return svn_error_createf
694 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
695 _("Unable to find repository location for '%s' in revision %ld"),
696 path, start_revnum);
698 end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t));
699 if (! end_path)
700 return svn_error_createf
701 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
702 _("The location for '%s' for revision %ld does not exist in the "
703 "repository or refers to an unrelated object"),
704 path, end_revnum);
706 /* Repository paths might be absolute, but we want to treat them as
707 relative.
708 ### Aren't they always absolute? */
709 if (start_path[0] == '/')
710 start_path = start_path + 1;
711 if (end_path[0] == '/')
712 end_path = end_path + 1;
714 /* Set our return variables */
715 *start_url = svn_path_join(repos_url, svn_path_uri_encode(start_path,
716 pool), pool);
717 if (end->kind != svn_opt_revision_unspecified)
718 *end_url = svn_path_join(repos_url, svn_path_uri_encode(end_path,
719 pool), pool);
721 svn_pool_destroy(subpool);
722 return SVN_NO_ERROR;
726 svn_error_t *
727 svn_client__get_youngest_common_ancestor(const char **ancestor_path,
728 svn_revnum_t *ancestor_revision,
729 const char *path_or_url1,
730 svn_revnum_t rev1,
731 const char *path_or_url2,
732 svn_revnum_t rev2,
733 svn_client_ctx_t *ctx,
734 apr_pool_t *pool)
736 apr_hash_t *history1, *history2;
737 apr_hash_index_t *hi;
738 svn_revnum_t yc_revision = SVN_INVALID_REVNUM;
739 const char *yc_path = NULL;
740 svn_opt_revision_t revision1, revision2;
742 revision1.kind = revision2.kind = svn_opt_revision_number;
743 revision1.value.number = rev1;
744 revision2.value.number = rev2;
746 /* We're going to cheat and use history-as-mergeinfo because it
747 saves us a bunch of annoying custom data comparisons and such. */
748 SVN_ERR(svn_client__get_history_as_mergeinfo(&history1, path_or_url1,
749 &revision1,
750 SVN_INVALID_REVNUM,
751 SVN_INVALID_REVNUM,
752 NULL, NULL, ctx, pool));
753 SVN_ERR(svn_client__get_history_as_mergeinfo(&history2, path_or_url2,
754 &revision2,
755 SVN_INVALID_REVNUM,
756 SVN_INVALID_REVNUM,
757 NULL, NULL, ctx, pool));
759 /* Loop through the first location's history, check for overlapping
760 paths and ranges in the second location's history, and
761 remembering the youngest matching location. */
762 for (hi = apr_hash_first(NULL, history1); hi; hi = apr_hash_next(hi))
764 const void *key;
765 apr_ssize_t klen;
766 void *val;
767 const char *path;
768 apr_array_header_t *ranges1, *ranges2, *common;
770 apr_hash_this(hi, &key, &klen, &val);
771 path = key;
772 ranges1 = val;
774 ranges2 = apr_hash_get(history2, key, klen);
775 if (ranges2)
777 /* We have a path match. Now, did our two histories share
778 any revisions at that path? */
779 SVN_ERR(svn_rangelist_intersect(&common, ranges1, ranges2, pool));
780 if (common->nelts)
782 svn_merge_range_t *yc_range =
783 APR_ARRAY_IDX(common, common->nelts - 1, svn_merge_range_t *);
784 if ((! SVN_IS_VALID_REVNUM(yc_revision))
785 || (yc_range->end > yc_revision))
787 yc_revision = yc_range->end;
788 yc_path = path + 1;
794 *ancestor_path = yc_path;
795 *ancestor_revision = yc_revision;
796 return SVN_NO_ERROR;