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