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>
24 #include "svn_error.h"
25 #include "svn_pools.h"
26 #include "svn_string.h"
27 #include "svn_sorts.h"
29 #include "svn_client.h"
31 #include "svn_props.h"
32 #include "svn_mergeinfo.h"
34 #include "mergeinfo.h"
36 #include "svn_private_config.h"
37 #include "private/svn_wc_private.h"
42 open_admin_tmp_file(apr_file_t
**fp
,
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
));
56 open_tmp_file(apr_file_t
**fp
,
60 svn_client__callback_baton_t
*cb
= callback_baton
;
63 if (cb
->base_dir
&& ! cb
->read_only_wc
)
64 truepath
= apr_pstrdup(pool
, cb
->base_dir
);
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
));
79 /* This implements the 'svn_ra_get_wc_prop_func_t' interface. */
81 get_wc_prop(void *baton
,
84 const svn_string_t
**value
,
87 svn_client__callback_baton_t
*cb
= baton
;
91 /* If we have a list of commit_items, search through that for a
92 match for this relative URL. */
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
,
110 /* If we don't have a base directory, then there are no properties. */
111 else if (cb
->base_dir
== NULL
)
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. */
121 push_wc_prop(void *baton
,
124 const svn_string_t
*value
,
127 svn_client__callback_baton_t
*cb
= baton
;
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
);
152 = svn_string_ncreate(value
->data
, value
->len
, cpool
);
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
;
168 /* This implements the 'svn_ra_set_wc_prop_func_t' interface. */
170 set_wc_prop(void *baton
,
173 const svn_string_t
*value
,
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
,
184 SVN_ERR(svn_wc_adm_retrieve(&adm_access
, cb
->base_access
,
185 (entry
->kind
== svn_node_dir
187 : svn_path_dirname(full_path
, 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'. */
215 invalidate_wcprop_for_entry(const char *path
,
216 const svn_wc_entry_t
*entry
,
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
)
226 : svn_path_dirname(path
, 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
,
235 /* This implements the `svn_ra_invalidate_wc_props_func_t' interface. */
237 invalidate_wc_props(void *baton
,
239 const char *prop_name
,
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
,
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
,
264 cancel_callback(void *baton
)
266 svn_client__callback_baton_t
*b
= baton
;
267 return (b
->ctx
->cancel_func
)(b
->ctx
->cancel_baton
);
272 get_client_string(void *baton
,
276 svn_client__callback_baton_t
*b
= baton
;
277 *name
= apr_pstrdup(pool
, b
->ctx
->client_name
);
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
,
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
;
310 cb
->commit_items
= commit_items
;
313 SVN_ERR(svn_ra_open2(ra_session
, base_url
, cbtable
, cb
,
320 svn_client_open_ra_session(svn_ra_session_t
**session
,
322 svn_client_ctx_t
*ctx
,
325 return svn_client__open_ra_session_internal(session
, url
, NULL
, NULL
, NULL
,
326 FALSE
, TRUE
, ctx
, pool
);
331 svn_client_uuid_from_url(const char **uuid
,
333 svn_client_ctx_t
*ctx
,
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
,
345 SVN_ERR(svn_ra_get_uuid2(ra_session
, uuid
, pool
));
347 /* destroy the RA session */
348 svn_pool_destroy(subpool
);
355 svn_client_uuid_from_path(const char **uuid
,
357 svn_wc_adm_access_t
*adm_access
,
358 svn_client_ctx_t
*ctx
,
361 const svn_wc_entry_t
*entry
;
363 SVN_ERR(svn_wc__entry_versioned(&entry
, path
, adm_access
,
364 TRUE
, /* show deleted */ pool
));
372 /* fallback to using the network. */
373 SVN_ERR(svn_client_uuid_from_url(uuid
, entry
->url
, ctx
, pool
));
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
));
385 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL
, NULL
,
386 _("'%s' has no URL"),
387 svn_path_local_style(path
, pool
));
389 return svn_client_uuid_from_path(uuid
, svn_path_dirname(path
, pool
),
390 adm_access
, ctx
, pool
);
401 svn_client__ra_session_from_path(svn_ra_session_t
**ra_session_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
,
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
;
418 const char *ignored_url
;
420 SVN_ERR(svn_client_url_from_path(&initial_url
, path_or_url
, pool
));
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
),
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
,
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
,
447 path_or_url
, &peg_revision
,
449 &start_rev
, &dead_end_rev
,
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
;
471 svn_client__path_relative_to_session(const char **rel_path
,
472 svn_ra_session_t
*ra_session
,
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)
481 *rel_path
= svn_path_uri_decode(svn_path_is_child(session_url
, url
, pool
),
488 svn_client__ensure_ra_session_url(const char **old_session_url
,
489 svn_ra_session_t
*ra_session
,
490 const char *session_url
,
493 *old_session_url
= NULL
;
494 SVN_ERR(svn_ra_get_session_url(ra_session
, old_session_url
, pool
));
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
));
504 /*** Repository Locations ***/
506 struct gls_receiver_baton_t
508 apr_array_header_t
*segments
;
509 svn_client_ctx_t
*ctx
;
514 gls_receiver(svn_location_segment_t
*segment
,
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
));
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. */
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
)
538 return (a_seg
->range_start
< b_seg
->range_start
) ? -1 : 1;
542 svn_client__repos_location_segments(apr_array_header_t
**segments
,
543 svn_ra_session_t
*ra_session
,
545 svn_revnum_t peg_revision
,
546 svn_revnum_t start_revision
,
547 svn_revnum_t end_revision
,
548 svn_client_ctx_t
*ctx
,
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
,
560 qsort((*segments
)->elts
, (*segments
)->nelts
,
561 (*segments
)->elt_size
, compare_segments
);
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
,
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
,
579 const char *repos_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 */
625 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL
, NULL
,
626 _("'%s' has no URL"),
627 svn_path_local_style(path
, pool
));
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. */
641 SVN_ERR(svn_client__open_ra_session_internal(&ra_session
, url
, NULL
,
642 NULL
, NULL
, FALSE
, TRUE
,
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
,
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
;
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. */
674 if (end
->kind
!= svn_opt_revision_unspecified
)
676 svn_pool_destroy(subpool
);
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
,
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
));
693 return svn_error_createf
694 (SVN_ERR_CLIENT_UNRELATED_RESOURCES
, NULL
,
695 _("Unable to find repository location for '%s' in revision %ld"),
698 end_path
= apr_hash_get(rev_locs
, &end_revnum
, sizeof(svn_revnum_t
));
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"),
706 /* Repository paths might be absolute, but we want to treat them as
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
,
717 if (end
->kind
!= svn_opt_revision_unspecified
)
718 *end_url
= svn_path_join(repos_url
, svn_path_uri_encode(end_path
,
721 svn_pool_destroy(subpool
);
727 svn_client__get_youngest_common_ancestor(const char **ancestor_path
,
728 svn_revnum_t
*ancestor_revision
,
729 const char *path_or_url1
,
731 const char *path_or_url2
,
733 svn_client_ctx_t
*ctx
,
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
,
752 NULL
, NULL
, ctx
, pool
));
753 SVN_ERR(svn_client__get_history_as_mergeinfo(&history2
, path_or_url2
,
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
))
768 apr_array_header_t
*ranges1
, *ranges2
, *common
;
770 apr_hash_this(hi
, &key
, &klen
, &val
);
774 ranges2
= apr_hash_get(history2
, key
, klen
);
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
));
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
;
794 *ancestor_path
= yc_path
;
795 *ancestor_revision
= yc_revision
;