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"
32 #include "private/svn_mergeinfo_private.h"
34 #define APR_WANT_STRFUNC
40 /*----------------------------------------------------------------*/
42 /*** Miscellaneous helper functions ***/
45 /* Pool cleanup handler: ensure that the access descriptor of the
46 filesystem (svn_fs_t *) DATA is set to NULL. */
48 cleanup_access(void *data
)
53 serr
= svn_fs_set_access(fs
, NULL
);
57 apr_status_t apr_err
= serr
->apr_err
;
58 svn_error_clear(serr
);
66 /* Fetch a username for use with SESS. */
68 get_username(svn_ra_session_t
*session
,
71 svn_ra_local__session_baton_t
*sess
= session
->priv
;
72 svn_auth_iterstate_t
*iterstate
;
73 svn_fs_access_t
*access_ctx
;
75 /* If we've already found the username don't ask for it again. */
78 /* Get a username somehow, so we have some svn:author property to
79 attach to a commit. */
80 if (sess
->callbacks
->auth_baton
)
83 svn_auth_cred_username_t
*username_creds
;
84 SVN_ERR(svn_auth_first_credentials(&creds
, &iterstate
,
85 SVN_AUTH_CRED_USERNAME
,
86 sess
->uuid
, /* realmstring */
87 sess
->callbacks
->auth_baton
,
90 /* No point in calling next_creds(), since that assumes that the
91 first_creds() somehow failed to authenticate. But there's no
92 challenge going on, so we use whatever creds we get back on
94 username_creds
= creds
;
95 if (username_creds
&& username_creds
->username
)
97 sess
->username
= apr_pstrdup(session
->pool
,
98 username_creds
->username
);
99 svn_error_clear(svn_auth_save_credentials(iterstate
, pool
));
108 /* If we have a real username, attach it to the filesystem so that it can
109 be used to validate locks. Even if there already is a user context
110 associated, it may contain irrelevant lock tokens, so always create a new.
114 SVN_ERR(svn_fs_create_access(&access_ctx
, sess
->username
,
116 SVN_ERR(svn_fs_set_access(sess
->fs
, access_ctx
));
118 /* Make sure this context is disassociated when the pool gets
120 apr_pool_cleanup_register(pool
, sess
->fs
, cleanup_access
,
121 apr_pool_cleanup_null
);
127 /*----------------------------------------------------------------*/
129 /*** The reporter vtable needed by do_update() and friends ***/
131 typedef struct reporter_baton_t
133 svn_ra_local__session_baton_t
*sess
;
140 make_reporter_baton(svn_ra_local__session_baton_t
*sess
,
144 reporter_baton_t
*rbaton
= apr_palloc(pool
, sizeof(*rbaton
));
146 rbaton
->report_baton
= report_baton
;
152 reporter_set_path(void *reporter_baton
,
154 svn_revnum_t revision
,
156 svn_boolean_t start_empty
,
157 const char *lock_token
,
160 reporter_baton_t
*rbaton
= reporter_baton
;
161 return svn_repos_set_path3(rbaton
->report_baton
, path
,
162 revision
, depth
, start_empty
, lock_token
, pool
);
167 reporter_delete_path(void *reporter_baton
,
171 reporter_baton_t
*rbaton
= reporter_baton
;
172 return svn_repos_delete_path(rbaton
->report_baton
, path
, pool
);
177 reporter_link_path(void *reporter_baton
,
180 svn_revnum_t revision
,
182 svn_boolean_t start_empty
,
183 const char *lock_token
,
186 reporter_baton_t
*rbaton
= reporter_baton
;
187 const char *fs_path
= NULL
;
188 const char *repos_url_decoded
;
191 url
= svn_path_uri_decode(url
, pool
);
192 repos_url_decoded
= svn_path_uri_decode(rbaton
->sess
->repos_url
, pool
);
193 repos_url_len
= strlen(repos_url_decoded
);
194 if (strncmp(url
, repos_url_decoded
, repos_url_len
) != 0)
195 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
197 "is not the same repository as\n"
198 "'%s'"), url
, rbaton
->sess
->repos_url
);
199 fs_path
= url
+ repos_url_len
;
200 return svn_repos_link_path3(rbaton
->report_baton
, path
, fs_path
, revision
,
201 depth
, start_empty
, lock_token
, pool
);
206 reporter_finish_report(void *reporter_baton
,
209 reporter_baton_t
*rbaton
= reporter_baton
;
210 return svn_repos_finish_report(rbaton
->report_baton
, pool
);
215 reporter_abort_report(void *reporter_baton
,
218 reporter_baton_t
*rbaton
= reporter_baton
;
219 return svn_repos_abort_report(rbaton
->report_baton
, pool
);
223 static const svn_ra_reporter3_t ra_local_reporter
=
226 reporter_delete_path
,
228 reporter_finish_report
,
229 reporter_abort_report
234 make_reporter(svn_ra_session_t
*session
,
235 const svn_ra_reporter3_t
**reporter
,
237 svn_revnum_t revision
,
239 const char *other_url
,
240 svn_boolean_t text_deltas
,
242 svn_boolean_t send_copyfrom_args
,
243 svn_boolean_t ignore_ancestry
,
244 const svn_delta_editor_t
*editor
,
248 svn_ra_local__session_baton_t
*sess
= session
->priv
;
251 const char *other_fs_path
= NULL
;
252 const char *repos_url_decoded
;
254 /* Get the HEAD revision if one is not supplied. */
255 if (! SVN_IS_VALID_REVNUM(revision
))
256 SVN_ERR(svn_fs_youngest_rev(&revision
, sess
->fs
, pool
));
258 /* If OTHER_URL was provided, validate it and convert it into a
259 regular filesystem path. */
262 other_url
= svn_path_uri_decode(other_url
, pool
);
263 repos_url_decoded
= svn_path_uri_decode(sess
->repos_url
, pool
);
264 repos_url_len
= strlen(repos_url_decoded
);
266 /* Sanity check: the other_url better be in the same repository as
267 the original session url! */
268 if (strncmp(other_url
, repos_url_decoded
, repos_url_len
) != 0)
269 return svn_error_createf
270 (SVN_ERR_RA_ILLEGAL_URL
, NULL
,
272 "is not the same repository as\n"
273 "'%s'"), other_url
, sess
->repos_url
);
275 other_fs_path
= other_url
+ repos_url_len
;
278 /* Pass back our reporter */
279 *reporter
= &ra_local_reporter
;
281 SVN_ERR(get_username(session
, pool
));
284 SVN_ERR(svn_delta_get_cancellation_editor(sess
->callbacks
->cancel_func
,
285 sess
->callback_baton
,
292 /* Build a reporter baton. */
293 SVN_ERR(svn_repos_begin_report2(&rbaton
,
309 /* Wrap the report baton given us by the repos layer with our own
311 *report_baton
= make_reporter_baton(sess
, rbaton
, pool
);
317 /*----------------------------------------------------------------*/
319 /*** Deltification stuff for get_commit_editor() ***/
321 struct deltify_etc_baton
323 svn_fs_t
*fs
; /* the fs to deltify in */
324 svn_repos_t
*repos
; /* repos for unlocking */
325 const char *fs_path
; /* fs-path part of split session URL */
326 apr_hash_t
*lock_tokens
; /* tokens to unlock, if any */
327 apr_pool_t
*pool
; /* pool for scratch work */
328 svn_commit_callback2_t callback
; /* the original callback */
329 void *callback_baton
; /* the original callback's baton */
332 /* This implements 'svn_commit_callback_t'. Its invokes the original
333 (wrapped) callback, but also does deltification on the new revision and
334 possibly unlocks committed paths.
335 BATON is 'struct deltify_etc_baton *'. */
337 deltify_etc(const svn_commit_info_t
*commit_info
,
338 void *baton
, apr_pool_t
*pool
)
340 struct deltify_etc_baton
*db
= baton
;
341 svn_error_t
*err1
, *err2
;
342 apr_hash_index_t
*hi
;
343 apr_pool_t
*iterpool
;
345 /* Invoke the original callback first, in case someone's waiting to
346 know the revision number so they can go off and annotate an
347 issue or something. */
348 err1
= (*db
->callback
)(commit_info
, db
->callback_baton
, pool
);
350 /* Maybe unlock the paths. */
353 iterpool
= svn_pool_create(db
->pool
);
354 for (hi
= apr_hash_first(db
->pool
, db
->lock_tokens
); hi
;
355 hi
= apr_hash_next(hi
))
357 const void *rel_path
;
359 const char *abs_path
, *token
;
361 svn_pool_clear(iterpool
);
362 apr_hash_this(hi
, &rel_path
, NULL
, &val
);
364 abs_path
= svn_path_join(db
->fs_path
, rel_path
, iterpool
);
365 /* We may get errors here if the lock was broken or stolen
366 after the commit succeeded. This is fine and should be
368 svn_error_clear(svn_repos_fs_unlock(db
->repos
, abs_path
, token
,
371 svn_pool_destroy(iterpool
);
374 /* But, deltification shouldn't be stopped just because someone's
375 random callback failed, so proceed unconditionally on to
377 err2
= svn_fs_deltify_revision(db
->fs
, commit_info
->revision
, db
->pool
);
379 /* It's more interesting if the original callback failed, so let
380 that one dominate. */
383 svn_error_clear(err2
);
390 /*----------------------------------------------------------------*/
392 /*** The RA vtable routines ***/
394 #define RA_LOCAL_DESCRIPTION \
395 N_("Module for accessing a repository on local disk.")
398 svn_ra_local__get_description(void)
400 return _(RA_LOCAL_DESCRIPTION
);
403 static const char * const *
404 svn_ra_local__get_schemes(apr_pool_t
*pool
)
406 static const char *schemes
[] = { "file", NULL
};
412 svn_ra_local__open(svn_ra_session_t
*session
,
413 const char *repos_URL
,
414 const svn_ra_callbacks2_t
*callbacks
,
415 void *callback_baton
,
419 svn_ra_local__session_baton_t
*sess
;
422 /* Allocate and stash the session_sess args we have already. */
423 sess
= apr_pcalloc(pool
, sizeof(*sess
));
424 sess
->callbacks
= callbacks
;
425 sess
->callback_baton
= callback_baton
;
427 /* Look through the URL, figure out which part points to the
428 repository, and which part is the path *within* the
430 SVN_ERR_W(svn_ra_local__split_URL(&(sess
->repos
),
435 _("Unable to open an ra_local session to URL"));
436 sess
->fs_path
= svn_stringbuf_create(fs_path
, session
->pool
);
438 /* Cache the filesystem object from the repos here for
440 sess
->fs
= svn_repos_fs(sess
->repos
);
442 /* Cache the repository UUID as well */
443 SVN_ERR(svn_fs_get_uuid(sess
->fs
, &sess
->uuid
, session
->pool
));
445 /* Be sure username is NULL so we know to look it up / ask for it */
446 sess
->username
= NULL
;
448 session
->priv
= sess
;
453 svn_ra_local__reparent(svn_ra_session_t
*session
,
457 svn_ra_local__session_baton_t
*sess
= session
->priv
;
458 const char *relpath
= "";
460 /* If the new URL isn't the same as our repository root URL, then
461 let's ensure that it's some child of it. */
462 if (strcmp(url
, sess
->repos_url
) != 0)
463 relpath
= svn_path_is_child(sess
->repos_url
, url
, pool
);
465 return svn_error_createf
466 (SVN_ERR_RA_ILLEGAL_URL
, NULL
,
467 _("URL '%s' is not a child of the session's repository root "
468 "URL '%s'"), url
, sess
->repos_url
);
470 /* Update our FS_PATH sess member to point to our new
471 relative-URL-turned-absolute-filesystem-path. */
472 relpath
= apr_pstrcat(pool
, "/", svn_path_uri_decode(relpath
, pool
), NULL
);
473 svn_stringbuf_set(sess
->fs_path
, relpath
);
479 svn_ra_local__get_session_url(svn_ra_session_t
*session
,
483 svn_ra_local__session_baton_t
*sess
= session
->priv
;
484 *url
= svn_path_join(sess
->repos_url
,
485 svn_path_uri_encode(sess
->fs_path
->data
+ 1, pool
),
491 svn_ra_local__get_latest_revnum(svn_ra_session_t
*session
,
492 svn_revnum_t
*latest_revnum
,
495 svn_ra_local__session_baton_t
*sess
= session
->priv
;
496 return svn_fs_youngest_rev(latest_revnum
, sess
->fs
, pool
);
500 svn_ra_local__get_file_revs(svn_ra_session_t
*session
,
504 svn_boolean_t include_merged_revisions
,
505 svn_file_rev_handler_t handler
,
509 svn_ra_local__session_baton_t
*sess
= session
->priv
;
510 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
511 return svn_repos_get_file_revs2(sess
->repos
, abs_path
, start
, end
,
512 include_merged_revisions
, NULL
, NULL
,
513 handler
, handler_baton
, pool
);
517 svn_ra_local__get_dated_revision(svn_ra_session_t
*session
,
518 svn_revnum_t
*revision
,
522 svn_ra_local__session_baton_t
*sess
= session
->priv
;
523 return svn_repos_dated_revision(revision
, sess
->repos
, tm
, pool
);
528 svn_ra_local__change_rev_prop(svn_ra_session_t
*session
,
531 const svn_string_t
*value
,
534 svn_ra_local__session_baton_t
*sess
= session
->priv
;
535 SVN_ERR(get_username(session
, pool
));
536 SVN_ERR(svn_repos_fs_change_rev_prop3(sess
->repos
, rev
, sess
->username
,
537 name
, value
, TRUE
, TRUE
, NULL
, NULL
,
543 svn_ra_local__get_uuid(svn_ra_session_t
*session
,
547 svn_ra_local__session_baton_t
*sess
= session
->priv
;
553 svn_ra_local__get_repos_root(svn_ra_session_t
*session
,
557 svn_ra_local__session_baton_t
*sess
= session
->priv
;
558 *url
= sess
->repos_url
;
563 svn_ra_local__rev_proplist(svn_ra_session_t
*session
,
568 svn_ra_local__session_baton_t
*sess
= session
->priv
;
569 return svn_repos_fs_revision_proplist(props
, sess
->repos
, rev
,
574 svn_ra_local__rev_prop(svn_ra_session_t
*session
,
577 svn_string_t
**value
,
580 svn_ra_local__session_baton_t
*sess
= session
->priv
;
581 return svn_repos_fs_revision_prop(value
, sess
->repos
, rev
, name
,
586 svn_ra_local__get_commit_editor(svn_ra_session_t
*session
,
587 const svn_delta_editor_t
**editor
,
589 apr_hash_t
*revprop_table
,
590 svn_commit_callback2_t callback
,
591 void *callback_baton
,
592 apr_hash_t
*lock_tokens
,
593 svn_boolean_t keep_locks
,
596 svn_ra_local__session_baton_t
*sess
= session
->priv
;
597 struct deltify_etc_baton
*db
= apr_palloc(pool
, sizeof(*db
));
598 apr_hash_index_t
*hi
;
599 svn_fs_access_t
*fs_access
;
602 db
->repos
= sess
->repos
;
603 db
->fs_path
= sess
->fs_path
->data
;
605 db
->lock_tokens
= lock_tokens
;
607 db
->lock_tokens
= NULL
;
609 db
->callback
= callback
;
610 db
->callback_baton
= callback_baton
;
612 SVN_ERR(get_username(session
, pool
));
614 /* If there are lock tokens to add, do so. */
617 SVN_ERR(svn_fs_get_access(&fs_access
, sess
->fs
));
619 /* If there is no access context, the filesystem will scream if a
623 for (hi
= apr_hash_first(pool
, lock_tokens
); hi
;
624 hi
= apr_hash_next(hi
))
629 apr_hash_this(hi
, NULL
, NULL
, &val
);
631 SVN_ERR(svn_fs_access_add_lock_token(fs_access
, token
));
636 /* Copy the revprops table so we can add the username. */
637 revprop_table
= apr_hash_copy(pool
, revprop_table
);
638 apr_hash_set(revprop_table
, SVN_PROP_REVISION_AUTHOR
, APR_HASH_KEY_STRING
,
639 svn_string_create(sess
->username
, pool
));
641 /* Get the repos commit-editor */
642 SVN_ERR(svn_repos_get_commit_editor5
643 (editor
, edit_baton
, sess
->repos
, NULL
,
644 svn_path_uri_decode(sess
->repos_url
, pool
), sess
->fs_path
->data
,
645 revprop_table
, deltify_etc
, db
, NULL
, NULL
, pool
));
652 svn_ra_local__get_mergeinfo(svn_ra_session_t
*session
,
653 svn_mergeinfo_catalog_t
*catalog
,
654 const apr_array_header_t
*paths
,
655 svn_revnum_t revision
,
656 svn_mergeinfo_inheritance_t inherit
,
657 svn_boolean_t include_descendants
,
660 svn_ra_local__session_baton_t
*sess
= session
->priv
;
661 svn_mergeinfo_catalog_t tmp_catalog
;
663 apr_array_header_t
*abs_paths
=
664 apr_array_make(pool
, 0, sizeof(const char *));
666 for (i
= 0; i
< paths
->nelts
; i
++)
668 const char *relative_path
= APR_ARRAY_IDX(paths
, i
, const char *);
669 APR_ARRAY_PUSH(abs_paths
, const char *) =
670 svn_path_join(sess
->fs_path
->data
, relative_path
, pool
);
673 SVN_ERR(svn_repos_fs_get_mergeinfo(&tmp_catalog
, sess
->repos
, abs_paths
,
674 revision
, inherit
, include_descendants
,
676 if (apr_hash_count(tmp_catalog
) > 0)
677 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog
,
689 svn_ra_local__do_update(svn_ra_session_t
*session
,
690 const svn_ra_reporter3_t
**reporter
,
692 svn_revnum_t update_revision
,
693 const char *update_target
,
695 svn_boolean_t send_copyfrom_args
,
696 const svn_delta_editor_t
*update_editor
,
700 return make_reporter(session
,
717 svn_ra_local__do_switch(svn_ra_session_t
*session
,
718 const svn_ra_reporter3_t
**reporter
,
720 svn_revnum_t update_revision
,
721 const char *update_target
,
723 const char *switch_url
,
724 const svn_delta_editor_t
*update_editor
,
728 return make_reporter(session
,
736 FALSE
, /* ### TODO(sussman): take new arg */
745 svn_ra_local__do_status(svn_ra_session_t
*session
,
746 const svn_ra_reporter3_t
**reporter
,
748 const char *status_target
,
749 svn_revnum_t revision
,
751 const svn_delta_editor_t
*status_editor
,
755 return make_reporter(session
,
772 svn_ra_local__do_diff(svn_ra_session_t
*session
,
773 const svn_ra_reporter3_t
**reporter
,
775 svn_revnum_t update_revision
,
776 const char *update_target
,
778 svn_boolean_t ignore_ancestry
,
779 svn_boolean_t text_deltas
,
780 const char *switch_url
,
781 const svn_delta_editor_t
*update_editor
,
785 return make_reporter(session
,
803 svn_ra_local__session_baton_t
*sess
;
804 svn_log_entry_receiver_t real_cb
;
809 cancellation_log_receiver(void *baton
,
810 svn_log_entry_t
*log_entry
,
813 struct log_baton
*b
= baton
;
814 svn_ra_local__session_baton_t
*sess
= b
->sess
;
816 SVN_ERR((sess
->callbacks
->cancel_func
)(sess
->callback_baton
));
818 return b
->real_cb(b
->real_baton
, log_entry
, pool
);
823 svn_ra_local__get_log(svn_ra_session_t
*session
,
824 const apr_array_header_t
*paths
,
828 svn_boolean_t discover_changed_paths
,
829 svn_boolean_t strict_node_history
,
830 svn_boolean_t include_merged_revisions
,
831 const apr_array_header_t
*revprops
,
832 svn_log_entry_receiver_t receiver
,
833 void *receiver_baton
,
836 svn_ra_local__session_baton_t
*sess
= session
->priv
;
839 apr_array_header_t
*abs_paths
=
840 apr_array_make(pool
, 0, sizeof(const char *));
844 for (i
= 0; i
< paths
->nelts
; i
++)
846 const char *relative_path
= APR_ARRAY_IDX(paths
, i
, const char *);
847 APR_ARRAY_PUSH(abs_paths
, const char *) =
848 svn_path_join(sess
->fs_path
->data
, relative_path
, pool
);
852 if (sess
->callbacks
&&
853 sess
->callbacks
->cancel_func
)
855 lb
.real_cb
= receiver
;
856 lb
.real_baton
= receiver_baton
;
859 receiver
= cancellation_log_receiver
;
860 receiver_baton
= &lb
;
863 return svn_repos_get_logs4(sess
->repos
,
868 discover_changed_paths
,
870 include_merged_revisions
,
880 svn_ra_local__do_check_path(svn_ra_session_t
*session
,
882 svn_revnum_t revision
,
883 svn_node_kind_t
*kind
,
886 svn_ra_local__session_baton_t
*sess
= session
->priv
;
888 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
890 if (! SVN_IS_VALID_REVNUM(revision
))
891 SVN_ERR(svn_fs_youngest_rev(&revision
, sess
->fs
, pool
));
892 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, revision
, pool
));
893 SVN_ERR(svn_fs_check_path(kind
, root
, abs_path
, pool
));
899 svn_ra_local__stat(svn_ra_session_t
*session
,
901 svn_revnum_t revision
,
902 svn_dirent_t
**dirent
,
905 svn_ra_local__session_baton_t
*sess
= session
->priv
;
907 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
909 if (! SVN_IS_VALID_REVNUM(revision
))
910 SVN_ERR(svn_fs_youngest_rev(&revision
, sess
->fs
, pool
));
911 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, revision
, pool
));
913 SVN_ERR(svn_repos_stat(dirent
, root
, abs_path
, pool
));
922 get_node_props(apr_hash_t
**props
,
923 svn_ra_local__session_baton_t
*sess
,
928 svn_revnum_t cmt_rev
;
929 const char *cmt_date
, *cmt_author
;
931 /* Create a hash with props attached to the fs node. */
932 SVN_ERR(svn_fs_node_proplist(props
, root
, path
, pool
));
934 /* Now add some non-tweakable metadata to the hash as well... */
936 /* The so-called 'entryprops' with info about CR & friends. */
937 SVN_ERR(svn_repos_get_committed_info(&cmt_rev
, &cmt_date
,
938 &cmt_author
, root
, path
, pool
));
941 SVN_PROP_ENTRY_COMMITTED_REV
,
943 svn_string_createf(pool
, "%ld", cmt_rev
));
945 SVN_PROP_ENTRY_COMMITTED_DATE
,
947 cmt_date
? svn_string_create(cmt_date
, pool
) : NULL
);
949 SVN_PROP_ENTRY_LAST_AUTHOR
,
951 cmt_author
? svn_string_create(cmt_author
, pool
) : NULL
);
955 svn_string_create(sess
->uuid
, pool
));
957 /* We have no 'wcprops' in ra_local, but might someday. */
963 /* Getting just one file. */
965 svn_ra_local__get_file(svn_ra_session_t
*session
,
967 svn_revnum_t revision
,
968 svn_stream_t
*stream
,
969 svn_revnum_t
*fetched_rev
,
974 svn_stream_t
*contents
;
975 svn_revnum_t youngest_rev
;
976 svn_ra_local__session_baton_t
*sess
= session
->priv
;
977 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
979 /* Open the revision's root. */
980 if (! SVN_IS_VALID_REVNUM(revision
))
982 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, sess
->fs
, pool
));
983 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, youngest_rev
, pool
));
984 if (fetched_rev
!= NULL
)
985 *fetched_rev
= youngest_rev
;
988 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, revision
, pool
));
992 /* Get a stream representing the file's contents. */
993 SVN_ERR(svn_fs_file_contents(&contents
, root
, abs_path
, pool
));
995 /* Now push data from the fs stream back at the caller's stream.
996 Note that this particular RA layer does not computing a
997 checksum as we go, and confirming it against the repository's
998 checksum when done. That's because it calls
999 svn_fs_file_contents() directly, which already checks the
1000 stored checksum, and all we're doing here is writing bytes in
1001 a loop. Truly, Nothing Can Go Wrong :-). But RA layers that
1002 go over a network should confirm the checksum. */
1003 SVN_ERR(svn_stream_copy2(contents
, stream
,
1005 ? sess
->callbacks
->cancel_func
: NULL
,
1006 sess
->callback_baton
,
1010 /* Handle props if requested. */
1012 SVN_ERR(get_node_props(props
, sess
, root
, abs_path
, pool
));
1014 return SVN_NO_ERROR
;
1019 /* Getting a directory's entries */
1020 static svn_error_t
*
1021 svn_ra_local__get_dir(svn_ra_session_t
*session
,
1022 apr_hash_t
**dirents
,
1023 svn_revnum_t
*fetched_rev
,
1026 svn_revnum_t revision
,
1027 apr_uint32_t dirent_fields
,
1030 svn_fs_root_t
*root
;
1031 svn_revnum_t youngest_rev
;
1032 apr_hash_t
*entries
;
1033 apr_hash_index_t
*hi
;
1034 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1035 apr_pool_t
*subpool
;
1036 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
1038 /* Open the revision's root. */
1039 if (! SVN_IS_VALID_REVNUM(revision
))
1041 SVN_ERR(svn_fs_youngest_rev(&youngest_rev
, sess
->fs
, pool
));
1042 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, youngest_rev
, pool
));
1043 if (fetched_rev
!= NULL
)
1044 *fetched_rev
= youngest_rev
;
1047 SVN_ERR(svn_fs_revision_root(&root
, sess
->fs
, revision
, pool
));
1051 /* Get the dir's entries. */
1052 SVN_ERR(svn_fs_dir_entries(&entries
, root
, abs_path
, pool
));
1054 /* Loop over the fs dirents, and build a hash of general
1056 *dirents
= apr_hash_make(pool
);
1057 subpool
= svn_pool_create(pool
);
1058 for (hi
= apr_hash_first(pool
, entries
); hi
; hi
= apr_hash_next(hi
))
1062 apr_hash_t
*prophash
;
1063 const char *datestring
, *entryname
, *fullpath
;
1064 svn_fs_dirent_t
*fs_entry
;
1065 svn_dirent_t
*entry
= apr_pcalloc(pool
, sizeof(*entry
));
1067 svn_pool_clear(subpool
);
1069 apr_hash_this(hi
, &key
, NULL
, &val
);
1070 entryname
= (const char *) key
;
1071 fs_entry
= (svn_fs_dirent_t
*) val
;
1073 fullpath
= svn_path_join(abs_path
, entryname
, subpool
);
1075 if (dirent_fields
& SVN_DIRENT_KIND
)
1078 entry
->kind
= fs_entry
->kind
;
1081 if (dirent_fields
& SVN_DIRENT_SIZE
)
1084 if (entry
->kind
== svn_node_dir
)
1087 SVN_ERR(svn_fs_file_length(&(entry
->size
), root
,
1088 fullpath
, subpool
));
1091 if (dirent_fields
& SVN_DIRENT_HAS_PROPS
)
1094 SVN_ERR(svn_fs_node_proplist(&prophash
, root
, fullpath
,
1096 entry
->has_props
= (apr_hash_count(prophash
)) ? TRUE
: FALSE
;
1099 if ((dirent_fields
& SVN_DIRENT_TIME
)
1100 || (dirent_fields
& SVN_DIRENT_LAST_AUTHOR
)
1101 || (dirent_fields
& SVN_DIRENT_CREATED_REV
))
1103 /* created_rev & friends */
1104 SVN_ERR(svn_repos_get_committed_info(&(entry
->created_rev
),
1106 &(entry
->last_author
),
1107 root
, fullpath
, subpool
));
1109 SVN_ERR(svn_time_from_cstring(&(entry
->time
), datestring
,
1111 if (entry
->last_author
)
1112 entry
->last_author
= apr_pstrdup(pool
, entry
->last_author
);
1116 apr_hash_set(*dirents
, entryname
, APR_HASH_KEY_STRING
, entry
);
1118 svn_pool_destroy(subpool
);
1121 /* Handle props if requested. */
1123 SVN_ERR(get_node_props(props
, sess
, root
, abs_path
, pool
));
1125 return SVN_NO_ERROR
;
1129 static svn_error_t
*
1130 svn_ra_local__get_locations(svn_ra_session_t
*session
,
1131 apr_hash_t
**locations
,
1133 svn_revnum_t peg_revision
,
1134 apr_array_header_t
*location_revisions
,
1137 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1138 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
1139 return svn_repos_trace_node_locations(sess
->fs
, locations
, abs_path
,
1140 peg_revision
, location_revisions
,
1145 static svn_error_t
*
1146 svn_ra_local__get_location_segments(svn_ra_session_t
*session
,
1148 svn_revnum_t peg_revision
,
1149 svn_revnum_t start_rev
,
1150 svn_revnum_t end_rev
,
1151 svn_location_segment_receiver_t receiver
,
1152 void *receiver_baton
,
1155 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1156 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
1157 return svn_repos_node_location_segments(sess
->repos
, abs_path
,
1158 peg_revision
, start_rev
, end_rev
,
1159 receiver
, receiver_baton
,
1164 static svn_error_t
*
1165 svn_ra_local__lock(svn_ra_session_t
*session
,
1166 apr_hash_t
*path_revs
,
1167 const char *comment
,
1168 svn_boolean_t force
,
1169 svn_ra_lock_callback_t lock_func
,
1173 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1174 apr_hash_index_t
*hi
;
1175 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1177 /* A username is absolutely required to lock a path. */
1178 SVN_ERR(get_username(session
, pool
));
1180 for (hi
= apr_hash_first(pool
, path_revs
); hi
; hi
= apr_hash_next(hi
))
1186 svn_revnum_t
*revnum
;
1187 const char *abs_path
;
1188 svn_error_t
*err
, *callback_err
= NULL
;
1190 svn_pool_clear(iterpool
);
1191 apr_hash_this(hi
, &key
, NULL
, &val
);
1195 abs_path
= svn_path_join(sess
->fs_path
->data
, path
, iterpool
);
1197 /* This wrapper will call pre- and post-lock hooks. */
1198 err
= svn_repos_fs_lock(&lock
, sess
->repos
, abs_path
, NULL
, comment
,
1199 FALSE
/* not DAV comment */,
1200 0 /* no expiration */, *revnum
, force
,
1203 if (err
&& !SVN_ERR_IS_LOCK_ERROR(err
))
1207 callback_err
= lock_func(lock_baton
, path
, TRUE
, err
? NULL
: lock
,
1210 svn_error_clear(err
);
1213 return callback_err
;
1216 svn_pool_destroy(iterpool
);
1218 return SVN_NO_ERROR
;
1222 static svn_error_t
*
1223 svn_ra_local__unlock(svn_ra_session_t
*session
,
1224 apr_hash_t
*path_tokens
,
1225 svn_boolean_t force
,
1226 svn_ra_lock_callback_t lock_func
,
1230 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1231 apr_hash_index_t
*hi
;
1232 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1234 /* A username is absolutely required to unlock a path. */
1235 SVN_ERR(get_username(session
, pool
));
1237 for (hi
= apr_hash_first(pool
, path_tokens
); hi
; hi
= apr_hash_next(hi
))
1242 const char *abs_path
, *token
;
1243 svn_error_t
*err
, *callback_err
= NULL
;
1245 svn_pool_clear(iterpool
);
1246 apr_hash_this(hi
, &key
, NULL
, &val
);
1248 /* Since we can't store NULL values in a hash, we turn "" to
1250 if (strcmp(val
, "") != 0)
1255 abs_path
= svn_path_join(sess
->fs_path
->data
, path
, iterpool
);
1257 /* This wrapper will call pre- and post-unlock hooks. */
1258 err
= svn_repos_fs_unlock(sess
->repos
, abs_path
, token
, force
,
1261 if (err
&& !SVN_ERR_IS_UNLOCK_ERROR(err
))
1265 callback_err
= lock_func(lock_baton
, path
, FALSE
, NULL
, err
, iterpool
);
1267 svn_error_clear(err
);
1270 return callback_err
;
1273 svn_pool_destroy(iterpool
);
1275 return SVN_NO_ERROR
;
1280 static svn_error_t
*
1281 svn_ra_local__get_lock(svn_ra_session_t
*session
,
1286 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1287 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
1288 return svn_fs_get_lock(lock
, sess
->fs
, abs_path
, pool
);
1293 static svn_error_t
*
1294 svn_ra_local__get_locks(svn_ra_session_t
*session
,
1299 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1300 const char *abs_path
= svn_path_join(sess
->fs_path
->data
, path
, pool
);
1302 /* Kinda silly to call the repos wrapper, since we have no authz
1303 func to give it. But heck, why not. */
1304 return svn_repos_fs_get_locks(locks
, sess
->repos
, abs_path
,
1309 static svn_error_t
*
1310 svn_ra_local__replay(svn_ra_session_t
*session
,
1311 svn_revnum_t revision
,
1312 svn_revnum_t low_water_mark
,
1313 svn_boolean_t send_deltas
,
1314 const svn_delta_editor_t
*editor
,
1318 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1319 svn_fs_root_t
*root
;
1321 SVN_ERR(svn_fs_revision_root(&root
, svn_repos_fs(sess
->repos
),
1323 SVN_ERR(svn_repos_replay2(root
, sess
->fs_path
->data
, low_water_mark
,
1324 send_deltas
, editor
, edit_baton
, NULL
, NULL
,
1327 return SVN_NO_ERROR
;
1331 static svn_error_t
*
1332 svn_ra_local__replay_range(svn_ra_session_t
*session
,
1333 svn_revnum_t start_revision
,
1334 svn_revnum_t end_revision
,
1335 svn_revnum_t low_water_mark
,
1336 svn_boolean_t send_deltas
,
1337 svn_ra_replay_revstart_callback_t revstart_func
,
1338 svn_ra_replay_revfinish_callback_t revfinish_func
,
1342 return svn_error_create(SVN_ERR_RA_NOT_IMPLEMENTED
, NULL
, NULL
);
1346 static svn_error_t
*
1347 svn_ra_local__has_capability(svn_ra_session_t
*session
,
1349 const char *capability
,
1352 svn_ra_local__session_baton_t
*sess
= session
->priv
;
1354 if (strcmp(capability
, SVN_RA_CAPABILITY_DEPTH
) == 0
1355 || strcmp(capability
, SVN_RA_CAPABILITY_LOG_REVPROPS
) == 0
1356 || strcmp(capability
, SVN_RA_CAPABILITY_PARTIAL_REPLAY
) == 0
1357 || strcmp(capability
, SVN_RA_CAPABILITY_COMMIT_REVPROPS
) == 0)
1361 else if (strcmp(capability
, SVN_RA_CAPABILITY_MERGEINFO
) == 0)
1363 /* With mergeinfo, the code's capabilities may not reflect the
1364 repository's, so inquire further. */
1365 SVN_ERR(svn_repos_has_capability(sess
->repos
, has
,
1366 SVN_REPOS_CAPABILITY_MERGEINFO
,
1369 else /* Don't know any other capabilities, so error. */
1371 return svn_error_createf
1372 (SVN_ERR_UNKNOWN_CAPABILITY
, NULL
,
1373 _("Don't know anything about capability '%s'"), capability
);
1376 return SVN_NO_ERROR
;
1379 /*----------------------------------------------------------------*/
1381 static const svn_version_t
*
1382 ra_local_version(void)
1387 /** The ra_vtable **/
1389 static const svn_ra__vtable_t ra_local_vtable
=
1392 svn_ra_local__get_description
,
1393 svn_ra_local__get_schemes
,
1395 svn_ra_local__reparent
,
1396 svn_ra_local__get_session_url
,
1397 svn_ra_local__get_latest_revnum
,
1398 svn_ra_local__get_dated_revision
,
1399 svn_ra_local__change_rev_prop
,
1400 svn_ra_local__rev_proplist
,
1401 svn_ra_local__rev_prop
,
1402 svn_ra_local__get_commit_editor
,
1403 svn_ra_local__get_file
,
1404 svn_ra_local__get_dir
,
1405 svn_ra_local__get_mergeinfo
,
1406 svn_ra_local__do_update
,
1407 svn_ra_local__do_switch
,
1408 svn_ra_local__do_status
,
1409 svn_ra_local__do_diff
,
1410 svn_ra_local__get_log
,
1411 svn_ra_local__do_check_path
,
1413 svn_ra_local__get_uuid
,
1414 svn_ra_local__get_repos_root
,
1415 svn_ra_local__get_locations
,
1416 svn_ra_local__get_location_segments
,
1417 svn_ra_local__get_file_revs
,
1419 svn_ra_local__unlock
,
1420 svn_ra_local__get_lock
,
1421 svn_ra_local__get_locks
,
1422 svn_ra_local__replay
,
1423 svn_ra_local__has_capability
,
1424 svn_ra_local__replay_range
1428 /*----------------------------------------------------------------*/
1430 /** The One Public Routine, called by libsvn_ra **/
1433 svn_ra_local__init(const svn_version_t
*loader_version
,
1434 const svn_ra__vtable_t
**vtable
,
1437 static const svn_version_checklist_t checklist
[] =
1439 { "svn_subr", svn_subr_version
},
1440 { "svn_delta", svn_delta_version
},
1441 { "svn_repos", svn_repos_version
},
1442 { "svn_fs", svn_fs_version
},
1447 /* Simplified version check to make sure we can safely use the
1448 VTABLE parameter. The RA loader does a more exhaustive check. */
1449 if (loader_version
->major
!= SVN_VER_MAJOR
)
1450 return svn_error_createf(SVN_ERR_VERSION_MISMATCH
, NULL
,
1451 _("Unsupported RA loader version (%d) for "
1453 loader_version
->major
);
1455 SVN_ERR(svn_ver_check_list(ra_local_version(), checklist
));
1457 #ifndef SVN_LIBSVN_CLIENT_LINKS_RA_LOCAL
1458 /* This assumes that POOL was the pool used to load the dso. */
1459 SVN_ERR(svn_fs_initialize(pool
));
1462 *vtable
= &ra_local_vtable
;
1464 return SVN_NO_ERROR
;
1467 /* Compatibility wrapper for the 1.1 and before API. */
1468 #define NAME "ra_local"
1469 #define DESCRIPTION RA_LOCAL_DESCRIPTION
1470 #define VTBL ra_local_vtable
1471 #define INITFUNC svn_ra_local__init
1472 #define COMPAT_INITFUNC svn_ra_local_init
1473 #include "../libsvn_ra/wrapper_template.h"