Mark many merge tests as skip-against-old-server.
[svn.git] / subversion / libsvn_ra_local / ra_plugin.c
blob6d855f4eb861dd6265e04bcfa1693acff5d3c0cc
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"
32 #include "private/svn_mergeinfo_private.h"
34 #define APR_WANT_STRFUNC
35 #include <apr_want.h>
37 #include <assert.h>
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. */
47 static apr_status_t
48 cleanup_access(void *data)
50 svn_error_t *serr;
51 svn_fs_t *fs = data;
53 serr = svn_fs_set_access(fs, NULL);
55 if (serr)
57 apr_status_t apr_err = serr->apr_err;
58 svn_error_clear(serr);
59 return apr_err;
62 return APR_SUCCESS;
66 /* Fetch a username for use with SESS. */
67 static svn_error_t *
68 get_username(svn_ra_session_t *session,
69 apr_pool_t *pool)
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. */
76 if (! sess->username)
78 /* Get a username somehow, so we have some svn:author property to
79 attach to a commit. */
80 if (sess->callbacks->auth_baton)
82 void *creds;
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,
88 pool));
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
93 the first try. */
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));
101 else
102 sess->username = "";
104 else
105 sess->username = "";
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.
112 if (*sess->username)
114 SVN_ERR(svn_fs_create_access(&access_ctx, sess->username,
115 pool));
116 SVN_ERR(svn_fs_set_access(sess->fs, access_ctx));
118 /* Make sure this context is disassociated when the pool gets
119 destroyed. */
120 apr_pool_cleanup_register(pool, sess->fs, cleanup_access,
121 apr_pool_cleanup_null);
124 return SVN_NO_ERROR;
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;
134 void *report_baton;
136 } reporter_baton_t;
139 static void *
140 make_reporter_baton(svn_ra_local__session_baton_t *sess,
141 void *report_baton,
142 apr_pool_t *pool)
144 reporter_baton_t *rbaton = apr_palloc(pool, sizeof(*rbaton));
145 rbaton->sess = sess;
146 rbaton->report_baton = report_baton;
147 return rbaton;
151 static svn_error_t *
152 reporter_set_path(void *reporter_baton,
153 const char *path,
154 svn_revnum_t revision,
155 svn_depth_t depth,
156 svn_boolean_t start_empty,
157 const char *lock_token,
158 apr_pool_t *pool)
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);
166 static svn_error_t *
167 reporter_delete_path(void *reporter_baton,
168 const char *path,
169 apr_pool_t *pool)
171 reporter_baton_t *rbaton = reporter_baton;
172 return svn_repos_delete_path(rbaton->report_baton, path, pool);
176 static svn_error_t *
177 reporter_link_path(void *reporter_baton,
178 const char *path,
179 const char *url,
180 svn_revnum_t revision,
181 svn_depth_t depth,
182 svn_boolean_t start_empty,
183 const char *lock_token,
184 apr_pool_t *pool)
186 reporter_baton_t *rbaton = reporter_baton;
187 const char *fs_path = NULL;
188 const char *repos_url_decoded;
189 int repos_url_len;
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,
196 _("'%s'\n"
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);
205 static svn_error_t *
206 reporter_finish_report(void *reporter_baton,
207 apr_pool_t *pool)
209 reporter_baton_t *rbaton = reporter_baton;
210 return svn_repos_finish_report(rbaton->report_baton, pool);
214 static svn_error_t *
215 reporter_abort_report(void *reporter_baton,
216 apr_pool_t *pool)
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 =
225 reporter_set_path,
226 reporter_delete_path,
227 reporter_link_path,
228 reporter_finish_report,
229 reporter_abort_report
233 static svn_error_t *
234 make_reporter(svn_ra_session_t *session,
235 const svn_ra_reporter3_t **reporter,
236 void **report_baton,
237 svn_revnum_t revision,
238 const char *target,
239 const char *other_url,
240 svn_boolean_t text_deltas,
241 svn_depth_t depth,
242 svn_boolean_t send_copyfrom_args,
243 svn_boolean_t ignore_ancestry,
244 const svn_delta_editor_t *editor,
245 void *edit_baton,
246 apr_pool_t *pool)
248 svn_ra_local__session_baton_t *sess = session->priv;
249 void *rbaton;
250 int repos_url_len;
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. */
260 if (other_url)
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,
271 _("'%s'\n"
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));
283 if (sess->callbacks)
284 SVN_ERR(svn_delta_get_cancellation_editor(sess->callbacks->cancel_func,
285 sess->callback_baton,
286 editor,
287 edit_baton,
288 &editor,
289 &edit_baton,
290 pool));
292 /* Build a reporter baton. */
293 SVN_ERR(svn_repos_begin_report2(&rbaton,
294 revision,
295 sess->repos,
296 sess->fs_path->data,
297 target,
298 other_fs_path,
299 text_deltas,
300 depth,
301 ignore_ancestry,
302 send_copyfrom_args,
303 editor,
304 edit_baton,
305 NULL,
306 NULL,
307 pool));
309 /* Wrap the report baton given us by the repos layer with our own
310 reporter baton. */
311 *report_baton = make_reporter_baton(sess, rbaton, pool);
313 return SVN_NO_ERROR;
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 *'. */
336 static svn_error_t *
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. */
351 if (db->lock_tokens)
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;
358 void *val;
359 const char *abs_path, *token;
361 svn_pool_clear(iterpool);
362 apr_hash_this(hi, &rel_path, NULL, &val);
363 token = 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
367 ignored. */
368 svn_error_clear(svn_repos_fs_unlock(db->repos, abs_path, token,
369 FALSE, iterpool));
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
376 deltification. */
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. */
381 if (err1)
383 svn_error_clear(err2);
384 return err1;
387 return err2;
390 /*----------------------------------------------------------------*/
392 /*** The RA vtable routines ***/
394 #define RA_LOCAL_DESCRIPTION \
395 N_("Module for accessing a repository on local disk.")
397 static const char *
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 };
408 return schemes;
411 static svn_error_t *
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,
416 apr_hash_t *config,
417 apr_pool_t *pool)
419 svn_ra_local__session_baton_t *sess;
420 const char *fs_path;
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
429 repository. */
430 SVN_ERR_W(svn_ra_local__split_URL(&(sess->repos),
431 &(sess->repos_url),
432 &fs_path,
433 repos_URL,
434 session->pool),
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
439 convenience. */
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;
449 return SVN_NO_ERROR;
452 static svn_error_t *
453 svn_ra_local__reparent(svn_ra_session_t *session,
454 const char *url,
455 apr_pool_t *pool)
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);
464 if (! relpath)
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);
475 return SVN_NO_ERROR;
478 static svn_error_t *
479 svn_ra_local__get_session_url(svn_ra_session_t *session,
480 const char **url,
481 apr_pool_t *pool)
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),
486 pool);
487 return SVN_NO_ERROR;
490 static svn_error_t *
491 svn_ra_local__get_latest_revnum(svn_ra_session_t *session,
492 svn_revnum_t *latest_revnum,
493 apr_pool_t *pool)
495 svn_ra_local__session_baton_t *sess = session->priv;
496 return svn_fs_youngest_rev(latest_revnum, sess->fs, pool);
499 static svn_error_t *
500 svn_ra_local__get_file_revs(svn_ra_session_t *session,
501 const char *path,
502 svn_revnum_t start,
503 svn_revnum_t end,
504 svn_boolean_t include_merged_revisions,
505 svn_file_rev_handler_t handler,
506 void *handler_baton,
507 apr_pool_t *pool)
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);
516 static svn_error_t *
517 svn_ra_local__get_dated_revision(svn_ra_session_t *session,
518 svn_revnum_t *revision,
519 apr_time_t tm,
520 apr_pool_t *pool)
522 svn_ra_local__session_baton_t *sess = session->priv;
523 return svn_repos_dated_revision(revision, sess->repos, tm, pool);
527 static svn_error_t *
528 svn_ra_local__change_rev_prop(svn_ra_session_t *session,
529 svn_revnum_t rev,
530 const char *name,
531 const svn_string_t *value,
532 apr_pool_t *pool)
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,
538 pool));
539 return SVN_NO_ERROR;
542 static svn_error_t *
543 svn_ra_local__get_uuid(svn_ra_session_t *session,
544 const char **uuid,
545 apr_pool_t *pool)
547 svn_ra_local__session_baton_t *sess = session->priv;
548 *uuid = sess->uuid;
549 return SVN_NO_ERROR;
552 static svn_error_t *
553 svn_ra_local__get_repos_root(svn_ra_session_t *session,
554 const char **url,
555 apr_pool_t *pool)
557 svn_ra_local__session_baton_t *sess = session->priv;
558 *url = sess->repos_url;
559 return SVN_NO_ERROR;
562 static svn_error_t *
563 svn_ra_local__rev_proplist(svn_ra_session_t *session,
564 svn_revnum_t rev,
565 apr_hash_t **props,
566 apr_pool_t *pool)
568 svn_ra_local__session_baton_t *sess = session->priv;
569 return svn_repos_fs_revision_proplist(props, sess->repos, rev,
570 NULL, NULL, pool);
573 static svn_error_t *
574 svn_ra_local__rev_prop(svn_ra_session_t *session,
575 svn_revnum_t rev,
576 const char *name,
577 svn_string_t **value,
578 apr_pool_t *pool)
580 svn_ra_local__session_baton_t *sess = session->priv;
581 return svn_repos_fs_revision_prop(value, sess->repos, rev, name,
582 NULL, NULL, pool);
585 static svn_error_t *
586 svn_ra_local__get_commit_editor(svn_ra_session_t *session,
587 const svn_delta_editor_t **editor,
588 void **edit_baton,
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,
594 apr_pool_t *pool)
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;
601 db->fs = sess->fs;
602 db->repos = sess->repos;
603 db->fs_path = sess->fs_path->data;
604 if (! keep_locks)
605 db->lock_tokens = lock_tokens;
606 else
607 db->lock_tokens = NULL;
608 db->pool = pool;
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. */
615 if (lock_tokens)
617 SVN_ERR(svn_fs_get_access(&fs_access, sess->fs));
619 /* If there is no access context, the filesystem will scream if a
620 lock is needed. */
621 if (fs_access)
623 for (hi = apr_hash_first(pool, lock_tokens); hi;
624 hi = apr_hash_next(hi))
626 void *val;
627 const char *token;
629 apr_hash_this(hi, NULL, NULL, &val);
630 token = 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));
647 return SVN_NO_ERROR;
651 static svn_error_t *
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,
658 apr_pool_t *pool)
660 svn_ra_local__session_baton_t *sess = session->priv;
661 svn_mergeinfo_catalog_t tmp_catalog;
662 int i;
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,
675 NULL, NULL, pool));
676 if (apr_hash_count(tmp_catalog) > 0)
677 SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog,
678 tmp_catalog,
679 sess->fs_path->data,
680 pool));
681 else
682 *catalog = NULL;
684 return SVN_NO_ERROR;
688 static svn_error_t *
689 svn_ra_local__do_update(svn_ra_session_t *session,
690 const svn_ra_reporter3_t **reporter,
691 void **report_baton,
692 svn_revnum_t update_revision,
693 const char *update_target,
694 svn_depth_t depth,
695 svn_boolean_t send_copyfrom_args,
696 const svn_delta_editor_t *update_editor,
697 void *update_baton,
698 apr_pool_t *pool)
700 return make_reporter(session,
701 reporter,
702 report_baton,
703 update_revision,
704 update_target,
705 NULL,
706 TRUE,
707 depth,
708 send_copyfrom_args,
709 FALSE,
710 update_editor,
711 update_baton,
712 pool);
716 static svn_error_t *
717 svn_ra_local__do_switch(svn_ra_session_t *session,
718 const svn_ra_reporter3_t **reporter,
719 void **report_baton,
720 svn_revnum_t update_revision,
721 const char *update_target,
722 svn_depth_t depth,
723 const char *switch_url,
724 const svn_delta_editor_t *update_editor,
725 void *update_baton,
726 apr_pool_t *pool)
728 return make_reporter(session,
729 reporter,
730 report_baton,
731 update_revision,
732 update_target,
733 switch_url,
734 TRUE,
735 depth,
736 FALSE, /* ### TODO(sussman): take new arg */
737 TRUE,
738 update_editor,
739 update_baton,
740 pool);
744 static svn_error_t *
745 svn_ra_local__do_status(svn_ra_session_t *session,
746 const svn_ra_reporter3_t **reporter,
747 void **report_baton,
748 const char *status_target,
749 svn_revnum_t revision,
750 svn_depth_t depth,
751 const svn_delta_editor_t *status_editor,
752 void *status_baton,
753 apr_pool_t *pool)
755 return make_reporter(session,
756 reporter,
757 report_baton,
758 revision,
759 status_target,
760 NULL,
761 FALSE,
762 depth,
763 FALSE,
764 FALSE,
765 status_editor,
766 status_baton,
767 pool);
771 static svn_error_t *
772 svn_ra_local__do_diff(svn_ra_session_t *session,
773 const svn_ra_reporter3_t **reporter,
774 void **report_baton,
775 svn_revnum_t update_revision,
776 const char *update_target,
777 svn_depth_t depth,
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,
782 void *update_baton,
783 apr_pool_t *pool)
785 return make_reporter(session,
786 reporter,
787 report_baton,
788 update_revision,
789 update_target,
790 switch_url,
791 text_deltas,
792 depth,
793 FALSE,
794 ignore_ancestry,
795 update_editor,
796 update_baton,
797 pool);
801 struct log_baton
803 svn_ra_local__session_baton_t *sess;
804 svn_log_entry_receiver_t real_cb;
805 void *real_baton;
808 static svn_error_t *
809 cancellation_log_receiver(void *baton,
810 svn_log_entry_t *log_entry,
811 apr_pool_t *pool)
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);
822 static svn_error_t *
823 svn_ra_local__get_log(svn_ra_session_t *session,
824 const apr_array_header_t *paths,
825 svn_revnum_t start,
826 svn_revnum_t end,
827 int limit,
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,
834 apr_pool_t *pool)
836 svn_ra_local__session_baton_t *sess = session->priv;
837 int i;
838 struct log_baton lb;
839 apr_array_header_t *abs_paths =
840 apr_array_make(pool, 0, sizeof(const char *));
842 if (paths)
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;
857 lb.sess = sess;
859 receiver = cancellation_log_receiver;
860 receiver_baton = &lb;
863 return svn_repos_get_logs4(sess->repos,
864 abs_paths,
865 start,
866 end,
867 limit,
868 discover_changed_paths,
869 strict_node_history,
870 include_merged_revisions,
871 revprops,
872 NULL, NULL,
873 receiver,
874 receiver_baton,
875 pool);
879 static svn_error_t *
880 svn_ra_local__do_check_path(svn_ra_session_t *session,
881 const char *path,
882 svn_revnum_t revision,
883 svn_node_kind_t *kind,
884 apr_pool_t *pool)
886 svn_ra_local__session_baton_t *sess = session->priv;
887 svn_fs_root_t *root;
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));
894 return SVN_NO_ERROR;
898 static svn_error_t *
899 svn_ra_local__stat(svn_ra_session_t *session,
900 const char *path,
901 svn_revnum_t revision,
902 svn_dirent_t **dirent,
903 apr_pool_t *pool)
905 svn_ra_local__session_baton_t *sess = session->priv;
906 svn_fs_root_t *root;
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));
915 return SVN_NO_ERROR;
921 static svn_error_t *
922 get_node_props(apr_hash_t **props,
923 svn_ra_local__session_baton_t *sess,
924 svn_fs_root_t *root,
925 const char *path,
926 apr_pool_t *pool)
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));
940 apr_hash_set(*props,
941 SVN_PROP_ENTRY_COMMITTED_REV,
942 APR_HASH_KEY_STRING,
943 svn_string_createf(pool, "%ld", cmt_rev));
944 apr_hash_set(*props,
945 SVN_PROP_ENTRY_COMMITTED_DATE,
946 APR_HASH_KEY_STRING,
947 cmt_date ? svn_string_create(cmt_date, pool) : NULL);
948 apr_hash_set(*props,
949 SVN_PROP_ENTRY_LAST_AUTHOR,
950 APR_HASH_KEY_STRING,
951 cmt_author ? svn_string_create(cmt_author, pool) : NULL);
952 apr_hash_set(*props,
953 SVN_PROP_ENTRY_UUID,
954 APR_HASH_KEY_STRING,
955 svn_string_create(sess->uuid, pool));
957 /* We have no 'wcprops' in ra_local, but might someday. */
959 return SVN_NO_ERROR;
963 /* Getting just one file. */
964 static svn_error_t *
965 svn_ra_local__get_file(svn_ra_session_t *session,
966 const char *path,
967 svn_revnum_t revision,
968 svn_stream_t *stream,
969 svn_revnum_t *fetched_rev,
970 apr_hash_t **props,
971 apr_pool_t *pool)
973 svn_fs_root_t *root;
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;
987 else
988 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
990 if (stream)
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,
1004 sess->callbacks
1005 ? sess->callbacks->cancel_func : NULL,
1006 sess->callback_baton,
1007 pool));
1010 /* Handle props if requested. */
1011 if (props)
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,
1024 apr_hash_t **props,
1025 const char *path,
1026 svn_revnum_t revision,
1027 apr_uint32_t dirent_fields,
1028 apr_pool_t *pool)
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;
1046 else
1047 SVN_ERR(svn_fs_revision_root(&root, sess->fs, revision, pool));
1049 if (dirents)
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
1055 svn_dirent_t's. */
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))
1060 const void *key;
1061 void *val;
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)
1077 /* node kind */
1078 entry->kind = fs_entry->kind;
1081 if (dirent_fields & SVN_DIRENT_SIZE)
1083 /* size */
1084 if (entry->kind == svn_node_dir)
1085 entry->size = 0;
1086 else
1087 SVN_ERR(svn_fs_file_length(&(entry->size), root,
1088 fullpath, subpool));
1091 if (dirent_fields & SVN_DIRENT_HAS_PROPS)
1093 /* has_props? */
1094 SVN_ERR(svn_fs_node_proplist(&prophash, root, fullpath,
1095 subpool));
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),
1105 &datestring,
1106 &(entry->last_author),
1107 root, fullpath, subpool));
1108 if (datestring)
1109 SVN_ERR(svn_time_from_cstring(&(entry->time), datestring,
1110 pool));
1111 if (entry->last_author)
1112 entry->last_author = apr_pstrdup(pool, entry->last_author);
1115 /* Store. */
1116 apr_hash_set(*dirents, entryname, APR_HASH_KEY_STRING, entry);
1118 svn_pool_destroy(subpool);
1121 /* Handle props if requested. */
1122 if (props)
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,
1132 const char *path,
1133 svn_revnum_t peg_revision,
1134 apr_array_header_t *location_revisions,
1135 apr_pool_t *pool)
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,
1141 NULL, NULL, pool);
1145 static svn_error_t *
1146 svn_ra_local__get_location_segments(svn_ra_session_t *session,
1147 const char *path,
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,
1153 apr_pool_t *pool)
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,
1160 NULL, NULL, pool);
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,
1170 void *lock_baton,
1171 apr_pool_t *pool)
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))
1182 svn_lock_t *lock;
1183 const void *key;
1184 const char *path;
1185 void *val;
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);
1192 path = key;
1193 revnum = 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,
1201 iterpool);
1203 if (err && !SVN_ERR_IS_LOCK_ERROR(err))
1204 return err;
1206 if (lock_func)
1207 callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
1208 err, iterpool);
1210 svn_error_clear(err);
1212 if (callback_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,
1227 void *lock_baton,
1228 apr_pool_t *pool)
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))
1239 const void *key;
1240 const char *path;
1241 void *val;
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);
1247 path = key;
1248 /* Since we can't store NULL values in a hash, we turn "" to
1249 NULL here. */
1250 if (strcmp(val, "") != 0)
1251 token = val;
1252 else
1253 token = NULL;
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,
1259 iterpool);
1261 if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
1262 return err;
1264 if (lock_func)
1265 callback_err = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
1267 svn_error_clear(err);
1269 if (callback_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,
1282 svn_lock_t **lock,
1283 const char *path,
1284 apr_pool_t *pool)
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,
1295 apr_hash_t **locks,
1296 const char *path,
1297 apr_pool_t *pool)
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,
1305 NULL, NULL, pool);
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,
1315 void *edit_baton,
1316 apr_pool_t *pool)
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),
1322 revision, pool));
1323 SVN_ERR(svn_repos_replay2(root, sess->fs_path->data, low_water_mark,
1324 send_deltas, editor, edit_baton, NULL, NULL,
1325 pool));
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,
1339 void *replay_baton,
1340 apr_pool_t *pool)
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,
1348 svn_boolean_t *has,
1349 const char *capability,
1350 apr_pool_t *pool)
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)
1359 *has = TRUE;
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,
1367 pool));
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)
1384 SVN_VERSION_BODY;
1387 /** The ra_vtable **/
1389 static const svn_ra__vtable_t ra_local_vtable =
1391 ra_local_version,
1392 svn_ra_local__get_description,
1393 svn_ra_local__get_schemes,
1394 svn_ra_local__open,
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,
1412 svn_ra_local__stat,
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,
1418 svn_ra_local__lock,
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 **/
1432 svn_error_t *
1433 svn_ra_local__init(const svn_version_t *loader_version,
1434 const svn_ra__vtable_t **vtable,
1435 apr_pool_t *pool)
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 },
1443 { NULL, NULL }
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 "
1452 "ra_local"),
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));
1460 #endif
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"