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