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