summaryrefslogtreecommitdiffstats
path: root/src/apps/svnsync-vbox/main.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/apps/svnsync-vbox/main.c
parentInitial commit. (diff)
downloadvirtualbox-c31cb7e733ae8e33d73383e04db264d4293f0b1a.tar.xz
virtualbox-c31cb7e733ae8e33d73383e04db264d4293f0b1a.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/apps/svnsync-vbox/main.c')
-rw-r--r--src/apps/svnsync-vbox/main.c3350
1 files changed, 3350 insertions, 0 deletions
diff --git a/src/apps/svnsync-vbox/main.c b/src/apps/svnsync-vbox/main.c
new file mode 100644
index 00000000..db654c90
--- /dev/null
+++ b/src/apps/svnsync-vbox/main.c
@@ -0,0 +1,3350 @@
+/* $Id: main.c $ */
+/** @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_version.h>
+#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
+
+#define SVNSYNC_PROP_START_REV SVNSYNC_PROP_PREFIX "start-rev"
+#define SVNSYNC_PROP_DEFAULT SVNSYNC_PROP_PREFIX "default"
+#define SVNSYNC_PROP_PROCESS SVNSYNC_PROP_PREFIX "process"
+#define SVNSYNC_PROP_EXTERNALS SVNSYNC_PROP_PREFIX "externals"
+#define SVNSYNC_PROP_LICENSE SVNSYNC_PROP_PREFIX "license"
+#define SVNSYNC_PROP_DEFAULT_PROCESS SVNSYNC_PROP_PREFIX "default-process"
+#define SVNSYNC_PROP_REPLACE_EXTERNALS SVNSYNC_PROP_PREFIX "replace-externals"
+#define SVNSYNC_PROP_REPLACE_LICENSE SVNSYNC_PROP_PREFIX "replace-license"
+#define SVNSYNC_PROP_IGNORE_CHANGESET SVNSYNC_PROP_PREFIX "ignore-changeset"
+#define SVNSYNC_PROP_REV__FMT SVNSYNC_PROP_PREFIX "rev-%ld"
+
+#define SVN_PROP_LICENSE "license"
+
+#define STRIP_LEADING_SLASH(x) (*(x) == '/' ? ((x)+1) : (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 * close_file(void *, const char *, apr_pool_t *);
+static svn_error_t * close_directory(void *, apr_pool_t *);
+static svn_error_t * change_dir_prop(void *, const char *, const svn_string_t *, apr_pool_t *);
+static svn_error_t * apply_textdelta(void *, const char *, apr_pool_t *, svn_txdelta_window_handler_t *, void **);
+static svn_error_t * change_file_prop(void *, const char *, const svn_string_t *, apr_pool_t *);
+
+/* 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>
+
+static svn_opt_subcommand_t initialize_cmd,
+ synchronize_cmd,
+ copy_revprops_cmd,
+ help_cmd;
+
+enum {
+ svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID,
+ svnsync_opt_no_auth_cache,
+ svnsync_opt_auth_username,
+ svnsync_opt_auth_password,
+ svnsync_opt_config_dir,
+#ifdef VBOX
+ svnsync_opt_start_rev,
+ svnsync_opt_default_process,
+ svnsync_opt_replace_externals,
+ svnsync_opt_replace_license,
+#endif /* VBOX */
+ svnsync_opt_version
+};
+
+#define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \
+ svnsync_opt_no_auth_cache, \
+ svnsync_opt_auth_username, \
+ svnsync_opt_auth_password, \
+ svnsync_opt_config_dir
+
+#ifdef VBOX
+#define SVNSYNC_OPTS_INITIALIZE SVNSYNC_OPTS_DEFAULT, \
+ svnsync_opt_start_rev, \
+ svnsync_opt_default_process, \
+ svnsync_opt_replace_externals, \
+ svnsync_opt_replace_license
+#endif /* VBOX */
+
+#ifdef VBOX
+static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
+#else /* !VBOX */
+static const svn_opt_subcommand_desc_t svnsync_cmd_table[] =
+#endif /* !VBOX */
+ {
+ { "initialize", initialize_cmd, { "init" },
+ 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 */
+ { "synchronize", synchronize_cmd, { "sync" },
+ 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 } },
+ { "help", help_cmd, { "?", "h" },
+ N_("usage: svnsync help [SUBCOMMAND...]\n"
+ "\n"
+ "Describe the usage of this program or its subcommands.\n"),
+ { 0 } },
+ { NULL, NULL, { 0 }, NULL, { 0 } }
+ };
+
+static const apr_getopt_option_t svnsync_options[] =
+ {
+ {"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") },
+ {"username", svnsync_opt_auth_username, 1,
+ N_("specify a username ARG") },
+ {"password", svnsync_opt_auth_password, 1,
+ N_("specify a password ARG") },
+ {"config-dir", svnsync_opt_config_dir, 1,
+ N_("read user configuration files from directory ARG")},
+#ifdef VBOX
+ {"start-rev", svnsync_opt_start_rev, 1,
+ N_("ignore all revisions before ARG")},
+ {"default-process", svnsync_opt_default_process, 1,
+ 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 {
+ svn_auth_baton_t *auth_baton;
+ svn_boolean_t non_interactive;
+ svn_boolean_t no_auth_cache;
+ const char *auth_username;
+ const char *auth_password;
+ const char *config_dir;
+#ifdef VBOX
+ svn_revnum_t start_rev;
+ const char *default_process;
+ svn_boolean_t replace_externals;
+ svn_boolean_t replace_license;
+#endif /* VBOX */
+ apr_hash_t *config;
+ svn_boolean_t version;
+ svn_boolean_t help;
+} opt_baton_t;
+
+
+
+
+/*** Helper functions ***/
+
+
+/* Global record of whether the user has requested cancellation. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+
+/* Callback function for apr_signal(). */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+
+/* Cancellation callback function. */
+static svn_error_t *
+check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ 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)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { "svn_ra", svn_ra_version },
+ { NULL, NULL }
+ };
+
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+#ifdef VBOX
+/* Get the export properties of the file/directory in PATH, as of REVISION.
+ * Cannot be done in the change_*_props callbacks, as they are invoked too
+ * late. Need to know before adding/opening a file/directory. */
+static svn_error_t *
+get_props_sync(svn_ra_session_t *session,
+ const char *default_process,
+ svn_boolean_t parent_deflt,
+ svn_boolean_t parent_rec,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t *proc,
+ svn_boolean_t *deflt,
+ svn_boolean_t *rec,
+ apr_pool_t *pool)
+{
+ apr_hash_t *props;
+ svn_string_t *value;
+ svn_node_kind_t nodekind;
+
+ SVN_ERR(svn_ra_check_path(session, path, revision, &nodekind, pool));
+ if (nodekind == svn_node_file)
+ SVN_ERR(svn_ra_get_file(session, path, revision, NULL, NULL, &props, pool));
+ else
+ SVN_ERR(svn_ra_get_dir2(session, NULL, NULL, &props, path, revision, 0, pool));
+ value = apr_hash_get(props, SVNSYNC_PROP_PROCESS, APR_HASH_KEY_STRING);
+ if (value)
+ *proc = !strcmp(value->data, "export");
+ else
+ *proc = parent_deflt;
+ if (deflt)
+ {
+ value = apr_hash_get(props, SVNSYNC_PROP_DEFAULT, APR_HASH_KEY_STRING);
+ if (value)
+ {
+ if (!strcmp(value->data, "export"))
+ {
+ *deflt = TRUE;
+ *rec = FALSE;
+ }
+ else if (!strcmp(value->data, "export-recursive"))
+ {
+ *proc = TRUE;
+ *deflt = TRUE;
+ *rec = TRUE;
+ }
+ else
+ {
+ *deflt = FALSE;
+ *rec = TRUE;
+ }
+ }
+ else
+ {
+ if (parent_rec)
+ {
+ *deflt = parent_deflt;
+ *rec = TRUE;
+ }
+ else
+ {
+ *deflt = !strcmp(default_process, "export");
+ *rec = FALSE;
+ }
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+#endif /* VBOX */
+
+
+/* Acquire a lock (of sorts) on the repository associated with the
+ * given RA SESSION.
+ */
+static svn_error_t *
+get_lock(svn_ra_session_t *session, apr_pool_t *pool)
+{
+ char hostname_str[APRMAXHOSTLEN + 1] = { 0 };
+ svn_string_t *mylocktoken, *reposlocktoken;
+ apr_status_t apr_err;
+ apr_pool_t *subpool;
+ int i;
+
+ apr_err = apr_gethostname(hostname_str, sizeof(hostname_str), pool);
+ if (apr_err)
+ return svn_error_wrap_apr(apr_err, _("Can't get local hostname"));
+
+ mylocktoken = svn_string_createf(pool, "%s:%s", hostname_str,
+ svn_uuid_generate(pool));
+
+ subpool = svn_pool_create(pool);
+
+ for (i = 0; i < 10; ++i)
+ {
+ svn_pool_clear(subpool);
+
+ SVN_ERR(svn_ra_rev_prop(session, 0, SVNSYNC_PROP_LOCK, &reposlocktoken,
+ subpool));
+
+ if (reposlocktoken)
+ {
+ /* Did we get it? If so, we're done, otherwise we sleep. */
+ if (strcmp(reposlocktoken->data, mylocktoken->data) == 0)
+ return SVN_NO_ERROR;
+ else
+ {
+ SVN_ERR(svn_cmdline_printf
+ (pool, _("Failed to get lock on destination "
+ "repos, currently held by '%s'\n"),
+ reposlocktoken->data));
+
+ apr_sleep(apr_time_from_sec(1));
+ }
+ }
+ else
+ {
+#ifdef VBOX
+ SVN_ERR(svn_ra_change_rev_prop2(session, 0, SVNSYNC_PROP_LOCK,
+ NULL, mylocktoken, subpool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_change_rev_prop(session, 0, SVNSYNC_PROP_LOCK,
+ mylocktoken, subpool));
+#endif /* !VBOX */
+ }
+ }
+
+ return svn_error_createf(APR_EINVAL, NULL,
+ "Couldn't get lock on destination repos "
+ "after %d attempts\n", i);
+}
+
+
+typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
+ void *baton,
+ apr_pool_t *pool);
+
+
+/* Lock the repository associated with RA SESSION, then execute the
+ * given FUNC/BATON pair while holding the lock. Finally, drop the
+ * lock once it finishes.
+ */
+static svn_error_t *
+with_locked(svn_ra_session_t *session,
+ with_locked_func_t func,
+ void *baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err, *err2;
+
+ SVN_ERR(get_lock(session, pool));
+
+ err = func(session, baton, pool);
+
+#ifdef VBOX
+ err2 = svn_ra_change_rev_prop2(session, 0, SVNSYNC_PROP_LOCK, NULL, NULL, pool);
+#else /* !VBOX */
+ err2 = svn_ra_change_rev_prop(session, 0, SVNSYNC_PROP_LOCK, NULL, pool);
+#endif /* !VBOX */
+ if (err2 && err)
+ {
+ svn_error_clear(err2); /* XXX what to do here? */
+
+ 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 *
+open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
+{
+#ifdef VBOX
+ return svn_io_open_unique_file3(fp, NULL, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ pool, pool);
+#else /* !VBOX */
+ const char *path;
+
+ SVN_ERR(svn_io_temp_dir(&path, pool));
+
+ path = svn_path_join(path, "tempfile", pool);
+
+ SVN_ERR(svn_io_open_unique_file2(fp, NULL, path, ".tmp",
+ svn_io_file_del_on_close, pool));
+
+ 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 *
+check_if_session_is_at_repos_root(svn_ra_session_t *sess,
+ const char *url,
+ apr_pool_t *pool)
+{
+ const char *sess_root;
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_get_repos_root(sess, &sess_root, pool));
+#endif /* !VBOX */
+
+ if (strcmp(url, sess_root) == 0)
+ return SVN_NO_ERROR;
+ else
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Session is rooted at '%s' but the repos root is '%s'"),
+ url, sess_root);
+}
+
+
+/* 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 *
+copy_revprops(svn_ra_session_t *from_session,
+ svn_ra_session_t *to_session,
+ svn_revnum_t rev,
+#ifdef VBOX
+ svn_revnum_t rev_to,
+#endif /* VBOX */
+ svn_boolean_t sync,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *revprops, *existing_props;
+ svn_boolean_t saw_sync_props = FALSE;
+ apr_hash_index_t *hi;
+
+ if (sync)
+#ifdef VBOX
+ SVN_ERR(svn_ra_rev_proplist(to_session, rev_to, &existing_props, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, pool));
+#endif /* !VBOX */
+
+ SVN_ERR(svn_ra_rev_proplist(from_session, rev, &revprops, pool));
+
+ for (hi = apr_hash_first(pool, revprops); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+
+ svn_pool_clear(subpool);
+ apr_hash_this(hi, &key, NULL, &val);
+
+ if (strncmp(key, SVNSYNC_PROP_PREFIX,
+ sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
+ saw_sync_props = TRUE;
+ else
+#ifdef VBOX
+ if (strncmp(key, SVN_PROP_REVISION_AUTHOR,
+ sizeof(SVN_PROP_REVISION_AUTHOR) - 1))
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, rev_to, key, NULL, val, subpool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_change_rev_prop(to_session, rev, key, val, subpool));
+#endif /* !VBOX */
+
+ if (sync)
+ apr_hash_set(existing_props, key, APR_HASH_KEY_STRING, NULL);
+ }
+
+ if (sync)
+ {
+ for (hi = apr_hash_first(pool, existing_props);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *name;
+
+ svn_pool_clear(subpool);
+
+ apr_hash_this(hi, &name, NULL, NULL);
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, rev_to, name, NULL, NULL,
+ subpool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_change_rev_prop(to_session, rev, name, NULL,
+ subpool));
+#endif /* !VBOX */
+ }
+ }
+
+#ifdef VBOX
+ if (saw_sync_props)
+ {
+ if (rev_to == rev)
+ SVN_ERR(svn_cmdline_printf(subpool,
+ _("Copied properties for revision %ld "
+ "(%s* properties skipped).\n"),
+ rev_to, SVNSYNC_PROP_PREFIX));
+ else
+ SVN_ERR(svn_cmdline_printf(subpool,
+ _("Copied properties for revision %ld "
+ "(%ld in source repository) "
+ "(%s* properties skipped).\n"),
+ rev_to, rev, SVNSYNC_PROP_PREFIX));
+ }
+ else
+ {
+ if (rev_to == rev)
+ SVN_ERR(svn_cmdline_printf(subpool,
+ _("Copied properties for revision %ld.\n"),
+ rev_to));
+ else
+ SVN_ERR(svn_cmdline_printf(subpool,
+ _("Copied properties for revision %ld "
+ "(%ld in source repository).\n"),
+ rev_to, rev));
+ }
+#else /* !VBOX */
+ if (saw_sync_props)
+ SVN_ERR(svn_cmdline_printf(subpool,
+ _("Copied properties for revision %ld "
+ "(%s* properties skipped).\n"),
+ rev, SVNSYNC_PROP_PREFIX));
+ else
+ SVN_ERR(svn_cmdline_printf(subpool,
+ _("Copied properties for revision %ld.\n"),
+ rev));
+#endif /* !VBOX */
+
+ svn_pool_destroy(subpool);
+
+ 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.
+ * Going the export/import way would lose the versioned properties. Unversioned
+ * 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 {
+ const svn_delta_editor_t *wrapped_editor;
+ void *wrapped_edit_baton;
+ svn_ra_session_t *from_session_prop;
+ svn_revnum_t current;
+ const char *default_process;
+ svn_boolean_t replace_externals;
+ svn_boolean_t replace_license;
+} initedit_baton_t;
+
+
+/* InitDir baton */
+typedef struct {
+ initedit_baton_t *edit_baton;
+ void *wrapped_dir_baton;
+ svn_boolean_t process_default;
+ svn_boolean_t process_recursive;
+ svn_boolean_t process;
+} initdir_baton_t;
+
+
+/* InitFile baton */
+typedef struct {
+ initedit_baton_t *edit_baton;
+ void *wrapped_file_baton;
+ svn_boolean_t process;
+} initfile_baton_t;
+
+
+/*** Editor vtable functions ***/
+
+static svn_error_t *
+init_set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ initedit_baton_t *eb = edit_baton;
+
+ DX(fprintf(stderr, "init set_target_revision %ld\n", target_revision);)
+ SVN_ERR(eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
+ target_revision, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+init_open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ initedit_baton_t *eb = edit_baton;
+ initdir_baton_t *db = apr_pcalloc(pool, sizeof(*db));
+
+ DX(fprintf(stderr, "init open_root\n");)
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process, TRUE,
+ FALSE,"", eb->current, &db->process,
+ &db->process_default, &db->process_recursive, pool));
+ DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
+ if (db->process)
+ SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
+ base_revision, pool,
+ &db->wrapped_dir_baton));
+
+ db->edit_baton = edit_baton;
+ *root_baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+init_add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ initdir_baton_t *pb = parent_baton;
+ initedit_baton_t *eb = pb->edit_baton;
+ initdir_baton_t *db = apr_pcalloc(pool, sizeof(*db));
+
+ DX(fprintf(stderr, "init add_directory %s\n", path);)
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
+ pb->process_default, pb->process_recursive, path,
+ eb->current, &db->process, &db->process_default,
+ &db->process_recursive, pool));
+ DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
+ if (db->process && !pb->process)
+ {
+ /* Parent directory is not exported, but this directory is. Warn user,
+ * because this can lead to destination repository weirdness. */
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("The parent of directory %s is not exported, "
+ "but the directory is. FIX ASAP!\n"), path));
+ db->process = FALSE;
+ }
+ if (db->process)
+ SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_dir_baton,
+ NULL, SVN_IGNORED_REVNUM, pool,
+ &db->wrapped_dir_baton));
+
+ db->edit_baton = eb;
+ *child_baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+init_close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ initdir_baton_t *db = dir_baton;
+ initedit_baton_t *eb = db->edit_baton;
+
+ DX(fprintf(stderr, "init close_directory\n");)
+ DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
+ if (db->process)
+ SVN_ERR(eb->wrapped_editor->close_directory(db->wrapped_dir_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+init_add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ initdir_baton_t *pb = parent_baton;
+ initedit_baton_t *eb = pb->edit_baton;
+ initfile_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
+
+ DX(fprintf(stderr, "init add_file %s\n", path);)
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
+ pb->process_default, pb->process_recursive,
+ path, eb->current, &fb->process, NULL, NULL, pool));
+ DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
+ if (fb->process && !pb->process)
+ {
+ /* Parent directory is not exported, but this file is. Warn user,
+ * because this can lead to destination repository weirdness. */
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("The parent of file %s is not exported, "
+ "but the file is. FIX ASAP!\n"), path));
+ fb->process = FALSE;
+ }
+ if (fb->process)
+ SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_dir_baton,
+ NULL, SVN_IGNORED_REVNUM, pool,
+ &fb->wrapped_file_baton));
+
+ fb->edit_baton = eb;
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+init_apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ initfile_baton_t *fb = file_baton;
+ initedit_baton_t *eb = fb->edit_baton;
+
+ DX(fprintf(stderr, "init apply_textdelta\n");)
+ DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
+ if (fb->process)
+ SVN_ERR(eb->wrapped_editor->apply_textdelta(fb->wrapped_file_baton,
+ base_checksum, pool,
+ 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 = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+init_close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ initfile_baton_t *fb = file_baton;
+ initedit_baton_t *eb = fb->edit_baton;
+
+ DX(fprintf(stderr, "init close_file\n");)
+ DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
+ if (fb->process)
+ SVN_ERR(eb->wrapped_editor->close_file(fb->wrapped_file_baton,
+ text_checksum, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+init_change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ initfile_baton_t *fb = file_baton;
+ initedit_baton_t *eb = fb->edit_baton;
+
+ DX(fprintf(stderr, "init change_file_prop %s\n", name);)
+ DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
+ if (svn_property_kind2(name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+ if (!strcmp(name, "cvs2svn:cvs-rev"))
+ 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. */
+ if (!strcmp(name, SVN_PROP_LICENSE))
+ return SVN_NO_ERROR;
+ if (!strcmp(name, SVNSYNC_PROP_LICENSE))
+ name = SVN_PROP_LICENSE;
+ }
+ /* Never export any svn:sync-* properties. */
+ if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
+ return SVN_NO_ERROR;
+
+ if (fb->process)
+ SVN_ERR(eb->wrapped_editor->change_file_prop(fb->wrapped_file_baton,
+ name, value, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+init_change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ initdir_baton_t *db = dir_baton;
+ initedit_baton_t *eb = db->edit_baton;
+
+ DX(fprintf(stderr, "init change_dir_prop %s\n", name);)
+ DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
+ if (svn_property_kind2(name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+ if (!strcmp(name, "cvs2svn:cvs-rev"))
+ 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. */
+ if (!strcmp(name, SVN_PROP_EXTERNALS))
+ return SVN_NO_ERROR;
+ if (!strcmp(name, SVNSYNC_PROP_EXTERNALS))
+ name = SVN_PROP_EXTERNALS;
+ }
+ /* Never export any svn:sync-* properties. */
+ if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
+ return SVN_NO_ERROR;
+
+ if (db->process)
+ SVN_ERR(eb->wrapped_editor->change_dir_prop(db->wrapped_dir_baton,
+ name, value, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+init_close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ initedit_baton_t *eb = edit_baton;
+
+ DX(fprintf(stderr, "init close_edit\n");)
+ SVN_ERR(eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/*** Initialization Editor factory function ***/
+
+/* Set WRAPPED_EDITOR and WRAPPED_EDIT_BATON to an editor/baton pair
+ * 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 *
+get_init_editor(const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ svn_revnum_t start_rev,
+ svn_ra_session_t *prop_session,
+ const char *default_process,
+ svn_boolean_t replace_externals,
+ svn_boolean_t replace_license,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool)
+{
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
+ initedit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb));
+
+ tree_editor->set_target_revision = init_set_target_revision;
+ tree_editor->open_root = init_open_root;
+ tree_editor->add_directory = init_add_directory;
+ tree_editor->change_dir_prop = init_change_dir_prop;
+ tree_editor->close_directory = init_close_directory;
+ tree_editor->add_file = init_add_file;
+ tree_editor->apply_textdelta = init_apply_textdelta;
+ tree_editor->close_file = init_close_file;
+ tree_editor->change_file_prop = init_change_file_prop;
+ tree_editor->close_edit = init_close_edit;
+
+ eb->wrapped_editor = wrapped_editor;
+ eb->wrapped_edit_baton = wrapped_edit_baton;
+ eb->current = start_rev;
+ eb->default_process = default_process;
+ eb->replace_externals = replace_externals;
+ eb->replace_license = replace_license;
+ eb->from_session_prop = prop_session;
+
+ *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;
+ apr_hash_t *config;
+#ifdef VBOX
+ svn_revnum_t start_rev;
+ const char *default_process;
+ svn_boolean_t replace_externals;
+ svn_boolean_t replace_license;
+#endif /* VBOX */
+ svn_ra_callbacks2_t *callbacks;
+} init_baton_t;
+
+
+#ifdef VBOX
+/* Implements `svn_commit_callback2_t' interface. */
+static svn_error_t *
+init_commit_callback(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ init_baton_t *sb = baton;
+
+ SVN_ERR(svn_cmdline_printf(pool, _("Imported source revision %ld as revision %ld.\n"),
+ sb->start_rev, commit_info->revision));
+
+ 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 *
+do_initialize(svn_ra_session_t *to_session, void *b, apr_pool_t *pool)
+{
+ svn_ra_session_t *from_session;
+ init_baton_t *baton = b;
+ svn_string_t *from_url;
+ svn_revnum_t latest;
+ const char *uuid;
+#ifdef VBOX
+ svn_string_t *start_rev_str;
+ const char *default_process;
+ svn_ra_session_t *from_session_prop;
+#endif /* VBOX */
+
+ /* First, sanity check to see that we're copying into a brand new repos. */
+
+ SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool));
+
+ 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. */
+
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
+ &from_url, pool));
+
+ if (from_url)
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Destination repository is already synchronizing from '%s'"),
+ from_url->data);
+
+ /* Now fill in our bookkeeping info in the dest repository. */
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL, baton->callbacks,
+ baton, baton->config, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_open2(&from_session, baton->from_url, baton->callbacks,
+ baton, baton->config, pool));
+#endif /* !VBOX */
+
+ SVN_ERR(check_if_session_is_at_repos_root(from_session, baton->from_url,
+ pool));
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
+ svn_string_create(baton->from_url, pool),
+ pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
+ svn_string_create(baton->from_url, pool),
+ pool));
+#endif /* !VBOX */
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_get_uuid(from_session, &uuid, pool));
+#endif /* !VBOX */
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL,
+ svn_string_create(uuid, pool), pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID,
+ svn_string_create(uuid, pool), pool));
+#endif /* !VBOX */
+
+#ifdef VBOX
+ start_rev_str = svn_string_create(apr_psprintf(pool, "%ld", baton->start_rev),
+ pool);
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_START_REV, NULL,
+ start_rev_str, pool));
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV, NULL,
+ start_rev_str, pool));
+ if (!baton->default_process)
+ default_process = "export";
+ else
+ default_process = baton->default_process;
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_DEFAULT_PROCESS, NULL,
+ svn_string_create(default_process, pool),
+ pool));
+ if (baton->replace_externals)
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ SVNSYNC_PROP_REPLACE_EXTERNALS, NULL,
+ svn_string_create("", pool), pool));
+ if (baton->replace_license)
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ SVNSYNC_PROP_REPLACE_LICENSE, NULL,
+ svn_string_create("", pool), pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_change_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
+ svn_string_create("0", pool), pool));
+#endif /* !VBOX */
+
+ /* Finally, copy all non-svnsync revprops from rev 0 of the source
+ repos into the dest repos. */
+
+#ifdef VBOX
+ SVN_ERR(copy_revprops(from_session, to_session, 0, 0, FALSE, pool));
+#else /* !VBOX */
+ SVN_ERR(copy_revprops(from_session, to_session, 0, FALSE, pool));
+#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
+ way people could check out/log/diff using a local fast mirror,
+ 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
+ if (baton->start_rev > 0)
+ {
+ const svn_delta_editor_t *commit_editor;
+ const svn_delta_editor_t *cancel_editor;
+ const svn_delta_editor_t *init_editor;
+ const svn_ra_reporter3_t *reporter;
+ void *commit_baton;
+ void *cancel_baton;
+ void *init_baton;
+ void *report_baton;
+ apr_hash_t *logrevprop;
+
+ logrevprop = apr_hash_make(pool);
+ apr_hash_set(logrevprop, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
+ svn_string_create("import", pool));
+ SVN_ERR(svn_ra_get_commit_editor3(to_session, &commit_editor, &commit_baton,
+ logrevprop,
+ init_commit_callback, baton,
+ NULL, FALSE, pool));
+
+ SVN_ERR(svn_ra_open4(&from_session_prop, NULL, baton->from_url, NULL,
+ baton->callbacks, baton, baton->config, pool));
+
+ SVN_ERR(get_init_editor(commit_editor, commit_baton, baton->start_rev,
+ from_session_prop, baton->default_process,
+ baton->replace_externals, baton->replace_license,
+ &init_editor, &init_baton, pool));
+
+ SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
+ init_editor, init_baton,
+ &cancel_editor, &cancel_baton,
+ pool));
+
+ /* Run it via an update reporter. */
+ SVN_ERR(svn_ra_do_update3(from_session, &reporter, &report_baton,
+ baton->start_rev, "", svn_depth_infinity, FALSE,
+ FALSE, cancel_editor, cancel_baton, pool, pool));
+ SVN_ERR(reporter->set_path(report_baton, "", baton->start_rev,
+ svn_depth_infinity, TRUE, NULL, pool));
+ SVN_ERR(reporter->finish_report(report_baton, pool));
+ }
+#endif /* VBOX */
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SUBCOMMAND: init */
+static svn_error_t *
+initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
+{
+ svn_ra_callbacks2_t callbacks = { 0 };
+ const char *to_url, *from_url;
+ svn_ra_session_t *to_session;
+ opt_baton_t *opt_baton = b;
+ apr_array_header_t *args;
+ init_baton_t baton;
+
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 2, pool));
+
+ to_url = svn_uri_canonicalize(APR_ARRAY_IDX(args, 0, const char *), pool);
+ from_url = svn_uri_canonicalize(APR_ARRAY_IDX(args, 1, const char *), pool);
+
+ if (! svn_path_is_url(to_url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), to_url);
+ if (! svn_path_is_url(from_url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), from_url);
+
+ baton.to_url = to_url;
+ baton.from_url = from_url;
+ baton.config = opt_baton->config;
+#ifdef VBOX
+ baton.start_rev = opt_baton->start_rev;
+ baton.default_process = opt_baton->default_process;
+ baton.replace_externals = opt_baton->replace_externals;
+ baton.replace_license = opt_baton->replace_license;
+#endif /* VBOX */
+
+ callbacks.open_tmp_file = open_tmp_file;
+ callbacks.auth_baton = opt_baton->auth_baton;
+
+ baton.callbacks = &callbacks;
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_open4(&to_session, NULL, baton.to_url, NULL,
+ &callbacks, &baton, baton.config, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_open2(&to_session,
+ baton.to_url,
+ &callbacks,
+ &baton,
+ baton.config,
+ pool));
+#endif /* !VBOX */
+
+ SVN_ERR(check_if_session_is_at_repos_root(to_session, baton.to_url, pool));
+
+ SVN_ERR(with_locked(to_session, do_initialize, &baton, pool));
+
+ 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 {
+ const svn_delta_editor_t *wrapped_editor;
+ void *wrapped_edit_baton;
+ const char *to_url; /* URL we're copying into, for correct copyfrom URLs */
+ svn_boolean_t called_open_root;
+#ifdef VBOX
+ svn_ra_session_t *from_session_prop;
+ svn_ra_session_t *to_session_prop;
+ svn_boolean_t changeset_live;
+ svn_revnum_t start_rev;
+ svn_revnum_t current;
+ const char *default_process;
+ svn_boolean_t replace_externals;
+ svn_boolean_t replace_license;
+#endif /* VBOX */
+ svn_revnum_t base_revision;
+} edit_baton_t;
+
+
+/* A dual-purpose baton for files and directories. */
+typedef struct {
+ void *edit_baton;
+#ifdef VBOX
+ svn_boolean_t prev_process, process;
+ svn_boolean_t prev_process_default, process_default;
+ svn_boolean_t prev_process_recursive, process_recursive;
+ svn_boolean_t ignore_everything; /* Ignore operations on this dir/file. */
+ svn_boolean_t ignore_everything_rec; /* Recursively ignore operations on subdirs/files. */
+#endif /* VBOX */
+ void *wrapped_node_baton;
+} node_baton_t;
+
+
+#ifdef VBOX
+static svn_revnum_t
+lookup_revnum(svn_ra_session_t *to_session,
+ svn_revnum_t revnum,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ svn_string_t *revprop;
+
+ err = svn_ra_rev_prop(to_session, 0, apr_psprintf(pool,
+ SVNSYNC_PROP_REV__FMT,
+ revnum),
+ &revprop, pool);
+ if (err || !revprop)
+ return SVN_INVALID_REVNUM;
+ else
+ return SVN_STR_TO_REV(revprop->data);
+}
+
+
+/* Helper which copies file contents and properties from src to dst. */
+static svn_error_t *
+copy_file(const char *src_path,
+ svn_revnum_t src_rev,
+ const char *dst_path,
+ void *file_baton,
+ void *wrapped_parent_node_baton,
+ svn_ra_session_t *from_session,
+ apr_pool_t *pool)
+{
+ node_baton_t *fb = file_baton;
+ edit_baton_t *eb = fb->edit_baton;
+ apr_pool_t *subpool;
+ apr_file_t *tmpfile;
+ apr_off_t offset = 0;
+ svn_stream_t *filestream;
+ svn_stream_t *emptystream = svn_stream_empty(pool);
+ svn_txdelta_stream_t *deltastream;
+ svn_txdelta_window_t *window;
+ svn_txdelta_window_handler_t window_handler;
+ void *window_handler_baton;
+ apr_hash_t *fileprops;
+ apr_hash_index_t *hi;
+ svn_error_t *e = NULL;
+
+ e = eb->wrapped_editor->add_file(dst_path, wrapped_parent_node_baton,
+ NULL, SVN_IGNORED_REVNUM, pool,
+ &fb->wrapped_node_baton);
+ if (e)
+ {
+ svn_error_clear(e);
+ SVN_ERR(eb->wrapped_editor->open_file(dst_path, wrapped_parent_node_baton,
+ SVN_IGNORED_REVNUM, pool,
+ &fb->wrapped_node_baton));
+ }
+
+ subpool = svn_pool_create(pool);
+ /* Copy over contents from src revision in source repository. */
+ SVN_ERR(open_tmp_file(&tmpfile, NULL, subpool));
+ filestream = svn_stream_from_aprfile2(tmpfile, FALSE, subpool);
+ SVN_ERR(svn_ra_get_file(from_session, STRIP_LEADING_SLASH(src_path), src_rev,
+ filestream, NULL, &fileprops, subpool));
+ SVN_ERR(svn_io_file_seek(tmpfile, APR_SET, &offset, subpool));
+
+ SVN_ERR(apply_textdelta(file_baton, NULL, subpool, &window_handler,
+ &window_handler_baton));
+ svn_txdelta2(&deltastream, emptystream, filestream, FALSE, subpool);
+ do
+ {
+ SVN_ERR(svn_txdelta_next_window(&window, deltastream, subpool));
+ window_handler(window, window_handler_baton);
+ } while (window);
+
+ SVN_ERR(svn_stream_close(filestream));
+
+ /* Copy over properties from src revision in source repository. */
+ for (hi = apr_hash_first(subpool, fileprops); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ SVN_ERR(change_file_prop(file_baton, key, val, subpool));
+ }
+
+ svn_pool_clear(subpool);
+ return SVN_NO_ERROR;
+}
+
+/* Helper which copies a directory and all contents from src to dst. */
+static svn_error_t *
+copy_dir_rec(const char *src_path,
+ svn_revnum_t src_rev,
+ const char *dst_path,
+ void *dir_baton,
+ void *wrapped_parent_node_baton,
+ svn_ra_session_t *from_session,
+ apr_pool_t *pool)
+{
+ node_baton_t *db = dir_baton;
+ edit_baton_t *eb = db->edit_baton;
+ apr_pool_t *subpool;
+ apr_hash_t *dirents, *dirprops;
+ apr_hash_index_t *hi;
+
+ SVN_ERR(eb->wrapped_editor->add_directory(dst_path, wrapped_parent_node_baton,
+ NULL, SVN_IGNORED_REVNUM, pool,
+ &db->wrapped_node_baton));
+
+ subpool = svn_pool_create(pool);
+ SVN_ERR(svn_ra_get_dir2(from_session, &dirents, NULL, &dirprops, src_path,
+ src_rev, SVN_DIRENT_KIND, subpool));
+
+ /* Copy over files and directories from src revision in source repository. */
+ for (hi = apr_hash_first(subpool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ svn_dirent_t *val_ent;
+ apr_pool_t *oppool;
+ const char *from_path, *to_path;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ val_ent = (svn_dirent_t *)val;
+ oppool = svn_pool_create(subpool);
+ from_path = svn_relpath_join(src_path, key, oppool);
+ to_path = svn_relpath_join(dst_path, key, oppool);
+ switch (val_ent->kind)
+ {
+ 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. */
+ SVN_ERR(add_file(to_path, db, from_path, src_rev, oppool, &fb));
+ SVN_ERR(close_file(fb, NULL, oppool));
+ break;
+ }
+ case svn_node_dir:
+ {
+ void *cdb;
+ /* Same as above, just for the directory. */
+ SVN_ERR(add_directory(to_path, db, from_path, src_rev, oppool, &cdb));
+ SVN_ERR(close_directory(cdb, oppool));
+ break;
+ }
+ default:
+ return svn_error_create(APR_EINVAL, NULL, _("unexpected svn node kind"));
+ }
+ svn_pool_clear(oppool);
+ }
+
+ /* Copy over properties from src revision in source repository. */
+ for (hi = apr_hash_first(subpool, dirprops); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ SVN_ERR(change_dir_prop(dir_baton, key, val, subpool));
+ }
+
+ svn_pool_clear(subpool);
+ return SVN_NO_ERROR;
+}
+#endif /* VBOX */
+
+/*** Editor vtable functions ***/
+
+static svn_error_t *
+set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *pool)
+{
+ edit_baton_t *eb = edit_baton;
+#ifdef VBOX
+ DX(fprintf(stderr, "set_target_revision %ld\n", target_revision);)
+#endif /* VBOX */
+ return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton,
+ target_revision, pool);
+}
+
+static svn_error_t *
+open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **root_baton)
+{
+ edit_baton_t *eb = edit_baton;
+#ifdef VBOX
+ node_baton_t *db = apr_pcalloc(pool, sizeof(*db));
+
+ DX(fprintf(stderr, "open_root\n");)
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process, TRUE,
+ FALSE, "", eb->current-1, &db->prev_process,
+ &db->prev_process_default,
+ &db->prev_process_recursive, pool));
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
+ TRUE, FALSE, "", eb->current, &db->process,
+ &db->process_default, &db->process_recursive, pool));
+ DX(fprintf(stderr, " %s (prev %s)\n", db->process ? "EXPORT" : "IGNORE", db->prev_process ? "EXPORT" : "IGNORE");)
+ if (db->process)
+ {
+ SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
+ base_revision, pool,
+ &db->wrapped_node_baton));
+ eb->called_open_root = TRUE;
+ }
+ db->edit_baton = edit_baton;
+ *root_baton = db;
+#else /* !VBOX */
+ node_baton_t *dir_baton = apr_palloc(pool, sizeof(*dir_baton));
+
+ SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
+ base_revision, pool,
+ &dir_baton->wrapped_node_baton));
+
+ eb->called_open_root = TRUE;
+ dir_baton->edit_baton = edit_baton;
+ *root_baton = dir_baton;
+#endif /* !VBOX */
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+delete_entry(const char *path,
+ svn_revnum_t base_revision,
+ void *parent_baton,
+ apr_pool_t *pool)
+{
+ node_baton_t *pb = parent_baton;
+ edit_baton_t *eb = pb->edit_baton;
+#ifdef VBOX
+ svn_boolean_t prev_process = FALSE;
+ svn_boolean_t ignore_everything;
+#endif /* VBOX */
+
+#ifdef VBOX
+ DX(fprintf(stderr, "delete_entry %s\n", path);)
+ /* 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. */
+ ignore_everything = pb->ignore_everything;
+ if (!ignore_everything)
+ {
+ svn_node_kind_t nodekind;
+ /* 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. */
+ SVN_ERR(svn_ra_check_path(eb->from_session_prop, STRIP_LEADING_SLASH(path),
+ eb->current-1, &nodekind, pool));
+ 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. */
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
+ pb->prev_process_default, pb->prev_process_recursive,
+ path, eb->current-1, &prev_process, NULL, NULL, pool));
+ }
+ DX(fprintf(stderr, " %s\n", prev_process ? "EXPORT" : "IGNORE");)
+ if (prev_process && !pb->process)
+ {
+ /* Parent directory is not exported, but this entry is. Warn user,
+ * because this can lead to destination repository weirdness. */
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("The parent of %s is not exported, but the file/directory (scheduled for deletion) is. FIX ASAP!\n"), path));
+ prev_process = FALSE;
+ }
+ }
+ if (prev_process && !ignore_everything)
+ {
+ eb->changeset_live = TRUE;
+ /* Deliberately ignore error, it's the only safe solution. */
+ eb->wrapped_editor->delete_entry(path, base_revision,
+ pb->wrapped_node_baton, pool);
+ }
+
+ return SVN_NO_ERROR;
+#else /* !VBOX */
+ return eb->wrapped_editor->delete_entry(path, base_revision,
+ pb->wrapped_node_baton, pool);
+#endif
+}
+
+static svn_error_t *
+add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ node_baton_t *pb = parent_baton;
+ edit_baton_t *eb = pb->edit_baton;
+#ifdef VBOX
+ node_baton_t *b = apr_pcalloc(pool, sizeof(*b));
+ svn_revnum_t dst_rev;
+
+ DX(fprintf(stderr, "add_directory %s\n", path);)
+ b->ignore_everything_rec = pb->ignore_everything_rec;
+ b->ignore_everything = pb->ignore_everything_rec;
+ 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. */
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
+ pb->process_default, pb->process_recursive, path,
+ eb->current, &b->process, &b->process_default,
+ &b->process_recursive, pool));
+ DX(fprintf(stderr, " %s\n", b->process ? "EXPORT" : "IGNORE");)
+ if (b->process && !pb->process)
+ {
+ /* Parent directory is not exported, but this directory is. Warn user,
+ * because this can lead to destination repository weirdness. */
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("The parent of directory %s is not exported, but the directory is. FIX ASAP!\n"), path));
+ b->process = FALSE;
+ }
+ /* 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->process = FALSE;
+ b->edit_baton = eb;
+ if (b->process && !b->ignore_everything)
+ {
+ eb->changeset_live = TRUE;
+ if (copyfrom_path)
+ {
+ dst_rev = lookup_revnum(eb->to_session_prop, copyfrom_rev, pool);
+ if (SVN_IS_VALID_REVNUM(dst_rev))
+ {
+ svn_node_kind_t nodekind;
+ /* Verify that the copyfrom source was exported to the destination
+ * repository. */
+ SVN_ERR(svn_ra_check_path(eb->to_session_prop,
+ STRIP_LEADING_SLASH(copyfrom_path), dst_rev,
+ &nodekind, pool));
+ if (nodekind == svn_node_none || nodekind != svn_node_dir)
+ dst_rev = SVN_INVALID_REVNUM;
+ else
+ copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
+ svn_path_uri_encode(copyfrom_path, pool));
+ }
+ }
+ else
+ dst_rev = copyfrom_rev;
+
+ if (!SVN_IS_VALID_REVNUM(copyfrom_rev) || SVN_IS_VALID_REVNUM(dst_rev))
+ {
+ /* Genuinely add a new dir, referring to other revision/name if known. */
+ SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
+ copyfrom_path,
+ dst_rev, pool,
+ &b->wrapped_node_baton));
+ }
+ else
+ {
+ if (!SVN_IS_VALID_REVNUM(copyfrom_rev))
+ copyfrom_rev = eb->current;
+ /* Detect copying from a branch and in that case copy from the
+ * destination directory in the revision currently being processed. */
+ if (copyfrom_path[0] == '/')
+ {
+ copyfrom_path = path;
+ copyfrom_rev = eb->current;
+ }
+ /* The dir was renamed, need to copy previous contents because we
+ * don't know which revnum to use for destination repository. */
+ SVN_ERR(copy_dir_rec(copyfrom_path, copyfrom_rev, path, b,
+ pb->wrapped_node_baton, eb->from_session_prop, pool));
+ b->ignore_everything_rec = TRUE;
+ b->ignore_everything = TRUE;
+ }
+ }
+ else
+ {
+ /* In this changeset there may be changes to files/dirs in this ignored
+ * directory. Make sure we ignore them all. */
+ b->ignore_everything_rec = TRUE;
+ b->ignore_everything = TRUE;
+ }
+#else /* !VBOX */
+ node_baton_t *b = apr_palloc(pool, sizeof(*b));
+
+ if (copyfrom_path)
+ copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
+ svn_path_uri_encode(copyfrom_path, pool));
+
+ SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
+ copyfrom_path,
+ copyfrom_rev, pool,
+ &b->wrapped_node_baton));
+
+ b->edit_baton = eb;
+#endif /* !VBOX */
+ *child_baton = b;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **child_baton)
+{
+ node_baton_t *pb = parent_baton;
+ edit_baton_t *eb = pb->edit_baton;
+#ifdef VBOX
+ node_baton_t *db = apr_pcalloc(pool, sizeof(*db));
+ svn_boolean_t dir_added_this_changeset = FALSE;
+ svn_boolean_t dir_present_in_target = FALSE;
+
+ DX(fprintf(stderr, "open_directory %s\n", path);)
+ db->ignore_everything_rec = pb->ignore_everything_rec;
+ db->ignore_everything = db->ignore_everything_rec;
+ if (!db->ignore_everything)
+ {
+ svn_node_kind_t nodekind;
+ /* 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. */
+ SVN_ERR(svn_ra_check_path(eb->from_session_prop, STRIP_LEADING_SLASH(path),
+ eb->current-1, &nodekind, pool));
+ dir_added_this_changeset = (nodekind != svn_node_dir);
+ if (!dir_added_this_changeset)
+ {
+ svn_revnum_t dst_rev;
+
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
+ pb->prev_process_default, pb->prev_process_recursive,
+ path, eb->current-1, &db->prev_process,
+ &db->prev_process_default, &db->prev_process_recursive,
+ pool));
+ dst_rev = lookup_revnum(eb->to_session_prop, eb->current-1, pool);
+ if (SVN_IS_VALID_REVNUM(dst_rev))
+ {
+ SVN_ERR(svn_ra_check_path(eb->to_session_prop, STRIP_LEADING_SLASH(path),
+ dst_rev, &nodekind, pool));
+ dir_present_in_target = (nodekind == svn_node_dir);
+ }
+ }
+ else
+ {
+ dir_present_in_target = TRUE;
+ }
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
+ pb->process_default, pb->process_recursive, path,
+ eb->current, &db->process, &db->process_default,
+ &db->process_recursive, pool));
+ if (dir_added_this_changeset)
+ {
+ db->prev_process = db->process;
+ db->prev_process_default = db->process_default;
+ db->prev_process_recursive = db->process_recursive;
+ }
+ DX(fprintf(stderr, " %s (prev %s)\n", db->process ? "EXPORT" : "IGNORE", db->prev_process ? "EXPORT" : "IGNORE");)
+ if (db->process && !pb->process)
+ {
+ /* Parent directory is not exported, but this directory is. Warn user,
+ * because this can lead to destination repository weirdness. */
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("The parent of directory %s is not exported, but the directory is. FIX ASAP!\n"), path));
+ db->process = FALSE;
+ db->ignore_everything_rec = TRUE;
+ db->ignore_everything = TRUE;
+ }
+ if (db->process && db->prev_process && !dir_added_this_changeset && !dir_present_in_target)
+ {
+ /* Directory is supposed to be there, but actually is not. Warn user,
+ * because this can lead to destination repository weirdness. */
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("The directory %s is exported but not present in the target repository. Ignoring it. FIX ASAP!\n"), path));
+ db->process = FALSE;
+ db->ignore_everything_rec = TRUE;
+ db->ignore_everything = TRUE;
+ }
+ }
+ else
+ db->process = FALSE;
+ db->edit_baton = eb;
+ if (!db->ignore_everything)
+ {
+ if (db->process)
+ {
+ if (db->prev_process)
+ SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton,
+ base_revision, pool,
+ &db->wrapped_node_baton));
+ else
+ {
+ apr_hash_t *dirprops;
+ apr_hash_index_t *hi;
+
+ /* Directory appears due to changes to the process settings. */
+ eb->changeset_live = TRUE;
+ SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton,
+ NULL, SVN_IGNORED_REVNUM, pool,
+ &db->wrapped_node_baton));
+ /* Copy over properties from current revision in source repo */
+ SVN_ERR(svn_ra_get_dir2(eb->from_session_prop, NULL, NULL, &dirprops,
+ path, eb->current, 0, pool));
+ for (hi = apr_hash_first(pool, dirprops); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+
+ apr_hash_this(hi, &key, NULL, &val);
+ SVN_ERR(change_dir_prop(db, key, val, pool));
+ }
+ /* Suppress change_dir_prop for this directory. Done already. */
+ db->ignore_everything = TRUE;
+
+ /** @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
+ {
+ if (db->prev_process && dir_present_in_target)
+ {
+ /* Directory disappears due to changes to the process settings. */
+ eb->changeset_live = TRUE;
+ SVN_ERR(eb->wrapped_editor->delete_entry(path, SVN_IGNORED_REVNUM,
+ pb->wrapped_node_baton, pool));
+ }
+ db->ignore_everything_rec = TRUE;
+ }
+ }
+#else /* !VBOX */
+ node_baton_t *db = apr_palloc(pool, sizeof(*db));
+
+ SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton,
+ base_revision, pool,
+ &db->wrapped_node_baton));
+
+ db->edit_baton = eb;
+#endif /* !VBOX */
+ *child_baton = db;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_rev,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ node_baton_t *pb = parent_baton;
+ edit_baton_t *eb = pb->edit_baton;
+#ifdef VBOX
+ node_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
+ svn_revnum_t dst_rev;
+
+ DX(fprintf(stderr, "add_file %s\n", path);)
+ fb->ignore_everything_rec = pb->ignore_everything_rec;
+ fb->ignore_everything = fb->ignore_everything_rec;
+ 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. */
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
+ pb->process_default, pb->process_recursive, path,
+ eb->current, &fb->process, NULL, NULL, pool));
+ fb->process_default = FALSE;
+ DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
+ if (fb->process && !pb->process)
+ {
+ /* Parent directory is not exported, but this file is. Warn user,
+ * because this can lead to destination repository weirdness. */
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("The parent of directory %s is not exported, but the file is. FIX ASAP!\n"), path));
+ fb->process = FALSE;
+ }
+ /* Fake previous process settings, to avoid warnings later on. */
+ fb->prev_process = fb->process;
+ fb->prev_process_default = fb->process_default;
+ }
+ else
+ fb->process = FALSE;
+ fb->edit_baton = eb;
+ if (fb->process && !fb->ignore_everything)
+ {
+ eb->changeset_live = TRUE;
+ if (copyfrom_path)
+ {
+ dst_rev = lookup_revnum(eb->to_session_prop, copyfrom_rev, pool);
+ if (SVN_IS_VALID_REVNUM(dst_rev))
+ {
+ svn_node_kind_t nodekind;
+ /* Verify that the copyfrom source was exported to the destination
+ * repository. */
+ SVN_ERR(svn_ra_check_path(eb->to_session_prop,
+ STRIP_LEADING_SLASH(copyfrom_path), dst_rev,
+ &nodekind, pool));
+ if (nodekind == svn_node_none || nodekind != svn_node_file)
+ dst_rev = SVN_INVALID_REVNUM;
+ else
+ copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
+ svn_path_uri_encode(copyfrom_path, pool));
+ }
+ }
+ else
+ dst_rev = copyfrom_rev;
+
+ if (!SVN_IS_VALID_REVNUM(copyfrom_rev) || SVN_IS_VALID_REVNUM(dst_rev))
+ {
+ /* Genuinely add a new file, referring to other revision/name if known. */
+ SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton,
+ copyfrom_path, dst_rev,
+ pool, &fb->wrapped_node_baton));
+ }
+ else
+ {
+ /* The file was renamed, need to copy previous contents because we
+ * don't know which revnum to use for destination repository. */
+ SVN_ERR(copy_file(copyfrom_path, copyfrom_rev, path, fb,
+ pb->wrapped_node_baton, eb->from_session_prop, pool));
+ }
+ }
+#else /* !VBOX */
+ node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
+
+ if (copyfrom_path)
+ copyfrom_path = apr_psprintf(pool, "%s%s", eb->to_url,
+ svn_path_uri_encode(copyfrom_path, pool));
+
+ SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton,
+ copyfrom_path, copyfrom_rev,
+ pool, &fb->wrapped_node_baton));
+
+ fb->edit_baton = eb;
+#endif /* !VBOX */
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *pool,
+ void **file_baton)
+{
+ node_baton_t *pb = parent_baton;
+ edit_baton_t *eb = pb->edit_baton;
+#ifdef VBOX
+ node_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
+ svn_boolean_t file_added_this_changeset = FALSE;
+
+ DX(fprintf(stderr, "open_file %s\n", path);)
+ fb->ignore_everything_rec = pb->ignore_everything_rec;
+ fb->ignore_everything = fb->ignore_everything_rec;
+ if (!fb->ignore_everything)
+ {
+ svn_node_kind_t nodekind;
+ /* Check whether the file was added in this changeset. If it was added
+ * there, the export check for the previous revision would fail. */
+ SVN_ERR(svn_ra_check_path(eb->from_session_prop, STRIP_LEADING_SLASH(path),
+ eb->current-1, &nodekind, pool));
+ file_added_this_changeset = (nodekind != svn_node_file);
+ if (!file_added_this_changeset)
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
+ pb->prev_process_default,
+ pb->prev_process_recursive,
+ path, eb->current-1, &fb->prev_process,
+ NULL, NULL, pool));
+ SVN_ERR(get_props_sync(eb->from_session_prop, eb->default_process,
+ pb->process_default, pb->process_recursive, path,
+ eb->current, &fb->process, NULL, NULL, pool));
+ if (file_added_this_changeset)
+ fb->prev_process = FALSE;
+ fb->prev_process_default = FALSE;
+ fb->process_default = FALSE;
+ DX(fprintf(stderr, " %s (prev %s)\n", fb->process ? "EXPORT" : "IGNORE", fb->prev_process ? "EXPORT" : "IGNORE");)
+ if (fb->process && !pb->process)
+ {
+ /* Parent directory is not exported, but this file is. Warn user,
+ * because this can lead to destination repository weirdness. */
+ SVN_ERR(svn_cmdline_printf(pool,
+ _("The parent of directory %s is not exported, but the file is. FIX ASAP!\n"), path));
+ fb->process = FALSE;
+ fb->ignore_everything = TRUE;
+ }
+ }
+ else
+ fb->process = FALSE;
+ fb->edit_baton = eb;
+ if (!fb->ignore_everything)
+ {
+ if (fb->process)
+ {
+ if (!file_added_this_changeset)
+ {
+ svn_node_kind_t nodekind;
+ /* Verify that the previous source was exported to the destination
+ * repository. */
+ SVN_ERR(svn_ra_check_path(eb->to_session_prop,
+ STRIP_LEADING_SLASH(path),
+ SVN_IGNORED_REVNUM, &nodekind, pool));
+ if (nodekind == svn_node_none || nodekind != svn_node_file)
+ fb->prev_process = FALSE;
+ }
+
+ if (fb->prev_process)
+ SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton,
+ base_revision, pool,
+ &fb->wrapped_node_baton));
+ else
+ {
+ /* File appears due to changes to the process settings. */
+ eb->changeset_live = TRUE;
+
+ SVN_ERR(copy_file(path, eb->current, path, fb, pb->wrapped_node_baton,
+ eb->from_session_prop, pool));
+ /* Suppress change_file_prop/apply_textdelta this file. Done already. */
+ fb->ignore_everything = TRUE;
+ }
+ }
+ else
+ {
+ if (!file_added_this_changeset)
+ {
+ svn_node_kind_t nodekind;
+ /* Verify that the previous source was exported to the destination
+ * repository. */
+ SVN_ERR(svn_ra_check_path(eb->to_session_prop,
+ STRIP_LEADING_SLASH(path),
+ SVN_IGNORED_REVNUM, &nodekind, pool));
+ if (nodekind == svn_node_none || nodekind != svn_node_file)
+ fb->prev_process = FALSE;
+ }
+
+ if (fb->prev_process)
+ {
+ /* File disappears due to changes to the process settings. */
+ eb->changeset_live = TRUE;
+ SVN_ERR(eb->wrapped_editor->delete_entry(path, SVN_IGNORED_REVNUM,
+ pb->wrapped_node_baton, pool));
+ fb->ignore_everything = TRUE;
+ }
+ }
+ }
+#else /* !VBOX */
+ node_baton_t *fb = apr_palloc(pool, sizeof(*fb));
+
+ SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton,
+ base_revision, pool,
+ &fb->wrapped_node_baton));
+
+ fb->edit_baton = eb;
+#endif /* !VBOX */
+ *file_baton = fb;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ node_baton_t *fb = file_baton;
+ edit_baton_t *eb = fb->edit_baton;
+
+#ifdef VBOX
+ DX(fprintf(stderr, "apply_textdelta\n");)
+ DX(fprintf(stderr, " %s (ignore_everything %d)\n", fb->process ? "EXPORT" : "IGNORE", fb->ignore_everything);)
+ if (fb->process && !fb->ignore_everything)
+ {
+ eb->changeset_live = TRUE;
+ return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
+ base_checksum, pool,
+ 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 = svn_delta_noop_window_handler;
+ *handler_baton = NULL;
+ return SVN_NO_ERROR;
+ }
+#else /* !VBOX */
+ return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton,
+ base_checksum, pool,
+ handler, handler_baton);
+#endif /* VBOX */
+}
+
+static svn_error_t *
+close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *pool)
+{
+ node_baton_t *fb = file_baton;
+ edit_baton_t *eb = fb->edit_baton;
+#ifdef VBOX
+ DX(fprintf(stderr, "close_file\n");)
+ DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
+ if (!fb->process)
+ return SVN_NO_ERROR;
+#endif /* VBOX */
+ return eb->wrapped_editor->close_file(fb->wrapped_node_baton,
+ text_checksum, pool);
+}
+
+static svn_error_t *
+absent_file(const char *path,
+ void *file_baton,
+ apr_pool_t *pool)
+{
+ node_baton_t *fb = file_baton;
+ edit_baton_t *eb = fb->edit_baton;
+#ifdef VBOX
+ DX(fprintf(stderr, "absent_file\n");)
+ DX(fprintf(stderr, " %s\n", fb->process ? "EXPORT" : "IGNORE");)
+ if (!fb->process)
+ return SVN_NO_ERROR;
+#endif /* VBOX */
+ return eb->wrapped_editor->absent_file(path, fb->wrapped_node_baton, pool);
+}
+
+static svn_error_t *
+close_directory(void *dir_baton,
+ apr_pool_t *pool)
+{
+ node_baton_t *db = dir_baton;
+ edit_baton_t *eb = db->edit_baton;
+#ifdef VBOX
+ DX(fprintf(stderr, "close_directory\n");)
+ DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
+ if (!db->process)
+ return SVN_NO_ERROR;
+#endif /* VBOX */
+ return eb->wrapped_editor->close_directory(db->wrapped_node_baton, pool);
+}
+
+static svn_error_t *
+absent_directory(const char *path,
+ void *dir_baton,
+ apr_pool_t *pool)
+{
+ node_baton_t *db = dir_baton;
+ edit_baton_t *eb = db->edit_baton;
+#ifdef VBOX
+ DX(fprintf(stderr, "absent_directory\n");)
+ DX(fprintf(stderr, " %s\n", db->process ? "EXPORT" : "IGNORE");)
+ if (!db->process)
+ return SVN_NO_ERROR;
+#endif /* VBOX */
+ return eb->wrapped_editor->absent_directory(path, db->wrapped_node_baton,
+ pool);
+}
+
+static svn_error_t *
+change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ node_baton_t *fb = file_baton;
+ edit_baton_t *eb = fb->edit_baton;
+
+#ifdef VBOX
+ DX(fprintf(stderr, "change_file_prop %s\n", name);)
+ 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 */
+#ifdef VBOX
+ if (svn_property_kind2(name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+ if (!strcmp(name, "cvs2svn:cvs-rev"))
+ 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. */
+ if (!strcmp(name, SVN_PROP_LICENSE))
+ return SVN_NO_ERROR;
+ if (!strcmp(name, SVNSYNC_PROP_LICENSE))
+ name = SVN_PROP_LICENSE;
+ }
+ /* Never export any svn:sync-* properties. */
+ if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
+ return SVN_NO_ERROR;
+ if (!fb->process || fb->ignore_everything)
+ return SVN_NO_ERROR;
+ eb->changeset_live = TRUE;
+#else /* !VBOX */
+ if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+#endif /* !VBOX */
+
+ return eb->wrapped_editor->change_file_prop(fb->wrapped_node_baton,
+ name, value, pool);
+}
+
+static svn_error_t *
+change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ node_baton_t *db = dir_baton;
+ edit_baton_t *eb = db->edit_baton;
+
+#ifdef VBOX
+ DX(fprintf(stderr, "change_dir_prop %s\n", name);)
+ 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 */
+#ifdef VBOX
+ if (svn_property_kind2(name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+ if (!strcmp(name, "cvs2svn:cvs-rev"))
+ 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. */
+ if (!strcmp(name, SVN_PROP_EXTERNALS))
+ return SVN_NO_ERROR;
+ if (!strcmp(name, SVNSYNC_PROP_EXTERNALS))
+ name = SVN_PROP_EXTERNALS;
+ }
+ /* Never export any svn:sync-* properties. */
+ if (!strncmp(name, SVNSYNC_PROP_PREFIX, sizeof(SVNSYNC_PROP_PREFIX) - 1))
+ return SVN_NO_ERROR;
+ if (!db->process || db->ignore_everything)
+ return SVN_NO_ERROR;
+ eb->changeset_live = TRUE;
+#else /* !VBOX */
+ if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
+ return SVN_NO_ERROR;
+#endif /* !VBOX */
+
+ return eb->wrapped_editor->change_dir_prop(db->wrapped_node_baton,
+ name, value, pool);
+}
+
+static svn_error_t *
+close_edit(void *edit_baton,
+ apr_pool_t *pool)
+{
+ edit_baton_t *eb = edit_baton;
+
+#ifdef VBOX
+ DX(fprintf(stderr, "close_edit\n");)
+ /* Suppress empty commits. No need to record something in the
+ * repository if the entire contents of a changeset is to be ignored. */
+ if (eb->start_rev && !eb->changeset_live)
+ {
+ DX(fprintf(stderr, " discard empty commit\n");)
+ SVN_ERR(eb->wrapped_editor->abort_edit(eb->wrapped_edit_baton, pool));
+ SVN_ERR(svn_cmdline_printf(pool, _("Skipped revision %ld in source "
+ "repository, empty commit.\n"),
+ eb->current));
+ 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
+ SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
+ eb->current, pool,
+ &baton));
+#else /* !VBOX */
+ SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton,
+ eb->base_revision, pool,
+ &baton));
+#endif /* !VBOX */
+ SVN_ERR(eb->wrapped_editor->close_directory(baton, pool));
+ }
+
+ return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool);
+}
+
+/*** Editor factory function ***/
+
+/* Set WRAPPED_EDITOR and WRAPPED_EDIT_BATON to an editor/baton pair
+ * 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 *
+get_sync_editor(const svn_delta_editor_t *wrapped_editor,
+ void *wrapped_edit_baton,
+ svn_revnum_t base_revision,
+#ifdef VBOX
+ svn_revnum_t start_rev,
+ svn_revnum_t current,
+ svn_ra_session_t *prop_session_from,
+ svn_ra_session_t *prop_session_to,
+ const char *default_process,
+ svn_boolean_t replace_externals,
+ svn_boolean_t replace_license,
+#endif /* VBOX */
+ const char *to_url,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_pool_t *pool)
+{
+ svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool);
+ edit_baton_t *eb = apr_palloc(pool, sizeof(*eb));
+
+ tree_editor->set_target_revision = set_target_revision;
+ tree_editor->open_root = open_root;
+ tree_editor->delete_entry = delete_entry;
+ tree_editor->add_directory = add_directory;
+ tree_editor->open_directory = open_directory;
+ tree_editor->change_dir_prop = change_dir_prop;
+ tree_editor->close_directory = close_directory;
+ tree_editor->absent_directory = absent_directory;
+ tree_editor->add_file = add_file;
+ tree_editor->open_file = open_file;
+ tree_editor->apply_textdelta = apply_textdelta;
+ tree_editor->change_file_prop = change_file_prop;
+ tree_editor->close_file = close_file;
+ tree_editor->absent_file = absent_file;
+ tree_editor->close_edit = close_edit;
+
+ eb->wrapped_editor = wrapped_editor;
+ eb->wrapped_edit_baton = wrapped_edit_baton;
+ eb->called_open_root = FALSE;
+ eb->base_revision = base_revision;
+#ifdef VBOX
+ eb->changeset_live = FALSE;
+ eb->start_rev = start_rev;
+ eb->current = current;
+ eb->default_process = default_process;
+ eb->replace_externals = replace_externals;
+ eb->replace_license = replace_license;
+ eb->from_session_prop = prop_session_from;
+ eb->to_session_prop = prop_session_to;
+#endif /* VBOX */
+ eb->to_url = to_url;
+
+ *editor = tree_editor;
+ *edit_baton = eb;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** `svnsync sync' ***/
+
+/* Baton for synchronizing the destination repository while locked. */
+typedef struct {
+ apr_hash_t *config;
+ svn_ra_callbacks2_t *callbacks;
+ const char *to_url;
+ svn_revnum_t committed_rev;
+#ifdef VBOX
+ svn_revnum_t from_rev;
+#endif /* VBOX */
+} sync_baton_t;
+
+
+/* Implements `svn_commit_callback2_t' interface. */
+static svn_error_t *
+commit_callback(const svn_commit_info_t *commit_info,
+ void *baton,
+ apr_pool_t *pool)
+{
+ sync_baton_t *sb = baton;
+
+#ifdef VBOX
+ if (sb->from_rev != commit_info->revision)
+ SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld (%ld in source repository).\n"),
+ commit_info->revision, sb->from_rev));
+ else
+ SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
+ commit_info->revision));
+#else /* !VBOX */
+ SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
+ commit_info->revision));
+#endif /* !VBOX */
+
+ sb->committed_rev = commit_info->revision;
+
+ 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 *
+open_source_session(svn_ra_session_t **from_session,
+ svn_string_t **last_merged_rev,
+#ifdef VBOX
+ svn_revnum_t *start_rev,
+#endif /* VBOX */
+ svn_ra_session_t *to_session,
+ svn_ra_callbacks2_t *callbacks,
+ apr_hash_t *config,
+ void *baton,
+ apr_pool_t *pool)
+{
+#ifdef VBOX
+ svn_string_t *start_rev_str;
+#endif /* VBOX */
+ svn_string_t *from_url, *from_uuid;
+ const char *uuid;
+
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
+ &from_url, pool));
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_UUID,
+ &from_uuid, pool));
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
+ last_merged_rev, pool));
+#ifdef VBOX
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_START_REV,
+ &start_rev_str, pool));
+#endif /* VBOX */
+
+#ifdef VBOX
+ if (! from_url || ! from_uuid || ! *last_merged_rev || ! start_rev_str)
+#else /* !VBOX */
+ if (! from_url || ! from_uuid || ! *last_merged_rev)
+#endif /* !VBOX */
+ return svn_error_create
+ (APR_EINVAL, NULL, _("Destination repository has not been initialized"));
+
+#ifdef VBOX
+ *start_rev = SVN_STR_TO_REV(start_rev_str->data);
+#endif /* VBOX */
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_open4(from_session, NULL, from_url->data, NULL, callbacks, baton,
+ config, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_open2(from_session, from_url->data, callbacks, baton,
+ config, pool));
+#endif /* !VBOX */
+
+ SVN_ERR(check_if_session_is_at_repos_root(*from_session, from_url->data,
+ 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
+ SVN_ERR(svn_ra_get_uuid2(*from_session, &uuid, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_get_uuid(*from_session, &uuid, pool));
+#endif /* !VBOX */
+
+ if (strcmp(uuid, from_uuid->data) != 0)
+ return svn_error_createf(APR_EINVAL, NULL,
+ _("UUID of source repository (%s) does not "
+ "match expected UUID (%s)"),
+ uuid, from_uuid->data);
+
+ 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 *
+do_synchronize(svn_ra_session_t *to_session, void *b, apr_pool_t *pool)
+{
+ svn_string_t *last_merged_rev;
+ svn_revnum_t from_latest, current;
+ svn_ra_session_t *from_session;
+ sync_baton_t *baton = b;
+ apr_pool_t *subpool;
+ svn_string_t *currently_copying;
+ svn_revnum_t to_latest, copying, last_merged;
+#ifdef VBOX
+ svn_revnum_t start_rev;
+ svn_string_t *from_url;
+ svn_string_t *default_process;
+ svn_string_t *replace_externals_str;
+ svn_boolean_t replace_externals;
+ svn_string_t *replace_license_str;
+ svn_boolean_t replace_license;
+ svn_string_t *ignoreprop;
+ svn_ra_session_t *from_session_prop;
+ svn_ra_session_t *to_session_prop;
+#endif /* VBOX */
+
+#ifdef VBOX
+ SVN_ERR(open_source_session(&from_session, &last_merged_rev, &start_rev,
+ to_session, baton->callbacks, baton->config,
+ baton, pool));
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
+ &from_url, pool));
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_DEFAULT_PROCESS,
+ &default_process, pool));
+ if (!default_process)
+ default_process = svn_string_create("export", pool);
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_REPLACE_EXTERNALS,
+ &replace_externals_str, pool));
+ replace_externals = !!replace_externals_str;
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_REPLACE_LICENSE,
+ &replace_license_str, pool));
+ replace_license = !!replace_license_str;
+ SVN_ERR(svn_ra_open4(&from_session_prop, NULL, from_url->data, NULL,
+ baton->callbacks, baton, baton->config, pool));
+ SVN_ERR(svn_ra_open4(&to_session_prop, NULL, baton->to_url, NULL,
+ baton->callbacks, baton, baton->config, pool));
+#else /* !VBOX */
+ SVN_ERR(open_source_session(&from_session, &last_merged_rev, to_session,
+ baton->callbacks, baton->config, baton, pool));
+#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.
+ */
+
+ SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
+ &currently_copying, pool));
+
+#ifndef VBOX
+ SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
+#endif /* !VBOX */
+
+ last_merged = SVN_STR_TO_REV(last_merged_rev->data);
+
+#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.*/
+ to_latest = last_merged;
+ }
+ else
+ SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
+#endif /* VBOX */
+
+ if (currently_copying)
+ {
+ copying = SVN_STR_TO_REV(currently_copying->data);
+
+ if ((copying < last_merged)
+ || (copying > (last_merged + 1))
+ || ((to_latest != last_merged) && (to_latest != copying)))
+ {
+ 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?"),
+ copying, last_merged, to_latest);
+ }
+ else if (copying == to_latest)
+ {
+ if (copying > last_merged)
+ {
+#ifdef VBOX
+/** @todo fix use of from/to revision numbers. */
+ SVN_ERR(copy_revprops(from_session, to_session,
+ to_latest, to_latest, TRUE, pool));
+#else /* !VBOX */
+ SVN_ERR(copy_revprops(from_session, to_session,
+ to_latest, TRUE, pool));
+#endif /* !VBOX */
+ last_merged = copying;
+ last_merged_rev = svn_string_create
+ (apr_psprintf(pool, "%ld", last_merged), pool);
+ }
+
+ /* 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. */
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ SVNSYNC_PROP_LAST_MERGED_REV, NULL,
+ last_merged_rev, pool));
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING, NULL,
+ NULL, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
+ SVNSYNC_PROP_LAST_MERGED_REV,
+ last_merged_rev, pool));
+ SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING,
+ NULL, pool));
+#endif /* !VBOX */
+ }
+ /* 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?"),
+ to_latest, last_merged);
+ }
+ }
+
+ /* Now check to see if there are any revisions to copy. */
+
+ SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
+
+ if (from_latest < atol(last_merged_rev->data))
+ return SVN_NO_ERROR;
+
+ subpool = svn_pool_create(pool);
+
+ /* Ok, so there are new revisions, iterate over them copying them
+ into the destination repository. */
+
+ for (current = atol(last_merged_rev->data) + 1;
+ current <= from_latest;
+ ++current)
+ {
+ const svn_delta_editor_t *commit_editor;
+ const svn_delta_editor_t *cancel_editor;
+ const svn_delta_editor_t *sync_editor;
+ void *commit_baton;
+ void *cancel_baton;
+ void *sync_baton;
+#ifdef VBOX
+ apr_hash_t *logrevprop;
+#endif /* VBOX */
+
+ svn_pool_clear(subpool);
+
+ /* 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. */
+#ifdef VBOX
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING, NULL,
+ svn_string_createf(subpool, "%ld",
+ current),
+ subpool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING,
+ svn_string_createf(subpool, "%ld",
+ current),
+ subpool));
+#endif /* !VBOX */
+
+ /* The actual copy is just a replay hooked up to a commit. */
+
+#ifdef VBOX
+ logrevprop = apr_hash_make(pool);
+ apr_hash_set(logrevprop, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
+ svn_string_create("", pool));
+ SVN_ERR(svn_ra_get_commit_editor3(to_session, &commit_editor,
+ &commit_baton,
+ logrevprop,
+ commit_callback, baton,
+ NULL, FALSE, subpool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_get_commit_editor2(to_session, &commit_editor,
+ &commit_baton,
+ "", /* empty log */
+ commit_callback, baton,
+ NULL, FALSE, subpool));
+#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
+ baton->from_rev = current;
+ baton->committed_rev = SVN_INVALID_REVNUM;
+ SVN_ERR(get_sync_editor(commit_editor, commit_baton, current - 1,
+ start_rev, current, from_session_prop,
+ to_session_prop, default_process->data,
+ replace_externals, replace_license,
+ baton->to_url, &sync_editor, &sync_baton,
+ subpool));
+#else /* !VBOX */
+ SVN_ERR(get_sync_editor(commit_editor, commit_baton, current - 1,
+ baton->to_url, &sync_editor, &sync_baton,
+ subpool));
+#endif /* !VBOX */
+
+ SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
+ sync_editor, sync_baton,
+ &cancel_editor,
+ &cancel_baton,
+ subpool));
+
+#ifdef VBOX
+ /* If svn:sync-ignore-changeset revprop exists in changeset, skip it. */
+ SVN_ERR(svn_ra_rev_prop(from_session, current,
+ SVNSYNC_PROP_IGNORE_CHANGESET,
+ &ignoreprop, subpool));
+ if (!ignoreprop)
+ SVN_ERR(svn_ra_replay(from_session, current, start_rev, TRUE,
+ cancel_editor, cancel_baton, subpool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_replay(from_session, current, 0, TRUE,
+ cancel_editor, cancel_baton, subpool));
+#endif /* !VBOX */
+
+ SVN_ERR(cancel_editor->close_edit(cancel_baton, subpool));
+
+#ifdef VBOX
+ if (!start_rev)
+ {
+ /* Sanity check that we actually committed the revision we meant to. */
+ if (baton->committed_rev != current)
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Commit created rev %ld but should have created %ld"),
+ baton->committed_rev, current);
+ }
+#else /* !VBOX */
+ /* Sanity check that we actually committed the revision we meant to. */
+ if (baton->committed_rev != current)
+ return svn_error_createf
+ (APR_EINVAL, NULL,
+ _("Commit created rev %ld but should have created %ld"),
+ baton->committed_rev, current);
+#endif /* !VBOX */
+
+ /* Ok, we're done with the data, now we just need to do the
+ revprops and we're all set. */
+
+#ifdef VBOX
+ if (SVN_IS_VALID_REVNUM(baton->committed_rev))
+ {
+ SVN_ERR(copy_revprops(from_session, to_session, current,
+ baton->committed_rev, TRUE, subpool));
+
+ /* Add a revision cross-reference revprop. */
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ apr_psprintf(subpool,
+ SVNSYNC_PROP_REV__FMT,
+ current), NULL,
+ svn_string_create(apr_psprintf(subpool,
+ "%ld",
+ baton->committed_rev),
+ 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. */
+ SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, subpool));
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ apr_psprintf(subpool,
+ SVNSYNC_PROP_REV__FMT,
+ current), NULL,
+ svn_string_create(apr_psprintf(subpool,
+ "%ld",
+ to_latest),
+ subpool),
+ subpool));
+ }
+#else /* !VBOX */
+ SVN_ERR(copy_revprops(from_session, to_session, current, TRUE, subpool));
+#endif /* !VBOX */
+
+ /* Ok, we're done, bring the last-merged-rev property up to date. */
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_change_rev_prop2
+ (to_session,
+ 0,
+ SVNSYNC_PROP_LAST_MERGED_REV, NULL,
+ svn_string_create(apr_psprintf(subpool, "%ld", current),
+ subpool),
+ subpool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_change_rev_prop
+ (to_session,
+ 0,
+ SVNSYNC_PROP_LAST_MERGED_REV,
+ svn_string_create(apr_psprintf(subpool, "%ld", current),
+ subpool),
+ subpool));
+#endif /* !VBOX */
+
+ /* And finally drop the currently copying prop, since we're done
+ with this revision. */
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_change_rev_prop2(to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING, NULL,
+ NULL, subpool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_change_rev_prop(to_session, 0,
+ SVNSYNC_PROP_CURRENTLY_COPYING,
+ NULL, subpool));
+#endif /* !VBOX */
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SUBCOMMAND: sync */
+static svn_error_t *
+synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
+{
+ svn_ra_callbacks2_t callbacks = { 0 };
+ svn_ra_session_t *to_session;
+ opt_baton_t *opt_baton = b;
+ apr_array_header_t *args;
+ sync_baton_t baton;
+ const char *to_url;
+
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
+
+ to_url = svn_uri_canonicalize(APR_ARRAY_IDX(args, 0, const char *), pool);
+
+ if (! svn_path_is_url(to_url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), to_url);
+
+ callbacks.open_tmp_file = open_tmp_file;
+ callbacks.auth_baton = opt_baton->auth_baton;
+
+ baton.callbacks = &callbacks;
+ baton.config = opt_baton->config;
+ baton.to_url = to_url;
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_open4(&to_session, NULL, to_url, NULL,
+ baton.callbacks, &baton, baton.config, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_open2(&to_session,
+ to_url,
+ baton.callbacks,
+ &baton,
+ baton.config,
+ pool));
+#endif /* !VBOX */
+
+ SVN_ERR(check_if_session_is_at_repos_root(to_session, to_url, pool));
+
+ SVN_ERR(with_locked(to_session, do_synchronize, &baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** `svnsync copy-revprops' ***/
+
+
+/* Baton for copying revision properties to the destination repository
+ * while locked.
+ */
+typedef struct {
+ apr_hash_t *config;
+ svn_ra_callbacks2_t *callbacks;
+ const char *to_url;
+ svn_revnum_t rev;
+} copy_revprops_baton_t;
+
+
+/* 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 *
+do_copy_revprops(svn_ra_session_t *to_session, void *b, apr_pool_t *pool)
+{
+ copy_revprops_baton_t *baton = b;
+ svn_ra_session_t *from_session;
+ svn_string_t *last_merged_rev;
+#ifdef VBOX
+ svn_revnum_t start_rev;
+#endif /* VBOX */
+
+#ifdef VBOX
+ SVN_ERR(open_source_session(&from_session, &last_merged_rev, &start_rev,
+ to_session, baton->callbacks, baton->config,
+ baton, pool));
+ if (start_rev)
+ return svn_error_create
+ (APR_EINVAL, NULL, _("Cannot copy revprops for repositories using "
+ "the start-rev feature (unimplemented)"));
+#else /* !VBOX */
+ SVN_ERR(open_source_session(&from_session, &last_merged_rev, to_session,
+ baton->callbacks, baton->config, baton, pool));
+#endif /* !VBOX */
+
+ if (baton->rev > SVN_STR_TO_REV(last_merged_rev->data))
+ return svn_error_create
+ (APR_EINVAL, NULL, _("Cannot copy revprops for a revision that has not "
+ "been synchronized yet"));
+
+#ifdef VBOX
+ SVN_ERR(copy_revprops(from_session, to_session, baton->rev, baton->rev, FALSE, pool));
+#else /* !VBOX */
+ SVN_ERR(copy_revprops(from_session, to_session, baton->rev, FALSE, pool));
+#endif /* !VBOX */
+
+ return SVN_NO_ERROR;
+}
+
+
+/* SUBCOMMAND: copy-revprops */
+static svn_error_t *
+copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
+{
+ svn_ra_callbacks2_t callbacks = { 0 };
+ svn_ra_session_t *to_session;
+ opt_baton_t *opt_baton = b;
+ apr_array_header_t *args;
+ copy_revprops_baton_t baton;
+ const char *to_url;
+ svn_revnum_t revision = SVN_INVALID_REVNUM;
+ char *digits_end = NULL;
+
+ SVN_ERR(svn_opt_parse_num_args(&args, os, 2, pool));
+
+ to_url = svn_uri_canonicalize(APR_ARRAY_IDX(args, 0, const char *), pool);
+ revision = strtol(APR_ARRAY_IDX(args, 1, const char *), &digits_end, 10);
+
+ if (! svn_path_is_url(to_url))
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Path '%s' is not a URL"), to_url);
+ if ((! SVN_IS_VALID_REVNUM(revision)) || (! digits_end) || *digits_end)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision number"));
+
+ callbacks.open_tmp_file = open_tmp_file;
+ callbacks.auth_baton = opt_baton->auth_baton;
+
+ baton.callbacks = &callbacks;
+ baton.config = opt_baton->config;
+ baton.to_url = to_url;
+ baton.rev = revision;
+
+#ifdef VBOX
+ SVN_ERR(svn_ra_open4(&to_session, NULL, to_url, NULL,
+ baton.callbacks, &baton, baton.config, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_ra_open2(&to_session,
+ to_url,
+ baton.callbacks,
+ &baton,
+ baton.config,
+ pool));
+#endif /* !VBOX */
+
+ SVN_ERR(check_if_session_is_at_repos_root(to_session, to_url, pool));
+
+ SVN_ERR(with_locked(to_session, do_copy_revprops, &baton, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** `svnsync help' ***/
+
+
+/* SUBCOMMAND: help */
+static svn_error_t *
+help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ opt_baton_t *opt_baton = baton;
+
+ 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");
+
+ svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
+ pool);
+
+ SVN_ERR(svn_ra_print_modules(version_footer, pool));
+
+#ifdef VBOX
+ SVN_ERR(svn_opt_print_help4(os, "svnsync",
+ opt_baton ? opt_baton->version : FALSE,
+ FALSE, FALSE, version_footer->data, header,
+ svnsync_cmd_table, svnsync_options, NULL,
+ NULL, pool));
+#else /* !VBOX */
+ SVN_ERR(svn_opt_print_help(os, "svnsync",
+ opt_baton ? opt_baton->version : FALSE,
+ FALSE, version_footer->data, header,
+ svnsync_cmd_table, svnsync_options, NULL,
+ pool));
+#endif /* !VBOX */
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Main ***/
+
+int
+main(int argc, const char *argv[])
+{
+#ifdef VBOX
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+#else /* !VBOX */
+ const svn_opt_subcommand_desc_t *subcommand = NULL;
+#endif /* !VBOX */
+ apr_array_header_t *received_opts;
+ opt_baton_t opt_baton;
+ svn_config_t *config;
+ apr_status_t apr_err;
+ apr_getopt_t *os;
+ apr_pool_t *pool;
+ svn_error_t *err;
+ int opt_id, i;
+
+ if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
+ {
+ return EXIT_FAILURE;
+ }
+
+ err = check_lib_versions();
+ if (err)
+ {
+ svn_handle_error2(err, stderr, FALSE, "svnsync: ");
+ return EXIT_FAILURE;
+ }
+
+ pool = svn_pool_create(NULL);
+
+ err = svn_ra_initialize(pool);
+ if (err)
+ {
+ svn_handle_error2(err, stderr, FALSE, "svnsync: ");
+ return EXIT_FAILURE;
+ }
+
+ memset(&opt_baton, 0, sizeof(opt_baton));
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ if (argc <= 1)
+ {
+ help_cmd(NULL, NULL, pool);
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ {
+ apr_status_t apr_err;
+ apr_err = apr_getopt_init(&os, pool, argc, argv);
+ if (apr_err)
+ {
+ err = svn_error_wrap_apr(apr_err, "Error initializing command line parsing");
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+ }
+ }
+
+ os->interleave = 1;
+
+ for (;;)
+ {
+ const char *opt_arg;
+
+ apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
+ if (APR_STATUS_IS_EOF(apr_err))
+ break;
+ else if (apr_err)
+ {
+ help_cmd(NULL, NULL, pool);
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ APR_ARRAY_PUSH(received_opts, int) = opt_id;
+
+ switch (opt_id)
+ {
+ case svnsync_opt_non_interactive:
+ opt_baton.non_interactive = TRUE;
+ break;
+
+ case svnsync_opt_no_auth_cache:
+ opt_baton.no_auth_cache = TRUE;
+ break;
+
+ case svnsync_opt_auth_username:
+ opt_baton.auth_username = opt_arg;
+ break;
+
+ case svnsync_opt_auth_password:
+ opt_baton.auth_password = opt_arg;
+ break;
+
+ case svnsync_opt_config_dir:
+ opt_baton.config_dir = opt_arg;
+ break;
+
+#ifdef VBOX
+ case svnsync_opt_start_rev:
+ opt_baton.start_rev = SVN_STR_TO_REV(opt_arg);
+ break;
+
+ case svnsync_opt_default_process:
+ opt_baton.default_process = opt_arg;
+ break;
+
+ case svnsync_opt_replace_externals:
+ opt_baton.replace_externals = TRUE;
+ break;
+
+ case svnsync_opt_replace_license:
+ opt_baton.replace_license = TRUE;
+ break;
+#endif /* VBOX */
+
+ case svnsync_opt_version:
+ opt_baton.version = TRUE;
+ break;
+
+ case '?':
+ case 'h':
+ opt_baton.help = TRUE;
+ break;
+
+ default:
+ {
+ help_cmd(NULL, NULL, pool);
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ if (opt_baton.help)
+#ifdef VBOX
+ subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
+#else /* !VBOX */
+ subcommand = svn_opt_get_canonical_subcommand(svnsync_cmd_table, "help");
+#endif /* !VBOX */
+
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_baton.version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+#ifdef VBOX
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+#else /* !VBOX */
+ static const svn_opt_subcommand_desc_t pseudo_cmd =
+#endif /* !VBOX */
+ { "--version", help_cmd, {0}, "",
+ {svnsync_opt_version, /* must accept its own option */
+ } };
+
+ subcommand = &pseudo_cmd;
+ }
+ else
+ {
+ help_cmd(NULL, NULL, pool);
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ const char *first_arg = os->argv[os->ind++];
+#ifdef VBOX
+ subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
+ first_arg);
+#else /* !VBOX */
+ subcommand = svn_opt_get_canonical_subcommand(svnsync_cmd_table,
+ first_arg);
+#endif /* !VBOX */
+ if (subcommand == NULL)
+ {
+ help_cmd(NULL, NULL, pool);
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ for (i = 0; i < received_opts->nelts; ++i)
+ {
+ opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+#ifdef VBOX
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
+#else /* !VBOX */
+ if (! svn_opt_subcommand_takes_option(subcommand, opt_id))
+#endif /* !VBOX */
+ {
+ const char *optstr;
+#ifdef VBOX
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
+ pool);
+#else /* !VBOX */
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code(opt_id, svnsync_options);
+#endif /* !VBOX */
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ help_cmd(NULL, NULL, pool);
+ else
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stderr, pool, _("subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svnsync help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+
+ err = svn_config_get_config(&opt_baton.config, NULL, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
+
+ config = apr_hash_get(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG,
+ APR_HASH_KEY_STRING);
+
+ apr_signal(SIGINT, signal_handler);
+
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, signal_handler);
+#endif
+
+#ifdef SIGHUP
+ apr_signal(SIGHUP, signal_handler);
+#endif
+
+#ifdef SIGTERM
+ apr_signal(SIGTERM, signal_handler);
+#endif
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#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. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+#ifdef VBOX
+ err = svn_cmdline_create_auth_baton(&opt_baton.auth_baton,
+ opt_baton.non_interactive,
+ opt_baton.auth_username,
+ opt_baton.auth_password,
+ opt_baton.config_dir,
+ opt_baton.no_auth_cache,
+ 1,
+ config,
+ check_cancel, NULL,
+ pool);
+ if (!err)
+ err = svn_cmdline_create_auth_baton(&opt_baton.auth_baton,
+ opt_baton.non_interactive,
+ opt_baton.auth_username,
+ opt_baton.auth_password,
+ opt_baton.config_dir,
+ opt_baton.no_auth_cache,
+ 1,
+ config,
+ check_cancel, NULL,
+ pool);
+#else /* !VBOX */
+ err = svn_cmdline_setup_auth_baton(&opt_baton.auth_baton,
+ opt_baton.non_interactive,
+ opt_baton.auth_username,
+ opt_baton.auth_password,
+ opt_baton.config_dir,
+ opt_baton.no_auth_cache,
+ config,
+ check_cancel, NULL,
+ pool);
+#endif /* !VBOX */
+
+ err = (*subcommand->cmd_func)(os, &opt_baton, pool);
+ if (err)
+ {
+ /* For argument-related problems, suggest using the 'help'
+ subcommand. */
+ if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
+ || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Try 'svnsync help' for more info"));
+ }
+ svn_handle_error2(err, stderr, FALSE, "svnsync: ");
+ svn_error_clear(err);
+
+ return EXIT_FAILURE;
+ }
+
+ svn_pool_destroy(pool);
+
+ return EXIT_SUCCESS;
+}