blob: 00dd6ea1d7d242bfb64ed068e17c8fa4c912e6a9 [file] [log] [blame]
/*
* svnmucc.c: Subversion Multiple URL Client
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
*/
/* Multiple URL Command Client
Combine a list of mv, cp and rm commands on URLs into a single commit.
How it works: the command line arguments are parsed into an array of
action structures. The action structures are interpreted to build a
tree of operation structures. The tree of operation structures is
used to drive an RA commit editor to produce a single commit.
To build this client, type 'make svnmucc' from the root of your
Subversion source directory.
*/
#include <stdio.h>
#include <string.h>
#include <apr_lib.h>
#include "svn_private_config.h"
#include "svn_hash.h"
#include "svn_client.h"
#include "private/svn_client_mtcc.h"
#include "svn_cmdline.h"
#include "svn_config.h"
#include "svn_error.h"
#include "svn_path.h"
#include "svn_pools.h"
#include "svn_props.h"
#include "svn_string.h"
#include "svn_subst.h"
#include "svn_utf.h"
#include "svn_version.h"
#include "private/svn_cmdline_private.h"
#include "private/svn_subr_private.h"
/* Version compatibility check */
static svn_error_t *
check_lib_versions(void)
{
static const svn_version_checklist_t checklist[] =
{
{ "svn_client", svn_client_version },
{ "svn_subr", svn_subr_version },
{ "svn_ra", svn_ra_version },
{ NULL, NULL }
};
SVN_VERSION_DEFINE(my_version);
return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
}
static svn_error_t *
commit_callback(const svn_commit_info_t *commit_info,
void *baton,
apr_pool_t *pool)
{
SVN_ERR(svn_cmdline_printf(pool, "r%ld committed by %s at %s\n",
commit_info->revision,
(commit_info->author
? commit_info->author : "(no author)"),
commit_info->date));
return SVN_NO_ERROR;
}
typedef enum action_code_t {
ACTION_MV,
ACTION_MKDIR,
ACTION_CP,
ACTION_PROPSET,
ACTION_PROPSETF,
ACTION_PROPDEL,
ACTION_PUT,
ACTION_RM
} action_code_t;
/* Return the portion of URL that is relative to ANCHOR (URI-decoded). */
static const char *
subtract_anchor(const char *anchor, const char *url, apr_pool_t *pool)
{
return svn_uri_skip_ancestor(anchor, url, pool);
}
struct action {
action_code_t action;
/* revision (copy-from-rev of path[0] for cp; base-rev for put) */
svn_revnum_t rev;
/* action path[0] path[1]
* ------ ------- -------
* mv source target
* mkdir target (null)
* cp source target
* put target source
* rm target (null)
* propset target (null)
*/
const char *path[2];
/* property name/value */
const char *prop_name;
const svn_string_t *prop_value;
};
static svn_error_t *
execute(const apr_array_header_t *actions,
const char *anchor,
apr_hash_t *revprops,
svn_revnum_t base_revision,
svn_client_ctx_t *ctx,
apr_pool_t *pool)
{
svn_client__mtcc_t *mtcc;
apr_pool_t *iterpool = svn_pool_create(pool);
svn_error_t *err;
int i;
SVN_ERR(svn_client__mtcc_create(&mtcc, anchor,
SVN_IS_VALID_REVNUM(base_revision)
? base_revision
: SVN_INVALID_REVNUM,
ctx, pool, iterpool));
for (i = 0; i < actions->nelts; ++i)
{
struct action *action = APR_ARRAY_IDX(actions, i, struct action *);
const char *path1, *path2;
svn_node_kind_t kind;
svn_pool_clear(iterpool);
switch (action->action)
{
case ACTION_MV:
path1 = subtract_anchor(anchor, action->path[0], pool);
path2 = subtract_anchor(anchor, action->path[1], pool);
SVN_ERR(svn_client__mtcc_add_move(path1, path2, mtcc, iterpool));
break;
case ACTION_CP:
path1 = subtract_anchor(anchor, action->path[0], pool);
path2 = subtract_anchor(anchor, action->path[1], pool);
SVN_ERR(svn_client__mtcc_add_copy(path1, action->rev, path2,
mtcc, iterpool));
break;
case ACTION_RM:
path1 = subtract_anchor(anchor, action->path[0], pool);
SVN_ERR(svn_client__mtcc_add_delete(path1, mtcc, iterpool));
break;
case ACTION_MKDIR:
path1 = subtract_anchor(anchor, action->path[0], pool);
SVN_ERR(svn_client__mtcc_add_mkdir(path1, mtcc, iterpool));
break;
case ACTION_PUT:
path1 = subtract_anchor(anchor, action->path[0], pool);
SVN_ERR(svn_client__mtcc_check_path(&kind, path1, TRUE, mtcc, pool));
if (kind == svn_node_dir)
{
SVN_ERR(svn_client__mtcc_add_delete(path1, mtcc, pool));
kind = svn_node_none;
}
{
svn_stream_t *src;
if (strcmp(action->path[1], "-") != 0)
SVN_ERR(svn_stream_open_readonly(&src, action->path[1],
pool, iterpool));
else
SVN_ERR(svn_stream_for_stdin2(&src, TRUE, pool));
if (kind == svn_node_file)
SVN_ERR(svn_client__mtcc_add_update_file(path1, src, NULL,
NULL, NULL,
mtcc, iterpool));
else if (kind == svn_node_none)
SVN_ERR(svn_client__mtcc_add_add_file(path1, src, NULL,
mtcc, iterpool));
}
break;
case ACTION_PROPSET:
case ACTION_PROPDEL:
path1 = subtract_anchor(anchor, action->path[0], pool);
SVN_ERR(svn_client__mtcc_add_propset(path1, action->prop_name,
action->prop_value, FALSE,
mtcc, iterpool));
break;
case ACTION_PROPSETF:
default:
SVN_ERR_MALFUNCTION_NO_RETURN();
}
}
err = svn_client__mtcc_commit(revprops, commit_callback, NULL,
mtcc, iterpool);
svn_pool_destroy(iterpool);
return svn_error_trace(err);
}
static svn_error_t *
read_propvalue_file(const svn_string_t **value_p,
const char *filename,
apr_pool_t *pool)
{
svn_stringbuf_t *value;
apr_pool_t *scratch_pool = svn_pool_create(pool);
SVN_ERR(svn_stringbuf_from_file2(&value, filename, scratch_pool));
*value_p = svn_string_create_from_buf(value, pool);
svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
/* Perform the typical suite of manipulations for user-provided URLs
on URL, returning the result (allocated from POOL): IRI-to-URI
conversion, auto-escaping, and canonicalization. */
static const char *
sanitize_url(const char *url,
apr_pool_t *pool)
{
url = svn_path_uri_from_iri(url, pool);
url = svn_path_uri_autoescape(url, pool);
return svn_uri_canonicalize(url, pool);
}
static void
usage(apr_pool_t *pool)
{
svn_error_clear(svn_cmdline_fprintf
(stderr, pool, _("Type 'svnmucc --help' for usage.\n")));
}
/* Print a usage message on STREAM. */
static void
help(FILE *stream, apr_pool_t *pool)
{
svn_error_clear(svn_cmdline_fputs(
_("usage: svnmucc ACTION...\n"
"Subversion multiple URL command client.\n"
"Type 'svnmucc --version' to see the program version and RA modules.\n"
"\n"
" Perform one or more Subversion repository URL-based ACTIONs, committing\n"
" the result as a (single) new revision.\n"
"\n"
"Actions:\n"
" cp REV SRC-URL DST-URL : copy SRC-URL@REV to DST-URL\n"
" mkdir URL : create new directory URL\n"
" mv SRC-URL DST-URL : move SRC-URL to DST-URL\n"
" rm URL : delete URL\n"
" put SRC-FILE URL : add or modify file URL with contents copied from\n"
" SRC-FILE (use \"-\" to read from standard input)\n"
" propset NAME VALUE URL : set property NAME on URL to VALUE\n"
" propsetf NAME FILE URL : set property NAME on URL to value read from FILE\n"
" propdel NAME URL : delete property NAME from URL\n"
"\n"
"Valid options:\n"
" -h, -? [--help] : display this text\n"
" -m [--message] ARG : use ARG as a log message\n"
" -F [--file] ARG : read log message from file ARG\n"
" -u [--username] ARG : commit the changes as username ARG\n"
" -p [--password] ARG : use ARG as the password\n"
" -U [--root-url] ARG : interpret all action URLs relative to ARG\n"
" -r [--revision] ARG : use revision ARG as baseline for changes\n"
" --with-revprop ARG : set revision property in the following format:\n"
" NAME[=VALUE]\n"
" --non-interactive : do no interactive prompting (default is to\n"
" prompt only if standard input is a terminal)\n"
" --force-interactive : do interactive prompting even if standard\n"
" input is not a terminal\n"
" --trust-server-cert : deprecated;\n"
" same as --trust-server-cert-failures=unknown-ca\n"
" --trust-server-cert-failures ARG\n"
" with --non-interactive, accept SSL server\n"
" certificates with failures; ARG is comma-separated\n"
" list of 'unknown-ca' (Unknown Authority),\n"
" 'cn-mismatch' (Hostname mismatch), 'expired'\n"
" (Expired certificate),'not-yet-valid' (Not yet\n"
" valid certificate) and 'other' (all other not\n"
" separately classified certificate errors).\n"
" -X [--extra-args] ARG : append arguments from file ARG (one per line;\n"
" use \"-\" to read from standard input)\n"
" --config-dir ARG : use ARG to override the config directory\n"
" --config-option ARG : use ARG to override a configuration option\n"
" --no-auth-cache : do not cache authentication tokens\n"
" --version : print version information\n"),
stream, pool));
}
static svn_error_t *
insufficient(void)
{
return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
"insufficient arguments");
}
static svn_error_t *
display_version(apr_pool_t *pool)
{
const char *ra_desc_start
= "The following repository access (RA) modules are available:\n\n";
svn_stringbuf_t *version_footer;
version_footer = svn_stringbuf_create(ra_desc_start, pool);
SVN_ERR(svn_ra_print_modules(version_footer, pool));
SVN_ERR(svn_opt_print_help4(NULL, "svnmucc", TRUE, FALSE, FALSE,
version_footer->data,
NULL, NULL, NULL, NULL, NULL, pool));
return SVN_NO_ERROR;
}
/* Return an error about the mutual exclusivity of the -m, -F, and
--with-revprop=svn:log command-line options. */
static svn_error_t *
mutually_exclusive_logs_error(void)
{
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--message (-m), --file (-F), and "
"--with-revprop=svn:log are mutually "
"exclusive"));
}
/* Obtain the log message from multiple sources, producing an error
if there are multiple sources. Store the result in *FINAL_MESSAGE. */
static svn_error_t *
sanitize_log_sources(const char **final_message,
const char *message,
apr_hash_t *revprops,
svn_stringbuf_t *filedata,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_string_t *msg;
*final_message = NULL;
/* If we already have a log message in the revprop hash, then just
make sure the user didn't try to also use -m or -F. Otherwise,
we need to consult -m or -F to find a log message, if any. */
msg = svn_hash_gets(revprops, SVN_PROP_REVISION_LOG);
if (msg)
{
if (filedata || message)
return mutually_exclusive_logs_error();
*final_message = apr_pstrdup(result_pool, msg->data);
/* Will be re-added by libsvn_client */
svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, NULL);
}
else if (filedata)
{
if (message)
return mutually_exclusive_logs_error();
*final_message = apr_pstrdup(result_pool, filedata->data);
}
else if (message)
{
*final_message = apr_pstrdup(result_pool, message);
}
return SVN_NO_ERROR;
}
/* Baton for log_message_func */
struct log_message_baton
{
svn_boolean_t non_interactive;
const char *log_message;
svn_client_ctx_t *ctx;
};
/* Implements svn_client_get_commit_log3_t */
static svn_error_t *
log_message_func(const char **log_msg,
const char **tmp_file,
const apr_array_header_t *commit_items,
void *baton,
apr_pool_t *pool)
{
struct log_message_baton *lmb = baton;
*tmp_file = NULL;
if (lmb->log_message)
{
svn_string_t *message = svn_string_create(lmb->log_message, pool);
SVN_ERR_W(svn_subst_translate_string2(&message, NULL, NULL,
message, NULL, FALSE,
pool, pool),
_("Error normalizing log message to internal format"));
*log_msg = message->data;
return SVN_NO_ERROR;
}
if (lmb->non_interactive)
{
return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
_("Cannot invoke editor to get log message "
"when non-interactive"));
}
else
{
svn_string_t *msg = svn_string_create("", pool);
SVN_ERR(svn_cmdline__edit_string_externally(
&msg, NULL, NULL, "", msg, "svnmucc-commit",
lmb->ctx->config, TRUE, NULL, pool));
if (msg && msg->data)
*log_msg = msg->data;
else
*log_msg = NULL;
return SVN_NO_ERROR;
}
}
/*
* On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
* either return an error to be displayed, or set *EXIT_CODE to non-zero and
* return SVN_NO_ERROR.
*/
static svn_error_t *
sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
{
apr_array_header_t *actions = apr_array_make(pool, 1,
sizeof(struct action *));
const char *anchor = NULL;
svn_error_t *err = SVN_NO_ERROR;
apr_getopt_t *opts;
enum {
config_dir_opt = SVN_OPT_FIRST_LONGOPT_ID,
config_inline_opt,
no_auth_cache_opt,
version_opt,
with_revprop_opt,
non_interactive_opt,
force_interactive_opt,
trust_server_cert_opt,
trust_server_cert_failures_opt,
};
static const apr_getopt_option_t options[] = {
{"message", 'm', 1, ""},
{"file", 'F', 1, ""},
{"username", 'u', 1, ""},
{"password", 'p', 1, ""},
{"root-url", 'U', 1, ""},
{"revision", 'r', 1, ""},
{"with-revprop", with_revprop_opt, 1, ""},
{"extra-args", 'X', 1, ""},
{"help", 'h', 0, ""},
{NULL, '?', 0, ""},
{"non-interactive", non_interactive_opt, 0, ""},
{"force-interactive", force_interactive_opt, 0, ""},
{"trust-server-cert", trust_server_cert_opt, 0, ""},
{"trust-server-cert-failures", trust_server_cert_failures_opt, 1, ""},
{"config-dir", config_dir_opt, 1, ""},
{"config-option", config_inline_opt, 1, ""},
{"no-auth-cache", no_auth_cache_opt, 0, ""},
{"version", version_opt, 0, ""},
{NULL, 0, 0, NULL}
};
const char *message = NULL;
svn_stringbuf_t *filedata = NULL;
const char *username = NULL, *password = NULL;
const char *root_url = NULL, *extra_args_file = NULL;
const char *config_dir = NULL;
apr_array_header_t *config_options;
svn_boolean_t non_interactive = FALSE;
svn_boolean_t force_interactive = FALSE;
svn_boolean_t trust_unknown_ca = FALSE;
svn_boolean_t trust_cn_mismatch = FALSE;
svn_boolean_t trust_expired = FALSE;
svn_boolean_t trust_not_yet_valid = FALSE;
svn_boolean_t trust_other_failure = FALSE;
svn_boolean_t no_auth_cache = FALSE;
svn_boolean_t show_version = FALSE;
svn_boolean_t show_help = FALSE;
svn_revnum_t base_revision = SVN_INVALID_REVNUM;
apr_array_header_t *action_args;
apr_hash_t *revprops = apr_hash_make(pool);
apr_hash_t *cfg_hash;
svn_config_t *cfg_config;
svn_client_ctx_t *ctx;
struct log_message_baton lmb;
int i;
/* Check library versions */
SVN_ERR(check_lib_versions());
config_options = apr_array_make(pool, 0,
sizeof(svn_cmdline__config_argument_t*));
apr_getopt_init(&opts, pool, argc, argv);
opts->interleave = 1;
while (1)
{
int opt;
const char *arg;
const char *opt_arg;
apr_status_t status = apr_getopt_long(opts, options, &opt, &arg);
if (APR_STATUS_IS_EOF(status))
break;
if (status != APR_SUCCESS)
{
usage(pool);
*exit_code = EXIT_FAILURE;
return SVN_NO_ERROR;
}
switch(opt)
{
case 'm':
SVN_ERR(svn_utf_cstring_to_utf8(&message, arg, pool));
break;
case 'F':
{
const char *arg_utf8;
SVN_ERR(svn_utf_cstring_to_utf8(&arg_utf8, arg, pool));
SVN_ERR(svn_stringbuf_from_file2(&filedata, arg, pool));
}
break;
case 'u':
username = apr_pstrdup(pool, arg);
break;
case 'p':
password = apr_pstrdup(pool, arg);
break;
case 'U':
SVN_ERR(svn_utf_cstring_to_utf8(&root_url, arg, pool));
if (! svn_path_is_url(root_url))
return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
"'%s' is not a URL\n", root_url);
root_url = sanitize_url(root_url, pool);
break;
case 'r':
{
const char *saved_arg = arg;
char *digits_end = NULL;
while (*arg == 'r')
arg++;
base_revision = strtol(arg, &digits_end, 10);
if ((! SVN_IS_VALID_REVNUM(base_revision))
|| (! digits_end)
|| *digits_end)
return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("Invalid revision number '%s'"),
saved_arg);
}
break;
case with_revprop_opt:
SVN_ERR(svn_opt_parse_revprop(&revprops, arg, pool));
break;
case 'X':
extra_args_file = apr_pstrdup(pool, arg);
break;
case non_interactive_opt:
non_interactive = TRUE;
break;
case force_interactive_opt:
force_interactive = TRUE;
break;
case trust_server_cert_opt: /* backward compat */
trust_unknown_ca = TRUE;
break;
case trust_server_cert_failures_opt:
SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
SVN_ERR(svn_cmdline__parse_trust_options(
&trust_unknown_ca,
&trust_cn_mismatch,
&trust_expired,
&trust_not_yet_valid,
&trust_other_failure,
opt_arg, pool));
break;
case config_dir_opt:
SVN_ERR(svn_utf_cstring_to_utf8(&config_dir, arg, pool));
break;
case config_inline_opt:
SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
SVN_ERR(svn_cmdline__parse_config_option(config_options, opt_arg,
"svnmucc: ",
pool));
break;
case no_auth_cache_opt:
no_auth_cache = TRUE;
break;
case version_opt:
show_version = TRUE;
break;
case 'h':
case '?':
show_help = TRUE;
break;
}
}
if (show_help)
{
help(stdout, pool);
return SVN_NO_ERROR;
}
if (show_version)
{
SVN_ERR(display_version(pool));
return SVN_NO_ERROR;
}
if (non_interactive && force_interactive)
{
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--non-interactive and --force-interactive "
"are mutually exclusive"));
}
else
non_interactive = !svn_cmdline__be_interactive(non_interactive,
force_interactive);
if (!non_interactive)
{
if (trust_unknown_ca || trust_cn_mismatch || trust_expired
|| trust_not_yet_valid || trust_other_failure)
return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
_("--trust-server-cert-failures requires "
"--non-interactive"));
}
/* Copy the rest of our command-line arguments to an array,
UTF-8-ing them along the way. */
action_args = apr_array_make(pool, opts->argc, sizeof(const char *));
while (opts->ind < opts->argc)
{
const char *arg = opts->argv[opts->ind++];
SVN_ERR(svn_utf_cstring_to_utf8(&APR_ARRAY_PUSH(action_args,
const char *),
arg, pool));
}
/* If there are extra arguments in a supplementary file, tack those
on, too (again, in UTF8 form). */
if (extra_args_file)
{
const char *extra_args_file_utf8;
svn_stringbuf_t *contents, *contents_utf8;
SVN_ERR(svn_utf_cstring_to_utf8(&extra_args_file_utf8,
extra_args_file, pool));
SVN_ERR(svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool));
SVN_ERR(svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool));
svn_cstring_split_append(action_args, contents_utf8->data, "\n\r",
FALSE, pool);
}
/* Now initialize the client context */
err = svn_config_get_config(&cfg_hash, config_dir, pool);
if (err)
{
/* Fallback to default config if the config directory isn't readable
or is not a directory. */
if (APR_STATUS_IS_EACCES(err->apr_err)
|| SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
{
svn_handle_warning2(stderr, err, "svnmucc: ");
svn_error_clear(err);
SVN_ERR(svn_config__get_default_config(&cfg_hash, pool));
}
else
return err;
}
if (config_options)
{
svn_error_clear(
svn_cmdline__apply_config_options(cfg_hash, config_options,
"svnmucc: ", "--config-option"));
}
SVN_ERR(svn_client_create_context2(&ctx, cfg_hash, pool));
cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG);
SVN_ERR(svn_cmdline_create_auth_baton2(
&ctx->auth_baton,
non_interactive,
username,
password,
config_dir,
no_auth_cache,
trust_unknown_ca,
trust_cn_mismatch,
trust_expired,
trust_not_yet_valid,
trust_other_failure,
cfg_config,
ctx->cancel_func,
ctx->cancel_baton,
pool));
lmb.non_interactive = non_interactive;
lmb.ctx = ctx;
/* Make sure we have a log message to use. */
SVN_ERR(sanitize_log_sources(&lmb.log_message, message, revprops, filedata,
pool, pool));
ctx->log_msg_func3 = log_message_func;
ctx->log_msg_baton3 = &lmb;
/* Now, we iterate over the combined set of arguments -- our actions. */
for (i = 0; i < action_args->nelts; )
{
int j, num_url_args;
const char *action_string = APR_ARRAY_IDX(action_args, i, const char *);
struct action *action = apr_pcalloc(pool, sizeof(*action));
/* First, parse the action. */
if (! strcmp(action_string, "mv"))
action->action = ACTION_MV;
else if (! strcmp(action_string, "cp"))
action->action = ACTION_CP;
else if (! strcmp(action_string, "mkdir"))
action->action = ACTION_MKDIR;
else if (! strcmp(action_string, "rm"))
action->action = ACTION_RM;
else if (! strcmp(action_string, "put"))
action->action = ACTION_PUT;
else if (! strcmp(action_string, "propset"))
action->action = ACTION_PROPSET;
else if (! strcmp(action_string, "propsetf"))
action->action = ACTION_PROPSETF;
else if (! strcmp(action_string, "propdel"))
action->action = ACTION_PROPDEL;
else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h")
|| ! strcmp(action_string, "help"))
{
help(stdout, pool);
return SVN_NO_ERROR;
}
else
return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
"'%s' is not an action\n",
action_string);
if (++i == action_args->nelts)
return insufficient();
/* For copies, there should be a revision number next. */
if (action->action == ACTION_CP)
{
const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *);
if (strcmp(rev_str, "head") == 0)
action->rev = SVN_INVALID_REVNUM;
else if (strcmp(rev_str, "HEAD") == 0)
action->rev = SVN_INVALID_REVNUM;
else
{
char *end;
while (*rev_str == 'r')
++rev_str;
action->rev = strtol(rev_str, &end, 0);
if (*end)
return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
"'%s' is not a revision\n",
rev_str);
}
if (++i == action_args->nelts)
return insufficient();
}
else
{
action->rev = SVN_INVALID_REVNUM;
}
/* For puts, there should be a local file next. */
if (action->action == ACTION_PUT)
{
action->path[1] =
svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i,
const char *), pool);
if (++i == action_args->nelts)
return insufficient();
}
/* For propset, propsetf, and propdel, a property name (and
maybe a property value or file which contains one) comes next. */
if ((action->action == ACTION_PROPSET)
|| (action->action == ACTION_PROPSETF)
|| (action->action == ACTION_PROPDEL))
{
action->prop_name = APR_ARRAY_IDX(action_args, i, const char *);
if (++i == action_args->nelts)
return insufficient();
if (action->action == ACTION_PROPDEL)
{
action->prop_value = NULL;
}
else if (action->action == ACTION_PROPSET)
{
action->prop_value =
svn_string_create(APR_ARRAY_IDX(action_args, i,
const char *), pool);
if (++i == action_args->nelts)
return insufficient();
}
else
{
const char *propval_file =
svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i,
const char *), pool);
if (++i == action_args->nelts)
return insufficient();
SVN_ERR(read_propvalue_file(&(action->prop_value),
propval_file, pool));
action->action = ACTION_PROPSET;
}
if (action->prop_value
&& svn_prop_needs_translation(action->prop_name))
{
svn_string_t *translated_value;
SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL,
NULL, action->prop_value,
NULL, FALSE, pool, pool),
"Error normalizing property value");
action->prop_value = translated_value;
}
}
/* How many URLs does this action expect? */
if (action->action == ACTION_RM
|| action->action == ACTION_MKDIR
|| action->action == ACTION_PUT
|| action->action == ACTION_PROPSET
|| action->action == ACTION_PROPSETF /* shouldn't see this one */
|| action->action == ACTION_PROPDEL)
num_url_args = 1;
else
num_url_args = 2;
/* Parse the required number of URLs. */
for (j = 0; j < num_url_args; ++j)
{
const char *url = APR_ARRAY_IDX(action_args, i, const char *);
/* If there's a ROOT_URL, we expect URL to be a path
relative to ROOT_URL (and we build a full url from the
combination of the two). Otherwise, it should be a full
url. */
if (! svn_path_is_url(url))
{
if (! root_url)
return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
"'%s' is not a URL, and "
"--root-url (-U) not provided\n",
url);
/* ### These relpaths are already URI-encoded. */
url = apr_pstrcat(pool, root_url, "/",
svn_relpath_canonicalize(url, pool),
SVN_VA_NULL);
}
url = sanitize_url(url, pool);
action->path[j] = url;
/* The first URL arguments to 'cp', 'pd', 'ps' could be the anchor,
but the other URLs should be children of the anchor. */
if (! (action->action == ACTION_CP && j == 0)
&& action->action != ACTION_PROPDEL
&& action->action != ACTION_PROPSET
&& action->action != ACTION_PROPSETF)
url = svn_uri_dirname(url, pool);
if (! anchor)
anchor = url;
else
{
anchor = svn_uri_get_longest_ancestor(anchor, url, pool);
if (!anchor || !anchor[0])
return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
"URLs in the action list do not "
"share a common ancestor");
}
if ((++i == action_args->nelts) && (j + 1 < num_url_args))
return insufficient();
}
APR_ARRAY_PUSH(actions, struct action *) = action;
}
if (! actions->nelts)
{
*exit_code = EXIT_FAILURE;
help(stderr, pool);
return SVN_NO_ERROR;
}
if ((err = execute(actions, anchor, revprops, base_revision, ctx, pool)))
{
if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
err = svn_error_quick_wrap(err,
_("Authentication failed and interactive"
" prompting is disabled; see the"
" --force-interactive option"));
return err;
}
return SVN_NO_ERROR;
}
int
main(int argc, const char *argv[])
{
apr_pool_t *pool;
int exit_code = EXIT_SUCCESS;
svn_error_t *err;
/* Initialize the app. */
if (svn_cmdline_init("svnmucc", stderr) != EXIT_SUCCESS)
return EXIT_FAILURE;
/* Create our top-level pool. Use a separate mutexless allocator,
* given this application is single threaded.
*/
pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
err = sub_main(&exit_code, argc, argv, pool);
/* Flush stdout and report if it fails. It would be flushed on exit anyway
but this makes sure that output is not silently lost if it fails. */
err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
if (err)
{
exit_code = EXIT_FAILURE;
svn_cmdline_handle_exit_error(err, NULL, "svnmucc: ");
}
svn_pool_destroy(pool);
return exit_code;
}