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