2 * ra_plugin.c : the main RA module for local repository access
4 * ====================================================================
5 * Copyright (c) 2000-2008 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 * ====================================================================
22 #include "svn_delta.h"
23 #include "svn_repos.h"
24 #include "svn_pools.h"
26 #include "svn_props.h"
27 #include "svn_mergeinfo.h"
30 #include "svn_private_config.h"
31 #include "../libsvn_ra/ra_loader.h"
33 #define APR_WANT_STRFUNC
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. */
47 cleanup_access(void *data
)
52 serr
= svn_fs_set_access(fs
, NULL
);
56 apr_status_t apr_err
= serr
->apr_err
;
57 svn_error_clear(serr
);
65 /* Fetch a username for use with SESS. */
67 get_username(svn_ra_session_t
*session
,
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. */
77 /* Get a username somehow, so we have some svn:author property to
78 attach to a commit. */
79 if (sess
->callbacks
->auth_baton
)
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
,
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
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
));
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.
113 SVN_ERR(svn_fs_create_access(&access_ctx
, sess
->username
,
115 SVN_ERR(svn_fs_set_access(sess
->fs
, access_ctx
));
117 /* Make sure this context is disassociated when the pool gets
119 apr_pool_cleanup_register(pool
, sess
->fs
, cleanup_access
,
120 apr_pool_cleanup_null
);
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
;
139 make_reporter_baton(svn_ra_local__session_baton_t
*sess
,
143 reporter_baton_t
*rbaton
= apr_palloc(pool
, sizeof(*rbaton
));
145 rbaton
->report_baton
= report_baton
;
151 reporter_set_path(void *reporter_baton
,
153 svn_revnum_t revision
,
155 svn_boolean_t start_empty
,
156 const char *lock_token
,
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
);
166 reporter_delete_path(void *reporter_baton
,
170 reporter_baton_t
*rbaton
= reporter_baton
;
171 return svn_repos_delete_path(rbaton
->report_baton
, path
, pool
);
176 reporter_link_path(void *reporter_baton
,
179 svn_revnum_t revision
,
181 svn_boolean_t start_empty
,
182 const char *lock_token
,
185 reporter_baton_t
*rbaton
= reporter_baton
;
186 const char *fs_path
= NULL
;
187 const char *repos_url_decoded
;
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
,
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
);
205 reporter_finish_report(void *reporter_baton
,
208 reporter_baton_t
*rbaton
= reporter_baton
;
209 return svn_repos_finish_report(rbaton
->report_baton
, pool
);
214 reporter_abort_report(void *reporter_baton
,
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
=
225 reporter_delete_path
,
227 reporter_finish_report
,
228 reporter_abort_report
233 make_reporter(svn_ra_session_t
*session
,
234 const svn_ra_reporter3_t
**reporter
,
236 svn_revnum_t revision
,
238 const char *other_url
,
239 svn_boolean_t text_deltas
,
241 svn_boolean_t send_copyfrom_args
,
242 svn_boolean_t ignore_ancestry
,
243 const svn_delta_editor_t
*editor
,
247 svn_ra_local__session_baton_t
*sess
= session
->priv
;
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. */
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
,
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
));
283 SVN_ERR(svn_delta_get_cancellation_editor(sess
->callbacks
->cancel_func
,
284 sess
->callback_baton
,
291 /* Build a reporter baton. */
292 SVN_ERR(svn_repos_begin_report2(&rbaton
,
308 /* Wrap the report baton given us by the repos layer with our own
310 *report_baton
= make_reporter_baton(sess
, rbaton
, pool
);
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 *'. */
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. */
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
;
358 const char *abs_path
, *token
;
360 svn_pool_clear(iterpool
);
361 apr_hash_this(hi
, &rel_path
, NULL
, &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
367 svn_error_clear(svn_repos_fs_unlock(db
->repos
, abs_path
, token
,
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
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. */
382 svn_error_clear(err2
);
389 /*----------------------------------------------------------------*/
391 /*** The RA vtable routines ***/
393 #define RA_LOCAL_DESCRIPTION \
394 N_("Module for accessing a repository on local disk.")
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
};
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
,
418 svn_ra_local__session_baton_t
*sess
;
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
429 SVN_ERR_W(svn_ra_local__split_URL(&(sess
->repos
),
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
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
;
452 svn_ra_local__reparent(svn_ra_session_t
*session
,
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
);
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
);
478 svn_ra_local__get_session_url(svn_ra_session_t
*session
,
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
),
490 svn_ra_local__get_latest_revnum(svn_ra_session_t
*session
,
491 svn_revnum_t
*latest_revnum
,
494 svn_ra_local__session_baton_t
*sess
= session
->priv
;
495 return svn_fs_youngest_rev(latest_revnum
, sess
->fs
, pool
);
499 svn_ra_local__get_file_revs(svn_ra_session_t
*session
,
503 svn_boolean_t include_merged_revisions
,
504 svn_file_rev_handler_t handler
,
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
);
516 svn_ra_local__get_dated_revision(svn_ra_session_t
*session
,
517 svn_revnum_t
*revision
,
521 svn_ra_local__session_baton_t
*sess
= session
->priv
;
522 return svn_repos_dated_revision(revision
, sess
->repos
, tm
, pool
);
527 svn_ra_local__change_rev_prop(svn_ra_session_t
*session
,
530 const svn_string_t
*value
,
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
,
542 svn_ra_local__get_uuid(svn_ra_session_t
*session
,
546 svn_ra_local__session_baton_t
*sess
= session
->priv
;
552 svn_ra_local__get_repos_root(svn_ra_session_t
*session
,
556 svn_ra_local__session_baton_t
*sess
= session
->priv
;
557 *url
= sess
->repos_url
;
562 svn_ra_local__rev_proplist(svn_ra_session_t
*session
,
567 svn_ra_local__session_baton_t
*sess
= session
->priv
;
568 return svn_repos_fs_revision_proplist(props
, sess
->repos
, rev
,
573 svn_ra_local__rev_prop(svn_ra_session_t
*session
,
576 svn_string_t
**value
,
579 svn_ra_local__session_baton_t
*sess
= session
->priv
;
580 return svn_repos_fs_revision_prop(value
, sess
->repos
, rev
, name
,
585 svn_ra_local__get_commit_editor(svn_ra_session_t
*session
,
586 const svn_delta_editor_t
**editor
,
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
,
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
;
601 db
->repos
= sess
->repos
;
602 db
->fs_path
= sess
->fs_path
->data
;
604 db
->lock_tokens
= lock_tokens
;
606 db
->lock_tokens
= NULL
;
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. */
616 SVN_ERR(svn_fs_get_access(&fs_access
, sess
->fs
));
618 /* If there is no access context, the filesystem will scream if a
622 for (hi
= apr_hash_first(pool
, lock_tokens
); hi
;
623 hi
= apr_hash_next(hi
))
628 apr_hash_this(hi
, NULL
, NULL
, &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
));
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 svn_boolean_t include_descendants
,
659 svn_ra_local__session_baton_t
*sess
= session
->priv
;
660 apr_hash_t
*tmp_mergeinfo
;
662 apr_array_header_t
*abs_paths
=
663 apr_array_make(pool
, 0, sizeof(const char *));
665 for (i
= 0; i
< paths
->nelts
; i
++)
667 const char *relative_path
= APR_ARRAY_IDX(paths
, i
, const char *);
668 APR_ARRAY_PUSH(abs_paths
, const char *) =
669 svn_path_join(sess
->fs_path
->data
, relative_path
, pool
);
672 SVN_ERR(svn_repos_fs_get_mergeinfo(&tmp_mergeinfo
, sess
->repos
, abs_paths
,
673 revision
, inherit
, include_descendants
,
676 if (tmp_mergeinfo
!= NULL
&& apr_hash_count(tmp_mergeinfo
) > 0)
681 apr_hash_index_t
*hi
;
683 *mergeinfo
= apr_hash_make(pool
);
685 for (hi
= apr_hash_first(pool
, tmp_mergeinfo
); hi
;
686 hi
= apr_hash_next(hi
))
688 const char *path
, *info
;
689 apr_ssize_t path_len
;
690 apr_hash_t
*for_path
;
692 apr_hash_this(hi
, &key
, &klen
, &value
);
693 path
= (const char *)key
+ sess
->fs_path
->len
;
694 path_len
= klen
- sess
->fs_path
->len
;
696 SVN_ERR(svn_mergeinfo_parse(&for_path
, info
, pool
));
697 apr_hash_set(*mergeinfo
, path
, path_len
, for_path
);
706 svn_ra_local__do_update(svn_ra_session_t
*session
,
707 const svn_ra_reporter3_t
**reporter
,
709 svn_revnum_t update_revision
,
710 const char *update_target
,
712 svn_boolean_t send_copyfrom_args
,
713 const svn_delta_editor_t
*update_editor
,
717 return make_reporter(session
,
734 svn_ra_local__do_switch(svn_ra_session_t
*session
,
735 const svn_ra_reporter3_t
**reporter
,
737 svn_revnum_t update_revision
,
738 const char *update_target
,
740 const char *switch_url
,
741 const svn_delta_editor_t
*update_editor
,
745 return make_reporter(session
,
753 FALSE
, /* ### TODO(sussman): take new arg */
762 svn_ra_local__do_status(svn_ra_session_t
*session
,
763 const svn_ra_reporter3_t
**reporter
,
765 const char *status_target
,
766 svn_revnum_t revision
,
768 const svn_delta_editor_t
*status_editor
,
772 return make_reporter(session
,
789 svn_ra_local__do_diff(svn_ra_session_t
*session
,
790 const svn_ra_reporter3_t
**reporter
,
792 svn_revnum_t update_revision
,
793 const char *update_target
,
795 svn_boolean_t ignore_ancestry
,
796 svn_boolean_t text_deltas
,
797 const char *switch_url
,
798 const svn_delta_editor_t
*update_editor
,
802 return make_reporter(session
,
820 svn_ra_local__session_baton_t
*sess
;
821 svn_log_entry_receiver_t real_cb
;
826 cancellation_log_receiver(void *baton
,
827 svn_log_entry_t
*log_entry
,
830 struct log_baton
*b
= baton
;
831 svn_ra_local__session_baton_t
*sess
= b
->sess
;
833 SVN_ERR((sess
->callbacks
->cancel_func
)(sess
->callback_baton
));
835 return b
->real_cb(b
->real_baton
, log_entry
, pool
);
840 svn_ra_local__get_log(svn_ra_session_t
*session
,
841 const apr_array_header_t
*paths
,
845 svn_boolean_t discover_changed_paths
,
846 svn_boolean_t strict_node_history
,
847 svn_boolean_t include_merged_revisions
,
848 const apr_array_header_t
*revprops
,
849 svn_log_entry_receiver_t receiver
,
850 void *receiver_baton
,
853 svn_ra_local__session_baton_t
*sess
= session
->priv
;
856 apr_array_header_t
*abs_paths
=
857 apr_array_make(pool
, 0, sizeof(const char *));
861 for (i
= 0; i
< paths
->nelts
; i
++)
863 const char *relative_path
= APR_ARRAY_IDX(paths
, i
, const char *);
864 APR_ARRAY_PUSH(abs_paths
, const char *) =
865 svn_path_join(sess
->fs_path
->data
, relative_path
, pool
);
869 if (sess
->callbacks
&&
870 sess
->callbacks
->cancel_func
)
872 lb
.real_cb
= receiver
;
873 lb
.real_baton
= receiver_baton
;
876 receiver
= cancellation_log_receiver
;
877 receiver_baton
= &lb
;
880 return svn_repos_get_logs4(sess
->repos
,
885 discover_changed_paths
,
887 include_merged_revisions
,
897 svn_ra_local__do_check_path(svn_ra_session_t
*session
,
899 svn_revnum_t revision
,
900 svn_node_kind_t
*kind
,
903 svn_ra_local__session_baton_t
*sess
= session
->priv
;
905 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
907 if (! SVN_IS_VALID_REVNUM(revision
))
908 SVN_ERR(svn_fs_youngest_rev(&revision
, sess
->fs
, pool
));
909 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, revision
, pool
));
910 SVN_ERR(svn_fs_check_path(kind
, root
, abs_path
, pool
));
916 svn_ra_local__stat(svn_ra_session_t
*session
,
918 svn_revnum_t revision
,
919 svn_dirent_t
**dirent
,
922 svn_ra_local__session_baton_t
*sess
= session
->priv
;
924 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
926 if (! SVN_IS_VALID_REVNUM(revision
))
927 SVN_ERR(svn_fs_youngest_rev(&revision
, sess
->fs
, pool
));
928 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, revision
, pool
));
930 SVN_ERR(svn_repos_stat(dirent
, root
, abs_path
, pool
));
939 get_node_props(apr_hash_t
**props
,
940 svn_ra_local__session_baton_t
*sess
,
945 svn_revnum_t cmt_rev
;
946 const char *cmt_date
, *cmt_author
;
948 /* Create a hash with props attached to the fs node. */
949 SVN_ERR(svn_fs_node_proplist(props
, root
, path
, pool
));
951 /* Now add some non-tweakable metadata to the hash as well... */
953 /* The so-called 'entryprops' with info about CR & friends. */
954 SVN_ERR(svn_repos_get_committed_info(&cmt_rev
, &cmt_date
,
955 &cmt_author
, root
, path
, pool
));
958 SVN_PROP_ENTRY_COMMITTED_REV
,
960 svn_string_createf(pool
, "%ld", cmt_rev
));
962 SVN_PROP_ENTRY_COMMITTED_DATE
,
964 cmt_date
? svn_string_create(cmt_date
, pool
) : NULL
);
966 SVN_PROP_ENTRY_LAST_AUTHOR
,
968 cmt_author
? svn_string_create(cmt_author
, pool
) : NULL
);
972 svn_string_create(sess
->uuid
, pool
));
974 /* We have no 'wcprops' in ra_local, but might someday. */
980 /* Getting just one file. */
982 svn_ra_local__get_file(svn_ra_session_t
*session
,
984 svn_revnum_t revision
,
985 svn_stream_t
*stream
,
986 svn_revnum_t
*fetched_rev
,
991 svn_stream_t
*contents
;
992 svn_revnum_t youngest_rev
;
993 svn_ra_local__session_baton_t
*sess
= session
->priv
;
994 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
996 /* Open the revision's root. */
997 if (! SVN_IS_VALID_REVNUM(revision
))
999 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, sess
->fs
, pool
));
1000 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, youngest_rev
, pool
));
1001 if (fetched_rev
!= NULL
)
1002 *fetched_rev
= youngest_rev
;
1005 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, revision
, pool
));
1009 /* Get a stream representing the file's contents. */
1010 SVN_ERR(svn_fs_file_contents(&contents
, root
, abs_path
, pool
));
1012 /* Now push data from the fs stream back at the caller's stream.
1013 Note that this particular RA layer does not computing a
1014 checksum as we go, and confirming it against the repository's
1015 checksum when done. That's because it calls
1016 svn_fs_file_contents() directly, which already checks the
1017 stored checksum, and all we're doing here is writing bytes in
1018 a loop. Truly, Nothing Can Go Wrong :-). But RA layers that
1019 go over a network should confirm the checksum. */
1020 SVN_ERR(svn_stream_copy2(contents
, stream
,
1022 ? sess
->callbacks
->cancel_func
: NULL
,
1023 sess
->callback_baton
,
1027 /* Handle props if requested. */
1029 SVN_ERR(get_node_props(props
, sess
, root
, abs_path
, pool
));
1031 return SVN_NO_ERROR
;
1036 /* Getting a directory's entries */
1037 static svn_error_t
*
1038 svn_ra_local__get_dir(svn_ra_session_t
*session
,
1039 apr_hash_t
**dirents
,
1040 svn_revnum_t
*fetched_rev
,
1043 svn_revnum_t revision
,
1044 apr_uint32_t dirent_fields
,
1047 svn_fs_root_t
*root
;
1048 svn_revnum_t youngest_rev
;
1049 apr_hash_t
*entries
;
1050 apr_hash_index_t
*hi
;
1051 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1052 apr_pool_t
*subpool
;
1053 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
1055 /* Open the revision's root. */
1056 if (! SVN_IS_VALID_REVNUM(revision
))
1058 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, sess
->fs
, pool
));
1059 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, youngest_rev
, pool
));
1060 if (fetched_rev
!= NULL
)
1061 *fetched_rev
= youngest_rev
;
1064 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, revision
, pool
));
1068 /* Get the dir's entries. */
1069 SVN_ERR(svn_fs_dir_entries(&entries
, root
, abs_path
, pool
));
1071 /* Loop over the fs dirents, and build a hash of general
1073 *dirents
= apr_hash_make(pool
);
1074 subpool
= svn_pool_create(pool
);
1075 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
1079 apr_hash_t
*prophash
;
1080 const char *datestring
, *entryname
, *fullpath
;
1081 svn_fs_dirent_t
*fs_entry
;
1082 svn_dirent_t
*entry
= apr_pcalloc(pool
, sizeof(*entry
));
1084 svn_pool_clear(subpool
);
1086 apr_hash_this(hi
, &key
, NULL
, &val
);
1087 entryname
= (const char *) key
;
1088 fs_entry
= (svn_fs_dirent_t
*) val
;
1090 fullpath
= svn_path_join(abs_path
, entryname
, subpool
);
1092 if (dirent_fields
& SVN_DIRENT_KIND
)
1095 entry
->kind
= fs_entry
->kind
;
1098 if (dirent_fields
& SVN_DIRENT_SIZE
)
1101 if (entry
->kind
== svn_node_dir
)
1104 SVN_ERR(svn_fs_file_length(&(entry
->size
), root
,
1105 fullpath
, subpool
));
1108 if (dirent_fields
& SVN_DIRENT_HAS_PROPS
)
1111 SVN_ERR(svn_fs_node_proplist(&prophash
, root
, fullpath
,
1113 entry
->has_props
= (apr_hash_count(prophash
)) ? TRUE
: FALSE
;
1116 if ((dirent_fields
& SVN_DIRENT_TIME
)
1117 || (dirent_fields
& SVN_DIRENT_LAST_AUTHOR
)
1118 || (dirent_fields
& SVN_DIRENT_CREATED_REV
))
1120 /* created_rev & friends */
1121 SVN_ERR(svn_repos_get_committed_info(&(entry
->created_rev
),
1123 &(entry
->last_author
),
1124 root
, fullpath
, subpool
));
1126 SVN_ERR(svn_time_from_cstring(&(entry
->time
), datestring
,
1128 if (entry
->last_author
)
1129 entry
->last_author
= apr_pstrdup(pool
, entry
->last_author
);
1133 apr_hash_set(*dirents
, entryname
, APR_HASH_KEY_STRING
, entry
);
1135 svn_pool_destroy(subpool
);
1138 /* Handle props if requested. */
1140 SVN_ERR(get_node_props(props
, sess
, root
, abs_path
, pool
));
1142 return SVN_NO_ERROR
;
1146 static svn_error_t
*
1147 svn_ra_local__get_locations(svn_ra_session_t
*session
,
1148 apr_hash_t
**locations
,
1150 svn_revnum_t peg_revision
,
1151 apr_array_header_t
*location_revisions
,
1154 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1155 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
1156 return svn_repos_trace_node_locations(sess
->fs
, locations
, abs_path
,
1157 peg_revision
, location_revisions
,
1162 static svn_error_t
*
1163 svn_ra_local__get_location_segments(svn_ra_session_t
*session
,
1165 svn_revnum_t peg_revision
,
1166 svn_revnum_t start_rev
,
1167 svn_revnum_t end_rev
,
1168 svn_location_segment_receiver_t receiver
,
1169 void *receiver_baton
,
1172 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1173 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
1174 return svn_repos_node_location_segments(sess
->repos
, abs_path
,
1175 peg_revision
, start_rev
, end_rev
,
1176 receiver
, receiver_baton
,
1181 static svn_error_t
*
1182 svn_ra_local__lock(svn_ra_session_t
*session
,
1183 apr_hash_t
*path_revs
,
1184 const char *comment
,
1185 svn_boolean_t force
,
1186 svn_ra_lock_callback_t lock_func
,
1190 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1191 apr_hash_index_t
*hi
;
1192 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1194 /* A username is absolutely required to lock a path. */
1195 SVN_ERR(get_username(session
, pool
));
1197 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
1203 svn_revnum_t
*revnum
;
1204 const char *abs_path
;
1205 svn_error_t
*err
, *callback_err
= NULL
;
1207 svn_pool_clear(iterpool
);
1208 apr_hash_this(hi
, &key
, NULL
, &val
);
1212 abs_path
= svn_path_join(sess
->fs_path
->data
, path
, iterpool
);
1214 /* This wrapper will call pre- and post-lock hooks. */
1215 err
= svn_repos_fs_lock(&lock
, sess
->repos
, abs_path
, NULL
, comment
,
1216 FALSE
/* not DAV comment */,
1217 0 /* no expiration */, *revnum
, force
,
1220 if (err
&& !SVN_ERR_IS_LOCK_ERROR(err
))
1224 callback_err
= lock_func(lock_baton
, path
, TRUE
, err
? NULL
: lock
,
1227 svn_error_clear(err
);
1230 return callback_err
;
1233 svn_pool_destroy(iterpool
);
1235 return SVN_NO_ERROR
;
1239 static svn_error_t
*
1240 svn_ra_local__unlock(svn_ra_session_t
*session
,
1241 apr_hash_t
*path_tokens
,
1242 svn_boolean_t force
,
1243 svn_ra_lock_callback_t lock_func
,
1247 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1248 apr_hash_index_t
*hi
;
1249 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1251 /* A username is absolutely required to unlock a path. */
1252 SVN_ERR(get_username(session
, pool
));
1254 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
1259 const char *abs_path
, *token
;
1260 svn_error_t
*err
, *callback_err
= NULL
;
1262 svn_pool_clear(iterpool
);
1263 apr_hash_this(hi
, &key
, NULL
, &val
);
1265 /* Since we can't store NULL values in a hash, we turn "" to
1267 if (strcmp(val
, "") != 0)
1272 abs_path
= svn_path_join(sess
->fs_path
->data
, path
, iterpool
);
1274 /* This wrapper will call pre- and post-unlock hooks. */
1275 err
= svn_repos_fs_unlock(sess
->repos
, abs_path
, token
, force
,
1278 if (err
&& !SVN_ERR_IS_UNLOCK_ERROR(err
))
1282 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
, iterpool
);
1284 svn_error_clear(err
);
1287 return callback_err
;
1290 svn_pool_destroy(iterpool
);
1292 return SVN_NO_ERROR
;
1297 static svn_error_t
*
1298 svn_ra_local__get_lock(svn_ra_session_t
*session
,
1303 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1304 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
1305 return svn_fs_get_lock(lock
, sess
->fs
, abs_path
, pool
);
1310 static svn_error_t
*
1311 svn_ra_local__get_locks(svn_ra_session_t
*session
,
1316 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1317 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
1319 /* Kinda silly to call the repos wrapper, since we have no authz
1320 func to give it. But heck, why not. */
1321 return svn_repos_fs_get_locks(locks
, sess
->repos
, abs_path
,
1326 static svn_error_t
*
1327 svn_ra_local__replay(svn_ra_session_t
*session
,
1328 svn_revnum_t revision
,
1329 svn_revnum_t low_water_mark
,
1330 svn_boolean_t send_deltas
,
1331 const svn_delta_editor_t
*editor
,
1335 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1336 svn_fs_root_t
*root
;
1338 SVN_ERR(svn_fs_revision_root(&root
, svn_repos_fs(sess
->repos
),
1340 SVN_ERR(svn_repos_replay2(root
, sess
->fs_path
->data
, low_water_mark
,
1341 send_deltas
, editor
, edit_baton
, NULL
, NULL
,
1344 return SVN_NO_ERROR
;
1348 static svn_error_t
*
1349 svn_ra_local__replay_range(svn_ra_session_t
*session
,
1350 svn_revnum_t start_revision
,
1351 svn_revnum_t end_revision
,
1352 svn_revnum_t low_water_mark
,
1353 svn_boolean_t send_deltas
,
1354 svn_ra_replay_revstart_callback_t revstart_func
,
1355 svn_ra_replay_revfinish_callback_t revfinish_func
,
1359 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, NULL
, NULL
);
1363 static svn_error_t
*
1364 svn_ra_local__has_capability(svn_ra_session_t
*session
,
1366 const char *capability
,
1369 if (strcmp(capability
, SVN_RA_CAPABILITY_DEPTH
) == 0
1370 || strcmp(capability
, SVN_RA_CAPABILITY_MERGEINFO
) == 0
1371 || strcmp(capability
, SVN_RA_CAPABILITY_LOG_REVPROPS
) == 0
1372 || strcmp(capability
, SVN_RA_CAPABILITY_PARTIAL_REPLAY
) == 0)
1376 else /* Don't know any other capabilities, so error. */
1378 return svn_error_createf
1379 (SVN_ERR_RA_UNKNOWN_CAPABILITY
, NULL
,
1380 _("Don't know anything about capability '%s'"), capability
);
1383 return SVN_NO_ERROR
;
1386 /*----------------------------------------------------------------*/
1388 static const svn_version_t
*
1389 ra_local_version(void)
1394 /** The ra_vtable **/
1396 static const svn_ra__vtable_t ra_local_vtable
=
1399 svn_ra_local__get_description
,
1400 svn_ra_local__get_schemes
,
1402 svn_ra_local__reparent
,
1403 svn_ra_local__get_session_url
,
1404 svn_ra_local__get_latest_revnum
,
1405 svn_ra_local__get_dated_revision
,
1406 svn_ra_local__change_rev_prop
,
1407 svn_ra_local__rev_proplist
,
1408 svn_ra_local__rev_prop
,
1409 svn_ra_local__get_commit_editor
,
1410 svn_ra_local__get_file
,
1411 svn_ra_local__get_dir
,
1412 svn_ra_local__get_mergeinfo
,
1413 svn_ra_local__do_update
,
1414 svn_ra_local__do_switch
,
1415 svn_ra_local__do_status
,
1416 svn_ra_local__do_diff
,
1417 svn_ra_local__get_log
,
1418 svn_ra_local__do_check_path
,
1420 svn_ra_local__get_uuid
,
1421 svn_ra_local__get_repos_root
,
1422 svn_ra_local__get_locations
,
1423 svn_ra_local__get_location_segments
,
1424 svn_ra_local__get_file_revs
,
1426 svn_ra_local__unlock
,
1427 svn_ra_local__get_lock
,
1428 svn_ra_local__get_locks
,
1429 svn_ra_local__replay
,
1430 svn_ra_local__has_capability
,
1431 svn_ra_local__replay_range
1435 /*----------------------------------------------------------------*/
1437 /** The One Public Routine, called by libsvn_ra **/
1440 svn_ra_local__init(const svn_version_t
*loader_version
,
1441 const svn_ra__vtable_t
**vtable
,
1444 static const svn_version_checklist_t checklist
[] =
1446 { "svn_subr", svn_subr_version
},
1447 { "svn_delta", svn_delta_version
},
1448 { "svn_repos", svn_repos_version
},
1449 { "svn_fs", svn_fs_version
},
1454 /* Simplified version check to make sure we can safely use the
1455 VTABLE parameter. The RA loader does a more exhaustive check. */
1456 if (loader_version
->major
!= SVN_VER_MAJOR
)
1457 return svn_error_createf(SVN_ERR_VERSION_MISMATCH
, NULL
,
1458 _("Unsupported RA loader version (%d) for "
1460 loader_version
->major
);
1462 SVN_ERR(svn_ver_check_list(ra_local_version(), checklist
));
1464 #ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
1465 /* This assumes that POOL was the pool used to load the dso. */
1466 SVN_ERR(svn_fs_initialize(pool
));
1469 *vtable
= &ra_local_vtable
;
1471 return SVN_NO_ERROR
;
1474 /* Compatibility wrapper for the 1.1 and before API. */
1475 #define NAME "ra_local"
1476 #define DESCRIPTION RA_LOCAL_DESCRIPTION
1477 #define VTBL ra_local_vtable
1478 #define INITFUNC svn_ra_local__init
1479 #define COMPAT_INITFUNC svn_ra_local_init
1480 #include "../libsvn_ra/wrapper_template.h"