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 * ====================================================================
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
,
658 svn_ra_local__session_baton_t
*sess
= session
->priv
;
659 apr_hash_t
*tmp_mergeinfo
;
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
));
674 if (tmp_mergeinfo
!= NULL
&& apr_hash_count(tmp_mergeinfo
) > 0)
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
;
694 SVN_ERR(svn_mergeinfo_parse(&for_path
, info
, pool
));
695 apr_hash_set(*mergeinfo
, path
, path_len
, for_path
);
704 svn_ra_local__do_update(svn_ra_session_t
*session
,
705 const svn_ra_reporter3_t
**reporter
,
707 svn_revnum_t update_revision
,
708 const char *update_target
,
710 svn_boolean_t send_copyfrom_args
,
711 const svn_delta_editor_t
*update_editor
,
715 return make_reporter(session
,
732 svn_ra_local__do_switch(svn_ra_session_t
*session
,
733 const svn_ra_reporter3_t
**reporter
,
735 svn_revnum_t update_revision
,
736 const char *update_target
,
738 const char *switch_url
,
739 const svn_delta_editor_t
*update_editor
,
743 return make_reporter(session
,
751 FALSE
, /* ### TODO(sussman): take new arg */
760 svn_ra_local__do_status(svn_ra_session_t
*session
,
761 const svn_ra_reporter3_t
**reporter
,
763 const char *status_target
,
764 svn_revnum_t revision
,
766 const svn_delta_editor_t
*status_editor
,
770 return make_reporter(session
,
787 svn_ra_local__do_diff(svn_ra_session_t
*session
,
788 const svn_ra_reporter3_t
**reporter
,
790 svn_revnum_t update_revision
,
791 const char *update_target
,
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
,
800 return make_reporter(session
,
818 svn_ra_local__session_baton_t
*sess
;
819 svn_log_entry_receiver_t real_cb
;
824 cancellation_log_receiver(void *baton
,
825 svn_log_entry_t
*log_entry
,
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
);
838 svn_ra_local__get_log(svn_ra_session_t
*session
,
839 const apr_array_header_t
*paths
,
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
,
851 svn_ra_local__session_baton_t
*sess
= session
->priv
;
854 apr_array_header_t
*abs_paths
=
855 apr_array_make(pool
, 0, sizeof(const char *));
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
;
874 receiver
= cancellation_log_receiver
;
875 receiver_baton
= &lb
;
878 return svn_repos_get_logs4(sess
->repos
,
883 discover_changed_paths
,
885 include_merged_revisions
,
895 svn_ra_local__do_check_path(svn_ra_session_t
*session
,
897 svn_revnum_t revision
,
898 svn_node_kind_t
*kind
,
901 svn_ra_local__session_baton_t
*sess
= session
->priv
;
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
));
914 svn_ra_local__stat(svn_ra_session_t
*session
,
916 svn_revnum_t revision
,
917 svn_dirent_t
**dirent
,
920 svn_ra_local__session_baton_t
*sess
= session
->priv
;
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
));
937 get_node_props(apr_hash_t
**props
,
938 svn_ra_local__session_baton_t
*sess
,
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
));
956 SVN_PROP_ENTRY_COMMITTED_REV
,
958 svn_string_createf(pool
, "%ld", cmt_rev
));
960 SVN_PROP_ENTRY_COMMITTED_DATE
,
962 cmt_date
? svn_string_create(cmt_date
, pool
) : NULL
);
964 SVN_PROP_ENTRY_LAST_AUTHOR
,
966 cmt_author
? svn_string_create(cmt_author
, pool
) : NULL
);
970 svn_string_create(sess
->uuid
, pool
));
972 /* We have no 'wcprops' in ra_local, but might someday. */
978 /* Getting just one file. */
980 svn_ra_local__get_file(svn_ra_session_t
*session
,
982 svn_revnum_t revision
,
983 svn_stream_t
*stream
,
984 svn_revnum_t
*fetched_rev
,
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
;
1003 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, revision
, pool
));
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
,
1020 ? sess
->callbacks
->cancel_func
: NULL
,
1021 sess
->callback_baton
,
1025 /* Handle props if requested. */
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
,
1041 svn_revnum_t revision
,
1042 apr_uint32_t dirent_fields
,
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
;
1062 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, revision
, pool
));
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
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
))
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
)
1093 entry
->kind
= fs_entry
->kind
;
1096 if (dirent_fields
& SVN_DIRENT_SIZE
)
1099 if (entry
->kind
== svn_node_dir
)
1102 SVN_ERR(svn_fs_file_length(&(entry
->size
), root
,
1103 fullpath
, subpool
));
1106 if (dirent_fields
& SVN_DIRENT_HAS_PROPS
)
1109 SVN_ERR(svn_fs_node_proplist(&prophash
, root
, fullpath
,
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
),
1121 &(entry
->last_author
),
1122 root
, fullpath
, subpool
));
1124 SVN_ERR(svn_time_from_cstring(&(entry
->time
), datestring
,
1126 if (entry
->last_author
)
1127 entry
->last_author
= apr_pstrdup(pool
, entry
->last_author
);
1131 apr_hash_set(*dirents
, entryname
, APR_HASH_KEY_STRING
, entry
);
1133 svn_pool_destroy(subpool
);
1136 /* Handle props if requested. */
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
,
1148 svn_revnum_t peg_revision
,
1149 apr_array_header_t
*location_revisions
,
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
,
1160 static svn_error_t
*
1161 svn_ra_local__get_location_segments(svn_ra_session_t
*session
,
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
,
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
,
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
,
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
))
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
);
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
,
1218 if (err
&& !SVN_ERR_IS_LOCK_ERROR(err
))
1222 callback_err
= lock_func(lock_baton
, path
, TRUE
, err
? NULL
: lock
,
1225 svn_error_clear(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
,
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
))
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
);
1263 /* Since we can't store NULL values in a hash, we turn "" to
1265 if (strcmp(val
, "") != 0)
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
,
1276 if (err
&& !SVN_ERR_IS_UNLOCK_ERROR(err
))
1280 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
, iterpool
);
1282 svn_error_clear(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
,
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
,
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
,
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
,
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
),
1338 SVN_ERR(svn_repos_replay2(root
, sess
->fs_path
->data
, low_water_mark
,
1339 send_deltas
, editor
, edit_baton
, NULL
, NULL
,
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
,
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
,
1364 const char *capability
,
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)
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)
1391 /** The ra_vtable **/
1393 static const svn_ra__vtable_t ra_local_vtable
=
1396 svn_ra_local__get_description
,
1397 svn_ra_local__get_schemes
,
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
,
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
,
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 **/
1437 svn_ra_local__init(const svn_version_t
*loader_version
,
1438 const svn_ra__vtable_t
**vtable
,
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
},
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 "
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
));
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"