1 /*
2 * ra.c : routines for interacting with the RA layer
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24
25
26 #include <apr_pools.h>
27
28 #include "svn_error.h"
29 #include "svn_hash.h"
30 #include "svn_pools.h"
31 #include "svn_string.h"
32 #include "svn_sorts.h"
33 #include "svn_ra.h"
34 #include "svn_client.h"
35 #include "svn_dirent_uri.h"
36 #include "svn_path.h"
37 #include "svn_props.h"
38 #include "svn_mergeinfo.h"
39 #include "client.h"
40 #include "mergeinfo.h"
41
42 #include "svn_private_config.h"
43 #include "private/svn_wc_private.h"
44 #include "private/svn_client_private.h"
45
46
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
50 {
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;
55
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;
62
63 /* Used as wri_abspath for obtaining access to the pristine store */
64 const char *wcroot_abspath;
65
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;
69
70 /* A client context. */
71 svn_client_ctx_t *ctx;
72
73 } callback_baton_t;
74
75
76
77 static svn_error_t *
open_tmp_file(apr_file_t ** fp,void * callback_baton,apr_pool_t * pool)78 open_tmp_file(apr_file_t **fp,
79 void *callback_baton,
80 apr_pool_t *pool)
81 {
82 return svn_error_trace(svn_io_open_unique_file3(fp, NULL, NULL,
83 svn_io_file_del_on_pool_cleanup,
84 pool, pool));
85 }
86
87
88 /* This implements the 'svn_ra_get_wc_prop_func_t' interface. */
89 static svn_error_t *
get_wc_prop(void * baton,const char * relpath,const char * name,const svn_string_t ** value,apr_pool_t * pool)90 get_wc_prop(void *baton,
91 const char *relpath,
92 const char *name,
93 const svn_string_t **value,
94 apr_pool_t *pool)
95 {
96 callback_baton_t *cb = baton;
97 const char *local_abspath = NULL;
98 svn_error_t *err;
99
100 *value = NULL;
101
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)
105 {
106 int i;
107 for (i = 0; i < cb->commit_items->nelts; i++)
108 {
109 svn_client_commit_item3_t *item
110 = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
111
112 if (! strcmp(relpath, item->session_relpath))
113 {
114 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
115 local_abspath = item->path;
116 break;
117 }
118 }
119
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
123 working copy. */
124 if (! local_abspath)
125 return SVN_NO_ERROR;
126 }
127
128 /* If we don't have a base directory, then there are no properties. */
129 else if (cb->base_dir_abspath == NULL)
130 return SVN_NO_ERROR;
131
132 else
133 local_abspath = svn_dirent_join(cb->base_dir_abspath, relpath, pool);
134
135 err = svn_wc_prop_get2(value, cb->ctx->wc_ctx, local_abspath, name,
136 pool, pool);
137 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
138 {
139 svn_error_clear(err);
140 err = NULL;
141 }
142 return svn_error_trace(err);
143 }
144
145 /* This implements the 'svn_ra_push_wc_prop_func_t' interface. */
146 static svn_error_t *
push_wc_prop(void * baton,const char * relpath,const char * name,const svn_string_t * value,apr_pool_t * pool)147 push_wc_prop(void *baton,
148 const char *relpath,
149 const char *name,
150 const svn_string_t *value,
151 apr_pool_t *pool)
152 {
153 callback_baton_t *cb = baton;
154 int i;
155
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));
163
164 for (i = 0; i < cb->commit_items->nelts; i++)
165 {
166 svn_client_commit_item3_t *item
167 = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
168
169 if (strcmp(relpath, item->session_relpath) == 0)
170 {
171 apr_pool_t *changes_pool = item->incoming_prop_changes->pool;
172 svn_prop_t *prop = apr_palloc(changes_pool, sizeof(*prop));
173
174 prop->name = apr_pstrdup(changes_pool, name);
175 if (value)
176 prop->value = svn_string_dup(value, changes_pool);
177 else
178 prop->value = NULL;
179
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;
183 return SVN_NO_ERROR;
184 }
185 }
186
187 return SVN_NO_ERROR;
188 }
189
190
191 /* This implements the 'svn_ra_set_wc_prop_func_t' interface. */
192 static svn_error_t *
set_wc_prop(void * baton,const char * path,const char * name,const svn_string_t * value,apr_pool_t * pool)193 set_wc_prop(void *baton,
194 const char *path,
195 const char *name,
196 const svn_string_t *value,
197 apr_pool_t *pool)
198 {
199 callback_baton_t *cb = baton;
200 const char *local_abspath;
201
202 local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool);
203
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,
213 name,
214 value, svn_depth_empty,
215 TRUE /* skip_checks */,
216 NULL /* changelist_filter */,
217 NULL, NULL /* cancellation */,
218 NULL, NULL /* notification */,
219 pool));
220 }
221
222
223 /* This implements the `svn_ra_invalidate_wc_props_func_t' interface. */
224 static svn_error_t *
invalidate_wc_props(void * baton,const char * path,const char * prop_name,apr_pool_t * pool)225 invalidate_wc_props(void *baton,
226 const char *path,
227 const char *prop_name,
228 apr_pool_t *pool)
229 {
230 callback_baton_t *cb = baton;
231 const char *local_abspath;
232
233 local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool);
234
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));
245 }
246
247
248 /* This implements the `svn_ra_get_wc_contents_func_t' interface. */
249 static svn_error_t *
get_wc_contents(void * baton,svn_stream_t ** contents,const svn_checksum_t * checksum,apr_pool_t * pool)250 get_wc_contents(void *baton,
251 svn_stream_t **contents,
252 const svn_checksum_t *checksum,
253 apr_pool_t *pool)
254 {
255 callback_baton_t *cb = baton;
256
257 if (! cb->wcroot_abspath)
258 {
259 *contents = NULL;
260 return SVN_NO_ERROR;
261 }
262
263 return svn_error_trace(
264 svn_wc__get_pristine_contents_by_checksum(contents,
265 cb->ctx->wc_ctx,
266 cb->wcroot_abspath,
267 checksum,
268 pool, pool));
269 }
270
271
272 static svn_error_t *
cancel_callback(void * baton)273 cancel_callback(void *baton)
274 {
275 callback_baton_t *b = baton;
276 return svn_error_trace((b->ctx->cancel_func)(b->ctx->cancel_baton));
277 }
278
279
280 static svn_error_t *
get_client_string(void * baton,const char ** name,apr_pool_t * pool)281 get_client_string(void *baton,
282 const char **name,
283 apr_pool_t *pool)
284 {
285 callback_baton_t *b = baton;
286 *name = apr_pstrdup(pool, b->ctx->client_name);
287 return SVN_NO_ERROR;
288 }
289
290
291 #define SVN_CLIENT__MAX_REDIRECT_ATTEMPTS 3 /* ### TODO: Make configurable. */
292
293 svn_error_t *
svn_client__open_ra_session_internal(svn_ra_session_t ** ra_session,const char ** corrected_url,const char * base_url,const char * base_dir_abspath,const apr_array_header_t * commit_items,svn_boolean_t write_dav_props,svn_boolean_t read_dav_props,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
304 {
305 svn_ra_callbacks2_t *cbtable;
306 callback_baton_t *cb = apr_pcalloc(result_pool, sizeof(*cb));
307 const char *uuid = NULL;
308
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));
313
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;
329
330 cb->commit_items = commit_items;
331 cb->ctx = ctx;
332
333 if (base_dir_abspath && (read_dav_props || write_dav_props))
334 {
335 svn_error_t *err = svn_wc__node_get_repos_info(NULL, NULL, NULL, &uuid,
336 ctx->wc_ctx,
337 base_dir_abspath,
338 result_pool,
339 scratch_pool);
340
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))
344 {
345 svn_error_clear(err);
346 uuid = NULL;
347 }
348 else
349 {
350 SVN_ERR(err);
351 cb->base_dir_isversioned = TRUE;
352 }
353 cb->base_dir_abspath = apr_pstrdup(result_pool, base_dir_abspath);
354 }
355
356 if (base_dir_abspath)
357 {
358 svn_error_t *err = svn_wc__get_wcroot(&cb->wcroot_abspath,
359 ctx->wc_ctx, base_dir_abspath,
360 result_pool, scratch_pool);
361
362 if (err)
363 {
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);
368
369 svn_error_clear(err);
370 cb->wcroot_abspath = NULL;
371 }
372 }
373
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
377 attempts. */
378 if (corrected_url)
379 {
380 apr_hash_t *attempted = apr_hash_make(scratch_pool);
381 int attempts_left = SVN_CLIENT__MAX_REDIRECT_ATTEMPTS;
382
383 *corrected_url = NULL;
384 while (attempts_left--)
385 {
386 const char *corrected = NULL;
387
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,
393 result_pool));
394
395 /* No error and no corrected URL? We're done here. */
396 if (! corrected)
397 break;
398
399 /* Notify the user that a redirect is being followed. */
400 if (ctx->notify_func2 != NULL)
401 {
402 svn_wc_notify_t *notify =
403 svn_wc_create_notify_url(corrected,
404 svn_wc_notify_url_redirect,
405 scratch_pool);
406 (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
407 }
408
409 /* Our caller will want to know what our final corrected URL was. */
410 *corrected_url = corrected;
411
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'"),
416 corrected);
417
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;
421 }
422 }
423 else
424 {
425 SVN_ERR(svn_ra_open4(ra_session, NULL, base_url,
426 uuid, cbtable, cb, ctx->config, result_pool));
427 }
428
429 return SVN_NO_ERROR;
430 }
431 #undef SVN_CLIENT__MAX_REDIRECT_ATTEMPTS
432
433
434 svn_error_t *
svn_client_open_ra_session2(svn_ra_session_t ** session,const char * url,const char * wri_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)435 svn_client_open_ra_session2(svn_ra_session_t **session,
436 const char *url,
437 const char *wri_abspath,
438 svn_client_ctx_t *ctx,
439 apr_pool_t *result_pool,
440 apr_pool_t *scratch_pool)
441 {
442 return svn_error_trace(
443 svn_client__open_ra_session_internal(session, NULL, url,
444 wri_abspath, NULL,
445 FALSE, FALSE,
446 ctx, result_pool,
447 scratch_pool));
448 }
449
450 svn_error_t *
svn_client__resolve_rev_and_url(svn_client__pathrev_t ** resolved_loc_p,svn_ra_session_t * ra_session,const char * path_or_url,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * revision,svn_client_ctx_t * ctx,apr_pool_t * pool)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,
457 apr_pool_t *pool)
458 {
459 svn_opt_revision_t peg_rev = *peg_revision;
460 svn_opt_revision_t start_rev = *revision;
461 const char *url;
462 svn_revnum_t rev;
463
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 */,
468 pool));
469
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));
475
476 SVN_ERR(svn_client__pathrev_create_with_session(resolved_loc_p,
477 ra_session, rev, url, pool));
478 return SVN_NO_ERROR;
479 }
480
481 svn_error_t *
svn_client__ra_session_from_path2(svn_ra_session_t ** ra_session_p,svn_client__pathrev_t ** resolved_loc_p,const char * path_or_url,const char * base_dir_abspath,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * revision,svn_client_ctx_t * ctx,apr_pool_t * 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,
489 apr_pool_t *pool)
490 {
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;
496
497 SVN_ERR(svn_client_url_from_path2(&initial_url, path_or_url, ctx, pool,
498 pool));
499 if (! initial_url)
500 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
501 _("'%s' has no URL"), path_or_url);
502
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));
507 else
508 wri_abspath = NULL;
509
510 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
511 initial_url,
512 wri_abspath,
513 NULL /* commit_items */,
514 base_dir_abspath != NULL,
515 base_dir_abspath != NULL,
516 ctx, pool, pool));
517
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;
522
523 SVN_ERR(svn_client__resolve_rev_and_url(&resolved_loc, ra_session,
524 path_or_url, peg_revision, revision,
525 ctx, pool));
526
527 /* Make the session point to the real URL. */
528 SVN_ERR(svn_ra_reparent(ra_session, resolved_loc->url, pool));
529
530 *ra_session_p = ra_session;
531 if (resolved_loc_p)
532 *resolved_loc_p = resolved_loc;
533
534 return SVN_NO_ERROR;
535 }
536
537
538 svn_error_t *
svn_client__ensure_ra_session_url(const char ** old_session_url,svn_ra_session_t * ra_session,const char * session_url,apr_pool_t * pool)539 svn_client__ensure_ra_session_url(const char **old_session_url,
540 svn_ra_session_t *ra_session,
541 const char *session_url,
542 apr_pool_t *pool)
543 {
544 SVN_ERR(svn_ra_get_session_url(ra_session, old_session_url, pool));
545 if (! session_url)
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));
549 return SVN_NO_ERROR;
550 }
551
552
553
554 /*** Repository Locations ***/
555
556 struct gls_receiver_baton_t
557 {
558 apr_array_header_t *segments;
559 svn_client_ctx_t *ctx;
560 apr_pool_t *pool;
561 };
562
563 static svn_error_t *
gls_receiver(svn_location_segment_t * segment,void * baton,apr_pool_t * pool)564 gls_receiver(svn_location_segment_t *segment,
565 void *baton,
566 apr_pool_t *pool)
567 {
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));
573 return SVN_NO_ERROR;
574 }
575
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. */
579 static int
compare_segments(const void * a,const void * b)580 compare_segments(const void *a, const void *b)
581 {
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)
587 return 0;
588 return (a_seg->range_start < b_seg->range_start) ? -1 : 1;
589 }
590
591 svn_error_t *
svn_client__repos_location_segments(apr_array_header_t ** segments,svn_ra_session_t * ra_session,const char * url,svn_revnum_t peg_revision,svn_revnum_t start_revision,svn_revnum_t end_revision,svn_client_ctx_t * ctx,apr_pool_t * pool)592 svn_client__repos_location_segments(apr_array_header_t **segments,
593 svn_ra_session_t *ra_session,
594 const char *url,
595 svn_revnum_t peg_revision,
596 svn_revnum_t start_revision,
597 svn_revnum_t end_revision,
598 svn_client_ctx_t *ctx,
599 apr_pool_t *pool)
600 {
601 struct gls_receiver_baton_t gls_receiver_baton;
602 const char *old_session_url;
603 svn_error_t *err;
604
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,
610 url, pool));
611 err = svn_ra_get_location_segments(ra_session, "", peg_revision,
612 start_revision, end_revision,
613 gls_receiver, &gls_receiver_baton,
614 pool);
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);
619 return SVN_NO_ERROR;
620 }
621
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.
625 *
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.
629 *
630 * RA_SESSION is an open RA session parented at URL.
631 */
632 static svn_error_t *
repos_locations(const char ** start_url,const char ** end_url,svn_ra_session_t * ra_session,const char * url,svn_revnum_t peg_revnum,svn_revnum_t start_revnum,svn_revnum_t end_revnum,apr_pool_t * result_pool,apr_pool_t * scratch_pool)633 repos_locations(const char **start_url,
634 const char **end_url,
635 svn_ra_session_t *ra_session,
636 const char *url,
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)
642 {
643 const char *repos_url, *start_path, *end_path;
644 apr_array_header_t *revs;
645 apr_hash_t *rev_locs;
646
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);
650
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))
654 {
655 if (start_url)
656 *start_url = apr_pstrdup(result_pool, url);
657 if (end_url)
658 *end_url = apr_pstrdup(result_pool, url);
659 return SVN_NO_ERROR;
660 }
661
662 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, scratch_pool));
663
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;
668
669 SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum,
670 revs, scratch_pool));
671
672 /* We'd better have all the paths we were looking for! */
673 if (start_url)
674 {
675 start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(svn_revnum_t));
676 if (! start_path)
677 return svn_error_createf
678 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
679 _("Unable to find repository location for '%s' in revision %ld"),
680 url, start_revnum);
681 *start_url = svn_path_url_add_component2(repos_url, start_path + 1,
682 result_pool);
683 }
684
685 if (end_url)
686 {
687 end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t));
688 if (! end_path)
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"),
693 url, end_revnum);
694
695 *end_url = svn_path_url_add_component2(repos_url, end_path + 1,
696 result_pool);
697 }
698
699 return SVN_NO_ERROR;
700 }
701
702 svn_error_t *
svn_client__repos_location(svn_client__pathrev_t ** op_loc_p,svn_ra_session_t * ra_session,const svn_client__pathrev_t * peg_loc,svn_revnum_t op_revnum,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
710 {
711 const char *old_session_url;
712 const char *op_url;
713 svn_error_t *err;
714
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)));
723
724 *op_loc_p = svn_client__pathrev_create(peg_loc->repos_root_url,
725 peg_loc->repos_uuid,
726 op_revnum, op_url, result_pool);
727 return SVN_NO_ERROR;
728 }
729
730 svn_error_t *
svn_client__repos_locations(const char ** start_url,svn_revnum_t * start_revision,const char ** end_url,svn_revnum_t * end_revision,svn_ra_session_t * ra_session,const char * path,const svn_opt_revision_t * revision,const svn_opt_revision_t * start,const svn_opt_revision_t * end,svn_client_ctx_t * ctx,apr_pool_t * 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,
736 const char *path,
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,
741 apr_pool_t *pool)
742 {
743 const char *url;
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);
749
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);
756
757 if (end == NULL)
758 {
759 static const svn_opt_revision_t unspecified_rev
760 = { svn_opt_revision_unspecified, { 0 } };
761
762 end = &unspecified_rev;
763 }
764
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))
769 {
770 SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, subpool));
771
772 if (revision->kind == svn_opt_revision_working)
773 {
774 const char *repos_root_url;
775 const char *repos_relpath;
776 svn_boolean_t is_copy;
777
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));
782
783 if (repos_relpath)
784 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
785 pool);
786 else
787 url = NULL;
788
789 if (url && is_copy && ra_session)
790 {
791 const char *session_url;
792 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url,
793 subpool));
794
795 if (strcmp(session_url, url) != 0)
796 {
797 /* We can't use the caller provided RA session now :( */
798 ra_session = NULL;
799 }
800 }
801 }
802 else
803 url = NULL;
804
805 if (! url)
806 SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx,
807 local_abspath_or_url, pool, subpool));
808
809 if (!url)
810 {
811 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
812 _("'%s' has no URL"),
813 svn_dirent_local_style(path, pool));
814 }
815 }
816 else
817 {
818 local_abspath_or_url = path;
819 url = path;
820 }
821
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. */
825
826 /* Open a RA session to this URL if we don't have one already. */
827 if (! ra_session)
828 SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
829 ctx, subpool, subpool));
830
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));
836
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;
842 else
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));
846
847 /* Set the output revision variables. */
848 if (start_revision)
849 {
850 *start_revision = start_revnum;
851 }
852 if (end_revision && end->kind != svn_opt_revision_unspecified)
853 {
854 *end_revision = end_revnum;
855 }
856
857 SVN_ERR(repos_locations(start_url, end_url,
858 ra_session, url, peg_revnum,
859 start_revnum, end_revnum,
860 pool, subpool));
861 svn_pool_destroy(subpool);
862 return SVN_NO_ERROR;
863 }
864
865 svn_error_t *
svn_client__calc_youngest_common_ancestor(svn_client__pathrev_t ** ancestor_p,const svn_client__pathrev_t * loc1,apr_hash_t * history1,svn_boolean_t has_rev_zero_history1,const svn_client__pathrev_t * loc2,apr_hash_t * history2,svn_boolean_t has_rev_zero_history2,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
875 {
876 apr_hash_index_t *hi;
877 svn_revnum_t yc_revision = SVN_INVALID_REVNUM;
878 const char *yc_relpath = NULL;
879
880 if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0)
881 {
882 *ancestor_p = NULL;
883 return SVN_NO_ERROR;
884 }
885
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))
890 {
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;
895
896 ranges2 = apr_hash_get(history2, path, path_len);
897 if (ranges2)
898 {
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));
903 if (common->nelts)
904 {
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))
909 {
910 yc_revision = yc_range->end;
911 yc_relpath = path + 1;
912 }
913 }
914 }
915 }
916
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)
920 {
921 yc_relpath = "";
922 yc_revision = 0;
923 }
924
925 if (yc_relpath)
926 {
927 *ancestor_p = svn_client__pathrev_create_with_relpath(
928 loc1->repos_root_url, loc1->repos_uuid,
929 yc_revision, yc_relpath, result_pool);
930 }
931 else
932 {
933 *ancestor_p = NULL;
934 }
935 return SVN_NO_ERROR;
936 }
937
938 svn_error_t *
svn_client__get_youngest_common_ancestor(svn_client__pathrev_t ** ancestor_p,const svn_client__pathrev_t * loc1,const svn_client__pathrev_t * loc2,svn_ra_session_t * session,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_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)
946 {
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;
951
952 if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0)
953 {
954 *ancestor_p = NULL;
955 return SVN_NO_ERROR;
956 }
957
958 /* Open an RA session for the two locations. */
959 if (session == NULL)
960 {
961 sesspool = svn_pool_create(scratch_pool);
962 SVN_ERR(svn_client_open_ra_session2(&session, loc1->url, NULL, ctx,
963 sesspool, sesspool));
964 }
965
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,
970 loc1,
971 SVN_INVALID_REVNUM,
972 SVN_INVALID_REVNUM,
973 session, ctx, scratch_pool));
974 SVN_ERR(svn_client__get_history_as_mergeinfo(&history2,
975 &has_rev_zero_history2,
976 loc2,
977 SVN_INVALID_REVNUM,
978 SVN_INVALID_REVNUM,
979 session, ctx, scratch_pool));
980 /* Close the ra session if we opened one. */
981 if (sesspool)
982 svn_pool_destroy(sesspool);
983
984 SVN_ERR(svn_client__calc_youngest_common_ancestor(ancestor_p,
985 loc1, history1,
986 has_rev_zero_history1,
987 loc2, history2,
988 has_rev_zero_history2,
989 result_pool,
990 scratch_pool));
991
992 return SVN_NO_ERROR;
993 }
994
995 svn_error_t *
svn_client__youngest_common_ancestor(const char ** ancestor_url,svn_revnum_t * ancestor_rev,const char * path_or_url1,const svn_opt_revision_t * revision1,const char * path_or_url2,const svn_opt_revision_t * revision2,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
1005 {
1006 apr_pool_t *sesspool = svn_pool_create(scratch_pool);
1007 svn_ra_session_t *session;
1008 svn_client__pathrev_t *loc1, *loc2, *ancestor;
1009
1010 /* Resolve the two locations */
1011 SVN_ERR(svn_client__ra_session_from_path2(&session, &loc1,
1012 path_or_url1, NULL,
1013 revision1, revision1,
1014 ctx, sesspool));
1015 SVN_ERR(svn_client__resolve_rev_and_url(&loc2, session,
1016 path_or_url2, revision2, revision2,
1017 ctx, scratch_pool));
1018
1019 SVN_ERR(svn_client__get_youngest_common_ancestor(
1020 &ancestor, loc1, loc2, session, ctx, result_pool, scratch_pool));
1021
1022 if (ancestor)
1023 {
1024 *ancestor_url = ancestor->url;
1025 *ancestor_rev = ancestor->rev;
1026 }
1027 else
1028 {
1029 *ancestor_url = NULL;
1030 *ancestor_rev = SVN_INVALID_REVNUM;
1031 }
1032 svn_pool_destroy(sesspool);
1033 return SVN_NO_ERROR;
1034 }
1035
1036
1037 struct ra_ev2_baton {
1038 /* The working copy context, from the client context. */
1039 svn_wc_context_t *wc_ctx;
1040
1041 /* For a given REPOS_RELPATH, provide a LOCAL_ABSPATH that represents
1042 that repository node. */
1043 apr_hash_t *relpath_map;
1044 };
1045
1046
1047 svn_error_t *
svn_client__ra_provide_base(svn_stream_t ** contents,svn_revnum_t * revision,void * baton,const char * repos_relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1048 svn_client__ra_provide_base(svn_stream_t **contents,
1049 svn_revnum_t *revision,
1050 void *baton,
1051 const char *repos_relpath,
1052 apr_pool_t *result_pool,
1053 apr_pool_t *scratch_pool)
1054 {
1055 struct ra_ev2_baton *reb = baton;
1056 const char *local_abspath;
1057 svn_error_t *err;
1058
1059 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1060 if (!local_abspath)
1061 {
1062 *contents = NULL;
1063 return SVN_NO_ERROR;
1064 }
1065
1066 err = svn_wc_get_pristine_contents2(contents, reb->wc_ctx, local_abspath,
1067 result_pool, scratch_pool);
1068 if (err)
1069 {
1070 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1071 return svn_error_trace(err);
1072
1073 svn_error_clear(err);
1074 *contents = NULL;
1075 return SVN_NO_ERROR;
1076 }
1077
1078 if (*contents != NULL)
1079 {
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));
1085 }
1086
1087 return SVN_NO_ERROR;
1088 }
1089
1090
1091 svn_error_t *
svn_client__ra_provide_props(apr_hash_t ** props,svn_revnum_t * revision,void * baton,const char * repos_relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1092 svn_client__ra_provide_props(apr_hash_t **props,
1093 svn_revnum_t *revision,
1094 void *baton,
1095 const char *repos_relpath,
1096 apr_pool_t *result_pool,
1097 apr_pool_t *scratch_pool)
1098 {
1099 struct ra_ev2_baton *reb = baton;
1100 const char *local_abspath;
1101 svn_error_t *err;
1102
1103 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1104 if (!local_abspath)
1105 {
1106 *props = NULL;
1107 return SVN_NO_ERROR;
1108 }
1109
1110 err = svn_wc_get_pristine_props(props, reb->wc_ctx, local_abspath,
1111 result_pool, scratch_pool);
1112 if (err)
1113 {
1114 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1115 return svn_error_trace(err);
1116
1117 svn_error_clear(err);
1118 *props = NULL;
1119 return SVN_NO_ERROR;
1120 }
1121
1122 if (*props != NULL)
1123 {
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));
1129 }
1130
1131 return SVN_NO_ERROR;
1132 }
1133
1134
1135 svn_error_t *
svn_client__ra_get_copysrc_kind(svn_node_kind_t * kind,void * baton,const char * repos_relpath,svn_revnum_t src_revision,apr_pool_t * scratch_pool)1136 svn_client__ra_get_copysrc_kind(svn_node_kind_t *kind,
1137 void *baton,
1138 const char *repos_relpath,
1139 svn_revnum_t src_revision,
1140 apr_pool_t *scratch_pool)
1141 {
1142 struct ra_ev2_baton *reb = baton;
1143 const char *local_abspath;
1144
1145 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1146 if (!local_abspath)
1147 {
1148 *kind = svn_node_unknown;
1149 return SVN_NO_ERROR;
1150 }
1151
1152 /* ### what to do with SRC_REVISION? */
1153
1154 SVN_ERR(svn_wc_read_kind2(kind, reb->wc_ctx, local_abspath,
1155 FALSE, FALSE, scratch_pool));
1156
1157 return SVN_NO_ERROR;
1158 }
1159
1160
1161 void *
svn_client__ra_make_cb_baton(svn_wc_context_t * wc_ctx,apr_hash_t * relpath_map,apr_pool_t * result_pool)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)
1165 {
1166 struct ra_ev2_baton *reb = apr_palloc(result_pool, sizeof(*reb));
1167
1168 SVN_ERR_ASSERT_NO_RETURN(wc_ctx != NULL);
1169 SVN_ERR_ASSERT_NO_RETURN(relpath_map != NULL);
1170
1171 reb->wc_ctx = wc_ctx;
1172 reb->relpath_map = relpath_map;
1173
1174 return reb;
1175 }
1176