In the command-line client, forbid
[svn.git] / subversion / libsvn_ra_local / ra_plugin.c
blobcca1c944577794f91c22fc4397dd640b3f03c068
1 /*
2 * ra_plugin.c : the main RA module for local repository access
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 #include "ra_local.h"
20 #include "svn_ra.h"
21 #include "svn_fs.h"
22 #include "svn_delta.h"
23 #include "svn_repos.h"
24 #include "svn_pools.h"
25 #include "svn_time.h"
26 #include "svn_props.h"
27 #include "svn_mergeinfo.h"
28 #include "svn_path.h"
30 #include "svn_private_config.h"
31 #include "../libsvn_ra/ra_loader.h"
33 #define APR_WANT_STRFUNC
34 #include <apr_want.h>
36 #include <assert.h>
39 /*----------------------------------------------------------------*/
41 /*** Miscellaneous helper functions ***/
44 /* Pool cleanup handler: ensure that the access descriptor of the
45 filesystem (svn_fs_t *) DATA is set to NULL. */
46 static apr_status_t
47 cleanup_access(void *data)
49 svn_error_t *serr;
50 svn_fs_t *fs = data;
52 serr = svn_fs_set_access(fs, NULL);
54 if (serr)
56 apr_status_t apr_err = serr->apr_err;
57 svn_error_clear(serr);
58 return apr_err;
61 return APR_SUCCESS;
65 /* Fetch a username for use with SESS. */
66 static svn_error_t *
67 get_username(svn_ra_session_t *session,
68 apr_pool_t *pool)
70 svn_ra_local__session_baton_t *sess = session->priv;
71 svn_auth_iterstate_t *iterstate;
72 svn_fs_access_t *access_ctx;
74 /* If we've already found the username don't ask for it again. */
75 if (! sess->username)
77 /* Get a username somehow, so we have some svn:author property to
78 attach to a commit. */
79 if (sess->callbacks->auth_baton)
81 void *creds;
82 svn_auth_cred_username_t *username_creds;
83 SVN_ERR(svn_auth_first_credentials(&creds, &iterstate,
84 SVN_AUTH_CRED_USERNAME,
85 sess->uuid, /* realmstring */
86 sess->callbacks->auth_baton,
87 pool));
89 /* No point in calling next_creds(), since that assumes that the
90 first_creds() somehow failed to authenticate. But there's no
91 challenge going on, so we use whatever creds we get back on
92 the first try. */
93 username_creds = creds;
94 if (username_creds && username_creds->username)
96 sess->username = apr_pstrdup(session->pool,
97 username_creds->username);
98 svn_error_clear(svn_auth_save_credentials(iterstate, pool));
100 else
101 sess->username = "";
103 else
104 sess->username = "";
107 /* If we have a real username, attach it to the filesystem so that it can
108 be used to validate locks. Even if there already is a user context
109 associated, it may contain irrelevant lock tokens, so always create a new.
111 if (*sess->username)
113 SVN_ERR(svn_fs_create_access(&access_ctx, sess->username,
114 pool));
115 SVN_ERR(svn_fs_set_access(sess->fs, access_ctx));
117 /* Make sure this context is disassociated when the pool gets
118 destroyed. */
119 apr_pool_cleanup_register(pool, sess->fs, cleanup_access,
120 apr_pool_cleanup_null);
123 return SVN_NO_ERROR;
126 /*----------------------------------------------------------------*/
128 /*** The reporter vtable needed by do_update() and friends ***/
130 typedef struct reporter_baton_t
132 svn_ra_local__session_baton_t *sess;
133 void *report_baton;
135 } reporter_baton_t;
138 static void *
139 make_reporter_baton(svn_ra_local__session_baton_t *sess,
140 void *report_baton,
141 apr_pool_t *pool)
143 reporter_baton_t *rbaton = apr_palloc(pool, sizeof(*rbaton));
144 rbaton->sess = sess;
145 rbaton->report_baton = report_baton;
146 return rbaton;
150 static svn_error_t *
151 reporter_set_path(void *reporter_baton,
152 const char *path,
153 svn_revnum_t revision,
154 svn_depth_t depth,
155 svn_boolean_t start_empty,
156 const char *lock_token,
157 apr_pool_t *pool)
159 reporter_baton_t *rbaton = reporter_baton;
160 return svn_repos_set_path3(rbaton->report_baton, path,
161 revision, depth, start_empty, lock_token, pool);
165 static svn_error_t *
166 reporter_delete_path(void *reporter_baton,
167 const char *path,
168 apr_pool_t *pool)
170 reporter_baton_t *rbaton = reporter_baton;
171 return svn_repos_delete_path(rbaton->report_baton, path, pool);
175 static svn_error_t *
176 reporter_link_path(void *reporter_baton,
177 const char *path,
178 const char *url,
179 svn_revnum_t revision,
180 svn_depth_t depth,
181 svn_boolean_t start_empty,
182 const char *lock_token,
183 apr_pool_t *pool)
185 reporter_baton_t *rbaton = reporter_baton;
186 const char *fs_path = NULL;
187 const char *repos_url_decoded;
188 int repos_url_len;
190 url = svn_path_uri_decode(url, pool);
191 repos_url_decoded = svn_path_uri_decode(rbaton->sess->repos_url, pool);
192 repos_url_len = strlen(repos_url_decoded);
193 if (strncmp(url, repos_url_decoded, repos_url_len) != 0)
194 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
195 _("'%s'\n"
196 "is not the same repository as\n"
197 "'%s'"), url, rbaton->sess->repos_url);
198 fs_path = url + repos_url_len;
199 return svn_repos_link_path3(rbaton->report_baton, path, fs_path, revision,
200 depth, start_empty, lock_token, pool);
204 static svn_error_t *
205 reporter_finish_report(void *reporter_baton,
206 apr_pool_t *pool)
208 reporter_baton_t *rbaton = reporter_baton;
209 return svn_repos_finish_report(rbaton->report_baton, pool);
213 static svn_error_t *
214 reporter_abort_report(void *reporter_baton,
215 apr_pool_t *pool)
217 reporter_baton_t *rbaton = reporter_baton;
218 return svn_repos_abort_report(rbaton->report_baton, pool);
222 static const svn_ra_reporter3_t ra_local_reporter =
224 reporter_set_path,
225 reporter_delete_path,
226 reporter_link_path,
227 reporter_finish_report,
228 reporter_abort_report
232 static svn_error_t *
233 make_reporter(svn_ra_session_t *session,
234 const svn_ra_reporter3_t **reporter,
235 void **report_baton,
236 svn_revnum_t revision,
237 const char *target,
238 const char *other_url,
239 svn_boolean_t text_deltas,
240 svn_depth_t depth,
241 svn_boolean_t send_copyfrom_args,
242 svn_boolean_t ignore_ancestry,
243 const svn_delta_editor_t *editor,
244 void *edit_baton,
245 apr_pool_t *pool)
247 svn_ra_local__session_baton_t *sess = session->priv;
248 void *rbaton;
249 int repos_url_len;
250 const char *other_fs_path = NULL;
251 const char *repos_url_decoded;
253 /* Get the HEAD revision if one is not supplied. */
254 if (! SVN_IS_VALID_REVNUM(revision))
255 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
257 /* If OTHER_URL was provided, validate it and convert it into a
258 regular filesystem path. */
259 if (other_url)
261 other_url = svn_path_uri_decode(other_url, pool);
262 repos_url_decoded = svn_path_uri_decode(sess->repos_url, pool);
263 repos_url_len = strlen(repos_url_decoded);
265 /* Sanity check: the other_url better be in the same repository as
266 the original session url! */
267 if (strncmp(other_url, repos_url_decoded, repos_url_len) != 0)
268 return svn_error_createf
269 (SVN_ERR_RA_ILLEGAL_URL, NULL,
270 _("'%s'\n"
271 "is not the same repository as\n"
272 "'%s'"), other_url, sess->repos_url);
274 other_fs_path = other_url + repos_url_len;
277 /* Pass back our reporter */
278 *reporter = &ra_local_reporter;
280 SVN_ERR(get_username(session, pool));
282 if (sess->callbacks)
283 SVN_ERR(svn_delta_get_cancellation_editor(sess->callbacks->cancel_func,
284 sess->callback_baton,
285 editor,
286 edit_baton,
287 &editor,
288 &edit_baton,
289 pool));
291 /* Build a reporter baton. */
292 SVN_ERR(svn_repos_begin_report2(&rbaton,
293 revision,
294 sess->repos,
295 sess->fs_path->data,
296 target,
297 other_fs_path,
298 text_deltas,
299 depth,
300 ignore_ancestry,
301 send_copyfrom_args,
302 editor,
303 edit_baton,
304 NULL,
305 NULL,
306 pool));
308 /* Wrap the report baton given us by the repos layer with our own
309 reporter baton. */
310 *report_baton = make_reporter_baton(sess, rbaton, pool);
312 return SVN_NO_ERROR;
316 /*----------------------------------------------------------------*/
318 /*** Deltification stuff for get_commit_editor() ***/
320 struct deltify_etc_baton
322 svn_fs_t *fs; /* the fs to deltify in */
323 svn_repos_t *repos; /* repos for unlocking */
324 const char *fs_path; /* fs-path part of split session URL */
325 apr_hash_t *lock_tokens; /* tokens to unlock, if any */
326 apr_pool_t *pool; /* pool for scratch work */
327 svn_commit_callback2_t callback; /* the original callback */
328 void *callback_baton; /* the original callback's baton */
331 /* This implements 'svn_commit_callback_t'. Its invokes the original
332 (wrapped) callback, but also does deltification on the new revision and
333 possibly unlocks committed paths.
334 BATON is 'struct deltify_etc_baton *'. */
335 static svn_error_t *
336 deltify_etc(const svn_commit_info_t *commit_info,
337 void *baton, apr_pool_t *pool)
339 struct deltify_etc_baton *db = baton;
340 svn_error_t *err1, *err2;
341 apr_hash_index_t *hi;
342 apr_pool_t *iterpool;
344 /* Invoke the original callback first, in case someone's waiting to
345 know the revision number so they can go off and annotate an
346 issue or something. */
347 err1 = (*db->callback)(commit_info, db->callback_baton, pool);
349 /* Maybe unlock the paths. */
350 if (db->lock_tokens)
352 iterpool = svn_pool_create(db->pool);
353 for (hi = apr_hash_first(db->pool, db->lock_tokens); hi;
354 hi = apr_hash_next(hi))
356 const void *rel_path;
357 void *val;
358 const char *abs_path, *token;
360 svn_pool_clear(iterpool);
361 apr_hash_this(hi, &rel_path, NULL, &val);
362 token = val;
363 abs_path = svn_path_join(db->fs_path, rel_path, iterpool);
364 /* We may get errors here if the lock was broken or stolen
365 after the commit succeeded. This is fine and should be
366 ignored. */
367 svn_error_clear(svn_repos_fs_unlock(db->repos, abs_path, token,
368 FALSE, iterpool));
370 svn_pool_destroy(iterpool);
373 /* But, deltification shouldn't be stopped just because someone's
374 random callback failed, so proceed unconditionally on to
375 deltification. */
376 err2 = svn_fs_deltify_revision(db->fs, commit_info->revision, db->pool);
378 /* It's more interesting if the original callback failed, so let
379 that one dominate. */
380 if (err1)
382 svn_error_clear(err2);
383 return err1;
386 return err2;
389 /*----------------------------------------------------------------*/
391 /*** The RA vtable routines ***/
393 #define RA_LOCAL_DESCRIPTION \
394 N_("Module for accessing a repository on local disk.")
396 static const char *
397 svn_ra_local__get_description(void)
399 return _(RA_LOCAL_DESCRIPTION);
402 static const char * const *
403 svn_ra_local__get_schemes(apr_pool_t *pool)
405 static const char *schemes[] = { "file", NULL };
407 return schemes;
410 static svn_error_t *
411 svn_ra_local__open(svn_ra_session_t *session,
412 const char *repos_URL,
413 const svn_ra_callbacks2_t *callbacks,
414 void *callback_baton,
415 apr_hash_t *config,
416 apr_pool_t *pool)
418 svn_ra_local__session_baton_t *sess;
419 const char *fs_path;
421 /* Allocate and stash the session_sess args we have already. */
422 sess = apr_pcalloc(pool, sizeof(*sess));
423 sess->callbacks = callbacks;
424 sess->callback_baton = callback_baton;
426 /* Look through the URL, figure out which part points to the
427 repository, and which part is the path *within* the
428 repository. */
429 SVN_ERR_W(svn_ra_local__split_URL(&(sess->repos),
430 &(sess->repos_url),
431 &fs_path,
432 repos_URL,
433 session->pool),
434 _("Unable to open an ra_local session to URL"));
435 sess->fs_path = svn_stringbuf_create(fs_path, session->pool);
437 /* Cache the filesystem object from the repos here for
438 convenience. */
439 sess->fs = svn_repos_fs(sess->repos);
441 /* Cache the repository UUID as well */
442 SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));
444 /* Be sure username is NULL so we know to look it up / ask for it */
445 sess->username = NULL;
447 session->priv = sess;
448 return SVN_NO_ERROR;
451 static svn_error_t *
452 svn_ra_local__reparent(svn_ra_session_t *session,
453 const char *url,
454 apr_pool_t *pool)
456 svn_ra_local__session_baton_t *sess = session->priv;
457 const char *relpath = "";
459 /* If the new URL isn't the same as our repository root URL, then
460 let's ensure that it's some child of it. */
461 if (strcmp(url, sess->repos_url) != 0)
462 relpath = svn_path_is_child(sess->repos_url, url, pool);
463 if (! relpath)
464 return svn_error_createf
465 (SVN_ERR_RA_ILLEGAL_URL, NULL,
466 _("URL '%s' is not a child of the session's repository root "
467 "URL '%s'"), url, sess->repos_url);
469 /* Update our FS_PATH sess member to point to our new
470 relative-URL-turned-absolute-filesystem-path. */
471 relpath = apr_pstrcat(pool, "/", svn_path_uri_decode(relpath, pool), NULL);
472 svn_stringbuf_set(sess->fs_path, relpath);
474 return SVN_NO_ERROR;
477 static svn_error_t *
478 svn_ra_local__get_session_url(svn_ra_session_t *session,
479 const char **url,
480 apr_pool_t *pool)
482 svn_ra_local__session_baton_t *sess = session->priv;
483 *url = svn_path_join(sess->repos_url,
484 svn_path_uri_encode(sess->fs_path->data + 1, pool),
485 pool);
486 return SVN_NO_ERROR;
489 static svn_error_t *
490 svn_ra_local__get_latest_revnum(svn_ra_session_t *session,
491 svn_revnum_t *latest_revnum,
492 apr_pool_t *pool)
494 svn_ra_local__session_baton_t *sess = session->priv;
495 return svn_fs_youngest_rev(latest_revnum, sess->fs, pool);
498 static svn_error_t *
499 svn_ra_local__get_file_revs(svn_ra_session_t *session,
500 const char *path,
501 svn_revnum_t start,
502 svn_revnum_t end,
503 svn_boolean_t include_merged_revisions,
504 svn_file_rev_handler_t handler,
505 void *handler_baton,
506 apr_pool_t *pool)
508 svn_ra_local__session_baton_t *sess = session->priv;
509 const char *abs_path = svn_path_join(sess->fs_path->data, path, pool);
510 return svn_repos_get_file_revs2(sess->repos, abs_path, start, end,
511 include_merged_revisions, NULL, NULL,
512 handler, handler_baton, pool);
515 static svn_error_t *
516 svn_ra_local__get_dated_revision(svn_ra_session_t *session,
517 svn_revnum_t *revision,
518 apr_time_t tm,
519 apr_pool_t *pool)
521 svn_ra_local__session_baton_t *sess = session->priv;
522 return svn_repos_dated_revision(revision, sess->repos, tm, pool);
526 static svn_error_t *
527 svn_ra_local__change_rev_prop(svn_ra_session_t *session,
528 svn_revnum_t rev,
529 const char *name,
530 const svn_string_t *value,
531 apr_pool_t *pool)
533 svn_ra_local__session_baton_t *sess = session->priv;
534 SVN_ERR(get_username(session, pool));
535 SVN_ERR(svn_repos_fs_change_rev_prop3(sess->repos, rev, sess->username,
536 name, value, TRUE, TRUE, NULL, NULL,
537 pool));
538 return SVN_NO_ERROR;
541 static svn_error_t *
542 svn_ra_local__get_uuid(svn_ra_session_t *session,
543 const char **uuid,
544 apr_pool_t *pool)
546 svn_ra_local__session_baton_t *sess = session->priv;
547 *uuid = sess->uuid;
548 return SVN_NO_ERROR;
551 static svn_error_t *
552 svn_ra_local__get_repos_root(svn_ra_session_t *session,
553 const char **url,
554 apr_pool_t *pool)
556 svn_ra_local__session_baton_t *sess = session->priv;
557 *url = sess->repos_url;
558 return SVN_NO_ERROR;
561 static svn_error_t *
562 svn_ra_local__rev_proplist(svn_ra_session_t *session,
563 svn_revnum_t rev,
564 apr_hash_t **props,
565 apr_pool_t *pool)
567 svn_ra_local__session_baton_t *sess = session->priv;
568 return svn_repos_fs_revision_proplist(props, sess->repos, rev,
569 NULL, NULL, pool);
572 static svn_error_t *
573 svn_ra_local__rev_prop(svn_ra_session_t *session,
574 svn_revnum_t rev,
575 const char *name,
576 svn_string_t **value,
577 apr_pool_t *pool)
579 svn_ra_local__session_baton_t *sess = session->priv;
580 return svn_repos_fs_revision_prop(value, sess->repos, rev, name,
581 NULL, NULL, pool);
584 static svn_error_t *
585 svn_ra_local__get_commit_editor(svn_ra_session_t *session,
586 const svn_delta_editor_t **editor,
587 void **edit_baton,
588 apr_hash_t *revprop_table,
589 svn_commit_callback2_t callback,
590 void *callback_baton,
591 apr_hash_t *lock_tokens,
592 svn_boolean_t keep_locks,
593 apr_pool_t *pool)
595 svn_ra_local__session_baton_t *sess = session->priv;
596 struct deltify_etc_baton *db = apr_palloc(pool, sizeof(*db));
597 apr_hash_index_t *hi;
598 svn_fs_access_t *fs_access;
600 db->fs = sess->fs;
601 db->repos = sess->repos;
602 db->fs_path = sess->fs_path->data;
603 if (! keep_locks)
604 db->lock_tokens = lock_tokens;
605 else
606 db->lock_tokens = NULL;
607 db->pool = pool;
608 db->callback = callback;
609 db->callback_baton = callback_baton;
611 SVN_ERR(get_username(session, pool));
613 /* If there are lock tokens to add, do so. */
614 if (lock_tokens)
616 SVN_ERR(svn_fs_get_access(&fs_access, sess->fs));
618 /* If there is no access context, the filesystem will scream if a
619 lock is needed. */
620 if (fs_access)
622 for (hi = apr_hash_first(pool, lock_tokens); hi;
623 hi = apr_hash_next(hi))
625 void *val;
626 const char *token;
628 apr_hash_this(hi, NULL, NULL, &val);
629 token = val;
630 SVN_ERR(svn_fs_access_add_lock_token(fs_access, token));
635 /* Copy the revprops table so we can add the username. */
636 revprop_table = apr_hash_copy(pool, revprop_table);
637 apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
638 svn_string_create(sess->username, pool));
640 /* Get the repos commit-editor */
641 SVN_ERR(svn_repos_get_commit_editor5
642 (editor, edit_baton, sess->repos, NULL,
643 svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data,
644 revprop_table, deltify_etc, db, NULL, NULL, pool));
646 return SVN_NO_ERROR;
650 static svn_error_t *
651 svn_ra_local__get_mergeinfo(svn_ra_session_t *session,
652 apr_hash_t **mergeinfo,
653 const apr_array_header_t *paths,
654 svn_revnum_t revision,
655 svn_mergeinfo_inheritance_t inherit,
656 apr_pool_t *pool)
658 svn_ra_local__session_baton_t *sess = session->priv;
659 apr_hash_t *tmp_mergeinfo;
660 int i;
661 apr_array_header_t *abs_paths =
662 apr_array_make(pool, 0, sizeof(const char *));
664 for (i = 0; i < paths->nelts; i++)
666 const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
667 APR_ARRAY_PUSH(abs_paths, const char *) =
668 svn_path_join(sess->fs_path->data, relative_path, pool);
671 SVN_ERR(svn_repos_fs_get_mergeinfo(&tmp_mergeinfo, sess->repos, abs_paths,
672 revision, inherit, NULL, NULL, pool));
673 *mergeinfo = NULL;
674 if (tmp_mergeinfo != NULL && apr_hash_count(tmp_mergeinfo) > 0)
676 const void *key;
677 apr_ssize_t klen;
678 void *value;
679 apr_hash_index_t *hi;
681 *mergeinfo = apr_hash_make(pool);
683 for (hi = apr_hash_first(pool, tmp_mergeinfo); hi;
684 hi = apr_hash_next(hi))
686 const char *path, *info;
687 apr_ssize_t path_len;
688 apr_hash_t *for_path;
690 apr_hash_this(hi, &key, &klen, &value);
691 path = (const char *)key + sess->fs_path->len;
692 path_len = klen - sess->fs_path->len;
693 info = value;
694 SVN_ERR(svn_mergeinfo_parse(&for_path, info, pool));
695 apr_hash_set(*mergeinfo, path, path_len, for_path);
699 return SVN_NO_ERROR;
703 static svn_error_t *
704 svn_ra_local__do_update(svn_ra_session_t *session,
705 const svn_ra_reporter3_t **reporter,
706 void **report_baton,
707 svn_revnum_t update_revision,
708 const char *update_target,
709 svn_depth_t depth,
710 svn_boolean_t send_copyfrom_args,
711 const svn_delta_editor_t *update_editor,
712 void *update_baton,
713 apr_pool_t *pool)
715 return make_reporter(session,
716 reporter,
717 report_baton,
718 update_revision,
719 update_target,
720 NULL,
721 TRUE,
722 depth,
723 send_copyfrom_args,
724 FALSE,
725 update_editor,
726 update_baton,
727 pool);
731 static svn_error_t *
732 svn_ra_local__do_switch(svn_ra_session_t *session,
733 const svn_ra_reporter3_t **reporter,
734 void **report_baton,
735 svn_revnum_t update_revision,
736 const char *update_target,
737 svn_depth_t depth,
738 const char *switch_url,
739 const svn_delta_editor_t *update_editor,
740 void *update_baton,
741 apr_pool_t *pool)
743 return make_reporter(session,
744 reporter,
745 report_baton,
746 update_revision,
747 update_target,
748 switch_url,
749 TRUE,
750 depth,
751 FALSE, /* ### TODO(sussman): take new arg */
752 TRUE,
753 update_editor,
754 update_baton,
755 pool);
759 static svn_error_t *
760 svn_ra_local__do_status(svn_ra_session_t *session,
761 const svn_ra_reporter3_t **reporter,
762 void **report_baton,
763 const char *status_target,
764 svn_revnum_t revision,
765 svn_depth_t depth,
766 const svn_delta_editor_t *status_editor,
767 void *status_baton,
768 apr_pool_t *pool)
770 return make_reporter(session,
771 reporter,
772 report_baton,
773 revision,
774 status_target,
775 NULL,
776 FALSE,
777 depth,
778 FALSE,
779 FALSE,
780 status_editor,
781 status_baton,
782 pool);
786 static svn_error_t *
787 svn_ra_local__do_diff(svn_ra_session_t *session,
788 const svn_ra_reporter3_t **reporter,
789 void **report_baton,
790 svn_revnum_t update_revision,
791 const char *update_target,
792 svn_depth_t depth,
793 svn_boolean_t ignore_ancestry,
794 svn_boolean_t text_deltas,
795 const char *switch_url,
796 const svn_delta_editor_t *update_editor,
797 void *update_baton,
798 apr_pool_t *pool)
800 return make_reporter(session,
801 reporter,
802 report_baton,
803 update_revision,
804 update_target,
805 switch_url,
806 text_deltas,
807 depth,
808 FALSE,
809 ignore_ancestry,
810 update_editor,
811 update_baton,
812 pool);
816 struct log_baton
818 svn_ra_local__session_baton_t *sess;
819 svn_log_entry_receiver_t real_cb;
820 void *real_baton;
823 static svn_error_t *
824 cancellation_log_receiver(void *baton,
825 svn_log_entry_t *log_entry,
826 apr_pool_t *pool)
828 struct log_baton *b = baton;
829 svn_ra_local__session_baton_t *sess = b->sess;
831 SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton));
833 return b->real_cb(b->real_baton, log_entry, pool);
837 static svn_error_t *
838 svn_ra_local__get_log(svn_ra_session_t *session,
839 const apr_array_header_t *paths,
840 svn_revnum_t start,
841 svn_revnum_t end,
842 int limit,
843 svn_boolean_t discover_changed_paths,
844 svn_boolean_t strict_node_history,
845 svn_boolean_t include_merged_revisions,
846 apr_array_header_t *revprops,
847 svn_log_entry_receiver_t receiver,
848 void *receiver_baton,
849 apr_pool_t *pool)
851 svn_ra_local__session_baton_t *sess = session->priv;
852 int i;
853 struct log_baton lb;
854 apr_array_header_t *abs_paths =
855 apr_array_make(pool, 0, sizeof(const char *));
857 if (paths)
859 for (i = 0; i < paths->nelts; i++)
861 const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
862 APR_ARRAY_PUSH(abs_paths, const char *) =
863 svn_path_join(sess->fs_path->data, relative_path, pool);
867 if (sess->callbacks &&
868 sess->callbacks->cancel_func)
870 lb.real_cb = receiver;
871 lb.real_baton = receiver_baton;
872 lb.sess = sess;
874 receiver = cancellation_log_receiver;
875 receiver_baton = &lb;
878 return svn_repos_get_logs4(sess->repos,
879 abs_paths,
880 start,
881 end,
882 limit,
883 discover_changed_paths,
884 strict_node_history,
885 include_merged_revisions,
886 revprops,
887 NULL, NULL,
888 receiver,
889 receiver_baton,
890 pool);
894 static svn_error_t *
895 svn_ra_local__do_check_path(svn_ra_session_t *session,
896 const char *path,
897 svn_revnum_t revision,
898 svn_node_kind_t *kind,
899 apr_pool_t *pool)
901 svn_ra_local__session_baton_t *sess = session->priv;
902 svn_fs_root_t *root;
903 const char *abs_path = svn_path_join(sess->fs_path->data, path, pool);
905 if (! SVN_IS_VALID_REVNUM(revision))
906 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
907 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
908 SVN_ERR(svn_fs_check_path(kind, root, abs_path, pool));
909 return SVN_NO_ERROR;
913 static svn_error_t *
914 svn_ra_local__stat(svn_ra_session_t *session,
915 const char *path,
916 svn_revnum_t revision,
917 svn_dirent_t **dirent,
918 apr_pool_t *pool)
920 svn_ra_local__session_baton_t *sess = session->priv;
921 svn_fs_root_t *root;
922 const char *abs_path = svn_path_join(sess->fs_path->data, path, pool);
924 if (! SVN_IS_VALID_REVNUM(revision))
925 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
926 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
928 SVN_ERR(svn_repos_stat(dirent, root, abs_path, pool));
930 return SVN_NO_ERROR;
936 static svn_error_t *
937 get_node_props(apr_hash_t **props,
938 svn_ra_local__session_baton_t *sess,
939 svn_fs_root_t *root,
940 const char *path,
941 apr_pool_t *pool)
943 svn_revnum_t cmt_rev;
944 const char *cmt_date, *cmt_author;
946 /* Create a hash with props attached to the fs node. */
947 SVN_ERR(svn_fs_node_proplist(props, root, path, pool));
949 /* Now add some non-tweakable metadata to the hash as well... */
951 /* The so-called 'entryprops' with info about CR & friends. */
952 SVN_ERR(svn_repos_get_committed_info(&cmt_rev, &cmt_date,
953 &cmt_author, root, path, pool));
955 apr_hash_set(*props,
956 SVN_PROP_ENTRY_COMMITTED_REV,
957 APR_HASH_KEY_STRING,
958 svn_string_createf(pool, "%ld", cmt_rev));
959 apr_hash_set(*props,
960 SVN_PROP_ENTRY_COMMITTED_DATE,
961 APR_HASH_KEY_STRING,
962 cmt_date ? svn_string_create(cmt_date, pool) : NULL);
963 apr_hash_set(*props,
964 SVN_PROP_ENTRY_LAST_AUTHOR,
965 APR_HASH_KEY_STRING,
966 cmt_author ? svn_string_create(cmt_author, pool) : NULL);
967 apr_hash_set(*props,
968 SVN_PROP_ENTRY_UUID,
969 APR_HASH_KEY_STRING,
970 svn_string_create(sess->uuid, pool));
972 /* We have no 'wcprops' in ra_local, but might someday. */
974 return SVN_NO_ERROR;
978 /* Getting just one file. */
979 static svn_error_t *
980 svn_ra_local__get_file(svn_ra_session_t *session,
981 const char *path,
982 svn_revnum_t revision,
983 svn_stream_t *stream,
984 svn_revnum_t *fetched_rev,
985 apr_hash_t **props,
986 apr_pool_t *pool)
988 svn_fs_root_t *root;
989 svn_stream_t *contents;
990 svn_revnum_t youngest_rev;
991 svn_ra_local__session_baton_t *sess = session->priv;
992 const char *abs_path = svn_path_join(sess->fs_path->data, path, pool);
994 /* Open the revision's root. */
995 if (! SVN_IS_VALID_REVNUM(revision))
997 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
998 SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
999 if (fetched_rev != NULL)
1000 *fetched_rev = youngest_rev;
1002 else
1003 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1005 if (stream)
1007 /* Get a stream representing the file's contents. */
1008 SVN_ERR(svn_fs_file_contents(&contents, root, abs_path, pool));
1010 /* Now push data from the fs stream back at the caller's stream.
1011 Note that this particular RA layer does not computing a
1012 checksum as we go, and confirming it against the repository's
1013 checksum when done. That's because it calls
1014 svn_fs_file_contents() directly, which already checks the
1015 stored checksum, and all we're doing here is writing bytes in
1016 a loop. Truly, Nothing Can Go Wrong :-). But RA layers that
1017 go over a network should confirm the checksum. */
1018 SVN_ERR(svn_stream_copy2(contents, stream,
1019 sess->callbacks
1020 ? sess->callbacks->cancel_func : NULL,
1021 sess->callback_baton,
1022 pool));
1025 /* Handle props if requested. */
1026 if (props)
1027 SVN_ERR(get_node_props(props, sess, root, abs_path, pool));
1029 return SVN_NO_ERROR;
1034 /* Getting a directory's entries */
1035 static svn_error_t *
1036 svn_ra_local__get_dir(svn_ra_session_t *session,
1037 apr_hash_t **dirents,
1038 svn_revnum_t *fetched_rev,
1039 apr_hash_t **props,
1040 const char *path,
1041 svn_revnum_t revision,
1042 apr_uint32_t dirent_fields,
1043 apr_pool_t *pool)
1045 svn_fs_root_t *root;
1046 svn_revnum_t youngest_rev;
1047 apr_hash_t *entries;
1048 apr_hash_index_t *hi;
1049 svn_ra_local__session_baton_t *sess = session->priv;
1050 apr_pool_t *subpool;
1051 const char *abs_path = svn_path_join(sess->fs_path->data, path, pool);
1053 /* Open the revision's root. */
1054 if (! SVN_IS_VALID_REVNUM(revision))
1056 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, sess->fs, pool));
1057 SVN_ERR(svn_fs_revision_root(&root, sess->fs, youngest_rev, pool));
1058 if (fetched_rev != NULL)
1059 *fetched_rev = youngest_rev;
1061 else
1062 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1064 if (dirents)
1066 /* Get the dir's entries. */
1067 SVN_ERR(svn_fs_dir_entries(&entries, root, abs_path, pool));
1069 /* Loop over the fs dirents, and build a hash of general
1070 svn_dirent_t's. */
1071 *dirents = apr_hash_make(pool);
1072 subpool = svn_pool_create(pool);
1073 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1075 const void *key;
1076 void *val;
1077 apr_hash_t *prophash;
1078 const char *datestring, *entryname, *fullpath;
1079 svn_fs_dirent_t *fs_entry;
1080 svn_dirent_t *entry = apr_pcalloc(pool, sizeof(*entry));
1082 svn_pool_clear(subpool);
1084 apr_hash_this(hi, &key, NULL, &val);
1085 entryname = (const char *) key;
1086 fs_entry = (svn_fs_dirent_t *) val;
1088 fullpath = svn_path_join(abs_path, entryname, subpool);
1090 if (dirent_fields & SVN_DIRENT_KIND)
1092 /* node kind */
1093 entry->kind = fs_entry->kind;
1096 if (dirent_fields & SVN_DIRENT_SIZE)
1098 /* size */
1099 if (entry->kind == svn_node_dir)
1100 entry->size = 0;
1101 else
1102 SVN_ERR(svn_fs_file_length(&(entry->size), root,
1103 fullpath, subpool));
1106 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1108 /* has_props? */
1109 SVN_ERR(svn_fs_node_proplist(&prophash, root, fullpath,
1110 subpool));
1111 entry->has_props = (apr_hash_count(prophash)) ? TRUE : FALSE;
1114 if ((dirent_fields & SVN_DIRENT_TIME)
1115 || (dirent_fields & SVN_DIRENT_LAST_AUTHOR)
1116 || (dirent_fields & SVN_DIRENT_CREATED_REV))
1118 /* created_rev & friends */
1119 SVN_ERR(svn_repos_get_committed_info(&(entry->created_rev),
1120 &datestring,
1121 &(entry->last_author),
1122 root, fullpath, subpool));
1123 if (datestring)
1124 SVN_ERR(svn_time_from_cstring(&(entry->time), datestring,
1125 pool));
1126 if (entry->last_author)
1127 entry->last_author = apr_pstrdup(pool, entry->last_author);
1130 /* Store. */
1131 apr_hash_set(*dirents, entryname, APR_HASH_KEY_STRING, entry);
1133 svn_pool_destroy(subpool);
1136 /* Handle props if requested. */
1137 if (props)
1138 SVN_ERR(get_node_props(props, sess, root, abs_path, pool));
1140 return SVN_NO_ERROR;
1144 static svn_error_t *
1145 svn_ra_local__get_locations(svn_ra_session_t *session,
1146 apr_hash_t **locations,
1147 const char *path,
1148 svn_revnum_t peg_revision,
1149 apr_array_header_t *location_revisions,
1150 apr_pool_t *pool)
1152 svn_ra_local__session_baton_t *sess = session->priv;
1153 const char *abs_path = svn_path_join(sess->fs_path->data, path, pool);
1154 return svn_repos_trace_node_locations(sess->fs, locations, abs_path,
1155 peg_revision, location_revisions,
1156 NULL, NULL, pool);
1160 static svn_error_t *
1161 svn_ra_local__get_location_segments(svn_ra_session_t *session,
1162 const char *path,
1163 svn_revnum_t peg_revision,
1164 svn_revnum_t start_rev,
1165 svn_revnum_t end_rev,
1166 svn_location_segment_receiver_t receiver,
1167 void *receiver_baton,
1168 apr_pool_t *pool)
1170 svn_ra_local__session_baton_t *sess = session->priv;
1171 const char *abs_path = svn_path_join(sess->fs_path->data, path, pool);
1172 return svn_repos_node_location_segments(sess->repos, abs_path,
1173 peg_revision, start_rev, end_rev,
1174 receiver, receiver_baton,
1175 NULL, NULL, pool);
1179 static svn_error_t *
1180 svn_ra_local__lock(svn_ra_session_t *session,
1181 apr_hash_t *path_revs,
1182 const char *comment,
1183 svn_boolean_t force,
1184 svn_ra_lock_callback_t lock_func,
1185 void *lock_baton,
1186 apr_pool_t *pool)
1188 svn_ra_local__session_baton_t *sess = session->priv;
1189 apr_hash_index_t *hi;
1190 apr_pool_t *iterpool = svn_pool_create(pool);
1192 /* A username is absolutely required to lock a path. */
1193 SVN_ERR(get_username(session, pool));
1195 for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
1197 svn_lock_t *lock;
1198 const void *key;
1199 const char *path;
1200 void *val;
1201 svn_revnum_t *revnum;
1202 const char *abs_path;
1203 svn_error_t *err, *callback_err = NULL;
1205 svn_pool_clear(iterpool);
1206 apr_hash_this(hi, &key, NULL, &val);
1207 path = key;
1208 revnum = val;
1210 abs_path = svn_path_join(sess->fs_path->data, path, iterpool);
1212 /* This wrapper will call pre- and post-lock hooks. */
1213 err = svn_repos_fs_lock(&lock, sess->repos, abs_path, NULL, comment,
1214 FALSE /* not DAV comment */,
1215 0 /* no expiration */, *revnum, force,
1216 iterpool);
1218 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
1219 return err;
1221 if (lock_func)
1222 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
1223 err, iterpool);
1225 svn_error_clear(err);
1227 if (callback_err)
1228 return callback_err;
1231 svn_pool_destroy(iterpool);
1233 return SVN_NO_ERROR;
1237 static svn_error_t *
1238 svn_ra_local__unlock(svn_ra_session_t *session,
1239 apr_hash_t *path_tokens,
1240 svn_boolean_t force,
1241 svn_ra_lock_callback_t lock_func,
1242 void *lock_baton,
1243 apr_pool_t *pool)
1245 svn_ra_local__session_baton_t *sess = session->priv;
1246 apr_hash_index_t *hi;
1247 apr_pool_t *iterpool = svn_pool_create(pool);
1249 /* A username is absolutely required to unlock a path. */
1250 SVN_ERR(get_username(session, pool));
1252 for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
1254 const void *key;
1255 const char *path;
1256 void *val;
1257 const char *abs_path, *token;
1258 svn_error_t *err, *callback_err = NULL;
1260 svn_pool_clear(iterpool);
1261 apr_hash_this(hi, &key, NULL, &val);
1262 path = key;
1263 /* Since we can't store NULL values in a hash, we turn "" to
1264 NULL here. */
1265 if (strcmp(val, "") != 0)
1266 token = val;
1267 else
1268 token = NULL;
1270 abs_path = svn_path_join(sess->fs_path->data, path, iterpool);
1272 /* This wrapper will call pre- and post-unlock hooks. */
1273 err = svn_repos_fs_unlock(sess->repos, abs_path, token, force,
1274 iterpool);
1276 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
1277 return err;
1279 if (lock_func)
1280 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
1282 svn_error_clear(err);
1284 if (callback_err)
1285 return callback_err;
1288 svn_pool_destroy(iterpool);
1290 return SVN_NO_ERROR;
1295 static svn_error_t *
1296 svn_ra_local__get_lock(svn_ra_session_t *session,
1297 svn_lock_t **lock,
1298 const char *path,
1299 apr_pool_t *pool)
1301 svn_ra_local__session_baton_t *sess = session->priv;
1302 const char *abs_path = svn_path_join(sess->fs_path->data, path, pool);
1303 return svn_fs_get_lock(lock, sess->fs, abs_path, pool);
1308 static svn_error_t *
1309 svn_ra_local__get_locks(svn_ra_session_t *session,
1310 apr_hash_t **locks,
1311 const char *path,
1312 apr_pool_t *pool)
1314 svn_ra_local__session_baton_t *sess = session->priv;
1315 const char *abs_path = svn_path_join(sess->fs_path->data, path, pool);
1317 /* Kinda silly to call the repos wrapper, since we have no authz
1318 func to give it. But heck, why not. */
1319 return svn_repos_fs_get_locks(locks, sess->repos, abs_path,
1320 NULL, NULL, pool);
1324 static svn_error_t *
1325 svn_ra_local__replay(svn_ra_session_t *session,
1326 svn_revnum_t revision,
1327 svn_revnum_t low_water_mark,
1328 svn_boolean_t send_deltas,
1329 const svn_delta_editor_t *editor,
1330 void *edit_baton,
1331 apr_pool_t *pool)
1333 svn_ra_local__session_baton_t *sess = session->priv;
1334 svn_fs_root_t *root;
1336 SVN_ERR(svn_fs_revision_root(&root, svn_repos_fs(sess->repos),
1337 revision, pool));
1338 SVN_ERR(svn_repos_replay2(root, sess->fs_path->data, low_water_mark,
1339 send_deltas, editor, edit_baton, NULL, NULL,
1340 pool));
1342 return SVN_NO_ERROR;
1346 static svn_error_t *
1347 svn_ra_local__replay_range(svn_ra_session_t *session,
1348 svn_revnum_t start_revision,
1349 svn_revnum_t end_revision,
1350 svn_revnum_t low_water_mark,
1351 svn_boolean_t send_deltas,
1352 svn_ra_replay_revstart_callback_t revstart_func,
1353 svn_ra_replay_revfinish_callback_t revfinish_func,
1354 void *replay_baton,
1355 apr_pool_t *pool)
1357 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED, NULL, NULL);
1361 static svn_error_t *
1362 svn_ra_local__has_capability(svn_ra_session_t *session,
1363 svn_boolean_t *has,
1364 const char *capability,
1365 apr_pool_t *pool)
1367 if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0
1368 || strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0
1369 || strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0)
1371 *has = TRUE;
1373 else /* Don't know any other capabilities, so error. */
1375 return svn_error_createf
1376 (SVN_ERR_RA_UNKNOWN_CAPABILITY, NULL,
1377 _("Don't know anything about capability '%s'"), capability);
1380 return SVN_NO_ERROR;
1383 /*----------------------------------------------------------------*/
1385 static const svn_version_t *
1386 ra_local_version(void)
1388 SVN_VERSION_BODY;
1391 /** The ra_vtable **/
1393 static const svn_ra__vtable_t ra_local_vtable =
1395 ra_local_version,
1396 svn_ra_local__get_description,
1397 svn_ra_local__get_schemes,
1398 svn_ra_local__open,
1399 svn_ra_local__reparent,
1400 svn_ra_local__get_session_url,
1401 svn_ra_local__get_latest_revnum,
1402 svn_ra_local__get_dated_revision,
1403 svn_ra_local__change_rev_prop,
1404 svn_ra_local__rev_proplist,
1405 svn_ra_local__rev_prop,
1406 svn_ra_local__get_commit_editor,
1407 svn_ra_local__get_file,
1408 svn_ra_local__get_dir,
1409 svn_ra_local__get_mergeinfo,
1410 svn_ra_local__do_update,
1411 svn_ra_local__do_switch,
1412 svn_ra_local__do_status,
1413 svn_ra_local__do_diff,
1414 svn_ra_local__get_log,
1415 svn_ra_local__do_check_path,
1416 svn_ra_local__stat,
1417 svn_ra_local__get_uuid,
1418 svn_ra_local__get_repos_root,
1419 svn_ra_local__get_locations,
1420 svn_ra_local__get_location_segments,
1421 svn_ra_local__get_file_revs,
1422 svn_ra_local__lock,
1423 svn_ra_local__unlock,
1424 svn_ra_local__get_lock,
1425 svn_ra_local__get_locks,
1426 svn_ra_local__replay,
1427 svn_ra_local__has_capability,
1428 svn_ra_local__replay_range
1432 /*----------------------------------------------------------------*/
1434 /** The One Public Routine, called by libsvn_ra **/
1436 svn_error_t *
1437 svn_ra_local__init(const svn_version_t *loader_version,
1438 const svn_ra__vtable_t **vtable,
1439 apr_pool_t *pool)
1441 static const svn_version_checklist_t checklist[] =
1443 { "svn_subr", svn_subr_version },
1444 { "svn_delta", svn_delta_version },
1445 { "svn_repos", svn_repos_version },
1446 { "svn_fs", svn_fs_version },
1447 { NULL, NULL }
1451 /* Simplified version check to make sure we can safely use the
1452 VTABLE parameter. The RA loader does a more exhaustive check. */
1453 if (loader_version->major != SVN_VER_MAJOR)
1454 return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
1455 _("Unsupported RA loader version (%d) for "
1456 "ra_local"),
1457 loader_version->major);
1459 SVN_ERR(svn_ver_check_list(ra_local_version(), checklist));
1461 #ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
1462 /* This assumes that POOL was the pool used to load the dso. */
1463 SVN_ERR(svn_fs_initialize(pool));
1464 #endif
1466 *vtable = &ra_local_vtable;
1468 return SVN_NO_ERROR;
1471 /* Compatibility wrapper for the 1.1 and before API. */
1472 #define NAME "ra_local"
1473 #define DESCRIPTION RA_LOCAL_DESCRIPTION
1474 #define VTBL ra_local_vtable
1475 #define INITFUNC svn_ra_local__init
1476 #define COMPAT_INITFUNC svn_ra_local_init
1477 #include "../libsvn_ra/wrapper_template.h"