/* $Id$ */
/** @file
* svnsync tool. Modified by Oracle.
*/
/*
* ====================================================================
* Copyright (c) 2005-2006 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/.
* ====================================================================
*/
#ifdef VBOX
#include <svn_cmdline.h>
#include <svn_config.h>
#include <svn_pools.h>
#include <svn_delta.h>
#include <svn_path.h>
#include <svn_props.h>
#include <svn_auth.h>
#include <svn_opt.h>
#include <svn_ra.h>
/* Debug conditional code. */
#ifdef DEBUG
#define DX(x) do { x } while (0);
#else /* !DEBUG */
#define DX(x) do { } while (0);
#endif /* !DEBUG */
#define _(x) x
#define N_(x) x
static svn_error_t * add_file(const char *, void *, const char *, svn_revnum_t, apr_pool_t *, void **);
static svn_error_t * add_directory(const char *, void *, const char *, svn_revnum_t, apr_pool_t *, void **);
static svn_error_t * apply_textdelta(void *, const char *, apr_pool_t *, svn_txdelta_window_handler_t *, void **);
/* The base for this code is version 1.5 from the subversion repository,
* revision 22364. The VBOX code has been updated to use the 1.6 API. */
#else /* !VBOX */
#include "svn_cmdline.h"
#include "svn_config.h"
#include "svn_pools.h"
#include "svn_delta.h"
#include "svn_path.h"
#include "svn_props.h"
#include "svn_auth.h"
#include "svn_opt.h"
#include "svn_ra.h"
#include "svn_private_config.h"
#endif /* !VBOX */
#include <apr_network_io.h>
#include <apr_signal.h>
#include <apr_uuid.h>
enum {
#ifdef VBOX
#endif /* VBOX */
};
#ifdef VBOX
#endif /* VBOX */
#ifdef VBOX
#else /* !VBOX */
static const svn_opt_subcommand_desc_t svnsync_cmd_table[] =
#endif /* !VBOX */
{
N_("usage: svnsync initialize DEST_URL SOURCE_URL\n"
"\n"
"Initialize a destination repository for synchronization from\n"
"another repository.\n"
"\n"
"The destination URL must point to the root of a repository with\n"
"no committed revisions. The destination repository must allow\n"
"revision property changes.\n"
"\n"
"You should not commit to, or make revision property changes in,\n"
"the destination repository by any method other than 'svnsync'.\n"
"In other words, the destination repository should be a read-only\n"
"mirror of the source repository.\n"),
#ifdef VBOX
{ SVNSYNC_OPTS_INITIALIZE } },
#else /* !VBOX */
{ SVNSYNC_OPTS_DEFAULT } },
#endif /* !VBOX */
N_("usage: svnsync synchronize DEST_URL\n"
"\n"
"Transfer all pending revisions from source to destination.\n"),
{ SVNSYNC_OPTS_DEFAULT } },
{ "copy-revprops", copy_revprops_cmd, { 0 },
N_("usage: svnsync copy-revprops DEST_URL REV\n"
"\n"
"Copy all revision properties for revision REV from source to\n"
"destination.\n"),
{ SVNSYNC_OPTS_DEFAULT } },
N_("usage: svnsync help [SUBCOMMAND...]\n"
"\n"
"Describe the usage of this program or its subcommands.\n"),
{ 0 } },
};
{
{"non-interactive", svnsync_opt_non_interactive, 0,
N_("do no interactive prompting") },
{"no-auth-cache", svnsync_opt_no_auth_cache, 0,
N_("do not cache authentication tokens") },
N_("specify a username ARG") },
N_("specify a password ARG") },
N_("read user configuration files from directory ARG")},
#ifdef VBOX
N_("ignore all revisions before ARG")},
N_("set default for processing files and directories to ARG")},
{"replace-externals", svnsync_opt_replace_externals, 0,
N_("replace svn:externals properties")},
{"replace-license", svnsync_opt_replace_license, 0,
N_("replace license properties")},
#endif /* VBOX */
{"version", svnsync_opt_version, 0,
N_("show program version information")},
{"help", 'h', 0,
N_("show help on a subcommand")},
{NULL, '?', 0,
N_("show help on a subcommand")},
{ 0, 0, 0, 0 }
};
typedef struct {
const char *auth_username;
const char *auth_password;
const char *config_dir;
#ifdef VBOX
const char *default_process;
#endif /* VBOX */
} opt_baton_t;
/*** Helper functions ***/
/* Global record of whether the user has requested cancellation. */
/* Callback function for apr_signal(). */
static void
{
}
/* Cancellation callback function. */
static svn_error_t *
{
if (cancelled)
else
return SVN_NO_ERROR;
}
/* Check that the version of libraries in use match what we expect. */
static svn_error_t *
check_lib_versions(void)
{
{
{ "svn_subr", svn_subr_version },
{ "svn_delta", svn_delta_version },
{ "svn_ra", svn_ra_version },
};
}
#ifdef VBOX
* Cannot be done in the change_*_props callbacks, as they are invoked too
static svn_error_t *
const char *default_process,
const char *path,
{
if (value)
else
*proc = parent_deflt;
if (deflt)
{
if (value)
{
{
}
{
}
else
{
}
}
else
{
if (parent_rec)
{
*deflt = parent_deflt;
}
else
{
}
}
}
return SVN_NO_ERROR;
}
#endif /* VBOX */
/* Acquire a lock (of sorts) on the repository associated with the
* given RA SESSION.
*/
static svn_error_t *
{
int i;
if (apr_err)
for (i = 0; i < 10; ++i)
{
subpool));
if (reposlocktoken)
{
/* Did we get it? If so, we're done, otherwise we sleep. */
return SVN_NO_ERROR;
else
{
(pool, _("Failed to get lock on destination "
"repos, currently held by '%s'\n"),
reposlocktoken->data));
}
}
else
{
mylocktoken, subpool));
}
}
"Couldn't get lock on destination repos "
"after %d attempts\n", i);
}
void *baton,
apr_pool_t *pool);
/* Lock the repository associated with RA SESSION, then execute the
* lock once it finishes.
*/
static svn_error_t *
void *baton,
{
{
return err;
}
else if (err2)
{
return err2;
}
else
{
return err;
}
}
/* Callback function for the RA session's open_tmp_file()
* requirements.
*/
static svn_error_t *
{
#ifdef VBOX
#else /* !VBOX */
const char *path;
return SVN_NO_ERROR;
#endif
}
/* Return SVN_NO_ERROR iff URL identifies the root directory of the
* repository associated with RA session SESS.
*/
static svn_error_t *
const char *url,
{
const char *sess_root;
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
return SVN_NO_ERROR;
else
return svn_error_createf
(APR_EINVAL, NULL,
_("Session is rooted at '%s' but the repos root is '%s'"),
}
/* Copy all the revision properties, except for those that have the
* "svn:sync-" prefix, from revision REV of the repository associated
* with RA session FROM_SESSION, to the repository associated with RA
* session TO_SESSION.
*
* If SYNC is TRUE, then properties on the destination revision that
* do not exist on the source revision will be removed.
*/
static svn_error_t *
#ifdef VBOX
#endif /* VBOX */
{
if (sync)
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
{
const void *key;
void *val;
sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
else
#ifdef VBOX
sizeof(SVN_PROP_REVISION_AUTHOR) - 1))
#else /* !VBOX */
#endif /* !VBOX */
if (sync)
}
if (sync)
{
hi;
{
const void *name;
#ifdef VBOX
subpool));
#else /* !VBOX */
subpool));
#endif /* !VBOX */
}
}
#ifdef VBOX
if (saw_sync_props)
{
_("Copied properties for revision %ld "
"(%s* properties skipped).\n"),
else
_("Copied properties for revision %ld "
"(%ld in source repository) "
"(%s* properties skipped).\n"),
}
else
{
_("Copied properties for revision %ld.\n"),
rev_to));
else
_("Copied properties for revision %ld "
"(%ld in source repository).\n"),
}
#else /* !VBOX */
if (saw_sync_props)
_("Copied properties for revision %ld "
"(%s* properties skipped).\n"),
else
_("Copied properties for revision %ld.\n"),
rev));
#endif /* !VBOX */
return SVN_NO_ERROR;
}
#ifdef VBOX
/*** Initialization Editor ***/
/* This editor has the job of creating the initial state for a destination
* repository that starts without history before a certain starting revision.
* properties are dropped, because they don't belong to the initial snapshot.
*
* It needs to create an entire tree in a single commit.
*/
/* InitEdit baton */
typedef struct {
void *wrapped_edit_baton;
const char *default_process;
/* InitDir baton */
typedef struct {
void *wrapped_dir_baton;
/* InitFile baton */
typedef struct {
void *wrapped_file_baton;
/*** Editor vtable functions ***/
static svn_error_t *
{
target_revision, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
void **root_baton)
{
&db->wrapped_dir_baton));
*root_baton = db;
return SVN_NO_ERROR;
}
static svn_error_t *
void *parent_baton,
const char *copyfrom_path,
void **child_baton)
{
{
/* Parent directory is not exported, but this directory is. Warn user,
* because this can lead to destination repository weirdness. */
_("The parent of directory %s is not exported, "
"but the directory is. FIX ASAP!\n"), path));
}
&db->wrapped_dir_baton));
*child_baton = db;
return SVN_NO_ERROR;
}
static svn_error_t *
{
return SVN_NO_ERROR;
}
static svn_error_t *
void *parent_baton,
const char *copyfrom_path,
void **file_baton)
{
{
/* Parent directory is not exported, but this file is. Warn user,
* because this can lead to destination repository weirdness. */
_("The parent of file %s is not exported, "
"but the file is. FIX ASAP!\n"), path));
}
&fb->wrapped_file_baton));
*file_baton = fb;
return SVN_NO_ERROR;
}
static svn_error_t *
const char *base_checksum,
void **handler_baton)
{
handler, handler_baton));
else
{
/* Must provide a window handler, there's no way of telling our caller
* to throw away its data as we're not interested. */
*handler_baton = NULL;
}
return SVN_NO_ERROR;
}
static svn_error_t *
const char *text_checksum,
{
text_checksum, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
const char *name,
const svn_string_t *value,
{
return SVN_NO_ERROR;
return SVN_NO_ERROR;
if (eb->replace_license)
{
/* Throw away the normal license property and replace it by the value
* of svn:sync-license, if present. */
return SVN_NO_ERROR;
}
/* Never export any svn:sync-* properties. */
return SVN_NO_ERROR;
return SVN_NO_ERROR;
}
static svn_error_t *
const char *name,
const svn_string_t *value,
{
return SVN_NO_ERROR;
return SVN_NO_ERROR;
if (eb->replace_externals)
{
/* Throw away the normal externals and replace them by the value of
* svn:sync-externals, if present. */
return SVN_NO_ERROR;
}
/* Never export any svn:sync-* properties. */
return SVN_NO_ERROR;
return SVN_NO_ERROR;
}
static svn_error_t *
{
return SVN_NO_ERROR;
}
/*** Initialization Editor factory function ***/
* that wraps our own commit EDITOR/EDIT_BATON. BASE_REVISION is the
* revision on which the driver of this returned editor will be basing
* the commit. TO_URL is the URL of the root of the repository into
* which the commit is being made.
*/
static svn_error_t *
void *wrapped_edit_baton,
const char *default_process,
const svn_delta_editor_t **editor,
void **edit_baton,
{
*editor = tree_editor;
*edit_baton = eb;
return SVN_NO_ERROR;
}
#endif /* VBOX */
/*** `svnsync init' ***/
/* Baton for initializing the destination repository while locked. */
typedef struct {
const char *from_url;
const char *to_url;
#ifdef VBOX
const char *default_process;
#endif /* VBOX */
} init_baton_t;
#ifdef VBOX
/* Implements `svn_commit_callback2_t' interface. */
static svn_error_t *
void *baton,
{
return SVN_NO_ERROR;
}
#endif /* VBOX */
/* Initialize the repository associated with RA session TO_SESSION,
* using information found in baton B, while the repository is
* locked. Implements `with_locked_func_t' interface.
*/
static svn_error_t *
{
const char *uuid;
#ifdef VBOX
const char *default_process;
#endif /* VBOX */
/* First, sanity check to see that we're copying into a brand new repos. */
if (latest != 0)
return svn_error_create
(APR_EINVAL, NULL,
_("Cannot initialize a repository with content in it"));
/* And check to see if anyone's run initialize on it before... We
may want a --force option to override this check. */
if (from_url)
return svn_error_createf
(APR_EINVAL, NULL,
_("Destination repository is already synchronizing from '%s'"),
/* Now fill in our bookkeeping info in the dest repository. */
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
pool));
pool));
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
#ifdef VBOX
pool);
start_rev_str, pool));
start_rev_str, pool));
if (!baton->default_process)
default_process = "export";
pool));
if (baton->replace_externals)
if (baton->replace_license)
#else /* !VBOX */
#endif /* !VBOX */
/* Finally, copy all non-svnsync revprops from rev 0 of the source
repos into the dest repos. */
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
/* TODO: It would be nice if we could set the dest repos UUID to be
equal to the UUID of the source repos, at least optionally. That
but switch --relocate to the actual final repository in order to
make changes... But at this time, the RA layer doesn't have a
way to set a UUID. */
#ifdef VBOX
{
void *commit_baton;
void *cancel_baton;
void *init_baton;
void *report_baton;
pool));
/* Run it via an update reporter. */
}
#endif /* VBOX */
return SVN_NO_ERROR;
}
/* SUBCOMMAND: init */
static svn_error_t *
{
if (! svn_path_is_url(to_url))
_("Path '%s' is not a URL"), to_url);
if (! svn_path_is_url(from_url))
_("Path '%s' is not a URL"), from_url);
#ifdef VBOX
#endif /* VBOX */
#ifdef VBOX
#else /* !VBOX */
&baton,
pool));
#endif /* !VBOX */
return SVN_NO_ERROR;
}
/*** Synchronization Editor ***/
/* This editor has a couple of jobs.
*
* First, it needs to filter out the propchanges that can't be passed over
* libsvn_ra.
*
* Second, it needs to adjust for the fact that we might not actually have
* permission to see all of the data from the remote repository, which means
* we could get revisions that are totally empty from our point of view.
*
* Third, it needs to adjust copyfrom paths, adding the root url for the
* destination repository to the beginning of them.
*/
/* Edit baton */
typedef struct {
void *wrapped_edit_baton;
#ifdef VBOX
const char *default_process;
#endif /* VBOX */
} edit_baton_t;
/* A dual-purpose baton for files and directories. */
typedef struct {
void *edit_baton;
#ifdef VBOX
#endif /* VBOX */
void *wrapped_node_baton;
} node_baton_t;
static svn_revnum_t
{
revnum),
return SVN_INVALID_REVNUM;
else
}
/* Helper which copies file contents and properties from src to dst. */
static svn_error_t *
const char *dst_path,
void *file_baton,
void *wrapped_parent_node_baton,
{
void *window_handler_baton;
svn_error_t *e = NULL;
&fb->wrapped_node_baton);
if (e)
{
svn_error_clear(e);
&fb->wrapped_node_baton));
}
/* Copy over contents from src revision in source repository. */
/* svn_ra_get_file() insists on getting a file path without leading /,
* and there is a leading / in our input parameter when copying from a
* different branch. The assertion is annoying, as continuing would work. */
if (src_path[0] == '/')
src_path++;
do
{
} while (window);
/* Copy over properties from src revision in source repository. */
{
const void *key;
void *val;
}
return SVN_NO_ERROR;
}
/* Helper which copies a directory and all contents from src to dst. */
static svn_error_t *
const char *dst_path,
void *dir_baton,
void *wrapped_parent_node_baton,
{
&db->wrapped_node_baton));
/* Copy over files and directories from src revision in source repository. */
{
const void *key;
void *val;
{
case svn_node_file:
{
void *fb;
/* Need to copy it from the to_path in the src repository (revision
* current), because that's where the updated (including
* deltas/properties) version is. */
break;
}
case svn_node_dir:
{
void *cdb;
/* Same as above, just for the directory. */
break;
}
default:
}
}
/* Copy over properties from src revision in source repository. */
{
const void *key;
void *val;
}
return SVN_NO_ERROR;
}
/*** Editor vtable functions ***/
static svn_error_t *
{
#ifdef VBOX
#endif /* VBOX */
}
static svn_error_t *
void **root_baton)
{
#ifdef VBOX
DX(fprintf(stderr, " %s (prev %s)\n", db->process ? "EXPORT" : "IGNORE", db->prev_process ? "EXPORT" : "IGNORE");)
{
&db->wrapped_node_baton));
}
*root_baton = db;
#else /* !VBOX */
*root_baton = dir_baton;
#endif /* !VBOX */
return SVN_NO_ERROR;
}
static svn_error_t *
void *parent_baton,
{
#ifdef VBOX
#endif /* VBOX */
#ifdef VBOX
/* Apply sync properties here, too. Avoid deleting items which are
* not in the exported tree, taking transient files into account (can happen
* e.g. if a directory is renamed and in the same changeset a file is
* deleted). Very tricky business. */
if (!ignore_everything)
{
/* Verify if the entry did actually exist. Note that some files exist
* only temporarily within a changeset and get deleted. So there's no
* reliable way for checking their presence. So always delete and hope
* that subversion optimizes out deletes for files which don't exist. */
if (nodekind == svn_node_none)
prev_process = TRUE;
else
{
/* Of course it doesn't make sense to get the properties of the current
* revision - it is to be deleted, so it doesn't have any properties. */
}
{
/* Parent directory is not exported, but this entry is. Warn user,
* because this can lead to destination repository weirdness. */
_("The parent of %s is not exported, but the file/directory (scheduled for deletion) is. FIX ASAP!\n"), path));
}
}
if (prev_process && !ignore_everything)
{
}
return SVN_NO_ERROR;
#else /* !VBOX */
#endif
}
static svn_error_t *
void *parent_baton,
const char *copyfrom_path,
void **child_baton)
{
#ifdef VBOX
if (!b->ignore_everything)
{
/* Of course it doesn't make sense to get the properties of the previous
* revision - it is to be added, so it didn't have any properties. */
&b->process_recursive, pool));
{
/* Parent directory is not exported, but this directory is. Warn user,
* because this can lead to destination repository weirdness. */
_("The parent of directory %s is not exported, but the directory is. FIX ASAP!\n"), path));
}
/* Fake previous process settings, to avoid warnings later on. */
b->prev_process = b->process;
b->prev_process_default = b->process_default;
b->prev_process_recursive = b->process_recursive;
}
else
b->edit_baton = eb;
if (b->process && !b->ignore_everything)
{
if (copyfrom_path)
{
if (SVN_IS_VALID_REVNUM(dst_rev))
{
/* Verify that the copyfrom source was exported to the destination
* repository. */
else
}
}
else
{
&b->wrapped_node_baton));
}
else
{
if (!SVN_IS_VALID_REVNUM(copyfrom_rev))
/* Detect copying from a branch and in that case copy from the
* destination directory in the revision currently being processed. */
if (copyfrom_path[0] == '/')
{
}
/* The dir was renamed, need to copy previous contents because we
* don't know which revnum to use for destination repository. */
b->ignore_everything_rec = TRUE;
b->ignore_everything = TRUE;
}
}
else
{
* directory. Make sure we ignore them all. */
b->ignore_everything_rec = TRUE;
b->ignore_everything = TRUE;
}
#else /* !VBOX */
if (copyfrom_path)
&b->wrapped_node_baton));
b->edit_baton = eb;
#endif /* !VBOX */
*child_baton = b;
return SVN_NO_ERROR;
}
static svn_error_t *
void *parent_baton,
void **child_baton)
{
#ifdef VBOX
if (!db->ignore_everything)
{
/* Verify that the directory was exported from the source
* repository. Can happen to be not there if the rename and
* a change to some file in the directory is in one changeset. */
if (!dir_added_this_changeset)
{
pool));
if (SVN_IS_VALID_REVNUM(dst_rev))
{
}
}
else
{
}
{
}
DX(fprintf(stderr, " %s (prev %s)\n", db->process ? "EXPORT" : "IGNORE", db->prev_process ? "EXPORT" : "IGNORE");)
{
/* Parent directory is not exported, but this directory is. Warn user,
* because this can lead to destination repository weirdness. */
_("The parent of directory %s is not exported, but the directory is. FIX ASAP!\n"), path));
}
{
/* Directory is supposed to be there, but actually is not. Warn user,
* because this can lead to destination repository weirdness. */
_("The directory %s is exported but not present in the target repository. Ignoring it. FIX ASAP!\n"), path));
}
}
else
if (!db->ignore_everything)
{
{
if (db->prev_process)
&db->wrapped_node_baton));
else
{
/* Directory appears due to changes to the process settings. */
&db->wrapped_node_baton));
/* Copy over properties from current revision in source repo */
{
const void *key;
void *val;
}
/* Suppress change_dir_prop for this directory. Done already. */
/* TODO: copy over files in this directory which were already exported
* due to inconsistent export settings (e.g. directory is not exported,
* but file in it is exported). */
}
}
else
{
{
/* Directory disappears due to changes to the process settings. */
}
}
}
#else /* !VBOX */
&db->wrapped_node_baton));
#endif /* !VBOX */
*child_baton = db;
return SVN_NO_ERROR;
}
static svn_error_t *
void *parent_baton,
const char *copyfrom_path,
void **file_baton)
{
#ifdef VBOX
if (!fb->ignore_everything)
{
/* Of course it doesn't make sense to get the properties of the previous
* revision - it is to be added, so it didn't have any properties. */
{
/* Parent directory is not exported, but this file is. Warn user,
* because this can lead to destination repository weirdness. */
_("The parent of directory %s is not exported, but the file is. FIX ASAP!\n"), path));
}
/* Fake previous process settings, to avoid warnings later on. */
}
else
{
if (copyfrom_path)
{
if (SVN_IS_VALID_REVNUM(dst_rev))
{
/* Verify that the copyfrom source was exported to the destination
* repository. */
else
}
}
else
{
}
else
{
/* The file was renamed, need to copy previous contents because we
* don't know which revnum to use for destination repository. */
}
}
#else /* !VBOX */
if (copyfrom_path)
#endif /* !VBOX */
*file_baton = fb;
return SVN_NO_ERROR;
}
static svn_error_t *
void *parent_baton,
void **file_baton)
{
#ifdef VBOX
if (!fb->ignore_everything)
{
/* Check whether the file was added in this changeset. If it was added
* there, the export check for the previous revision would fail. */
DX(fprintf(stderr, " %s (prev %s)\n", fb->process ? "EXPORT" : "IGNORE", fb->prev_process ? "EXPORT" : "IGNORE");)
{
/* Parent directory is not exported, but this file is. Warn user,
* because this can lead to destination repository weirdness. */
_("The parent of directory %s is not exported, but the file is. FIX ASAP!\n"), path));
}
}
else
if (!fb->ignore_everything)
{
{
{
/* Verify that the previous source was exported to the destination
* repository. */
}
if (fb->prev_process)
&fb->wrapped_node_baton));
else
{
/* File appears due to changes to the process settings. */
/* Suppress change_file_prop/apply_textdelta this file. Done already. */
}
}
else
{
{
/* Verify that the previous source was exported to the destination
* repository. */
}
if (fb->prev_process)
{
/* File disappears due to changes to the process settings. */
}
}
}
#else /* !VBOX */
&fb->wrapped_node_baton));
#endif /* !VBOX */
*file_baton = fb;
return SVN_NO_ERROR;
}
static svn_error_t *
const char *base_checksum,
void **handler_baton)
{
#ifdef VBOX
DX(fprintf(stderr, " %s (ignore_everything %d)\n", fb->process ? "EXPORT" : "IGNORE", fb->ignore_everything);)
{
}
else
{
/* Must provide a window handler, there's no way of telling our caller
* to throw away its data as we're not interested. */
*handler_baton = NULL;
return SVN_NO_ERROR;
}
#else /* !VBOX */
#endif /* VBOX */
}
static svn_error_t *
const char *text_checksum,
{
#ifdef VBOX
return SVN_NO_ERROR;
#endif /* VBOX */
}
static svn_error_t *
void *file_baton,
{
#ifdef VBOX
return SVN_NO_ERROR;
#endif /* VBOX */
}
static svn_error_t *
{
#ifdef VBOX
return SVN_NO_ERROR;
#endif /* VBOX */
}
static svn_error_t *
void *dir_baton,
{
#ifdef VBOX
return SVN_NO_ERROR;
#endif /* VBOX */
pool);
}
static svn_error_t *
const char *name,
const svn_string_t *value,
{
#ifdef VBOX
DX(fprintf(stderr, " %s (ignore_everything %d)\n", fb->process ? "EXPORT" : "IGNORE", fb->ignore_everything);)
#endif /* VBOX */
/* only regular properties can pass over libsvn_ra */
return SVN_NO_ERROR;
#ifdef VBOX
return SVN_NO_ERROR;
if (eb->replace_license)
{
/* Throw away the normal license property and replace it by the value
* of svn:sync-license, if present. */
return SVN_NO_ERROR;
}
/* Never export any svn:sync-* properties. */
return SVN_NO_ERROR;
return SVN_NO_ERROR;
#endif /* VBOX */
}
static svn_error_t *
const char *name,
const svn_string_t *value,
{
#ifdef VBOX
DX(fprintf(stderr, " %s (ignore_everything %d)\n", db->process ? "EXPORT" : "IGNORE", db->ignore_everything);)
#endif /* VBOX */
/* only regular properties can pass over libsvn_ra */
return SVN_NO_ERROR;
#ifdef VBOX
return SVN_NO_ERROR;
if (eb->replace_externals)
{
/* Throw away the normal externals and replace them by the value of
* svn:sync-externals, if present. */
return SVN_NO_ERROR;
}
/* Never export any svn:sync-* properties. */
return SVN_NO_ERROR;
return SVN_NO_ERROR;
#endif /* VBOX */
}
static svn_error_t *
{
#ifdef VBOX
/* Suppress empty commits. No need to record something in the
* repository if the entire contents of a changeset is to be ignored. */
{
"repository, empty commit.\n"),
return SVN_NO_ERROR;
}
#endif /* VBOX */
/* If we haven't opened the root yet, that means we're transferring
an empty revision, probably because we aren't allowed to see the
contents for some reason. In any event, we need to open the root
and close it again, before we can close out the edit, or the
commit will fail. */
if (! eb->called_open_root)
{
void *baton;
#ifdef VBOX
&baton));
#else /* !VBOX */
&baton));
#endif /* !VBOX */
}
}
/*** Editor factory function ***/
* that wraps our own commit EDITOR/EDIT_BATON. BASE_REVISION is the
* revision on which the driver of this returned editor will be basing
* the commit. TO_URL is the URL of the root of the repository into
* which the commit is being made.
*/
static svn_error_t *
void *wrapped_edit_baton,
#ifdef VBOX
const char *default_process,
#endif /* VBOX */
const char *to_url,
const svn_delta_editor_t **editor,
void **edit_baton,
{
#ifdef VBOX
#endif /* VBOX */
*editor = tree_editor;
*edit_baton = eb;
return SVN_NO_ERROR;
}
/*** `svnsync sync' ***/
/* Baton for synchronizing the destination repository while locked. */
typedef struct {
const char *to_url;
#ifdef VBOX
#endif /* VBOX */
} sync_baton_t;
/* Implements `svn_commit_callback2_t' interface. */
static svn_error_t *
void *baton,
{
#ifdef VBOX
else
commit_info->revision));
#else /* !VBOX */
commit_info->revision));
#endif /* !VBOX */
return SVN_NO_ERROR;
}
/* Set *FROM_SESSION to an RA session associated with the source
* repository of the synchronization, as determined by reading
* svn:sync- properties from the destination repository (associated
* with TO_SESSION). Set LAST_MERGED_REV to the value of the property
* which records the most recently synchronized revision.
*** VBOX
* Set START_REV_STR to the properly which records the starting revision.
*** VBOX
*
* CALLBACKS is a vtable of RA callbacks to provide when creating
* *FROM_SESSION. CONFIG is a configuration hash.
*/
static svn_error_t *
#ifdef VBOX
#endif /* VBOX */
void *baton,
{
#ifdef VBOX
#endif /* VBOX */
const char *uuid;
last_merged_rev, pool));
#ifdef VBOX
&start_rev_str, pool));
#endif /* VBOX */
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
return svn_error_create
#ifdef VBOX
#endif /* VBOX */
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
pool));
/* Ok, now sanity check the UUID of the source repository, it
wouldn't be a good thing to sync from a different repository. */
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
_("UUID of source repository (%s) does not "
"match expected UUID (%s)"),
return SVN_NO_ERROR;
}
/* Synchronize the repository associated with RA session TO_SESSION,
* using information found in baton B, while the repository is
* locked. Implements `with_locked_func_t' interface.
*/
static svn_error_t *
{
#ifdef VBOX
#endif /* VBOX */
#ifdef VBOX
&default_process, pool));
if (!default_process)
&replace_license_str, pool));
#else /* !VBOX */
#endif /* !VBOX */
/* Check to see if we have revprops that still need to be copied for
a prior revision we didn't finish copying. But first, check for
state sanity. Remember, mirroring is not an atomic action,
because revision properties are copied separately from the
revision's contents.
So, any time that currently-copying is not set, then
last-merged-rev should be the HEAD revision of the destination
repository. That is, if we didn't fall over in the middle of a
previous synchronization, then our destination repository should
have exactly as many revisions in it as we've synchronized.
Alternately, if currently-copying *is* set, it must
be either last-merged-rev or last-merged-rev + 1, and the HEAD
revision must be equal to either last-merged-rev or
currently-copying. If this is not the case, somebody has meddled
with the destination without using svnsync.
*/
¤tly_copying, pool));
#ifndef VBOX
#endif /* !VBOX */
#ifdef VBOX
if (start_rev)
{
/* Fake the destination repository revnum to be what the complete sync
* code expects. TODO: this probably breaks continuing after an abort.*/
}
else
#endif /* VBOX */
if (currently_copying)
{
if ((copying < last_merged)
{
return svn_error_createf
(APR_EINVAL, NULL,
_("Revision being currently copied (%ld), last merged revision "
"(%ld), and destination HEAD (%ld) are inconsistent; have you "
"committed to the destination without using svnsync?"),
}
{
if (copying > last_merged)
{
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
}
/* Now update last merged rev and drop currently changing.
Note that the order here is significant, if we do them
in the wrong order there are race conditions where we
end up not being able to tell if there have been bogus
(i.e. non-svnsync) commits to the dest repository. */
last_merged_rev, pool));
}
/* If copying > to_latest, then we just fall through to
attempting to copy the revision again. */
}
else
{
if (to_latest != last_merged)
{
return svn_error_createf
(APR_EINVAL, NULL,
_("Destination HEAD (%ld) is not the last merged revision (%ld); "
"have you committed to the destination without using svnsync?"),
}
}
/* Now check to see if there are any revisions to copy. */
return SVN_NO_ERROR;
/* Ok, so there are new revisions, iterate over them copying them
into the destination repository. */
current <= from_latest;
++current)
{
void *commit_baton;
void *cancel_baton;
void *sync_baton;
#ifdef VBOX
#endif /* VBOX */
/* We set this property so that if we error out for some reason
we can later determine where we were in the process of
merging a revision. If we had committed the change, but we
hadn't finished copying the revprops we need to know that, so
we can go back and finish the job before we move on.
NOTE: We have to set this before we start the commit editor,
because ra_svn doesn't let you change rev props during a
commit. */
current),
subpool));
/* The actual copy is just a replay hooked up to a commit. */
#ifdef VBOX
#else /* !VBOX */
"", /* empty log */
#endif /* !VBOX */
/* There's one catch though, the diff shows us props we can't
send over the RA interface, so we need an editor that's smart
enough to filter those out for us. */
#ifdef VBOX
subpool));
#else /* !VBOX */
subpool));
#endif /* !VBOX */
subpool));
#ifdef VBOX
/* If svn:sync-ignore-changeset revprop exists in changeset, skip it. */
&ignoreprop, subpool));
if (!ignoreprop)
#else /* !VBOX */
#endif /* !VBOX */
#ifdef VBOX
if (!start_rev)
{
/* Sanity check that we actually committed the revision we meant to. */
return svn_error_createf
(APR_EINVAL, NULL,
_("Commit created rev %ld but should have created %ld"),
}
#else /* !VBOX */
/* Sanity check that we actually committed the revision we meant to. */
return svn_error_createf
(APR_EINVAL, NULL,
_("Commit created rev %ld but should have created %ld"),
#endif /* !VBOX */
/* Ok, we're done with the data, now we just need to do the
revprops and we're all set. */
#ifdef VBOX
{
/* Add a revision cross-reference revprop. */
current),
"%ld",
subpool),
subpool));
}
else
{
/* Add a revision cross-reference revprop for an empty commit,
* referring to the previous commit (this avoids unnecessary copy_file
* operation just because a source file was not modified when it
* appears in the destination repository. */
current),
"%ld",
subpool),
subpool));
}
#else /* !VBOX */
#endif /* !VBOX */
/* Ok, we're done, bring the last-merged-rev property up to date. */
0,
subpool),
subpool));
/* And finally drop the currently copying prop, since we're done
with this revision. */
}
return SVN_NO_ERROR;
}
/* SUBCOMMAND: sync */
static svn_error_t *
{
const char *to_url;
if (! svn_path_is_url(to_url))
_("Path '%s' is not a URL"), to_url);
#ifdef VBOX
#else /* !VBOX */
&baton,
pool));
#endif /* !VBOX */
return SVN_NO_ERROR;
}
/*** `svnsync copy-revprops' ***/
/* Baton for copying revision properties to the destination repository
* while locked.
*/
typedef struct {
const char *to_url;
/* Copy revision properties to the repository associated with RA
* session TO_SESSION, using information found in baton B, while the
* repository is locked. Implements `with_locked_func_t' interface.
*/
static svn_error_t *
{
#ifdef VBOX
#endif /* VBOX */
#ifdef VBOX
if (start_rev)
return svn_error_create
"the start-rev feature (unimplemented)"));
#else /* !VBOX */
#endif /* !VBOX */
return svn_error_create
"been synchronized yet"));
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
return SVN_NO_ERROR;
}
/* SUBCOMMAND: copy-revprops */
static svn_error_t *
{
const char *to_url;
if (! svn_path_is_url(to_url))
_("Path '%s' is not a URL"), to_url);
_("Invalid revision number"));
#ifdef VBOX
#else /* !VBOX */
&baton,
pool));
#endif /* !VBOX */
return SVN_NO_ERROR;
}
/*** `svnsync help' ***/
/* SUBCOMMAND: help */
static svn_error_t *
{
const char *header =
_("general usage: svnsync SUBCOMMAND DEST_URL [ARGS & OPTIONS ...]\n"
"Type 'svnsync help <subcommand>' for help on a specific subcommand.\n"
"Type 'svnsync --version' to see the program version and RA modules.\n"
"\n"
"Available subcommands:\n");
const char *ra_desc_start
= _("The following repository access (RA) modules are available:\n\n");
pool);
#ifdef VBOX
#else /* !VBOX */
pool));
#endif /* !VBOX */
return SVN_NO_ERROR;
}
/*** Main ***/
int
{
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
int opt_id, i;
{
return EXIT_FAILURE;
}
err = check_lib_versions();
if (err)
{
return EXIT_FAILURE;
}
if (err)
{
return EXIT_FAILURE;
}
if (argc <= 1)
{
return EXIT_FAILURE;
}
if (err)
for (;;)
{
const char *opt_arg;
if (APR_STATUS_IS_EOF(apr_err))
break;
else if (apr_err)
{
return EXIT_FAILURE;
}
switch (opt_id)
{
break;
break;
break;
break;
case svnsync_opt_config_dir:
break;
#ifdef VBOX
case svnsync_opt_start_rev:
break;
break;
break;
break;
#endif /* VBOX */
case svnsync_opt_version:
break;
case '?':
case 'h':
break;
default:
{
return EXIT_FAILURE;
}
}
}
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
if (subcommand == NULL)
{
{
{
/* Use the "help" subcommand to handle the "--version" option. */
#ifdef VBOX
#else /* !VBOX */
static const svn_opt_subcommand_desc_t pseudo_cmd =
#endif /* !VBOX */
{svnsync_opt_version, /* must accept its own option */
} };
subcommand = &pseudo_cmd;
}
else
{
return EXIT_FAILURE;
}
}
else
{
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
if (subcommand == NULL)
{
return EXIT_FAILURE;
}
}
}
for (i = 0; i < received_opts->nelts; ++i)
{
continue;
#ifdef VBOX
#else /* !VBOX */
#endif /* !VBOX */
{
const char *optstr;
#ifdef VBOX
pool);
#else /* !VBOX */
#endif /* !VBOX */
else
"Type 'svnsync help %s' for usage.\n"),
return EXIT_FAILURE;
}
}
if (err)
#ifdef SIGBREAK
/* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
#endif
#ifdef SIGHUP
#endif
#ifdef SIGTERM
#endif
#ifdef SIGPIPE
/* Disable SIGPIPE generation for the platforms that have it. */
#endif
#ifdef SIGXFSZ
/* Disable SIGXFSZ generation for the platforms that have it,
otherwise working with large files when compiled against an APR
that doesn't have large file support will crash the program,
which is uncool. */
#endif
#ifdef VBOX
1,
pool);
if (!err)
1,
pool);
#else /* !VBOX */
pool);
#endif /* !VBOX */
if (err)
{
/* For argument-related problems, suggest using the 'help'
subcommand. */
{
_("Try 'svnsync help' for more info"));
}
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}