]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/ra.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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 #include "private/svn_sorts_private.h"
46
47 \f
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 \f
80 static svn_error_t *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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
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 *
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, and the
406      RA->open() call above reveals a CORRECTED_URL, try the new URL.
407      We'll do this in a loop up to some maximum number follow-and-retry
408      attempts.  */
409   if (corrected_url)
410     {
411       apr_hash_t *attempted = apr_hash_make(scratch_pool);
412       int attempts_left = SVN_CLIENT__MAX_REDIRECT_ATTEMPTS;
413
414       *corrected_url = NULL;
415       while (attempts_left--)
416         {
417           const char *corrected = NULL;
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_open4(ra_session,
422                                attempts_left == 0 ? NULL : &corrected,
423                                base_url, uuid, cbtable, cb, ctx->config,
424                                result_pool));
425
426           /* No error and no corrected URL?  We're done here. */
427           if (! corrected)
428             break;
429
430           /* Notify the user that a redirect is being followed. */
431           if (ctx->notify_func2 != NULL)
432             {
433               svn_wc_notify_t *notify =
434                 svn_wc_create_notify_url(corrected,
435                                          svn_wc_notify_url_redirect,
436                                          scratch_pool);
437               ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
438             }
439
440           /* Our caller will want to know what our final corrected URL was. */
441           *corrected_url = corrected;
442
443           /* Make sure we've not attempted this URL before. */
444           if (svn_hash_gets(attempted, corrected))
445             return svn_error_createf(SVN_ERR_CLIENT_CYCLE_DETECTED, NULL,
446                                      _("Redirect cycle detected for URL '%s'"),
447                                      corrected);
448
449           /* Remember this CORRECTED_URL so we don't wind up in a loop. */
450           svn_hash_sets(attempted, corrected, (void *)1);
451           base_url = corrected;
452         }
453     }
454   else
455     {
456       SVN_ERR(svn_ra_open4(ra_session, NULL, base_url,
457                            uuid, cbtable, cb, ctx->config, result_pool));
458     }
459
460   return SVN_NO_ERROR;
461 }
462 #undef SVN_CLIENT__MAX_REDIRECT_ATTEMPTS
463
464
465 svn_error_t *
466 svn_client_open_ra_session2(svn_ra_session_t **session,
467                             const char *url,
468                             const char *wri_abspath,
469                             svn_client_ctx_t *ctx,
470                             apr_pool_t *result_pool,
471                             apr_pool_t *scratch_pool)
472 {
473   return svn_error_trace(
474              svn_client__open_ra_session_internal(session, NULL, url,
475                                                   wri_abspath, NULL,
476                                                   FALSE, FALSE,
477                                                   ctx, result_pool,
478                                                   scratch_pool));
479 }
480
481 svn_error_t *
482 svn_client__resolve_rev_and_url(svn_client__pathrev_t **resolved_loc_p,
483                                 svn_ra_session_t *ra_session,
484                                 const char *path_or_url,
485                                 const svn_opt_revision_t *peg_revision,
486                                 const svn_opt_revision_t *revision,
487                                 svn_client_ctx_t *ctx,
488                                 apr_pool_t *pool)
489 {
490   svn_opt_revision_t peg_rev = *peg_revision;
491   svn_opt_revision_t start_rev = *revision;
492   const char *url;
493   svn_revnum_t rev;
494
495   /* Default revisions: peg -> working or head; operative -> peg. */
496   SVN_ERR(svn_opt_resolve_revisions(&peg_rev, &start_rev,
497                                     svn_path_is_url(path_or_url),
498                                     TRUE /* notice_local_mods */,
499                                     pool));
500
501   /* Run the history function to get the object's (possibly
502      different) url in REVISION. */
503   SVN_ERR(svn_client__repos_locations(&url, &rev, NULL, NULL,
504                                       ra_session, path_or_url, &peg_rev,
505                                       &start_rev, NULL, ctx, pool));
506
507   SVN_ERR(svn_client__pathrev_create_with_session(resolved_loc_p,
508                                                   ra_session, rev, url, pool));
509   return SVN_NO_ERROR;
510 }
511
512 svn_error_t *
513 svn_client__ra_session_from_path2(svn_ra_session_t **ra_session_p,
514                                   svn_client__pathrev_t **resolved_loc_p,
515                                   const char *path_or_url,
516                                   const char *base_dir_abspath,
517                                   const svn_opt_revision_t *peg_revision,
518                                   const svn_opt_revision_t *revision,
519                                   svn_client_ctx_t *ctx,
520                                   apr_pool_t *pool)
521 {
522   svn_ra_session_t *ra_session;
523   const char *initial_url;
524   const char *corrected_url;
525   svn_client__pathrev_t *resolved_loc;
526   const char *wri_abspath;
527
528   SVN_ERR(svn_client_url_from_path2(&initial_url, path_or_url, ctx, pool,
529                                     pool));
530   if (! initial_url)
531     return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
532                              _("'%s' has no URL"), path_or_url);
533
534   if (base_dir_abspath)
535     wri_abspath = base_dir_abspath;
536   else if (!svn_path_is_url(path_or_url))
537     SVN_ERR(svn_dirent_get_absolute(&wri_abspath, path_or_url, pool));
538   else
539     wri_abspath = NULL;
540
541   SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
542                                                initial_url,
543                                                wri_abspath,
544                                                NULL /* commit_items */,
545                                                base_dir_abspath != NULL,
546                                                base_dir_abspath != NULL,
547                                                ctx, pool, pool));
548
549   /* If we got a CORRECTED_URL, we'll want to refer to that as the
550      URL-ized form of PATH_OR_URL from now on. */
551   if (corrected_url && svn_path_is_url(path_or_url))
552     path_or_url = corrected_url;
553
554   SVN_ERR(svn_client__resolve_rev_and_url(&resolved_loc, ra_session,
555                                           path_or_url, peg_revision, revision,
556                                           ctx, pool));
557
558   /* Make the session point to the real URL. */
559   SVN_ERR(svn_ra_reparent(ra_session, resolved_loc->url, pool));
560
561   *ra_session_p = ra_session;
562   if (resolved_loc_p)
563     *resolved_loc_p = resolved_loc;
564
565   return SVN_NO_ERROR;
566 }
567
568
569 svn_error_t *
570 svn_client__ensure_ra_session_url(const char **old_session_url,
571                                   svn_ra_session_t *ra_session,
572                                   const char *session_url,
573                                   apr_pool_t *pool)
574 {
575   SVN_ERR(svn_ra_get_session_url(ra_session, old_session_url, pool));
576   if (! session_url)
577     SVN_ERR(svn_ra_get_repos_root2(ra_session, &session_url, pool));
578   if (strcmp(*old_session_url, session_url) != 0)
579     SVN_ERR(svn_ra_reparent(ra_session, session_url, pool));
580   return SVN_NO_ERROR;
581 }
582
583
584 \f
585 /*** Repository Locations ***/
586
587 struct gls_receiver_baton_t
588 {
589   apr_array_header_t *segments;
590   svn_client_ctx_t *ctx;
591   apr_pool_t *pool;
592 };
593
594 static svn_error_t *
595 gls_receiver(svn_location_segment_t *segment,
596              void *baton,
597              apr_pool_t *pool)
598 {
599   struct gls_receiver_baton_t *b = baton;
600   APR_ARRAY_PUSH(b->segments, svn_location_segment_t *) =
601     svn_location_segment_dup(segment, b->pool);
602   if (b->ctx->cancel_func)
603     SVN_ERR((b->ctx->cancel_func)(b->ctx->cancel_baton));
604   return SVN_NO_ERROR;
605 }
606
607 /* A qsort-compatible function which sorts svn_location_segment_t's
608    based on their revision range covering, resulting in ascending
609    (oldest-to-youngest) ordering. */
610 static int
611 compare_segments(const void *a, const void *b)
612 {
613   const svn_location_segment_t *a_seg
614     = *((const svn_location_segment_t * const *) a);
615   const svn_location_segment_t *b_seg
616     = *((const svn_location_segment_t * const *) b);
617   if (a_seg->range_start == b_seg->range_start)
618     return 0;
619   return (a_seg->range_start < b_seg->range_start) ? -1 : 1;
620 }
621
622 svn_error_t *
623 svn_client__repos_location_segments(apr_array_header_t **segments,
624                                     svn_ra_session_t *ra_session,
625                                     const char *url,
626                                     svn_revnum_t peg_revision,
627                                     svn_revnum_t start_revision,
628                                     svn_revnum_t end_revision,
629                                     svn_client_ctx_t *ctx,
630                                     apr_pool_t *pool)
631 {
632   struct gls_receiver_baton_t gls_receiver_baton;
633   const char *old_session_url;
634   svn_error_t *err;
635
636   *segments = apr_array_make(pool, 8, sizeof(svn_location_segment_t *));
637   gls_receiver_baton.segments = *segments;
638   gls_receiver_baton.ctx = ctx;
639   gls_receiver_baton.pool = pool;
640   SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
641                                             url, pool));
642   err = svn_ra_get_location_segments(ra_session, "", peg_revision,
643                                      start_revision, end_revision,
644                                      gls_receiver, &gls_receiver_baton,
645                                      pool);
646   SVN_ERR(svn_error_compose_create(
647             err, svn_ra_reparent(ra_session, old_session_url, pool)));
648   svn_sort__array(*segments, compare_segments);
649   return SVN_NO_ERROR;
650 }
651
652 /* Set *START_URL and *END_URL to the URLs that the object URL@PEG_REVNUM
653  * had in revisions START_REVNUM and END_REVNUM.  Return an error if the
654  * node cannot be traced back to one of the requested revisions.
655  *
656  * START_URL and/or END_URL may be NULL if not wanted.  START_REVNUM and
657  * END_REVNUM must be valid revision numbers except that END_REVNUM may
658  * be SVN_INVALID_REVNUM if END_URL is NULL.
659  *
660  * YOUNGEST_REV is the already retrieved youngest revision of the ra session,
661  * but can be SVN_INVALID_REVNUM if the value is not already retrieved.
662  *
663  * RA_SESSION is an open RA session parented at URL.
664  */
665 static svn_error_t *
666 repos_locations(const char **start_url,
667                 const char **end_url,
668                 svn_ra_session_t *ra_session,
669                 const char *url,
670                 svn_revnum_t peg_revnum,
671                 svn_revnum_t start_revnum,
672                 svn_revnum_t end_revnum,
673                 svn_revnum_t youngest_rev,
674                 apr_pool_t *result_pool,
675                 apr_pool_t *scratch_pool)
676 {
677   const char *repos_url, *start_path, *end_path;
678   apr_array_header_t *revs;
679   apr_hash_t *rev_locs;
680
681   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(peg_revnum));
682   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(start_revnum));
683   SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(end_revnum) || end_url == NULL);
684
685   /* Avoid a network request in the common easy case. */
686   if (start_revnum == peg_revnum
687       && (end_revnum == peg_revnum || end_revnum == SVN_INVALID_REVNUM))
688     {
689       if (start_url)
690         *start_url = apr_pstrdup(result_pool, url);
691       if (end_url)
692         *end_url = apr_pstrdup(result_pool, url);
693       return SVN_NO_ERROR;
694     }
695
696   SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, scratch_pool));
697
698   /* Handle another common case: The repository root can't move */
699   if (! strcmp(repos_url, url))
700     {
701       if (! SVN_IS_VALID_REVNUM(youngest_rev))
702         SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest_rev,
703                                          scratch_pool));
704
705       if (start_revnum > youngest_rev
706           || (SVN_IS_VALID_REVNUM(end_revnum) && (end_revnum > youngest_rev)))
707         return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
708                                  _("No such revision %ld"),
709                                  (start_revnum > youngest_rev)
710                                         ? start_revnum : end_revnum);
711
712       if (start_url)
713         *start_url = apr_pstrdup(result_pool, repos_url);
714       if (end_url)
715         *end_url = apr_pstrdup(result_pool, repos_url);
716       return SVN_NO_ERROR;
717     }
718
719   revs = apr_array_make(scratch_pool, 2, sizeof(svn_revnum_t));
720   APR_ARRAY_PUSH(revs, svn_revnum_t) = start_revnum;
721   if (end_revnum != start_revnum && end_revnum != SVN_INVALID_REVNUM)
722     APR_ARRAY_PUSH(revs, svn_revnum_t) = end_revnum;
723
724   SVN_ERR(svn_ra_get_locations(ra_session, &rev_locs, "", peg_revnum,
725                                revs, scratch_pool));
726
727   /* We'd better have all the paths we were looking for! */
728   if (start_url)
729     {
730       start_path = apr_hash_get(rev_locs, &start_revnum, sizeof(svn_revnum_t));
731       if (! start_path)
732         return svn_error_createf
733           (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
734            _("Unable to find repository location for '%s' in revision %ld"),
735            url, start_revnum);
736       *start_url = svn_path_url_add_component2(repos_url, start_path + 1,
737                                                result_pool);
738     }
739
740   if (end_url)
741     {
742       end_path = apr_hash_get(rev_locs, &end_revnum, sizeof(svn_revnum_t));
743       if (! end_path)
744         return svn_error_createf
745           (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
746            _("The location for '%s' for revision %ld does not exist in the "
747              "repository or refers to an unrelated object"),
748            url, end_revnum);
749
750       *end_url = svn_path_url_add_component2(repos_url, end_path + 1,
751                                              result_pool);
752     }
753
754   return SVN_NO_ERROR;
755 }
756
757 svn_error_t *
758 svn_client__repos_location(svn_client__pathrev_t **op_loc_p,
759                            svn_ra_session_t *ra_session,
760                            const svn_client__pathrev_t *peg_loc,
761                            svn_revnum_t op_revnum,
762                            svn_client_ctx_t *ctx,
763                            apr_pool_t *result_pool,
764                            apr_pool_t *scratch_pool)
765 {
766   const char *old_session_url;
767   const char *op_url;
768   svn_error_t *err;
769
770   SVN_ERR(svn_client__ensure_ra_session_url(&old_session_url, ra_session,
771                                             peg_loc->url, scratch_pool));
772   err = repos_locations(&op_url, NULL, ra_session,
773                         peg_loc->url, peg_loc->rev,
774                         op_revnum, SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
775                         result_pool, scratch_pool);
776   SVN_ERR(svn_error_compose_create(
777             err, svn_ra_reparent(ra_session, old_session_url, scratch_pool)));
778
779   *op_loc_p = svn_client__pathrev_create(peg_loc->repos_root_url,
780                                          peg_loc->repos_uuid,
781                                          op_revnum, op_url, result_pool);
782   return SVN_NO_ERROR;
783 }
784
785 svn_error_t *
786 svn_client__repos_locations(const char **start_url,
787                             svn_revnum_t *start_revision,
788                             const char **end_url,
789                             svn_revnum_t *end_revision,
790                             svn_ra_session_t *ra_session,
791                             const char *path,
792                             const svn_opt_revision_t *revision,
793                             const svn_opt_revision_t *start,
794                             const svn_opt_revision_t *end,
795                             svn_client_ctx_t *ctx,
796                             apr_pool_t *pool)
797 {
798   const char *url;
799   const char *local_abspath_or_url;
800   svn_revnum_t peg_revnum = SVN_INVALID_REVNUM;
801   svn_revnum_t start_revnum, end_revnum;
802   svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
803   apr_pool_t *subpool = svn_pool_create(pool);
804
805   /* Ensure that we are given some real revision data to work with.
806      (It's okay if the END is unspecified -- in that case, we'll just
807      set it to the same thing as START.)  */
808   if (revision->kind == svn_opt_revision_unspecified
809       || start->kind == svn_opt_revision_unspecified)
810     return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, NULL);
811
812   if (end == NULL)
813     {
814       static const svn_opt_revision_t unspecified_rev
815         = { svn_opt_revision_unspecified, { 0 } };
816
817       end = &unspecified_rev;
818     }
819
820   /* Determine LOCAL_ABSPATH_OR_URL, URL, and possibly PEG_REVNUM.
821      If we are looking at the working version of a WC path that is scheduled
822      as a copy, then we need to use the copy-from URL and peg revision. */
823   if (! svn_path_is_url(path))
824     {
825       SVN_ERR(svn_dirent_get_absolute(&local_abspath_or_url, path, subpool));
826
827       if (revision->kind == svn_opt_revision_working)
828         {
829           const char *repos_root_url;
830           const char *repos_relpath;
831           svn_boolean_t is_copy;
832
833           SVN_ERR(svn_wc__node_get_origin(&is_copy, &peg_revnum, &repos_relpath,
834                                           &repos_root_url, NULL, NULL, NULL,
835                                           ctx->wc_ctx, local_abspath_or_url,
836                                           FALSE, subpool, subpool));
837
838           if (repos_relpath)
839             url = svn_path_url_add_component2(repos_root_url, repos_relpath,
840                                               pool);
841           else
842             url = NULL;
843
844           if (url && is_copy && ra_session)
845             {
846               const char *session_url;
847               SVN_ERR(svn_ra_get_session_url(ra_session, &session_url,
848                                              subpool));
849
850               if (strcmp(session_url, url) != 0)
851                 {
852                   /* We can't use the caller provided RA session now :( */
853                   ra_session = NULL;
854                 }
855             }
856         }
857       else
858         url = NULL;
859
860       if (! url)
861         SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx,
862                                      local_abspath_or_url, pool, subpool));
863
864       if (!url)
865         {
866           return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
867                                    _("'%s' has no URL"),
868                                    svn_dirent_local_style(path, pool));
869         }
870     }
871   else
872     {
873       local_abspath_or_url = path;
874       url = path;
875     }
876
877   /* ### We should be smarter here.  If the callers just asks for BASE and
878      WORKING revisions, we should already have the correct URLs, so we
879      don't need to do anything more here in that case. */
880
881   /* Open a RA session to this URL if we don't have one already. */
882   if (! ra_session)
883     SVN_ERR(svn_client_open_ra_session2(&ra_session, url, NULL,
884                                         ctx, subpool, subpool));
885
886   /* Resolve the opt_revision_ts. */
887   if (peg_revnum == SVN_INVALID_REVNUM)
888     SVN_ERR(svn_client__get_revision_number(&peg_revnum, &youngest_rev,
889                                             ctx->wc_ctx, local_abspath_or_url,
890                                             ra_session, revision, pool));
891
892   SVN_ERR(svn_client__get_revision_number(&start_revnum, &youngest_rev,
893                                           ctx->wc_ctx, local_abspath_or_url,
894                                           ra_session, start, pool));
895   if (end->kind == svn_opt_revision_unspecified)
896     end_revnum = start_revnum;
897   else
898     SVN_ERR(svn_client__get_revision_number(&end_revnum, &youngest_rev,
899                                             ctx->wc_ctx, local_abspath_or_url,
900                                             ra_session, end, pool));
901
902   /* Set the output revision variables. */
903   if (start_revision)
904     {
905       *start_revision = start_revnum;
906     }
907   if (end_revision && end->kind != svn_opt_revision_unspecified)
908     {
909       *end_revision = end_revnum;
910     }
911
912   SVN_ERR(repos_locations(start_url, end_url,
913                           ra_session, url, peg_revnum,
914                           start_revnum, end_revnum, youngest_rev,
915                           pool, subpool));
916   svn_pool_destroy(subpool);
917   return SVN_NO_ERROR;
918 }
919
920 svn_error_t *
921 svn_client__calc_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p,
922                                           const svn_client__pathrev_t *loc1,
923                                           apr_hash_t *history1,
924                                           svn_boolean_t has_rev_zero_history1,
925                                           const svn_client__pathrev_t *loc2,
926                                           apr_hash_t *history2,
927                                           svn_boolean_t has_rev_zero_history2,
928                                           apr_pool_t *result_pool,
929                                           apr_pool_t *scratch_pool)
930 {
931   apr_hash_index_t *hi;
932   svn_revnum_t yc_revision = SVN_INVALID_REVNUM;
933   const char *yc_relpath = NULL;
934
935   if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0)
936     {
937       *ancestor_p = NULL;
938       return SVN_NO_ERROR;
939     }
940
941   /* Loop through the first location's history, check for overlapping
942      paths and ranges in the second location's history, and
943      remembering the youngest matching location. */
944   for (hi = apr_hash_first(scratch_pool, history1); hi; hi = apr_hash_next(hi))
945     {
946       const char *path = apr_hash_this_key(hi);
947       apr_ssize_t path_len = apr_hash_this_key_len(hi);
948       svn_rangelist_t *ranges1 = apr_hash_this_val(hi);
949       svn_rangelist_t *ranges2, *common;
950
951       ranges2 = apr_hash_get(history2, path, path_len);
952       if (ranges2)
953         {
954           /* We have a path match.  Now, did our two histories share
955              any revisions at that path? */
956           SVN_ERR(svn_rangelist_intersect(&common, ranges1, ranges2,
957                                           TRUE, scratch_pool));
958           if (common->nelts)
959             {
960               svn_merge_range_t *yc_range =
961                 APR_ARRAY_IDX(common, common->nelts - 1, svn_merge_range_t *);
962               if ((! SVN_IS_VALID_REVNUM(yc_revision))
963                   || (yc_range->end > yc_revision))
964                 {
965                   yc_revision = yc_range->end;
966                   yc_relpath = path + 1;
967                 }
968             }
969         }
970     }
971
972   /* It's possible that PATH_OR_URL1 and PATH_OR_URL2's only common
973      history is revision 0. */
974   if (!yc_relpath && has_rev_zero_history1 && has_rev_zero_history2)
975     {
976       yc_relpath = "";
977       yc_revision = 0;
978     }
979
980   if (yc_relpath)
981     {
982       *ancestor_p = svn_client__pathrev_create_with_relpath(
983                       loc1->repos_root_url, loc1->repos_uuid,
984                       yc_revision, yc_relpath, result_pool);
985     }
986   else
987     {
988       *ancestor_p = NULL;
989     }
990   return SVN_NO_ERROR;
991 }
992
993 svn_error_t *
994 svn_client__get_youngest_common_ancestor(svn_client__pathrev_t **ancestor_p,
995                                          const svn_client__pathrev_t *loc1,
996                                          const svn_client__pathrev_t *loc2,
997                                          svn_ra_session_t *session,
998                                          svn_client_ctx_t *ctx,
999                                          apr_pool_t *result_pool,
1000                                          apr_pool_t *scratch_pool)
1001 {
1002   apr_pool_t *sesspool = NULL;
1003   apr_hash_t *history1, *history2;
1004   svn_boolean_t has_rev_zero_history1;
1005   svn_boolean_t has_rev_zero_history2;
1006
1007   if (strcmp(loc1->repos_root_url, loc2->repos_root_url) != 0)
1008     {
1009       *ancestor_p = NULL;
1010       return SVN_NO_ERROR;
1011     }
1012
1013   /* Open an RA session for the two locations. */
1014   if (session == NULL)
1015     {
1016       sesspool = svn_pool_create(scratch_pool);
1017       SVN_ERR(svn_client_open_ra_session2(&session, loc1->url, NULL, ctx,
1018                                           sesspool, sesspool));
1019     }
1020
1021   /* We're going to cheat and use history-as-mergeinfo because it
1022      saves us a bunch of annoying custom data comparisons and such. */
1023   SVN_ERR(svn_client__get_history_as_mergeinfo(&history1,
1024                                                &has_rev_zero_history1,
1025                                                loc1,
1026                                                SVN_INVALID_REVNUM,
1027                                                SVN_INVALID_REVNUM,
1028                                                session, ctx, scratch_pool));
1029   SVN_ERR(svn_client__get_history_as_mergeinfo(&history2,
1030                                                &has_rev_zero_history2,
1031                                                loc2,
1032                                                SVN_INVALID_REVNUM,
1033                                                SVN_INVALID_REVNUM,
1034                                                session, ctx, scratch_pool));
1035   /* Close the ra session if we opened one. */
1036   if (sesspool)
1037     svn_pool_destroy(sesspool);
1038
1039   SVN_ERR(svn_client__calc_youngest_common_ancestor(ancestor_p,
1040                                                     loc1, history1,
1041                                                     has_rev_zero_history1,
1042                                                     loc2, history2,
1043                                                     has_rev_zero_history2,
1044                                                     result_pool,
1045                                                     scratch_pool));
1046
1047   return SVN_NO_ERROR;
1048 }
1049
1050 struct ra_ev2_baton {
1051   /* The working copy context, from the client context.  */
1052   svn_wc_context_t *wc_ctx;
1053
1054   /* For a given REPOS_RELPATH, provide a LOCAL_ABSPATH that represents
1055      that repository node.  */
1056   apr_hash_t *relpath_map;
1057 };
1058
1059
1060 svn_error_t *
1061 svn_client__ra_provide_base(svn_stream_t **contents,
1062                             svn_revnum_t *revision,
1063                             void *baton,
1064                             const char *repos_relpath,
1065                             apr_pool_t *result_pool,
1066                             apr_pool_t *scratch_pool)
1067 {
1068   struct ra_ev2_baton *reb = baton;
1069   const char *local_abspath;
1070   svn_error_t *err;
1071
1072   local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1073   if (!local_abspath)
1074     {
1075       *contents = NULL;
1076       return SVN_NO_ERROR;
1077     }
1078
1079   err = svn_wc_get_pristine_contents2(contents, reb->wc_ctx, local_abspath,
1080                                       result_pool, scratch_pool);
1081   if (err)
1082     {
1083       if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1084         return svn_error_trace(err);
1085
1086       svn_error_clear(err);
1087       *contents = NULL;
1088       return SVN_NO_ERROR;
1089     }
1090
1091   if (*contents != NULL)
1092     {
1093       /* The pristine contents refer to the BASE, or to the pristine of
1094          a copy/move to this location. Fetch the correct revision.  */
1095       SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL,
1096                                       NULL,
1097                                       reb->wc_ctx, local_abspath, FALSE,
1098                                       scratch_pool, scratch_pool));
1099     }
1100
1101   return SVN_NO_ERROR;
1102 }
1103
1104
1105 svn_error_t *
1106 svn_client__ra_provide_props(apr_hash_t **props,
1107                              svn_revnum_t *revision,
1108                              void *baton,
1109                              const char *repos_relpath,
1110                              apr_pool_t *result_pool,
1111                              apr_pool_t *scratch_pool)
1112 {
1113   struct ra_ev2_baton *reb = baton;
1114   const char *local_abspath;
1115   svn_error_t *err;
1116
1117   local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1118   if (!local_abspath)
1119     {
1120       *props = NULL;
1121       return SVN_NO_ERROR;
1122     }
1123
1124   err = svn_wc_get_pristine_props(props, reb->wc_ctx, local_abspath,
1125                                   result_pool, scratch_pool);
1126   if (err)
1127     {
1128       if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1129         return svn_error_trace(err);
1130
1131       svn_error_clear(err);
1132       *props = NULL;
1133       return SVN_NO_ERROR;
1134     }
1135
1136   if (*props != NULL)
1137     {
1138       /* The pristine props refer to the BASE, or to the pristine props of
1139          a copy/move to this location. Fetch the correct revision.  */
1140       SVN_ERR(svn_wc__node_get_origin(NULL, revision, NULL, NULL, NULL, NULL,
1141                                       NULL,
1142                                       reb->wc_ctx, local_abspath, FALSE,
1143                                       scratch_pool, scratch_pool));
1144     }
1145
1146   return SVN_NO_ERROR;
1147 }
1148
1149
1150 svn_error_t *
1151 svn_client__ra_get_copysrc_kind(svn_node_kind_t *kind,
1152                                 void *baton,
1153                                 const char *repos_relpath,
1154                                 svn_revnum_t src_revision,
1155                                 apr_pool_t *scratch_pool)
1156 {
1157   struct ra_ev2_baton *reb = baton;
1158   const char *local_abspath;
1159
1160   local_abspath = svn_hash_gets(reb->relpath_map, repos_relpath);
1161   if (!local_abspath)
1162     {
1163       *kind = svn_node_unknown;
1164       return SVN_NO_ERROR;
1165     }
1166
1167   /* ### what to do with SRC_REVISION?  */
1168
1169   SVN_ERR(svn_wc_read_kind2(kind, reb->wc_ctx, local_abspath,
1170                             FALSE, FALSE, scratch_pool));
1171
1172   return SVN_NO_ERROR;
1173 }
1174
1175
1176 void *
1177 svn_client__ra_make_cb_baton(svn_wc_context_t *wc_ctx,
1178                              apr_hash_t *relpath_map,
1179                              apr_pool_t *result_pool)
1180 {
1181   struct ra_ev2_baton *reb = apr_palloc(result_pool, sizeof(*reb));
1182
1183   SVN_ERR_ASSERT_NO_RETURN(wc_ctx != NULL);
1184   SVN_ERR_ASSERT_NO_RETURN(relpath_map != NULL);
1185
1186   reb->wc_ctx = wc_ctx;
1187   reb->relpath_map = relpath_map;
1188
1189   return reb;
1190 }