#include "svn_cmdline.h"
#include "svn_config.h"
#include "svn_pools.h"
#include "svn_delta.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_props.h"
#include "svn_auth.h"
#include "svn_opt.h"
#include "svn_ra.h"
#include "svn_utf.h"
#include "svn_subst.h"
#include "svn_string.h"
#include "svn_version.h"
#include "private/svn_opt_private.h"
#include "private/svn_ra_private.h"
#include "private/svn_cmdline_private.h"
#include "sync.h"
#include "svn_private_config.h"
#include <apr_signal.h>
#include <apr_uuid.h>
static svn_opt_subcommand_t initialize_cmd,
synchronize_cmd,
copy_revprops_cmd,
info_cmd,
help_cmd;
enum svnsync__opt {
svnsync_opt_non_interactive = SVN_OPT_FIRST_LONGOPT_ID,
svnsync_opt_no_auth_cache,
svnsync_opt_auth_username,
svnsync_opt_auth_password,
svnsync_opt_source_username,
svnsync_opt_source_password,
svnsync_opt_sync_username,
svnsync_opt_sync_password,
svnsync_opt_config_dir,
svnsync_opt_config_options,
svnsync_opt_source_prop_encoding,
svnsync_opt_disable_locking,
svnsync_opt_version,
svnsync_opt_trust_server_cert,
svnsync_opt_allow_non_empty,
svnsync_opt_steal_lock
};
#define SVNSYNC_OPTS_DEFAULT svnsync_opt_non_interactive, \
svnsync_opt_no_auth_cache, \
svnsync_opt_auth_username, \
svnsync_opt_auth_password, \
svnsync_opt_trust_server_cert, \
svnsync_opt_source_username, \
svnsync_opt_source_password, \
svnsync_opt_sync_username, \
svnsync_opt_sync_password, \
svnsync_opt_config_dir, \
svnsync_opt_config_options
static const svn_opt_subcommand_desc2_t svnsync_cmd_table[] =
{
{ "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"
"If the source URL is not the root of a repository, only the\n"
"specified part of the repository will be synchronized.\n"
"\n"
"The destination URL must point to the root of a repository which\n"
"has been configured to allow revision property changes. In\n"
"the general case, the destination repository must contain no\n"
"committed revisions. Use --allow-non-empty to override this\n"
"restriction, which will cause svnsync to assume that any revisions\n"
"already present in the destination repository perfectly mirror\n"
"their counterparts in the source repository. (This is useful\n"
"when initializing a copy of a repository as a mirror of that same\n"
"repository, for example.)\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"),
{ SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
svnsync_opt_allow_non_empty, svnsync_opt_disable_locking,
svnsync_opt_steal_lock } },
{ "synchronize", synchronize_cmd, { "sync" },
N_("usage: svnsync synchronize DEST_URL [SOURCE_URL]\n"
"\n"
"Transfer all pending revisions to the destination from the source\n"
"with which it was initialized.\n"
"\n"
"If SOURCE_URL is provided, use that as the source repository URL,\n"
"ignoring what is recorded in the destination repository as the\n"
"source URL. Specifying SOURCE_URL is recommended in particular\n"
"if untrusted users/administrators may have write access to the\n"
"DEST_URL repository.\n"),
{ SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q',
svnsync_opt_disable_locking, svnsync_opt_steal_lock } },
{ "copy-revprops", copy_revprops_cmd, { 0 },
N_("usage:\n"
"\n"
" 1. svnsync copy-revprops DEST_URL [SOURCE_URL]\n"
" 2. svnsync copy-revprops DEST_URL REV[:REV2]\n"
"\n"
"Copy the revision properties in a given range of revisions to the\n"
"destination from the source with which it was initialized. If the\n"
"revision range is not specified, it defaults to all revisions in\n"
"the DEST_URL repository. Note also that the 'HEAD' revision is the\n"
"latest in DEST_URL, not necessarily the latest in SOURCE_URL.\n"
"\n"
"If SOURCE_URL is provided, use that as the source repository URL,\n"
"ignoring what is recorded in the destination repository as the\n"
"source URL. Specifying SOURCE_URL is recommended in particular\n"
"if untrusted users/administrators may have write access to the\n"
"DEST_URL repository.\n"
"\n"
"Form 2 is deprecated syntax, equivalent to specifying \"-rREV[:REV2]\".\n"),
{ SVNSYNC_OPTS_DEFAULT, svnsync_opt_source_prop_encoding, 'q', 'r',
svnsync_opt_disable_locking, svnsync_opt_steal_lock } },
{ "info", info_cmd, { 0 },
N_("usage: svnsync info DEST_URL\n"
"\n"
"Print information about the synchronization destination repository\n"
"located at DEST_URL.\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[] =
{
{"quiet", 'q', 0,
N_("print as little as possible") },
{"revision", 'r', 1,
N_("operate on revision ARG (or range ARG1:ARG2)\n"
" "
"A revision argument can be one of:\n"
" "
" NUMBER revision number\n"
" "
" 'HEAD' latest in repository") },
{"allow-non-empty", svnsync_opt_allow_non_empty, 0,
N_("allow a non-empty destination repository") },
{"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 (deprecated;\n"
" "
"see --source-username and --sync-username)") },
{"password", svnsync_opt_auth_password, 1,
N_("specify a password ARG (deprecated;\n"
" "
"see --source-password and --sync-password)") },
{"trust-server-cert", svnsync_opt_trust_server_cert, 0,
N_("accept SSL server certificates from unknown\n"
" "
"certificate authorities without prompting (but only\n"
" "
"with '--non-interactive')") },
{"source-username", svnsync_opt_source_username, 1,
N_("connect to source repository with username ARG") },
{"source-password", svnsync_opt_source_password, 1,
N_("connect to source repository with password ARG") },
{"sync-username", svnsync_opt_sync_username, 1,
N_("connect to sync repository with username ARG") },
{"sync-password", svnsync_opt_sync_password, 1,
N_("connect to sync repository with password ARG") },
{"config-dir", svnsync_opt_config_dir, 1,
N_("read user configuration files from directory ARG")},
{"config-option", svnsync_opt_config_options, 1,
N_("set user configuration option in the format:\n"
" "
" FILE:SECTION:OPTION=[VALUE]\n"
" "
"For example:\n"
" "
" servers:global:http-library=serf")},
{"source-prop-encoding", svnsync_opt_source_prop_encoding, 1,
N_("convert translatable properties from encoding ARG\n"
" "
"to UTF-8. If not specified, then properties are\n"
" "
"presumed to be encoded in UTF-8.")},
{"disable-locking", svnsync_opt_disable_locking, 0,
N_("Disable built-in locking. Use of this option can\n"
" "
"corrupt the mirror unless you ensure that no other\n"
" "
"instance of svnsync is running concurrently.")},
{"steal-lock", svnsync_opt_steal_lock, 0,
N_("Steal locks as necessary. Use, with caution,\n"
" "
"if your mirror repository contains stale locks\n"
" "
"and is not being concurrently accessed by another\n"
" "
"svnsync instance.")},
{"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 opt_baton_t {
svn_boolean_t non_interactive;
svn_boolean_t trust_server_cert;
svn_boolean_t no_auth_cache;
svn_auth_baton_t *source_auth_baton;
svn_auth_baton_t *sync_auth_baton;
const char *source_username;
const char *source_password;
const char *sync_username;
const char *sync_password;
const char *config_dir;
apr_hash_t *config;
const char *source_prop_encoding;
svn_boolean_t disable_locking;
svn_boolean_t steal_lock;
svn_boolean_t quiet;
svn_boolean_t allow_non_empty;
svn_boolean_t version;
svn_boolean_t help;
svn_opt_revision_t start_rev;
svn_opt_revision_t end_rev;
} opt_baton_t;
static volatile sig_atomic_t cancelled = FALSE;
static void
signal_handler(int signum)
{
apr_signal(signum, SIG_IGN);
cancelled = TRUE;
}
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;
}
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);
}
static svn_error_t *
lock_retry_func(void *baton,
const svn_string_t *reposlocktoken,
apr_pool_t *pool)
{
return svn_cmdline_printf(pool,
_("Failed to get lock on destination "
"repos, currently held by '%s'\n"),
reposlocktoken->data);
}
static svn_error_t *
get_lock(const svn_string_t **lock_string_p,
svn_ra_session_t *session,
svn_boolean_t steal_lock,
apr_pool_t *pool)
{
svn_error_t *err;
svn_boolean_t be_atomic;
const svn_string_t *stolen_lock;
SVN_ERR(svn_ra_has_capability(session, &be_atomic,
SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
pool));
if (! be_atomic)
{
err = svn_error_create(
SVN_ERR_UNSUPPORTED_FEATURE, NULL,
_("Target server does not support atomic revision property "
"edits; consider upgrading it to 1.7 or using an external "
"locking program"));
svn_handle_warning2(stderr, err, "svnsync: ");
svn_error_clear(err);
}
err = svn_ra__get_operational_lock(lock_string_p, &stolen_lock, session,
SVNSYNC_PROP_LOCK, steal_lock,
10 , lock_retry_func, NULL,
check_cancel, NULL, pool);
if (!err && stolen_lock)
{
return svn_cmdline_printf(pool,
_("Stole lock previously held by '%s'\n"),
stolen_lock->data);
}
return err;
}
typedef struct subcommand_baton_t {
apr_hash_t *config;
svn_ra_callbacks2_t source_callbacks;
svn_ra_callbacks2_t sync_callbacks;
svn_boolean_t quiet;
svn_boolean_t allow_non_empty;
const char *to_url;
const char *source_prop_encoding;
const char *from_url;
svn_revnum_t committed_rev;
svn_revnum_t start_rev;
svn_revnum_t end_rev;
} subcommand_baton_t;
typedef svn_error_t *(*with_locked_func_t)(svn_ra_session_t *session,
subcommand_baton_t *baton,
apr_pool_t *pool);
static svn_error_t *
with_locked(svn_ra_session_t *session,
with_locked_func_t func,
subcommand_baton_t *baton,
svn_boolean_t steal_lock,
apr_pool_t *pool)
{
const svn_string_t *lock_string;
svn_error_t *err;
SVN_ERR(get_lock(&lock_string, session, steal_lock, pool));
err = func(session, baton, pool);
return svn_error_compose_create(err,
svn_ra__release_operational_lock(session, SVNSYNC_PROP_LOCK,
lock_string, pool));
}
static svn_error_t *
open_tmp_file(apr_file_t **fp, void *callback_baton, apr_pool_t *pool)
{
return svn_io_open_unique_file3(fp, NULL, NULL,
svn_io_file_del_on_pool_cleanup,
pool, pool);
}
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;
SVN_ERR(svn_ra_get_repos_root2(sess, &sess_root, pool));
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);
}
static svn_error_t *
remove_props_not_in_source(svn_ra_session_t *session,
svn_revnum_t rev,
apr_hash_t *source_props,
apr_hash_t *target_props,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create(pool);
apr_hash_index_t *hi;
for (hi = apr_hash_first(pool, target_props);
hi;
hi = apr_hash_next(hi))
{
const char *propname = svn__apr_hash_index_key(hi);
svn_pool_clear(subpool);
if (rev == 0 && !strncmp(propname, SVNSYNC_PROP_PREFIX,
sizeof(SVNSYNC_PROP_PREFIX) - 1))
continue;
if (! apr_hash_get(source_props, propname, APR_HASH_KEY_STRING))
SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
NULL, subpool));
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
typedef svn_boolean_t (*filter_func_t)(const char *key);
static apr_hash_t *
filter_props(int *filtered_count, apr_hash_t *props,
filter_func_t filter,
apr_pool_t *pool)
{
apr_hash_index_t *hi;
apr_hash_t *filtered = apr_hash_make(pool);
*filtered_count = 0;
for (hi = apr_hash_first(pool, props); hi ; hi = apr_hash_next(hi))
{
const char *propname = svn__apr_hash_index_key(hi);
void *propval = svn__apr_hash_index_val(hi);
if (!filter || filter(propname) == FALSE)
{
apr_hash_set(filtered, propname, APR_HASH_KEY_STRING, propval);
}
else
{
*filtered_count += 1;
}
}
return filtered;
}
static svn_error_t *
write_revprops(int *filtered_count,
svn_ra_session_t *session,
svn_revnum_t rev,
apr_hash_t *rev_props,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create(pool);
apr_hash_index_t *hi;
*filtered_count = 0;
for (hi = apr_hash_first(pool, rev_props); hi; hi = apr_hash_next(hi))
{
const char *propname = svn__apr_hash_index_key(hi);
const svn_string_t *propval = svn__apr_hash_index_val(hi);
svn_pool_clear(subpool);
if (strncmp(propname, SVNSYNC_PROP_PREFIX,
sizeof(SVNSYNC_PROP_PREFIX) - 1) != 0)
{
SVN_ERR(svn_ra_change_rev_prop2(session, rev, propname, NULL,
propval, subpool));
}
else
{
*filtered_count += 1;
}
}
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
log_properties_copied(svn_boolean_t syncprops_found,
svn_revnum_t rev,
apr_pool_t *pool)
{
if (syncprops_found)
SVN_ERR(svn_cmdline_printf(pool,
_("Copied properties for revision %ld "
"(%s* properties skipped).\n"),
rev, SVNSYNC_PROP_PREFIX));
else
SVN_ERR(svn_cmdline_printf(pool,
_("Copied properties for revision %ld.\n"),
rev));
return SVN_NO_ERROR;
}
static svn_error_t *
log_properties_normalized(int normalized_rev_props_count,
int normalized_node_props_count,
apr_pool_t *pool)
{
if (normalized_rev_props_count > 0 || normalized_node_props_count > 0)
SVN_ERR(svn_cmdline_printf(pool,
_("NOTE: Normalized %s* properties "
"to LF line endings (%d rev-props, "
"%d node-props).\n"),
SVN_PROP_PREFIX,
normalized_rev_props_count,
normalized_node_props_count));
return SVN_NO_ERROR;
}
static svn_error_t *
copy_revprops(svn_ra_session_t *from_session,
svn_ra_session_t *to_session,
svn_revnum_t rev,
svn_boolean_t sync,
svn_boolean_t quiet,
const char *source_prop_encoding,
int *normalized_count,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create(pool);
apr_hash_t *existing_props, *rev_props;
int filtered_count = 0;
if (sync)
SVN_ERR(svn_ra_rev_proplist(to_session, rev, &existing_props, subpool));
SVN_ERR(svn_ra_rev_proplist(from_session, rev, &rev_props, subpool));
SVN_ERR(svnsync_normalize_revprops(rev_props, normalized_count,
source_prop_encoding, pool));
SVN_ERR(write_revprops(&filtered_count, to_session, rev, rev_props, pool));
if (sync)
SVN_ERR(remove_props_not_in_source(to_session, rev,
rev_props, existing_props, pool));
if (! quiet)
SVN_ERR(log_properties_copied(filtered_count > 0, rev, pool));
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static subcommand_baton_t *
make_subcommand_baton(opt_baton_t *opt_baton,
const char *to_url,
const char *from_url,
svn_revnum_t start_rev,
svn_revnum_t end_rev,
apr_pool_t *pool)
{
subcommand_baton_t *b = apr_pcalloc(pool, sizeof(*b));
b->config = opt_baton->config;
b->source_callbacks.open_tmp_file = open_tmp_file;
b->source_callbacks.auth_baton = opt_baton->source_auth_baton;
b->sync_callbacks.open_tmp_file = open_tmp_file;
b->sync_callbacks.auth_baton = opt_baton->sync_auth_baton;
b->quiet = opt_baton->quiet;
b->allow_non_empty = opt_baton->allow_non_empty;
b->to_url = to_url;
b->source_prop_encoding = opt_baton->source_prop_encoding;
b->from_url = from_url;
b->start_rev = start_rev;
b->end_rev = end_rev;
return b;
}
static svn_error_t *
open_target_session(svn_ra_session_t **to_session_p,
subcommand_baton_t *baton,
apr_pool_t *pool);
static svn_error_t *
do_initialize(svn_ra_session_t *to_session,
subcommand_baton_t *baton,
apr_pool_t *pool)
{
svn_ra_session_t *from_session;
svn_string_t *from_url;
svn_revnum_t latest, from_latest;
const char *uuid, *root_url;
int normalized_rev_props_count;
SVN_ERR(svn_ra_get_latest_revnum(to_session, &latest, pool));
if ((latest != 0) && (! baton->allow_non_empty))
return svn_error_create
(APR_EINVAL, NULL,
_("Destination repository already contains revision history; consider "
"using --allow-non-empty if the repository's revisions are known "
"to mirror their respective revisions in the source repository"));
SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_FROM_URL,
&from_url, pool));
if (from_url && (! baton->allow_non_empty))
return svn_error_createf
(APR_EINVAL, NULL,
_("Destination repository is already synchronizing from '%s'"),
from_url->data);
SVN_ERR(svn_ra_open4(&from_session, NULL, baton->from_url, NULL,
&(baton->source_callbacks), baton,
baton->config, pool));
SVN_ERR(svn_ra_get_repos_root2(from_session, &root_url, pool));
if (svn_uri__is_child(root_url, baton->from_url, pool))
{
svn_boolean_t server_supports_partial_replay;
svn_error_t *err = svn_ra_has_capability(from_session,
&server_supports_partial_replay,
SVN_RA_CAPABILITY_PARTIAL_REPLAY,
pool);
if (err && err->apr_err != SVN_ERR_UNKNOWN_CAPABILITY)
return svn_error_trace(err);
if (err || !server_supports_partial_replay)
return svn_error_create(SVN_ERR_RA_PARTIAL_REPLAY_NOT_SUPPORTED, err,
NULL);
}
if (latest != 0)
{
SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
if (from_latest < latest)
return svn_error_create
(APR_EINVAL, NULL,
_("Destination repository has more revisions than source "
"repository"));
}
SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_URL, NULL,
svn_string_create(baton->from_url, pool),
pool));
SVN_ERR(svn_ra_get_uuid2(from_session, &uuid, pool));
SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_FROM_UUID, NULL,
svn_string_create(uuid, pool), pool));
SVN_ERR(svn_ra_change_rev_prop2(to_session, 0, SVNSYNC_PROP_LAST_MERGED_REV,
NULL, svn_string_createf(pool, "%ld", latest),
pool));
SVN_ERR(copy_revprops(from_session, to_session, latest, FALSE, baton->quiet,
baton->source_prop_encoding, &normalized_rev_props_count,
pool));
SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
initialize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
{
const char *to_url, *from_url;
svn_ra_session_t *to_session;
opt_baton_t *opt_baton = b;
apr_array_header_t *targets;
subcommand_baton_t *baton;
SVN_ERR(svn_opt__args_to_target_array(&targets, os,
apr_array_make(pool, 0,
sizeof(const char *)),
pool));
if (targets->nelts < 2)
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
if (targets->nelts > 2)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
to_url = APR_ARRAY_IDX(targets, 0, const char *);
from_url = APR_ARRAY_IDX(targets, 1, const char *);
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 = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
SVN_ERR(open_target_session(&to_session, baton, pool));
if (opt_baton->disable_locking)
SVN_ERR(do_initialize(to_session, baton, pool));
else
SVN_ERR(with_locked(to_session, do_initialize, baton,
opt_baton->steal_lock, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
commit_callback(const svn_commit_info_t *commit_info,
void *baton,
apr_pool_t *pool)
{
subcommand_baton_t *sb = baton;
if (! sb->quiet)
{
SVN_ERR(svn_cmdline_printf(pool, _("Committed revision %ld.\n"),
commit_info->revision));
}
sb->committed_rev = commit_info->revision;
return SVN_NO_ERROR;
}
static svn_error_t *
open_source_session(svn_ra_session_t **from_session,
svn_string_t **last_merged_rev,
const char *from_url,
svn_ra_session_t *to_session,
svn_ra_callbacks2_t *callbacks,
apr_hash_t *config,
void *baton,
apr_pool_t *pool)
{
apr_hash_t *props;
svn_string_t *from_url_str, *from_uuid_str;
SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
from_url_str = apr_hash_get(props, SVNSYNC_PROP_FROM_URL,
APR_HASH_KEY_STRING);
from_uuid_str = apr_hash_get(props, SVNSYNC_PROP_FROM_UUID,
APR_HASH_KEY_STRING);
*last_merged_rev = apr_hash_get(props, SVNSYNC_PROP_LAST_MERGED_REV,
APR_HASH_KEY_STRING);
if (! from_url_str || ! from_uuid_str || ! *last_merged_rev)
return svn_error_create
(APR_EINVAL, NULL,
_("Destination repository has not been initialized"));
if (! from_url)
from_url = from_url_str->data;
SVN_ERR(svn_ra_open4(from_session, NULL, from_url, from_uuid_str->data,
callbacks, baton, config, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
open_target_session(svn_ra_session_t **target_session_p,
subcommand_baton_t *baton,
apr_pool_t *pool)
{
svn_ra_session_t *target_session;
SVN_ERR(svn_ra_open4(&target_session, NULL, baton->to_url, NULL,
&(baton->sync_callbacks), baton, baton->config, pool));
SVN_ERR(check_if_session_is_at_repos_root(target_session, baton->to_url, pool));
*target_session_p = target_session;
return SVN_NO_ERROR;
}
typedef struct replay_baton_t {
svn_ra_session_t *from_session;
svn_ra_session_t *to_session;
subcommand_baton_t *sb;
svn_boolean_t has_commit_revprops_capability;
int normalized_rev_props_count;
int normalized_node_props_count;
} replay_baton_t;
static replay_baton_t *
make_replay_baton(svn_ra_session_t *from_session,
svn_ra_session_t *to_session,
subcommand_baton_t *sb, apr_pool_t *pool)
{
replay_baton_t *rb = apr_pcalloc(pool, sizeof(*rb));
rb->from_session = from_session;
rb->to_session = to_session;
rb->sb = sb;
return rb;
}
static svn_boolean_t
filter_exclude_date_author_sync(const char *key)
{
if (strcmp(key, SVN_PROP_REVISION_AUTHOR) == 0)
return TRUE;
else if (strcmp(key, SVN_PROP_REVISION_DATE) == 0)
return TRUE;
else if (strncmp(key, SVNSYNC_PROP_PREFIX,
sizeof(SVNSYNC_PROP_PREFIX) - 1) == 0)
return TRUE;
return FALSE;
}
static svn_boolean_t
filter_include_date_author_sync(const char *key)
{
return ! filter_exclude_date_author_sync(key);
}
static svn_boolean_t
filter_exclude_log(const char *key)
{
if (strcmp(key, SVN_PROP_REVISION_LOG) == 0)
return TRUE;
else
return FALSE;
}
static svn_boolean_t
filter_include_log(const char *key)
{
return ! filter_exclude_log(key);
}
static svn_error_t *
replay_rev_started(svn_revnum_t revision,
void *replay_baton,
const svn_delta_editor_t **editor,
void **edit_baton,
apr_hash_t *rev_props,
apr_pool_t *pool)
{
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;
replay_baton_t *rb = replay_baton;
apr_hash_t *filtered;
int filtered_count;
int normalized_count;
SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
SVNSYNC_PROP_CURRENTLY_COPYING,
NULL,
svn_string_createf(pool, "%ld", revision),
pool));
filtered = filter_props(&filtered_count, rev_props,
(rb->has_commit_revprops_capability
? filter_exclude_date_author_sync
: filter_include_log),
pool);
if (! apr_hash_get(filtered, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING))
apr_hash_set(filtered, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING,
svn_string_create("", pool));
SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
rb->sb->source_prop_encoding, pool));
rb->normalized_rev_props_count += normalized_count;
SVN_ERR(svn_ra_get_commit_editor3(rb->to_session, &commit_editor,
&commit_baton,
filtered,
commit_callback, rb->sb,
NULL, FALSE, pool));
SVN_ERR(svnsync_get_sync_editor(commit_editor, commit_baton, revision - 1,
rb->sb->to_url, rb->sb->source_prop_encoding,
rb->sb->quiet, &sync_editor, &sync_baton,
&(rb->normalized_node_props_count), pool));
SVN_ERR(svn_delta_get_cancellation_editor(check_cancel, NULL,
sync_editor, sync_baton,
&cancel_editor,
&cancel_baton,
pool));
*editor = cancel_editor;
*edit_baton = cancel_baton;
return SVN_NO_ERROR;
}
static svn_error_t *
replay_rev_finished(svn_revnum_t revision,
void *replay_baton,
const svn_delta_editor_t *editor,
void *edit_baton,
apr_hash_t *rev_props,
apr_pool_t *pool)
{
apr_pool_t *subpool = svn_pool_create(pool);
replay_baton_t *rb = replay_baton;
apr_hash_t *filtered, *existing_props;
int filtered_count;
int normalized_count;
SVN_ERR(editor->close_edit(edit_baton, pool));
if (rb->sb->committed_rev != revision)
return svn_error_createf
(APR_EINVAL, NULL,
_("Commit created rev %ld but should have created %ld"),
rb->sb->committed_rev, revision);
SVN_ERR(svn_ra_rev_proplist(rb->to_session, revision, &existing_props,
subpool));
filtered = filter_props(&filtered_count, rev_props,
(rb->has_commit_revprops_capability
? filter_include_date_author_sync
: filter_exclude_log),
subpool);
SVN_ERR(svnsync_normalize_revprops(filtered, &normalized_count,
rb->sb->source_prop_encoding, pool));
rb->normalized_rev_props_count += normalized_count;
SVN_ERR(write_revprops(&filtered_count, rb->to_session, revision, filtered,
subpool));
SVN_ERR(remove_props_not_in_source(rb->to_session, revision,
rev_props, existing_props, subpool));
svn_pool_clear(subpool);
SVN_ERR(svn_ra_change_rev_prop2(
rb->to_session,
0,
SVNSYNC_PROP_LAST_MERGED_REV,
NULL,
svn_string_create(apr_psprintf(pool, "%ld", revision),
subpool),
subpool));
SVN_ERR(svn_ra_change_rev_prop2(rb->to_session, 0,
SVNSYNC_PROP_CURRENTLY_COPYING,
NULL, NULL, subpool));
if (! rb->sb->quiet)
SVN_ERR(log_properties_copied(filtered_count > 0, revision, subpool));
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
static svn_error_t *
do_synchronize(svn_ra_session_t *to_session,
subcommand_baton_t *baton, apr_pool_t *pool)
{
svn_string_t *last_merged_rev;
svn_revnum_t from_latest;
svn_ra_session_t *from_session;
svn_string_t *currently_copying;
svn_revnum_t to_latest, copying, last_merged;
svn_revnum_t start_revision, end_revision;
replay_baton_t *rb;
int normalized_rev_props_count = 0;
SVN_ERR(open_source_session(&from_session, &last_merged_rev,
baton->from_url, to_session,
&(baton->source_callbacks), baton->config,
baton, pool));
SVN_ERR(svn_ra_rev_prop(to_session, 0, SVNSYNC_PROP_CURRENTLY_COPYING,
¤tly_copying, pool));
SVN_ERR(svn_ra_get_latest_revnum(to_session, &to_latest, pool));
last_merged = SVN_STR_TO_REV(last_merged_rev->data);
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)
{
SVN_ERR(copy_revprops(from_session, to_session, to_latest, TRUE,
baton->quiet, baton->source_prop_encoding,
&normalized_rev_props_count, pool));
last_merged = copying;
last_merged_rev = svn_string_create
(apr_psprintf(pool, "%ld", last_merged), pool);
}
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
{
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);
}
SVN_ERR(svn_ra_get_latest_revnum(from_session, &from_latest, pool));
if (from_latest < last_merged)
return SVN_NO_ERROR;
rb = make_replay_baton(from_session, to_session, baton, pool);
SVN_ERR(svn_ra_has_capability(rb->to_session,
&rb->has_commit_revprops_capability,
SVN_RA_CAPABILITY_COMMIT_REVPROPS,
pool));
start_revision = last_merged + 1;
end_revision = from_latest;
SVN_ERR(check_cancel(NULL));
SVN_ERR(svn_ra_replay_range(from_session, start_revision, end_revision,
0, TRUE, replay_rev_started,
replay_rev_finished, rb, pool));
SVN_ERR(log_properties_normalized(rb->normalized_rev_props_count
+ normalized_rev_props_count,
rb->normalized_node_props_count,
pool));
return SVN_NO_ERROR;
}
static svn_error_t *
synchronize_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
{
svn_ra_session_t *to_session;
opt_baton_t *opt_baton = b;
apr_array_header_t *targets;
subcommand_baton_t *baton;
const char *to_url, *from_url;
SVN_ERR(svn_opt__args_to_target_array(&targets, os,
apr_array_make(pool, 0,
sizeof(const char *)),
pool));
if (targets->nelts < 1)
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
if (targets->nelts > 2)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
to_url = APR_ARRAY_IDX(targets, 0, const char *);
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 (targets->nelts == 2)
{
from_url = APR_ARRAY_IDX(targets, 1, const char *);
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);
}
else
{
from_url = NULL;
}
baton = make_subcommand_baton(opt_baton, to_url, from_url, 0, 0, pool);
SVN_ERR(open_target_session(&to_session, baton, pool));
if (opt_baton->disable_locking)
SVN_ERR(do_synchronize(to_session, baton, pool));
else
SVN_ERR(with_locked(to_session, do_synchronize, baton,
opt_baton->steal_lock, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
do_copy_revprops(svn_ra_session_t *to_session,
subcommand_baton_t *baton, apr_pool_t *pool)
{
svn_ra_session_t *from_session;
svn_string_t *last_merged_rev;
svn_revnum_t i;
svn_revnum_t step = 1;
int normalized_rev_props_count = 0;
SVN_ERR(open_source_session(&from_session, &last_merged_rev,
baton->from_url, to_session,
&(baton->source_callbacks), baton->config,
baton, pool));
if (! SVN_IS_VALID_REVNUM(baton->start_rev))
baton->start_rev = SVN_STR_TO_REV(last_merged_rev->data);
if (! SVN_IS_VALID_REVNUM(baton->end_rev))
baton->end_rev = SVN_STR_TO_REV(last_merged_rev->data);
if (baton->start_rev > SVN_STR_TO_REV(last_merged_rev->data))
return svn_error_createf
(APR_EINVAL, NULL,
_("Cannot copy revprops for a revision (%ld) that has not "
"been synchronized yet"), baton->start_rev);
if (baton->end_rev > SVN_STR_TO_REV(last_merged_rev->data))
return svn_error_createf
(APR_EINVAL, NULL,
_("Cannot copy revprops for a revision (%ld) that has not "
"been synchronized yet"), baton->end_rev);
step = (baton->start_rev > baton->end_rev) ? -1 : 1;
for (i = baton->start_rev; i != baton->end_rev + step; i = i + step)
{
int normalized_count;
SVN_ERR(check_cancel(NULL));
SVN_ERR(copy_revprops(from_session, to_session, i, TRUE, baton->quiet,
baton->source_prop_encoding, &normalized_count,
pool));
normalized_rev_props_count += normalized_count;
}
SVN_ERR(log_properties_normalized(normalized_rev_props_count, 0, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
resolve_revnums(svn_revnum_t *start_revnum,
svn_revnum_t *end_revnum,
svn_opt_revision_t start_revision,
svn_opt_revision_t end_revision)
{
svn_revnum_t start_rev, end_rev;
if ((start_revision.kind == svn_opt_revision_unspecified) &&
(end_revision.kind == svn_opt_revision_unspecified))
{
*start_revnum = 0;
*end_revnum = SVN_INVALID_REVNUM;
return SVN_NO_ERROR;
}
if (start_revision.kind == svn_opt_revision_head)
{
start_rev = SVN_INVALID_REVNUM;
}
else
{
start_rev = start_revision.value.number;
if (! SVN_IS_VALID_REVNUM(start_rev))
return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Invalid revision number (%ld)"),
start_rev);
}
if (end_revision.kind == svn_opt_revision_unspecified)
{
end_rev = start_rev;
}
else if (end_revision.kind == svn_opt_revision_head)
{
end_rev = SVN_INVALID_REVNUM;
}
else
{
end_rev = end_revision.value.number;
if (! SVN_IS_VALID_REVNUM(end_rev))
return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Invalid revision number (%ld)"),
end_rev);
}
*start_revnum = start_rev;
*end_revnum = end_rev;
return SVN_NO_ERROR;
}
static svn_error_t *
copy_revprops_cmd(apr_getopt_t *os, void *b, apr_pool_t *pool)
{
svn_ra_session_t *to_session;
opt_baton_t *opt_baton = b;
apr_array_header_t *targets;
subcommand_baton_t *baton;
const char *to_url = NULL;
const char *from_url;
svn_opt_revision_t start_revision, end_revision;
svn_revnum_t start_rev = 0, end_rev = SVN_INVALID_REVNUM;
if (os->argc - os->ind > 2)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
if (os->argc - os->ind < 1)
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
if (os->argc - os->ind == 2)
{
const char *arg_str = os->argv[os->argc - 1];
const char *utf_arg_str;
SVN_ERR(svn_utf_cstring_to_utf8(&utf_arg_str, arg_str, pool));
if (! svn_path_is_url(utf_arg_str))
{
os->argc--;
if ((opt_baton->start_rev.kind != svn_opt_revision_unspecified)
|| (opt_baton->end_rev.kind != svn_opt_revision_unspecified))
return svn_error_create(
SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Cannot specify revisions via both command-line arguments "
"and the --revision (-r) option"));
start_revision.kind = svn_opt_revision_unspecified;
end_revision.kind = svn_opt_revision_unspecified;
if (svn_opt_parse_revision(&start_revision, &end_revision,
arg_str, pool) != 0)
return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Invalid revision range '%s' provided"),
arg_str);
SVN_ERR(resolve_revnums(&start_rev, &end_rev,
start_revision, end_revision));
SVN_ERR(svn_opt__args_to_target_array(
&targets, os,
apr_array_make(pool, 1, sizeof(const char *)), pool));
if (targets->nelts != 1)
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
to_url = APR_ARRAY_IDX(targets, 0, const char *);
from_url = NULL;
}
}
if (! to_url)
{
SVN_ERR(resolve_revnums(&start_rev, &end_rev,
opt_baton->start_rev, opt_baton->end_rev));
SVN_ERR(svn_opt__args_to_target_array(
&targets, os,
apr_array_make(pool, 2, sizeof(const char *)), pool));
if (targets->nelts < 1)
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
if (targets->nelts > 2)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
to_url = APR_ARRAY_IDX(targets, 0, const char *);
if (targets->nelts == 2)
from_url = APR_ARRAY_IDX(targets, 1, const char *);
else
from_url = NULL;
}
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 (from_url && (! 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 = make_subcommand_baton(opt_baton, to_url, from_url,
start_rev, end_rev, pool);
SVN_ERR(open_target_session(&to_session, baton, pool));
if (opt_baton->disable_locking)
SVN_ERR(do_copy_revprops(to_session, baton, pool));
else
SVN_ERR(with_locked(to_session, do_copy_revprops, baton,
opt_baton->steal_lock, pool));
return SVN_NO_ERROR;
}
static svn_error_t *
info_cmd(apr_getopt_t *os, void *b, apr_pool_t * pool)
{
svn_ra_session_t *to_session;
opt_baton_t *opt_baton = b;
apr_array_header_t *targets;
subcommand_baton_t *baton;
const char *to_url;
apr_hash_t *props;
svn_string_t *from_url, *from_uuid, *last_merged_rev;
SVN_ERR(svn_opt__args_to_target_array(&targets, os,
apr_array_make(pool, 0,
sizeof(const char *)),
pool));
if (targets->nelts < 1)
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
if (targets->nelts > 1)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
to_url = APR_ARRAY_IDX(targets, 0, const char *);
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);
baton = make_subcommand_baton(opt_baton, to_url, NULL, 0, 0, pool);
SVN_ERR(open_target_session(&to_session, baton, pool));
SVN_ERR(svn_ra_rev_proplist(to_session, 0, &props, pool));
from_url = apr_hash_get(props, SVNSYNC_PROP_FROM_URL,
APR_HASH_KEY_STRING);
if (! from_url)
return svn_error_createf
(SVN_ERR_BAD_URL, NULL,
_("Repository '%s' is not initialized for synchronization"), to_url);
from_uuid = apr_hash_get(props, SVNSYNC_PROP_FROM_UUID,
APR_HASH_KEY_STRING);
last_merged_rev = apr_hash_get(props, SVNSYNC_PROP_LAST_MERGED_REV,
APR_HASH_KEY_STRING);
SVN_ERR(svn_cmdline_printf(pool, _("Source URL: %s\n"), from_url->data));
if (from_uuid)
SVN_ERR(svn_cmdline_printf(pool, _("Source Repository UUID: %s\n"),
from_uuid->data));
if (last_merged_rev)
SVN_ERR(svn_cmdline_printf(pool, _("Last Merged Revision: %s\n"),
last_merged_rev->data));
return SVN_NO_ERROR;
}
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));
SVN_ERR(svn_opt_print_help3(os, "svnsync",
opt_baton ? opt_baton->version : FALSE,
opt_baton ? opt_baton->quiet : FALSE,
version_footer->data, header,
svnsync_cmd_table, svnsync_options, NULL,
NULL, pool));
return SVN_NO_ERROR;
}
int
main(int argc, const char *argv[])
{
const svn_opt_subcommand_desc2_t *subcommand = NULL;
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;
const char *username = NULL, *source_username = NULL, *sync_username = NULL;
const char *password = NULL, *source_password = NULL, *sync_password = NULL;
apr_array_header_t *config_options = NULL;
const char *source_prop_encoding = NULL;
apr_allocator_t *allocator;
if (svn_cmdline_init("svnsync", stderr) != EXIT_SUCCESS)
{
return EXIT_FAILURE;
}
err = check_lib_versions();
if (err)
return svn_cmdline_handle_exit_error(err, NULL, "svnsync: ");
if (apr_allocator_create(&allocator))
return EXIT_FAILURE;
apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE);
pool = svn_pool_create_ex(NULL, allocator);
apr_allocator_owner_set(allocator, pool);
err = svn_ra_initialize(pool);
if (err)
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
memset(&opt_baton, 0, sizeof(opt_baton));
opt_baton.start_rev.kind = svn_opt_revision_unspecified;
opt_baton.end_rev.kind = svn_opt_revision_unspecified;
received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
if (argc <= 1)
{
SVN_INT_ERR(help_cmd(NULL, NULL, pool));
svn_pool_destroy(pool);
return EXIT_FAILURE;
}
err = svn_cmdline__getopt_init(&os, argc, argv, pool);
if (err)
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
os->interleave = 1;
for (;;)
{
const char *opt_arg;
svn_error_t* opt_err = NULL;
apr_err = apr_getopt_long(os, svnsync_options, &opt_id, &opt_arg);
if (APR_STATUS_IS_EOF(apr_err))
break;
else if (apr_err)
{
SVN_INT_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_trust_server_cert:
opt_baton.trust_server_cert = TRUE;
break;
case svnsync_opt_no_auth_cache:
opt_baton.no_auth_cache = TRUE;
break;
case svnsync_opt_auth_username:
opt_err = svn_utf_cstring_to_utf8(&username, opt_arg, pool);
break;
case svnsync_opt_auth_password:
opt_err = svn_utf_cstring_to_utf8(&password, opt_arg, pool);
break;
case svnsync_opt_source_username:
opt_err = svn_utf_cstring_to_utf8(&source_username, opt_arg, pool);
break;
case svnsync_opt_source_password:
opt_err = svn_utf_cstring_to_utf8(&source_password, opt_arg, pool);
break;
case svnsync_opt_sync_username:
opt_err = svn_utf_cstring_to_utf8(&sync_username, opt_arg, pool);
break;
case svnsync_opt_sync_password:
opt_err = svn_utf_cstring_to_utf8(&sync_password, opt_arg, pool);
break;
case svnsync_opt_config_dir:
{
const char *path_utf8;
opt_err = svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool);
if (!opt_err)
opt_baton.config_dir = svn_dirent_internal_style(path_utf8, pool);
}
break;
case svnsync_opt_config_options:
if (!config_options)
config_options =
apr_array_make(pool, 1,
sizeof(svn_cmdline__config_argument_t*));
err = svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool);
if (!err)
err = svn_cmdline__parse_config_option(config_options,
opt_arg, pool);
if (err)
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
break;
case svnsync_opt_source_prop_encoding:
opt_err = svn_utf_cstring_to_utf8(&source_prop_encoding, opt_arg,
pool);
break;
case svnsync_opt_disable_locking:
opt_baton.disable_locking = TRUE;
break;
case svnsync_opt_steal_lock:
opt_baton.steal_lock = TRUE;
break;
case svnsync_opt_version:
opt_baton.version = TRUE;
break;
case svnsync_opt_allow_non_empty:
opt_baton.allow_non_empty = TRUE;
break;
case 'q':
opt_baton.quiet = TRUE;
break;
case 'r':
if (svn_opt_parse_revision(&opt_baton.start_rev,
&opt_baton.end_rev,
opt_arg, pool) != 0)
{
const char *utf8_opt_arg;
err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
if (! err)
err = svn_error_createf(
SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Syntax error in revision argument '%s'"),
utf8_opt_arg);
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
}
if (((opt_baton.start_rev.kind != svn_opt_revision_number) &&
(opt_baton.start_rev.kind != svn_opt_revision_head))
|| ((opt_baton.end_rev.kind != svn_opt_revision_number) &&
(opt_baton.end_rev.kind != svn_opt_revision_head) &&
(opt_baton.end_rev.kind != svn_opt_revision_unspecified)))
{
err = svn_error_createf(
SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Invalid revision range '%s' provided"), opt_arg);
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
}
break;
case '?':
case 'h':
opt_baton.help = TRUE;
break;
default:
{
SVN_INT_ERR(help_cmd(NULL, NULL, pool));
svn_pool_destroy(pool);
return EXIT_FAILURE;
}
}
if(opt_err)
return svn_cmdline_handle_exit_error(opt_err, pool, "svnsync: ");
}
if (opt_baton.help)
subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table, "help");
if ((username || password)
&& (source_username || sync_username
|| source_password || sync_password))
{
err = svn_error_create
(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Cannot use --username or --password with any of "
"--source-username, --source-password, --sync-username, "
"or --sync-password.\n"));
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
}
if (username)
{
source_username = username;
sync_username = username;
}
if (password)
{
source_password = password;
sync_password = password;
}
opt_baton.source_username = source_username;
opt_baton.source_password = source_password;
opt_baton.sync_username = sync_username;
opt_baton.sync_password = sync_password;
if (opt_baton.steal_lock && opt_baton.disable_locking)
{
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--disable-locking and --steal-lock are "
"mutually exclusive"));
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
}
if (opt_baton.trust_server_cert && !opt_baton.non_interactive)
{
err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--trust-server-cert requires "
"--non-interactive"));
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
}
err = svn_config_ensure(opt_baton.config_dir, pool);
if (err)
return svn_cmdline_handle_exit_error(err, pool, "synsync: ");
if (subcommand == NULL)
{
if (os->ind >= os->argc)
{
if (opt_baton.version)
{
static const svn_opt_subcommand_desc2_t pseudo_cmd =
{ "--version", help_cmd, {0}, "",
{svnsync_opt_version,
'q',
} };
subcommand = &pseudo_cmd;
}
else
{
SVN_INT_ERR(help_cmd(NULL, NULL, pool));
svn_pool_destroy(pool);
return EXIT_FAILURE;
}
}
else
{
const char *first_arg = os->argv[os->ind++];
subcommand = svn_opt_get_canonical_subcommand2(svnsync_cmd_table,
first_arg);
if (subcommand == NULL)
{
SVN_INT_ERR(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;
if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
{
const char *optstr;
const apr_getopt_option_t *badopt =
svn_opt_get_option_from_code2(opt_id, svnsync_options, subcommand,
pool);
svn_opt_format_option(&optstr, badopt, FALSE, pool);
if (subcommand->name[0] == '-')
{
SVN_INT_ERR(help_cmd(NULL, NULL, pool));
}
else
{
err = svn_error_createf
(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Subcommand '%s' doesn't accept option '%s'\n"
"Type 'svnsync help %s' for usage.\n"),
subcommand->name, optstr, subcommand->name);
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
}
}
}
err = svn_config_get_config(&opt_baton.config, opt_baton.config_dir, pool);
if (err)
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
if (config_options)
{
svn_error_clear(
svn_cmdline__apply_config_options(opt_baton.config, config_options,
"svnsync: ", "--config-option"));
}
config = apr_hash_get(opt_baton.config, SVN_CONFIG_CATEGORY_CONFIG,
APR_HASH_KEY_STRING);
opt_baton.source_prop_encoding = source_prop_encoding;
apr_signal(SIGINT, signal_handler);
#ifdef SIGBREAK
apr_signal(SIGBREAK, signal_handler);
#endif
#ifdef SIGHUP
apr_signal(SIGHUP, signal_handler);
#endif
#ifdef SIGTERM
apr_signal(SIGTERM, signal_handler);
#endif
#ifdef SIGPIPE
apr_signal(SIGPIPE, SIG_IGN);
#endif
#ifdef SIGXFSZ
apr_signal(SIGXFSZ, SIG_IGN);
#endif
err = svn_cmdline_create_auth_baton(&opt_baton.source_auth_baton,
opt_baton.non_interactive,
opt_baton.source_username,
opt_baton.source_password,
opt_baton.config_dir,
opt_baton.no_auth_cache,
opt_baton.trust_server_cert,
config,
check_cancel, NULL,
pool);
if (! err)
err = svn_cmdline_create_auth_baton(&opt_baton.sync_auth_baton,
opt_baton.non_interactive,
opt_baton.sync_username,
opt_baton.sync_password,
opt_baton.config_dir,
opt_baton.no_auth_cache,
opt_baton.trust_server_cert,
config,
check_cancel, NULL,
pool);
if (! err)
err = (*subcommand->cmd_func)(os, &opt_baton, pool);
if (err)
{
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"));
}
return svn_cmdline_handle_exit_error(err, pool, "svnsync: ");
}
svn_pool_destroy(pool);
return EXIT_SUCCESS;
}