ra_plugin.c revision 362181
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 **redirect_url,
558                   const char *repos_URL,
559                   const svn_ra_callbacks2_t *callbacks,
560                   void *callback_baton,
561                   svn_auth_baton_t *auth_baton,
562                   apr_hash_t *config,
563                   apr_pool_t *result_pool,
564                   apr_pool_t *scratch_pool)
565{
566  const char *client_string;
567  svn_ra_local__session_baton_t *sess;
568  const char *fs_path;
569  static volatile svn_atomic_t cache_init_state = 0;
570  apr_pool_t *pool = result_pool;
571
572  /* Initialise the FSFS memory cache size.  We can only do this once
573     so one CONFIG will win the race and all others will be ignored
574     silently.  */
575  SVN_ERR(svn_atomic__init_once(&cache_init_state, cache_init, config, pool));
576
577  /* We don't support redirections in ra-local. */
578  if (corrected_url)
579    *corrected_url = NULL;
580  if (redirect_url)
581    *redirect_url = NULL;
582
583  /* Allocate and stash the session_sess args we have already. */
584  sess = apr_pcalloc(pool, sizeof(*sess));
585  sess->callbacks = callbacks;
586  sess->callback_baton = callback_baton;
587  sess->auth_baton = auth_baton;
588
589  /* Look through the URL, figure out which part points to the
590     repository, and which part is the path *within* the
591     repository. */
592  SVN_ERR(svn_ra_local__split_URL(&(sess->repos),
593                                  &(sess->repos_url),
594                                  &fs_path,
595                                  repos_URL,
596                                  session->pool));
597  sess->fs_path = svn_stringbuf_create(fs_path, session->pool);
598
599  /* Cache the filesystem object from the repos here for
600     convenience. */
601  sess->fs = svn_repos_fs(sess->repos);
602
603  /* Ignore FS warnings. */
604  svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL);
605
606  /* Cache the repository UUID as well */
607  SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));
608
609  /* Be sure username is NULL so we know to look it up / ask for it */
610  sess->username = NULL;
611
612  if (sess->callbacks->get_client_string != NULL)
613    SVN_ERR(sess->callbacks->get_client_string(sess->callback_baton,
614                                               &client_string, pool));
615  else
616    client_string = NULL;
617
618  if (client_string)
619    sess->useragent = apr_pstrcat(pool, USER_AGENT " ",
620                                  client_string, SVN_VA_NULL);
621  else
622    sess->useragent = USER_AGENT;
623
624  session->priv = sess;
625  return SVN_NO_ERROR;
626}
627
628static svn_error_t *
629svn_ra_local__dup_session(svn_ra_session_t *new_session,
630                          svn_ra_session_t *session,
631                          const char *new_session_url,
632                          apr_pool_t *result_pool,
633                          apr_pool_t *scratch_pool)
634{
635  svn_ra_local__session_baton_t *old_sess = session->priv;
636  svn_ra_local__session_baton_t *new_sess;
637  const char *fs_path;
638
639  /* Allocate and stash the session_sess args we have already. */
640  new_sess = apr_pcalloc(result_pool, sizeof(*new_sess));
641  new_sess->callbacks = old_sess->callbacks;
642  new_sess->callback_baton = old_sess->callback_baton;
643
644  /* ### Re-use existing FS handle? */
645
646  /* Reuse existing code */
647  SVN_ERR(svn_ra_local__split_URL(&(new_sess->repos),
648                                  &(new_sess->repos_url),
649                                  &fs_path,
650                                  new_session_url,
651                                  result_pool));
652
653  new_sess->fs_path = svn_stringbuf_create(fs_path, result_pool);
654
655  /* Cache the filesystem object from the repos here for
656     convenience. */
657  new_sess->fs = svn_repos_fs(new_sess->repos);
658
659  /* Ignore FS warnings. */
660  svn_fs_set_warning_func(new_sess->fs, ignore_warnings, NULL);
661
662  /* Cache the repository UUID as well */
663  new_sess->uuid = apr_pstrdup(result_pool, old_sess->uuid);
664
665  new_sess->username = old_sess->username
666                            ? apr_pstrdup(result_pool, old_sess->username)
667                            : NULL;
668
669  new_sess->useragent = apr_pstrdup(result_pool, old_sess->useragent);
670  new_session->priv = new_sess;
671
672  return SVN_NO_ERROR;
673}
674
675static svn_error_t *
676svn_ra_local__reparent(svn_ra_session_t *session,
677                       const char *url,
678                       apr_pool_t *pool)
679{
680  svn_ra_local__session_baton_t *sess = session->priv;
681  const char *relpath = svn_uri_skip_ancestor(sess->repos_url, url, pool);
682
683  /* If the new URL isn't the same as our repository root URL, then
684     let's ensure that it's some child of it. */
685  if (! relpath)
686    return svn_error_createf
687      (SVN_ERR_RA_ILLEGAL_URL, NULL,
688       _("URL '%s' is not a child of the session's repository root "
689         "URL '%s'"), url, sess->repos_url);
690
691  /* Update our FS_PATH sess member to point to our new
692     relative-URL-turned-absolute-filesystem-path. */
693  svn_stringbuf_set(sess->fs_path,
694                    svn_fspath__canonicalize(relpath, pool));
695
696  return SVN_NO_ERROR;
697}
698
699static svn_error_t *
700svn_ra_local__get_session_url(svn_ra_session_t *session,
701                              const char **url,
702                              apr_pool_t *pool)
703{
704  svn_ra_local__session_baton_t *sess = session->priv;
705  *url = svn_path_url_add_component2(sess->repos_url,
706                                     sess->fs_path->data + 1,
707                                     pool);
708  return SVN_NO_ERROR;
709}
710
711static svn_error_t *
712svn_ra_local__get_latest_revnum(svn_ra_session_t *session,
713                                svn_revnum_t *latest_revnum,
714                                apr_pool_t *pool)
715{
716  svn_ra_local__session_baton_t *sess = session->priv;
717  return svn_fs_youngest_rev(latest_revnum, sess->fs, pool);
718}
719
720static svn_error_t *
721svn_ra_local__get_file_revs(svn_ra_session_t *session,
722                            const char *path,
723                            svn_revnum_t start,
724                            svn_revnum_t end,
725                            svn_boolean_t include_merged_revisions,
726                            svn_file_rev_handler_t handler,
727                            void *handler_baton,
728                            apr_pool_t *pool)
729{
730  svn_ra_local__session_baton_t *sess = session->priv;
731  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
732  return svn_repos_get_file_revs2(sess->repos, abs_path, start, end,
733                                  include_merged_revisions, NULL, NULL,
734                                  handler, handler_baton, pool);
735}
736
737static svn_error_t *
738svn_ra_local__get_dated_revision(svn_ra_session_t *session,
739                                 svn_revnum_t *revision,
740                                 apr_time_t tm,
741                                 apr_pool_t *pool)
742{
743  svn_ra_local__session_baton_t *sess = session->priv;
744  return svn_repos_dated_revision(revision, sess->repos, tm, pool);
745}
746
747
748static svn_error_t *
749svn_ra_local__change_rev_prop(svn_ra_session_t *session,
750                              svn_revnum_t rev,
751                              const char *name,
752                              const svn_string_t *const *old_value_p,
753                              const svn_string_t *value,
754                              apr_pool_t *pool)
755{
756  svn_ra_local__session_baton_t *sess = session->priv;
757
758  SVN_ERR(get_username(session, pool));
759  return svn_repos_fs_change_rev_prop4(sess->repos, rev, sess->username,
760                                       name, old_value_p, value, TRUE, TRUE,
761                                       NULL, NULL, pool);
762}
763
764static svn_error_t *
765svn_ra_local__get_uuid(svn_ra_session_t *session,
766                       const char **uuid,
767                       apr_pool_t *pool)
768{
769  svn_ra_local__session_baton_t *sess = session->priv;
770  *uuid = sess->uuid;
771  return SVN_NO_ERROR;
772}
773
774static svn_error_t *
775svn_ra_local__get_repos_root(svn_ra_session_t *session,
776                             const char **url,
777                             apr_pool_t *pool)
778{
779  svn_ra_local__session_baton_t *sess = session->priv;
780  *url = sess->repos_url;
781  return SVN_NO_ERROR;
782}
783
784static svn_error_t *
785svn_ra_local__rev_proplist(svn_ra_session_t *session,
786                           svn_revnum_t rev,
787                           apr_hash_t **props,
788                           apr_pool_t *pool)
789{
790  svn_ra_local__session_baton_t *sess = session->priv;
791  return svn_repos_fs_revision_proplist(props, sess->repos, rev,
792                                        NULL, NULL, pool);
793}
794
795static svn_error_t *
796svn_ra_local__rev_prop(svn_ra_session_t *session,
797                       svn_revnum_t rev,
798                       const char *name,
799                       svn_string_t **value,
800                       apr_pool_t *pool)
801{
802  svn_ra_local__session_baton_t *sess = session->priv;
803  return svn_repos_fs_revision_prop(value, sess->repos, rev, name,
804                                    NULL, NULL, pool);
805}
806
807struct ccw_baton
808{
809  svn_commit_callback2_t original_callback;
810  void *original_baton;
811
812  svn_ra_session_t *session;
813};
814
815/* Wrapper which populates the repos_root field of the commit_info struct */
816static svn_error_t *
817commit_callback_wrapper(const svn_commit_info_t *commit_info,
818                        void *baton,
819                        apr_pool_t *scratch_pool)
820{
821  struct ccw_baton *ccwb = baton;
822  svn_commit_info_t *ci = svn_commit_info_dup(commit_info, scratch_pool);
823
824  SVN_ERR(svn_ra_local__get_repos_root(ccwb->session, &ci->repos_root,
825                                       scratch_pool));
826
827  return svn_error_trace(ccwb->original_callback(ci, ccwb->original_baton,
828                                                 scratch_pool));
829}
830
831
832/* The repository layer does not correctly fill in REPOS_ROOT in
833   commit_info, as it doesn't know the url that is used to access
834   it. This hooks the callback to fill in the missing pieces. */
835static void
836remap_commit_callback(svn_commit_callback2_t *callback,
837                      void **callback_baton,
838                      svn_ra_session_t *session,
839                      svn_commit_callback2_t original_callback,
840                      void *original_baton,
841                      apr_pool_t *result_pool)
842{
843  if (original_callback == NULL)
844    {
845      *callback = NULL;
846      *callback_baton = NULL;
847    }
848  else
849    {
850      /* Allocate this in RESULT_POOL, since the callback will be called
851         long after this function has returned. */
852      struct ccw_baton *ccwb = apr_palloc(result_pool, sizeof(*ccwb));
853
854      ccwb->session = session;
855      ccwb->original_callback = original_callback;
856      ccwb->original_baton = original_baton;
857
858      *callback = commit_callback_wrapper;
859      *callback_baton = ccwb;
860    }
861}
862
863static svn_error_t *
864svn_ra_local__get_commit_editor(svn_ra_session_t *session,
865                                const svn_delta_editor_t **editor,
866                                void **edit_baton,
867                                apr_hash_t *revprop_table,
868                                svn_commit_callback2_t callback,
869                                void *callback_baton,
870                                apr_hash_t *lock_tokens,
871                                svn_boolean_t keep_locks,
872                                apr_pool_t *pool)
873{
874  svn_ra_local__session_baton_t *sess = session->priv;
875  struct deltify_etc_baton *deb = apr_palloc(pool, sizeof(*deb));
876
877  /* Set repos_root_url in commit info */
878  remap_commit_callback(&callback, &callback_baton, session,
879                        callback, callback_baton, pool);
880
881  /* Prepare the baton for deltify_etc()  */
882  deb->fs = sess->fs;
883  deb->repos = sess->repos;
884  deb->fspath_base = sess->fs_path->data;
885  if (! keep_locks)
886    deb->lock_tokens = lock_tokens;
887  else
888    deb->lock_tokens = NULL;
889  deb->commit_cb = callback;
890  deb->commit_baton = callback_baton;
891
892  SVN_ERR(get_username(session, pool));
893
894  /* If there are lock tokens to add, do so. */
895  SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
896                            session->pool, pool));
897
898  /* Copy the revprops table so we can add the username. */
899  revprop_table = apr_hash_copy(pool, revprop_table);
900  svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
901                svn_string_create(sess->username, pool));
902  svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
903                svn_string_create(SVN_VER_NUMBER, pool));
904  svn_hash_sets(revprop_table, SVN_PROP_TXN_USER_AGENT,
905                svn_string_create(sess->useragent, pool));
906
907  /* Get the repos commit-editor */
908  return svn_repos_get_commit_editor5
909         (editor, edit_baton, sess->repos, NULL,
910          svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data,
911          revprop_table, deltify_etc, deb, NULL, NULL, pool);
912}
913
914
915/* Implements svn_repos_mergeinfo_receiver_t.
916 * It add MERGEINFO for PATH to the svn_mergeinfo_catalog_t BATON.
917 */
918static svn_error_t *
919mergeinfo_receiver(const char *path,
920                   svn_mergeinfo_t mergeinfo,
921                   void *baton,
922                   apr_pool_t *scratch_pool)
923{
924  svn_mergeinfo_catalog_t catalog = baton;
925  apr_pool_t *result_pool = apr_hash_pool_get(catalog);
926  apr_size_t len = strlen(path);
927
928  apr_hash_set(catalog,
929               apr_pstrmemdup(result_pool, path, len),
930               len,
931               svn_mergeinfo_dup(mergeinfo, result_pool));
932
933  return SVN_NO_ERROR;
934}
935
936
937static svn_error_t *
938svn_ra_local__get_mergeinfo(svn_ra_session_t *session,
939                            svn_mergeinfo_catalog_t *catalog,
940                            const apr_array_header_t *paths,
941                            svn_revnum_t revision,
942                            svn_mergeinfo_inheritance_t inherit,
943                            svn_boolean_t include_descendants,
944                            apr_pool_t *pool)
945{
946  svn_ra_local__session_baton_t *sess = session->priv;
947  svn_mergeinfo_catalog_t tmp_catalog = svn_hash__make(pool);
948  int i;
949  apr_array_header_t *abs_paths =
950    apr_array_make(pool, 0, sizeof(const char *));
951
952  for (i = 0; i < paths->nelts; i++)
953    {
954      const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
955      APR_ARRAY_PUSH(abs_paths, const char *) =
956        svn_fspath__join(sess->fs_path->data, relative_path, pool);
957    }
958
959  SVN_ERR(svn_repos_fs_get_mergeinfo2(sess->repos, abs_paths, revision,
960                                      inherit, include_descendants,
961                                      NULL, NULL,
962                                      mergeinfo_receiver, tmp_catalog,
963                                      pool));
964  if (apr_hash_count(tmp_catalog) > 0)
965    SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog,
966                                                      tmp_catalog,
967                                                      sess->fs_path->data,
968                                                      pool));
969  else
970    *catalog = NULL;
971
972  return SVN_NO_ERROR;
973}
974
975
976static svn_error_t *
977svn_ra_local__do_update(svn_ra_session_t *session,
978                        const svn_ra_reporter3_t **reporter,
979                        void **report_baton,
980                        svn_revnum_t update_revision,
981                        const char *update_target,
982                        svn_depth_t depth,
983                        svn_boolean_t send_copyfrom_args,
984                        svn_boolean_t ignore_ancestry,
985                        const svn_delta_editor_t *update_editor,
986                        void *update_baton,
987                        apr_pool_t *result_pool,
988                        apr_pool_t *scratch_pool)
989{
990  return make_reporter(session,
991                       reporter,
992                       report_baton,
993                       update_revision,
994                       update_target,
995                       NULL,
996                       TRUE,
997                       depth,
998                       send_copyfrom_args,
999                       ignore_ancestry,
1000                       update_editor,
1001                       update_baton,
1002                       result_pool, scratch_pool);
1003}
1004
1005
1006static svn_error_t *
1007svn_ra_local__do_switch(svn_ra_session_t *session,
1008                        const svn_ra_reporter3_t **reporter,
1009                        void **report_baton,
1010                        svn_revnum_t update_revision,
1011                        const char *update_target,
1012                        svn_depth_t depth,
1013                        const char *switch_url,
1014                        svn_boolean_t send_copyfrom_args,
1015                        svn_boolean_t ignore_ancestry,
1016                        const svn_delta_editor_t *update_editor,
1017                        void *update_baton,
1018                        apr_pool_t *result_pool,
1019                        apr_pool_t *scratch_pool)
1020{
1021  return make_reporter(session,
1022                       reporter,
1023                       report_baton,
1024                       update_revision,
1025                       update_target,
1026                       switch_url,
1027                       TRUE /* text_deltas */,
1028                       depth,
1029                       send_copyfrom_args,
1030                       ignore_ancestry,
1031                       update_editor,
1032                       update_baton,
1033                       result_pool, scratch_pool);
1034}
1035
1036
1037static svn_error_t *
1038svn_ra_local__do_status(svn_ra_session_t *session,
1039                        const svn_ra_reporter3_t **reporter,
1040                        void **report_baton,
1041                        const char *status_target,
1042                        svn_revnum_t revision,
1043                        svn_depth_t depth,
1044                        const svn_delta_editor_t *status_editor,
1045                        void *status_baton,
1046                        apr_pool_t *pool)
1047{
1048  return make_reporter(session,
1049                       reporter,
1050                       report_baton,
1051                       revision,
1052                       status_target,
1053                       NULL,
1054                       FALSE,
1055                       depth,
1056                       FALSE,
1057                       FALSE,
1058                       status_editor,
1059                       status_baton,
1060                       pool, pool);
1061}
1062
1063
1064static svn_error_t *
1065svn_ra_local__do_diff(svn_ra_session_t *session,
1066                      const svn_ra_reporter3_t **reporter,
1067                      void **report_baton,
1068                      svn_revnum_t update_revision,
1069                      const char *update_target,
1070                      svn_depth_t depth,
1071                      svn_boolean_t ignore_ancestry,
1072                      svn_boolean_t text_deltas,
1073                      const char *switch_url,
1074                      const svn_delta_editor_t *update_editor,
1075                      void *update_baton,
1076                      apr_pool_t *pool)
1077{
1078  return make_reporter(session,
1079                       reporter,
1080                       report_baton,
1081                       update_revision,
1082                       update_target,
1083                       switch_url,
1084                       text_deltas,
1085                       depth,
1086                       FALSE,
1087                       ignore_ancestry,
1088                       update_editor,
1089                       update_baton,
1090                       pool, pool);
1091}
1092
1093
1094struct log_baton
1095{
1096  svn_ra_local__session_baton_t *sess;
1097  svn_log_entry_receiver_t real_cb;
1098  void *real_baton;
1099};
1100
1101static svn_error_t *
1102log_receiver_wrapper(void *baton,
1103                     svn_log_entry_t *log_entry,
1104                     apr_pool_t *pool)
1105{
1106  struct log_baton *b = baton;
1107  svn_ra_local__session_baton_t *sess = b->sess;
1108
1109  if (sess->callbacks->cancel_func)
1110    SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton));
1111
1112  /* For consistency with the other RA layers, replace an empty
1113     changed-paths hash with a NULL one.
1114
1115     ### Should this be done by svn_ra_get_log2() instead, then? */
1116  if ((log_entry->changed_paths2)
1117      && (apr_hash_count(log_entry->changed_paths2) == 0))
1118    {
1119      log_entry->changed_paths = NULL;
1120      log_entry->changed_paths2 = NULL;
1121    }
1122
1123  return b->real_cb(b->real_baton, log_entry, pool);
1124}
1125
1126
1127static svn_error_t *
1128svn_ra_local__get_log(svn_ra_session_t *session,
1129                      const apr_array_header_t *paths,
1130                      svn_revnum_t start,
1131                      svn_revnum_t end,
1132                      int limit,
1133                      svn_boolean_t discover_changed_paths,
1134                      svn_boolean_t strict_node_history,
1135                      svn_boolean_t include_merged_revisions,
1136                      const apr_array_header_t *revprops,
1137                      svn_log_entry_receiver_t receiver,
1138                      void *receiver_baton,
1139                      apr_pool_t *pool)
1140{
1141  svn_ra_local__session_baton_t *sess = session->priv;
1142  struct log_baton lb;
1143  apr_array_header_t *abs_paths =
1144    apr_array_make(pool, 0, sizeof(const char *));
1145
1146  if (paths)
1147    {
1148      int i;
1149
1150      for (i = 0; i < paths->nelts; i++)
1151        {
1152          const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
1153          APR_ARRAY_PUSH(abs_paths, const char *) =
1154            svn_fspath__join(sess->fs_path->data, relative_path, pool);
1155        }
1156    }
1157
1158  lb.real_cb = receiver;
1159  lb.real_baton = receiver_baton;
1160  lb.sess = sess;
1161  receiver = log_receiver_wrapper;
1162  receiver_baton = &lb;
1163
1164  return svn_repos__get_logs_compat(sess->repos,
1165                                    abs_paths,
1166                                    start,
1167                                    end,
1168                                    limit,
1169                                    discover_changed_paths,
1170                                    strict_node_history,
1171                                    include_merged_revisions,
1172                                    revprops,
1173                                    NULL, NULL,
1174                                    receiver,
1175                                    receiver_baton,
1176                                    pool);
1177}
1178
1179
1180static svn_error_t *
1181svn_ra_local__do_check_path(svn_ra_session_t *session,
1182                            const char *path,
1183                            svn_revnum_t revision,
1184                            svn_node_kind_t *kind,
1185                            apr_pool_t *pool)
1186{
1187  svn_ra_local__session_baton_t *sess = session->priv;
1188  svn_fs_root_t *root;
1189  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1190
1191  if (! SVN_IS_VALID_REVNUM(revision))
1192    SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
1193  SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1194  return svn_fs_check_path(kind, root, abs_path, pool);
1195}
1196
1197
1198static svn_error_t *
1199svn_ra_local__stat(svn_ra_session_t *session,
1200                   const char *path,
1201                   svn_revnum_t revision,
1202                   svn_dirent_t **dirent,
1203                   apr_pool_t *pool)
1204{
1205  svn_ra_local__session_baton_t *sess = session->priv;
1206  svn_fs_root_t *root;
1207  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1208
1209  if (! SVN_IS_VALID_REVNUM(revision))
1210    SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
1211  SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1212
1213  return svn_repos_stat(dirent, root, abs_path, pool);
1214}
1215
1216
1217
1218
1219/* Obtain the properties for a node, including its 'entry props */
1220static svn_error_t *
1221get_node_props(apr_hash_t **props,
1222               svn_fs_root_t *root,
1223               const char *path,
1224               const char *uuid,
1225               apr_pool_t *result_pool,
1226               apr_pool_t *scratch_pool)
1227{
1228  svn_revnum_t cmt_rev;
1229  const char *cmt_date, *cmt_author;
1230
1231  /* Create a hash with props attached to the fs node. */
1232  SVN_ERR(svn_fs_node_proplist(props, root, path, result_pool));
1233
1234  /* Now add some non-tweakable metadata to the hash as well... */
1235
1236  /* The so-called 'entryprops' with info about CR & friends. */
1237  SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date,
1238                                       &cmt_author, root, path,
1239                                       scratch_pool));
1240
1241  svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV,
1242                svn_string_createf(result_pool, "%ld", cmt_rev));
1243  svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, cmt_date ?
1244                svn_string_create(cmt_date, result_pool) : NULL);
1245  svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, cmt_author ?
1246                svn_string_create(cmt_author, result_pool) : NULL);
1247  svn_hash_sets(*props, SVN_PROP_ENTRY_UUID,
1248                svn_string_create(uuid, result_pool));
1249
1250  /* We have no 'wcprops' in ra_local, but might someday. */
1251
1252  return SVN_NO_ERROR;
1253}
1254
1255
1256/* Getting just one file. */
1257static svn_error_t *
1258svn_ra_local__get_file(svn_ra_session_t *session,
1259                       const char *path,
1260                       svn_revnum_t revision,
1261                       svn_stream_t *stream,
1262                       svn_revnum_t *fetched_rev,
1263                       apr_hash_t **props,
1264                       apr_pool_t *pool)
1265{
1266  svn_fs_root_t *root;
1267  svn_stream_t *contents;
1268  svn_revnum_t youngest_rev;
1269  svn_ra_local__session_baton_t *sess = session->priv;
1270  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1271  svn_node_kind_t node_kind;
1272
1273  /* Open the revision's root. */
1274  if (! SVN_IS_VALID_REVNUM(revision))
1275    {
1276      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
1277      SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
1278      if (fetched_rev != NULL)
1279        *fetched_rev = youngest_rev;
1280    }
1281  else
1282    SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1283
1284  SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, pool));
1285  if (node_kind == svn_node_none)
1286    {
1287      return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1288                               _("'%s' path not found"), abs_path);
1289    }
1290  else if (node_kind != svn_node_file)
1291    {
1292      return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
1293                               _("'%s' is not a file"), abs_path);
1294    }
1295
1296  if (stream)
1297    {
1298      /* Get a stream representing the file's contents. */
1299      SVN_ERR(svn_fs_file_contents(&contents, root, abs_path, pool));
1300
1301      /* Now push data from the fs stream back at the caller's stream.
1302         Note that this particular RA layer does not computing a
1303         checksum as we go, and confirming it against the repository's
1304         checksum when done.  That's because it calls
1305         svn_fs_file_contents() directly, which already checks the
1306         stored checksum, and all we're doing here is writing bytes in
1307         a loop.  Truly, Nothing Can Go Wrong :-).  But RA layers that
1308         go over a network should confirm the checksum.
1309
1310         Note: we are not supposed to close the passed-in stream, so
1311         disown the thing.
1312      */
1313      SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(stream, pool),
1314                               sess->callbacks
1315                                 ? sess->callbacks->cancel_func : NULL,
1316                               sess->callback_baton,
1317                               pool));
1318    }
1319
1320  /* Handle props if requested. */
1321  if (props)
1322    SVN_ERR(get_node_props(props, root, abs_path, sess->uuid, pool, pool));
1323
1324  return SVN_NO_ERROR;
1325}
1326
1327
1328
1329/* Getting a directory's entries */
1330static svn_error_t *
1331svn_ra_local__get_dir(svn_ra_session_t *session,
1332                      apr_hash_t **dirents,
1333                      svn_revnum_t *fetched_rev,
1334                      apr_hash_t **props,
1335                      const char *path,
1336                      svn_revnum_t revision,
1337                      apr_uint32_t dirent_fields,
1338                      apr_pool_t *pool)
1339{
1340  svn_fs_root_t *root;
1341  svn_revnum_t youngest_rev;
1342  apr_hash_t *entries;
1343  apr_hash_index_t *hi;
1344  svn_ra_local__session_baton_t *sess = session->priv;
1345  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1346
1347  /* Open the revision's root. */
1348  if (! SVN_IS_VALID_REVNUM(revision))
1349    {
1350      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
1351      SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
1352      if (fetched_rev != NULL)
1353        *fetched_rev = youngest_rev;
1354    }
1355  else
1356    SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1357
1358  if (dirents)
1359    {
1360      apr_pool_t *iterpool = svn_pool_create(pool);
1361      /* Get the dir's entries. */
1362      SVN_ERR(svn_fs_dir_entries(&entries, root, abs_path, pool));
1363
1364      /* Loop over the fs dirents, and build a hash of general
1365         svn_dirent_t's. */
1366      *dirents = apr_hash_make(pool);
1367      for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1368        {
1369          const void *key;
1370          void *val;
1371          const char *datestring, *entryname, *fullpath;
1372          svn_fs_dirent_t *fs_entry;
1373          svn_dirent_t *entry = svn_dirent_create(pool);
1374
1375          svn_pool_clear(iterpool);
1376
1377          apr_hash_this(hi, &key, NULL, &val);
1378          entryname = (const char *) key;
1379          fs_entry = (svn_fs_dirent_t *) val;
1380
1381          fullpath = svn_dirent_join(abs_path, entryname, iterpool);
1382
1383          if (dirent_fields & SVN_DIRENT_KIND)
1384            {
1385              /* node kind */
1386              entry->kind = fs_entry->kind;
1387            }
1388
1389          if (dirent_fields & SVN_DIRENT_SIZE)
1390            {
1391              /* size  */
1392              if (fs_entry->kind == svn_node_dir)
1393                entry->size = SVN_INVALID_FILESIZE;
1394              else
1395                SVN_ERR(svn_fs_file_length(&(entry->size), root,
1396                                           fullpath, iterpool));
1397            }
1398
1399          if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1400            {
1401              /* has_props? */
1402              SVN_ERR(svn_fs_node_has_props(&entry->has_props,
1403                                            root, fullpath,
1404                                            iterpool));
1405            }
1406
1407          if ((dirent_fields & SVN_DIRENT_TIME)
1408              || (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1409              || (dirent_fields & SVN_DIRENT_CREATED_REV))
1410            {
1411              /* created_rev & friends */
1412              SVN_ERR(svn_repos_get_committed_info(&(entry->created_rev),
1413                                                   &datestring,
1414                                                   &(entry->last_author),
1415                                                   root, fullpath, iterpool));
1416              if (datestring)
1417                SVN_ERR(svn_time_from_cstring(&(entry->time), datestring,
1418                                              pool));
1419              if (entry->last_author)
1420                entry->last_author = apr_pstrdup(pool, entry->last_author);
1421            }
1422
1423          /* Store. */
1424          svn_hash_sets(*dirents, entryname, entry);
1425        }
1426      svn_pool_destroy(iterpool);
1427    }
1428
1429  /* Handle props if requested. */
1430  if (props)
1431    SVN_ERR(get_node_props(props, root, abs_path, sess->uuid, pool, pool));
1432
1433  return SVN_NO_ERROR;
1434}
1435
1436
1437static svn_error_t *
1438svn_ra_local__get_locations(svn_ra_session_t *session,
1439                            apr_hash_t **locations,
1440                            const char *path,
1441                            svn_revnum_t peg_revision,
1442                            const apr_array_header_t *location_revisions,
1443                            apr_pool_t *pool)
1444{
1445  svn_ra_local__session_baton_t *sess = session->priv;
1446  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1447  return svn_repos_trace_node_locations(sess->fs, locations, abs_path,
1448                                        peg_revision, location_revisions,
1449                                        NULL, NULL, pool);
1450}
1451
1452
1453static svn_error_t *
1454svn_ra_local__get_location_segments(svn_ra_session_t *session,
1455                                    const char *path,
1456                                    svn_revnum_t peg_revision,
1457                                    svn_revnum_t start_rev,
1458                                    svn_revnum_t end_rev,
1459                                    svn_location_segment_receiver_t receiver,
1460                                    void *receiver_baton,
1461                                    apr_pool_t *pool)
1462{
1463  svn_ra_local__session_baton_t *sess = session->priv;
1464  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1465  return svn_repos_node_location_segments(sess->repos, abs_path,
1466                                          peg_revision, start_rev, end_rev,
1467                                          receiver, receiver_baton,
1468                                          NULL, NULL, pool);
1469}
1470
1471struct lock_baton_t {
1472  svn_ra_lock_callback_t lock_func;
1473  void *lock_baton;
1474  const char *fs_path;
1475  svn_boolean_t is_lock;
1476  svn_error_t *cb_err;
1477};
1478
1479/* Implements svn_fs_lock_callback_t.  Used by svn_ra_local__lock and
1480   svn_ra_local__unlock to forward to supplied callback and record any
1481   callback error. */
1482static svn_error_t *
1483lock_cb(void *lock_baton,
1484        const char *path,
1485        const svn_lock_t *lock,
1486        svn_error_t *fs_err,
1487        apr_pool_t *pool)
1488{
1489  struct lock_baton_t *b = lock_baton;
1490
1491  if (b && !b->cb_err && b->lock_func)
1492    {
1493      path = svn_fspath__skip_ancestor(b->fs_path, path);
1494      b->cb_err = b->lock_func(b->lock_baton, path, b->is_lock, lock, fs_err,
1495                               pool);
1496    }
1497
1498  return SVN_NO_ERROR;
1499}
1500
1501static svn_error_t *
1502svn_ra_local__lock(svn_ra_session_t *session,
1503                   apr_hash_t *path_revs,
1504                   const char *comment,
1505                   svn_boolean_t force,
1506                   svn_ra_lock_callback_t lock_func,
1507                   void *lock_baton,
1508                   apr_pool_t *pool)
1509{
1510  svn_ra_local__session_baton_t *sess = session->priv;
1511  apr_hash_t *targets = apr_hash_make(pool);
1512  apr_hash_index_t *hi;
1513  svn_error_t *err;
1514  struct lock_baton_t baton = {0};
1515
1516  /* A username is absolutely required to lock a path. */
1517  SVN_ERR(get_username(session, pool));
1518
1519  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1520    {
1521      const char *abs_path = svn_fspath__join(sess->fs_path->data,
1522                                              apr_hash_this_key(hi), pool);
1523      svn_revnum_t current_rev = *(svn_revnum_t *)apr_hash_this_val(hi);
1524      svn_fs_lock_target_t *target = svn_fs_lock_target_create(NULL,
1525                                                               current_rev,
1526                                                               pool);
1527
1528      svn_hash_sets(targets, abs_path, target);
1529    }
1530
1531  baton.lock_func = lock_func;
1532  baton.lock_baton = lock_baton;
1533  baton.fs_path = sess->fs_path->data;
1534  baton.is_lock = TRUE;
1535  baton.cb_err = SVN_NO_ERROR;
1536
1537  err = svn_repos_fs_lock_many(sess->repos, targets, comment,
1538                               FALSE /* not DAV comment */,
1539                               0 /* no expiration */, force,
1540                               lock_cb, &baton,
1541                               pool, pool);
1542
1543  if (err && baton.cb_err)
1544    svn_error_compose(err, baton.cb_err);
1545  else if (!err)
1546    err = baton.cb_err;
1547
1548  return svn_error_trace(err);
1549}
1550
1551
1552static svn_error_t *
1553svn_ra_local__unlock(svn_ra_session_t *session,
1554                     apr_hash_t *path_tokens,
1555                     svn_boolean_t force,
1556                     svn_ra_lock_callback_t lock_func,
1557                     void *lock_baton,
1558                     apr_pool_t *pool)
1559{
1560  svn_ra_local__session_baton_t *sess = session->priv;
1561  apr_hash_t *targets = apr_hash_make(pool);
1562  apr_hash_index_t *hi;
1563  svn_error_t *err;
1564  struct lock_baton_t baton = {0};
1565
1566  /* A username is absolutely required to unlock a path. */
1567  SVN_ERR(get_username(session, pool));
1568
1569  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1570    {
1571      const char *abs_path = svn_fspath__join(sess->fs_path->data,
1572                                              apr_hash_this_key(hi), pool);
1573      const char *token = apr_hash_this_val(hi);
1574
1575      svn_hash_sets(targets, abs_path, token);
1576    }
1577
1578  baton.lock_func = lock_func;
1579  baton.lock_baton = lock_baton;
1580  baton.fs_path = sess->fs_path->data;
1581  baton.is_lock = FALSE;
1582  baton.cb_err = SVN_NO_ERROR;
1583
1584  err = svn_repos_fs_unlock_many(sess->repos, targets, force, lock_cb, &baton,
1585                                 pool, pool);
1586
1587  if (err && baton.cb_err)
1588    svn_error_compose(err, baton.cb_err);
1589  else if (!err)
1590    err = baton.cb_err;
1591
1592  return svn_error_trace(err);
1593}
1594
1595
1596
1597static svn_error_t *
1598svn_ra_local__get_lock(svn_ra_session_t *session,
1599                       svn_lock_t **lock,
1600                       const char *path,
1601                       apr_pool_t *pool)
1602{
1603  svn_ra_local__session_baton_t *sess = session->priv;
1604  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1605  return svn_fs_get_lock(lock, sess->fs, abs_path, pool);
1606}
1607
1608
1609
1610static svn_error_t *
1611svn_ra_local__get_locks(svn_ra_session_t *session,
1612                        apr_hash_t **locks,
1613                        const char *path,
1614                        svn_depth_t depth,
1615                        apr_pool_t *pool)
1616{
1617  svn_ra_local__session_baton_t *sess = session->priv;
1618  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1619
1620  /* Kinda silly to call the repos wrapper, since we have no authz
1621     func to give it.  But heck, why not. */
1622  return svn_repos_fs_get_locks2(locks, sess->repos, abs_path, depth,
1623                                 NULL, NULL, pool);
1624}
1625
1626
1627static svn_error_t *
1628svn_ra_local__replay(svn_ra_session_t *session,
1629                     svn_revnum_t revision,
1630                     svn_revnum_t low_water_mark,
1631                     svn_boolean_t send_deltas,
1632                     const svn_delta_editor_t *editor,
1633                     void *edit_baton,
1634                     apr_pool_t *pool)
1635{
1636  svn_ra_local__session_baton_t *sess = session->priv;
1637  svn_fs_root_t *root;
1638
1639  SVN_ERR(svn_fs_revision_root(&root, svn_repos_fs(sess->repos),
1640                               revision, pool));
1641  return svn_repos_replay2(root, sess->fs_path->data, low_water_mark,
1642                           send_deltas, editor, edit_baton, NULL, NULL,
1643                           pool);
1644}
1645
1646
1647static svn_error_t *
1648svn_ra_local__replay_range(svn_ra_session_t *session,
1649                           svn_revnum_t start_revision,
1650                           svn_revnum_t end_revision,
1651                           svn_revnum_t low_water_mark,
1652                           svn_boolean_t send_deltas,
1653                           svn_ra_replay_revstart_callback_t revstart_func,
1654                           svn_ra_replay_revfinish_callback_t revfinish_func,
1655                           void *replay_baton,
1656                           apr_pool_t *pool)
1657{
1658  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
1659}
1660
1661
1662static svn_error_t *
1663svn_ra_local__has_capability(svn_ra_session_t *session,
1664                             svn_boolean_t *has,
1665                             const char *capability,
1666                             apr_pool_t *pool)
1667{
1668  svn_ra_local__session_baton_t *sess = session->priv;
1669
1670  if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0
1671      || strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0
1672      || strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0
1673      || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0
1674      || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0
1675      || strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0
1676      || strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0
1677      || strcmp(capability, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) == 0
1678      || strcmp(capability, SVN_RA_CAPABILITY_LIST) == 0
1679      )
1680    {
1681      *has = TRUE;
1682    }
1683  else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
1684    {
1685      /* With mergeinfo, the code's capabilities may not reflect the
1686         repository's, so inquire further. */
1687      SVN_ERR(svn_repos_has_capability(sess->repos, has,
1688                                       SVN_REPOS_CAPABILITY_MERGEINFO,
1689                                       pool));
1690    }
1691  else  /* Don't know any other capabilities, so error. */
1692    {
1693      return svn_error_createf
1694        (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
1695         _("Don't know anything about capability '%s'"), capability);
1696    }
1697
1698  return SVN_NO_ERROR;
1699}
1700
1701static svn_error_t *
1702svn_ra_local__get_deleted_rev(svn_ra_session_t *session,
1703                              const char *path,
1704                              svn_revnum_t peg_revision,
1705                              svn_revnum_t end_revision,
1706                              svn_revnum_t *revision_deleted,
1707                              apr_pool_t *pool)
1708{
1709  svn_ra_local__session_baton_t *sess = session->priv;
1710  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1711
1712  SVN_ERR(svn_repos_deleted_rev(sess->fs,
1713                                abs_path,
1714                                peg_revision,
1715                                end_revision,
1716                                revision_deleted,
1717                                pool));
1718
1719  return SVN_NO_ERROR;
1720}
1721
1722static svn_error_t *
1723svn_ra_local__get_inherited_props(svn_ra_session_t *session,
1724                                  apr_array_header_t **iprops,
1725                                  const char *path,
1726                                  svn_revnum_t revision,
1727                                  apr_pool_t *result_pool,
1728                                  apr_pool_t *scratch_pool)
1729{
1730  svn_fs_root_t *root;
1731  svn_ra_local__session_baton_t *sess = session->priv;
1732  const char *abs_path = svn_fspath__join(sess->fs_path->data, path,
1733                                          scratch_pool);
1734  svn_node_kind_t node_kind;
1735
1736  /* Open the revision's root. */
1737  SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, scratch_pool));
1738
1739  SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, scratch_pool));
1740  if (node_kind == svn_node_none)
1741    {
1742      return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1743                               _("'%s' path not found"), abs_path);
1744    }
1745
1746  return svn_error_trace(
1747                svn_repos_fs_get_inherited_props(iprops, root, abs_path,
1748                                                 NULL /* propname */,
1749                                                 NULL, NULL /* auth */,
1750                                                 result_pool, scratch_pool));
1751}
1752
1753static svn_error_t *
1754svn_ra_local__register_editor_shim_callbacks(svn_ra_session_t *session,
1755                                    svn_delta_shim_callbacks_t *callbacks)
1756{
1757  /* This is currenly a no-op, since we don't provide our own editor, just
1758     use the one the libsvn_repos hands back to us. */
1759  return SVN_NO_ERROR;
1760}
1761
1762
1763static svn_error_t *
1764svn_ra_local__get_commit_ev2(svn_editor_t **editor,
1765                             svn_ra_session_t *session,
1766                             apr_hash_t *revprops,
1767                             svn_commit_callback2_t commit_cb,
1768                             void *commit_baton,
1769                             apr_hash_t *lock_tokens,
1770                             svn_boolean_t keep_locks,
1771                             svn_ra__provide_base_cb_t provide_base_cb,
1772                             svn_ra__provide_props_cb_t provide_props_cb,
1773                             svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
1774                             void *cb_baton,
1775                             svn_cancel_func_t cancel_func,
1776                             void *cancel_baton,
1777                             apr_pool_t *result_pool,
1778                             apr_pool_t *scratch_pool)
1779{
1780  svn_ra_local__session_baton_t *sess = session->priv;
1781  struct deltify_etc_baton *deb = apr_palloc(result_pool, sizeof(*deb));
1782
1783  remap_commit_callback(&commit_cb, &commit_baton, session,
1784                        commit_cb, commit_baton, result_pool);
1785
1786  /* NOTE: the RA callbacks are ignored. We pass everything directly to
1787     the REPOS editor.  */
1788
1789  /* Prepare the baton for deltify_etc()  */
1790  deb->fs = sess->fs;
1791  deb->repos = sess->repos;
1792  deb->fspath_base = sess->fs_path->data;
1793  if (! keep_locks)
1794    deb->lock_tokens = lock_tokens;
1795  else
1796    deb->lock_tokens = NULL;
1797  deb->commit_cb = commit_cb;
1798  deb->commit_baton = commit_baton;
1799
1800  /* Ensure there is a username (and an FS access context) associated with
1801     the session and its FS handle.  */
1802  SVN_ERR(get_username(session, scratch_pool));
1803
1804  /* If there are lock tokens to add, do so.  */
1805  SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
1806                            session->pool, scratch_pool));
1807
1808  /* Copy the REVPROPS and insert the author/username.  */
1809  revprops = apr_hash_copy(scratch_pool, revprops);
1810  svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR,
1811                svn_string_create(sess->username, scratch_pool));
1812
1813  return svn_error_trace(svn_repos__get_commit_ev2(
1814                           editor, sess->repos, NULL /* authz */,
1815                           NULL /* authz_repos_name */, NULL /* authz_user */,
1816                           revprops,
1817                           deltify_etc, deb, cancel_func, cancel_baton,
1818                           result_pool, scratch_pool));
1819}
1820
1821/* Trivially forward repos-layer callbacks to RA-layer callbacks.
1822 * Their signatures are the same. */
1823typedef struct dirent_receiver_baton_t
1824{
1825  svn_ra_dirent_receiver_t receiver;
1826  void *receiver_baton;
1827} dirent_receiver_baton_t;
1828
1829static svn_error_t *
1830dirent_receiver(const char *rel_path,
1831                svn_dirent_t *dirent,
1832                void *baton,
1833                apr_pool_t *pool)
1834{
1835  dirent_receiver_baton_t *b = baton;
1836  return b->receiver(rel_path, dirent, b->receiver_baton, pool);
1837}
1838
1839static svn_error_t *
1840svn_ra_local__list(svn_ra_session_t *session,
1841                   const char *path,
1842                   svn_revnum_t revision,
1843                   const apr_array_header_t *patterns,
1844                   svn_depth_t depth,
1845                   apr_uint32_t dirent_fields,
1846                   svn_ra_dirent_receiver_t receiver,
1847                   void *receiver_baton,
1848                   apr_pool_t *pool)
1849{
1850  svn_ra_local__session_baton_t *sess = session->priv;
1851  svn_fs_root_t *root;
1852  svn_boolean_t path_info_only = (dirent_fields & ~SVN_DIRENT_KIND) == 0;
1853
1854  dirent_receiver_baton_t baton;
1855  baton.receiver = receiver;
1856  baton.receiver_baton = receiver_baton;
1857
1858  SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1859  path = svn_dirent_join(sess->fs_path->data, path, pool);
1860  return svn_error_trace(svn_repos_list(root, path, patterns, depth,
1861                                        path_info_only, NULL, NULL,
1862                                        dirent_receiver, &baton,
1863                                        sess->callbacks
1864                                          ? sess->callbacks->cancel_func
1865                                          : NULL,
1866                                        sess->callback_baton, pool));
1867}
1868
1869/*----------------------------------------------------------------*/
1870
1871static const svn_version_t *
1872ra_local_version(void)
1873{
1874  SVN_VERSION_BODY;
1875}
1876
1877/** The ra_vtable **/
1878
1879static const svn_ra__vtable_t ra_local_vtable =
1880{
1881  ra_local_version,
1882  svn_ra_local__get_description,
1883  svn_ra_local__get_schemes,
1884  svn_ra_local__open,
1885  svn_ra_local__dup_session,
1886  svn_ra_local__reparent,
1887  svn_ra_local__get_session_url,
1888  svn_ra_local__get_latest_revnum,
1889  svn_ra_local__get_dated_revision,
1890  svn_ra_local__change_rev_prop,
1891  svn_ra_local__rev_proplist,
1892  svn_ra_local__rev_prop,
1893  svn_ra_local__get_commit_editor,
1894  svn_ra_local__get_file,
1895  svn_ra_local__get_dir,
1896  svn_ra_local__get_mergeinfo,
1897  svn_ra_local__do_update,
1898  svn_ra_local__do_switch,
1899  svn_ra_local__do_status,
1900  svn_ra_local__do_diff,
1901  svn_ra_local__get_log,
1902  svn_ra_local__do_check_path,
1903  svn_ra_local__stat,
1904  svn_ra_local__get_uuid,
1905  svn_ra_local__get_repos_root,
1906  svn_ra_local__get_locations,
1907  svn_ra_local__get_location_segments,
1908  svn_ra_local__get_file_revs,
1909  svn_ra_local__lock,
1910  svn_ra_local__unlock,
1911  svn_ra_local__get_lock,
1912  svn_ra_local__get_locks,
1913  svn_ra_local__replay,
1914  svn_ra_local__has_capability,
1915  svn_ra_local__replay_range,
1916  svn_ra_local__get_deleted_rev,
1917  svn_ra_local__get_inherited_props,
1918  NULL /* set_svn_ra_open */,
1919  svn_ra_local__list ,
1920  svn_ra_local__register_editor_shim_callbacks,
1921  svn_ra_local__get_commit_ev2,
1922  NULL /* replay_range_ev2 */
1923};
1924
1925
1926/*----------------------------------------------------------------*/
1927
1928/** The One Public Routine, called by libsvn_ra **/
1929
1930svn_error_t *
1931svn_ra_local__init(const svn_version_t *loader_version,
1932                   const svn_ra__vtable_t **vtable,
1933                   apr_pool_t *pool)
1934{
1935  static const svn_version_checklist_t checklist[] =
1936    {
1937      { "svn_subr",  svn_subr_version },
1938      { "svn_delta", svn_delta_version },
1939      { "svn_repos", svn_repos_version },
1940      { "svn_fs",    svn_fs_version },
1941      { NULL, NULL }
1942    };
1943
1944
1945  /* Simplified version check to make sure we can safely use the
1946     VTABLE parameter. The RA loader does a more exhaustive check. */
1947  if (loader_version->major != SVN_VER_MAJOR)
1948    return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
1949                             _("Unsupported RA loader version (%d) for "
1950                               "ra_local"),
1951                             loader_version->major);
1952
1953  SVN_ERR(svn_ver_check_list2(ra_local_version(), checklist, svn_ver_equal));
1954
1955#ifndef SVN_LIBSVN_RA_LINKS_RA_LOCAL
1956  /* This means the library was loaded as a DSO, so use the DSO pool. */
1957  SVN_ERR(svn_fs_initialize(svn_dso__pool()));
1958#endif
1959
1960  *vtable = &ra_local_vtable;
1961
1962  return SVN_NO_ERROR;
1963}
1964
1965/* Compatibility wrapper for the 1.1 and before API. */
1966#define NAME "ra_local"
1967#define DESCRIPTION RA_LOCAL_DESCRIPTION
1968#define VTBL ra_local_vtable
1969#define INITFUNC svn_ra_local__init
1970#define COMPAT_INITFUNC svn_ra_local_init
1971#include "../libsvn_ra/wrapper_template.h"
1972