2 * opt.c : option and argument parsing for Subversion command lines
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
26 #define APR_WANT_STRFUNC
32 #include <apr_pools.h>
33 #include <apr_general.h>
35 #include <apr_file_info.h>
38 #include "svn_cmdline.h"
39 #include "svn_version.h"
40 #include "svn_types.h"
42 #include "svn_error.h"
43 #include "svn_dirent_uri.h"
47 #include "svn_props.h"
48 #include "svn_ctype.h"
50 #include "private/svn_opt_private.h"
53 #include "svn_private_config.h"
58 const svn_opt_subcommand_desc2_t *
59 svn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table,
67 while (table[i].name) {
69 if (strcmp(cmd_name, table[i].name) == 0)
71 for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
72 if (strcmp(cmd_name, table[i].aliases[j]) == 0)
78 /* If we get here, there was no matching subcommand name or alias. */
82 const apr_getopt_option_t *
83 svn_opt_get_option_from_code2(int code,
84 const apr_getopt_option_t *option_table,
85 const svn_opt_subcommand_desc2_t *command,
90 for (i = 0; option_table[i].optch; i++)
91 if (option_table[i].optch == code)
97 for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) &&
98 command->desc_overrides[j].optch); j++)
99 if (command->desc_overrides[j].optch == code)
101 apr_getopt_option_t *tmpopt =
102 apr_palloc(pool, sizeof(*tmpopt));
103 *tmpopt = option_table[i];
104 tmpopt->description = command->desc_overrides[j].desc;
108 return &(option_table[i]);
115 const apr_getopt_option_t *
116 svn_opt_get_option_from_code(int code,
117 const apr_getopt_option_t *option_table)
121 for (i = 0; option_table[i].optch; i++)
122 if (option_table[i].optch == code)
123 return &(option_table[i]);
129 /* Like svn_opt_get_option_from_code2(), but also, if CODE appears a second
130 * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that
131 * second name, else set it to NULL. */
132 static const apr_getopt_option_t *
133 get_option_from_code(const char **long_alias,
135 const apr_getopt_option_t *option_table,
136 const svn_opt_subcommand_desc2_t *command,
139 const apr_getopt_option_t *i;
140 const apr_getopt_option_t *opt
141 = svn_opt_get_option_from_code2(code, option_table, command, pool);
143 /* Find a long alias in the table, if there is one. */
145 for (i = option_table; i->optch; i++)
147 if (i->optch == code && i->name != opt->name)
149 *long_alias = i->name;
158 /* Print an option OPT nicely into a STRING allocated in POOL.
159 * If OPT has a single-character short form, then print OPT->name (if not
160 * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias.
161 * If DOC is set, include the generic documentation string of OPT,
162 * localized to the current locale if a translation is available.
165 format_option(const char **string,
166 const apr_getopt_option_t *opt,
167 const char *long_alias,
179 /* We have a valid option which may or may not have a "short
180 name" (a single-character alias for the long option). */
181 if (opt->optch <= 255)
182 opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name);
184 opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias);
186 opts = apr_psprintf(pool, "--%s", opt->name);
189 opts = apr_pstrcat(pool, opts, _(" ARG"), (char *)NULL);
192 opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description));
198 svn_opt_format_option(const char **string,
199 const apr_getopt_option_t *opt,
203 format_option(string, opt, NULL, doc, pool);
208 svn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command,
210 const int *global_options)
214 for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
215 if (command->valid_options[i] == option_code)
219 for (i = 0; global_options[i]; i++)
220 if (global_options[i] == option_code)
227 svn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command,
230 return svn_opt_subcommand_takes_option3(command,
237 svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command,
242 for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
243 if (command->valid_options[i] == option_code)
250 /* Print the canonical command name for CMD, and all its aliases, to
251 STREAM. If HELP is set, print CMD's help string too, in which case
252 obtain option usage from OPTIONS_TABLE. */
254 print_command_info2(const svn_opt_subcommand_desc2_t *cmd,
255 const apr_getopt_option_t *options_table,
256 const int *global_options,
261 svn_boolean_t first_time;
264 /* Print the canonical command name. */
265 SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
267 /* Print the list of aliases. */
269 for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
271 if (cmd->aliases[i] == NULL)
275 SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
279 SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
281 SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
285 SVN_ERR(svn_cmdline_fputs(")", stream, pool));
289 const apr_getopt_option_t *option;
290 const char *long_alias;
291 svn_boolean_t have_options = FALSE;
293 SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help)));
295 /* Loop over all valid option codes attached to the subcommand */
296 for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
298 if (cmd->valid_options[i])
302 SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
307 /* convert each option code into an option */
308 option = get_option_from_code(&long_alias, cmd->valid_options[i],
309 options_table, cmd, pool);
311 /* print the option's docstring */
312 if (option && option->description)
315 format_option(&optstr, option, long_alias, TRUE, pool);
316 SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n",
321 /* And global options too */
322 if (global_options && *global_options)
324 SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"),
328 for (i = 0; global_options[i]; i++)
331 /* convert each option code into an option */
332 option = get_option_from_code(&long_alias, global_options[i],
333 options_table, cmd, pool);
335 /* print the option's docstring */
336 if (option && option->description)
339 format_option(&optstr, option, long_alias, TRUE, pool);
340 SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n",
347 SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
354 svn_opt_print_generic_help2(const char *header,
355 const svn_opt_subcommand_desc2_t *cmd_table,
356 const apr_getopt_option_t *opt_table,
358 apr_pool_t *pool, FILE *stream)
364 if ((err = svn_cmdline_fputs(header, stream, pool)))
367 while (cmd_table[i].name)
369 if ((err = svn_cmdline_fputs(" ", stream, pool))
370 || (err = print_command_info2(cmd_table + i, opt_table,
373 || (err = svn_cmdline_fputs("\n", stream, pool)))
378 if ((err = svn_cmdline_fputs("\n", stream, pool)))
382 if ((err = svn_cmdline_fputs(footer, stream, pool)))
389 * Don't print anything on broken pipes. The pipe was likely
390 * closed by the process at the other end. We expect that
391 * process to perform error reporting as necessary.
393 * ### This assumes that there is only one error in a chain for
394 * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
395 if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
396 svn_handle_error2(err, stderr, FALSE, "svn: ");
397 svn_error_clear(err);
402 svn_opt_subcommand_help3(const char *subcommand,
403 const svn_opt_subcommand_desc2_t *table,
404 const apr_getopt_option_t *options_table,
405 const int *global_options,
408 const svn_opt_subcommand_desc2_t *cmd =
409 svn_opt_get_canonical_subcommand2(table, subcommand);
413 err = print_command_info2(cmd, options_table, global_options,
416 err = svn_cmdline_fprintf(stderr, pool,
417 _("\"%s\": unknown command.\n\n"), subcommand);
420 svn_handle_error2(err, stderr, FALSE, "svn: ");
421 svn_error_clear(err);
427 /*** Parsing revision and date options. ***/
430 /** Parsing "X:Y"-style arguments. **/
432 /* If WORD matches one of the special revision descriptors,
433 * case-insensitively, set *REVISION accordingly:
435 * - For "head", set REVISION->kind to svn_opt_revision_head.
437 * - For "prev", set REVISION->kind to svn_opt_revision_previous.
439 * - For "base", set REVISION->kind to svn_opt_revision_base.
441 * - For "committed", set REVISION->kind to svn_opt_revision_committed.
443 * If match, return 0, else return -1 and don't touch REVISION.
446 revision_from_word(svn_opt_revision_t *revision, const char *word)
448 if (svn_cstring_casecmp(word, "head") == 0)
450 revision->kind = svn_opt_revision_head;
452 else if (svn_cstring_casecmp(word, "prev") == 0)
454 revision->kind = svn_opt_revision_previous;
456 else if (svn_cstring_casecmp(word, "base") == 0)
458 revision->kind = svn_opt_revision_base;
460 else if (svn_cstring_casecmp(word, "committed") == 0)
462 revision->kind = svn_opt_revision_committed;
471 /* Parse one revision specification. Return pointer to character
472 after revision, or NULL if the revision is invalid. Modifies
473 str, so make sure to pass a copy of anything precious. Uses
474 POOL for temporary allocation. */
475 static char *parse_one_rev(svn_opt_revision_t *revision, char *str,
480 /* Allow any number of 'r's to prefix a revision number, because
481 that way if a script pastes svn output into another svn command
482 (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work,
483 even when compounded.
485 As it happens, none of our special revision words begins with
486 "r". If any ever do, then this code will have to get smarter.
488 Incidentally, this allows "r{DATE}". We could avoid that with
489 some trivial code rearrangement, but it's not clear what would
490 be gained by doing so. */
496 svn_boolean_t matched;
500 /* Brackets denote a date. */
502 end = strchr(str, '}');
506 err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
509 svn_error_clear(err);
514 revision->kind = svn_opt_revision_date;
515 revision->value.date = tm;
518 else if (svn_ctype_isdigit(*str))
522 while (svn_ctype_isdigit(*end))
526 revision->kind = svn_opt_revision_number;
527 revision->value.number = SVN_STR_TO_REV(str);
531 else if (svn_ctype_isalpha(*str))
534 while (svn_ctype_isalpha(*end))
538 if (revision_from_word(revision, str) != 0)
549 svn_opt_parse_revision(svn_opt_revision_t *start_revision,
550 svn_opt_revision_t *end_revision,
554 char *left_rev, *right_rev, *end;
556 /* Operate on a copy of the argument. */
557 left_rev = apr_pstrdup(pool, arg);
559 right_rev = parse_one_rev(start_revision, left_rev, pool);
560 if (right_rev && *right_rev == ':')
563 end = parse_one_rev(end_revision, right_rev, pool);
564 if (!end || *end != '\0')
567 else if (!right_rev || *right_rev != '\0')
575 svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
579 svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
581 range->start.kind = svn_opt_revision_unspecified;
582 range->end.kind = svn_opt_revision_unspecified;
584 if (svn_opt_parse_revision(&(range->start), &(range->end),
588 APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
593 svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
594 svn_opt_revision_t *op_rev,
595 svn_boolean_t is_url,
596 svn_boolean_t notice_local_mods,
599 if (peg_rev->kind == svn_opt_revision_unspecified)
603 peg_rev->kind = svn_opt_revision_head;
607 if (notice_local_mods)
608 peg_rev->kind = svn_opt_revision_working;
610 peg_rev->kind = svn_opt_revision_base;
614 if (op_rev->kind == svn_opt_revision_unspecified)
621 svn_opt__revision_to_string(const svn_opt_revision_t *revision,
622 apr_pool_t *result_pool)
624 switch (revision->kind)
626 case svn_opt_revision_unspecified:
627 return "unspecified";
628 case svn_opt_revision_number:
629 return apr_psprintf(result_pool, "%ld", revision->value.number);
630 case svn_opt_revision_date:
631 /* ### svn_time_to_human_cstring()? */
632 return svn_time_to_cstring(revision->value.date, result_pool);
633 case svn_opt_revision_committed:
635 case svn_opt_revision_previous:
637 case svn_opt_revision_base:
639 case svn_opt_revision_working:
641 case svn_opt_revision_head:
648 svn_opt_revision_range_t *
649 svn_opt__revision_range_create(const svn_opt_revision_t *start_revision,
650 const svn_opt_revision_t *end_revision,
651 apr_pool_t *result_pool)
653 svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
655 range->start = *start_revision;
656 range->end = *end_revision;
660 svn_opt_revision_range_t *
661 svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,
662 svn_revnum_t end_revnum,
663 apr_pool_t *result_pool)
665 svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
667 range->start.kind = svn_opt_revision_number;
668 range->start.value.number = start_revnum;
669 range->end.kind = svn_opt_revision_number;
670 range->end.value.number = end_revnum;
676 /*** Parsing arguments. ***/
677 #define DEFAULT_ARRAY_SIZE 5
680 /* Copy STR into POOL and push the copy onto ARRAY. */
682 array_push_str(apr_array_header_t *array,
686 /* ### Not sure if this function is still necessary. It used to
687 convert str to svn_stringbuf_t * and push it, but now it just
688 dups str in pool and pushes the copy. So its only effect is
689 transfer str's lifetime to pool. Is that something callers are
692 APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
697 svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
700 if (targets->nelts == 0)
701 APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */
702 assert(targets->nelts);
707 svn_opt_parse_num_args(apr_array_header_t **args_p,
713 apr_array_header_t *args
714 = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
716 /* loop for num_args and add each arg to the args array */
717 for (i = 0; i < num_args; i++)
719 if (os->ind >= os->argc)
721 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
723 array_push_str(args, os->argv[os->ind++], pool);
731 svn_opt_parse_all_args(apr_array_header_t **args_p,
735 apr_array_header_t *args
736 = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
738 if (os->ind > os->argc)
740 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
742 while (os->ind < os->argc)
744 array_push_str(args, os->argv[os->ind++], pool);
753 svn_opt_parse_path(svn_opt_revision_t *rev,
754 const char **truepath,
755 const char *path /* UTF-8! */,
760 SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool));
762 /* Parse the peg revision, if one was found */
766 svn_opt_revision_t start_revision, end_revision;
768 end_revision.kind = svn_opt_revision_unspecified;
770 if (peg_rev[1] == '\0') /* looking at empty peg revision */
773 start_revision.kind = svn_opt_revision_unspecified;
774 start_revision.value.number = 0;
776 else /* looking at non-empty peg revision */
778 const char *rev_str = &peg_rev[1];
780 /* URLs get treated differently from wc paths. */
781 if (svn_path_is_url(path))
783 /* URLs are URI-encoded, so we look for dates with
784 URI-encoded delimeters. */
785 size_t rev_len = strlen(rev_str);
789 && (rev_str[2] == 'B'
790 || rev_str[2] == 'b')
791 && rev_str[rev_len-3] == '%'
792 && rev_str[rev_len-2] == '7'
793 && (rev_str[rev_len-1] == 'D'
794 || rev_str[rev_len-1] == 'd'))
796 rev_str = svn_path_uri_decode(rev_str, pool);
799 ret = svn_opt_parse_revision(&start_revision,
804 if (ret || end_revision.kind != svn_opt_revision_unspecified)
806 /* If an svn+ssh URL was used and it contains only one @,
807 * provide an error message that presents a possible solution
808 * to the parsing error (issue #2349). */
809 if (strncmp(path, "svn+ssh://", 10) == 0)
813 at = strchr(path, '@');
814 if (at && strrchr(path, '@') == at)
815 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
816 _("Syntax error parsing peg revision "
817 "'%s'; did you mean '%s@'?"),
821 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
822 _("Syntax error parsing peg revision '%s'"),
825 rev->kind = start_revision.kind;
826 rev->value = start_revision.value;
830 /* Didn't find a peg revision. */
831 rev->kind = svn_opt_revision_unspecified;
838 /* Note: This is substantially copied into svn_client_args_to_target_array() in
839 * order to move to libsvn_client while maintaining backward compatibility. */
841 svn_opt__args_to_target_array(apr_array_header_t **targets_p,
843 const apr_array_header_t *known_targets,
847 svn_error_t *err = SVN_NO_ERROR;
848 apr_array_header_t *input_targets =
849 apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
850 apr_array_header_t *output_targets =
851 apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
853 /* Step 1: create a master array of targets that are in UTF-8
854 encoding, and come from concatenating the targets left by apr_getopt,
855 plus any extra targets (e.g., from the --targets switch.) */
857 for (; os->ind < os->argc; os->ind++)
859 /* The apr_getopt targets are still in native encoding. */
860 const char *raw_target = os->argv[os->ind];
861 SVN_ERR(svn_utf_cstring_to_utf8
862 ((const char **) apr_array_push(input_targets),
868 for (i = 0; i < known_targets->nelts; i++)
870 /* The --targets array have already been converted to UTF-8,
871 because we needed to split up the list with svn_cstring_split. */
872 const char *utf8_target = APR_ARRAY_IDX(known_targets,
874 APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
878 /* Step 2: process each target. */
880 for (i = 0; i < input_targets->nelts; i++)
882 const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
883 const char *true_target;
884 const char *target; /* after all processing is finished */
888 * This is needed so that the target can be properly canonicalized,
889 * otherwise the canonicalization does not treat a ".@BASE" as a "."
890 * with a BASE peg revision, and it is not canonicalized to "@BASE".
891 * If any peg revision exists, it is appended to the final
892 * canonicalized path or URL. Do not use svn_opt_parse_path()
893 * because the resulting peg revision is a structure that would have
894 * to be converted back into a string. Converting from a string date
895 * to the apr_time_t field in the svn_opt_revision_value_t and back to
896 * a string would not necessarily preserve the exact bytes of the
897 * input date, so its easier just to keep it in string form.
899 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
902 /* URLs and wc-paths get treated differently. */
903 if (svn_path_is_url(true_target))
905 SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target,
908 else /* not a url, so treat as a path */
910 const char *base_name;
912 SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target,
915 /* If the target has the same name as a Subversion
916 working copy administrative dir, skip it. */
917 base_name = svn_dirent_basename(true_target, pool);
920 The canonical list of administrative directory names is
921 maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
922 That list can't be used here, because that use would
923 create a circular dependency between libsvn_wc and
924 libsvn_subr. Make sure changes to the lists are always
926 if (0 == strcmp(base_name, ".svn")
927 || 0 == strcmp(base_name, "_svn"))
929 err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
930 err, _("'%s' ends in a reserved name"),
936 target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL);
938 APR_ARRAY_PUSH(output_targets, const char *) = target;
942 /* kff todo: need to remove redundancies from targets before
943 passing it to the cmd_func. */
945 *targets_p = output_targets;
951 svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
954 const char *sep, *propname;
955 svn_string_t *propval;
958 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
959 _("Revision property pair is empty"));
961 if (! *revprop_table_p)
962 *revprop_table_p = apr_hash_make(pool);
964 sep = strchr(revprop_spec, '=');
967 propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec);
968 SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool));
969 propval = svn_string_create(sep + 1, pool);
973 SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
974 propval = svn_string_create_empty(pool);
977 if (!svn_prop_name_is_valid(propname))
978 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
979 _("'%s' is not a valid Subversion property name"),
982 svn_hash_sets(*revprop_table_p, propname, propval);
988 svn_opt__split_arg_at_peg_revision(const char **true_target,
989 const char **peg_revision,
990 const char *utf8_target,
993 const char *peg_start = NULL; /* pointer to the peg revision, if any */
996 for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target;
999 /* If we hit a path separator, stop looking. This is OK
1000 only because our revision specifiers can't contain '/'. */
1013 /* Error out if target is the empty string. */
1014 if (ptr == utf8_target)
1015 return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
1016 _("'%s' is just a peg revision. "
1017 "Maybe try '%s@' instead?"),
1018 utf8_target, utf8_target);
1020 *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target);
1022 *peg_revision = apr_pstrdup(pool, peg_start);
1026 *true_target = utf8_target;
1031 return SVN_NO_ERROR;
1035 svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in,
1040 /* Convert to URI. */
1041 target = svn_path_uri_from_iri(url_in, pool);
1042 /* Auto-escape some ASCII characters. */
1043 target = svn_path_uri_autoescape(target, pool);
1045 #if '/' != SVN_PATH_LOCAL_SEPARATOR
1046 /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */
1047 if (strchr(target, SVN_PATH_LOCAL_SEPARATOR))
1049 char *p = apr_pstrdup(pool, target);
1052 /* Convert all local-style separators to the canonical ones. */
1053 for (; *p != '\0'; ++p)
1054 if (*p == SVN_PATH_LOCAL_SEPARATOR)
1059 /* Verify that no backpaths are present in the URL. */
1060 if (svn_path_is_backpath_present(target))
1061 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1062 _("URL '%s' contains a '..' element"),
1065 /* Strip any trailing '/' and collapse other redundant elements. */
1066 target = svn_uri_canonicalize(target, pool);
1069 return SVN_NO_ERROR;
1073 svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in,
1076 const char *apr_target;
1077 char *truenamed_target; /* APR-encoded */
1078 apr_status_t apr_err;
1080 /* canonicalize case, and change all separators to '/'. */
1081 SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool));
1082 apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
1083 APR_FILEPATH_TRUENAME, pool);
1086 /* We have a canonicalized APR-encoded target now. */
1087 apr_target = truenamed_target;
1088 else if (APR_STATUS_IS_ENOENT(apr_err))
1089 /* It's okay for the file to not exist, that just means we
1090 have to accept the case given to the client. We'll use
1091 the original APR-encoded target. */
1094 return svn_error_createf(apr_err, NULL,
1095 _("Error resolving case of '%s'"),
1096 svn_dirent_local_style(path_in, pool));
1098 /* convert back to UTF-8. */
1099 SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool));
1100 *path_out = svn_dirent_canonicalize(*path_out, pool);
1102 return SVN_NO_ERROR;
1107 svn_opt__print_version_info(const char *pgm_name,
1109 const svn_version_extended_t *info,
1110 svn_boolean_t quiet,
1111 svn_boolean_t verbose,
1115 return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER);
1117 SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
1118 " compiled on %s\n\n"),
1119 pgm_name, SVN_VERSION,
1120 svn_version_ext_build_host(info)));
1121 SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
1125 SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
1130 const apr_array_header_t *libs;
1132 SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
1133 SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
1134 svn_version_ext_runtime_host(info)));
1135 if (svn_version_ext_runtime_osname(info))
1137 SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"),
1138 svn_version_ext_runtime_osname(info)));
1141 libs = svn_version_ext_linked_libs(info);
1142 if (libs && libs->nelts)
1144 const svn_version_ext_linked_lib_t *lib;
1147 SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
1149 for (i = 0; i < libs->nelts; ++i)
1151 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
1152 if (lib->runtime_version)
1153 SVN_ERR(svn_cmdline_printf(pool,
1154 " - %s %s (compiled with %s)\n",
1156 lib->runtime_version,
1157 lib->compiled_version));
1159 SVN_ERR(svn_cmdline_printf(pool,
1160 " - %s %s (static)\n",
1162 lib->compiled_version));
1166 libs = svn_version_ext_loaded_libs(info);
1167 if (libs && libs->nelts)
1169 const svn_version_ext_loaded_lib_t *lib;
1172 SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
1174 for (i = 0; i < libs->nelts; ++i)
1176 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
1178 SVN_ERR(svn_cmdline_printf(pool,
1180 lib->name, lib->version));
1182 SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name));
1187 return SVN_NO_ERROR;
1191 svn_opt_print_help4(apr_getopt_t *os,
1192 const char *pgm_name,
1193 svn_boolean_t print_version,
1194 svn_boolean_t quiet,
1195 svn_boolean_t verbose,
1196 const char *version_footer,
1198 const svn_opt_subcommand_desc2_t *cmd_table,
1199 const apr_getopt_option_t *option_table,
1200 const int *global_options,
1204 apr_array_header_t *targets = NULL;
1207 SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1209 if (os && targets->nelts) /* help on subcommand(s) requested */
1213 for (i = 0; i < targets->nelts; i++)
1215 svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *),
1216 cmd_table, option_table,
1217 global_options, pool);
1220 else if (print_version) /* just --version */
1222 SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
1223 svn_version_extended(verbose, pool),
1224 quiet, verbose, pool));
1226 else if (os && !targets->nelts) /* `-h', `--help', or `help' */
1227 svn_opt_print_generic_help2(header,
1233 else /* unknown option or cmd */
1234 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1235 _("Type '%s help' for usage.\n"), pgm_name));
1237 return SVN_NO_ERROR;