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"
45 #include "private/svn_sorts_private.h"
48 /* This is the baton that we pass svn_ra_open3(), and is associated with
49 the callback table we provide to RA. */
50 typedef struct callback_baton_t
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
;
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
;
64 /* Used as wri_abspath for obtaining access to the pristine store */
65 const char *wcroot_abspath
;
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
;
71 /* A client context. */
72 svn_client_ctx_t
*ctx
;
74 /* Last progress reported by progress callback. */
75 apr_off_t last_progress
;
81 open_tmp_file(apr_file_t
**fp
,
85 return svn_error_trace(svn_io_open_unique_file3(fp
, NULL
, NULL
,
86 svn_io_file_del_on_pool_cleanup
,
91 /* This implements the 'svn_ra_get_wc_prop_func_t' interface. */
93 get_wc_prop(void *baton
,
96 const svn_string_t
**value
,
99 callback_baton_t
*cb
= baton
;
100 const char *local_abspath
= NULL
;
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
)
110 for (i
= 0; i
< cb
->commit_items
->nelts
; i
++)
112 svn_client_commit_item3_t
*item
113 = APR_ARRAY_IDX(cb
->commit_items
, i
, svn_client_commit_item3_t
*);
115 if (! strcmp(relpath
, item
->session_relpath
))
117 SVN_ERR_ASSERT(svn_dirent_is_absolute(item
->path
));
118 local_abspath
= item
->path
;
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
131 /* If we don't have a base directory, then there are no properties. */
132 else if (cb
->base_dir_abspath
== NULL
)
136 local_abspath
= svn_dirent_join(cb
->base_dir_abspath
, relpath
, pool
);
138 err
= svn_wc_prop_get2(value
, cb
->ctx
->wc_ctx
, local_abspath
, name
,
140 if (err
&& err
->apr_err
== SVN_ERR_WC_PATH_NOT_FOUND
)
142 svn_error_clear(err
);
145 return svn_error_trace(err
);
148 /* This implements the 'svn_ra_push_wc_prop_func_t' interface. */
150 push_wc_prop(void *baton
,
153 const svn_string_t
*value
,
156 callback_baton_t
*cb
= baton
;
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
));
167 for (i
= 0; i
< cb
->commit_items
->nelts
; i
++)
169 svn_client_commit_item3_t
*item
170 = APR_ARRAY_IDX(cb
->commit_items
, i
, svn_client_commit_item3_t
*);
172 if (strcmp(relpath
, item
->session_relpath
) == 0)
174 apr_pool_t
*changes_pool
= item
->incoming_prop_changes
->pool
;
175 svn_prop_t
*prop
= apr_palloc(changes_pool
, sizeof(*prop
));
177 prop
->name
= apr_pstrdup(changes_pool
, name
);
179 prop
->value
= svn_string_dup(value
, changes_pool
);
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
;
194 /* This implements the 'svn_ra_set_wc_prop_func_t' interface. */
196 set_wc_prop(void *baton
,
199 const svn_string_t
*value
,
202 callback_baton_t
*cb
= baton
;
203 const char *local_abspath
;
205 local_abspath
= svn_dirent_join(cb
->base_dir_abspath
, path
, pool
);
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
,
217 value
, svn_depth_empty
,
218 TRUE
/* skip_checks */,
219 NULL
/* changelist_filter */,
220 NULL
, NULL
/* cancellation */,
221 NULL
, NULL
/* notification */,
226 /* This implements the `svn_ra_invalidate_wc_props_func_t' interface. */
228 invalidate_wc_props(void *baton
,
230 const char *prop_name
,
233 callback_baton_t
*cb
= baton
;
234 const char *local_abspath
;
236 local_abspath
= svn_dirent_join(cb
->base_dir_abspath
, path
, pool
);
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
));
251 /* This implements the `svn_ra_get_wc_contents_func_t' interface. */
253 get_wc_contents(void *baton
,
254 svn_stream_t
**contents
,
255 const svn_checksum_t
*checksum
,
258 callback_baton_t
*cb
= baton
;
260 if (! cb
->wcroot_abspath
)
266 return svn_error_trace(
267 svn_wc__get_pristine_contents_by_checksum(contents
,
276 cancel_callback(void *baton
)
278 callback_baton_t
*b
= baton
;
279 return svn_error_trace((b
->ctx
->cancel_func
)(b
->ctx
->cancel_baton
));
284 get_client_string(void *baton
,
288 callback_baton_t
*b
= baton
;
289 *name
= apr_pstrdup(pool
, b
->ctx
->client_name
);
293 /* Implements svn_ra_progress_notify_func_t. Accumulates progress information
294 * for different RA sessions and reports total progress to caller. */
296 progress_func(apr_off_t progress
,
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
);
306 private_ctx
->total_progress
+= (progress
- b
->last_progress
);
307 b
->last_progress
= progress
;
309 if (public_ctx
->progress_func
)
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
314 public_ctx
->progress_func(private_ctx
->total_progress
, -1,
315 public_ctx
->progress_baton
, pool
);
319 #define SVN_CLIENT__MAX_REDIRECT_ATTEMPTS 3 /* ### TODO: Make configurable. */
322 svn_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
)
333 svn_ra_callbacks2_t
*cbtable
;
334 callback_baton_t
*cb
= apr_pcalloc(result_pool
, sizeof(*cb
));
335 const char *uuid
= NULL
;
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
));
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
;
361 cb
->commit_items
= commit_items
;
364 if (base_dir_abspath
&& (read_dav_props
|| write_dav_props
))
366 svn_error_t
*err
= svn_wc__node_get_repos_info(NULL
, NULL
, NULL
, &uuid
,
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
))
376 svn_error_clear(err
);
382 cb
->base_dir_isversioned
= TRUE
;
384 cb
->base_dir_abspath
= apr_pstrdup(result_pool
, base_dir_abspath
);
387 if (base_dir_abspath
)
389 svn_error_t
*err
= svn_wc__get_wcroot(&cb
->wcroot_abspath
,
390 ctx
->wc_ctx
, base_dir_abspath
,
391 result_pool
, scratch_pool
);
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
);
400 svn_error_clear(err
);
401 cb
->wcroot_abspath
= NULL
;
405 /* If the caller allows for auto-following redirections, and the
406 RA->open() call above reveals a CORRECTED_URL, try the new URL.
407 We'll do this in a loop up to some maximum number follow-and-retry
411 apr_hash_t
*attempted
= apr_hash_make(scratch_pool
);
412 int attempts_left
= SVN_CLIENT__MAX_REDIRECT_ATTEMPTS
;
414 *corrected_url
= NULL
;
415 while (attempts_left
--)
417 const char *corrected
= NULL
;
419 /* Try to open the RA session. If this is our last attempt,
420 don't accept corrected URLs from the RA provider. */
421 SVN_ERR(svn_ra_open4(ra_session
,
422 attempts_left
== 0 ? NULL
: &corrected
,
423 base_url
, uuid
, cbtable
, cb
, ctx
->config
,
426 /* No error and no corrected URL? We're done here. */
430 /* Notify the user that a redirect is being followed. */
431 if (ctx
->notify_func2
!= NULL
)
433 svn_wc_notify_t
*notify
=
434 svn_wc_create_notify_url(corrected
,
435 svn_wc_notify_url_redirect
,
437 ctx
->notify_func2(ctx
->notify_baton2
, notify
, scratch_pool
);
440 /* Our caller will want to know what our final corrected URL was. */
441 *corrected_url
= corrected
;
443 /* Make sure we've not attempted this URL before. */
444 if (svn_hash_gets(attempted
, corrected
))
445 return svn_error_createf(SVN_ERR_CLIENT_CYCLE_DETECTED
, NULL
,
446 _("Redirect cycle detected for URL '%s'"),
449 /* Remember this CORRECTED_URL so we don't wind up in a loop. */
450 svn_hash_sets(attempted
, corrected
, (void *)1);
451 base_url
= corrected
;
456 SVN_ERR(svn_ra_open4(ra_session
, NULL
, base_url
,
457 uuid
, cbtable
, cb
, ctx
->config
, result_pool
));
462 #undef SVN_CLIENT__MAX_REDIRECT_ATTEMPTS
466 svn_client_open_ra_session2(svn_ra_session_t
**session
,
468 const char *wri_abspath
,
469 svn_client_ctx_t
*ctx
,
470 apr_pool_t
*result_pool
,
471 apr_pool_t
*scratch_pool
)
473 return svn_error_trace(
474 svn_client__open_ra_session_internal(session
, NULL
, url
,
482 svn_client__resolve_rev_and_url(svn_client__pathrev_t
**resolved_loc_p
,
483 svn_ra_session_t
*ra_session
,
484 const char *path_or_url
,
485 const svn_opt_revision_t
*peg_revision
,
486 const svn_opt_revision_t
*revision
,
487 svn_client_ctx_t
*ctx
,
490 svn_opt_revision_t peg_rev
= *peg_revision
;
491 svn_opt_revision_t start_rev
= *revision
;
495 /* Default revisions: peg -> working or head; operative -> peg. */
496 SVN_ERR(svn_opt_resolve_revisions(&peg_rev
, &start_rev
,
497 svn_path_is_url(path_or_url
),
498 TRUE
/* notice_local_mods */,
501 /* Run the history function to get the object's (possibly
502 different) url in REVISION. */
503 SVN_ERR(svn_client__repos_locations(&url
, &rev
, NULL
, NULL
,
504 ra_session
, path_or_url
, &peg_rev
,
505 &start_rev
, NULL
, ctx
, pool
));
507 SVN_ERR(svn_client__pathrev_create_with_session(resolved_loc_p
,
508 ra_session
, rev
, url
, pool
));
513 svn_client__ra_session_from_path2(svn_ra_session_t
**ra_session_p
,
514 svn_client__pathrev_t
**resolved_loc_p
,
515 const char *path_or_url
,
516 const char *base_dir_abspath
,
517 const svn_opt_revision_t
*peg_revision
,
518 const svn_opt_revision_t
*revision
,
519 svn_client_ctx_t
*ctx
,
522 svn_ra_session_t
*ra_session
;
523 const char *initial_url
;
524 const char *corrected_url
;
525 svn_client__pathrev_t
*resolved_loc
;
526 const char *wri_abspath
;
528 SVN_ERR(svn_client_url_from_path2(&initial_url
, path_or_url
, ctx
, pool
,
531 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL
, NULL
,
532 _("'%s' has no URL"), path_or_url
);
534 if (base_dir_abspath
)
535 wri_abspath
= base_dir_abspath
;
536 else if (!svn_path_is_url(path_or_url
))
537 SVN_ERR(svn_dirent_get_absolute(&wri_abspath
, path_or_url
, pool
));
541 SVN_ERR(svn_client__open_ra_session_internal(&ra_session
, &corrected_url
,
544 NULL
/* commit_items */,
545 base_dir_abspath
!= NULL
,
546 base_dir_abspath
!= NULL
,
549 /* If we got a CORRECTED_URL, we'll want to refer to that as the
550 URL-ized form of PATH_OR_URL from now on. */
551 if (corrected_url
&& svn_path_is_url(path_or_url
))
552 path_or_url
= corrected_url
;
554 SVN_ERR(svn_client__resolve_rev_and_url(&resolved_loc
, ra_session
,
555 path_or_url
, peg_revision
, revision
,
558 /* Make the session point to the real URL. */
559 SVN_ERR(svn_ra_reparent(ra_session
, resolved_loc
->url
, pool
));
561 *ra_session_p
= ra_session
;
563 *resolved_loc_p
= resolved_loc
;
570 svn_client__ensure_ra_session_url(const char **old_session_url
,
571 svn_ra_session_t
*ra_session
,
572 const char *session_url
,
575 SVN_ERR(svn_ra_get_session_url(ra_session
, old_session_url
, pool
));
577 SVN_ERR(svn_ra_get_repos_root2(ra_session
, &session_url
, pool
));
578 if (strcmp(*old_session_url
, session_url
) != 0)
579 SVN_ERR(svn_ra_reparent(ra_session
, session_url
, pool
));
585 /*** Repository Locations ***/
587 struct gls_receiver_baton_t
589 apr_array_header_t
*segments
;
590 svn_client_ctx_t
*ctx
;
595 gls_receiver(svn_location_segment_t
*segment
,
599 struct gls_receiver_baton_t
*b
= baton
;
600 APR_ARRAY_PUSH(b
->segments
, svn_location_segment_t
*) =
601 svn_location_segment_dup(segment
, b
->pool
);
602 if (b
->ctx
->cancel_func
)
603 SVN_ERR((b
->ctx
->cancel_func
)(b
->ctx
->cancel_baton
));
607 /* A qsort-compatible function which sorts svn_location_segment_t's
608 based on their revision range covering, resulting in ascending
609 (oldest-to-youngest) ordering. */
611 compare_segments(const void *a
, const void *b
)
613 const svn_location_segment_t
*a_seg
614 = *((const svn_location_segment_t
* const *) a
);
615 const svn_location_segment_t
*b_seg
616 = *((const svn_location_segment_t
* const *) b
);
617 if (a_seg
->range_start
== b_seg
->range_start
)
619 return (a_seg
->range_start
< b_seg
->range_start
) ? -1 : 1;
623 svn_client__repos_location_segments(apr_array_header_t
**segments
,
624 svn_ra_session_t
*ra_session
,
626 svn_revnum_t peg_revision
,
627 svn_revnum_t start_revision
,
628 svn_revnum_t end_revision
,
629 svn_client_ctx_t
*ctx
,
632 struct gls_receiver_baton_t gls_receiver_baton
;
633 const char *old_session_url
;
636 *segments
= apr_array_make(pool
, 8, sizeof(svn_location_segment_t
*));
637 gls_receiver_baton
.segments
= *segments
;
638 gls_receiver_baton
.ctx
= ctx
;
639 gls_receiver_baton
.pool
= pool
;
640 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url
, ra_session
,
642 err
= svn_ra_get_location_segments(ra_session
, "", peg_revision
,
643 start_revision
, end_revision
,
644 gls_receiver
, &gls_receiver_baton
,
646 SVN_ERR(svn_error_compose_create(
647 err
, svn_ra_reparent(ra_session
, old_session_url
, pool
)));
648 svn_sort__array(*segments
, compare_segments
);
652 /* Set *START_URL and *END_URL to the URLs that the object URL@PEG_REVNUM
653 * had in revisions START_REVNUM and END_REVNUM. Return an error if the
654 * node cannot be traced back to one of the requested revisions.
656 * START_URL and/or END_URL may be NULL if not wanted. START_REVNUM and
657 * END_REVNUM must be valid revision numbers except that END_REVNUM may
658 * be SVN_INVALID_REVNUM if END_URL is NULL.
660 * YOUNGEST_REV is the already retrieved youngest revision of the ra session,
661 * but can be SVN_INVALID_REVNUM if the value is not already retrieved.
663 * RA_SESSION is an open RA session parented at URL.
666 repos_locations(const char **start_url
,
667 const char **end_url
,
668 svn_ra_session_t
*ra_session
,
670 svn_revnum_t peg_revnum
,
671 svn_revnum_t start_revnum
,
672 svn_revnum_t end_revnum
,
673 svn_revnum_t youngest_rev
,
674 apr_pool_t
*result_pool
,
675 apr_pool_t
*scratch_pool
)
677 const char *repos_url
, *start_path
, *end_path
;
678 apr_array_header_t
*revs
;
679 apr_hash_t
*rev_locs
;
681 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(peg_revnum
));
682 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_revnum
));
683 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_revnum
) || end_url
== NULL
);
685 /* Avoid a network request in the common easy case. */
686 if (start_revnum
== peg_revnum
687 && (end_revnum
== peg_revnum
|| end_revnum
== SVN_INVALID_REVNUM
))
690 *start_url
= apr_pstrdup(result_pool
, url
);
692 *end_url
= apr_pstrdup(result_pool
, url
);
696 SVN_ERR(svn_ra_get_repos_root2(ra_session
, &repos_url
, scratch_pool
));
698 /* Handle another common case: The repository root can't move */
699 if (! strcmp(repos_url
, url
))
701 if (! SVN_IS_VALID_REVNUM(youngest_rev
))
702 SVN_ERR(svn_ra_get_latest_revnum(ra_session
, &youngest_rev
,
705 if (start_revnum
> youngest_rev
706 || (SVN_IS_VALID_REVNUM(end_revnum
) && (end_revnum
> youngest_rev
)))
707 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION
, NULL
,
708 _("No such revision %ld"),
709 (start_revnum
> youngest_rev
)
710 ? start_revnum
: end_revnum
);
713 *start_url
= apr_pstrdup(result_pool
, repos_url
);
715 *end_url
= apr_pstrdup(result_pool
, repos_url
);
719 revs
= apr_array_make(scratch_pool
, 2, sizeof(svn_revnum_t
));
720 APR_ARRAY_PUSH(revs
, svn_revnum_t
) = start_revnum
;
721 if (end_revnum
!= start_revnum
&& end_revnum
!= SVN_INVALID_REVNUM
)
722 APR_ARRAY_PUSH(revs
, svn_revnum_t
) = end_revnum
;
724 SVN_ERR(svn_ra_get_locations(ra_session
, &rev_locs
, "", peg_revnum
,
725 revs
, scratch_pool
));
727 /* We'd better have all the paths we were looking for! */
730 start_path
= apr_hash_get(rev_locs
, &start_revnum
, sizeof(start_revnum
));
732 return svn_error_createf
733 (SVN_ERR_CLIENT_UNRELATED_RESOURCES
, NULL
,
734 _("Unable to find repository location for '%s' in revision %ld"),
736 *start_url
= svn_path_url_add_component2(repos_url
, start_path
+ 1,
742 end_path
= apr_hash_get(rev_locs
, &end_revnum
, sizeof(end_revnum
));
744 return svn_error_createf
745 (SVN_ERR_CLIENT_UNRELATED_RESOURCES
, NULL
,
746 _("The location for '%s' for revision %ld does not exist in the "
747 "repository or refers to an unrelated object"),
750 *end_url
= svn_path_url_add_component2(repos_url
, end_path
+ 1,
758 svn_client__repos_location(svn_client__pathrev_t
**op_loc_p
,
759 svn_ra_session_t
*ra_session
,
760 const svn_client__pathrev_t
*peg_loc
,
761 svn_revnum_t op_revnum
,
762 svn_client_ctx_t
*ctx
,
763 apr_pool_t
*result_pool
,
764 apr_pool_t
*scratch_pool
)
766 const char *old_session_url
;
770 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url
, ra_session
,
771 peg_loc
->url
, scratch_pool
));
772 err
= repos_locations(&op_url
, NULL
, ra_session
,
773 peg_loc
->url
, peg_loc
->rev
,
774 op_revnum
, SVN_INVALID_REVNUM
, SVN_INVALID_REVNUM
,
775 result_pool
, scratch_pool
);
776 SVN_ERR(svn_error_compose_create(
777 err
, svn_ra_reparent(ra_session
, old_session_url
, scratch_pool
)));
779 *op_loc_p
= svn_client__pathrev_create(peg_loc
->repos_root_url
,
781 op_revnum
, op_url
, result_pool
);
786 svn_client__repos_locations(const char **start_url
,
787 svn_revnum_t
*start_revision
,
788 const char **end_url
,
789 svn_revnum_t
*end_revision
,
790 svn_ra_session_t
*ra_session
,
792 const svn_opt_revision_t
*revision
,
793 const svn_opt_revision_t
*start
,
794 const svn_opt_revision_t
*end
,
795 svn_client_ctx_t
*ctx
,
799 const char *local_abspath_or_url
;
800 svn_revnum_t peg_revnum
= SVN_INVALID_REVNUM
;
801 svn_revnum_t start_revnum
, end_revnum
;
802 svn_revnum_t youngest_rev
= SVN_INVALID_REVNUM
;
803 apr_pool_t
*subpool
= svn_pool_create(pool
);
805 /* Ensure that we are given some real revision data to work with.
806 (It's okay if the END is unspecified -- in that case, we'll just
807 set it to the same thing as START.) */
808 if (revision
->kind
== svn_opt_revision_unspecified
809 || start
->kind
== svn_opt_revision_unspecified
)
810 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION
, NULL
, NULL
);
814 static const svn_opt_revision_t unspecified_rev
815 = { svn_opt_revision_unspecified
, { 0 } };
817 end
= &unspecified_rev
;
820 /* Determine LOCAL_ABSPATH_OR_URL, URL, and possibly PEG_REVNUM.
821 If we are looking at the working version of a WC path that is scheduled
822 as a copy, then we need to use the copy-from URL and peg revision. */
823 if (! svn_path_is_url(path
))
825 SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url
, path
, subpool
));
827 if (revision
->kind
== svn_opt_revision_working
)
829 const char *repos_root_url
;
830 const char *repos_relpath
;
831 svn_boolean_t is_copy
;
833 SVN_ERR(svn_wc__node_get_origin(&is_copy
, &peg_revnum
, &repos_relpath
,
834 &repos_root_url
, NULL
, NULL
, NULL
,
835 ctx
->wc_ctx
, local_abspath_or_url
,
836 FALSE
, subpool
, subpool
));
839 url
= svn_path_url_add_component2(repos_root_url
, repos_relpath
,
844 if (url
&& is_copy
&& ra_session
)
846 const char *session_url
;
847 SVN_ERR(svn_ra_get_session_url(ra_session
, &session_url
,
850 if (strcmp(session_url
, url
) != 0)
852 /* We can't use the caller provided RA session now :( */
861 SVN_ERR(svn_wc__node_get_url(&url
, ctx
->wc_ctx
,
862 local_abspath_or_url
, pool
, subpool
));
866 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL
, NULL
,
867 _("'%s' has no URL"),
868 svn_dirent_local_style(path
, pool
));
873 local_abspath_or_url
= path
;
877 /* ### We should be smarter here. If the callers just asks for BASE and
878 WORKING revisions, we should already have the correct URLs, so we
879 don't need to do anything more here in that case. */
881 /* Open a RA session to this URL if we don't have one already. */
883 SVN_ERR(svn_client_open_ra_session2(&ra_session
, url
, NULL
,
884 ctx
, subpool
, subpool
));
886 /* Resolve the opt_revision_ts. */
887 if (peg_revnum
== SVN_INVALID_REVNUM
)
888 SVN_ERR(svn_client__get_revision_number(&peg_revnum
, &youngest_rev
,
889 ctx
->wc_ctx
, local_abspath_or_url
,
890 ra_session
, revision
, pool
));
892 SVN_ERR(svn_client__get_revision_number(&start_revnum
, &youngest_rev
,
893 ctx
->wc_ctx
, local_abspath_or_url
,
894 ra_session
, start
, pool
));
895 if (end
->kind
== svn_opt_revision_unspecified
)
896 end_revnum
= start_revnum
;
898 SVN_ERR(svn_client__get_revision_number(&end_revnum
, &youngest_rev
,
899 ctx
->wc_ctx
, local_abspath_or_url
,
900 ra_session
, end
, pool
));
902 /* Set the output revision variables. */
905 *start_revision
= start_revnum
;
907 if (end_revision
&& end
->kind
!= svn_opt_revision_unspecified
)
909 *end_revision
= end_revnum
;
912 SVN_ERR(repos_locations(start_url
, end_url
,
913 ra_session
, url
, peg_revnum
,
914 start_revnum
, end_revnum
, youngest_rev
,
916 svn_pool_destroy(subpool
);
921 svn_client__calc_youngest_common_ancestor(svn_client__pathrev_t
**ancestor_p
,
922 const svn_client__pathrev_t
*loc1
,
923 apr_hash_t
*history1
,
924 svn_boolean_t has_rev_zero_history1
,
925 const svn_client__pathrev_t
*loc2
,
926 apr_hash_t
*history2
,
927 svn_boolean_t has_rev_zero_history2
,
928 apr_pool_t
*result_pool
,
929 apr_pool_t
*scratch_pool
)
931 apr_hash_index_t
*hi
;
932 svn_revnum_t yc_revision
= SVN_INVALID_REVNUM
;
933 const char *yc_relpath
= NULL
;
935 if (strcmp(loc1
->repos_root_url
, loc2
->repos_root_url
) != 0)
941 /* Loop through the first location's history, check for overlapping
942 paths and ranges in the second location's history, and
943 remembering the youngest matching location. */
944 for (hi
= apr_hash_first(scratch_pool
, history1
); hi
; hi
= apr_hash_next(hi
))
946 const char *path
= apr_hash_this_key(hi
);
947 apr_ssize_t path_len
= apr_hash_this_key_len(hi
);
948 svn_rangelist_t
*ranges1
= apr_hash_this_val(hi
);
949 svn_rangelist_t
*ranges2
, *common
;
951 ranges2
= apr_hash_get(history2
, path
, path_len
);
954 /* We have a path match. Now, did our two histories share
955 any revisions at that path? */
956 SVN_ERR(svn_rangelist_intersect(&common
, ranges1
, ranges2
,
957 TRUE
, scratch_pool
));
960 svn_merge_range_t
*yc_range
=
961 APR_ARRAY_IDX(common
, common
->nelts
- 1, svn_merge_range_t
*);
962 if ((! SVN_IS_VALID_REVNUM(yc_revision
))
963 || (yc_range
->end
> yc_revision
))
965 yc_revision
= yc_range
->end
;
966 yc_relpath
= path
+ 1;
972 /* It's possible that PATH_OR_URL1 and PATH_OR_URL2's only common
973 history is revision 0. */
974 if (!yc_relpath
&& has_rev_zero_history1
&& has_rev_zero_history2
)
982 *ancestor_p
= svn_client__pathrev_create_with_relpath(
983 loc1
->repos_root_url
, loc1
->repos_uuid
,
984 yc_revision
, yc_relpath
, result_pool
);
994 svn_client__get_youngest_common_ancestor(svn_client__pathrev_t
**ancestor_p
,
995 const svn_client__pathrev_t
*loc1
,
996 const svn_client__pathrev_t
*loc2
,
997 svn_ra_session_t
*session
,
998 svn_client_ctx_t
*ctx
,
999 apr_pool_t
*result_pool
,
1000 apr_pool_t
*scratch_pool
)
1002 apr_pool_t
*sesspool
= NULL
;
1003 apr_hash_t
*history1
, *history2
;
1004 svn_boolean_t has_rev_zero_history1
;
1005 svn_boolean_t has_rev_zero_history2
;
1007 if (strcmp(loc1
->repos_root_url
, loc2
->repos_root_url
) != 0)
1010 return SVN_NO_ERROR
;
1013 /* Open an RA session for the two locations. */
1014 if (session
== NULL
)
1016 sesspool
= svn_pool_create(scratch_pool
);
1017 SVN_ERR(svn_client_open_ra_session2(&session
, loc1
->url
, NULL
, ctx
,
1018 sesspool
, sesspool
));
1021 /* We're going to cheat and use history-as-mergeinfo because it
1022 saves us a bunch of annoying custom data comparisons and such. */
1023 SVN_ERR(svn_client__get_history_as_mergeinfo(&history1
,
1024 &has_rev_zero_history1
,
1028 session
, ctx
, scratch_pool
));
1029 SVN_ERR(svn_client__get_history_as_mergeinfo(&history2
,
1030 &has_rev_zero_history2
,
1034 session
, ctx
, scratch_pool
));
1035 /* Close the ra session if we opened one. */
1037 svn_pool_destroy(sesspool
);
1039 SVN_ERR(svn_client__calc_youngest_common_ancestor(ancestor_p
,
1041 has_rev_zero_history1
,
1043 has_rev_zero_history2
,
1047 return SVN_NO_ERROR
;
1050 struct ra_ev2_baton
{
1051 /* The working copy context, from the client context. */
1052 svn_wc_context_t
*wc_ctx
;
1054 /* For a given REPOS_RELPATH, provide a LOCAL_ABSPATH that represents
1055 that repository node. */
1056 apr_hash_t
*relpath_map
;
1061 svn_client__ra_provide_base(svn_stream_t
**contents
,
1062 svn_revnum_t
*revision
,
1064 const char *repos_relpath
,
1065 apr_pool_t
*result_pool
,
1066 apr_pool_t
*scratch_pool
)
1068 struct ra_ev2_baton
*reb
= baton
;
1069 const char *local_abspath
;
1072 local_abspath
= svn_hash_gets(reb
->relpath_map
, repos_relpath
);
1076 return SVN_NO_ERROR
;
1079 err
= svn_wc_get_pristine_contents2(contents
, reb
->wc_ctx
, local_abspath
,
1080 result_pool
, scratch_pool
);
1083 if (err
->apr_err
!= SVN_ERR_WC_PATH_NOT_FOUND
)
1084 return svn_error_trace(err
);
1086 svn_error_clear(err
);
1088 return SVN_NO_ERROR
;
1091 if (*contents
!= NULL
)
1093 /* The pristine contents refer to the BASE, or to the pristine of
1094 a copy/move to this location. Fetch the correct revision. */
1095 SVN_ERR(svn_wc__node_get_origin(NULL
, revision
, NULL
, NULL
, NULL
, NULL
,
1097 reb
->wc_ctx
, local_abspath
, FALSE
,
1098 scratch_pool
, scratch_pool
));
1101 return SVN_NO_ERROR
;
1106 svn_client__ra_provide_props(apr_hash_t
**props
,
1107 svn_revnum_t
*revision
,
1109 const char *repos_relpath
,
1110 apr_pool_t
*result_pool
,
1111 apr_pool_t
*scratch_pool
)
1113 struct ra_ev2_baton
*reb
= baton
;
1114 const char *local_abspath
;
1117 local_abspath
= svn_hash_gets(reb
->relpath_map
, repos_relpath
);
1121 return SVN_NO_ERROR
;
1124 err
= svn_wc_get_pristine_props(props
, reb
->wc_ctx
, local_abspath
,
1125 result_pool
, scratch_pool
);
1128 if (err
->apr_err
!= SVN_ERR_WC_PATH_NOT_FOUND
)
1129 return svn_error_trace(err
);
1131 svn_error_clear(err
);
1133 return SVN_NO_ERROR
;
1138 /* The pristine props refer to the BASE, or to the pristine props of
1139 a copy/move to this location. Fetch the correct revision. */
1140 SVN_ERR(svn_wc__node_get_origin(NULL
, revision
, NULL
, NULL
, NULL
, NULL
,
1142 reb
->wc_ctx
, local_abspath
, FALSE
,
1143 scratch_pool
, scratch_pool
));
1146 return SVN_NO_ERROR
;
1151 svn_client__ra_get_copysrc_kind(svn_node_kind_t
*kind
,
1153 const char *repos_relpath
,
1154 svn_revnum_t src_revision
,
1155 apr_pool_t
*scratch_pool
)
1157 struct ra_ev2_baton
*reb
= baton
;
1158 const char *local_abspath
;
1160 local_abspath
= svn_hash_gets(reb
->relpath_map
, repos_relpath
);
1163 *kind
= svn_node_unknown
;
1164 return SVN_NO_ERROR
;
1167 /* ### what to do with SRC_REVISION? */
1169 SVN_ERR(svn_wc_read_kind2(kind
, reb
->wc_ctx
, local_abspath
,
1170 FALSE
, FALSE
, scratch_pool
));
1172 return SVN_NO_ERROR
;
1177 svn_client__ra_make_cb_baton(svn_wc_context_t
*wc_ctx
,
1178 apr_hash_t
*relpath_map
,
1179 apr_pool_t
*result_pool
)
1181 struct ra_ev2_baton
*reb
= apr_palloc(result_pool
, sizeof(*reb
));
1183 SVN_ERR_ASSERT_NO_RETURN(wc_ctx
!= NULL
);
1184 SVN_ERR_ASSERT_NO_RETURN(relpath_map
!= NULL
);
1186 reb
->wc_ctx
= wc_ctx
;
1187 reb
->relpath_map
= relpath_map
;