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