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->callbacks->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->callbacks->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, (char *)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                                  (char *)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                                  1024 * 1024,  /* process-local transfers
364                                                   should be fast */
365                                  result_pool));
366
367  /* Wrap the report baton given us by the repos layer with our own
368     reporter baton. */
369  *report_baton = make_reporter_baton(sess, rbaton, result_pool);
370
371  return SVN_NO_ERROR;
372}
373
374
375/*----------------------------------------------------------------*/
376
377/*** Deltification stuff for get_commit_editor() ***/
378
379struct deltify_etc_baton
380{
381  svn_fs_t *fs;                     /* the fs to deltify in */
382  svn_repos_t *repos;               /* repos for unlocking */
383  const char *fspath_base;          /* fs-path part of split session URL */
384
385  apr_hash_t *lock_tokens;          /* tokens to unlock, if any */
386
387  svn_commit_callback2_t commit_cb; /* the original callback */
388  void *commit_baton;               /* the original callback's baton */
389};
390
391/* This implements 'svn_commit_callback_t'.  Its invokes the original
392   (wrapped) callback, but also does deltification on the new revision and
393   possibly unlocks committed paths.
394   BATON is 'struct deltify_etc_baton *'. */
395static svn_error_t *
396deltify_etc(const svn_commit_info_t *commit_info,
397            void *baton,
398            apr_pool_t *scratch_pool)
399{
400  struct deltify_etc_baton *deb = baton;
401  svn_error_t *err1 = SVN_NO_ERROR;
402  svn_error_t *err2;
403
404  /* Invoke the original callback first, in case someone's waiting to
405     know the revision number so they can go off and annotate an
406     issue or something. */
407  if (deb->commit_cb)
408    err1 = deb->commit_cb(commit_info, deb->commit_baton, scratch_pool);
409
410  /* Maybe unlock the paths. */
411  if (deb->lock_tokens)
412    {
413      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
414      apr_hash_index_t *hi;
415
416      for (hi = apr_hash_first(scratch_pool, deb->lock_tokens); hi;
417           hi = apr_hash_next(hi))
418        {
419          const void *relpath = svn__apr_hash_index_key(hi);
420          const char *token = svn__apr_hash_index_val(hi);
421          const char *fspath;
422
423          svn_pool_clear(iterpool);
424
425          fspath = svn_fspath__join(deb->fspath_base, relpath, iterpool);
426
427          /* We may get errors here if the lock was broken or stolen
428             after the commit succeeded.  This is fine and should be
429             ignored. */
430          svn_error_clear(svn_repos_fs_unlock(deb->repos, fspath, token,
431                                              FALSE, iterpool));
432        }
433
434      svn_pool_destroy(iterpool);
435    }
436
437  /* But, deltification shouldn't be stopped just because someone's
438     random callback failed, so proceed unconditionally on to
439     deltification. */
440  err2 = svn_fs_deltify_revision(deb->fs, commit_info->revision, scratch_pool);
441
442  return svn_error_compose_create(err1, err2);
443}
444
445
446/* If LOCK_TOKENS is not NULL, then copy all tokens into the access context
447   of FS. The tokens' paths will be prepended with FSPATH_BASE.
448
449   ACCESS_POOL must match (or exceed) the lifetime of the access context
450   that was associated with FS. Typically, this is the session pool.
451
452   Temporary allocations are made in SCRATCH_POOL.  */
453static svn_error_t *
454apply_lock_tokens(svn_fs_t *fs,
455                  const char *fspath_base,
456                  apr_hash_t *lock_tokens,
457                  apr_pool_t *access_pool,
458                  apr_pool_t *scratch_pool)
459{
460  if (lock_tokens)
461    {
462      svn_fs_access_t *access_ctx;
463
464      SVN_ERR(svn_fs_get_access(&access_ctx, fs));
465
466      /* If there is no access context, the filesystem will scream if a
467         lock is needed.  */
468      if (access_ctx)
469        {
470          apr_hash_index_t *hi;
471
472          /* Note: we have no use for an iterpool here since the data
473             within the loop is copied into ACCESS_POOL.  */
474
475          for (hi = apr_hash_first(scratch_pool, lock_tokens); hi;
476               hi = apr_hash_next(hi))
477            {
478              const void *relpath = svn__apr_hash_index_key(hi);
479              const char *token = svn__apr_hash_index_val(hi);
480              const char *fspath;
481
482              /* The path needs to live as long as ACCESS_CTX.  */
483              fspath = svn_fspath__join(fspath_base, relpath, access_pool);
484
485              /* The token must live as long as ACCESS_CTX.  */
486              token = apr_pstrdup(access_pool, token);
487
488              SVN_ERR(svn_fs_access_add_lock_token2(access_ctx, fspath,
489                                                    token));
490            }
491        }
492    }
493
494  return SVN_NO_ERROR;
495}
496
497
498/*----------------------------------------------------------------*/
499
500/*** The RA vtable routines ***/
501
502#define RA_LOCAL_DESCRIPTION \
503        N_("Module for accessing a repository on local disk.")
504
505static const char *
506svn_ra_local__get_description(apr_pool_t *pool)
507{
508  return _(RA_LOCAL_DESCRIPTION);
509}
510
511static const char * const *
512svn_ra_local__get_schemes(apr_pool_t *pool)
513{
514  static const char *schemes[] = { "file", NULL };
515
516  return schemes;
517}
518
519/* Do nothing.
520 *
521 * Why is this acceptable?  FS warnings used to be used for only
522 * two things: failures to close BDB repositories and failures to
523 * interact with memcached in FSFS (new in 1.6).  In 1.5 and earlier,
524 * we did not call svn_fs_set_warning_func in ra_local, which means
525 * that any BDB-closing failure would have led to abort()s; the fact
526 * that this hasn't led to huge hues and cries makes it seem likely
527 * that this just doesn't happen that often, at least not through
528 * ra_local.  And as far as memcached goes, it seems unlikely that
529 * somebody is going to go through the trouble of setting up and
530 * running memcached servers but then use ra_local access.  So we
531 * ignore errors here, so that memcached can use the FS warnings API
532 * without crashing ra_local.
533 */
534static void
535ignore_warnings(void *baton,
536                svn_error_t *err)
537{
538#ifdef SVN_DEBUG
539  SVN_DBG(("Ignoring FS warning %d\n", err ? err->apr_err : 0));
540#endif
541  return;
542}
543
544static svn_error_t *
545svn_ra_local__open(svn_ra_session_t *session,
546                   const char **corrected_url,
547                   const char *repos_URL,
548                   const svn_ra_callbacks2_t *callbacks,
549                   void *callback_baton,
550                   apr_hash_t *config,
551                   apr_pool_t *pool)
552{
553  svn_ra_local__session_baton_t *sess;
554  const char *fs_path;
555  static volatile svn_atomic_t cache_init_state = 0;
556
557  /* Initialise the FSFS memory cache size.  We can only do this once
558     so one CONFIG will win the race and all others will be ignored
559     silently.  */
560  SVN_ERR(svn_atomic__init_once(&cache_init_state, cache_init, config, pool));
561
562  /* We don't support redirections in ra-local. */
563  if (corrected_url)
564    *corrected_url = NULL;
565
566  /* Allocate and stash the session_sess args we have already. */
567  sess = apr_pcalloc(pool, sizeof(*sess));
568  sess->callbacks = callbacks;
569  sess->callback_baton = callback_baton;
570
571  /* Look through the URL, figure out which part points to the
572     repository, and which part is the path *within* the
573     repository. */
574  SVN_ERR_W(svn_ra_local__split_URL(&(sess->repos),
575                                    &(sess->repos_url),
576                                    &fs_path,
577                                    repos_URL,
578                                    session->pool),
579            _("Unable to open an ra_local session to URL"));
580  sess->fs_path = svn_stringbuf_create(fs_path, session->pool);
581
582  /* Cache the filesystem object from the repos here for
583     convenience. */
584  sess->fs = svn_repos_fs(sess->repos);
585
586  /* Ignore FS warnings. */
587  svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL);
588
589  /* Cache the repository UUID as well */
590  SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));
591
592  /* Be sure username is NULL so we know to look it up / ask for it */
593  sess->username = NULL;
594
595  session->priv = sess;
596  return SVN_NO_ERROR;
597}
598
599static svn_error_t *
600svn_ra_local__reparent(svn_ra_session_t *session,
601                       const char *url,
602                       apr_pool_t *pool)
603{
604  svn_ra_local__session_baton_t *sess = session->priv;
605  const char *relpath = svn_uri_skip_ancestor(sess->repos_url, url, pool);
606
607  /* If the new URL isn't the same as our repository root URL, then
608     let's ensure that it's some child of it. */
609  if (! relpath)
610    return svn_error_createf
611      (SVN_ERR_RA_ILLEGAL_URL, NULL,
612       _("URL '%s' is not a child of the session's repository root "
613         "URL '%s'"), url, sess->repos_url);
614
615  /* Update our FS_PATH sess member to point to our new
616     relative-URL-turned-absolute-filesystem-path. */
617  svn_stringbuf_set(sess->fs_path,
618                    svn_fspath__canonicalize(relpath, pool));
619
620  return SVN_NO_ERROR;
621}
622
623static svn_error_t *
624svn_ra_local__get_session_url(svn_ra_session_t *session,
625                              const char **url,
626                              apr_pool_t *pool)
627{
628  svn_ra_local__session_baton_t *sess = session->priv;
629  *url = svn_path_url_add_component2(sess->repos_url,
630                                     sess->fs_path->data + 1,
631                                     pool);
632  return SVN_NO_ERROR;
633}
634
635static svn_error_t *
636svn_ra_local__get_latest_revnum(svn_ra_session_t *session,
637                                svn_revnum_t *latest_revnum,
638                                apr_pool_t *pool)
639{
640  svn_ra_local__session_baton_t *sess = session->priv;
641  return svn_fs_youngest_rev(latest_revnum, sess->fs, pool);
642}
643
644static svn_error_t *
645svn_ra_local__get_file_revs(svn_ra_session_t *session,
646                            const char *path,
647                            svn_revnum_t start,
648                            svn_revnum_t end,
649                            svn_boolean_t include_merged_revisions,
650                            svn_file_rev_handler_t handler,
651                            void *handler_baton,
652                            apr_pool_t *pool)
653{
654  svn_ra_local__session_baton_t *sess = session->priv;
655  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
656  return svn_repos_get_file_revs2(sess->repos, abs_path, start, end,
657                                  include_merged_revisions, NULL, NULL,
658                                  handler, handler_baton, pool);
659}
660
661static svn_error_t *
662svn_ra_local__get_dated_revision(svn_ra_session_t *session,
663                                 svn_revnum_t *revision,
664                                 apr_time_t tm,
665                                 apr_pool_t *pool)
666{
667  svn_ra_local__session_baton_t *sess = session->priv;
668  return svn_repos_dated_revision(revision, sess->repos, tm, pool);
669}
670
671
672static svn_error_t *
673svn_ra_local__change_rev_prop(svn_ra_session_t *session,
674                              svn_revnum_t rev,
675                              const char *name,
676                              const svn_string_t *const *old_value_p,
677                              const svn_string_t *value,
678                              apr_pool_t *pool)
679{
680  svn_ra_local__session_baton_t *sess = session->priv;
681
682  SVN_ERR(get_username(session, pool));
683  return svn_repos_fs_change_rev_prop4(sess->repos, rev, sess->username,
684                                       name, old_value_p, value, TRUE, TRUE,
685                                       NULL, NULL, pool);
686}
687
688static svn_error_t *
689svn_ra_local__get_uuid(svn_ra_session_t *session,
690                       const char **uuid,
691                       apr_pool_t *pool)
692{
693  svn_ra_local__session_baton_t *sess = session->priv;
694  *uuid = sess->uuid;
695  return SVN_NO_ERROR;
696}
697
698static svn_error_t *
699svn_ra_local__get_repos_root(svn_ra_session_t *session,
700                             const char **url,
701                             apr_pool_t *pool)
702{
703  svn_ra_local__session_baton_t *sess = session->priv;
704  *url = sess->repos_url;
705  return SVN_NO_ERROR;
706}
707
708static svn_error_t *
709svn_ra_local__rev_proplist(svn_ra_session_t *session,
710                           svn_revnum_t rev,
711                           apr_hash_t **props,
712                           apr_pool_t *pool)
713{
714  svn_ra_local__session_baton_t *sess = session->priv;
715  return svn_repos_fs_revision_proplist(props, sess->repos, rev,
716                                        NULL, NULL, pool);
717}
718
719static svn_error_t *
720svn_ra_local__rev_prop(svn_ra_session_t *session,
721                       svn_revnum_t rev,
722                       const char *name,
723                       svn_string_t **value,
724                       apr_pool_t *pool)
725{
726  svn_ra_local__session_baton_t *sess = session->priv;
727  return svn_repos_fs_revision_prop(value, sess->repos, rev, name,
728                                    NULL, NULL, pool);
729}
730
731static svn_error_t *
732svn_ra_local__get_commit_editor(svn_ra_session_t *session,
733                                const svn_delta_editor_t **editor,
734                                void **edit_baton,
735                                apr_hash_t *revprop_table,
736                                svn_commit_callback2_t callback,
737                                void *callback_baton,
738                                apr_hash_t *lock_tokens,
739                                svn_boolean_t keep_locks,
740                                apr_pool_t *pool)
741{
742  svn_ra_local__session_baton_t *sess = session->priv;
743  struct deltify_etc_baton *deb = apr_palloc(pool, sizeof(*deb));
744
745  /* Prepare the baton for deltify_etc()  */
746  deb->fs = sess->fs;
747  deb->repos = sess->repos;
748  deb->fspath_base = sess->fs_path->data;
749  if (! keep_locks)
750    deb->lock_tokens = lock_tokens;
751  else
752    deb->lock_tokens = NULL;
753  deb->commit_cb = callback;
754  deb->commit_baton = callback_baton;
755
756  SVN_ERR(get_username(session, pool));
757
758  /* If there are lock tokens to add, do so. */
759  SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
760                            session->pool, pool));
761
762  /* Copy the revprops table so we can add the username. */
763  revprop_table = apr_hash_copy(pool, revprop_table);
764  svn_hash_sets(revprop_table, SVN_PROP_REVISION_AUTHOR,
765                svn_string_create(sess->username, pool));
766  svn_hash_sets(revprop_table, SVN_PROP_TXN_CLIENT_COMPAT_VERSION,
767                svn_string_create(SVN_VER_NUMBER, pool));
768
769  /* Get the repos commit-editor */
770  return svn_repos_get_commit_editor5
771         (editor, edit_baton, sess->repos, NULL,
772          svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data,
773          revprop_table, deltify_etc, deb, NULL, NULL, pool);
774}
775
776
777static svn_error_t *
778svn_ra_local__get_mergeinfo(svn_ra_session_t *session,
779                            svn_mergeinfo_catalog_t *catalog,
780                            const apr_array_header_t *paths,
781                            svn_revnum_t revision,
782                            svn_mergeinfo_inheritance_t inherit,
783                            svn_boolean_t include_descendants,
784                            apr_pool_t *pool)
785{
786  svn_ra_local__session_baton_t *sess = session->priv;
787  svn_mergeinfo_catalog_t tmp_catalog;
788  int i;
789  apr_array_header_t *abs_paths =
790    apr_array_make(pool, 0, sizeof(const char *));
791
792  for (i = 0; i < paths->nelts; i++)
793    {
794      const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
795      APR_ARRAY_PUSH(abs_paths, const char *) =
796        svn_fspath__join(sess->fs_path->data, relative_path, pool);
797    }
798
799  SVN_ERR(svn_repos_fs_get_mergeinfo(&tmp_catalog, sess->repos, abs_paths,
800                                     revision, inherit, include_descendants,
801                                     NULL, NULL, pool));
802  if (apr_hash_count(tmp_catalog) > 0)
803    SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog,
804                                                      tmp_catalog,
805                                                      sess->fs_path->data,
806                                                      pool));
807  else
808    *catalog = NULL;
809
810  return SVN_NO_ERROR;
811}
812
813
814static svn_error_t *
815svn_ra_local__do_update(svn_ra_session_t *session,
816                        const svn_ra_reporter3_t **reporter,
817                        void **report_baton,
818                        svn_revnum_t update_revision,
819                        const char *update_target,
820                        svn_depth_t depth,
821                        svn_boolean_t send_copyfrom_args,
822                        svn_boolean_t ignore_ancestry,
823                        const svn_delta_editor_t *update_editor,
824                        void *update_baton,
825                        apr_pool_t *result_pool,
826                        apr_pool_t *scratch_pool)
827{
828  return make_reporter(session,
829                       reporter,
830                       report_baton,
831                       update_revision,
832                       update_target,
833                       NULL,
834                       TRUE,
835                       depth,
836                       send_copyfrom_args,
837                       ignore_ancestry,
838                       update_editor,
839                       update_baton,
840                       result_pool, scratch_pool);
841}
842
843
844static svn_error_t *
845svn_ra_local__do_switch(svn_ra_session_t *session,
846                        const svn_ra_reporter3_t **reporter,
847                        void **report_baton,
848                        svn_revnum_t update_revision,
849                        const char *update_target,
850                        svn_depth_t depth,
851                        const char *switch_url,
852                        svn_boolean_t send_copyfrom_args,
853                        svn_boolean_t ignore_ancestry,
854                        const svn_delta_editor_t *update_editor,
855                        void *update_baton,
856                        apr_pool_t *result_pool,
857                        apr_pool_t *scratch_pool)
858{
859  return make_reporter(session,
860                       reporter,
861                       report_baton,
862                       update_revision,
863                       update_target,
864                       switch_url,
865                       TRUE /* text_deltas */,
866                       depth,
867                       send_copyfrom_args,
868                       ignore_ancestry,
869                       update_editor,
870                       update_baton,
871                       result_pool, scratch_pool);
872}
873
874
875static svn_error_t *
876svn_ra_local__do_status(svn_ra_session_t *session,
877                        const svn_ra_reporter3_t **reporter,
878                        void **report_baton,
879                        const char *status_target,
880                        svn_revnum_t revision,
881                        svn_depth_t depth,
882                        const svn_delta_editor_t *status_editor,
883                        void *status_baton,
884                        apr_pool_t *pool)
885{
886  return make_reporter(session,
887                       reporter,
888                       report_baton,
889                       revision,
890                       status_target,
891                       NULL,
892                       FALSE,
893                       depth,
894                       FALSE,
895                       FALSE,
896                       status_editor,
897                       status_baton,
898                       pool, pool);
899}
900
901
902static svn_error_t *
903svn_ra_local__do_diff(svn_ra_session_t *session,
904                      const svn_ra_reporter3_t **reporter,
905                      void **report_baton,
906                      svn_revnum_t update_revision,
907                      const char *update_target,
908                      svn_depth_t depth,
909                      svn_boolean_t ignore_ancestry,
910                      svn_boolean_t text_deltas,
911                      const char *switch_url,
912                      const svn_delta_editor_t *update_editor,
913                      void *update_baton,
914                      apr_pool_t *pool)
915{
916  return make_reporter(session,
917                       reporter,
918                       report_baton,
919                       update_revision,
920                       update_target,
921                       switch_url,
922                       text_deltas,
923                       depth,
924                       FALSE,
925                       ignore_ancestry,
926                       update_editor,
927                       update_baton,
928                       pool, pool);
929}
930
931
932struct log_baton
933{
934  svn_ra_local__session_baton_t *sess;
935  svn_log_entry_receiver_t real_cb;
936  void *real_baton;
937};
938
939static svn_error_t *
940log_receiver_wrapper(void *baton,
941                     svn_log_entry_t *log_entry,
942                     apr_pool_t *pool)
943{
944  struct log_baton *b = baton;
945  svn_ra_local__session_baton_t *sess = b->sess;
946
947  if (sess->callbacks->cancel_func)
948    SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton));
949
950  /* For consistency with the other RA layers, replace an empty
951     changed-paths hash with a NULL one.
952
953     ### Should this be done by svn_ra_get_log2() instead, then? */
954  if ((log_entry->changed_paths2)
955      && (apr_hash_count(log_entry->changed_paths2) == 0))
956    {
957      log_entry->changed_paths = NULL;
958      log_entry->changed_paths2 = NULL;
959    }
960
961  return b->real_cb(b->real_baton, log_entry, pool);
962}
963
964
965static svn_error_t *
966svn_ra_local__get_log(svn_ra_session_t *session,
967                      const apr_array_header_t *paths,
968                      svn_revnum_t start,
969                      svn_revnum_t end,
970                      int limit,
971                      svn_boolean_t discover_changed_paths,
972                      svn_boolean_t strict_node_history,
973                      svn_boolean_t include_merged_revisions,
974                      const apr_array_header_t *revprops,
975                      svn_log_entry_receiver_t receiver,
976                      void *receiver_baton,
977                      apr_pool_t *pool)
978{
979  svn_ra_local__session_baton_t *sess = session->priv;
980  struct log_baton lb;
981  apr_array_header_t *abs_paths =
982    apr_array_make(pool, 0, sizeof(const char *));
983
984  if (paths)
985    {
986      int i;
987
988      for (i = 0; i < paths->nelts; i++)
989        {
990          const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
991          APR_ARRAY_PUSH(abs_paths, const char *) =
992            svn_fspath__join(sess->fs_path->data, relative_path, pool);
993        }
994    }
995
996  lb.real_cb = receiver;
997  lb.real_baton = receiver_baton;
998  lb.sess = sess;
999  receiver = log_receiver_wrapper;
1000  receiver_baton = &lb;
1001
1002  return svn_repos_get_logs4(sess->repos,
1003                             abs_paths,
1004                             start,
1005                             end,
1006                             limit,
1007                             discover_changed_paths,
1008                             strict_node_history,
1009                             include_merged_revisions,
1010                             revprops,
1011                             NULL, NULL,
1012                             receiver,
1013                             receiver_baton,
1014                             pool);
1015}
1016
1017
1018static svn_error_t *
1019svn_ra_local__do_check_path(svn_ra_session_t *session,
1020                            const char *path,
1021                            svn_revnum_t revision,
1022                            svn_node_kind_t *kind,
1023                            apr_pool_t *pool)
1024{
1025  svn_ra_local__session_baton_t *sess = session->priv;
1026  svn_fs_root_t *root;
1027  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1028
1029  if (! SVN_IS_VALID_REVNUM(revision))
1030    SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
1031  SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1032  return svn_fs_check_path(kind, root, abs_path, pool);
1033}
1034
1035
1036static svn_error_t *
1037svn_ra_local__stat(svn_ra_session_t *session,
1038                   const char *path,
1039                   svn_revnum_t revision,
1040                   svn_dirent_t **dirent,
1041                   apr_pool_t *pool)
1042{
1043  svn_ra_local__session_baton_t *sess = session->priv;
1044  svn_fs_root_t *root;
1045  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1046
1047  if (! SVN_IS_VALID_REVNUM(revision))
1048    SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
1049  SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1050
1051  return svn_repos_stat(dirent, root, abs_path, pool);
1052}
1053
1054
1055
1056
1057static svn_error_t *
1058get_node_props(apr_hash_t **props,
1059               apr_array_header_t **inherited_props,
1060               svn_ra_local__session_baton_t *sess,
1061               svn_fs_root_t *root,
1062               const char *path,
1063               apr_pool_t *result_pool,
1064               apr_pool_t *scratch_pool)
1065{
1066  svn_revnum_t cmt_rev;
1067  const char *cmt_date, *cmt_author;
1068
1069  /* Create a hash with props attached to the fs node. */
1070  if (props)
1071    {
1072      SVN_ERR(svn_fs_node_proplist(props, root, path, result_pool));
1073    }
1074
1075  /* Get inherited properties if requested. */
1076  if (inherited_props)
1077    {
1078      SVN_ERR(svn_repos_fs_get_inherited_props(inherited_props, root, path,
1079                                               NULL, NULL, NULL,
1080                                               result_pool, scratch_pool));
1081    }
1082
1083  /* Now add some non-tweakable metadata to the hash as well... */
1084
1085  if (props)
1086    {
1087      /* The so-called 'entryprops' with info about CR & friends. */
1088      SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date,
1089                                           &cmt_author, root, path,
1090                                           scratch_pool));
1091
1092      svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV,
1093                    svn_string_createf(result_pool, "%ld", cmt_rev));
1094      svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, cmt_date ?
1095                    svn_string_create(cmt_date, result_pool) :NULL);
1096      svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, cmt_author ?
1097                    svn_string_create(cmt_author, result_pool) :NULL);
1098      svn_hash_sets(*props, SVN_PROP_ENTRY_UUID,
1099                    svn_string_create(sess->uuid, result_pool));
1100
1101      /* We have no 'wcprops' in ra_local, but might someday. */
1102    }
1103
1104  return SVN_NO_ERROR;
1105}
1106
1107
1108/* Getting just one file. */
1109static svn_error_t *
1110svn_ra_local__get_file(svn_ra_session_t *session,
1111                       const char *path,
1112                       svn_revnum_t revision,
1113                       svn_stream_t *stream,
1114                       svn_revnum_t *fetched_rev,
1115                       apr_hash_t **props,
1116                       apr_pool_t *pool)
1117{
1118  svn_fs_root_t *root;
1119  svn_stream_t *contents;
1120  svn_revnum_t youngest_rev;
1121  svn_ra_local__session_baton_t *sess = session->priv;
1122  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1123  svn_node_kind_t node_kind;
1124
1125  /* Open the revision's root. */
1126  if (! SVN_IS_VALID_REVNUM(revision))
1127    {
1128      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
1129      SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
1130      if (fetched_rev != NULL)
1131        *fetched_rev = youngest_rev;
1132    }
1133  else
1134    SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1135
1136  SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, pool));
1137  if (node_kind == svn_node_none)
1138    {
1139      return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1140                               _("'%s' path not found"), abs_path);
1141    }
1142  else if (node_kind != svn_node_file)
1143    {
1144      return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
1145                               _("'%s' is not a file"), abs_path);
1146    }
1147
1148  if (stream)
1149    {
1150      /* Get a stream representing the file's contents. */
1151      SVN_ERR(svn_fs_file_contents(&contents, root, abs_path, pool));
1152
1153      /* Now push data from the fs stream back at the caller's stream.
1154         Note that this particular RA layer does not computing a
1155         checksum as we go, and confirming it against the repository's
1156         checksum when done.  That's because it calls
1157         svn_fs_file_contents() directly, which already checks the
1158         stored checksum, and all we're doing here is writing bytes in
1159         a loop.  Truly, Nothing Can Go Wrong :-).  But RA layers that
1160         go over a network should confirm the checksum.
1161
1162         Note: we are not supposed to close the passed-in stream, so
1163         disown the thing.
1164      */
1165      SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(stream, pool),
1166                               sess->callbacks
1167                                 ? sess->callbacks->cancel_func : NULL,
1168                               sess->callback_baton,
1169                               pool));
1170    }
1171
1172  /* Handle props if requested. */
1173  if (props)
1174    SVN_ERR(get_node_props(props, NULL, sess, root, abs_path, pool, pool));
1175
1176  return SVN_NO_ERROR;
1177}
1178
1179
1180
1181/* Getting a directory's entries */
1182static svn_error_t *
1183svn_ra_local__get_dir(svn_ra_session_t *session,
1184                      apr_hash_t **dirents,
1185                      svn_revnum_t *fetched_rev,
1186                      apr_hash_t **props,
1187                      const char *path,
1188                      svn_revnum_t revision,
1189                      apr_uint32_t dirent_fields,
1190                      apr_pool_t *pool)
1191{
1192  svn_fs_root_t *root;
1193  svn_revnum_t youngest_rev;
1194  apr_hash_t *entries;
1195  apr_hash_index_t *hi;
1196  svn_ra_local__session_baton_t *sess = session->priv;
1197  apr_pool_t *subpool;
1198  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1199
1200  /* Open the revision's root. */
1201  if (! SVN_IS_VALID_REVNUM(revision))
1202    {
1203      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
1204      SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
1205      if (fetched_rev != NULL)
1206        *fetched_rev = youngest_rev;
1207    }
1208  else
1209    SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1210
1211  if (dirents)
1212    {
1213      /* Get the dir's entries. */
1214      SVN_ERR(svn_fs_dir_entries(&entries, root, abs_path, pool));
1215
1216      /* Loop over the fs dirents, and build a hash of general
1217         svn_dirent_t's. */
1218      *dirents = apr_hash_make(pool);
1219      subpool = svn_pool_create(pool);
1220      for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1221        {
1222          const void *key;
1223          void *val;
1224          apr_hash_t *prophash;
1225          const char *datestring, *entryname, *fullpath;
1226          svn_fs_dirent_t *fs_entry;
1227          svn_dirent_t *entry = svn_dirent_create(pool);
1228
1229          svn_pool_clear(subpool);
1230
1231          apr_hash_this(hi, &key, NULL, &val);
1232          entryname = (const char *) key;
1233          fs_entry = (svn_fs_dirent_t *) val;
1234
1235          fullpath = svn_dirent_join(abs_path, entryname, subpool);
1236
1237          if (dirent_fields & SVN_DIRENT_KIND)
1238            {
1239              /* node kind */
1240              entry->kind = fs_entry->kind;
1241            }
1242
1243          if (dirent_fields & SVN_DIRENT_SIZE)
1244            {
1245              /* size  */
1246              if (entry->kind == svn_node_dir)
1247                entry->size = 0;
1248              else
1249                SVN_ERR(svn_fs_file_length(&(entry->size), root,
1250                                           fullpath, subpool));
1251            }
1252
1253          if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1254            {
1255              /* has_props? */
1256              SVN_ERR(svn_fs_node_proplist(&prophash, root, fullpath,
1257                                           subpool));
1258              entry->has_props = (apr_hash_count(prophash) != 0);
1259            }
1260
1261          if ((dirent_fields & SVN_DIRENT_TIME)
1262              || (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1263              || (dirent_fields & SVN_DIRENT_CREATED_REV))
1264            {
1265              /* created_rev & friends */
1266              SVN_ERR(svn_repos_get_committed_info(&(entry->created_rev),
1267                                                   &datestring,
1268                                                   &(entry->last_author),
1269                                                   root, fullpath, subpool));
1270              if (datestring)
1271                SVN_ERR(svn_time_from_cstring(&(entry->time), datestring,
1272                                              pool));
1273              if (entry->last_author)
1274                entry->last_author = apr_pstrdup(pool, entry->last_author);
1275            }
1276
1277          /* Store. */
1278          svn_hash_sets(*dirents, entryname, entry);
1279        }
1280      svn_pool_destroy(subpool);
1281    }
1282
1283  /* Handle props if requested. */
1284  if (props)
1285    SVN_ERR(get_node_props(props, NULL, sess, root, abs_path, pool, pool));
1286
1287  return SVN_NO_ERROR;
1288}
1289
1290
1291static svn_error_t *
1292svn_ra_local__get_locations(svn_ra_session_t *session,
1293                            apr_hash_t **locations,
1294                            const char *path,
1295                            svn_revnum_t peg_revision,
1296                            const apr_array_header_t *location_revisions,
1297                            apr_pool_t *pool)
1298{
1299  svn_ra_local__session_baton_t *sess = session->priv;
1300  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1301  return svn_repos_trace_node_locations(sess->fs, locations, abs_path,
1302                                        peg_revision, location_revisions,
1303                                        NULL, NULL, pool);
1304}
1305
1306
1307static svn_error_t *
1308svn_ra_local__get_location_segments(svn_ra_session_t *session,
1309                                    const char *path,
1310                                    svn_revnum_t peg_revision,
1311                                    svn_revnum_t start_rev,
1312                                    svn_revnum_t end_rev,
1313                                    svn_location_segment_receiver_t receiver,
1314                                    void *receiver_baton,
1315                                    apr_pool_t *pool)
1316{
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  return svn_repos_node_location_segments(sess->repos, abs_path,
1320                                          peg_revision, start_rev, end_rev,
1321                                          receiver, receiver_baton,
1322                                          NULL, NULL, pool);
1323}
1324
1325
1326static svn_error_t *
1327svn_ra_local__lock(svn_ra_session_t *session,
1328                   apr_hash_t *path_revs,
1329                   const char *comment,
1330                   svn_boolean_t force,
1331                   svn_ra_lock_callback_t lock_func,
1332                   void *lock_baton,
1333                   apr_pool_t *pool)
1334{
1335  svn_ra_local__session_baton_t *sess = session->priv;
1336  apr_hash_index_t *hi;
1337  apr_pool_t *iterpool = svn_pool_create(pool);
1338
1339  /* A username is absolutely required to lock a path. */
1340  SVN_ERR(get_username(session, pool));
1341
1342  for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1343    {
1344      svn_lock_t *lock;
1345      const void *key;
1346      const char *path;
1347      void *val;
1348      svn_revnum_t *revnum;
1349      const char *abs_path;
1350      svn_error_t *err, *callback_err = NULL;
1351
1352      svn_pool_clear(iterpool);
1353      apr_hash_this(hi, &key, NULL, &val);
1354      path = key;
1355      revnum = val;
1356
1357      abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool);
1358
1359      /* This wrapper will call pre- and post-lock hooks. */
1360      err = svn_repos_fs_lock(&lock, sess->repos, abs_path, NULL, comment,
1361                              FALSE /* not DAV comment */,
1362                              0 /* no expiration */, *revnum, force,
1363                              iterpool);
1364
1365      if (err && !SVN_ERR_IS_LOCK_ERROR(err))
1366        return err;
1367
1368      if (lock_func)
1369        callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
1370                                 err, iterpool);
1371
1372      svn_error_clear(err);
1373
1374      if (callback_err)
1375        return callback_err;
1376    }
1377
1378  svn_pool_destroy(iterpool);
1379
1380  return SVN_NO_ERROR;
1381}
1382
1383
1384static svn_error_t *
1385svn_ra_local__unlock(svn_ra_session_t *session,
1386                     apr_hash_t *path_tokens,
1387                     svn_boolean_t force,
1388                     svn_ra_lock_callback_t lock_func,
1389                     void *lock_baton,
1390                     apr_pool_t *pool)
1391{
1392  svn_ra_local__session_baton_t *sess = session->priv;
1393  apr_hash_index_t *hi;
1394  apr_pool_t *iterpool = svn_pool_create(pool);
1395
1396  /* A username is absolutely required to unlock a path. */
1397  SVN_ERR(get_username(session, pool));
1398
1399  for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1400    {
1401      const void *key;
1402      const char *path;
1403      void *val;
1404      const char *abs_path, *token;
1405      svn_error_t *err, *callback_err = NULL;
1406
1407      svn_pool_clear(iterpool);
1408      apr_hash_this(hi, &key, NULL, &val);
1409      path = key;
1410      /* Since we can't store NULL values in a hash, we turn "" to
1411         NULL here. */
1412      if (strcmp(val, "") != 0)
1413        token = val;
1414      else
1415        token = NULL;
1416
1417      abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool);
1418
1419      /* This wrapper will call pre- and post-unlock hooks. */
1420      err = svn_repos_fs_unlock(sess->repos, abs_path, token, force,
1421                                iterpool);
1422
1423      if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
1424        return err;
1425
1426      if (lock_func)
1427        callback_err = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
1428
1429      svn_error_clear(err);
1430
1431      if (callback_err)
1432        return callback_err;
1433    }
1434
1435  svn_pool_destroy(iterpool);
1436
1437  return SVN_NO_ERROR;
1438}
1439
1440
1441
1442static svn_error_t *
1443svn_ra_local__get_lock(svn_ra_session_t *session,
1444                       svn_lock_t **lock,
1445                       const char *path,
1446                       apr_pool_t *pool)
1447{
1448  svn_ra_local__session_baton_t *sess = session->priv;
1449  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1450  return svn_fs_get_lock(lock, sess->fs, abs_path, pool);
1451}
1452
1453
1454
1455static svn_error_t *
1456svn_ra_local__get_locks(svn_ra_session_t *session,
1457                        apr_hash_t **locks,
1458                        const char *path,
1459                        svn_depth_t depth,
1460                        apr_pool_t *pool)
1461{
1462  svn_ra_local__session_baton_t *sess = session->priv;
1463  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1464
1465  /* Kinda silly to call the repos wrapper, since we have no authz
1466     func to give it.  But heck, why not. */
1467  return svn_repos_fs_get_locks2(locks, sess->repos, abs_path, depth,
1468                                 NULL, NULL, pool);
1469}
1470
1471
1472static svn_error_t *
1473svn_ra_local__replay(svn_ra_session_t *session,
1474                     svn_revnum_t revision,
1475                     svn_revnum_t low_water_mark,
1476                     svn_boolean_t send_deltas,
1477                     const svn_delta_editor_t *editor,
1478                     void *edit_baton,
1479                     apr_pool_t *pool)
1480{
1481  svn_ra_local__session_baton_t *sess = session->priv;
1482  svn_fs_root_t *root;
1483
1484  SVN_ERR(svn_fs_revision_root(&root, svn_repos_fs(sess->repos),
1485                               revision, pool));
1486  return svn_repos_replay2(root, sess->fs_path->data, low_water_mark,
1487                           send_deltas, editor, edit_baton, NULL, NULL,
1488                           pool);
1489}
1490
1491
1492static svn_error_t *
1493svn_ra_local__replay_range(svn_ra_session_t *session,
1494                           svn_revnum_t start_revision,
1495                           svn_revnum_t end_revision,
1496                           svn_revnum_t low_water_mark,
1497                           svn_boolean_t send_deltas,
1498                           svn_ra_replay_revstart_callback_t revstart_func,
1499                           svn_ra_replay_revfinish_callback_t revfinish_func,
1500                           void *replay_baton,
1501                           apr_pool_t *pool)
1502{
1503  return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
1504}
1505
1506
1507static svn_error_t *
1508svn_ra_local__has_capability(svn_ra_session_t *session,
1509                             svn_boolean_t *has,
1510                             const char *capability,
1511                             apr_pool_t *pool)
1512{
1513  svn_ra_local__session_baton_t *sess = session->priv;
1514
1515  if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0
1516      || strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0
1517      || strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0
1518      || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0
1519      || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0
1520      || strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0
1521      || strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0
1522      || strcmp(capability, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) == 0
1523      )
1524    {
1525      *has = TRUE;
1526    }
1527  else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
1528    {
1529      /* With mergeinfo, the code's capabilities may not reflect the
1530         repository's, so inquire further. */
1531      SVN_ERR(svn_repos_has_capability(sess->repos, has,
1532                                       SVN_REPOS_CAPABILITY_MERGEINFO,
1533                                       pool));
1534    }
1535  else  /* Don't know any other capabilities, so error. */
1536    {
1537      return svn_error_createf
1538        (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
1539         _("Don't know anything about capability '%s'"), capability);
1540    }
1541
1542  return SVN_NO_ERROR;
1543}
1544
1545static svn_error_t *
1546svn_ra_local__get_deleted_rev(svn_ra_session_t *session,
1547                              const char *path,
1548                              svn_revnum_t peg_revision,
1549                              svn_revnum_t end_revision,
1550                              svn_revnum_t *revision_deleted,
1551                              apr_pool_t *pool)
1552{
1553  svn_ra_local__session_baton_t *sess = session->priv;
1554  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1555
1556  SVN_ERR(svn_repos_deleted_rev(sess->fs,
1557                                abs_path,
1558                                peg_revision,
1559                                end_revision,
1560                                revision_deleted,
1561                                pool));
1562
1563  return SVN_NO_ERROR;
1564}
1565
1566static svn_error_t *
1567svn_ra_local__get_inherited_props(svn_ra_session_t *session,
1568                                  apr_array_header_t **iprops,
1569                                  const char *path,
1570                                  svn_revnum_t revision,
1571                                  apr_pool_t *result_pool,
1572                                  apr_pool_t *scratch_pool)
1573{
1574  svn_fs_root_t *root;
1575  svn_revnum_t youngest_rev;
1576  svn_ra_local__session_baton_t *sess = session->priv;
1577  const char *abs_path = svn_fspath__join(sess->fs_path->data, path,
1578                                          scratch_pool);
1579  svn_node_kind_t node_kind;
1580
1581  /* Open the revision's root. */
1582  if (! SVN_IS_VALID_REVNUM(revision))
1583    {
1584      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, scratch_pool));
1585      SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev,
1586                                   scratch_pool));
1587    }
1588  else
1589    {
1590      SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, scratch_pool));
1591    }
1592
1593  SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, scratch_pool));
1594  if (node_kind == svn_node_none)
1595    {
1596      return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1597                               _("'%s' path not found"), abs_path);
1598    }
1599
1600  return svn_error_trace(get_node_props(NULL, iprops, sess, root, abs_path,
1601                                        result_pool, scratch_pool));
1602}
1603
1604static svn_error_t *
1605svn_ra_local__register_editor_shim_callbacks(svn_ra_session_t *session,
1606                                    svn_delta_shim_callbacks_t *callbacks)
1607{
1608  /* This is currenly a no-op, since we don't provide our own editor, just
1609     use the one the libsvn_repos hands back to us. */
1610  return SVN_NO_ERROR;
1611}
1612
1613
1614static svn_error_t *
1615svn_ra_local__get_commit_ev2(svn_editor_t **editor,
1616                             svn_ra_session_t *session,
1617                             apr_hash_t *revprops,
1618                             svn_commit_callback2_t commit_cb,
1619                             void *commit_baton,
1620                             apr_hash_t *lock_tokens,
1621                             svn_boolean_t keep_locks,
1622                             svn_ra__provide_base_cb_t provide_base_cb,
1623                             svn_ra__provide_props_cb_t provide_props_cb,
1624                             svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
1625                             void *cb_baton,
1626                             svn_cancel_func_t cancel_func,
1627                             void *cancel_baton,
1628                             apr_pool_t *result_pool,
1629                             apr_pool_t *scratch_pool)
1630{
1631  svn_ra_local__session_baton_t *sess = session->priv;
1632  struct deltify_etc_baton *deb = apr_palloc(result_pool, sizeof(*deb));
1633
1634  /* NOTE: the RA callbacks are ignored. We pass everything directly to
1635     the REPOS editor.  */
1636
1637  /* Prepare the baton for deltify_etc()  */
1638  deb->fs = sess->fs;
1639  deb->repos = sess->repos;
1640  deb->fspath_base = sess->fs_path->data;
1641  if (! keep_locks)
1642    deb->lock_tokens = lock_tokens;
1643  else
1644    deb->lock_tokens = NULL;
1645  deb->commit_cb = commit_cb;
1646  deb->commit_baton = commit_baton;
1647
1648  /* Ensure there is a username (and an FS access context) associated with
1649     the session and its FS handle.  */
1650  SVN_ERR(get_username(session, scratch_pool));
1651
1652  /* If there are lock tokens to add, do so.  */
1653  SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
1654                            session->pool, scratch_pool));
1655
1656  /* Copy the REVPROPS and insert the author/username.  */
1657  revprops = apr_hash_copy(scratch_pool, revprops);
1658  svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR,
1659                svn_string_create(sess->username, scratch_pool));
1660
1661  return svn_error_trace(svn_repos__get_commit_ev2(
1662                           editor, sess->repos, NULL /* authz */,
1663                           NULL /* authz_repos_name */, NULL /* authz_user */,
1664                           revprops,
1665                           deltify_etc, deb, cancel_func, cancel_baton,
1666                           result_pool, scratch_pool));
1667}
1668
1669/*----------------------------------------------------------------*/
1670
1671static const svn_version_t *
1672ra_local_version(void)
1673{
1674  SVN_VERSION_BODY;
1675}
1676
1677/** The ra_vtable **/
1678
1679static const svn_ra__vtable_t ra_local_vtable =
1680{
1681  ra_local_version,
1682  svn_ra_local__get_description,
1683  svn_ra_local__get_schemes,
1684  svn_ra_local__open,
1685  svn_ra_local__reparent,
1686  svn_ra_local__get_session_url,
1687  svn_ra_local__get_latest_revnum,
1688  svn_ra_local__get_dated_revision,
1689  svn_ra_local__change_rev_prop,
1690  svn_ra_local__rev_proplist,
1691  svn_ra_local__rev_prop,
1692  svn_ra_local__get_commit_editor,
1693  svn_ra_local__get_file,
1694  svn_ra_local__get_dir,
1695  svn_ra_local__get_mergeinfo,
1696  svn_ra_local__do_update,
1697  svn_ra_local__do_switch,
1698  svn_ra_local__do_status,
1699  svn_ra_local__do_diff,
1700  svn_ra_local__get_log,
1701  svn_ra_local__do_check_path,
1702  svn_ra_local__stat,
1703  svn_ra_local__get_uuid,
1704  svn_ra_local__get_repos_root,
1705  svn_ra_local__get_locations,
1706  svn_ra_local__get_location_segments,
1707  svn_ra_local__get_file_revs,
1708  svn_ra_local__lock,
1709  svn_ra_local__unlock,
1710  svn_ra_local__get_lock,
1711  svn_ra_local__get_locks,
1712  svn_ra_local__replay,
1713  svn_ra_local__has_capability,
1714  svn_ra_local__replay_range,
1715  svn_ra_local__get_deleted_rev,
1716  svn_ra_local__register_editor_shim_callbacks,
1717  svn_ra_local__get_inherited_props,
1718  svn_ra_local__get_commit_ev2
1719};
1720
1721
1722/*----------------------------------------------------------------*/
1723
1724/** The One Public Routine, called by libsvn_ra **/
1725
1726svn_error_t *
1727svn_ra_local__init(const svn_version_t *loader_version,
1728                   const svn_ra__vtable_t **vtable,
1729                   apr_pool_t *pool)
1730{
1731  static const svn_version_checklist_t checklist[] =
1732    {
1733      { "svn_subr",  svn_subr_version },
1734      { "svn_delta", svn_delta_version },
1735      { "svn_repos", svn_repos_version },
1736      { "svn_fs",    svn_fs_version },
1737      { NULL, NULL }
1738    };
1739
1740
1741  /* Simplified version check to make sure we can safely use the
1742     VTABLE parameter. The RA loader does a more exhaustive check. */
1743  if (loader_version->major != SVN_VER_MAJOR)
1744    return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
1745                             _("Unsupported RA loader version (%d) for "
1746                               "ra_local"),
1747                             loader_version->major);
1748
1749  SVN_ERR(svn_ver_check_list2(ra_local_version(), checklist, svn_ver_equal));
1750
1751#ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
1752  /* This assumes that POOL was the pool used to load the dso. */
1753  SVN_ERR(svn_fs_initialize(pool));
1754#endif
1755
1756  *vtable = &ra_local_vtable;
1757
1758  return SVN_NO_ERROR;
1759}
1760
1761/* Compatibility wrapper for the 1.1 and before API. */
1762#define NAME "ra_local"
1763#define DESCRIPTION RA_LOCAL_DESCRIPTION
1764#define VTBL ra_local_vtable
1765#define INITFUNC svn_ra_local__init
1766#define COMPAT_INITFUNC svn_ra_local_init
1767#include "../libsvn_ra/wrapper_template.h"
1768