1/*
2 * ra_plugin.c : the main RA module for local repository access
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#include "ra_local.h"
25#include "svn_hash.h"
26#include "svn_ra.h"
27#include "svn_fs.h"
28#include "svn_delta.h"
29#include "svn_repos.h"
30#include "svn_pools.h"
31#include "svn_time.h"
32#include "svn_props.h"
33#include "svn_mergeinfo.h"
34#include "svn_path.h"
35#include "svn_version.h"
36#include "svn_cache_config.h"
37
38#include "svn_private_config.h"
39#include "../libsvn_ra/ra_loader.h"
40#include "private/svn_mergeinfo_private.h"
41#include "private/svn_repos_private.h"
42#include "private/svn_fspath.h"
43#include "private/svn_atomic.h"
44#include "private/svn_subr_private.h"
45
46#define APR_WANT_STRFUNC
47#include <apr_want.h>
48
49/*----------------------------------------------------------------*/
50
51/*** Miscellaneous helper functions ***/
52
53
54/* Pool cleanup handler: ensure that the access descriptor of the
55   filesystem (svn_fs_t *) DATA is set to NULL. */
56static apr_status_t
57cleanup_access(void *data)
58{
59  svn_error_t *serr;
60  svn_fs_t *fs = data;
61
62  serr = svn_fs_set_access(fs, NULL);
63
64  if (serr)
65    {
66      apr_status_t apr_err = serr->apr_err;
67      svn_error_clear(serr);
68      return apr_err;
69    }
70
71  return APR_SUCCESS;
72}
73
74
75/* Fetch a username for use with SESSION, and store it in SESSION->username.
76 *
77 * Allocate the username in SESSION->pool.  Use SCRATCH_POOL for temporary
78 * allocations. */
79static svn_error_t *
80get_username(svn_ra_session_t *session,
81             apr_pool_t *scratch_pool)
82{
83  svn_ra_local__session_baton_t *sess = session->priv;
84
85  /* If we've already found the username don't ask for it again. */
86  if (! sess->username)
87    {
88      /* Get a username somehow, so we have some svn:author property to
89         attach to a commit. */
90      if (sess->auth_baton)
91        {
92          void *creds;
93          svn_auth_cred_username_t *username_creds;
94          svn_auth_iterstate_t *iterstate;
95
96          SVN_ERR(svn_auth_first_credentials(&creds, &iterstate,
97                                             SVN_AUTH_CRED_USERNAME,
98                                             sess->uuid, /* realmstring */
99                                             sess->auth_baton,
100                                             scratch_pool));
101
102          /* No point in calling next_creds(), since that assumes that the
103             first_creds() somehow failed to authenticate.  But there's no
104             challenge going on, so we use whatever creds we get back on
105             the first try. */
106          username_creds = creds;
107          if (username_creds && username_creds->username)
108            {
109              sess->username = apr_pstrdup(session->pool,
110                                           username_creds->username);
111              svn_error_clear(svn_auth_save_credentials(iterstate,
112                                                        scratch_pool));
113            }
114          else
115            sess->username = "";
116        }
117      else
118        sess->username = "";
119    }
120
121  /* If we have a real username, attach it to the filesystem so that it can
122     be used to validate locks.  Even if there already is a user context
123     associated, it may contain irrelevant lock tokens, so always create a new.
124  */
125  if (*sess->username)
126    {
127      svn_fs_access_t *access_ctx;
128
129      SVN_ERR(svn_fs_create_access(&access_ctx, sess->username,
130                                   session->pool));
131      SVN_ERR(svn_fs_set_access(sess->fs, access_ctx));
132
133      /* Make sure this context is disassociated when the pool gets
134         destroyed. */
135      apr_pool_cleanup_register(session->pool, sess->fs, cleanup_access,
136                                apr_pool_cleanup_null);
137    }
138
139  return SVN_NO_ERROR;
140}
141
142/* Implements an svn_atomic__init_once callback.  Sets the FSFS memory
143   cache size. */
144static svn_error_t *
145cache_init(void *baton, apr_pool_t *pool)
146{
147  apr_hash_t *config_hash = baton;
148  svn_config_t *config = NULL;
149  const char *memory_cache_size_str;
150
151  if (config_hash)
152    config = svn_hash_gets(config_hash, SVN_CONFIG_CATEGORY_CONFIG);
153  svn_config_get(config, &memory_cache_size_str, SVN_CONFIG_SECTION_MISCELLANY,
154                 SVN_CONFIG_OPTION_MEMORY_CACHE_SIZE, NULL);
155  if (memory_cache_size_str)
156    {
157      apr_uint64_t memory_cache_size;
158      svn_cache_config_t settings = *svn_cache_config_get();
159
160      SVN_ERR(svn_error_quick_wrap(svn_cstring_atoui64(&memory_cache_size,
161                                                       memory_cache_size_str),
162                                   _("memory-cache-size invalid")));
163      settings.cache_size = 1024 * 1024 * memory_cache_size;
164      svn_cache_config_set(&settings);
165    }
166
167  return SVN_NO_ERROR;
168}
169
170/*----------------------------------------------------------------*/
171
172/*** The reporter vtable needed by do_update() and friends ***/
173
174typedef struct reporter_baton_t
175{
176  svn_ra_local__session_baton_t *sess;
177  void *report_baton;
178
179} reporter_baton_t;
180
181
182static void *
183make_reporter_baton(svn_ra_local__session_baton_t *sess,
184                    void *report_baton,
185                    apr_pool_t *pool)
186{
187  reporter_baton_t *rbaton = apr_palloc(pool, sizeof(*rbaton));
188  rbaton->sess = sess;
189  rbaton->report_baton = report_baton;
190  return rbaton;
191}
192
193
194static svn_error_t *
195reporter_set_path(void *reporter_baton,
196                  const char *path,
197                  svn_revnum_t revision,
198                  svn_depth_t depth,
199                  svn_boolean_t start_empty,
200                  const char *lock_token,
201                  apr_pool_t *pool)
202{
203  reporter_baton_t *rbaton = reporter_baton;
204  return svn_repos_set_path3(rbaton->report_baton, path,
205                             revision, depth, start_empty, lock_token, pool);
206}
207
208
209static svn_error_t *
210reporter_delete_path(void *reporter_baton,
211                     const char *path,
212                     apr_pool_t *pool)
213{
214  reporter_baton_t *rbaton = reporter_baton;
215  return svn_repos_delete_path(rbaton->report_baton, path, pool);
216}
217
218
219static svn_error_t *
220reporter_link_path(void *reporter_baton,
221                   const char *path,
222                   const char *url,
223                   svn_revnum_t revision,
224                   svn_depth_t depth,
225                   svn_boolean_t start_empty,
226                   const char *lock_token,
227                   apr_pool_t *pool)
228{
229  reporter_baton_t *rbaton = reporter_baton;
230  const char *repos_url = rbaton->sess->repos_url;
231  const char *relpath = svn_uri_skip_ancestor(repos_url, url, pool);
232  const char *fs_path;
233
234  if (!relpath)
235    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
236                             _("'%s'\n"
237                               "is not the same repository as\n"
238                               "'%s'"), url, rbaton->sess->repos_url);
239
240  /* Convert the relpath to an fspath */
241  if (relpath[0] == '\0')
242    fs_path = "/";
243  else
244    fs_path = apr_pstrcat(pool, "/", relpath, SVN_VA_NULL);
245
246  return svn_repos_link_path3(rbaton->report_baton, path, fs_path, revision,
247                              depth, start_empty, lock_token, pool);
248}
249
250
251static svn_error_t *
252reporter_finish_report(void *reporter_baton,
253                       apr_pool_t *pool)
254{
255  reporter_baton_t *rbaton = reporter_baton;
256  return svn_repos_finish_report(rbaton->report_baton, pool);
257}
258
259
260static svn_error_t *
261reporter_abort_report(void *reporter_baton,
262                      apr_pool_t *pool)
263{
264  reporter_baton_t *rbaton = reporter_baton;
265  return svn_repos_abort_report(rbaton->report_baton, pool);
266}
267
268
269static const svn_ra_reporter3_t ra_local_reporter =
270{
271  reporter_set_path,
272  reporter_delete_path,
273  reporter_link_path,
274  reporter_finish_report,
275  reporter_abort_report
276};
277
278
279/* ...
280 *
281 * Wrap a cancellation editor using SESSION's cancellation function around
282 * the supplied EDITOR.  ### Some callers (via svn_ra_do_update2() etc.)
283 * don't appear to know that we do this, and are supplying an editor that
284 * they have already wrapped with the same cancellation editor, so it ends
285 * up double-wrapped.
286 *
287 * Allocate @a *reporter and @a *report_baton in @a result_pool.  Use
288 * @a scratch_pool for temporary allocations.
289 */
290static svn_error_t *
291make_reporter(svn_ra_session_t *session,
292              const svn_ra_reporter3_t **reporter,
293              void **report_baton,
294              svn_revnum_t revision,
295              const char *target,
296              const char *other_url,
297              svn_boolean_t text_deltas,
298              svn_depth_t depth,
299              svn_boolean_t send_copyfrom_args,
300              svn_boolean_t ignore_ancestry,
301              const svn_delta_editor_t *editor,
302              void *edit_baton,
303              apr_pool_t *result_pool,
304              apr_pool_t *scratch_pool)
305{
306  svn_ra_local__session_baton_t *sess = session->priv;
307  void *rbaton;
308  const char *other_fs_path = NULL;
309
310  /* Get the HEAD revision if one is not supplied. */
311  if (! SVN_IS_VALID_REVNUM(revision))
312    SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, scratch_pool));
313
314  /* If OTHER_URL was provided, validate it and convert it into a
315     regular filesystem path. */
316  if (other_url)
317    {
318      const char *other_relpath
319        = svn_uri_skip_ancestor(sess->repos_url, other_url, scratch_pool);
320
321      /* Sanity check:  the other_url better be in the same repository as
322         the original session url! */
323      if (! other_relpath)
324        return svn_error_createf
325          (SVN_ERR_RA_ILLEGAL_URL, NULL,
326           _("'%s'\n"
327             "is not the same repository as\n"
328             "'%s'"), other_url, sess->repos_url);
329
330      other_fs_path = apr_pstrcat(scratch_pool, "/", other_relpath,
331                                  SVN_VA_NULL);
332    }
333
334  /* Pass back our reporter */
335  *reporter = &ra_local_reporter;
336
337  SVN_ERR(get_username(session, scratch_pool));
338
339  if (sess->callbacks)
340    SVN_ERR(svn_delta_get_cancellation_editor(sess->callbacks->cancel_func,
341                                              sess->callback_baton,
342                                              editor,
343                                              edit_baton,
344                                              &editor,
345                                              &edit_baton,
346                                              result_pool));
347
348  /* Build a reporter baton. */
349  SVN_ERR(svn_repos_begin_report3(&rbaton,
350                                  revision,
351                                  sess->repos,
352                                  sess->fs_path->data,
353                                  target,
354                                  other_fs_path,
355                                  text_deltas,
356                                  depth,
357                                  ignore_ancestry,
358                                  send_copyfrom_args,
359                                  editor,
360                                  edit_baton,
361                                  NULL,
362                                  NULL,
363                                  0, /* Disable zero-copy codepath, because
364                                        RA API users are unaware about the
365                                        zero-copy code path limitation (do
366                                        not access FSFS data structures
367                                        and, hence, caches).  See notes
368                                        to svn_repos_begin_report3() for
369                                        additional details. */
370                                  result_pool));
371
372  /* Wrap the report baton given us by the repos layer with our own
373     reporter baton. */
374  *report_baton = make_reporter_baton(sess, rbaton, result_pool);
375
376  return SVN_NO_ERROR;
377}
378
379
380/*----------------------------------------------------------------*/
381
382/*** Deltification stuff for get_commit_editor() ***/
383
384struct deltify_etc_baton
385{
386  svn_fs_t *fs;                     /* the fs to deltify in */
387  svn_repos_t *repos;               /* repos for unlocking */
388  const char *fspath_base;          /* fs-path part of split session URL */
389
390  apr_hash_t *lock_tokens;          /* tokens to unlock, if any */
391
392  svn_commit_callback2_t commit_cb; /* the original callback */
393  void *commit_baton;               /* the original callback's baton */
394};
395
396/* This implements 'svn_commit_callback_t'.  Its invokes the original
397   (wrapped) callback, but also does deltification on the new revision and
398   possibly unlocks committed paths.
399   BATON is 'struct deltify_etc_baton *'. */
400static svn_error_t *
401deltify_etc(const svn_commit_info_t *commit_info,
402            void *baton,
403            apr_pool_t *scratch_pool)
404{
405  struct deltify_etc_baton *deb = baton;
406  svn_error_t *err1 = SVN_NO_ERROR;
407  svn_error_t *err2;
408
409  /* Invoke the original callback first, in case someone's waiting to
410     know the revision number so they can go off and annotate an
411     issue or something. */
412  if (deb->commit_cb)
413    err1 = deb->commit_cb(commit_info, deb->commit_baton, scratch_pool);
414
415  /* Maybe unlock the paths. */
416  if (deb->lock_tokens)
417    {
418      apr_pool_t *subpool = svn_pool_create(scratch_pool);
419      apr_hash_t *targets = apr_hash_make(subpool);
420      apr_hash_index_t *hi;
421
422      for (hi = apr_hash_first(subpool, deb->lock_tokens); hi;
423           hi = apr_hash_next(hi))
424        {
425          const void *relpath = apr_hash_this_key(hi);
426          const char *token = apr_hash_this_val(hi);
427          const char *fspath;
428
429          fspath = svn_fspath__join(deb->fspath_base, relpath, subpool);
430          svn_hash_sets(targets, fspath, token);
431        }
432
433      /* We may get errors here if the lock was broken or stolen
434         after the commit succeeded.  This is fine and should be
435         ignored. */
436      svn_error_clear(svn_repos_fs_unlock_many(deb->repos, targets, FALSE,
437                                               NULL, NULL,
438                                               subpool, subpool));
439
440      svn_pool_destroy(subpool);
441    }
442
443  /* But, deltification shouldn't be stopped just because someone's
444     random callback failed, so proceed unconditionally on to
445     deltification. */
446  err2 = svn_fs_deltify_revision(deb->fs, commit_info->revision, scratch_pool);
447
448  return svn_error_compose_create(err1, err2);
449}
450
451
452/* If LOCK_TOKENS is not NULL, then copy all tokens into the access context
453   of FS. The tokens' paths will be prepended with FSPATH_BASE.
454
455   ACCESS_POOL must match (or exceed) the lifetime of the access context
456   that was associated with FS. Typically, this is the session pool.
457
458   Temporary allocations are made in SCRATCH_POOL.  */
459static svn_error_t *
460apply_lock_tokens(svn_fs_t *fs,
461                  const char *fspath_base,
462                  apr_hash_t *lock_tokens,
463                  apr_pool_t *access_pool,
464                  apr_pool_t *scratch_pool)
465{
466  if (lock_tokens)
467    {
468      svn_fs_access_t *access_ctx;
469
470      SVN_ERR(svn_fs_get_access(&access_ctx, fs));
471
472      /* If there is no access context, the filesystem will scream if a
473         lock is needed.  */
474      if (access_ctx)
475        {
476          apr_hash_index_t *hi;
477
478          /* Note: we have no use for an iterpool here since the data
479             within the loop is copied into ACCESS_POOL.  */
480
481          for (hi = apr_hash_first(scratch_pool, lock_tokens); hi;
482               hi = apr_hash_next(hi))
483            {
484              const void *relpath = apr_hash_this_key(hi);
485              const char *token = apr_hash_this_val(hi);
486              const char *fspath;
487
488              /* The path needs to live as long as ACCESS_CTX.  */
489              fspath = svn_fspath__join(fspath_base, relpath, access_pool);
490
491              /* The token must live as long as ACCESS_CTX.  */
492              token = apr_pstrdup(access_pool, token);
493
494              SVN_ERR(svn_fs_access_add_lock_token2(access_ctx, fspath,
495                                                    token));
496            }
497        }
498    }
499
500  return SVN_NO_ERROR;
501}
502
503
504/*----------------------------------------------------------------*/
505
506/*** The RA vtable routines ***/
507
508#define RA_LOCAL_DESCRIPTION \
509        N_("Module for accessing a repository on local disk.")
510
511static const char *
512svn_ra_local__get_description(apr_pool_t *pool)
513{
514  return _(RA_LOCAL_DESCRIPTION);
515}
516
517static const char * const *
518svn_ra_local__get_schemes(apr_pool_t *pool)
519{
520  static const char *schemes[] = { "file", NULL };
521
522  return schemes;
523}
524
525/* Do nothing.
526 *
527 * Why is this acceptable?  FS warnings used to be used for only
528 * two things: failures to close BDB repositories and failures to
529 * interact with memcached in FSFS (new in 1.6).  In 1.5 and earlier,
530 * we did not call svn_fs_set_warning_func in ra_local, which means
531 * that any BDB-closing failure would have led to abort()s; the fact
532 * that this hasn't led to huge hues and cries makes it seem likely
533 * that this just doesn't happen that often, at least not through
534 * ra_local.  And as far as memcached goes, it seems unlikely that
535 * somebody is going to go through the trouble of setting up and
536 * running memcached servers but then use ra_local access.  So we
537 * ignore errors here, so that memcached can use the FS warnings API
538 * without crashing ra_local.
539 */
540static void
541ignore_warnings(void *baton,
542                svn_error_t *err)
543{
544#ifdef SVN_DEBUG
545  SVN_DBG(("Ignoring FS warning %s\n",
546           svn_error_symbolic_name(err ? err->apr_err : 0)));
547#endif
548  return;
549}
550
551#define USER_AGENT "SVN/" SVN_VER_NUMBER " (" SVN_BUILD_TARGET ")" \
552                   " ra_local"
553
554static svn_error_t *
555svn_ra_local__open(svn_ra_session_t *session,
556                   const char **corrected_url,
557                   const char *repos_URL,
558                   const svn_ra_callbacks2_t *callbacks,
559                   void *callback_baton,
560                   svn_auth_baton_t *auth_baton,
561                   apr_hash_t *config,
562                   apr_pool_t *result_pool,
563                   apr_pool_t *scratch_pool)
564{
565  const char *client_string;
566  svn_ra_local__session_baton_t *sess;
567  const char *fs_path;
568  static volatile svn_atomic_t cache_init_state = 0;
569  apr_pool_t *pool = result_pool;
570
571  /* Initialise the FSFS memory cache size.  We can only do this once
572     so one CONFIG will win the race and all others will be ignored
573     silently.  */
574  SVN_ERR(svn_atomic__init_once(&cache_init_state, cache_init, config, pool));
575
576  /* We don't support redirections in ra-local. */
577  if (corrected_url)
578    *corrected_url = NULL;
579
580  /* Allocate and stash the session_sess args we have already. */
581  sess = apr_pcalloc(pool, sizeof(*sess));
582  sess->callbacks = callbacks;
583  sess->callback_baton = callback_baton;
584  sess->auth_baton = auth_baton;
585
586  /* Look through the URL, figure out which part points to the
587     repository, and which part is the path *within* the
588     repository. */
589  SVN_ERR(svn_ra_local__split_URL(&(sess->repos),
590                                  &(sess->repos_url),
591                                  &fs_path,
592                                  repos_URL,
593                                  session->pool));
594  sess->fs_path = svn_stringbuf_create(fs_path, session->pool);
595
596  /* Cache the filesystem object from the repos here for
597     convenience. */
598  sess->fs = svn_repos_fs(sess->repos);
599
600  /* Ignore FS warnings. */
601  svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL);
602
603  /* Cache the repository UUID as well */
604  SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));
605
606  /* Be sure username is NULL so we know to look it up / ask for it */
607  sess->username = NULL;
608
609  if (sess->callbacks->get_client_string != NULL)
610    SVN_ERR(sess->callbacks->get_client_string(sess->callback_baton,
611                                               &client_string, pool));
612  else
613    client_string = NULL;
614
615  if (client_string)
616    sess->useragent = apr_pstrcat(pool, USER_AGENT " ",
617                                  client_string, SVN_VA_NULL);
618  else
619    sess->useragent = USER_AGENT;
620
621  session->priv = sess;
622  return SVN_NO_ERROR;
623}
624
625static svn_error_t *
626svn_ra_local__dup_session(svn_ra_session_t *new_session,
627                          svn_ra_session_t *session,
628                          const char *new_session_url,
629                          apr_pool_t *result_pool,
630                          apr_pool_t *scratch_pool)
631{
632  svn_ra_local__session_baton_t *old_sess = session->priv;
633  svn_ra_local__session_baton_t *new_sess;
634  const char *fs_path;
635
636  /* Allocate and stash the session_sess args we have already. */
637  new_sess = apr_pcalloc(result_pool, sizeof(*new_sess));
638  new_sess->callbacks = old_sess->callbacks;
639  new_sess->callback_baton = old_sess->callback_baton;
640
641  /* ### Re-use existing FS handle? */
642
643  /* Reuse existing code */
644  SVN_ERR(svn_ra_local__split_URL(&(new_sess->repos),
645                                  &(new_sess->repos_url),
646                                  &fs_path,
647                                  new_session_url,
648                                  result_pool));
649
650  new_sess->fs_path = svn_stringbuf_create(fs_path, result_pool);
651
652  /* Cache the filesystem object from the repos here for
653     convenience. */
654  new_sess->fs = svn_repos_fs(new_sess->repos);
655
656  /* Ignore FS warnings. */
657  svn_fs_set_warning_func(new_sess->fs, ignore_warnings, NULL);
658
659  /* Cache the repository UUID as well */
660  new_sess->uuid = apr_pstrdup(result_pool, old_sess->uuid);
661
662  new_sess->username = old_sess->username
663                            ? apr_pstrdup(result_pool, old_sess->username)
664                            : NULL;
665
666  new_sess->useragent = apr_pstrdup(result_pool, old_sess->useragent);
667  new_session->priv = new_sess;
668
669  return SVN_NO_ERROR;
670}
671
672static svn_error_t *
673svn_ra_local__reparent(svn_ra_session_t *session,
674                       const char *url,
675                       apr_pool_t *pool)
676{
677  svn_ra_local__session_baton_t *sess = session->priv;
678  const char *relpath = svn_uri_skip_ancestor(sess->repos_url, url, pool);
679
680  /* If the new URL isn't the same as our repository root URL, then
681     let's ensure that it's some child of it. */
682  if (! relpath)
683    return svn_error_createf
684      (SVN_ERR_RA_ILLEGAL_URL, NULL,
685       _("URL '%s' is not a child of the session's repository root "
686         "URL '%s'"), url, sess->repos_url);
687
688  /* Update our FS_PATH sess member to point to our new
689     relative-URL-turned-absolute-filesystem-path. */
690  svn_stringbuf_set(sess->fs_path,
691                    svn_fspath__canonicalize(relpath, pool));
692
693  return SVN_NO_ERROR;
694}
695
696static svn_error_t *
697svn_ra_local__get_session_url(svn_ra_session_t *session,
698                              const char **url,
699                              apr_pool_t *pool)
700{
701  svn_ra_local__session_baton_t *sess = session->priv;
702  *url = svn_path_url_add_component2(sess->repos_url,
703                                     sess->fs_path->data + 1,
704                                     pool);
705  return SVN_NO_ERROR;
706}
707
708static svn_error_t *
709svn_ra_local__get_latest_revnum(svn_ra_session_t *session,
710                                svn_revnum_t *latest_revnum,
711                                apr_pool_t *pool)
712{
713  svn_ra_local__session_baton_t *sess = session->priv;
714  return svn_fs_youngest_rev(latest_revnum, sess->fs, pool);
715}
716
717static svn_error_t *
718svn_ra_local__get_file_revs(svn_ra_session_t *session,
719                            const char *path,
720                            svn_revnum_t start,
721                            svn_revnum_t end,
722                            svn_boolean_t include_merged_revisions,
723                            svn_file_rev_handler_t handler,
724                            void *handler_baton,
725                            apr_pool_t *pool)
726{
727  svn_ra_local__session_baton_t *sess = session->priv;
728  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
729  return svn_repos_get_file_revs2(sess->repos, abs_path, start, end,
730                                  include_merged_revisions, NULL, NULL,
731                                  handler, handler_baton, pool);
732}
733
734static svn_error_t *
735svn_ra_local__get_dated_revision(svn_ra_session_t *session,
736                                 svn_revnum_t *revision,
737                                 apr_time_t tm,
738                                 apr_pool_t *pool)
739{
740  svn_ra_local__session_baton_t *sess = session->priv;
741  return svn_repos_dated_revision(revision, sess->repos, tm, pool);
742}
743
744
745static svn_error_t *
746svn_ra_local__change_rev_prop(svn_ra_session_t *session,
747                              svn_revnum_t rev,
748                              const char *name,
749                              const svn_string_t *const *old_value_p,
750                              const svn_string_t *value,
751                              apr_pool_t *pool)
752{
753  svn_ra_local__session_baton_t *sess = session->priv;
754
755  SVN_ERR(get_username(session, pool));
756  return svn_repos_fs_change_rev_prop4(sess->repos, rev, sess->username,
757                                       name, old_value_p, value, TRUE, TRUE,
758                                       NULL, NULL, pool);
759}
760
761static svn_error_t *
762svn_ra_local__get_uuid(svn_ra_session_t *session,
763                       const char **uuid,
764                       apr_pool_t *pool)
765{
766  svn_ra_local__session_baton_t *sess = session->priv;
767  *uuid = sess->uuid;
768  return SVN_NO_ERROR;
769}
770
771static svn_error_t *
772svn_ra_local__get_repos_root(svn_ra_session_t *session,
773                             const char **url,
774                             apr_pool_t *pool)
775{
776  svn_ra_local__session_baton_t *sess = session->priv;
777  *url = sess->repos_url;
778  return SVN_NO_ERROR;
779}
780
781static svn_error_t *
782svn_ra_local__rev_proplist(svn_ra_session_t *session,
783                           svn_revnum_t rev,
784                           apr_hash_t **props,
785                           apr_pool_t *pool)
786{
787  svn_ra_local__session_baton_t *sess = session->priv;
788  return svn_repos_fs_revision_proplist(props, sess->repos, rev,
789                                        NULL, NULL, pool);
790}
791
792static svn_error_t *
793svn_ra_local__rev_prop(svn_ra_session_t *session,
794                       svn_revnum_t rev,
795                       const char *name,
796                       svn_string_t **value,
797                       apr_pool_t *pool)
798{
799  svn_ra_local__session_baton_t *sess = session->priv;
800  return svn_repos_fs_revision_prop(value, sess->repos, rev, name,
801                                    NULL, NULL, pool);
802}
803
804struct ccw_baton
805{
806  svn_commit_callback2_t original_callback;
807  void *original_baton;
808
809  svn_ra_session_t *session;
810};
811
812/* Wrapper which populates the repos_root field of the commit_info struct */
813static svn_error_t *
814commit_callback_wrapper(const svn_commit_info_t *commit_info,
815                        void *baton,
816                        apr_pool_t *scratch_pool)
817{
818  struct ccw_baton *ccwb = baton;
819  svn_commit_info_t *ci = svn_commit_info_dup(commit_info, scratch_pool);
820
821  SVN_ERR(svn_ra_local__get_repos_root(ccwb->session, &ci->repos_root,
822                                       scratch_pool));
823
824  return svn_error_trace(ccwb->original_callback(ci, ccwb->original_baton,
825                                                 scratch_pool));
826}
827
828
829/* The repository layer does not correctly fill in REPOS_ROOT in
830   commit_info, as it doesn't know the url that is used to access
831   it. This hooks the callback to fill in the missing pieces. */
832static void
833remap_commit_callback(svn_commit_callback2_t *callback,
834                      void **callback_baton,
835                      svn_ra_session_t *session,
836                      svn_commit_callback2_t original_callback,
837                      void *original_baton,
838                      apr_pool_t *result_pool)
839{
840  if (original_callback == NULL)
841    {
842      *callback = NULL;
843      *callback_baton = NULL;
844    }
845  else
846    {
847      /* Allocate this in RESULT_POOL, since the callback will be called
848         long after this function has returned. */
849      struct ccw_baton *ccwb = apr_palloc(result_pool, sizeof(*ccwb));
850
851      ccwb->session = session;
852      ccwb->original_callback = original_callback;
853      ccwb->original_baton = original_baton;
854
855      *callback = commit_callback_wrapper;
856      *callback_baton = ccwb;
857    }
858}
859
860static svn_error_t *
861svn_ra_local__get_commit_editor(svn_ra_session_t *session,
862                                const svn_delta_editor_t **editor,
863                                void **edit_baton,
864                                apr_hash_t *revprop_table,
865                                svn_commit_callback2_t callback,
866                                void *callback_baton,
867                                apr_hash_t *lock_tokens,
868                                svn_boolean_t keep_locks,
869                                apr_pool_t *pool)
870{
871  svn_ra_local__session_baton_t *sess = session->priv;
872  struct deltify_etc_baton *deb = apr_palloc(pool, sizeof(*deb));
873
874  /* Set repos_root_url in commit info */
875  remap_commit_callback(&callback, &callback_baton, session,
876                        callback, callback_baton, pool);
877
878  /* Prepare the baton for deltify_etc()  */
879  deb->fs = sess->fs;
880  deb->repos = sess->repos;
881  deb->fspath_base = sess->fs_path->data;
882  if (! keep_locks)
883    deb->lock_tokens = lock_tokens;
884  else
885    deb->lock_tokens = NULL;
886  deb->commit_cb = callback;
887  deb->commit_baton = callback_baton;
888
889  SVN_ERR(get_username(session, pool));
890
891  /* If there are lock tokens to add, do so. */
892  SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
893                            session->pool, pool));
894
895  /* Copy the revprops table so we can add the username. */
896  revprop_table = apr_hash_copy(pool, revprop_table);
897  svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
898                svn_string_create(sess->username, pool));
899  svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
900                svn_string_create(SVN_VER_NUMBER, pool));
901  svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
902                svn_string_create(sess->useragent, pool));
903
904  /* Get the repos commit-editor */
905  return svn_repos_get_commit_editor5
906         (editor, edit_baton, sess->repos, NULL,
907          svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data,
908          revprop_table, deltify_etc, deb, NULL, NULL, pool);
909}
910
911
912static svn_error_t *
913svn_ra_local__get_mergeinfo(svn_ra_session_t *session,
914                            svn_mergeinfo_catalog_t *catalog,
915                            const apr_array_header_t *paths,
916                            svn_revnum_t revision,
917                            svn_mergeinfo_inheritance_t inherit,
918                            svn_boolean_t include_descendants,
919                            apr_pool_t *pool)
920{
921  svn_ra_local__session_baton_t *sess = session->priv;
922  svn_mergeinfo_catalog_t tmp_catalog;
923  int i;
924  apr_array_header_t *abs_paths =
925    apr_array_make(pool, 0, sizeof(const char *));
926
927  for (i = 0; i < paths->nelts; i++)
928    {
929      const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
930      APR_ARRAY_PUSH(abs_paths, const char *) =
931        svn_fspath__join(sess->fs_path->data, relative_path, pool);
932    }
933
934  SVN_ERR(svn_repos_fs_get_mergeinfo(&tmp_catalog, sess->repos, abs_paths,
935                                     revision, inherit, include_descendants,
936                                     NULL, NULL, pool));
937  if (apr_hash_count(tmp_catalog) > 0)
938    SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog,
939                                                      tmp_catalog,
940                                                      sess->fs_path->data,
941                                                      pool));
942  else
943    *catalog = NULL;
944
945  return SVN_NO_ERROR;
946}
947
948
949static svn_error_t *
950svn_ra_local__do_update(svn_ra_session_t *session,
951                        const svn_ra_reporter3_t **reporter,
952                        void **report_baton,
953                        svn_revnum_t update_revision,
954                        const char *update_target,
955                        svn_depth_t depth,
956                        svn_boolean_t send_copyfrom_args,
957                        svn_boolean_t ignore_ancestry,
958                        const svn_delta_editor_t *update_editor,
959                        void *update_baton,
960                        apr_pool_t *result_pool,
961                        apr_pool_t *scratch_pool)
962{
963  return make_reporter(session,
964                       reporter,
965                       report_baton,
966                       update_revision,
967                       update_target,
968                       NULL,
969                       TRUE,
970                       depth,
971                       send_copyfrom_args,
972                       ignore_ancestry,
973                       update_editor,
974                       update_baton,
975                       result_pool, scratch_pool);
976}
977
978
979static svn_error_t *
980svn_ra_local__do_switch(svn_ra_session_t *session,
981                        const svn_ra_reporter3_t **reporter,
982                        void **report_baton,
983                        svn_revnum_t update_revision,
984                        const char *update_target,
985                        svn_depth_t depth,
986                        const char *switch_url,
987                        svn_boolean_t send_copyfrom_args,
988                        svn_boolean_t ignore_ancestry,
989                        const svn_delta_editor_t *update_editor,
990                        void *update_baton,
991                        apr_pool_t *result_pool,
992                        apr_pool_t *scratch_pool)
993{
994  return make_reporter(session,
995                       reporter,
996                       report_baton,
997                       update_revision,
998                       update_target,
999                       switch_url,
1000                       TRUE /* text_deltas */,
1001                       depth,
1002                       send_copyfrom_args,
1003                       ignore_ancestry,
1004                       update_editor,
1005                       update_baton,
1006                       result_pool, scratch_pool);
1007}
1008
1009
1010static svn_error_t *
1011svn_ra_local__do_status(svn_ra_session_t *session,
1012                        const svn_ra_reporter3_t **reporter,
1013                        void **report_baton,
1014                        const char *status_target,
1015                        svn_revnum_t revision,
1016                        svn_depth_t depth,
1017                        const svn_delta_editor_t *status_editor,
1018                        void *status_baton,
1019                        apr_pool_t *pool)
1020{
1021  return make_reporter(session,
1022                       reporter,
1023                       report_baton,
1024                       revision,
1025                       status_target,
1026                       NULL,
1027                       FALSE,
1028                       depth,
1029                       FALSE,
1030                       FALSE,
1031                       status_editor,
1032                       status_baton,
1033                       pool, pool);
1034}
1035
1036
1037static svn_error_t *
1038svn_ra_local__do_diff(svn_ra_session_t *session,
1039                      const svn_ra_reporter3_t **reporter,
1040                      void **report_baton,
1041                      svn_revnum_t update_revision,
1042                      const char *update_target,
1043                      svn_depth_t depth,
1044                      svn_boolean_t ignore_ancestry,
1045                      svn_boolean_t text_deltas,
1046                      const char *switch_url,
1047                      const svn_delta_editor_t *update_editor,
1048                      void *update_baton,
1049                      apr_pool_t *pool)
1050{
1051  return make_reporter(session,
1052                       reporter,
1053                       report_baton,
1054                       update_revision,
1055                       update_target,
1056                       switch_url,
1057                       text_deltas,
1058                       depth,
1059                       FALSE,
1060                       ignore_ancestry,
1061                       update_editor,
1062                       update_baton,
1063                       pool, pool);
1064}
1065
1066
1067struct log_baton
1068{
1069  svn_ra_local__session_baton_t *sess;
1070  svn_log_entry_receiver_t real_cb;
1071  void *real_baton;
1072};
1073
1074static svn_error_t *
1075log_receiver_wrapper(void *baton,
1076                     svn_log_entry_t *log_entry,
1077                     apr_pool_t *pool)
1078{
1079  struct log_baton *b = baton;
1080  svn_ra_local__session_baton_t *sess = b->sess;
1081
1082  if (sess->callbacks->cancel_func)
1083    SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton));
1084
1085  /* For consistency with the other RA layers, replace an empty
1086     changed-paths hash with a NULL one.
1087
1088     ### Should this be done by svn_ra_get_log2() instead, then? */
1089  if ((log_entry->changed_paths2)
1090      && (apr_hash_count(log_entry->changed_paths2) == 0))
1091    {
1092      log_entry->changed_paths = NULL;
1093      log_entry->changed_paths2 = NULL;
1094    }
1095
1096  return b->real_cb(b->real_baton, log_entry, pool);
1097}
1098
1099
1100static svn_error_t *
1101svn_ra_local__get_log(svn_ra_session_t *session,
1102                      const apr_array_header_t *paths,
1103                      svn_revnum_t start,
1104                      svn_revnum_t end,
1105                      int limit,
1106                      svn_boolean_t discover_changed_paths,
1107                      svn_boolean_t strict_node_history,
1108                      svn_boolean_t include_merged_revisions,
1109                      const apr_array_header_t *revprops,
1110                      svn_log_entry_receiver_t receiver,
1111                      void *receiver_baton,
1112                      apr_pool_t *pool)
1113{
1114  svn_ra_local__session_baton_t *sess = session->priv;
1115  struct log_baton lb;
1116  apr_array_header_t *abs_paths =
1117    apr_array_make(pool, 0, sizeof(const char *));
1118
1119  if (paths)
1120    {
1121      int i;
1122
1123      for (i = 0; i < paths->nelts; i++)
1124        {
1125          const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
1126          APR_ARRAY_PUSH(abs_paths, const char *) =
1127            svn_fspath__join(sess->fs_path->data, relative_path, pool);
1128        }
1129    }
1130
1131  lb.real_cb = receiver;
1132  lb.real_baton = receiver_baton;
1133  lb.sess = sess;
1134  receiver = log_receiver_wrapper;
1135  receiver_baton = &lb;
1136
1137  return svn_repos_get_logs4(sess->repos,
1138                             abs_paths,
1139                             start,
1140                             end,
1141                             limit,
1142                             discover_changed_paths,
1143                             strict_node_history,
1144                             include_merged_revisions,
1145                             revprops,
1146                             NULL, NULL,
1147                             receiver,
1148                             receiver_baton,
1149                             pool);
1150}
1151
1152
1153static svn_error_t *
1154svn_ra_local__do_check_path(svn_ra_session_t *session,
1155                            const char *path,
1156                            svn_revnum_t revision,
1157                            svn_node_kind_t *kind,
1158                            apr_pool_t *pool)
1159{
1160  svn_ra_local__session_baton_t *sess = session->priv;
1161  svn_fs_root_t *root;
1162  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1163
1164  if (! SVN_IS_VALID_REVNUM(revision))
1165    SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
1166  SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1167  return svn_fs_check_path(kind, root, abs_path, pool);
1168}
1169
1170
1171static svn_error_t *
1172svn_ra_local__stat(svn_ra_session_t *session,
1173                   const char *path,
1174                   svn_revnum_t revision,
1175                   svn_dirent_t **dirent,
1176                   apr_pool_t *pool)
1177{
1178  svn_ra_local__session_baton_t *sess = session->priv;
1179  svn_fs_root_t *root;
1180  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1181
1182  if (! SVN_IS_VALID_REVNUM(revision))
1183    SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
1184  SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1185
1186  return svn_repos_stat(dirent, root, abs_path, pool);
1187}
1188
1189
1190
1191
1192/* Obtain the properties for a node, including its 'entry props */
1193static svn_error_t *
1194get_node_props(apr_hash_t **props,
1195               svn_fs_root_t *root,
1196               const char *path,
1197               const char *uuid,
1198               apr_pool_t *result_pool,
1199               apr_pool_t *scratch_pool)
1200{
1201  svn_revnum_t cmt_rev;
1202  const char *cmt_date, *cmt_author;
1203
1204  /* Create a hash with props attached to the fs node. */
1205  SVN_ERR(svn_fs_node_proplist(props, root, path, result_pool));
1206
1207  /* Now add some non-tweakable metadata to the hash as well... */
1208
1209  /* The so-called 'entryprops' with info about CR & friends. */
1210  SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date,
1211                                       &cmt_author, root, path,
1212                                       scratch_pool));
1213
1214  svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV,
1215                svn_string_createf(result_pool, "%ld", cmt_rev));
1216  svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, cmt_date ?
1217                svn_string_create(cmt_date, result_pool) : NULL);
1218  svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, cmt_author ?
1219                svn_string_create(cmt_author, result_pool) : NULL);
1220  svn_hash_sets(*props, SVN_PROP_ENTRY_UUID,
1221                svn_string_create(uuid, result_pool));
1222
1223  /* We have no 'wcprops' in ra_local, but might someday. */
1224
1225  return SVN_NO_ERROR;
1226}
1227
1228
1229/* Getting just one file. */
1230static svn_error_t *
1231svn_ra_local__get_file(svn_ra_session_t *session,
1232                       const char *path,
1233                       svn_revnum_t revision,
1234                       svn_stream_t *stream,
1235                       svn_revnum_t *fetched_rev,
1236                       apr_hash_t **props,
1237                       apr_pool_t *pool)
1238{
1239  svn_fs_root_t *root;
1240  svn_stream_t *contents;
1241  svn_revnum_t youngest_rev;
1242  svn_ra_local__session_baton_t *sess = session->priv;
1243  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1244  svn_node_kind_t node_kind;
1245
1246  /* Open the revision's root. */
1247  if (! SVN_IS_VALID_REVNUM(revision))
1248    {
1249      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
1250      SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
1251      if (fetched_rev != NULL)
1252        *fetched_rev = youngest_rev;
1253    }
1254  else
1255    SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1256
1257  SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, pool));
1258  if (node_kind == svn_node_none)
1259    {
1260      return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1261                               _("'%s' path not found"), abs_path);
1262    }
1263  else if (node_kind != svn_node_file)
1264    {
1265      return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
1266                               _("'%s' is not a file"), abs_path);
1267    }
1268
1269  if (stream)
1270    {
1271      /* Get a stream representing the file's contents. */
1272      SVN_ERR(svn_fs_file_contents(&contents, root, abs_path, pool));
1273
1274      /* Now push data from the fs stream back at the caller's stream.
1275         Note that this particular RA layer does not computing a
1276         checksum as we go, and confirming it against the repository's
1277         checksum when done.  That's because it calls
1278         svn_fs_file_contents() directly, which already checks the
1279         stored checksum, and all we're doing here is writing bytes in
1280         a loop.  Truly, Nothing Can Go Wrong :-).  But RA layers that
1281         go over a network should confirm the checksum.
1282
1283         Note: we are not supposed to close the passed-in stream, so
1284         disown the thing.
1285      */
1286      SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(stream, pool),
1287                               sess->callbacks
1288                                 ? sess->callbacks->cancel_func : NULL,
1289                               sess->callback_baton,
1290                               pool));
1291    }
1292
1293  /* Handle props if requested. */
1294  if (props)
1295    SVN_ERR(get_node_props(props, root, abs_path, sess->uuid, pool, pool));
1296
1297  return SVN_NO_ERROR;
1298}
1299
1300
1301
1302/* Getting a directory's entries */
1303static svn_error_t *
1304svn_ra_local__get_dir(svn_ra_session_t *session,
1305                      apr_hash_t **dirents,
1306                      svn_revnum_t *fetched_rev,
1307                      apr_hash_t **props,
1308                      const char *path,
1309                      svn_revnum_t revision,
1310                      apr_uint32_t dirent_fields,
1311                      apr_pool_t *pool)
1312{
1313  svn_fs_root_t *root;
1314  svn_revnum_t youngest_rev;
1315  apr_hash_t *entries;
1316  apr_hash_index_t *hi;
1317  svn_ra_local__session_baton_t *sess = session->priv;
1318  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1319
1320  /* Open the revision's root. */
1321  if (! SVN_IS_VALID_REVNUM(revision))
1322    {
1323      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
1324      SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
1325      if (fetched_rev != NULL)
1326        *fetched_rev = youngest_rev;
1327    }
1328  else
1329    SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1330
1331  if (dirents)
1332    {
1333      apr_pool_t *iterpool = svn_pool_create(pool);
1334      /* Get the dir's entries. */
1335      SVN_ERR(svn_fs_dir_entries(&entries, root, abs_path, pool));
1336
1337      /* Loop over the fs dirents, and build a hash of general
1338         svn_dirent_t's. */
1339      *dirents = apr_hash_make(pool);
1340      for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1341        {
1342          const void *key;
1343          void *val;
1344          const char *datestring, *entryname, *fullpath;
1345          svn_fs_dirent_t *fs_entry;
1346          svn_dirent_t *entry = svn_dirent_create(pool);
1347
1348          svn_pool_clear(iterpool);
1349
1350          apr_hash_this(hi, &key, NULL, &val);
1351          entryname = (const char *) key;
1352          fs_entry = (svn_fs_dirent_t *) val;
1353
1354          fullpath = svn_dirent_join(abs_path, entryname, iterpool);
1355
1356          if (dirent_fields & SVN_DIRENT_KIND)
1357            {
1358              /* node kind */
1359              entry->kind = fs_entry->kind;
1360            }
1361
1362          if (dirent_fields & SVN_DIRENT_SIZE)
1363            {
1364              /* size  */
1365              if (entry->kind == svn_node_dir)
1366                entry->size = 0;
1367              else
1368                SVN_ERR(svn_fs_file_length(&(entry->size), root,
1369                                           fullpath, iterpool));
1370            }
1371
1372          if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1373            {
1374              /* has_props? */
1375              SVN_ERR(svn_fs_node_has_props(&entry->has_props,
1376                                            root, fullpath,
1377                                            iterpool));
1378            }
1379
1380          if ((dirent_fields & SVN_DIRENT_TIME)
1381              || (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1382              || (dirent_fields & SVN_DIRENT_CREATED_REV))
1383            {
1384              /* created_rev & friends */
1385              SVN_ERR(svn_repos_get_committed_info(&(entry->created_rev),
1386                                                   &datestring,
1387                                                   &(entry->last_author),
1388                                                   root, fullpath, iterpool));
1389              if (datestring)
1390                SVN_ERR(svn_time_from_cstring(&(entry->time), datestring,
1391                                              pool));
1392              if (entry->last_author)
1393                entry->last_author = apr_pstrdup(pool, entry->last_author);
1394            }
1395
1396          /* Store. */
1397          svn_hash_sets(*dirents, entryname, entry);
1398        }
1399      svn_pool_destroy(iterpool);
1400    }
1401
1402  /* Handle props if requested. */
1403  if (props)
1404    SVN_ERR(get_node_props(props, root, abs_path, sess->uuid, pool, pool));
1405
1406  return SVN_NO_ERROR;
1407}
1408
1409
1410static svn_error_t *
1411svn_ra_local__get_locations(svn_ra_session_t *session,
1412                            apr_hash_t **locations,
1413                            const char *path,
1414                            svn_revnum_t peg_revision,
1415                            const apr_array_header_t *location_revisions,
1416                            apr_pool_t *pool)
1417{
1418  svn_ra_local__session_baton_t *sess = session->priv;
1419  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1420  return svn_repos_trace_node_locations(sess->fs, locations, abs_path,
1421                                        peg_revision, location_revisions,
1422                                        NULL, NULL, pool);
1423}
1424
1425
1426static svn_error_t *
1427svn_ra_local__get_location_segments(svn_ra_session_t *session,
1428                                    const char *path,
1429                                    svn_revnum_t peg_revision,
1430                                    svn_revnum_t start_rev,
1431                                    svn_revnum_t end_rev,
1432                                    svn_location_segment_receiver_t receiver,
1433                                    void *receiver_baton,
1434                                    apr_pool_t *pool)
1435{
1436  svn_ra_local__session_baton_t *sess = session->priv;
1437  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1438  return svn_repos_node_location_segments(sess->repos, abs_path,
1439                                          peg_revision, start_rev, end_rev,
1440                                          receiver, receiver_baton,
1441                                          NULL, NULL, pool);
1442}
1443
1444struct lock_baton_t {
1445  svn_ra_lock_callback_t lock_func;
1446  void *lock_baton;
1447  const char *fs_path;
1448  svn_boolean_t is_lock;
1449  svn_error_t *cb_err;
1450};
1451
1452/* Implements svn_fs_lock_callback_t.  Used by svn_ra_local__lock and
1453   svn_ra_local__unlock to forward to supplied callback and record any
1454   callback error. */
1455static svn_error_t *
1456lock_cb(void *lock_baton,
1457        const char *path,
1458        const svn_lock_t *lock,
1459        svn_error_t *fs_err,
1460        apr_pool_t *pool)
1461{
1462  struct lock_baton_t *b = lock_baton;
1463
1464  if (b && !b->cb_err && b->lock_func)
1465    {
1466      path = svn_fspath__skip_ancestor(b->fs_path, path);
1467      b->cb_err = b->lock_func(b->lock_baton, path, b->is_lock, lock, fs_err,
1468                               pool);
1469    }
1470
1471  return SVN_NO_ERROR;
1472}
1473
1474static svn_error_t *
1475svn_ra_local__lock(svn_ra_session_t *session,
1476                   apr_hash_t *path_revs,
1477                   const char *comment,
1478                   svn_boolean_t force,
1479                   svn_ra_lock_callback_t lock_func,
1480                   void *lock_baton,
1481                   apr_pool_t *pool)
1482{
1483  svn_ra_local__session_baton_t *sess = session->priv;
1484  apr_hash_t *targets = apr_hash_make(pool);
1485  apr_hash_index_t *hi;
1486  svn_error_t *err;
1487  struct lock_baton_t baton = {0};
1488
1489  /* A username is absolutely required to lock a path. */
1490  SVN_ERR(get_username(session, pool));
1491
1492  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1493    {
1494      const char *abs_path = svn_fspath__join(sess->fs_path->data,
1495                                              apr_hash_this_key(hi), pool);
1496      svn_revnum_t current_rev = *(svn_revnum_t *)apr_hash_this_val(hi);
1497      svn_fs_lock_target_t *target = svn_fs_lock_target_create(NULL,
1498                                                               current_rev,
1499                                                               pool);
1500
1501      svn_hash_sets(targets, abs_path, target);
1502    }
1503
1504  baton.lock_func = lock_func;
1505  baton.lock_baton = lock_baton;
1506  baton.fs_path = sess->fs_path->data;
1507  baton.is_lock = TRUE;
1508  baton.cb_err = SVN_NO_ERROR;
1509
1510  err = svn_repos_fs_lock_many(sess->repos, targets, comment,
1511                               FALSE /* not DAV comment */,
1512                               0 /* no expiration */, force,
1513                               lock_cb, &baton,
1514                               pool, pool);
1515
1516  if (err && baton.cb_err)
1517    svn_error_compose(err, baton.cb_err);
1518  else if (!err)
1519    err = baton.cb_err;
1520
1521  return svn_error_trace(err);
1522}
1523
1524
1525static svn_error_t *
1526svn_ra_local__unlock(svn_ra_session_t *session,
1527                     apr_hash_t *path_tokens,
1528                     svn_boolean_t force,
1529                     svn_ra_lock_callback_t lock_func,
1530                     void *lock_baton,
1531                     apr_pool_t *pool)
1532{
1533  svn_ra_local__session_baton_t *sess = session->priv;
1534  apr_hash_t *targets = apr_hash_make(pool);
1535  apr_hash_index_t *hi;
1536  svn_error_t *err;
1537  struct lock_baton_t baton = {0};
1538
1539  /* A username is absolutely required to unlock a path. */
1540  SVN_ERR(get_username(session, pool));
1541
1542  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1543    {
1544      const char *abs_path = svn_fspath__join(sess->fs_path->data,
1545                                              apr_hash_this_key(hi), pool);
1546      const char *token = apr_hash_this_val(hi);
1547
1548      svn_hash_sets(targets, abs_path, token);
1549    }
1550
1551  baton.lock_func = lock_func;
1552  baton.lock_baton = lock_baton;
1553  baton.fs_path = sess->fs_path->data;
1554  baton.is_lock = FALSE;
1555  baton.cb_err = SVN_NO_ERROR;
1556
1557  err = svn_repos_fs_unlock_many(sess->repos, targets, force, lock_cb, &baton,
1558                                 pool, pool);
1559
1560  if (err && baton.cb_err)
1561    svn_error_compose(err, baton.cb_err);
1562  else if (!err)
1563    err = baton.cb_err;
1564
1565  return svn_error_trace(err);
1566}
1567
1568
1569
1570static svn_error_t *
1571svn_ra_local__get_lock(svn_ra_session_t *session,
1572                       svn_lock_t **lock,
1573                       const char *path,
1574                       apr_pool_t *pool)
1575{
1576  svn_ra_local__session_baton_t *sess = session->priv;
1577  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1578  return svn_fs_get_lock(lock, sess->fs, abs_path, pool);
1579}
1580
1581
1582
1583static svn_error_t *
1584svn_ra_local__get_locks(svn_ra_session_t *session,
1585                        apr_hash_t **locks,
1586                        const char *path,
1587                        svn_depth_t depth,
1588                        apr_pool_t *pool)
1589{
1590  svn_ra_local__session_baton_t *sess = session->priv;
1591  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1592
1593  /* Kinda silly to call the repos wrapper, since we have no authz
1594     func to give it.  But heck, why not. */
1595  return svn_repos_fs_get_locks2(locks, sess->repos, abs_path, depth,
1596                                 NULL, NULL, pool);
1597}
1598
1599
1600static svn_error_t *
1601svn_ra_local__replay(svn_ra_session_t *session,
1602                     svn_revnum_t revision,
1603                     svn_revnum_t low_water_mark,
1604                     svn_boolean_t send_deltas,
1605                     const svn_delta_editor_t *editor,
1606                     void *edit_baton,
1607                     apr_pool_t *pool)
1608{
1609  svn_ra_local__session_baton_t *sess = session->priv;
1610  svn_fs_root_t *root;
1611
1612  SVN_ERR(svn_fs_revision_root(&root, svn_repos_fs(sess->repos),
1613                               revision, pool));
1614  return svn_repos_replay2(root, sess->fs_path->data, low_water_mark,
1615                           send_deltas, editor, edit_baton, NULL, NULL,
1616                           pool);
1617}
1618
1619
1620static svn_error_t *
1621svn_ra_local__replay_range(svn_ra_session_t *session,
1622                           svn_revnum_t start_revision,
1623                           svn_revnum_t end_revision,
1624                           svn_revnum_t low_water_mark,
1625                           svn_boolean_t send_deltas,
1626                           svn_ra_replay_revstart_callback_t revstart_func,
1627                           svn_ra_replay_revfinish_callback_t revfinish_func,
1628                           void *replay_baton,
1629                           apr_pool_t *pool)
1630{
1631  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
1632}
1633
1634
1635static svn_error_t *
1636svn_ra_local__has_capability(svn_ra_session_t *session,
1637                             svn_boolean_t *has,
1638                             const char *capability,
1639                             apr_pool_t *pool)
1640{
1641  svn_ra_local__session_baton_t *sess = session->priv;
1642
1643  if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0
1644      || strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0
1645      || strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0
1646      || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0
1647      || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0
1648      || strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0
1649      || strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0
1650      || strcmp(capability, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) == 0
1651      )
1652    {
1653      *has = TRUE;
1654    }
1655  else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
1656    {
1657      /* With mergeinfo, the code's capabilities may not reflect the
1658         repository's, so inquire further. */
1659      SVN_ERR(svn_repos_has_capability(sess->repos, has,
1660                                       SVN_REPOS_CAPABILITY_MERGEINFO,
1661                                       pool));
1662    }
1663  else  /* Don't know any other capabilities, so error. */
1664    {
1665      return svn_error_createf
1666        (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
1667         _("Don't know anything about capability '%s'"), capability);
1668    }
1669
1670  return SVN_NO_ERROR;
1671}
1672
1673static svn_error_t *
1674svn_ra_local__get_deleted_rev(svn_ra_session_t *session,
1675                              const char *path,
1676                              svn_revnum_t peg_revision,
1677                              svn_revnum_t end_revision,
1678                              svn_revnum_t *revision_deleted,
1679                              apr_pool_t *pool)
1680{
1681  svn_ra_local__session_baton_t *sess = session->priv;
1682  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1683
1684  SVN_ERR(svn_repos_deleted_rev(sess->fs,
1685                                abs_path,
1686                                peg_revision,
1687                                end_revision,
1688                                revision_deleted,
1689                                pool));
1690
1691  return SVN_NO_ERROR;
1692}
1693
1694static svn_error_t *
1695svn_ra_local__get_inherited_props(svn_ra_session_t *session,
1696                                  apr_array_header_t **iprops,
1697                                  const char *path,
1698                                  svn_revnum_t revision,
1699                                  apr_pool_t *result_pool,
1700                                  apr_pool_t *scratch_pool)
1701{
1702  svn_fs_root_t *root;
1703  svn_revnum_t youngest_rev;
1704  svn_ra_local__session_baton_t *sess = session->priv;
1705  const char *abs_path = svn_fspath__join(sess->fs_path->data, path,
1706                                          scratch_pool);
1707  svn_node_kind_t node_kind;
1708
1709  /* Open the revision's root. */
1710  if (! SVN_IS_VALID_REVNUM(revision))
1711    {
1712      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, scratch_pool));
1713      SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev,
1714                                   scratch_pool));
1715    }
1716  else
1717    {
1718      SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, scratch_pool));
1719    }
1720
1721  SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, scratch_pool));
1722  if (node_kind == svn_node_none)
1723    {
1724      return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1725                               _("'%s' path not found"), abs_path);
1726    }
1727
1728  return svn_error_trace(
1729                svn_repos_fs_get_inherited_props(iprops, root, abs_path,
1730                                                 NULL /* propname */,
1731                                                 NULL, NULL /* auth */,
1732                                                 result_pool, scratch_pool));
1733}
1734
1735static svn_error_t *
1736svn_ra_local__register_editor_shim_callbacks(svn_ra_session_t *session,
1737                                    svn_delta_shim_callbacks_t *callbacks)
1738{
1739  /* This is currenly a no-op, since we don't provide our own editor, just
1740     use the one the libsvn_repos hands back to us. */
1741  return SVN_NO_ERROR;
1742}
1743
1744
1745static svn_error_t *
1746svn_ra_local__get_commit_ev2(svn_editor_t **editor,
1747                             svn_ra_session_t *session,
1748                             apr_hash_t *revprops,
1749                             svn_commit_callback2_t commit_cb,
1750                             void *commit_baton,
1751                             apr_hash_t *lock_tokens,
1752                             svn_boolean_t keep_locks,
1753                             svn_ra__provide_base_cb_t provide_base_cb,
1754                             svn_ra__provide_props_cb_t provide_props_cb,
1755                             svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
1756                             void *cb_baton,
1757                             svn_cancel_func_t cancel_func,
1758                             void *cancel_baton,
1759                             apr_pool_t *result_pool,
1760                             apr_pool_t *scratch_pool)
1761{
1762  svn_ra_local__session_baton_t *sess = session->priv;
1763  struct deltify_etc_baton *deb = apr_palloc(result_pool, sizeof(*deb));
1764
1765  remap_commit_callback(&commit_cb, &commit_baton, session,
1766                        commit_cb, commit_baton, result_pool);
1767
1768  /* NOTE: the RA callbacks are ignored. We pass everything directly to
1769     the REPOS editor.  */
1770
1771  /* Prepare the baton for deltify_etc()  */
1772  deb->fs = sess->fs;
1773  deb->repos = sess->repos;
1774  deb->fspath_base = sess->fs_path->data;
1775  if (! keep_locks)
1776    deb->lock_tokens = lock_tokens;
1777  else
1778    deb->lock_tokens = NULL;
1779  deb->commit_cb = commit_cb;
1780  deb->commit_baton = commit_baton;
1781
1782  /* Ensure there is a username (and an FS access context) associated with
1783     the session and its FS handle.  */
1784  SVN_ERR(get_username(session, scratch_pool));
1785
1786  /* If there are lock tokens to add, do so.  */
1787  SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
1788                            session->pool, scratch_pool));
1789
1790  /* Copy the REVPROPS and insert the author/username.  */
1791  revprops = apr_hash_copy(scratch_pool, revprops);
1792  svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR,
1793                svn_string_create(sess->username, scratch_pool));
1794
1795  return svn_error_trace(svn_repos__get_commit_ev2(
1796                           editor, sess->repos, NULL /* authz */,
1797                           NULL /* authz_repos_name */, NULL /* authz_user */,
1798                           revprops,
1799                           deltify_etc, deb, cancel_func, cancel_baton,
1800                           result_pool, scratch_pool));
1801}
1802
1803/*----------------------------------------------------------------*/
1804
1805static const svn_version_t *
1806ra_local_version(void)
1807{
1808  SVN_VERSION_BODY;
1809}
1810
1811/** The ra_vtable **/
1812
1813static const svn_ra__vtable_t ra_local_vtable =
1814{
1815  ra_local_version,
1816  svn_ra_local__get_description,
1817  svn_ra_local__get_schemes,
1818  svn_ra_local__open,
1819  svn_ra_local__dup_session,
1820  svn_ra_local__reparent,
1821  svn_ra_local__get_session_url,
1822  svn_ra_local__get_latest_revnum,
1823  svn_ra_local__get_dated_revision,
1824  svn_ra_local__change_rev_prop,
1825  svn_ra_local__rev_proplist,
1826  svn_ra_local__rev_prop,
1827  svn_ra_local__get_commit_editor,
1828  svn_ra_local__get_file,
1829  svn_ra_local__get_dir,
1830  svn_ra_local__get_mergeinfo,
1831  svn_ra_local__do_update,
1832  svn_ra_local__do_switch,
1833  svn_ra_local__do_status,
1834  svn_ra_local__do_diff,
1835  svn_ra_local__get_log,
1836  svn_ra_local__do_check_path,
1837  svn_ra_local__stat,
1838  svn_ra_local__get_uuid,
1839  svn_ra_local__get_repos_root,
1840  svn_ra_local__get_locations,
1841  svn_ra_local__get_location_segments,
1842  svn_ra_local__get_file_revs,
1843  svn_ra_local__lock,
1844  svn_ra_local__unlock,
1845  svn_ra_local__get_lock,
1846  svn_ra_local__get_locks,
1847  svn_ra_local__replay,
1848  svn_ra_local__has_capability,
1849  svn_ra_local__replay_range,
1850  svn_ra_local__get_deleted_rev,
1851  svn_ra_local__register_editor_shim_callbacks,
1852  svn_ra_local__get_inherited_props,
1853  svn_ra_local__get_commit_ev2
1854};
1855
1856
1857/*----------------------------------------------------------------*/
1858
1859/** The One Public Routine, called by libsvn_ra **/
1860
1861svn_error_t *
1862svn_ra_local__init(const svn_version_t *loader_version,
1863                   const svn_ra__vtable_t **vtable,
1864                   apr_pool_t *pool)
1865{
1866  static const svn_version_checklist_t checklist[] =
1867    {
1868      { "svn_subr",  svn_subr_version },
1869      { "svn_delta", svn_delta_version },
1870      { "svn_repos", svn_repos_version },
1871      { "svn_fs",    svn_fs_version },
1872      { NULL, NULL }
1873    };
1874
1875
1876  /* Simplified version check to make sure we can safely use the
1877     VTABLE parameter. The RA loader does a more exhaustive check. */
1878  if (loader_version->major != SVN_VER_MAJOR)
1879    return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
1880                             _("Unsupported RA loader version (%d) for "
1881                               "ra_local"),
1882                             loader_version->major);
1883
1884  SVN_ERR(svn_ver_check_list2(ra_local_version(), checklist, svn_ver_equal));
1885
1886#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
1887  /* This means the library was loaded as a DSO, so use the DSO pool. */
1888  SVN_ERR(svn_fs_initialize(svn_dso__pool()));
1889#endif
1890
1891  *vtable = &ra_local_vtable;
1892
1893  return SVN_NO_ERROR;
1894}
1895
1896/* Compatibility wrapper for the 1.1 and before API. */
1897#define NAME "ra_local"
1898#define DESCRIPTION RA_LOCAL_DESCRIPTION
1899#define VTBL ra_local_vtable
1900#define INITFUNC svn_ra_local__init
1901#define COMPAT_INITFUNC svn_ra_local_init
1902#include "../libsvn_ra/wrapper_template.h"
1903