diff options
Diffstat (limited to 'subversion/libsvn_subr/opt.c')
-rw-r--r-- | subversion/libsvn_subr/opt.c | 1240 |
1 files changed, 1240 insertions, 0 deletions
diff --git a/subversion/libsvn_subr/opt.c b/subversion/libsvn_subr/opt.c new file mode 100644 index 0000000..28ffed1 --- /dev/null +++ b/subversion/libsvn_subr/opt.c @@ -0,0 +1,1240 @@ +/* + * opt.c : option and argument parsing for Subversion command lines + * + * ==================================================================== + * 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. + * ==================================================================== + */ + + + +#define APR_WANT_STRFUNC +#include <apr_want.h> + +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <apr_pools.h> +#include <apr_general.h> +#include <apr_lib.h> +#include <apr_file_info.h> + +#include "svn_hash.h" +#include "svn_cmdline.h" +#include "svn_version.h" +#include "svn_types.h" +#include "svn_opt.h" +#include "svn_error.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_utf.h" +#include "svn_time.h" +#include "svn_props.h" +#include "svn_ctype.h" + +#include "private/svn_opt_private.h" + +#include "opt.h" +#include "svn_private_config.h" + + +/*** Code. ***/ + +const svn_opt_subcommand_desc2_t * +svn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table, + const char *cmd_name) +{ + int i = 0; + + if (cmd_name == NULL) + return NULL; + + while (table[i].name) { + int j; + if (strcmp(cmd_name, table[i].name) == 0) + return table + i; + for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++) + if (strcmp(cmd_name, table[i].aliases[j]) == 0) + return table + i; + + i++; + } + + /* If we get here, there was no matching subcommand name or alias. */ + return NULL; +} + +const apr_getopt_option_t * +svn_opt_get_option_from_code2(int code, + const apr_getopt_option_t *option_table, + const svn_opt_subcommand_desc2_t *command, + apr_pool_t *pool) +{ + apr_size_t i; + + for (i = 0; option_table[i].optch; i++) + if (option_table[i].optch == code) + { + if (command) + { + int j; + + for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) && + command->desc_overrides[j].optch); j++) + if (command->desc_overrides[j].optch == code) + { + apr_getopt_option_t *tmpopt = + apr_palloc(pool, sizeof(*tmpopt)); + *tmpopt = option_table[i]; + tmpopt->description = command->desc_overrides[j].desc; + return tmpopt; + } + } + return &(option_table[i]); + } + + return NULL; +} + + +const apr_getopt_option_t * +svn_opt_get_option_from_code(int code, + const apr_getopt_option_t *option_table) +{ + apr_size_t i; + + for (i = 0; option_table[i].optch; i++) + if (option_table[i].optch == code) + return &(option_table[i]); + + return NULL; +} + + +/* Like svn_opt_get_option_from_code2(), but also, if CODE appears a second + * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that + * second name, else set it to NULL. */ +static const apr_getopt_option_t * +get_option_from_code(const char **long_alias, + int code, + const apr_getopt_option_t *option_table, + const svn_opt_subcommand_desc2_t *command, + apr_pool_t *pool) +{ + const apr_getopt_option_t *i; + const apr_getopt_option_t *opt + = svn_opt_get_option_from_code2(code, option_table, command, pool); + + /* Find a long alias in the table, if there is one. */ + *long_alias = NULL; + for (i = option_table; i->optch; i++) + { + if (i->optch == code && i->name != opt->name) + { + *long_alias = i->name; + break; + } + } + + return opt; +} + + +/* Print an option OPT nicely into a STRING allocated in POOL. + * If OPT has a single-character short form, then print OPT->name (if not + * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias. + * If DOC is set, include the generic documentation string of OPT, + * localized to the current locale if a translation is available. + */ +static void +format_option(const char **string, + const apr_getopt_option_t *opt, + const char *long_alias, + svn_boolean_t doc, + apr_pool_t *pool) +{ + char *opts; + + if (opt == NULL) + { + *string = "?"; + return; + } + + /* We have a valid option which may or may not have a "short + name" (a single-character alias for the long option). */ + if (opt->optch <= 255) + opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name); + else if (long_alias) + opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias); + else + opts = apr_psprintf(pool, "--%s", opt->name); + + if (opt->has_arg) + opts = apr_pstrcat(pool, opts, _(" ARG"), (char *)NULL); + + if (doc) + opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description)); + + *string = opts; +} + +void +svn_opt_format_option(const char **string, + const apr_getopt_option_t *opt, + svn_boolean_t doc, + apr_pool_t *pool) +{ + format_option(string, opt, NULL, doc, pool); +} + + +svn_boolean_t +svn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command, + int option_code, + const int *global_options) +{ + apr_size_t i; + + for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) + if (command->valid_options[i] == option_code) + return TRUE; + + if (global_options) + for (i = 0; global_options[i]; i++) + if (global_options[i] == option_code) + return TRUE; + + return FALSE; +} + +svn_boolean_t +svn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command, + int option_code) +{ + return svn_opt_subcommand_takes_option3(command, + option_code, + NULL); +} + + +svn_boolean_t +svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command, + int option_code) +{ + apr_size_t i; + + for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) + if (command->valid_options[i] == option_code) + return TRUE; + + return FALSE; +} + + +/* Print the canonical command name for CMD, and all its aliases, to + STREAM. If HELP is set, print CMD's help string too, in which case + obtain option usage from OPTIONS_TABLE. */ +static svn_error_t * +print_command_info2(const svn_opt_subcommand_desc2_t *cmd, + const apr_getopt_option_t *options_table, + const int *global_options, + svn_boolean_t help, + apr_pool_t *pool, + FILE *stream) +{ + svn_boolean_t first_time; + apr_size_t i; + + /* Print the canonical command name. */ + SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool)); + + /* Print the list of aliases. */ + first_time = TRUE; + for (i = 0; i < SVN_OPT_MAX_ALIASES; i++) + { + if (cmd->aliases[i] == NULL) + break; + + if (first_time) { + SVN_ERR(svn_cmdline_fputs(" (", stream, pool)); + first_time = FALSE; + } + else + SVN_ERR(svn_cmdline_fputs(", ", stream, pool)); + + SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool)); + } + + if (! first_time) + SVN_ERR(svn_cmdline_fputs(")", stream, pool)); + + if (help) + { + const apr_getopt_option_t *option; + const char *long_alias; + svn_boolean_t have_options = FALSE; + + SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help))); + + /* Loop over all valid option codes attached to the subcommand */ + for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) + { + if (cmd->valid_options[i]) + { + if (!have_options) + { + SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"), + stream, pool)); + have_options = TRUE; + } + + /* convert each option code into an option */ + option = get_option_from_code(&long_alias, cmd->valid_options[i], + options_table, cmd, pool); + + /* print the option's docstring */ + if (option && option->description) + { + const char *optstr; + format_option(&optstr, option, long_alias, TRUE, pool); + SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n", + optstr)); + } + } + } + /* And global options too */ + if (global_options && *global_options) + { + SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"), + stream, pool)); + have_options = TRUE; + + for (i = 0; global_options[i]; i++) + { + + /* convert each option code into an option */ + option = get_option_from_code(&long_alias, global_options[i], + options_table, cmd, pool); + + /* print the option's docstring */ + if (option && option->description) + { + const char *optstr; + format_option(&optstr, option, long_alias, TRUE, pool); + SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n", + optstr)); + } + } + } + + if (have_options) + SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n")); + } + + return SVN_NO_ERROR; +} + +void +svn_opt_print_generic_help2(const char *header, + const svn_opt_subcommand_desc2_t *cmd_table, + const apr_getopt_option_t *opt_table, + const char *footer, + apr_pool_t *pool, FILE *stream) +{ + int i = 0; + svn_error_t *err; + + if (header) + if ((err = svn_cmdline_fputs(header, stream, pool))) + goto print_error; + + while (cmd_table[i].name) + { + if ((err = svn_cmdline_fputs(" ", stream, pool)) + || (err = print_command_info2(cmd_table + i, opt_table, + NULL, FALSE, + pool, stream)) + || (err = svn_cmdline_fputs("\n", stream, pool))) + goto print_error; + i++; + } + + if ((err = svn_cmdline_fputs("\n", stream, pool))) + goto print_error; + + if (footer) + if ((err = svn_cmdline_fputs(footer, stream, pool))) + goto print_error; + + return; + + print_error: + /* Issue #3014: + * Don't print anything on broken pipes. The pipe was likely + * closed by the process at the other end. We expect that + * process to perform error reporting as necessary. + * + * ### This assumes that there is only one error in a chain for + * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */ + if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR) + svn_handle_error2(err, stderr, FALSE, "svn: "); + svn_error_clear(err); +} + + +void +svn_opt_subcommand_help3(const char *subcommand, + const svn_opt_subcommand_desc2_t *table, + const apr_getopt_option_t *options_table, + const int *global_options, + apr_pool_t *pool) +{ + const svn_opt_subcommand_desc2_t *cmd = + svn_opt_get_canonical_subcommand2(table, subcommand); + svn_error_t *err; + + if (cmd) + err = print_command_info2(cmd, options_table, global_options, + TRUE, pool, stdout); + else + err = svn_cmdline_fprintf(stderr, pool, + _("\"%s\": unknown command.\n\n"), subcommand); + + if (err) { + svn_handle_error2(err, stderr, FALSE, "svn: "); + svn_error_clear(err); + } +} + + + +/*** Parsing revision and date options. ***/ + + +/** Parsing "X:Y"-style arguments. **/ + +/* If WORD matches one of the special revision descriptors, + * case-insensitively, set *REVISION accordingly: + * + * - For "head", set REVISION->kind to svn_opt_revision_head. + * + * - For "prev", set REVISION->kind to svn_opt_revision_previous. + * + * - For "base", set REVISION->kind to svn_opt_revision_base. + * + * - For "committed", set REVISION->kind to svn_opt_revision_committed. + * + * If match, return 0, else return -1 and don't touch REVISION. + */ +static int +revision_from_word(svn_opt_revision_t *revision, const char *word) +{ + if (svn_cstring_casecmp(word, "head") == 0) + { + revision->kind = svn_opt_revision_head; + } + else if (svn_cstring_casecmp(word, "prev") == 0) + { + revision->kind = svn_opt_revision_previous; + } + else if (svn_cstring_casecmp(word, "base") == 0) + { + revision->kind = svn_opt_revision_base; + } + else if (svn_cstring_casecmp(word, "committed") == 0) + { + revision->kind = svn_opt_revision_committed; + } + else + return -1; + + return 0; +} + + +/* Parse one revision specification. Return pointer to character + after revision, or NULL if the revision is invalid. Modifies + str, so make sure to pass a copy of anything precious. Uses + POOL for temporary allocation. */ +static char *parse_one_rev(svn_opt_revision_t *revision, char *str, + apr_pool_t *pool) +{ + char *end, save; + + /* Allow any number of 'r's to prefix a revision number, because + that way if a script pastes svn output into another svn command + (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work, + even when compounded. + + As it happens, none of our special revision words begins with + "r". If any ever do, then this code will have to get smarter. + + Incidentally, this allows "r{DATE}". We could avoid that with + some trivial code rearrangement, but it's not clear what would + be gained by doing so. */ + while (*str == 'r') + str++; + + if (*str == '{') + { + svn_boolean_t matched; + apr_time_t tm; + svn_error_t *err; + + /* Brackets denote a date. */ + str++; + end = strchr(str, '}'); + if (!end) + return NULL; + *end = '\0'; + err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool); + if (err) + { + svn_error_clear(err); + return NULL; + } + if (!matched) + return NULL; + revision->kind = svn_opt_revision_date; + revision->value.date = tm; + return end + 1; + } + else if (svn_ctype_isdigit(*str)) + { + /* It's a number. */ + end = str + 1; + while (svn_ctype_isdigit(*end)) + end++; + save = *end; + *end = '\0'; + revision->kind = svn_opt_revision_number; + revision->value.number = SVN_STR_TO_REV(str); + *end = save; + return end; + } + else if (svn_ctype_isalpha(*str)) + { + end = str + 1; + while (svn_ctype_isalpha(*end)) + end++; + save = *end; + *end = '\0'; + if (revision_from_word(revision, str) != 0) + return NULL; + *end = save; + return end; + } + else + return NULL; +} + + +int +svn_opt_parse_revision(svn_opt_revision_t *start_revision, + svn_opt_revision_t *end_revision, + const char *arg, + apr_pool_t *pool) +{ + char *left_rev, *right_rev, *end; + + /* Operate on a copy of the argument. */ + left_rev = apr_pstrdup(pool, arg); + + right_rev = parse_one_rev(start_revision, left_rev, pool); + if (right_rev && *right_rev == ':') + { + right_rev++; + end = parse_one_rev(end_revision, right_rev, pool); + if (!end || *end != '\0') + return -1; + } + else if (!right_rev || *right_rev != '\0') + return -1; + + return 0; +} + + +int +svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges, + const char *arg, + apr_pool_t *pool) +{ + svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range)); + + range->start.kind = svn_opt_revision_unspecified; + range->end.kind = svn_opt_revision_unspecified; + + if (svn_opt_parse_revision(&(range->start), &(range->end), + arg, pool) == -1) + return -1; + + APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range; + return 0; +} + +svn_error_t * +svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev, + svn_opt_revision_t *op_rev, + svn_boolean_t is_url, + svn_boolean_t notice_local_mods, + apr_pool_t *pool) +{ + if (peg_rev->kind == svn_opt_revision_unspecified) + { + if (is_url) + { + peg_rev->kind = svn_opt_revision_head; + } + else + { + if (notice_local_mods) + peg_rev->kind = svn_opt_revision_working; + else + peg_rev->kind = svn_opt_revision_base; + } + } + + if (op_rev->kind == svn_opt_revision_unspecified) + *op_rev = *peg_rev; + + return SVN_NO_ERROR; +} + +const char * +svn_opt__revision_to_string(const svn_opt_revision_t *revision, + apr_pool_t *result_pool) +{ + switch (revision->kind) + { + case svn_opt_revision_unspecified: + return "unspecified"; + case svn_opt_revision_number: + return apr_psprintf(result_pool, "%ld", revision->value.number); + case svn_opt_revision_date: + /* ### svn_time_to_human_cstring()? */ + return svn_time_to_cstring(revision->value.date, result_pool); + case svn_opt_revision_committed: + return "committed"; + case svn_opt_revision_previous: + return "previous"; + case svn_opt_revision_base: + return "base"; + case svn_opt_revision_working: + return "working"; + case svn_opt_revision_head: + return "head"; + default: + return NULL; + } +} + +svn_opt_revision_range_t * +svn_opt__revision_range_create(const svn_opt_revision_t *start_revision, + const svn_opt_revision_t *end_revision, + apr_pool_t *result_pool) +{ + svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range)); + + range->start = *start_revision; + range->end = *end_revision; + return range; +} + +svn_opt_revision_range_t * +svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum, + svn_revnum_t end_revnum, + apr_pool_t *result_pool) +{ + svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range)); + + range->start.kind = svn_opt_revision_number; + range->start.value.number = start_revnum; + range->end.kind = svn_opt_revision_number; + range->end.value.number = end_revnum; + return range; +} + + + +/*** Parsing arguments. ***/ +#define DEFAULT_ARRAY_SIZE 5 + + +/* Copy STR into POOL and push the copy onto ARRAY. */ +static void +array_push_str(apr_array_header_t *array, + const char *str, + apr_pool_t *pool) +{ + /* ### Not sure if this function is still necessary. It used to + convert str to svn_stringbuf_t * and push it, but now it just + dups str in pool and pushes the copy. So its only effect is + transfer str's lifetime to pool. Is that something callers are + depending on? */ + + APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str); +} + + +void +svn_opt_push_implicit_dot_target(apr_array_header_t *targets, + apr_pool_t *pool) +{ + if (targets->nelts == 0) + APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */ + assert(targets->nelts); +} + + +svn_error_t * +svn_opt_parse_num_args(apr_array_header_t **args_p, + apr_getopt_t *os, + int num_args, + apr_pool_t *pool) +{ + int i; + apr_array_header_t *args + = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); + + /* loop for num_args and add each arg to the args array */ + for (i = 0; i < num_args; i++) + { + if (os->ind >= os->argc) + { + return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); + } + array_push_str(args, os->argv[os->ind++], pool); + } + + *args_p = args; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt_parse_all_args(apr_array_header_t **args_p, + apr_getopt_t *os, + apr_pool_t *pool) +{ + apr_array_header_t *args + = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); + + if (os->ind > os->argc) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); + } + while (os->ind < os->argc) + { + array_push_str(args, os->argv[os->ind++], pool); + } + + *args_p = args; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_opt_parse_path(svn_opt_revision_t *rev, + const char **truepath, + const char *path /* UTF-8! */, + apr_pool_t *pool) +{ + const char *peg_rev; + + SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool)); + + /* Parse the peg revision, if one was found */ + if (strlen(peg_rev)) + { + int ret; + svn_opt_revision_t start_revision, end_revision; + + end_revision.kind = svn_opt_revision_unspecified; + + if (peg_rev[1] == '\0') /* looking at empty peg revision */ + { + ret = 0; + start_revision.kind = svn_opt_revision_unspecified; + start_revision.value.number = 0; + } + else /* looking at non-empty peg revision */ + { + const char *rev_str = &peg_rev[1]; + + /* URLs get treated differently from wc paths. */ + if (svn_path_is_url(path)) + { + /* URLs are URI-encoded, so we look for dates with + URI-encoded delimeters. */ + size_t rev_len = strlen(rev_str); + if (rev_len > 6 + && rev_str[0] == '%' + && rev_str[1] == '7' + && (rev_str[2] == 'B' + || rev_str[2] == 'b') + && rev_str[rev_len-3] == '%' + && rev_str[rev_len-2] == '7' + && (rev_str[rev_len-1] == 'D' + || rev_str[rev_len-1] == 'd')) + { + rev_str = svn_path_uri_decode(rev_str, pool); + } + } + ret = svn_opt_parse_revision(&start_revision, + &end_revision, + rev_str, pool); + } + + if (ret || end_revision.kind != svn_opt_revision_unspecified) + { + /* If an svn+ssh URL was used and it contains only one @, + * provide an error message that presents a possible solution + * to the parsing error (issue #2349). */ + if (strncmp(path, "svn+ssh://", 10) == 0) + { + const char *at; + + at = strchr(path, '@'); + if (at && strrchr(path, '@') == at) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Syntax error parsing peg revision " + "'%s'; did you mean '%s@'?"), + &peg_rev[1], path); + } + + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Syntax error parsing peg revision '%s'"), + &peg_rev[1]); + } + rev->kind = start_revision.kind; + rev->value = start_revision.value; + } + else + { + /* Didn't find a peg revision. */ + rev->kind = svn_opt_revision_unspecified; + } + + return SVN_NO_ERROR; +} + + +/* Note: This is substantially copied into svn_client_args_to_target_array() in + * order to move to libsvn_client while maintaining backward compatibility. */ +svn_error_t * +svn_opt__args_to_target_array(apr_array_header_t **targets_p, + apr_getopt_t *os, + const apr_array_header_t *known_targets, + apr_pool_t *pool) +{ + int i; + svn_error_t *err = SVN_NO_ERROR; + apr_array_header_t *input_targets = + apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); + apr_array_header_t *output_targets = + apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); + + /* Step 1: create a master array of targets that are in UTF-8 + encoding, and come from concatenating the targets left by apr_getopt, + plus any extra targets (e.g., from the --targets switch.) */ + + for (; os->ind < os->argc; os->ind++) + { + /* The apr_getopt targets are still in native encoding. */ + const char *raw_target = os->argv[os->ind]; + SVN_ERR(svn_utf_cstring_to_utf8 + ((const char **) apr_array_push(input_targets), + raw_target, pool)); + } + + if (known_targets) + { + for (i = 0; i < known_targets->nelts; i++) + { + /* The --targets array have already been converted to UTF-8, + because we needed to split up the list with svn_cstring_split. */ + const char *utf8_target = APR_ARRAY_IDX(known_targets, + i, const char *); + APR_ARRAY_PUSH(input_targets, const char *) = utf8_target; + } + } + + /* Step 2: process each target. */ + + for (i = 0; i < input_targets->nelts; i++) + { + const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *); + const char *true_target; + const char *target; /* after all processing is finished */ + const char *peg_rev; + + /* + * This is needed so that the target can be properly canonicalized, + * otherwise the canonicalization does not treat a ".@BASE" as a "." + * with a BASE peg revision, and it is not canonicalized to "@BASE". + * If any peg revision exists, it is appended to the final + * canonicalized path or URL. Do not use svn_opt_parse_path() + * because the resulting peg revision is a structure that would have + * to be converted back into a string. Converting from a string date + * to the apr_time_t field in the svn_opt_revision_value_t and back to + * a string would not necessarily preserve the exact bytes of the + * input date, so its easier just to keep it in string form. + */ + SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev, + utf8_target, pool)); + + /* URLs and wc-paths get treated differently. */ + if (svn_path_is_url(true_target)) + { + SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target, + pool)); + } + else /* not a url, so treat as a path */ + { + const char *base_name; + + SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target, + pool)); + + /* If the target has the same name as a Subversion + working copy administrative dir, skip it. */ + base_name = svn_dirent_basename(true_target, pool); + + /* FIXME: + The canonical list of administrative directory names is + maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir(). + That list can't be used here, because that use would + create a circular dependency between libsvn_wc and + libsvn_subr. Make sure changes to the lists are always + synchronized! */ + if (0 == strcmp(base_name, ".svn") + || 0 == strcmp(base_name, "_svn")) + { + err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, + err, _("'%s' ends in a reserved name"), + utf8_target); + continue; + } + } + + target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL); + + APR_ARRAY_PUSH(output_targets, const char *) = target; + } + + + /* kff todo: need to remove redundancies from targets before + passing it to the cmd_func. */ + + *targets_p = output_targets; + + return err; +} + +svn_error_t * +svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec, + apr_pool_t *pool) +{ + const char *sep, *propname; + svn_string_t *propval; + + if (! *revprop_spec) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Revision property pair is empty")); + + if (! *revprop_table_p) + *revprop_table_p = apr_hash_make(pool); + + sep = strchr(revprop_spec, '='); + if (sep) + { + propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec); + SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool)); + propval = svn_string_create(sep + 1, pool); + } + else + { + SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool)); + propval = svn_string_create_empty(pool); + } + + if (!svn_prop_name_is_valid(propname)) + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is not a valid Subversion property name"), + propname); + + svn_hash_sets(*revprop_table_p, propname, propval); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt__split_arg_at_peg_revision(const char **true_target, + const char **peg_revision, + const char *utf8_target, + apr_pool_t *pool) +{ + const char *peg_start = NULL; /* pointer to the peg revision, if any */ + const char *ptr; + + for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target; + --ptr) + { + /* If we hit a path separator, stop looking. This is OK + only because our revision specifiers can't contain '/'. */ + if (*ptr == '/') + break; + + if (*ptr == '@') + { + peg_start = ptr; + break; + } + } + + if (peg_start) + { + /* Error out if target is the empty string. */ + if (ptr == utf8_target) + return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL, + _("'%s' is just a peg revision. " + "Maybe try '%s@' instead?"), + utf8_target, utf8_target); + + *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target); + if (peg_revision) + *peg_revision = apr_pstrdup(pool, peg_start); + } + else + { + *true_target = utf8_target; + if (peg_revision) + *peg_revision = ""; + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in, + apr_pool_t *pool) +{ + const char *target; + + /* Convert to URI. */ + target = svn_path_uri_from_iri(url_in, pool); + /* Auto-escape some ASCII characters. */ + target = svn_path_uri_autoescape(target, pool); + +#if '/' != SVN_PATH_LOCAL_SEPARATOR + /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */ + if (strchr(target, SVN_PATH_LOCAL_SEPARATOR)) + { + char *p = apr_pstrdup(pool, target); + target = p; + + /* Convert all local-style separators to the canonical ones. */ + for (; *p != '\0'; ++p) + if (*p == SVN_PATH_LOCAL_SEPARATOR) + *p = '/'; + } +#endif + + /* Verify that no backpaths are present in the URL. */ + if (svn_path_is_backpath_present(target)) + return svn_error_createf(SVN_ERR_BAD_URL, 0, + _("URL '%s' contains a '..' element"), + target); + + /* Strip any trailing '/' and collapse other redundant elements. */ + target = svn_uri_canonicalize(target, pool); + + *url_out = target; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in, + apr_pool_t *pool) +{ + const char *apr_target; + char *truenamed_target; /* APR-encoded */ + apr_status_t apr_err; + + /* canonicalize case, and change all separators to '/'. */ + SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool)); + apr_err = apr_filepath_merge(&truenamed_target, "", apr_target, + APR_FILEPATH_TRUENAME, pool); + + if (!apr_err) + /* We have a canonicalized APR-encoded target now. */ + apr_target = truenamed_target; + else if (APR_STATUS_IS_ENOENT(apr_err)) + /* It's okay for the file to not exist, that just means we + have to accept the case given to the client. We'll use + the original APR-encoded target. */ + ; + else + return svn_error_createf(apr_err, NULL, + _("Error resolving case of '%s'"), + svn_dirent_local_style(path_in, pool)); + + /* convert back to UTF-8. */ + SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool)); + *path_out = svn_dirent_canonicalize(*path_out, pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_opt__print_version_info(const char *pgm_name, + const char *footer, + const svn_version_extended_t *info, + svn_boolean_t quiet, + svn_boolean_t verbose, + apr_pool_t *pool) +{ + if (quiet) + return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER); + + SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n" + " compiled %s, %s on %s\n\n"), + pgm_name, SVN_VERSION, + svn_version_ext_build_date(info), + svn_version_ext_build_time(info), + svn_version_ext_build_host(info))); + SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info))); + + if (footer) + { + SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer)); + } + + if (verbose) + { + const apr_array_header_t *libs; + + SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool)); + SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"), + svn_version_ext_runtime_host(info))); + if (svn_version_ext_runtime_osname(info)) + { + SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"), + svn_version_ext_runtime_osname(info))); + } + + libs = svn_version_ext_linked_libs(info); + if (libs && libs->nelts) + { + const svn_version_ext_linked_lib_t *lib; + int i; + + SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"), + stdout, pool)); + for (i = 0; i < libs->nelts; ++i) + { + lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t); + if (lib->runtime_version) + SVN_ERR(svn_cmdline_printf(pool, + " - %s %s (compiled with %s)\n", + lib->name, + lib->runtime_version, + lib->compiled_version)); + else + SVN_ERR(svn_cmdline_printf(pool, + " - %s %s (static)\n", + lib->name, + lib->compiled_version)); + } + } + + libs = svn_version_ext_loaded_libs(info); + if (libs && libs->nelts) + { + const svn_version_ext_loaded_lib_t *lib; + int i; + + SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"), + stdout, pool)); + for (i = 0; i < libs->nelts; ++i) + { + lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t); + if (lib->version) + SVN_ERR(svn_cmdline_printf(pool, + " - %s (%s)\n", + lib->name, lib->version)); + else + SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name)); + } + } + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_opt_print_help4(apr_getopt_t *os, + const char *pgm_name, + svn_boolean_t print_version, + svn_boolean_t quiet, + svn_boolean_t verbose, + const char *version_footer, + const char *header, + const svn_opt_subcommand_desc2_t *cmd_table, + const apr_getopt_option_t *option_table, + const int *global_options, + const char *footer, + apr_pool_t *pool) +{ + apr_array_header_t *targets = NULL; + + if (os) + SVN_ERR(svn_opt_parse_all_args(&targets, os, pool)); + + if (os && targets->nelts) /* help on subcommand(s) requested */ + { + int i; + + for (i = 0; i < targets->nelts; i++) + { + svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *), + cmd_table, option_table, + global_options, pool); + } + } + else if (print_version) /* just --version */ + { + SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer, + svn_version_extended(verbose, pool), + quiet, verbose, pool)); + } + else if (os && !targets->nelts) /* `-h', `--help', or `help' */ + svn_opt_print_generic_help2(header, + cmd_table, + option_table, + footer, + pool, + stdout); + else /* unknown option or cmd */ + SVN_ERR(svn_cmdline_fprintf(stderr, pool, + _("Type '%s help' for usage.\n"), pgm_name)); + + return SVN_NO_ERROR; +} |