]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_local/ra_plugin.c
THIS BRANCH IS OBSOLETE, PLEASE READ:
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_ra_local / ra_plugin.c
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 \f
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. */
56 static apr_status_t
57 cleanup_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. */
79 static svn_error_t *
80 get_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. */
144 static svn_error_t *
145 cache_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 \f
172 /*** The reporter vtable needed by do_update() and friends ***/
173
174 typedef struct reporter_baton_t
175 {
176   svn_ra_local__session_baton_t *sess;
177   void *report_baton;
178
179 } reporter_baton_t;
180
181
182 static void *
183 make_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
194 static svn_error_t *
195 reporter_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
209 static svn_error_t *
210 reporter_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
219 static svn_error_t *
220 reporter_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
251 static svn_error_t *
252 reporter_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
260 static svn_error_t *
261 reporter_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
269 static 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  */
290 static svn_error_t *
291 make_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 \f
382 /*** Deltification stuff for get_commit_editor() ***/
383
384 struct 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 *'. */
400 static svn_error_t *
401 deltify_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.  */
459 static svn_error_t *
460 apply_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 \f
506 /*** The RA vtable routines ***/
507
508 #define RA_LOCAL_DESCRIPTION \
509         N_("Module for accessing a repository on local disk.")
510
511 static const char *
512 svn_ra_local__get_description(apr_pool_t *pool)
513 {
514   return _(RA_LOCAL_DESCRIPTION);
515 }
516
517 static const char * const *
518 svn_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  */
540 static void
541 ignore_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
554 static svn_error_t *
555 svn_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
628 static svn_error_t *
629 svn_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
675 static svn_error_t *
676 svn_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
699 static svn_error_t *
700 svn_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
711 static svn_error_t *
712 svn_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
720 static svn_error_t *
721 svn_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
737 static svn_error_t *
738 svn_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
748 static svn_error_t *
749 svn_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
764 static svn_error_t *
765 svn_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
774 static svn_error_t *
775 svn_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
784 static svn_error_t *
785 svn_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
795 static svn_error_t *
796 svn_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
807 struct 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 */
816 static svn_error_t *
817 commit_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. */
835 static void
836 remap_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
863 static svn_error_t *
864 svn_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  */
918 static svn_error_t *
919 mergeinfo_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
937 static svn_error_t *
938 svn_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
976 static svn_error_t *
977 svn_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
1006 static svn_error_t *
1007 svn_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
1037 static svn_error_t *
1038 svn_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
1064 static svn_error_t *
1065 svn_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
1094 struct 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
1101 static svn_error_t *
1102 log_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
1127 static svn_error_t *
1128 svn_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
1180 static svn_error_t *
1181 svn_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
1198 static svn_error_t *
1199 svn_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 \f
1219 /* Obtain the properties for a node, including its 'entry props */
1220 static svn_error_t *
1221 get_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. */
1257 static svn_error_t *
1258 svn_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 */
1330 static svn_error_t *
1331 svn_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
1437 static svn_error_t *
1438 svn_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
1453 static svn_error_t *
1454 svn_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
1471 struct 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. */
1482 static svn_error_t *
1483 lock_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
1501 static svn_error_t *
1502 svn_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
1552 static svn_error_t *
1553 svn_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
1597 static svn_error_t *
1598 svn_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
1610 static svn_error_t *
1611 svn_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
1627 static svn_error_t *
1628 svn_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
1647 static svn_error_t *
1648 svn_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
1662 static svn_error_t *
1663 svn_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
1701 static svn_error_t *
1702 svn_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
1722 static svn_error_t *
1723 svn_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
1753 static svn_error_t *
1754 svn_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
1763 static svn_error_t *
1764 svn_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. */
1823 typedef struct dirent_receiver_baton_t
1824 {
1825   svn_ra_dirent_receiver_t receiver;
1826   void *receiver_baton;
1827 } dirent_receiver_baton_t;
1828
1829 static svn_error_t *
1830 dirent_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
1839 static svn_error_t *
1840 svn_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 \f
1871 static const svn_version_t *
1872 ra_local_version(void)
1873 {
1874   SVN_VERSION_BODY;
1875 }
1876
1877 /** The ra_vtable **/
1878
1879 static 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 \f
1928 /** The One Public Routine, called by libsvn_ra **/
1929
1930 svn_error_t *
1931 svn_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 \f
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"