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 #include "private/svn_sorts_private.h"
46
47
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
51 {
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;
56
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;
63
64 /* Used as wri_abspath for obtaining access to the pristine store */
65 const char *wcroot_abspath;
66
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;
70
71 /* A client context. */
72 svn_client_ctx_t *ctx;
73
74 /* Last progress reported by progress callback. */
75 apr_off_t last_progress;
76 } callback_baton_t;
77
78
79
80 static svn_error_t *
open_tmp_file(apr_file_t ** fp,void * callback_baton,apr_pool_t * pool)81 open_tmp_file(apr_file_t **fp,
82 void *callback_baton,
83 apr_pool_t *pool)
84 {
85 return svn_error_trace(svn_io_open_unique_file3(fp, NULL, NULL,
86 svn_io_file_del_on_pool_cleanup,
87 pool, pool));
88 }
89
90
91 /* This implements the 'svn_ra_get_wc_prop_func_t' interface. */
92 static svn_error_t *
get_wc_prop(void * baton,const char * relpath,const char * name,const svn_string_t ** value,apr_pool_t * pool)93 get_wc_prop(void *baton,
94 const char *relpath,
95 const char *name,
96 const svn_string_t **value,
97 apr_pool_t *pool)
98 {
99 callback_baton_t *cb = baton;
100 const char *local_abspath = NULL;
101 svn_error_t *err;
102
103 *value = NULL;
104
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)
108 {
109 int i;
110 for (i = 0; i < cb->commit_items->nelts; i++)
111 {
112 svn_client_commit_item3_t *item
113 = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
114
115 if (! strcmp(relpath, item->session_relpath))
116 {
117 SVN_ERR_ASSERT(svn_dirent_is_absolute(item->path));
118 local_abspath = item->path;
119 break;
120 }
121 }
122
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
126 working copy. */
127 if (! local_abspath)
128 return SVN_NO_ERROR;
129 }
130
131 /* If we don't have a base directory, then there are no properties. */
132 else if (cb->base_dir_abspath == NULL)
133 return SVN_NO_ERROR;
134
135 else
136 local_abspath = svn_dirent_join(cb->base_dir_abspath, relpath, pool);
137
138 err = svn_wc_prop_get2(value, cb->ctx->wc_ctx, local_abspath, name,
139 pool, pool);
140 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
141 {
142 svn_error_clear(err);
143 err = NULL;
144 }
145 return svn_error_trace(err);
146 }
147
148 /* This implements the 'svn_ra_push_wc_prop_func_t' interface. */
149 static svn_error_t *
push_wc_prop(void * baton,const char * relpath,const char * name,const svn_string_t * value,apr_pool_t * pool)150 push_wc_prop(void *baton,
151 const char *relpath,
152 const char *name,
153 const svn_string_t *value,
154 apr_pool_t *pool)
155 {
156 callback_baton_t *cb = baton;
157 int i;
158
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));
166
167 for (i = 0; i < cb->commit_items->nelts; i++)
168 {
169 svn_client_commit_item3_t *item
170 = APR_ARRAY_IDX(cb->commit_items, i, svn_client_commit_item3_t *);
171
172 if (strcmp(relpath, item->session_relpath) == 0)
173 {
174 apr_pool_t *changes_pool = item->incoming_prop_changes->pool;
175 svn_prop_t *prop = apr_palloc(changes_pool, sizeof(*prop));
176
177 prop->name = apr_pstrdup(changes_pool, name);
178 if (value)
179 prop->value = svn_string_dup(value, changes_pool);
180 else
181 prop->value = NULL;
182
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;
186 return SVN_NO_ERROR;
187 }
188 }
189
190 return SVN_NO_ERROR;
191 }
192
193
194 /* This implements the 'svn_ra_set_wc_prop_func_t' interface. */
195 static svn_error_t *
set_wc_prop(void * baton,const char * path,const char * name,const svn_string_t * value,apr_pool_t * pool)196 set_wc_prop(void *baton,
197 const char *path,
198 const char *name,
199 const svn_string_t *value,
200 apr_pool_t *pool)
201 {
202 callback_baton_t *cb = baton;
203 const char *local_abspath;
204
205 local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool);
206
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,
216 name,
217 value, svn_depth_empty,
218 TRUE /* skip_checks */,
219 NULL /* changelist_filter */,
220 NULL, NULL /* cancellation */,
221 NULL, NULL /* notification */,
222 pool));
223 }
224
225
226 /* This implements the `svn_ra_invalidate_wc_props_func_t' interface. */
227 static svn_error_t *
invalidate_wc_props(void * baton,const char * path,const char * prop_name,apr_pool_t * pool)228 invalidate_wc_props(void *baton,
229 const char *path,
230 const char *prop_name,
231 apr_pool_t *pool)
232 {
233 callback_baton_t *cb = baton;
234 const char *local_abspath;
235
236 local_abspath = svn_dirent_join(cb->base_dir_abspath, path, pool);
237
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));
248 }
249
250
251 /* This implements the `svn_ra_get_wc_contents_func_t' interface. */
252 static svn_error_t *
get_wc_contents(void * baton,svn_stream_t ** contents,const svn_checksum_t * checksum,apr_pool_t * pool)253 get_wc_contents(void *baton,
254 svn_stream_t **contents,
255 const svn_checksum_t *checksum,
256 apr_pool_t *pool)
257 {
258 callback_baton_t *cb = baton;
259
260 if (! cb->wcroot_abspath)
261 {
262 *contents = NULL;
263 return SVN_NO_ERROR;
264 }
265
266 return svn_error_trace(
267 svn_wc__get_pristine_contents_by_checksum(contents,
268 cb->ctx->wc_ctx,
269 cb->wcroot_abspath,
270 checksum,
271 pool, pool));
272 }
273
274
275 static svn_error_t *
cancel_callback(void * baton)276 cancel_callback(void *baton)
277 {
278 callback_baton_t *b = baton;
279 return svn_error_trace((b->ctx->cancel_func)(b->ctx->cancel_baton));
280 }
281
282
283 static svn_error_t *
get_client_string(void * baton,const char ** name,apr_pool_t * pool)284 get_client_string(void *baton,
285 const char **name,
286 apr_pool_t *pool)
287 {
288 callback_baton_t *b = baton;
289 *name = apr_pstrdup(pool, b->ctx->client_name);
290 return SVN_NO_ERROR;
291 }
292
293 /* Implements svn_ra_progress_notify_func_t. Accumulates progress information
294 * for different RA sessions and reports total progress to caller. */
295 static void
progress_func(apr_off_t progress,apr_off_t total,void * baton,apr_pool_t * pool)296 progress_func(apr_off_t progress,
297 apr_off_t total,
298 void *baton,
299 apr_pool_t *pool)
300 {
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);
305
306 private_ctx->total_progress += (progress - b->last_progress);
307 b->last_progress = progress;
308
309 if (public_ctx->progress_func)
310 {
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
313 RA sessions. */
314 public_ctx->progress_func(private_ctx->total_progress, -1,
315 public_ctx->progress_baton, pool);
316 }
317 }
318
319 #define SVN_CLIENT__MAX_REDIRECT_ATTEMPTS 3 /* ### TODO: Make configurable. */
320
321 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)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)
332 {
333 svn_ra_callbacks2_t *cbtable;
334 callback_baton_t *cb = apr_pcalloc(result_pool, sizeof(*cb));
335 const char *uuid = NULL;
336
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));
341
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;
360
361 cb->commit_items = commit_items;
362 cb->ctx = ctx;
363
364 if (base_dir_abspath && (read_dav_props || write_dav_props))
365 {
366 svn_error_t *err = svn_wc__node_get_repos_info(NULL, NULL, NULL, &uuid,
367 ctx->wc_ctx,
368 base_dir_abspath,
369 result_pool,
370 scratch_pool);
371
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))
375 {
376 svn_error_clear(err);
377 uuid = NULL;
378 }
379 else
380 {
381 SVN_ERR(err);
382 cb->base_dir_isversioned = TRUE;
383 }
384 cb->base_dir_abspath = apr_pstrdup(result_pool, base_dir_abspath);
385 }
386
387 if (base_dir_abspath)
388 {
389 svn_error_t *err = svn_wc__get_wcroot(&cb->wcroot_abspath,
390 ctx->wc_ctx, base_dir_abspath,
391 result_pool, scratch_pool);
392
393 if (err)
394 {
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);
399
400 svn_error_clear(err);
401 cb->wcroot_abspath = NULL;
402 }
403 }
404
405 /* If the caller allows for auto-following redirections, try the new URL.
406 We'll do this in a loop up to some maximum number follow-and-retry
407 attempts. */
408 if (corrected_url)
409 {
410 apr_hash_t *attempted = apr_hash_make(scratch_pool);
411 int attempts_left = SVN_CLIENT__MAX_REDIRECT_ATTEMPTS;
412
413 *corrected_url = NULL;
414 while (attempts_left--)
415 {
416 const char *corrected = NULL; /* canonicalized version */
417 const char *redirect_url = NULL; /* non-canonicalized version */
418
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_open5(ra_session,
422 attempts_left == 0 ? NULL : &corrected,
423 attempts_left == 0 ? NULL : &redirect_url,
424 base_url, uuid, cbtable, cb, ctx->config,
425 result_pool));
426
427 /* No error and no corrected URL? We're done here. */
428 if (! corrected)
429 break;
430
431 /* Notify the user that a redirect is being followed. */
432 if (ctx->notify_func2 != NULL)
433 {
434 svn_wc_notify_t *notify =
435 svn_wc_create_notify_url(corrected,
436 svn_wc_notify_url_redirect,
437 scratch_pool);
438 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
439 }
440
441 /* Our caller will want to know what our final corrected URL was. */
442 *corrected_url = corrected;
443
444 /* Make sure we've not attempted this URL before. */
445 if (svn_hash_gets(attempted, redirect_url))
446 return svn_error_createf(SVN_ERR_CLIENT_CYCLE_DETECTED, NULL,
447 _("Redirect cycle detected for URL '%s'"),
448 redirect_url);
449
450 /*
451 * Remember this redirect URL so we don't wind up in a loop.
452 *
453 * Store the non-canonicalized version of the URL. The canonicalized
454 * version is insufficient for loop detection because we might not get
455 * an exact match against URLs used by the RA protocol-layer (the URL
456 * used by the protocol may contain trailing slashes, for example,
457 * which are stripped during canonicalization).
458 */
459 svn_hash_sets(attempted, redirect_url, (void *)1);
460
461 base_url = corrected;
462 }
463 }
464 else
465 {
466 SVN_ERR(svn_ra_open5(ra_session, NULL, NULL, base_url,
467 uuid, cbtable, cb, ctx->config, result_pool));
468 }
469
470 return SVN_NO_ERROR;
471 }
472 #undef SVN_CLIENT__MAX_REDIRECT_ATTEMPTS
473
474
475 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)476 svn_client_open_ra_session2(svn_ra_session_t **session,
477 const char *url,
478 const char *wri_abspath,
479 svn_client_ctx_t *ctx,
480 apr_pool_t *result_pool,
481 apr_pool_t *scratch_pool)
482 {
483 return svn_error_trace(
484 svn_client__open_ra_session_internal(session, NULL, url,
485 wri_abspath, NULL,
486 FALSE, FALSE,
487 ctx, result_pool,
488 scratch_pool));
489 }
490
491 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)492 svn_client__resolve_rev_and_url(svn_client__pathrev_t **resolved_loc_p,
493 svn_ra_session_t *ra_session,
494 const char *path_or_url,
495 const svn_opt_revision_t *peg_revision,
496 const svn_opt_revision_t *revision,
497 svn_client_ctx_t *ctx,
498 apr_pool_t *pool)
499 {
500 svn_opt_revision_t peg_rev = *peg_revision;
501 svn_opt_revision_t start_rev = *revision;
502 const char *url;
503 svn_revnum_t rev;
504
505 /* Default revisions: peg -> working or head; operative -> peg. */
506 SVN_ERR(svn_opt_resolve_revisions(&peg_rev, &start_rev,
507 svn_path_is_url(path_or_url),
508 TRUE /* notice_local_mods */,
509 pool));
510
511 /* Run the history function to get the object's (possibly
512 different) url in REVISION. */
513 SVN_ERR(svn_client__repos_locations(&url, &rev, NULL, NULL,
514 ra_session, path_or_url, &peg_rev,
515 &start_rev, NULL, ctx, pool));
516
517 SVN_ERR(svn_client__pathrev_create_with_session(resolved_loc_p,
518 ra_session, rev, url, pool));
519 return SVN_NO_ERROR;
520 }
521
522 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)523 svn_client__ra_session_from_path2(svn_ra_session_t **ra_session_p,
524 svn_client__pathrev_t **resolved_loc_p,
525 const char *path_or_url,
526 const char *base_dir_abspath,
527 const svn_opt_revision_t *peg_revision,
528 const svn_opt_revision_t *revision,
529 svn_client_ctx_t *ctx,
530 apr_pool_t *pool)
531 {
532 svn_ra_session_t *ra_session;
533 const char *initial_url;
534 const char *corrected_url;
535 svn_client__pathrev_t *resolved_loc;
536 const char *wri_abspath;
537
538 SVN_ERR(svn_client_url_from_path2(&initial_url, path_or_url, ctx, pool,
539 pool));
540 if (! initial_url)
541 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
542 _("'%s' has no URL"), path_or_url);
543
544 if (base_dir_abspath)
545 wri_abspath = base_dir_abspath;
546 else if (!svn_path_is_url(path_or_url))
547 SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url, pool));
548 else
549 wri_abspath = NULL;
550
551 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
552 initial_url,
553 wri_abspath,
554 NULL /* commit_items */,
555 base_dir_abspath != NULL,
556 base_dir_abspath != NULL,
557 ctx, pool, pool));
558
559 /* If we got a CORRECTED_URL, we'll want to refer to that as the
560 URL-ized form of PATH_OR_URL from now on. */
561 if (corrected_url && svn_path_is_url(path_or_url))
562 path_or_url = corrected_url;
563
564 SVN_ERR(svn_client__resolve_rev_and_url(&resolved_loc, ra_session,
565 path_or_url, peg_revision, revision,
566 ctx, pool));
567
568 /* Make the session point to the real URL. */
569 SVN_ERR(svn_ra_reparent(ra_session, resolved_loc->url, pool));
570
571 *ra_session_p = ra_session;
572 if (resolved_loc_p)
573 *resolved_loc_p = resolved_loc;
574
575 return SVN_NO_ERROR;
576 }
577
578
579 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)580 svn_client__ensure_ra_session_url(const char **old_session_url,
581 svn_ra_session_t *ra_session,
582 const char *session_url,
583 apr_pool_t *pool)
584 {
585 SVN_ERR(svn_ra_get_session_url(ra_session, old_session_url, pool));
586 if (! session_url)
587 SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_url, pool));
588 if (strcmp(*old_session_url, session_url) != 0)
589 SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
590 return SVN_NO_ERROR;
591 }
592
593
594
595 /*** Repository Locations ***/
596
597 struct gls_receiver_baton_t
598 {
599 apr_array_header_t *segments;
600 svn_client_ctx_t *ctx;
601 apr_pool_t *pool;
602 };
603
604 static svn_error_t *
gls_receiver(svn_location_segment_t * segment,void * baton,apr_pool_t * pool)605 gls_receiver(svn_location_segment_t *segment,
606 void *baton,
607 apr_pool_t *pool)
608 {
609 struct gls_receiver_baton_t *b = baton;
610 APR_ARRAY_PUSH(b->segments, svn_location_segment_t *) =
611 svn_location_segment_dup(segment, b->pool);
612 if (b->ctx->cancel_func)
613 SVN_ERR((b->ctx->cancel_func)(b->ctx->cancel_baton));
614 return SVN_NO_ERROR;
615 }
616
617 /* A qsort-compatible function which sorts svn_location_segment_t's
618 based on their revision range covering, resulting in ascending
619 (oldest-to-youngest) ordering. */
620 static int
compare_segments(const void * a,const void * b)621 compare_segments(const void *a, const void *b)
622 {
623 const svn_location_segment_t *a_seg
624 = *((const svn_location_segment_t * const *) a);
625 const svn_location_segment_t *b_seg
626 = *((const svn_location_segment_t * const *) b);
627 if (a_seg->range_start == b_seg->range_start)
628 return 0;
629 return (a_seg->range_start < b_seg->range_start) ? -1 : 1;
630 }
631
632 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)633 svn_client__repos_location_segments(apr_array_header_t **segments,
634 svn_ra_session_t *ra_session,
635 const char *url,
636 svn_revnum_t peg_revision,
637 svn_revnum_t start_revision,
638 svn_revnum_t end_revision,
639 svn_client_ctx_t *ctx,
640 apr_pool_t *pool)
641 {
642 struct gls_receiver_baton_t gls_receiver_baton;
643 const char *old_session_url;
644 svn_error_t *err;
645
646 *segments = apr_array_make(pool, 8, sizeof(svn_location_segment_t *));
647 gls_receiver_baton.segments = *segments;
648 gls_receiver_baton.ctx = ctx;
649 gls_receiver_baton.pool = pool;
650 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
651 url, pool));
652 err = svn_ra_get_location_segments(ra_session, "", peg_revision,
653 start_revision, end_revision,
654 gls_receiver, &gls_receiver_baton,
655 pool);
656 SVN_ERR(svn_error_compose_create(
657 err, svn_ra_reparent(ra_session, old_session_url, pool)));
658 svn_sort__array(*segments, compare_segments);
659 return SVN_NO_ERROR;
660 }
661
662 /* Set *START_URL and *END_URL to the URLs that the object URL@PEG_REVNUM
663 * had in revisions START_REVNUM and END_REVNUM. Return an error if the
664 * node cannot be traced back to one of the requested revisions.
665 *
666 * START_URL and/or END_URL may be NULL if not wanted. START_REVNUM and
667 * END_REVNUM must be valid revision numbers except that END_REVNUM may
668 * be SVN_INVALID_REVNUM if END_URL is NULL.
669 *
670 * YOUNGEST_REV is the already retrieved youngest revision of the ra session,
671 * but can be SVN_INVALID_REVNUM if the value is not already retrieved.
672 *
673 * RA_SESSION is an open RA session parented at URL.
674 */
675 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,svn_revnum_t youngest_rev,apr_pool_t * result_pool,apr_pool_t * scratch_pool)676 repos_locations(const char **start_url,
677 const char **end_url,
678 svn_ra_session_t *ra_session,
679 const char *url,
680 svn_revnum_t peg_revnum,
681 svn_revnum_t start_revnum,
682 svn_revnum_t end_revnum,
683 svn_revnum_t youngest_rev,
684 apr_pool_t *result_pool,
685 apr_pool_t *scratch_pool)
686 {
687 const char *repos_url, *start_path, *end_path;
688 apr_array_header_t *revs;
689 apr_hash_t *rev_locs;
690
691 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(peg_revnum));
692 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_revnum));
693 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_revnum) || end_url == NULL);
694
695 /* Avoid a network request in the common easy case. */
696 if (start_revnum == peg_revnum
697 && (end_revnum == peg_revnum || end_revnum == SVN_INVALID_REVNUM))
698 {
699 if (start_url)
700 *start_url = apr_pstrdup(result_pool, url);
701 if (end_url)
702 *end_url = apr_pstrdup(result_pool, url);
703 return SVN_NO_ERROR;
704 }
705
706 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, scratch_pool));
707
708 /* Handle another common case: The repository root can't move */
709 if (! strcmp(repos_url, url))
710 {
711 if (! SVN_IS_VALID_REVNUM(youngest_rev))
712 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest_rev,
713 scratch_pool));
714
715 if (start_revnum > youngest_rev
716 || (SVN_IS_VALID_REVNUM(end_revnum) && (end_revnum > youngest_rev)))
717 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
718 _("No such revision %ld"),
719 (start_revnum > youngest_rev)
720 ? start_revnum : end_revnum);
721
722 if (start_url)
723 *start_url = apr_pstrdup(result_pool, repos_url);
724 if (end_url)
725 *end_url = apr_pstrdup(result_pool, repos_url);
726 return SVN_NO_ERROR;
727 }
728
729 revs = apr_array_make(scratch_pool, 2, sizeof(svn_revnum_t));
730 APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum;
731 if (end_revnum != start_revnum && end_revnum != SVN_INVALID_REVNUM)
732 APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum;
733
734 SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum,
735 revs, scratch_pool));
736
737 /* We'd better have all the paths we were looking for! */
738 if (start_url)
739 {
740 start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(start_revnum));
741 if (! start_path)
742 return svn_error_createf
743 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
744 _("Unable to find repository location for '%s' in revision %ld"),
745 url, start_revnum);
746 *start_url = svn_path_url_add_component2(repos_url, start_path + 1,
747 result_pool);
748 }
749
750 if (end_url)
751 {
752 end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(end_revnum));
753 if (! end_path)
754 return svn_error_createf
755 (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
756 _("The location for '%s' for revision %ld does not exist in the "
757 "repository or refers to an unrelated object"),
758 url, end_revnum);
759
760 *end_url = svn_path_url_add_component2(repos_url, end_path + 1,
761 result_pool);
762 }
763
764 return SVN_NO_ERROR;
765 }
766
767 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)768 svn_client__repos_location(svn_client__pathrev_t **op_loc_p,
769 svn_ra_session_t *ra_session,
770 const svn_client__pathrev_t *peg_loc,
771 svn_revnum_t op_revnum,
772 svn_client_ctx_t *ctx,
773 apr_pool_t *result_pool,
774 apr_pool_t *scratch_pool)
775 {
776 const char *old_session_url;
777 const char *op_url;
778 svn_error_t *err;
779
780 SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
781 peg_loc->url, scratch_pool));
782 err = repos_locations(&op_url, NULL, ra_session,
783 peg_loc->url, peg_loc->rev,
784 op_revnum, SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
785 result_pool, scratch_pool);
786 SVN_ERR(svn_error_compose_create(
787 err, svn_ra_reparent(ra_session, old_session_url, scratch_pool)));
788
789 *op_loc_p = svn_client__pathrev_create(peg_loc->repos_root_url,
790 peg_loc->repos_uuid,
791 op_revnum, op_url, result_pool);
792 return SVN_NO_ERROR;
793 }
794
795 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)796 svn_client__repos_locations(const char **start_url,
797 svn_revnum_t *start_revision,
798 const char **end_url,
799 svn_revnum_t *end_revision,
800 svn_ra_session_t *ra_session,
801 const char *path,
802 const svn_opt_revision_t *revision,
803 const svn_opt_revision_t *start,
804 const svn_opt_revision_t *end,
805 svn_client_ctx_t *ctx,
806 apr_pool_t *pool)
807 {
808 const char *url;
809 const char *local_abspath_or_url;
810 svn_revnum_t peg_revnum = SVN_INVALID_REVNUM;
811 svn_revnum_t start_revnum, end_revnum;
812 svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
813 apr_pool_t *subpool = svn_pool_create(pool);
814
815 /* Ensure that we are given some real revision data to work with.
816 (It's okay if the END is unspecified -- in that case, we'll just
817 set it to the same thing as START.) */
818 if (revision->kind == svn_opt_revision_unspecified
819 || start->kind == svn_opt_revision_unspecified)
820 return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
821
822 if (end == NULL)
823 {
824 static const svn_opt_revision_t unspecified_rev
825 = { svn_opt_revision_unspecified, { 0 } };
826
827 end = &unspecified_rev;
828 }
829
830 /* Determine LOCAL_ABSPATH_OR_URL, URL, and possibly PEG_REVNUM.
831 If we are looking at the working version of a WC path that is scheduled
832 as a copy, then we need to use the copy-from URL and peg revision. */
833 if (! svn_path_is_url(path))
834 {
835 SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, subpool));
836
837 if (revision->kind == svn_opt_revision_working)
838 {
839 const char *repos_root_url;
840 const char *repos_relpath;
841 svn_boolean_t is_copy;
842
843 SVN_ERR(svn_wc__node_get_origin(&is_copy, &peg_revnum, &repos_relpath,
844 &repos_root_url, NULL, NULL, NULL,
845 ctx->wc_ctx, local_abspath_or_url,
846 FALSE, subpool, subpool));
847
848 if (repos_relpath)
849 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
850 pool);
851 else
852 url = NULL;
853
854 if (url && is_copy && ra_session)
855 {
856 const char *session_url;
857 SVN_ERR(svn_ra_get_session_url(ra_session, &session_url,
858 subpool));
859
860 if (strcmp(session_url, url) != 0)
861 {
862 /* We can't use the caller provided RA session now :( */
863 ra_session = NULL;
864 }
865 }
866 }
867 else
868 url = NULL;
869
870 if (! url)
871 SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx,
872 local_abspath_or_url, pool, subpool));
873
874 if (!url)
875 {
876 return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
877 _("'%s' has no URL"),
878 svn_dirent_local_style(path, pool));
879 }
880 }
881 else
882 {
883 local_abspath_or_url = path;
884 url = path;
885 }
886
887 /* ### We should be smarter here. If the callers just asks for BASE and
888 WORKING revisions, we should already have the correct URLs, so we
889 don't need to do anything more here in that case. */
890
891 /* Open a RA session to this URL if we don't have one already. */
892 if (! ra_session)
893 SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
894 ctx, subpool, subpool));
895
896 /* Resolve the opt_revision_ts. */
897 if (peg_revnum == SVN_INVALID_REVNUM)
898 SVN_ERR(svn_client__get_revision_number(&peg_revnum, &youngest_rev,
899 ctx->wc_ctx, local_abspath_or_url,
900 ra_session, revision, pool));
901
902 SVN_ERR(svn_client__get_revision_number(&start_revnum, &youngest_rev,
903 ctx->wc_ctx, local_abspath_or_url,
904 ra_session, start, pool));
905 if (end->kind == svn_opt_revision_unspecified)
906 end_revnum = start_revnum;
907 else
908 SVN_ERR(svn_client__get_revision_number(&end_revnum, &youngest_rev,
909 ctx->wc_ctx, local_abspath_or_url,
910 ra_session, end, pool));
911
912 /* Set the output revision variables. */
913 if (start_revision)
914 {
915 *start_revision = start_revnum;
916 }
917 if (end_revision && end->kind != svn_opt_revision_unspecified)
918 {
919 *end_revision = end_revnum;
920 }
921
922 SVN_ERR(repos_locations(start_url, end_url,
923 ra_session, url, peg_revnum,
924 start_revnum, end_revnum, youngest_rev,
925 pool, subpool));
926 svn_pool_destroy(subpool);
927 return SVN_NO_ERROR;
928 }
929
930 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)931 svn_client__calc_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p,
932 const svn_client__pathrev_t *loc1,
933 apr_hash_t *history1,
934 svn_boolean_t has_rev_zero_history1,
935 const svn_client__pathrev_t *loc2,
936 apr_hash_t *history2,
937 svn_boolean_t has_rev_zero_history2,
938 apr_pool_t *result_pool,
939 apr_pool_t *scratch_pool)
940 {
941 apr_hash_index_t *hi;
942 svn_revnum_t yc_revision = SVN_INVALID_REVNUM;
943 const char *yc_relpath = NULL;
944
945 if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0)
946 {
947 *ancestor_p = NULL;
948 return SVN_NO_ERROR;
949 }
950
951 /* Loop through the first location's history, check for overlapping
952 paths and ranges in the second location's history, and
953 remembering the youngest matching location. */
954 for (hi = apr_hash_first(scratch_pool, history1); hi; hi = apr_hash_next(hi))
955 {
956 const char *path = apr_hash_this_key(hi);
957 apr_ssize_t path_len = apr_hash_this_key_len(hi);
958 svn_rangelist_t *ranges1 = apr_hash_this_val(hi);
959 svn_rangelist_t *ranges2, *common;
960
961 ranges2 = apr_hash_get(history2, path, path_len);
962 if (ranges2)
963 {
964 /* We have a path match. Now, did our two histories share
965 any revisions at that path? */
966 SVN_ERR(svn_rangelist_intersect(&common, ranges1, ranges2,
967 TRUE, scratch_pool));
968 if (common->nelts)
969 {
970 svn_merge_range_t *yc_range =
971 APR_ARRAY_IDX(common, common->nelts - 1, svn_merge_range_t *);
972 if ((! SVN_IS_VALID_REVNUM(yc_revision))
973 || (yc_range->end > yc_revision))
974 {
975 yc_revision = yc_range->end;
976 yc_relpath = path + 1;
977 }
978 }
979 }
980 }
981
982 /* It's possible that PATH_OR_URL1 and PATH_OR_URL2's only common
983 history is revision 0. */
984 if (!yc_relpath && has_rev_zero_history1 && has_rev_zero_history2)
985 {
986 yc_relpath = "";
987 yc_revision = 0;
988 }
989
990 if (yc_relpath)
991 {
992 *ancestor_p = svn_client__pathrev_create_with_relpath(
993 loc1->repos_root_url, loc1->repos_uuid,
994 yc_revision, yc_relpath, result_pool);
995 }
996 else
997 {
998 *ancestor_p = NULL;
999 }
1000 return SVN_NO_ERROR;
1001 }
1002
1003 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)1004 svn_client__get_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p,
1005 const svn_client__pathrev_t *loc1,
1006 const svn_client__pathrev_t *loc2,
1007 svn_ra_session_t *session,
1008 svn_client_ctx_t *ctx,
1009 apr_pool_t *result_pool,
1010 apr_pool_t *scratch_pool)
1011 {
1012 apr_pool_t *sesspool = NULL;
1013 apr_hash_t *history1, *history2;
1014 svn_boolean_t has_rev_zero_history1;
1015 svn_boolean_t has_rev_zero_history2;
1016
1017 if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0)
1018 {
1019 *ancestor_p = NULL;
1020 return SVN_NO_ERROR;
1021 }
1022
1023 /* Open an RA session for the two locations. */
1024 if (session == NULL)
1025 {
1026 sesspool = svn_pool_create(scratch_pool);
1027 SVN_ERR(svn_client_open_ra_session2(&session, loc1->url, NULL, ctx,
1028 sesspool, sesspool));
1029 }
1030
1031 /* We're going to cheat and use history-as-mergeinfo because it
1032 saves us a bunch of annoying custom data comparisons and such. */
1033 SVN_ERR(svn_client__get_history_as_mergeinfo(&history1,
1034 &has_rev_zero_history1,
1035 loc1,
1036 SVN_INVALID_REVNUM,
1037 SVN_INVALID_REVNUM,
1038 session, ctx, scratch_pool));
1039 SVN_ERR(svn_client__get_history_as_mergeinfo(&history2,
1040 &has_rev_zero_history2,
1041 loc2,
1042 SVN_INVALID_REVNUM,
1043 SVN_INVALID_REVNUM,
1044 session, ctx, scratch_pool));
1045 /* Close the ra session if we opened one. */
1046 if (sesspool)
1047 svn_pool_destroy(sesspool);
1048
1049 SVN_ERR(svn_client__calc_youngest_common_ancestor(ancestor_p,
1050 loc1, history1,
1051 has_rev_zero_history1,
1052 loc2, history2,
1053 has_rev_zero_history2,
1054 result_pool,
1055 scratch_pool));
1056
1057 return SVN_NO_ERROR;
1058 }
1059
1060 struct ra_ev2_baton {
1061 /* The working copy context, from the client context. */
1062 svn_wc_context_t *wc_ctx;
1063
1064 /* For a given REPOS_RELPATH, provide a LOCAL_ABSPATH that represents
1065 that repository node. */
1066 apr_hash_t *relpath_map;
1067 };
1068
1069
1070 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)1071 svn_client__ra_provide_base(svn_stream_t **contents,
1072 svn_revnum_t *revision,
1073 void *baton,
1074 const char *repos_relpath,
1075 apr_pool_t *result_pool,
1076 apr_pool_t *scratch_pool)
1077 {
1078 struct ra_ev2_baton *reb = baton;
1079 const char *local_abspath;
1080 svn_error_t *err;
1081
1082 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1083 if (!local_abspath)
1084 {
1085 *contents = NULL;
1086 return SVN_NO_ERROR;
1087 }
1088
1089 err = svn_wc_get_pristine_contents2(contents, reb->wc_ctx, local_abspath,
1090 result_pool, scratch_pool);
1091 if (err)
1092 {
1093 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1094 return svn_error_trace(err);
1095
1096 svn_error_clear(err);
1097 *contents = NULL;
1098 return SVN_NO_ERROR;
1099 }
1100
1101 if (*contents != NULL)
1102 {
1103 /* The pristine contents refer to the BASE, or to the pristine of
1104 a copy/move to this location. Fetch the correct revision. */
1105 SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL,
1106 NULL,
1107 reb->wc_ctx, local_abspath, FALSE,
1108 scratch_pool, scratch_pool));
1109 }
1110
1111 return SVN_NO_ERROR;
1112 }
1113
1114
1115 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)1116 svn_client__ra_provide_props(apr_hash_t **props,
1117 svn_revnum_t *revision,
1118 void *baton,
1119 const char *repos_relpath,
1120 apr_pool_t *result_pool,
1121 apr_pool_t *scratch_pool)
1122 {
1123 struct ra_ev2_baton *reb = baton;
1124 const char *local_abspath;
1125 svn_error_t *err;
1126
1127 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1128 if (!local_abspath)
1129 {
1130 *props = NULL;
1131 return SVN_NO_ERROR;
1132 }
1133
1134 err = svn_wc_get_pristine_props(props, reb->wc_ctx, local_abspath,
1135 result_pool, scratch_pool);
1136 if (err)
1137 {
1138 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1139 return svn_error_trace(err);
1140
1141 svn_error_clear(err);
1142 *props = NULL;
1143 return SVN_NO_ERROR;
1144 }
1145
1146 if (*props != NULL)
1147 {
1148 /* The pristine props refer to the BASE, or to the pristine props of
1149 a copy/move to this location. Fetch the correct revision. */
1150 SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL,
1151 NULL,
1152 reb->wc_ctx, local_abspath, FALSE,
1153 scratch_pool, scratch_pool));
1154 }
1155
1156 return SVN_NO_ERROR;
1157 }
1158
1159
1160 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)1161 svn_client__ra_get_copysrc_kind(svn_node_kind_t *kind,
1162 void *baton,
1163 const char *repos_relpath,
1164 svn_revnum_t src_revision,
1165 apr_pool_t *scratch_pool)
1166 {
1167 struct ra_ev2_baton *reb = baton;
1168 const char *local_abspath;
1169
1170 local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1171 if (!local_abspath)
1172 {
1173 *kind = svn_node_unknown;
1174 return SVN_NO_ERROR;
1175 }
1176
1177 /* ### what to do with SRC_REVISION? */
1178
1179 SVN_ERR(svn_wc_read_kind2(kind, reb->wc_ctx, local_abspath,
1180 FALSE, FALSE, scratch_pool));
1181
1182 return SVN_NO_ERROR;
1183 }
1184
1185
1186 void *
svn_client__ra_make_cb_baton(svn_wc_context_t * wc_ctx,apr_hash_t * relpath_map,apr_pool_t * result_pool)1187 svn_client__ra_make_cb_baton(svn_wc_context_t *wc_ctx,
1188 apr_hash_t *relpath_map,
1189 apr_pool_t *result_pool)
1190 {
1191 struct ra_ev2_baton *reb = apr_palloc(result_pool, sizeof(*reb));
1192
1193 SVN_ERR_ASSERT_NO_RETURN(wc_ctx != NULL);
1194 SVN_ERR_ASSERT_NO_RETURN(relpath_map != NULL);
1195
1196 reb->wc_ctx = wc_ctx;
1197 reb->relpath_map = relpath_map;
1198
1199 return reb;
1200 }
1201