ra.c revision 299742
1/*
2 * ra.c :  routines for interacting with the RA layer
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24
25
26#include <apr_pools.h>
27
28#include "svn_error.h"
29#include "svn_hash.h"
30#include "svn_pools.h"
31#include "svn_string.h"
32#include "svn_sorts.h"
33#include "svn_ra.h"
34#include "svn_client.h"
35#include "svn_dirent_uri.h"
36#include "svn_path.h"
37#include "svn_props.h"
38#include "svn_mergeinfo.h"
39#include "client.h"
40#include "mergeinfo.h"
41
42#include "svn_private_config.h"
43#include "private/svn_wc_private.h"
44#include "private/svn_client_private.h"
45#include "private/svn_sorts_private.h"
46
47
48/* This is the baton that we pass svn_ra_open3(), and is associated with
49   the callback table we provide to RA. */
50typedef 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
80static svn_error_t *
81open_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. */
92static svn_error_t *
93get_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. */
149static svn_error_t *
150push_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. */
195static svn_error_t *
196set_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. */
227static svn_error_t *
228invalidate_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. */
252static svn_error_t *
253get_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
275static svn_error_t *
276cancel_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
283static svn_error_t *
284get_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. */
295static void
296progress_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
321svn_error_t *
322svn_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
465svn_error_t *
466svn_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
481svn_error_t *
482svn_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
512svn_error_t *
513svn_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
569svn_error_t *
570svn_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
585/*** Repository Locations ***/
586
587struct gls_receiver_baton_t
588{
589  apr_array_header_t *segments;
590  svn_client_ctx_t *ctx;
591  apr_pool_t *pool;
592};
593
594static svn_error_t *
595gls_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. */
610static int
611compare_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
622svn_error_t *
623svn_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 */
665static svn_error_t *
666repos_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
757svn_error_t *
758svn_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
785svn_error_t *
786svn_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
920svn_error_t *
921svn_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
993svn_error_t *
994svn_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
1050struct 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
1060svn_error_t *
1061svn_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
1105svn_error_t *
1106svn_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
1150svn_error_t *
1151svn_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
1176void *
1177svn_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}
1191