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