blob: 26df01b2dbbb223fd3915f0170d7b9a7962dad52 [file] [log] [blame]
/*
* ra_plugin.c : the main RA module for local repository access
*
* ====================================================================
* Copyright (c) 2000-2002 CollabNet. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://subversion.tigris.org/license-1.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
*
* This software consists of voluntary contributions made by many
* individuals. For exact contribution history, see the revision
* history and logs, available at http://subversion.tigris.org/.
* ====================================================================
*/
#include "ra_local.h"
#include "svn_ra.h"
#include "svn_fs.h"
#include "svn_delta.h"
#include "svn_repos.h"
#include "svn_pools.h"
#define APR_WANT_STRFUNC
#include <apr_want.h>
/*----------------------------------------------------------------*/
/** Callbacks **/
/* A device to record the targets of commits, and ensuring that proper
commit closure happens on them (namely, revision setting and wc
property setting). This is passed to the `commit hook' routine by
svn_fs_get_editor. ( ) */
struct commit_cleanup_baton
{
/* Allocation for this baton, as well as all committed_targets */
apr_pool_t *pool;
/* The filesystem that we just committed to. */
svn_fs_t *fs;
/* If non-null, store the new revision here. */
svn_revnum_t *new_rev;
/* If non-null, store the repository date of the commit here. */
const char **committed_date;
/* If non-null, store the repository author of the commit here. */
const char **committed_author;
};
/* An instance of svn_ra_local__commit_hook_t.
*
* BATON is `struct commit_cleanup_baton *'. Loop over all committed
* target paths in BATON->committed_targets, invoking
* BATON->close_func() on each one with NEW_REV.
*
* Set *(BATON->new_rev) to NEW_REV, and copy COMMITTED_DATE and
* COMMITTED_AUTHOR to *(BATON->committed_date) and
* *(BATON->committed_date) respectively, allocating the new storage
* in BATON->pool.
*
* This routine is originally passed as a "hook" to the filesystem
* commit editor. When we get here, the track-editor has already
* stored committed targets inside the baton.
*/
static svn_error_t *
cleanup_commit (svn_revnum_t new_rev,
const char *committed_date,
const char *committed_author,
void *baton)
{
struct commit_cleanup_baton *cb = baton;
/* Store the new revision information in the baton. */
if (cb->new_rev)
*(cb->new_rev) = new_rev;
if (cb->committed_date)
*(cb->committed_date) = committed_date
? apr_pstrdup (cb->pool, committed_date)
: NULL;
if (cb->committed_author)
*(cb->committed_author) = committed_author
? apr_pstrdup (cb->pool, committed_author)
: NULL;
return SVN_NO_ERROR;
}
/* The reporter vtable needed by do_update() */
static const svn_ra_reporter_t ra_local_reporter =
{
svn_repos_set_path,
svn_repos_delete_path,
svn_repos_finish_report,
svn_repos_abort_report
};
/*----------------------------------------------------------------*/
/** The RA plugin routines **/
static svn_error_t *
svn_ra_local__open (void **session_baton,
svn_stringbuf_t *repos_URL,
const svn_ra_callbacks_t *callbacks,
void *callback_baton,
apr_pool_t *pool)
{
svn_ra_local__session_baton_t *session;
void *a, *auth_baton;
svn_ra_username_authenticator_t *authenticator;
/* Allocate and stash the session_baton args we have already. */
session = apr_pcalloc (pool, sizeof(*session));
session->pool = pool;
session->repository_URL = repos_URL;
/* Get the username by "pulling" it from the callbacks. */
SVN_ERR (callbacks->get_authenticator (&a,
&auth_baton,
svn_ra_auth_username,
callback_baton, pool));
authenticator = (svn_ra_username_authenticator_t *) a;
SVN_ERR (authenticator->get_username (&(session->username),
auth_baton, FALSE, pool));
/* Look through the URL, figure out which part points to the
repository, and which part is the path *within* the
repository. */
SVN_ERR_W (svn_ra_local__split_URL (&(session->repos_path),
&(session->fs_path),
session->repository_URL,
session->pool),
"Unable to open an ra_local session to URL");
/* Open the filesystem at located at environment `repos_path' */
SVN_ERR (svn_repos_open (&(session->repos),
session->repos_path->data,
session->pool));
/* Cache the filesystem object from the repos here for
convenience. */
session->fs = svn_repos_fs (session->repos);
/* Stuff the callbacks/baton here. */
session->callbacks = callbacks;
session->callback_baton = callback_baton;
/* ### ra_local is not going to bother to store the username in the
working copy. This means that the username will always be
fetched from getuid() or from a commandline arg, which is fine.
The reason for this decision is that in ra_local, authentication
and authorization are blurred; we'd have to use authorization as
a *test* to decide if the authentication was valid. And we
certainly don't want to track every subsequent svn_fs_* call's
error, just to decide if it's legitmate to store a username! */
*session_baton = session;
return SVN_NO_ERROR;
}
static svn_error_t *
svn_ra_local__close (void *session_baton)
{
svn_ra_local__session_baton_t *baton =
(svn_ra_local__session_baton_t *) session_baton;
/* Close the repository, which will free any memory used by it. */
SVN_ERR (svn_repos_close (baton->repos));
/* NULL out the FS cache so no one is tempted to use it again. */
baton->fs = NULL;
return SVN_NO_ERROR;
}
static svn_error_t *
svn_ra_local__get_latest_revnum (void *session_baton,
svn_revnum_t *latest_revnum)
{
svn_ra_local__session_baton_t *baton =
(svn_ra_local__session_baton_t *) session_baton;
SVN_ERR (svn_fs_youngest_rev (latest_revnum, baton->fs, baton->pool));
return SVN_NO_ERROR;
}
static svn_error_t *
svn_ra_local__get_dated_revision (void *session_baton,
svn_revnum_t *revision,
apr_time_t tm)
{
svn_ra_local__session_baton_t *baton =
(svn_ra_local__session_baton_t *) session_baton;
SVN_ERR (svn_repos_dated_revision (revision, baton->repos, tm, baton->pool));
return SVN_NO_ERROR;
}
static svn_error_t *
svn_ra_local__get_commit_editor (void *session_baton,
const svn_delta_editor_t **editor,
void **edit_baton,
svn_revnum_t *new_rev,
const char **committed_date,
const char **committed_author,
svn_stringbuf_t *log_msg)
{
svn_ra_local__session_baton_t *sess_baton = session_baton;
struct commit_cleanup_baton *cb
= apr_pcalloc (sess_baton->pool, sizeof (*cb));
/* Construct a commit cleanup baton */
cb->pool = sess_baton->pool;
cb->fs = sess_baton->fs;
cb->new_rev = new_rev;
cb->committed_date = committed_date;
cb->committed_author = committed_author;
/* Get the repos commit-editor */
SVN_ERR (svn_ra_local__get_editor (editor, edit_baton, sess_baton,
log_msg, cleanup_commit, cb,
sess_baton->pool));
return SVN_NO_ERROR;
}
static svn_error_t *
svn_ra_local__do_checkout (void *session_baton,
svn_revnum_t revision,
svn_boolean_t recurse,
const svn_delta_edit_fns_t *editor,
void *edit_baton)
{
svn_revnum_t revnum_to_fetch;
svn_ra_local__session_baton_t *sbaton =
(svn_ra_local__session_baton_t *) session_baton;
if (! SVN_IS_VALID_REVNUM(revision))
SVN_ERR (svn_ra_local__get_latest_revnum (sbaton, &revnum_to_fetch));
else
revnum_to_fetch = revision;
SVN_ERR (svn_ra_local__checkout (sbaton->fs,
revnum_to_fetch,
recurse,
sbaton->repository_URL,
sbaton->fs_path,
editor, edit_baton, sbaton->pool));
return SVN_NO_ERROR;
}
static svn_error_t *
svn_ra_local__do_update (void *session_baton,
const svn_ra_reporter_t **reporter,
void **report_baton,
svn_revnum_t update_revision,
svn_stringbuf_t *update_target,
svn_boolean_t recurse,
const svn_delta_edit_fns_t *update_editor,
void *update_baton)
{
svn_delta_edit_fns_t *pipe_editor;
struct svn_pipe_edit_baton *pipe_edit_baton;
svn_revnum_t revnum_to_update_to;
svn_stringbuf_t *switch_path;
svn_ra_local__session_baton_t *sbaton = session_baton;
/* ### fix the update_target param at some point */
const char *target;
target = update_target ? update_target->data : NULL;
/* We want dir_delta to run on -identical- fs paths. */
switch_path =
svn_stringbuf_create_from_string (sbaton->fs_path, sbaton->pool);
if (target)
svn_path_add_component_nts (switch_path, target);
if (! SVN_IS_VALID_REVNUM(update_revision))
SVN_ERR (svn_ra_local__get_latest_revnum (sbaton, &revnum_to_update_to));
else
revnum_to_update_to = update_revision;
/* Wrap UPDATE_EDITOR with a custom "pipe" editor that pushes extra
'entry' properties into the stream, whenever {open_root,
open_file, open_dir, add_file, add_dir} are called. */
SVN_ERR (svn_ra_local__get_update_pipe_editor
(&pipe_editor,
&pipe_edit_baton,
update_editor,
update_baton,
sbaton,
svn_stringbuf_create_from_string (sbaton->fs_path, sbaton->pool),
sbaton->pool));
/* Pass back our reporter */
*reporter = &ra_local_reporter;
/* Build a reporter baton. */
return svn_repos_begin_report (report_baton,
revnum_to_update_to,
sbaton->username,
sbaton->repos,
sbaton->fs_path->data,
target,
switch_path->data,
TRUE, /* send text-deltas */
recurse,
pipe_editor, pipe_edit_baton,
sbaton->pool);
}
static svn_error_t *
svn_ra_local__do_switch (void *session_baton,
const svn_ra_reporter_t **reporter,
void **report_baton,
svn_revnum_t update_revision,
svn_stringbuf_t *update_target,
svn_boolean_t recurse,
svn_stringbuf_t *switch_url,
const svn_delta_edit_fns_t *update_editor,
void *update_baton)
{
svn_delta_edit_fns_t *pipe_editor;
struct svn_pipe_edit_baton *pipe_edit_baton;
svn_revnum_t revnum_to_update_to;
const svn_string_t *switch_repos_path, *switch_fs_path;
svn_ra_local__session_baton_t *sbaton = session_baton;
svn_stringbuf_t *pipe_anchor;
/* ### fix the update_target param at some point */
const char *target;
target = update_target ? update_target->data : NULL;
/* Pull the relevant fs-path portion out of switch_url. */
SVN_ERR_W (svn_ra_local__split_URL (&switch_repos_path, &switch_fs_path,
switch_url, sbaton->pool),
"The 'switch' URL is invalid.");
/* Sanity check: the switch_url better be in the same repository as
the original session url! */
if (! svn_string_compare (sbaton->repos_path, switch_repos_path))
return svn_error_createf (SVN_ERR_RA_ILLEGAL_URL, 0, NULL, sbaton->pool,
"'%s'\n"
"is not the same repository as\n"
"'%s'", switch_repos_path->data,
sbaton->repos_path->data);
if (! SVN_IS_VALID_REVNUM(update_revision))
SVN_ERR (svn_ra_local__get_latest_revnum (sbaton, &revnum_to_update_to));
else
revnum_to_update_to = update_revision;
/* Make sure the pipe editor is anchored in the same way as the
update editor. */
/* Assume that we should anchor the pipe editor on the switch path
directly. This is normal when switching a directory, since
tgt-anchor is the directory itself, and tgt-target is NULL. */
pipe_anchor = svn_stringbuf_create_from_string (switch_fs_path,
sbaton->pool);
if (update_target)
{
/* If the target is defined, then we must be switching a file.
The pipe editor needs to be anchored on the target's parent
directory. But here's the catch: the pipe-editor is going to
receive open_file(src-basename), because there's no
delete/add happening. Somehow the pipe-editor needs to fetch
the CR from *tgt*-basename. So we stash it in the
pipe-editor's own baton. ### do this.
*/
svn_path_remove_component (pipe_anchor);
}
/* Wrap UPDATE_EDITOR with a custom "pipe" editor that pushes extra
'entry' properties into the stream, whenever {open_root,
open_file, open_dir, add_file, add_dir} are called. */
SVN_ERR (svn_ra_local__get_update_pipe_editor
(&pipe_editor,
&pipe_edit_baton,
update_editor,
update_baton,
sbaton,
pipe_anchor,
sbaton->pool));
/* Pass back our reporter */
*reporter = &ra_local_reporter;
/* Build a reporter baton. */
return svn_repos_begin_report (report_baton,
revnum_to_update_to,
sbaton->username,
sbaton->repos,
sbaton->fs_path->data,
target,
switch_fs_path->data,
TRUE, /* we want text-deltas */
recurse,
pipe_editor, pipe_edit_baton,
sbaton->pool);
}
static svn_error_t *
svn_ra_local__do_status (void *session_baton,
const svn_ra_reporter_t **reporter,
void **report_baton,
svn_stringbuf_t *status_target,
svn_boolean_t recurse,
const svn_delta_edit_fns_t *status_editor,
void *status_baton)
{
svn_revnum_t revnum_to_update_to;
svn_stringbuf_t *switch_path;
svn_ra_local__session_baton_t *sbaton = session_baton;
/* ### fix the status_target param at some point */
const char *target;
target = status_target ? status_target->data : NULL;
/* We want dir_delta to run on -identical- fs paths. */
switch_path =
svn_stringbuf_create_from_string (sbaton->fs_path, sbaton->pool);
if (target)
svn_path_add_component_nts (switch_path, target);
SVN_ERR (svn_ra_local__get_latest_revnum (sbaton, &revnum_to_update_to));
/* Pass back our reporter */
*reporter = &ra_local_reporter;
/* Build a reporter baton. */
return svn_repos_begin_report (report_baton,
revnum_to_update_to,
sbaton->username,
sbaton->repos,
sbaton->fs_path->data,
target,
switch_path->data,
FALSE, /* don't send text-deltas */
recurse,
status_editor, status_baton,
sbaton->pool);
}
static svn_error_t *
svn_ra_local__get_log (void *session_baton,
const apr_array_header_t *paths,
svn_revnum_t start,
svn_revnum_t end,
svn_boolean_t discover_changed_paths,
svn_log_message_receiver_t receiver,
void *receiver_baton)
{
svn_ra_local__session_baton_t *sbaton = session_baton;
apr_array_header_t *abs_paths
= apr_array_make (sbaton->pool, paths->nelts, sizeof (svn_stringbuf_t *));
int i;
for (i = 0; i < paths->nelts; i++)
{
svn_stringbuf_t *relative_path
= (((svn_stringbuf_t **)(paths)->elts)[i]);
svn_stringbuf_t *abs_path
= svn_stringbuf_create_from_string (sbaton->fs_path, sbaton->pool);
/* ### Not sure if this counts as a workaround or not. The
session baton uses the empty string to mean root, and not
sure that should change. However, it would be better to use
a path library function to add this separator -- hardcoding
it is totally bogus. See issue #559, though it may be only
tangentially related. */
if (abs_path->len == 0)
svn_stringbuf_appendcstr (abs_path, "/");
svn_path_add_component (abs_path, relative_path);
(*((svn_stringbuf_t **)(apr_array_push (abs_paths)))) = abs_path;
}
return svn_repos_get_logs (sbaton->repos,
abs_paths,
start,
end,
discover_changed_paths,
receiver,
receiver_baton,
sbaton->pool);
}
static svn_error_t *
svn_ra_local__do_check_path (svn_node_kind_t *kind,
void *session_baton,
const char *path,
svn_revnum_t revision)
{
svn_ra_local__session_baton_t *sbaton = session_baton;
svn_fs_root_t *root;
svn_stringbuf_t *abs_path
= svn_stringbuf_create_from_string (sbaton->fs_path, sbaton->pool);
/* ### Not sure if this counts as a workaround or not. The
session baton uses the empty string to mean root, and not
sure that should change. However, it would be better to use
a path library function to add this separator -- hardcoding
it is totally bogus. See issue #559, though it may be only
tangentially related. */
if (abs_path->len == 0)
svn_stringbuf_appendcstr (abs_path, "/");
/* If we were given a relative path to append, append it. */
if (path)
svn_path_add_component_nts (abs_path, path);
if (! SVN_IS_VALID_REVNUM (revision))
SVN_ERR (svn_fs_youngest_rev (&revision, sbaton->fs, sbaton->pool));
SVN_ERR (svn_fs_revision_root (&root, sbaton->fs, revision, sbaton->pool));
*kind = svn_fs_check_path (root, abs_path->data, sbaton->pool);
return SVN_NO_ERROR;
}
/* Getting just one file. */
static svn_error_t *
svn_ra_local__get_file (void *session_baton,
const char *path,
svn_revnum_t revision,
svn_stream_t *stream,
svn_revnum_t *fetched_rev,
apr_hash_t **props)
{
svn_fs_root_t *root;
svn_stream_t *contents;
svn_revnum_t youngest_rev;
char buf[SVN_STREAM_CHUNK_SIZE];
apr_size_t rlen, wlen;
svn_ra_local__session_baton_t *sbaton = session_baton;
svn_stringbuf_t *abs_path
= svn_stringbuf_create_from_string (sbaton->fs_path, sbaton->pool);
/* ### Not sure if this counts as a workaround or not. The
session baton uses the empty string to mean root, and not
sure that should change. However, it would be better to use
a path library function to add this separator -- hardcoding
it is totally bogus. See issue #559, though it may be only
tangentially related. */
if (abs_path->len == 0)
svn_stringbuf_appendcstr (abs_path, "/");
/* If we were given a relative path to append, append it. */
if (path)
svn_path_add_component_nts (abs_path, path);
/* Open the revision's root. */
if (! SVN_IS_VALID_REVNUM (revision))
{
SVN_ERR (svn_fs_youngest_rev (&youngest_rev, sbaton->fs, sbaton->pool));
SVN_ERR (svn_fs_revision_root (&root, sbaton->fs,
youngest_rev, sbaton->pool));
if (fetched_rev != NULL)
*fetched_rev = youngest_rev;
}
else
SVN_ERR (svn_fs_revision_root (&root, sbaton->fs,
revision, sbaton->pool));
/* Get a stream representing the file's contents. */
SVN_ERR (svn_fs_file_contents (&contents, root,
abs_path->data, sbaton->pool));
/* Now push data from the fs stream back at the caller's stream. */
while (1)
{
/* read a maximum number of bytes from the file, please. */
rlen = SVN_STREAM_CHUNK_SIZE;
SVN_ERR (svn_stream_read (contents, buf, &rlen));
/* write however many bytes you read, please. */
wlen = rlen;
SVN_ERR (svn_stream_write (stream, buf, &wlen));
if (wlen != rlen)
{
/* Uh oh, didn't write as many bytes as we read, and no
error was returned. According to the docstring, this
should never happen. */
return
svn_error_create (SVN_ERR_UNEXPECTED_EOF, 0, NULL,
sbaton->pool, "Error writing to svn_stream.");
}
if (rlen != SVN_STREAM_CHUNK_SIZE)
{
/* svn_stream_read didn't throw an error, yet it didn't read
all the bytes requested. According to the docstring,
this means a plain old EOF happened, so we're done. */
break;
}
}
if (props)
{
svn_revnum_t committed_rev;
svn_string_t *committed_date, *last_author;
svn_stringbuf_t *value;
svn_string_t *abs_path_s;
char *revision_str = NULL;
/* Create a hash with props attached to the fs node. */
SVN_ERR (svn_fs_node_proplist (props, root, abs_path->data,
sbaton->pool));
/* Now add some non-tweakable metadata to the hash as well... */
/* The so-called 'entryprops' with info about CR & friends. */
abs_path_s = svn_string_create_from_buf (abs_path, sbaton->pool);
SVN_ERR (svn_repos_get_committed_info (&committed_rev,
&committed_date,
&last_author,
root, abs_path_s,
sbaton->pool));
revision_str = apr_psprintf (sbaton->pool, "%ld", committed_rev);
value = svn_stringbuf_create (revision_str, sbaton->pool);
apr_hash_set (*props, SVN_PROP_ENTRY_COMMITTED_REV,
APR_HASH_KEY_STRING, value);
if (committed_date)
value = svn_stringbuf_create_from_string (committed_date,
sbaton->pool);
else
value = NULL;
apr_hash_set (*props, SVN_PROP_ENTRY_COMMITTED_DATE,
APR_HASH_KEY_STRING, value);
if (last_author)
value = svn_stringbuf_create_from_string (last_author, sbaton->pool);
else
value = NULL;
apr_hash_set (*props, SVN_PROP_ENTRY_LAST_AUTHOR,
APR_HASH_KEY_STRING, value);
/* We have no 'wcprops' in ra_local, but might someday. */
}
return SVN_NO_ERROR;
}
/*----------------------------------------------------------------*/
/** The ra_plugin **/
static const svn_ra_plugin_t ra_local_plugin =
{
"ra_local",
"Module for accessing a repository on local disk.",
svn_ra_local__open,
svn_ra_local__close,
svn_ra_local__get_latest_revnum,
svn_ra_local__get_dated_revision,
svn_ra_local__get_commit_editor,
svn_ra_local__get_file,
svn_ra_local__do_checkout,
svn_ra_local__do_update,
svn_ra_local__do_switch,
svn_ra_local__do_status,
NULL,
svn_ra_local__get_log,
svn_ra_local__do_check_path
};
/*----------------------------------------------------------------*/
/** The One Public Routine, called by libsvn_client **/
svn_error_t *
svn_ra_local_init (int abi_version,
apr_pool_t *pool,
apr_hash_t *hash)
{
apr_hash_set (hash, "file", APR_HASH_KEY_STRING, &ra_local_plugin);
/* ben sez: todo: check that abi_version >=1. */
return SVN_NO_ERROR;
}
/* ----------------------------------------------------------------
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end:
*/