blob: a84376787a3dff632de16622b546bd5323c5c9a0 [file] [log] [blame]
/*
* switch.c: implement 'switch' feature via wc & ra interfaces.
*
* ====================================================================
* 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/.
* ====================================================================
*/
/* ==================================================================== */
/*** Includes. ***/
#include <assert.h>
#include "svn_wc.h"
#include "svn_client.h"
#include "svn_string.h"
#include "svn_error.h"
#include "svn_path.h"
#include "client.h"
/*** Code. ***/
/* This feature is essentially identical to 'svn update' (see
./update.c), but with two differences:
- the reporter->finish_report() routine needs to make the server
run delta_dirs() on two *different* paths, rather than on two
identical paths.
- after the update runs, we need to more than just
ensure_uniform_revision; we need to rewrite all the entries'
URL attributes.
*/
svn_error_t *
svn_client_switch (const svn_delta_editor_t *before_editor,
void *before_edit_baton,
const svn_delta_editor_t *after_editor,
void *after_edit_baton,
svn_client_auth_baton_t *auth_baton,
svn_stringbuf_t *path,
svn_stringbuf_t *switch_url,
const svn_client_revision_t *revision,
svn_boolean_t recurse,
svn_wc_notify_func_t notify_func,
void *notify_baton,
apr_pool_t *pool)
{
const svn_ra_reporter_t *reporter;
void *report_baton;
svn_wc_entry_t *entry, *session_entry;
svn_stringbuf_t *URL, *anchor, *target;
void *ra_baton, *session;
svn_ra_plugin_t *ra_lib;
svn_revnum_t revnum;
svn_error_t *err = NULL;
/* Sanity check. Without these, the switch is meaningless. */
assert (path != NULL);
assert (path->len > 0);
assert (switch_url != NULL);
assert (switch_url->len > 0);
SVN_ERR (svn_wc_entry (&entry, path, pool));
if (! entry)
return svn_error_createf
(SVN_ERR_WC_PATH_NOT_FOUND, 0, NULL, pool,
"svn_client_update: %s is not under revision control", path->data);
if (entry->kind == svn_node_file)
{
SVN_ERR (svn_wc_get_actual_target (path, &anchor, &target, pool));
/* get the parent entry */
SVN_ERR (svn_wc_entry (&session_entry, anchor, pool));
if (! entry)
return svn_error_createf
(SVN_ERR_WC_PATH_NOT_FOUND, 0, NULL, pool,
"svn_client_update: %s is not under revision control", path->data);
}
else if (entry->kind == svn_node_dir)
{
/* Unlike 'svn up', we do *not* split the local path into an
anchor/target pair. We do this because we know that the
target isn't going to be deleted, because we're doing a
switch. This means the update editor gets anchored on PATH
itself, and thus PATH's name will never change, which is
exactly what we want. */
anchor = path;
target = NULL;
session_entry = entry;
}
if (! session_entry->url)
return svn_error_createf
(SVN_ERR_ENTRY_MISSING_URL, 0, NULL, pool,
"svn_client_switch: entry '%s' has no URL", path->data);
URL = svn_stringbuf_dup (session_entry->url, pool);
/* Get revnum set to something meaningful, so we can fetch the
switch editor. */
if (revision->kind == svn_client_revision_number)
revnum = revision->value.number; /* do the trivial conversion manually */
else
revnum = SVN_INVALID_REVNUM; /* no matter, do real conversion later */
/* Get the RA vtable that matches working copy's current URL. */
SVN_ERR (svn_ra_init_ra_libs (&ra_baton, pool));
SVN_ERR (svn_ra_get_ra_library (&ra_lib, ra_baton, URL->data, pool));
if (entry->kind == svn_node_dir)
{
const svn_delta_editor_t *switch_editor;
void *switch_edit_baton;
const svn_delta_editor_t *wrap_editor;
void *wrap_edit_baton;
const svn_delta_edit_fns_t *wrapped_old_editor;
void *wrapped_old_edit_baton;
/* Open an RA session to 'source' URL */
SVN_ERR (svn_client__open_ra_session (&session, ra_lib, URL, path,
NULL, TRUE, TRUE, TRUE,
auth_baton, pool));
SVN_ERR (svn_client__get_revision_number
(&revnum, ra_lib, session, revision, path->data, pool));
/* Fetch the switch (update) editor. If REVISION is invalid, that's
okay; the RA driver will call editor->set_target_revision() later
on. */
SVN_ERR (svn_wc_get_switch_editor (anchor, target,
revnum, switch_url, recurse,
&switch_editor, &switch_edit_baton,
pool));
/* Wrap it up with outside editors. */
svn_delta_wrap_editor (&wrap_editor, &wrap_edit_baton,
before_editor, before_edit_baton,
switch_editor, switch_edit_baton,
after_editor, after_edit_baton, pool);
/* ### todo: This is a TEMPORARY wrapper around our editor so we
can use it with an old driver. */
svn_delta_compat_wrap (&wrapped_old_editor, &wrapped_old_edit_baton,
wrap_editor, wrap_edit_baton, pool);
/* Tell RA to do a update of URL+TARGET to REVISION; if we pass an
invalid revnum, that means RA will use the latest revision. */
SVN_ERR (ra_lib->do_switch (session,
&reporter, &report_baton,
revnum,
target,
recurse,
switch_url,
wrapped_old_editor, wrapped_old_edit_baton));
/* Drive the reporter structure, describing the revisions within
PATH. When we call reporter->finish_report, the
update_editor will be driven by svn_repos_dir_delta. */
err = svn_wc_crawl_revisions (path, reporter, report_baton,
TRUE, recurse,
notify_func, notify_baton,
pool);
}
else if (entry->kind == svn_node_file)
{
/* If switching a single file, just fetch the file directly and
"install" it into the working copy just like the
update-editor does. */
apr_array_header_t *proparray;
apr_hash_t *prophash;
apr_hash_index_t *hi;
apr_file_t *fp;
svn_stringbuf_t *new_text_path;
svn_stream_t *file_stream;
svn_revnum_t fetched_rev = 1; /* this will be set by get_file() */
/* Create a unique file */
SVN_ERR (svn_io_open_unique_file (&fp, &new_text_path,
path->data, ".new-text-base",
FALSE, /* don't delete on close */
pool));
/* Create a generic stream that operates on this file. */
file_stream = svn_stream_from_aprfile (fp, pool);
/* Open an RA session to 'target' file URL. */
/* ### FIXME: we shouldn't be passing a NULL base-dir to
open_ra_session. This is a just a way of forcing the server
to send a fulltext instead of svndiff data against something
in our working copy. We need to add a callback to
open_ra_session that will fetch a 'source' stream from the
WC, so that ra_dav's implementation of get_file() can use the
svndiff data to construct a fulltext. */
SVN_ERR (svn_client__open_ra_session (&session, ra_lib, switch_url, NULL,
NULL, TRUE, TRUE, TRUE,
auth_baton, pool));
SVN_ERR (svn_client__get_revision_number
(&revnum, ra_lib, session, revision, path->data, pool));
/* Passing "" as a relative path, since we opened the file URL.
This pushes the text of the file into our file_stream, which
means it ends up in our unique tmpfile. We also get the full
proplist. */
SVN_ERR (ra_lib->get_file (session, "", revnum, file_stream,
&fetched_rev, &prophash));
SVN_ERR (svn_stream_close (file_stream));
/* Convert the prophash into an array, which is what
svn_wc_install_file (and its helpers) want. */
proparray = apr_array_make (pool, 1, sizeof(svn_prop_t));
for (hi = apr_hash_first (pool, prophash); hi; hi = apr_hash_next (hi))
{
const void *key;
apr_ssize_t klen;
void *val;
svn_prop_t *prop;
apr_hash_this (hi, &key, &klen, &val);
prop = apr_array_push (proparray);
prop->name = (const char *) key;
prop->value = svn_string_create_from_buf ((svn_stringbuf_t *) val,
pool);
}
/* This the same code as the update-editor's close_file(). */
SVN_ERR (svn_wc_install_file (path->data, revnum,
new_text_path->data,
proparray, TRUE, /* is full proplist */
switch_url->data, /* new url */
pool));
if (notify_func != NULL)
(*notify_func) (notify_baton, svn_wc_notify_update, path->data);
}
/* Sleep for one second to ensure timestamp integrity. */
apr_sleep (APR_USEC_PER_SEC * 1);
if (err)
return err;
/* Close the RA session. */
SVN_ERR (ra_lib->close (session));
return SVN_NO_ERROR;
}
/*
* local variables:
* eval: (load-file "../../tools/dev/svn-dev.el")
* end: */