]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_ra_local/ra_plugin.c
MFV r353141 (by phillip):
[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 *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 /* Implements svn_repos_mergeinfo_receiver_t.
913  * It add MERGEINFO for PATH to the svn_mergeinfo_catalog_t BATON.
914  */
915 static svn_error_t *
916 mergeinfo_receiver(const char *path,
917                    svn_mergeinfo_t mergeinfo,
918                    void *baton,
919                    apr_pool_t *scratch_pool)
920 {
921   svn_mergeinfo_catalog_t catalog = baton;
922   apr_pool_t *result_pool = apr_hash_pool_get(catalog);
923   apr_size_t len = strlen(path);
924
925   apr_hash_set(catalog,
926                apr_pstrmemdup(result_pool, path, len),
927                len,
928                svn_mergeinfo_dup(mergeinfo, result_pool));
929
930   return SVN_NO_ERROR;
931 }
932
933
934 static svn_error_t *
935 svn_ra_local__get_mergeinfo(svn_ra_session_t *session,
936                             svn_mergeinfo_catalog_t *catalog,
937                             const apr_array_header_t *paths,
938                             svn_revnum_t revision,
939                             svn_mergeinfo_inheritance_t inherit,
940                             svn_boolean_t include_descendants,
941                             apr_pool_t *pool)
942 {
943   svn_ra_local__session_baton_t *sess = session->priv;
944   svn_mergeinfo_catalog_t tmp_catalog = svn_hash__make(pool);
945   int i;
946   apr_array_header_t *abs_paths =
947     apr_array_make(pool, 0, sizeof(const char *));
948
949   for (i = 0; i < paths->nelts; i++)
950     {
951       const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
952       APR_ARRAY_PUSH(abs_paths, const char *) =
953         svn_fspath__join(sess->fs_path->data, relative_path, pool);
954     }
955
956   SVN_ERR(svn_repos_fs_get_mergeinfo2(sess->repos, abs_paths, revision,
957                                       inherit, include_descendants,
958                                       NULL, NULL,
959                                       mergeinfo_receiver, tmp_catalog,
960                                       pool));
961   if (apr_hash_count(tmp_catalog) > 0)
962     SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog,
963                                                       tmp_catalog,
964                                                       sess->fs_path->data,
965                                                       pool));
966   else
967     *catalog = NULL;
968
969   return SVN_NO_ERROR;
970 }
971
972
973 static svn_error_t *
974 svn_ra_local__do_update(svn_ra_session_t *session,
975                         const svn_ra_reporter3_t **reporter,
976                         void **report_baton,
977                         svn_revnum_t update_revision,
978                         const char *update_target,
979                         svn_depth_t depth,
980                         svn_boolean_t send_copyfrom_args,
981                         svn_boolean_t ignore_ancestry,
982                         const svn_delta_editor_t *update_editor,
983                         void *update_baton,
984                         apr_pool_t *result_pool,
985                         apr_pool_t *scratch_pool)
986 {
987   return make_reporter(session,
988                        reporter,
989                        report_baton,
990                        update_revision,
991                        update_target,
992                        NULL,
993                        TRUE,
994                        depth,
995                        send_copyfrom_args,
996                        ignore_ancestry,
997                        update_editor,
998                        update_baton,
999                        result_pool, scratch_pool);
1000 }
1001
1002
1003 static svn_error_t *
1004 svn_ra_local__do_switch(svn_ra_session_t *session,
1005                         const svn_ra_reporter3_t **reporter,
1006                         void **report_baton,
1007                         svn_revnum_t update_revision,
1008                         const char *update_target,
1009                         svn_depth_t depth,
1010                         const char *switch_url,
1011                         svn_boolean_t send_copyfrom_args,
1012                         svn_boolean_t ignore_ancestry,
1013                         const svn_delta_editor_t *update_editor,
1014                         void *update_baton,
1015                         apr_pool_t *result_pool,
1016                         apr_pool_t *scratch_pool)
1017 {
1018   return make_reporter(session,
1019                        reporter,
1020                        report_baton,
1021                        update_revision,
1022                        update_target,
1023                        switch_url,
1024                        TRUE /* text_deltas */,
1025                        depth,
1026                        send_copyfrom_args,
1027                        ignore_ancestry,
1028                        update_editor,
1029                        update_baton,
1030                        result_pool, scratch_pool);
1031 }
1032
1033
1034 static svn_error_t *
1035 svn_ra_local__do_status(svn_ra_session_t *session,
1036                         const svn_ra_reporter3_t **reporter,
1037                         void **report_baton,
1038                         const char *status_target,
1039                         svn_revnum_t revision,
1040                         svn_depth_t depth,
1041                         const svn_delta_editor_t *status_editor,
1042                         void *status_baton,
1043                         apr_pool_t *pool)
1044 {
1045   return make_reporter(session,
1046                        reporter,
1047                        report_baton,
1048                        revision,
1049                        status_target,
1050                        NULL,
1051                        FALSE,
1052                        depth,
1053                        FALSE,
1054                        FALSE,
1055                        status_editor,
1056                        status_baton,
1057                        pool, pool);
1058 }
1059
1060
1061 static svn_error_t *
1062 svn_ra_local__do_diff(svn_ra_session_t *session,
1063                       const svn_ra_reporter3_t **reporter,
1064                       void **report_baton,
1065                       svn_revnum_t update_revision,
1066                       const char *update_target,
1067                       svn_depth_t depth,
1068                       svn_boolean_t ignore_ancestry,
1069                       svn_boolean_t text_deltas,
1070                       const char *switch_url,
1071                       const svn_delta_editor_t *update_editor,
1072                       void *update_baton,
1073                       apr_pool_t *pool)
1074 {
1075   return make_reporter(session,
1076                        reporter,
1077                        report_baton,
1078                        update_revision,
1079                        update_target,
1080                        switch_url,
1081                        text_deltas,
1082                        depth,
1083                        FALSE,
1084                        ignore_ancestry,
1085                        update_editor,
1086                        update_baton,
1087                        pool, pool);
1088 }
1089
1090
1091 struct log_baton
1092 {
1093   svn_ra_local__session_baton_t *sess;
1094   svn_log_entry_receiver_t real_cb;
1095   void *real_baton;
1096 };
1097
1098 static svn_error_t *
1099 log_receiver_wrapper(void *baton,
1100                      svn_log_entry_t *log_entry,
1101                      apr_pool_t *pool)
1102 {
1103   struct log_baton *b = baton;
1104   svn_ra_local__session_baton_t *sess = b->sess;
1105
1106   if (sess->callbacks->cancel_func)
1107     SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton));
1108
1109   /* For consistency with the other RA layers, replace an empty
1110      changed-paths hash with a NULL one.
1111
1112      ### Should this be done by svn_ra_get_log2() instead, then? */
1113   if ((log_entry->changed_paths2)
1114       && (apr_hash_count(log_entry->changed_paths2) == 0))
1115     {
1116       log_entry->changed_paths = NULL;
1117       log_entry->changed_paths2 = NULL;
1118     }
1119
1120   return b->real_cb(b->real_baton, log_entry, pool);
1121 }
1122
1123
1124 static svn_error_t *
1125 svn_ra_local__get_log(svn_ra_session_t *session,
1126                       const apr_array_header_t *paths,
1127                       svn_revnum_t start,
1128                       svn_revnum_t end,
1129                       int limit,
1130                       svn_boolean_t discover_changed_paths,
1131                       svn_boolean_t strict_node_history,
1132                       svn_boolean_t include_merged_revisions,
1133                       const apr_array_header_t *revprops,
1134                       svn_log_entry_receiver_t receiver,
1135                       void *receiver_baton,
1136                       apr_pool_t *pool)
1137 {
1138   svn_ra_local__session_baton_t *sess = session->priv;
1139   struct log_baton lb;
1140   apr_array_header_t *abs_paths =
1141     apr_array_make(pool, 0, sizeof(const char *));
1142
1143   if (paths)
1144     {
1145       int i;
1146
1147       for (i = 0; i < paths->nelts; i++)
1148         {
1149           const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
1150           APR_ARRAY_PUSH(abs_paths, const char *) =
1151             svn_fspath__join(sess->fs_path->data, relative_path, pool);
1152         }
1153     }
1154
1155   lb.real_cb = receiver;
1156   lb.real_baton = receiver_baton;
1157   lb.sess = sess;
1158   receiver = log_receiver_wrapper;
1159   receiver_baton = &lb;
1160
1161   return svn_repos__get_logs_compat(sess->repos,
1162                                     abs_paths,
1163                                     start,
1164                                     end,
1165                                     limit,
1166                                     discover_changed_paths,
1167                                     strict_node_history,
1168                                     include_merged_revisions,
1169                                     revprops,
1170                                     NULL, NULL,
1171                                     receiver,
1172                                     receiver_baton,
1173                                     pool);
1174 }
1175
1176
1177 static svn_error_t *
1178 svn_ra_local__do_check_path(svn_ra_session_t *session,
1179                             const char *path,
1180                             svn_revnum_t revision,
1181                             svn_node_kind_t *kind,
1182                             apr_pool_t *pool)
1183 {
1184   svn_ra_local__session_baton_t *sess = session->priv;
1185   svn_fs_root_t *root;
1186   const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1187
1188   if (! SVN_IS_VALID_REVNUM(revision))
1189     SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
1190   SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1191   return svn_fs_check_path(kind, root, abs_path, pool);
1192 }
1193
1194
1195 static svn_error_t *
1196 svn_ra_local__stat(svn_ra_session_t *session,
1197                    const char *path,
1198                    svn_revnum_t revision,
1199                    svn_dirent_t **dirent,
1200                    apr_pool_t *pool)
1201 {
1202   svn_ra_local__session_baton_t *sess = session->priv;
1203   svn_fs_root_t *root;
1204   const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1205
1206   if (! SVN_IS_VALID_REVNUM(revision))
1207     SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
1208   SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1209
1210   return svn_repos_stat(dirent, root, abs_path, pool);
1211 }
1212
1213
1214
1215 \f
1216 /* Obtain the properties for a node, including its 'entry props */
1217 static svn_error_t *
1218 get_node_props(apr_hash_t **props,
1219                svn_fs_root_t *root,
1220                const char *path,
1221                const char *uuid,
1222                apr_pool_t *result_pool,
1223                apr_pool_t *scratch_pool)
1224 {
1225   svn_revnum_t cmt_rev;
1226   const char *cmt_date, *cmt_author;
1227
1228   /* Create a hash with props attached to the fs node. */
1229   SVN_ERR(svn_fs_node_proplist(props, root, path, result_pool));
1230
1231   /* Now add some non-tweakable metadata to the hash as well... */
1232
1233   /* The so-called 'entryprops' with info about CR & friends. */
1234   SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date,
1235                                        &cmt_author, root, path,
1236                                        scratch_pool));
1237
1238   svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_REV,
1239                 svn_string_createf(result_pool, "%ld", cmt_rev));
1240   svn_hash_sets(*props, SVN_PROP_ENTRY_COMMITTED_DATE, cmt_date ?
1241                 svn_string_create(cmt_date, result_pool) : NULL);
1242   svn_hash_sets(*props, SVN_PROP_ENTRY_LAST_AUTHOR, cmt_author ?
1243                 svn_string_create(cmt_author, result_pool) : NULL);
1244   svn_hash_sets(*props, SVN_PROP_ENTRY_UUID,
1245                 svn_string_create(uuid, result_pool));
1246
1247   /* We have no 'wcprops' in ra_local, but might someday. */
1248
1249   return SVN_NO_ERROR;
1250 }
1251
1252
1253 /* Getting just one file. */
1254 static svn_error_t *
1255 svn_ra_local__get_file(svn_ra_session_t *session,
1256                        const char *path,
1257                        svn_revnum_t revision,
1258                        svn_stream_t *stream,
1259                        svn_revnum_t *fetched_rev,
1260                        apr_hash_t **props,
1261                        apr_pool_t *pool)
1262 {
1263   svn_fs_root_t *root;
1264   svn_stream_t *contents;
1265   svn_revnum_t youngest_rev;
1266   svn_ra_local__session_baton_t *sess = session->priv;
1267   const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1268   svn_node_kind_t node_kind;
1269
1270   /* Open the revision's root. */
1271   if (! SVN_IS_VALID_REVNUM(revision))
1272     {
1273       SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
1274       SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
1275       if (fetched_rev != NULL)
1276         *fetched_rev = youngest_rev;
1277     }
1278   else
1279     SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1280
1281   SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, pool));
1282   if (node_kind == svn_node_none)
1283     {
1284       return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1285                                _("'%s' path not found"), abs_path);
1286     }
1287   else if (node_kind != svn_node_file)
1288     {
1289       return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
1290                                _("'%s' is not a file"), abs_path);
1291     }
1292
1293   if (stream)
1294     {
1295       /* Get a stream representing the file's contents. */
1296       SVN_ERR(svn_fs_file_contents(&contents, root, abs_path, pool));
1297
1298       /* Now push data from the fs stream back at the caller's stream.
1299          Note that this particular RA layer does not computing a
1300          checksum as we go, and confirming it against the repository's
1301          checksum when done.  That's because it calls
1302          svn_fs_file_contents() directly, which already checks the
1303          stored checksum, and all we're doing here is writing bytes in
1304          a loop.  Truly, Nothing Can Go Wrong :-).  But RA layers that
1305          go over a network should confirm the checksum.
1306
1307          Note: we are not supposed to close the passed-in stream, so
1308          disown the thing.
1309       */
1310       SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(stream, pool),
1311                                sess->callbacks
1312                                  ? sess->callbacks->cancel_func : NULL,
1313                                sess->callback_baton,
1314                                pool));
1315     }
1316
1317   /* Handle props if requested. */
1318   if (props)
1319     SVN_ERR(get_node_props(props, root, abs_path, sess->uuid, pool, pool));
1320
1321   return SVN_NO_ERROR;
1322 }
1323
1324
1325
1326 /* Getting a directory's entries */
1327 static svn_error_t *
1328 svn_ra_local__get_dir(svn_ra_session_t *session,
1329                       apr_hash_t **dirents,
1330                       svn_revnum_t *fetched_rev,
1331                       apr_hash_t **props,
1332                       const char *path,
1333                       svn_revnum_t revision,
1334                       apr_uint32_t dirent_fields,
1335                       apr_pool_t *pool)
1336 {
1337   svn_fs_root_t *root;
1338   svn_revnum_t youngest_rev;
1339   apr_hash_t *entries;
1340   apr_hash_index_t *hi;
1341   svn_ra_local__session_baton_t *sess = session->priv;
1342   const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1343
1344   /* Open the revision's root. */
1345   if (! SVN_IS_VALID_REVNUM(revision))
1346     {
1347       SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
1348       SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
1349       if (fetched_rev != NULL)
1350         *fetched_rev = youngest_rev;
1351     }
1352   else
1353     SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1354
1355   if (dirents)
1356     {
1357       apr_pool_t *iterpool = svn_pool_create(pool);
1358       /* Get the dir's entries. */
1359       SVN_ERR(svn_fs_dir_entries(&entries, root, abs_path, pool));
1360
1361       /* Loop over the fs dirents, and build a hash of general
1362          svn_dirent_t's. */
1363       *dirents = apr_hash_make(pool);
1364       for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1365         {
1366           const void *key;
1367           void *val;
1368           const char *datestring, *entryname, *fullpath;
1369           svn_fs_dirent_t *fs_entry;
1370           svn_dirent_t *entry = svn_dirent_create(pool);
1371
1372           svn_pool_clear(iterpool);
1373
1374           apr_hash_this(hi, &key, NULL, &val);
1375           entryname = (const char *) key;
1376           fs_entry = (svn_fs_dirent_t *) val;
1377
1378           fullpath = svn_dirent_join(abs_path, entryname, iterpool);
1379
1380           if (dirent_fields & SVN_DIRENT_KIND)
1381             {
1382               /* node kind */
1383               entry->kind = fs_entry->kind;
1384             }
1385
1386           if (dirent_fields & SVN_DIRENT_SIZE)
1387             {
1388               /* size  */
1389               if (fs_entry->kind == svn_node_dir)
1390                 entry->size = SVN_INVALID_FILESIZE;
1391               else
1392                 SVN_ERR(svn_fs_file_length(&(entry->size), root,
1393                                            fullpath, iterpool));
1394             }
1395
1396           if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1397             {
1398               /* has_props? */
1399               SVN_ERR(svn_fs_node_has_props(&entry->has_props,
1400                                             root, fullpath,
1401                                             iterpool));
1402             }
1403
1404           if ((dirent_fields & SVN_DIRENT_TIME)
1405               || (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1406               || (dirent_fields & SVN_DIRENT_CREATED_REV))
1407             {
1408               /* created_rev & friends */
1409               SVN_ERR(svn_repos_get_committed_info(&(entry->created_rev),
1410                                                    &datestring,
1411                                                    &(entry->last_author),
1412                                                    root, fullpath, iterpool));
1413               if (datestring)
1414                 SVN_ERR(svn_time_from_cstring(&(entry->time), datestring,
1415                                               pool));
1416               if (entry->last_author)
1417                 entry->last_author = apr_pstrdup(pool, entry->last_author);
1418             }
1419
1420           /* Store. */
1421           svn_hash_sets(*dirents, entryname, entry);
1422         }
1423       svn_pool_destroy(iterpool);
1424     }
1425
1426   /* Handle props if requested. */
1427   if (props)
1428     SVN_ERR(get_node_props(props, root, abs_path, sess->uuid, pool, pool));
1429
1430   return SVN_NO_ERROR;
1431 }
1432
1433
1434 static svn_error_t *
1435 svn_ra_local__get_locations(svn_ra_session_t *session,
1436                             apr_hash_t **locations,
1437                             const char *path,
1438                             svn_revnum_t peg_revision,
1439                             const apr_array_header_t *location_revisions,
1440                             apr_pool_t *pool)
1441 {
1442   svn_ra_local__session_baton_t *sess = session->priv;
1443   const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1444   return svn_repos_trace_node_locations(sess->fs, locations, abs_path,
1445                                         peg_revision, location_revisions,
1446                                         NULL, NULL, pool);
1447 }
1448
1449
1450 static svn_error_t *
1451 svn_ra_local__get_location_segments(svn_ra_session_t *session,
1452                                     const char *path,
1453                                     svn_revnum_t peg_revision,
1454                                     svn_revnum_t start_rev,
1455                                     svn_revnum_t end_rev,
1456                                     svn_location_segment_receiver_t receiver,
1457                                     void *receiver_baton,
1458                                     apr_pool_t *pool)
1459 {
1460   svn_ra_local__session_baton_t *sess = session->priv;
1461   const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1462   return svn_repos_node_location_segments(sess->repos, abs_path,
1463                                           peg_revision, start_rev, end_rev,
1464                                           receiver, receiver_baton,
1465                                           NULL, NULL, pool);
1466 }
1467
1468 struct lock_baton_t {
1469   svn_ra_lock_callback_t lock_func;
1470   void *lock_baton;
1471   const char *fs_path;
1472   svn_boolean_t is_lock;
1473   svn_error_t *cb_err;
1474 };
1475
1476 /* Implements svn_fs_lock_callback_t.  Used by svn_ra_local__lock and
1477    svn_ra_local__unlock to forward to supplied callback and record any
1478    callback error. */
1479 static svn_error_t *
1480 lock_cb(void *lock_baton,
1481         const char *path,
1482         const svn_lock_t *lock,
1483         svn_error_t *fs_err,
1484         apr_pool_t *pool)
1485 {
1486   struct lock_baton_t *b = lock_baton;
1487
1488   if (b && !b->cb_err && b->lock_func)
1489     {
1490       path = svn_fspath__skip_ancestor(b->fs_path, path);
1491       b->cb_err = b->lock_func(b->lock_baton, path, b->is_lock, lock, fs_err,
1492                                pool);
1493     }
1494
1495   return SVN_NO_ERROR;
1496 }
1497
1498 static svn_error_t *
1499 svn_ra_local__lock(svn_ra_session_t *session,
1500                    apr_hash_t *path_revs,
1501                    const char *comment,
1502                    svn_boolean_t force,
1503                    svn_ra_lock_callback_t lock_func,
1504                    void *lock_baton,
1505                    apr_pool_t *pool)
1506 {
1507   svn_ra_local__session_baton_t *sess = session->priv;
1508   apr_hash_t *targets = apr_hash_make(pool);
1509   apr_hash_index_t *hi;
1510   svn_error_t *err;
1511   struct lock_baton_t baton = {0};
1512
1513   /* A username is absolutely required to lock a path. */
1514   SVN_ERR(get_username(session, pool));
1515
1516   for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1517     {
1518       const char *abs_path = svn_fspath__join(sess->fs_path->data,
1519                                               apr_hash_this_key(hi), pool);
1520       svn_revnum_t current_rev = *(svn_revnum_t *)apr_hash_this_val(hi);
1521       svn_fs_lock_target_t *target = svn_fs_lock_target_create(NULL,
1522                                                                current_rev,
1523                                                                pool);
1524
1525       svn_hash_sets(targets, abs_path, target);
1526     }
1527
1528   baton.lock_func = lock_func;
1529   baton.lock_baton = lock_baton;
1530   baton.fs_path = sess->fs_path->data;
1531   baton.is_lock = TRUE;
1532   baton.cb_err = SVN_NO_ERROR;
1533
1534   err = svn_repos_fs_lock_many(sess->repos, targets, comment,
1535                                FALSE /* not DAV comment */,
1536                                0 /* no expiration */, force,
1537                                lock_cb, &baton,
1538                                pool, pool);
1539
1540   if (err && baton.cb_err)
1541     svn_error_compose(err, baton.cb_err);
1542   else if (!err)
1543     err = baton.cb_err;
1544
1545   return svn_error_trace(err);
1546 }
1547
1548
1549 static svn_error_t *
1550 svn_ra_local__unlock(svn_ra_session_t *session,
1551                      apr_hash_t *path_tokens,
1552                      svn_boolean_t force,
1553                      svn_ra_lock_callback_t lock_func,
1554                      void *lock_baton,
1555                      apr_pool_t *pool)
1556 {
1557   svn_ra_local__session_baton_t *sess = session->priv;
1558   apr_hash_t *targets = apr_hash_make(pool);
1559   apr_hash_index_t *hi;
1560   svn_error_t *err;
1561   struct lock_baton_t baton = {0};
1562
1563   /* A username is absolutely required to unlock a path. */
1564   SVN_ERR(get_username(session, pool));
1565
1566   for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1567     {
1568       const char *abs_path = svn_fspath__join(sess->fs_path->data,
1569                                               apr_hash_this_key(hi), pool);
1570       const char *token = apr_hash_this_val(hi);
1571
1572       svn_hash_sets(targets, abs_path, token);
1573     }
1574
1575   baton.lock_func = lock_func;
1576   baton.lock_baton = lock_baton;
1577   baton.fs_path = sess->fs_path->data;
1578   baton.is_lock = FALSE;
1579   baton.cb_err = SVN_NO_ERROR;
1580
1581   err = svn_repos_fs_unlock_many(sess->repos, targets, force, lock_cb, &baton,
1582                                  pool, pool);
1583
1584   if (err && baton.cb_err)
1585     svn_error_compose(err, baton.cb_err);
1586   else if (!err)
1587     err = baton.cb_err;
1588
1589   return svn_error_trace(err);
1590 }
1591
1592
1593
1594 static svn_error_t *
1595 svn_ra_local__get_lock(svn_ra_session_t *session,
1596                        svn_lock_t **lock,
1597                        const char *path,
1598                        apr_pool_t *pool)
1599 {
1600   svn_ra_local__session_baton_t *sess = session->priv;
1601   const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1602   return svn_fs_get_lock(lock, sess->fs, abs_path, pool);
1603 }
1604
1605
1606
1607 static svn_error_t *
1608 svn_ra_local__get_locks(svn_ra_session_t *session,
1609                         apr_hash_t **locks,
1610                         const char *path,
1611                         svn_depth_t depth,
1612                         apr_pool_t *pool)
1613 {
1614   svn_ra_local__session_baton_t *sess = session->priv;
1615   const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1616
1617   /* Kinda silly to call the repos wrapper, since we have no authz
1618      func to give it.  But heck, why not. */
1619   return svn_repos_fs_get_locks2(locks, sess->repos, abs_path, depth,
1620                                  NULL, NULL, pool);
1621 }
1622
1623
1624 static svn_error_t *
1625 svn_ra_local__replay(svn_ra_session_t *session,
1626                      svn_revnum_t revision,
1627                      svn_revnum_t low_water_mark,
1628                      svn_boolean_t send_deltas,
1629                      const svn_delta_editor_t *editor,
1630                      void *edit_baton,
1631                      apr_pool_t *pool)
1632 {
1633   svn_ra_local__session_baton_t *sess = session->priv;
1634   svn_fs_root_t *root;
1635
1636   SVN_ERR(svn_fs_revision_root(&root, svn_repos_fs(sess->repos),
1637                                revision, pool));
1638   return svn_repos_replay2(root, sess->fs_path->data, low_water_mark,
1639                            send_deltas, editor, edit_baton, NULL, NULL,
1640                            pool);
1641 }
1642
1643
1644 static svn_error_t *
1645 svn_ra_local__replay_range(svn_ra_session_t *session,
1646                            svn_revnum_t start_revision,
1647                            svn_revnum_t end_revision,
1648                            svn_revnum_t low_water_mark,
1649                            svn_boolean_t send_deltas,
1650                            svn_ra_replay_revstart_callback_t revstart_func,
1651                            svn_ra_replay_revfinish_callback_t revfinish_func,
1652                            void *replay_baton,
1653                            apr_pool_t *pool)
1654 {
1655   return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
1656 }
1657
1658
1659 static svn_error_t *
1660 svn_ra_local__has_capability(svn_ra_session_t *session,
1661                              svn_boolean_t *has,
1662                              const char *capability,
1663                              apr_pool_t *pool)
1664 {
1665   svn_ra_local__session_baton_t *sess = session->priv;
1666
1667   if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0
1668       || strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0
1669       || strcmp(capability, SVN_RA_CAPABILITY_PARTIAL_REPLAY) == 0
1670       || strcmp(capability, SVN_RA_CAPABILITY_COMMIT_REVPROPS) == 0
1671       || strcmp(capability, SVN_RA_CAPABILITY_ATOMIC_REVPROPS) == 0
1672       || strcmp(capability, SVN_RA_CAPABILITY_INHERITED_PROPS) == 0
1673       || strcmp(capability, SVN_RA_CAPABILITY_EPHEMERAL_TXNPROPS) == 0
1674       || strcmp(capability, SVN_RA_CAPABILITY_GET_FILE_REVS_REVERSE) == 0
1675       || strcmp(capability, SVN_RA_CAPABILITY_LIST) == 0
1676       )
1677     {
1678       *has = TRUE;
1679     }
1680   else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
1681     {
1682       /* With mergeinfo, the code's capabilities may not reflect the
1683          repository's, so inquire further. */
1684       SVN_ERR(svn_repos_has_capability(sess->repos, has,
1685                                        SVN_REPOS_CAPABILITY_MERGEINFO,
1686                                        pool));
1687     }
1688   else  /* Don't know any other capabilities, so error. */
1689     {
1690       return svn_error_createf
1691         (SVN_ERR_UNKNOWN_CAPABILITY, NULL,
1692          _("Don't know anything about capability '%s'"), capability);
1693     }
1694
1695   return SVN_NO_ERROR;
1696 }
1697
1698 static svn_error_t *
1699 svn_ra_local__get_deleted_rev(svn_ra_session_t *session,
1700                               const char *path,
1701                               svn_revnum_t peg_revision,
1702                               svn_revnum_t end_revision,
1703                               svn_revnum_t *revision_deleted,
1704                               apr_pool_t *pool)
1705 {
1706   svn_ra_local__session_baton_t *sess = session->priv;
1707   const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
1708
1709   SVN_ERR(svn_repos_deleted_rev(sess->fs,
1710                                 abs_path,
1711                                 peg_revision,
1712                                 end_revision,
1713                                 revision_deleted,
1714                                 pool));
1715
1716   return SVN_NO_ERROR;
1717 }
1718
1719 static svn_error_t *
1720 svn_ra_local__get_inherited_props(svn_ra_session_t *session,
1721                                   apr_array_header_t **iprops,
1722                                   const char *path,
1723                                   svn_revnum_t revision,
1724                                   apr_pool_t *result_pool,
1725                                   apr_pool_t *scratch_pool)
1726 {
1727   svn_fs_root_t *root;
1728   svn_ra_local__session_baton_t *sess = session->priv;
1729   const char *abs_path = svn_fspath__join(sess->fs_path->data, path,
1730                                           scratch_pool);
1731   svn_node_kind_t node_kind;
1732
1733   /* Open the revision's root. */
1734   SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, scratch_pool));
1735
1736   SVN_ERR(svn_fs_check_path(&node_kind, root, abs_path, scratch_pool));
1737   if (node_kind == svn_node_none)
1738     {
1739       return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1740                                _("'%s' path not found"), abs_path);
1741     }
1742
1743   return svn_error_trace(
1744                 svn_repos_fs_get_inherited_props(iprops, root, abs_path,
1745                                                  NULL /* propname */,
1746                                                  NULL, NULL /* auth */,
1747                                                  result_pool, scratch_pool));
1748 }
1749
1750 static svn_error_t *
1751 svn_ra_local__register_editor_shim_callbacks(svn_ra_session_t *session,
1752                                     svn_delta_shim_callbacks_t *callbacks)
1753 {
1754   /* This is currenly a no-op, since we don't provide our own editor, just
1755      use the one the libsvn_repos hands back to us. */
1756   return SVN_NO_ERROR;
1757 }
1758
1759
1760 static svn_error_t *
1761 svn_ra_local__get_commit_ev2(svn_editor_t **editor,
1762                              svn_ra_session_t *session,
1763                              apr_hash_t *revprops,
1764                              svn_commit_callback2_t commit_cb,
1765                              void *commit_baton,
1766                              apr_hash_t *lock_tokens,
1767                              svn_boolean_t keep_locks,
1768                              svn_ra__provide_base_cb_t provide_base_cb,
1769                              svn_ra__provide_props_cb_t provide_props_cb,
1770                              svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
1771                              void *cb_baton,
1772                              svn_cancel_func_t cancel_func,
1773                              void *cancel_baton,
1774                              apr_pool_t *result_pool,
1775                              apr_pool_t *scratch_pool)
1776 {
1777   svn_ra_local__session_baton_t *sess = session->priv;
1778   struct deltify_etc_baton *deb = apr_palloc(result_pool, sizeof(*deb));
1779
1780   remap_commit_callback(&commit_cb, &commit_baton, session,
1781                         commit_cb, commit_baton, result_pool);
1782
1783   /* NOTE: the RA callbacks are ignored. We pass everything directly to
1784      the REPOS editor.  */
1785
1786   /* Prepare the baton for deltify_etc()  */
1787   deb->fs = sess->fs;
1788   deb->repos = sess->repos;
1789   deb->fspath_base = sess->fs_path->data;
1790   if (! keep_locks)
1791     deb->lock_tokens = lock_tokens;
1792   else
1793     deb->lock_tokens = NULL;
1794   deb->commit_cb = commit_cb;
1795   deb->commit_baton = commit_baton;
1796
1797   /* Ensure there is a username (and an FS access context) associated with
1798      the session and its FS handle.  */
1799   SVN_ERR(get_username(session, scratch_pool));
1800
1801   /* If there are lock tokens to add, do so.  */
1802   SVN_ERR(apply_lock_tokens(sess->fs, sess->fs_path->data, lock_tokens,
1803                             session->pool, scratch_pool));
1804
1805   /* Copy the REVPROPS and insert the author/username.  */
1806   revprops = apr_hash_copy(scratch_pool, revprops);
1807   svn_hash_sets(revprops, SVN_PROP_REVISION_AUTHOR,
1808                 svn_string_create(sess->username, scratch_pool));
1809
1810   return svn_error_trace(svn_repos__get_commit_ev2(
1811                            editor, sess->repos, NULL /* authz */,
1812                            NULL /* authz_repos_name */, NULL /* authz_user */,
1813                            revprops,
1814                            deltify_etc, deb, cancel_func, cancel_baton,
1815                            result_pool, scratch_pool));
1816 }
1817
1818 /* Trivially forward repos-layer callbacks to RA-layer callbacks.
1819  * Their signatures are the same. */
1820 typedef struct dirent_receiver_baton_t
1821 {
1822   svn_ra_dirent_receiver_t receiver;
1823   void *receiver_baton;
1824 } dirent_receiver_baton_t;
1825
1826 static svn_error_t *
1827 dirent_receiver(const char *rel_path,
1828                 svn_dirent_t *dirent,
1829                 void *baton,
1830                 apr_pool_t *pool)
1831 {
1832   dirent_receiver_baton_t *b = baton;
1833   return b->receiver(rel_path, dirent, b->receiver_baton, pool);
1834 }
1835
1836 static svn_error_t *
1837 svn_ra_local__list(svn_ra_session_t *session,
1838                    const char *path,
1839                    svn_revnum_t revision,
1840                    const apr_array_header_t *patterns,
1841                    svn_depth_t depth,
1842                    apr_uint32_t dirent_fields,
1843                    svn_ra_dirent_receiver_t receiver,
1844                    void *receiver_baton,
1845                    apr_pool_t *pool)
1846 {
1847   svn_ra_local__session_baton_t *sess = session->priv;
1848   svn_fs_root_t *root;
1849   svn_boolean_t path_info_only = (dirent_fields & ~SVN_DIRENT_KIND) == 0;
1850
1851   dirent_receiver_baton_t baton;
1852   baton.receiver = receiver;
1853   baton.receiver_baton = receiver_baton;
1854
1855   SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1856   path = svn_dirent_join(sess->fs_path->data, path, pool);
1857   return svn_error_trace(svn_repos_list(root, path, patterns, depth,
1858                                         path_info_only, NULL, NULL,
1859                                         dirent_receiver, &baton,
1860                                         sess->callbacks
1861                                           ? sess->callbacks->cancel_func
1862                                           : NULL,
1863                                         sess->callback_baton, pool));
1864 }
1865
1866 /*----------------------------------------------------------------*/
1867 \f
1868 static const svn_version_t *
1869 ra_local_version(void)
1870 {
1871   SVN_VERSION_BODY;
1872 }
1873
1874 /** The ra_vtable **/
1875
1876 static const svn_ra__vtable_t ra_local_vtable =
1877 {
1878   ra_local_version,
1879   svn_ra_local__get_description,
1880   svn_ra_local__get_schemes,
1881   svn_ra_local__open,
1882   svn_ra_local__dup_session,
1883   svn_ra_local__reparent,
1884   svn_ra_local__get_session_url,
1885   svn_ra_local__get_latest_revnum,
1886   svn_ra_local__get_dated_revision,
1887   svn_ra_local__change_rev_prop,
1888   svn_ra_local__rev_proplist,
1889   svn_ra_local__rev_prop,
1890   svn_ra_local__get_commit_editor,
1891   svn_ra_local__get_file,
1892   svn_ra_local__get_dir,
1893   svn_ra_local__get_mergeinfo,
1894   svn_ra_local__do_update,
1895   svn_ra_local__do_switch,
1896   svn_ra_local__do_status,
1897   svn_ra_local__do_diff,
1898   svn_ra_local__get_log,
1899   svn_ra_local__do_check_path,
1900   svn_ra_local__stat,
1901   svn_ra_local__get_uuid,
1902   svn_ra_local__get_repos_root,
1903   svn_ra_local__get_locations,
1904   svn_ra_local__get_location_segments,
1905   svn_ra_local__get_file_revs,
1906   svn_ra_local__lock,
1907   svn_ra_local__unlock,
1908   svn_ra_local__get_lock,
1909   svn_ra_local__get_locks,
1910   svn_ra_local__replay,
1911   svn_ra_local__has_capability,
1912   svn_ra_local__replay_range,
1913   svn_ra_local__get_deleted_rev,
1914   svn_ra_local__get_inherited_props,
1915   NULL /* set_svn_ra_open */,
1916   svn_ra_local__list ,
1917   svn_ra_local__register_editor_shim_callbacks,
1918   svn_ra_local__get_commit_ev2,
1919   NULL /* replay_range_ev2 */
1920 };
1921
1922
1923 /*----------------------------------------------------------------*/
1924 \f
1925 /** The One Public Routine, called by libsvn_ra **/
1926
1927 svn_error_t *
1928 svn_ra_local__init(const svn_version_t *loader_version,
1929                    const svn_ra__vtable_t **vtable,
1930                    apr_pool_t *pool)
1931 {
1932   static const svn_version_checklist_t checklist[] =
1933     {
1934       { "svn_subr",  svn_subr_version },
1935       { "svn_delta", svn_delta_version },
1936       { "svn_repos", svn_repos_version },
1937       { "svn_fs",    svn_fs_version },
1938       { NULL, NULL }
1939     };
1940
1941
1942   /* Simplified version check to make sure we can safely use the
1943      VTABLE parameter. The RA loader does a more exhaustive check. */
1944   if (loader_version->major != SVN_VER_MAJOR)
1945     return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
1946                              _("Unsupported RA loader version (%d) for "
1947                                "ra_local"),
1948                              loader_version->major);
1949
1950   SVN_ERR(svn_ver_check_list2(ra_local_version(), checklist, svn_ver_equal));
1951
1952 #ifndef SVN_LIBSVN_RA_LINKS_RA_LOCAL
1953   /* This means the library was loaded as a DSO, so use the DSO pool. */
1954   SVN_ERR(svn_fs_initialize(svn_dso__pool()));
1955 #endif
1956
1957   *vtable = &ra_local_vtable;
1958
1959   return SVN_NO_ERROR;
1960 }
1961 \f
1962 /* Compatibility wrapper for the 1.1 and before API. */
1963 #define NAME "ra_local"
1964 #define DESCRIPTION RA_LOCAL_DESCRIPTION
1965 #define VTBL ra_local_vtable
1966 #define INITFUNC svn_ra_local__init
1967 #define COMPAT_INITFUNC svn_ra_local_init
1968 #include "../libsvn_ra/wrapper_template.h"