Remove no-longer-used svn_*_get_mergeinfo_for_tree APIs.
[svn.git] / subversion / libsvn_ra_local / ra_plugin.c
blob9dac061a66d93ba1130469d8ea04e9034574ed54
1 /*
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 * ====================================================================
19 #include "ra_local.h"
20 #include "svn_ra.h"
21 #include "svn_fs.h"
22 #include "svn_delta.h"
23 #include "svn_repos.h"
24 #include "svn_pools.h"
25 #include "svn_time.h"
26 #include "svn_props.h"
27 #include "svn_mergeinfo.h"
28 #include "svn_path.h"
30 #include "svn_private_config.h"
31 #include "../libsvn_ra/ra_loader.h"
33 #define APR_WANT_STRFUNC
34 #include <apr_want.h>
36 #include <assert.h>
39 /*----------------------------------------------------------------*/
41 /*** Miscellaneous helper functions ***/
44 /* Pool cleanup handler: ensure that the access descriptor of the
45 filesystem (svn_fs_t *) DATA is set to NULL. */
46 static apr_status_t
47 cleanup_access(void *data)
49 svn_error_t *serr;
50 svn_fs_t *fs = data;
52 serr = svn_fs_set_access(fs, NULL);
54 if (serr)
56 apr_status_t apr_err = serr->apr_err;
57 svn_error_clear(serr);
58 return apr_err;
61 return APR_SUCCESS;
65 /* Fetch a username for use with SESS. */
66 static svn_error_t *
67 get_username(svn_ra_session_t *session,
68 apr_pool_t *pool)
70 svn_ra_local__session_baton_t *sess = session->priv;
71 svn_auth_iterstate_t *iterstate;
72 svn_fs_access_t *access_ctx;
74 /* If we've already found the username don't ask for it again. */
75 if (! sess->username)
77 /* Get a username somehow, so we have some svn:author property to
78 attach to a commit. */
79 if (sess->callbacks->auth_baton)
81 void *creds;
82 svn_auth_cred_username_t *username_creds;
83 SVN_ERR(svn_auth_first_credentials(&creds, &iterstate,
84 SVN_AUTH_CRED_USERNAME,
85 sess->uuid, /* realmstring */
86 sess->callbacks->auth_baton,
87 pool));
89 /* No point in calling next_creds(), since that assumes that the
90 first_creds() somehow failed to authenticate. But there's no
91 challenge going on, so we use whatever creds we get back on
92 the first try. */
93 username_creds = creds;
94 if (username_creds && username_creds->username)
96 sess->username = apr_pstrdup(session->pool,
97 username_creds->username);
98 svn_error_clear(svn_auth_save_credentials(iterstate, pool));
100 else
101 sess->username = "";
103 else
104 sess->username = "";
107 /* If we have a real username, attach it to the filesystem so that it can
108 be used to validate locks. Even if there already is a user context
109 associated, it may contain irrelevant lock tokens, so always create a new.
111 if (*sess->username)
113 SVN_ERR(svn_fs_create_access(&access_ctx, sess->username,
114 pool));
115 SVN_ERR(svn_fs_set_access(sess->fs, access_ctx));
117 /* Make sure this context is disassociated when the pool gets
118 destroyed. */
119 apr_pool_cleanup_register(pool, sess->fs, cleanup_access,
120 apr_pool_cleanup_null);
123 return SVN_NO_ERROR;
126 /*----------------------------------------------------------------*/
128 /*** The reporter vtable needed by do_update() and friends ***/
130 typedef struct reporter_baton_t
132 svn_ra_local__session_baton_t *sess;
133 void *report_baton;
135 } reporter_baton_t;
138 static void *
139 make_reporter_baton(svn_ra_local__session_baton_t *sess,
140 void *report_baton,
141 apr_pool_t *pool)
143 reporter_baton_t *rbaton = apr_palloc(pool, sizeof(*rbaton));
144 rbaton->sess = sess;
145 rbaton->report_baton = report_baton;
146 return rbaton;
150 static svn_error_t *
151 reporter_set_path(void *reporter_baton,
152 const char *path,
153 svn_revnum_t revision,
154 svn_depth_t depth,
155 svn_boolean_t start_empty,
156 const char *lock_token,
157 apr_pool_t *pool)
159 reporter_baton_t *rbaton = reporter_baton;
160 return svn_repos_set_path3(rbaton->report_baton, path,
161 revision, depth, start_empty, lock_token, pool);
165 static svn_error_t *
166 reporter_delete_path(void *reporter_baton,
167 const char *path,
168 apr_pool_t *pool)
170 reporter_baton_t *rbaton = reporter_baton;
171 return svn_repos_delete_path(rbaton->report_baton, path, pool);
175 static svn_error_t *
176 reporter_link_path(void *reporter_baton,
177 const char *path,
178 const char *url,
179 svn_revnum_t revision,
180 svn_depth_t depth,
181 svn_boolean_t start_empty,
182 const char *lock_token,
183 apr_pool_t *pool)
185 reporter_baton_t *rbaton = reporter_baton;
186 const char *fs_path = NULL;
187 const char *repos_url_decoded;
188 int repos_url_len;
190 url = svn_path_uri_decode(url, pool);
191 repos_url_decoded = svn_path_uri_decode(rbaton->sess->repos_url, pool);
192 repos_url_len = strlen(repos_url_decoded);
193 if (strncmp(url, repos_url_decoded, repos_url_len) != 0)
194 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
195 _("'%s'\n"
196 "is not the same repository as\n"
197 "'%s'"), url, rbaton->sess->repos_url);
198 fs_path = url + repos_url_len;
199 return svn_repos_link_path3(rbaton->report_baton, path, fs_path, revision,
200 depth, start_empty, lock_token, pool);
204 static svn_error_t *
205 reporter_finish_report(void *reporter_baton,
206 apr_pool_t *pool)
208 reporter_baton_t *rbaton = reporter_baton;
209 return svn_repos_finish_report(rbaton->report_baton, pool);
213 static svn_error_t *
214 reporter_abort_report(void *reporter_baton,
215 apr_pool_t *pool)
217 reporter_baton_t *rbaton = reporter_baton;
218 return svn_repos_abort_report(rbaton->report_baton, pool);
222 static const svn_ra_reporter3_t ra_local_reporter =
224 reporter_set_path,
225 reporter_delete_path,
226 reporter_link_path,
227 reporter_finish_report,
228 reporter_abort_report
232 static svn_error_t *
233 make_reporter(svn_ra_session_t *session,
234 const svn_ra_reporter3_t **reporter,
235 void **report_baton,
236 svn_revnum_t revision,
237 const char *target,
238 const char *other_url,
239 svn_boolean_t text_deltas,
240 svn_depth_t depth,
241 svn_boolean_t send_copyfrom_args,
242 svn_boolean_t ignore_ancestry,
243 const svn_delta_editor_t *editor,
244 void *edit_baton,
245 apr_pool_t *pool)
247 svn_ra_local__session_baton_t *sess = session->priv;
248 void *rbaton;
249 int repos_url_len;
250 const char *other_fs_path = NULL;
251 const char *repos_url_decoded;
253 /* Get the HEAD revision if one is not supplied. */
254 if (! SVN_IS_VALID_REVNUM(revision))
255 SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));
257 /* If OTHER_URL was provided, validate it and convert it into a
258 regular filesystem path. */
259 if (other_url)
261 other_url = svn_path_uri_decode(other_url, pool);
262 repos_url_decoded = svn_path_uri_decode(sess->repos_url, pool);
263 repos_url_len = strlen(repos_url_decoded);
265 /* Sanity check: the other_url better be in the same repository as
266 the original session url! */
267 if (strncmp(other_url, repos_url_decoded, repos_url_len) != 0)
268 return svn_error_createf
269 (SVN_ERR_RA_ILLEGAL_URL, NULL,
270 _("'%s'\n"
271 "is not the same repository as\n"
272 "'%s'"), other_url, sess->repos_url);
274 other_fs_path = other_url + repos_url_len;
277 /* Pass back our reporter */
278 *reporter = &ra_local_reporter;
280 SVN_ERR(get_username(session, pool));
282 if (sess->callbacks)
283 SVN_ERR(svn_delta_get_cancellation_editor(sess->callbacks->cancel_func,
284 sess->callback_baton,
285 editor,
286 edit_baton,
287 &editor,
288 &edit_baton,
289 pool));
291 /* Build a reporter baton. */
292 SVN_ERR(svn_repos_begin_report2(&rbaton,
293 revision,
294 sess->repos,
295 sess->fs_path->data,
296 target,
297 other_fs_path,
298 text_deltas,
299 depth,
300 ignore_ancestry,
301 send_copyfrom_args,
302 editor,
303 edit_baton,
304 NULL,
305 NULL,
306 pool));
308 /* Wrap the report baton given us by the repos layer with our own
309 reporter baton. */
310 *report_baton = make_reporter_baton(sess, rbaton, pool);
312 return SVN_NO_ERROR;
316 /*----------------------------------------------------------------*/
318 /*** Deltification stuff for get_commit_editor() ***/
320 struct deltify_etc_baton
322 svn_fs_t *fs; /* the fs to deltify in */
323 svn_repos_t *repos; /* repos for unlocking */
324 const char *fs_path; /* fs-path part of split session URL */
325 apr_hash_t *lock_tokens; /* tokens to unlock, if any */
326 apr_pool_t *pool; /* pool for scratch work */
327 svn_commit_callback2_t callback; /* the original callback */
328 void *callback_baton; /* the original callback's baton */
331 /* This implements 'svn_commit_callback_t'. Its invokes the original
332 (wrapped) callback, but also does deltification on the new revision and
333 possibly unlocks committed paths.
334 BATON is 'struct deltify_etc_baton *'. */
335 static svn_error_t *
336 deltify_etc(const svn_commit_info_t *commit_info,
337 void *baton, apr_pool_t *pool)
339 struct deltify_etc_baton *db = baton;
340 svn_error_t *err1, *err2;
341 apr_hash_index_t *hi;
342 apr_pool_t *iterpool;
344 /* Invoke the original callback first, in case someone's waiting to
345 know the revision number so they can go off and annotate an
346 issue or something. */
347 err1 = (*db->callback)(commit_info, db->callback_baton, pool);
349 /* Maybe unlock the paths. */
350 if (db->lock_tokens)
352 iterpool = svn_pool_create(db->pool);
353 for (hi = apr_hash_first(db->pool, db->lock_tokens); hi;
354 hi = apr_hash_next(hi))
356 const void *rel_path;
357 void *val;
358 const char *abs_path, *token;
360 svn_pool_clear(iterpool);
361 apr_hash_this(hi, &rel_path, NULL, &val);
362 token = val;
363 abs_path = svn_path_join(db->fs_path, rel_path, iterpool);
364 /* We may get errors here if the lock was broken or stolen
365 after the commit succeeded. This is fine and should be
366 ignored. */
367 svn_error_clear(svn_repos_fs_unlock(db->repos, abs_path, token,
368 FALSE, iterpool));
370 svn_pool_destroy(iterpool);
373 /* But, deltification shouldn't be stopped just because someone's
374 random callback failed, so proceed unconditionally on to
375 deltification. */
376 err2 = svn_fs_deltify_revision(db->fs, commit_info->revision, db->pool);
378 /* It's more interesting if the original callback failed, so let
379 that one dominate. */
380 if (err1)
382 svn_error_clear(err2);
383 return err1;
386 return err2;
389 /*----------------------------------------------------------------*/
391 /*** The RA vtable routines ***/
393 #define RA_LOCAL_DESCRIPTION \
394 N_("Module for accessing a repository on local disk.")
396 static const char *
397 svn_ra_local__get_description(void)
399 return _(RA_LOCAL_DESCRIPTION);
402 static const char * const *
403 svn_ra_local__get_schemes(apr_pool_t *pool)
405 static const char *schemes[] = { "file", NULL };
407 return schemes;
410 static svn_error_t *
411 svn_ra_local__open(svn_ra_session_t *session,
412 const char *repos_URL,
413 const svn_ra_callbacks2_t *callbacks,
414 void *callback_baton,
415 apr_hash_t *config,
416 apr_pool_t *pool)
418 svn_ra_local__session_baton_t *sess;
419 const char *fs_path;
421 /* Allocate and stash the session_sess args we have already. */
422 sess = apr_pcalloc(pool, sizeof(*sess));
423 sess->callbacks = callbacks;
424 sess->callback_baton = callback_baton;
426 /* Look through the URL, figure out which part points to the
427 repository, and which part is the path *within* the
428 repository. */
429 SVN_ERR_W(svn_ra_local__split_URL(&(sess->repos),
430 &(sess->repos_url),
431 &fs_path,
432 repos_URL,
433 session->pool),
434 _("Unable to open an ra_local session to URL"));
435 sess->fs_path = svn_stringbuf_create(fs_path, session->pool);
437 /* Cache the filesystem object from the repos here for
438 convenience. */
439 sess->fs = svn_repos_fs(sess->repos);
441 /* Cache the repository UUID as well */
442 SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));
444 /* Be sure username is NULL so we know to look it up / ask for it */
445 sess->username = NULL;
447 session->priv = sess;
448 return SVN_NO_ERROR;
451 static svn_error_t *
452 svn_ra_local__reparent(svn_ra_session_t *session,
453 const char *url,
454 apr_pool_t *pool)
456 svn_ra_local__session_baton_t *sess = session->priv;
457 const char *relpath = "";
459 /* If the new URL isn't the same as our repository root URL, then
460 let's ensure that it's some child of it. */
461 if (strcmp(url, sess->repos_url) != 0)
462 relpath = svn_path_is_child(sess->repos_url, url, pool);
463 if (! relpath)
464 return svn_error_createf
465 (SVN_ERR_RA_ILLEGAL_URL, NULL,
466 _("URL '%s' is not a child of the session's repository root "
467 "URL '%s'"), url, sess->repos_url);
469 /* Update our FS_PATH sess member to point to our new
470 relative-URL-turned-absolute-filesystem-path. */
471 relpath = apr_pstrcat(pool, "/", svn_path_uri_decode(relpath, pool), NULL);
472 svn_stringbuf_set(sess->fs_path, relpath);
474 return SVN_NO_ERROR;
477 static svn_error_t *
478 svn_ra_local__get_session_url(svn_ra_session_t *session,
479 const char **url,
480 apr_pool_t *pool)
482 svn_ra_local__session_baton_t *sess = session->priv;
483 *url = svn_path_join(sess->repos_url,
484 svn_path_uri_encode(sess->fs_path->data + 1, pool),
485 pool);
486 return SVN_NO_ERROR;
489 static svn_error_t *
490 svn_ra_local__get_latest_revnum(svn_ra_session_t *session,
491 svn_revnum_t *latest_revnum,
492 apr_pool_t *pool)
494 svn_ra_local__session_baton_t *sess = session->priv;
495 return svn_fs_youngest_rev(latest_revnum, sess->fs, pool);
498 static svn_error_t *
499 svn_ra_local__get_file_revs(svn_ra_session_t *session,
500 const char *path,
501 svn_revnum_t start,
502 svn_revnum_t end,
503 svn_boolean_t include_merged_revisions,
504 svn_file_rev_handler_t handler,
505 void *handler_baton,
506 apr_pool_t *pool)
508 svn_ra_local__session_baton_t *sess = session->priv;
509 const char *abs_path = svn_path_join(sess->fs_path->data, path, pool);
510 return svn_repos_get_file_revs2(sess->repos, abs_path, start, end,
511 include_merged_revisions, NULL, NULL,
512 handler, handler_baton, pool);
515 static svn_error_t *
516 svn_ra_local__get_dated_revision(svn_ra_session_t *session,
517 svn_revnum_t *revision,
518 apr_time_t tm,
519 apr_pool_t *pool)
521 svn_ra_local__session_baton_t *sess = session->priv;
522 return svn_repos_dated_revision(revision, sess->repos, tm, pool);
526 static svn_error_t *
527 svn_ra_local__change_rev_prop(svn_ra_session_t *session,
528 svn_revnum_t rev,
529 const char *name,
530 const svn_string_t *value,
531 apr_pool_t *pool)
533 svn_ra_local__session_baton_t *sess = session->priv;
534 SVN_ERR(get_username(session, pool));
535 SVN_ERR(svn_repos_fs_change_rev_prop3(sess->repos, rev, sess->username,
536 name, value, TRUE, TRUE, NULL, NULL,
537 pool));
538 return SVN_NO_ERROR;
541 static svn_error_t *
542 svn_ra_local__get_uuid(svn_ra_session_t *session,
543 const char **uuid,
544 apr_pool_t *pool)
546 svn_ra_local__session_baton_t *sess = session->priv;
547 *uuid = sess->uuid;
548 return SVN_NO_ERROR;
551 static svn_error_t *
552 svn_ra_local__get_repos_root(svn_ra_session_t *session,
553 const char **url,
554 apr_pool_t *pool)
556 svn_ra_local__session_baton_t *sess = session->priv;
557 *url = sess->repos_url;
558 return SVN_NO_ERROR;
561 static svn_error_t *
562 svn_ra_local__rev_proplist(svn_ra_session_t *session,
563 svn_revnum_t rev,
564 apr_hash_t **props,
565 apr_pool_t *pool)
567 svn_ra_local__session_baton_t *sess = session->priv;
568 return svn_repos_fs_revision_proplist(props, sess->repos, rev,
569 NULL, NULL, pool);
572 static svn_error_t *
573 svn_ra_local__rev_prop(svn_ra_session_t *session,
574 svn_revnum_t rev,
575 const char *name,
576 svn_string_t **value,
577 apr_pool_t *pool)
579 svn_ra_local__session_baton_t *sess = session->priv;
580 return svn_repos_fs_revision_prop(value, sess->repos, rev, name,
581 NULL, NULL, pool);
584 static svn_error_t *
585 svn_ra_local__get_commit_editor(svn_ra_session_t *session,
586 const svn_delta_editor_t **editor,
587 void **edit_baton,
588 apr_hash_t *revprop_table,
589 svn_commit_callback2_t callback,
590 void *callback_baton,
591 apr_hash_t *lock_tokens,
592 svn_boolean_t keep_locks,
593 apr_pool_t *pool)
595 svn_ra_local__session_baton_t *sess = session->priv;
596 struct deltify_etc_baton *db = apr_palloc(pool, sizeof(*db));
597 apr_hash_index_t *hi;
598 svn_fs_access_t *fs_access;
600 db->fs = sess->fs;
601 db->repos = sess->repos;
602 db->fs_path = sess->fs_path->data;
603 if (! keep_locks)
604 db->lock_tokens = lock_tokens;
605 else
606 db->lock_tokens = NULL;
607 db->pool = pool;
608 db->callback = callback;
609 db->callback_baton = callback_baton;
611 SVN_ERR(get_username(session, pool));
613 /* If there are lock tokens to add, do so. */
614 if (lock_tokens)
616 SVN_ERR(svn_fs_get_access(&fs_access, sess->fs));
618 /* If there is no access context, the filesystem will scream if a
619 lock is needed. */
620 if (fs_access)
622 for (hi = apr_hash_first(pool, lock_tokens); hi;
623 hi = apr_hash_next(hi))
625 void *val;
626 const char *token;
628 apr_hash_this(hi, NULL, NULL, &val);
629 token = val;
630 SVN_ERR(svn_fs_access_add_lock_token(fs_access, token));
635 /* Copy the revprops table so we can add the username. */
636 revprop_table = apr_hash_copy(pool, revprop_table);
637 apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
638 svn_string_create(sess->username, pool));
640 /* Get the repos commit-editor */
641 SVN_ERR(svn_repos_get_commit_editor5
642 (editor, edit_baton, sess->repos, NULL,
643 svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data,
644 revprop_table, deltify_etc, db, NULL, NULL, pool));
646 return SVN_NO_ERROR;
650 static svn_error_t *
651 svn_ra_local__get_mergeinfo(svn_ra_session_t *session,
652 apr_hash_t **mergeinfo,
653 const apr_array_header_t *paths,
654 svn_revnum_t revision,
655 svn_mergeinfo_inheritance_t inherit,
656 svn_boolean_t include_descendants,
657 apr_pool_t *pool)
659 svn_ra_local__session_baton_t *sess = session->priv;
660 apr_hash_t *tmp_mergeinfo;
661 int i;
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,
674 NULL, NULL, pool));
675 *mergeinfo = NULL;
676 if (tmp_mergeinfo != NULL && apr_hash_count(tmp_mergeinfo) > 0)
678 const void *key;
679 apr_ssize_t klen;
680 void *value;
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;
695 info = value;
696 SVN_ERR(svn_mergeinfo_parse(&for_path, info, pool));
697 apr_hash_set(*mergeinfo, path, path_len, for_path);
701 return SVN_NO_ERROR;
705 static svn_error_t *
706 svn_ra_local__do_update(svn_ra_session_t *session,
707 const svn_ra_reporter3_t **reporter,
708 void **report_baton,
709 svn_revnum_t update_revision,
710 const char *update_target,
711 svn_depth_t depth,
712 svn_boolean_t send_copyfrom_args,
713 const svn_delta_editor_t *update_editor,
714 void *update_baton,
715 apr_pool_t *pool)
717 return make_reporter(session,
718 reporter,
719 report_baton,
720 update_revision,
721 update_target,
722 NULL,
723 TRUE,
724 depth,
725 send_copyfrom_args,
726 FALSE,
727 update_editor,
728 update_baton,
729 pool);
733 static svn_error_t *
734 svn_ra_local__do_switch(svn_ra_session_t *session,
735 const svn_ra_reporter3_t **reporter,
736 void **report_baton,
737 svn_revnum_t update_revision,
738 const char *update_target,
739 svn_depth_t depth,
740 const char *switch_url,
741 const svn_delta_editor_t *update_editor,
742 void *update_baton,
743 apr_pool_t *pool)
745 return make_reporter(session,
746 reporter,
747 report_baton,
748 update_revision,
749 update_target,
750 switch_url,
751 TRUE,
752 depth,
753 FALSE, /* ### TODO(sussman): take new arg */
754 TRUE,
755 update_editor,
756 update_baton,
757 pool);
761 static svn_error_t *
762 svn_ra_local__do_status(svn_ra_session_t *session,
763 const svn_ra_reporter3_t **reporter,
764 void **report_baton,
765 const char *status_target,
766 svn_revnum_t revision,
767 svn_depth_t depth,
768 const svn_delta_editor_t *status_editor,
769 void *status_baton,
770 apr_pool_t *pool)
772 return make_reporter(session,
773 reporter,
774 report_baton,
775 revision,
776 status_target,
777 NULL,
778 FALSE,
779 depth,
780 FALSE,
781 FALSE,
782 status_editor,
783 status_baton,
784 pool);
788 static svn_error_t *
789 svn_ra_local__do_diff(svn_ra_session_t *session,
790 const svn_ra_reporter3_t **reporter,
791 void **report_baton,
792 svn_revnum_t update_revision,
793 const char *update_target,
794 svn_depth_t depth,
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,
799 void *update_baton,
800 apr_pool_t *pool)
802 return make_reporter(session,
803 reporter,
804 report_baton,
805 update_revision,
806 update_target,
807 switch_url,
808 text_deltas,
809 depth,
810 FALSE,
811 ignore_ancestry,
812 update_editor,
813 update_baton,
814 pool);
818 struct log_baton
820 svn_ra_local__session_baton_t *sess;
821 svn_log_entry_receiver_t real_cb;
822 void *real_baton;
825 static svn_error_t *
826 cancellation_log_receiver(void *baton,
827 svn_log_entry_t *log_entry,
828 apr_pool_t *pool)
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);
839 static svn_error_t *
840 svn_ra_local__get_log(svn_ra_session_t *session,
841 const apr_array_header_t *paths,
842 svn_revnum_t start,
843 svn_revnum_t end,
844 int limit,
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,
851 apr_pool_t *pool)
853 svn_ra_local__session_baton_t *sess = session->priv;
854 int i;
855 struct log_baton lb;
856 apr_array_header_t *abs_paths =
857 apr_array_make(pool, 0, sizeof(const char *));
859 if (paths)
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;
874 lb.sess = sess;
876 receiver = cancellation_log_receiver;
877 receiver_baton = &lb;
880 return svn_repos_get_logs4(sess->repos,
881 abs_paths,
882 start,
883 end,
884 limit,
885 discover_changed_paths,
886 strict_node_history,
887 include_merged_revisions,
888 revprops,
889 NULL, NULL,
890 receiver,
891 receiver_baton,
892 pool);
896 static svn_error_t *
897 svn_ra_local__do_check_path(svn_ra_session_t *session,
898 const char *path,
899 svn_revnum_t revision,
900 svn_node_kind_t *kind,
901 apr_pool_t *pool)
903 svn_ra_local__session_baton_t *sess = session->priv;
904 svn_fs_root_t *root;
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));
911 return SVN_NO_ERROR;
915 static svn_error_t *
916 svn_ra_local__stat(svn_ra_session_t *session,
917 const char *path,
918 svn_revnum_t revision,
919 svn_dirent_t **dirent,
920 apr_pool_t *pool)
922 svn_ra_local__session_baton_t *sess = session->priv;
923 svn_fs_root_t *root;
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));
932 return SVN_NO_ERROR;
938 static svn_error_t *
939 get_node_props(apr_hash_t **props,
940 svn_ra_local__session_baton_t *sess,
941 svn_fs_root_t *root,
942 const char *path,
943 apr_pool_t *pool)
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));
957 apr_hash_set(*props,
958 SVN_PROP_ENTRY_COMMITTED_REV,
959 APR_HASH_KEY_STRING,
960 svn_string_createf(pool, "%ld", cmt_rev));
961 apr_hash_set(*props,
962 SVN_PROP_ENTRY_COMMITTED_DATE,
963 APR_HASH_KEY_STRING,
964 cmt_date ? svn_string_create(cmt_date, pool) : NULL);
965 apr_hash_set(*props,
966 SVN_PROP_ENTRY_LAST_AUTHOR,
967 APR_HASH_KEY_STRING,
968 cmt_author ? svn_string_create(cmt_author, pool) : NULL);
969 apr_hash_set(*props,
970 SVN_PROP_ENTRY_UUID,
971 APR_HASH_KEY_STRING,
972 svn_string_create(sess->uuid, pool));
974 /* We have no 'wcprops' in ra_local, but might someday. */
976 return SVN_NO_ERROR;
980 /* Getting just one file. */
981 static svn_error_t *
982 svn_ra_local__get_file(svn_ra_session_t *session,
983 const char *path,
984 svn_revnum_t revision,
985 svn_stream_t *stream,
986 svn_revnum_t *fetched_rev,
987 apr_hash_t **props,
988 apr_pool_t *pool)
990 svn_fs_root_t *root;
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;
1004 else
1005 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1007 if (stream)
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,
1021 sess->callbacks
1022 ? sess->callbacks->cancel_func : NULL,
1023 sess->callback_baton,
1024 pool));
1027 /* Handle props if requested. */
1028 if (props)
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,
1041 apr_hash_t **props,
1042 const char *path,
1043 svn_revnum_t revision,
1044 apr_uint32_t dirent_fields,
1045 apr_pool_t *pool)
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;
1063 else
1064 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1066 if (dirents)
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
1072 svn_dirent_t's. */
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))
1077 const void *key;
1078 void *val;
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)
1094 /* node kind */
1095 entry->kind = fs_entry->kind;
1098 if (dirent_fields & SVN_DIRENT_SIZE)
1100 /* size */
1101 if (entry->kind == svn_node_dir)
1102 entry->size = 0;
1103 else
1104 SVN_ERR(svn_fs_file_length(&(entry->size), root,
1105 fullpath, subpool));
1108 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1110 /* has_props? */
1111 SVN_ERR(svn_fs_node_proplist(&prophash, root, fullpath,
1112 subpool));
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),
1122 &datestring,
1123 &(entry->last_author),
1124 root, fullpath, subpool));
1125 if (datestring)
1126 SVN_ERR(svn_time_from_cstring(&(entry->time), datestring,
1127 pool));
1128 if (entry->last_author)
1129 entry->last_author = apr_pstrdup(pool, entry->last_author);
1132 /* Store. */
1133 apr_hash_set(*dirents, entryname, APR_HASH_KEY_STRING, entry);
1135 svn_pool_destroy(subpool);
1138 /* Handle props if requested. */
1139 if (props)
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,
1149 const char *path,
1150 svn_revnum_t peg_revision,
1151 apr_array_header_t *location_revisions,
1152 apr_pool_t *pool)
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,
1158 NULL, NULL, pool);
1162 static svn_error_t *
1163 svn_ra_local__get_location_segments(svn_ra_session_t *session,
1164 const char *path,
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,
1170 apr_pool_t *pool)
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,
1177 NULL, NULL, pool);
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,
1187 void *lock_baton,
1188 apr_pool_t *pool)
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))
1199 svn_lock_t *lock;
1200 const void *key;
1201 const char *path;
1202 void *val;
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);
1209 path = key;
1210 revnum = 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,
1218 iterpool);
1220 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
1221 return err;
1223 if (lock_func)
1224 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
1225 err, iterpool);
1227 svn_error_clear(err);
1229 if (callback_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,
1244 void *lock_baton,
1245 apr_pool_t *pool)
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))
1256 const void *key;
1257 const char *path;
1258 void *val;
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);
1264 path = key;
1265 /* Since we can't store NULL values in a hash, we turn "" to
1266 NULL here. */
1267 if (strcmp(val, "") != 0)
1268 token = val;
1269 else
1270 token = NULL;
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,
1276 iterpool);
1278 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
1279 return err;
1281 if (lock_func)
1282 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
1284 svn_error_clear(err);
1286 if (callback_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,
1299 svn_lock_t **lock,
1300 const char *path,
1301 apr_pool_t *pool)
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,
1312 apr_hash_t **locks,
1313 const char *path,
1314 apr_pool_t *pool)
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,
1322 NULL, NULL, pool);
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,
1332 void *edit_baton,
1333 apr_pool_t *pool)
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),
1339 revision, pool));
1340 SVN_ERR(svn_repos_replay2(root, sess->fs_path->data, low_water_mark,
1341 send_deltas, editor, edit_baton, NULL, NULL,
1342 pool));
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,
1356 void *replay_baton,
1357 apr_pool_t *pool)
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,
1365 svn_boolean_t *has,
1366 const char *capability,
1367 apr_pool_t *pool)
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)
1374 *has = TRUE;
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)
1391 SVN_VERSION_BODY;
1394 /** The ra_vtable **/
1396 static const svn_ra__vtable_t ra_local_vtable =
1398 ra_local_version,
1399 svn_ra_local__get_description,
1400 svn_ra_local__get_schemes,
1401 svn_ra_local__open,
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,
1419 svn_ra_local__stat,
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,
1425 svn_ra_local__lock,
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 **/
1439 svn_error_t *
1440 svn_ra_local__init(const svn_version_t *loader_version,
1441 const svn_ra__vtable_t **vtable,
1442 apr_pool_t *pool)
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 },
1450 { NULL, NULL }
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 "
1459 "ra_local"),
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));
1467 #endif
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"