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"), SVN_VA_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 /* Issue #3014: Don't print anything on broken pipes. */
421 if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
422 svn_handle_error2(err, stderr, FALSE, "svn: ");
423 svn_error_clear(err);
429 /*** Parsing revision and date options. ***/
432 /** Parsing "X:Y"-style arguments. **/
434 /* If WORD matches one of the special revision descriptors,
435 * case-insensitively, set *REVISION accordingly:
437 * - For "head", set REVISION->kind to svn_opt_revision_head.
439 * - For "prev", set REVISION->kind to svn_opt_revision_previous.
441 * - For "base", set REVISION->kind to svn_opt_revision_base.
443 * - For "committed", set REVISION->kind to svn_opt_revision_committed.
445 * If match, return 0, else return -1 and don't touch REVISION.
448 revision_from_word(svn_opt_revision_t *revision, const char *word)
450 if (svn_cstring_casecmp(word, "head") == 0)
452 revision->kind = svn_opt_revision_head;
454 else if (svn_cstring_casecmp(word, "prev") == 0)
456 revision->kind = svn_opt_revision_previous;
458 else if (svn_cstring_casecmp(word, "base") == 0)
460 revision->kind = svn_opt_revision_base;
462 else if (svn_cstring_casecmp(word, "committed") == 0)
464 revision->kind = svn_opt_revision_committed;
473 /* Parse one revision specification. Return pointer to character
474 after revision, or NULL if the revision is invalid. Modifies
475 str, so make sure to pass a copy of anything precious. Uses
476 POOL for temporary allocation. */
477 static char *parse_one_rev(svn_opt_revision_t *revision, char *str,
482 /* Allow any number of 'r's to prefix a revision number, because
483 that way if a script pastes svn output into another svn command
484 (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work,
485 even when compounded.
487 As it happens, none of our special revision words begins with
488 "r". If any ever do, then this code will have to get smarter.
490 Incidentally, this allows "r{DATE}". We could avoid that with
491 some trivial code rearrangement, but it's not clear what would
492 be gained by doing so. */
498 svn_boolean_t matched;
502 /* Brackets denote a date. */
504 end = strchr(str, '}');
508 err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
511 svn_error_clear(err);
516 revision->kind = svn_opt_revision_date;
517 revision->value.date = tm;
520 else if (svn_ctype_isdigit(*str))
524 while (svn_ctype_isdigit(*end))
528 revision->kind = svn_opt_revision_number;
529 revision->value.number = SVN_STR_TO_REV(str);
533 else if (svn_ctype_isalpha(*str))
536 while (svn_ctype_isalpha(*end))
540 if (revision_from_word(revision, str) != 0)
551 svn_opt_parse_revision(svn_opt_revision_t *start_revision,
552 svn_opt_revision_t *end_revision,
556 char *left_rev, *right_rev, *end;
558 /* Operate on a copy of the argument. */
559 left_rev = apr_pstrdup(pool, arg);
561 right_rev = parse_one_rev(start_revision, left_rev, pool);
562 if (right_rev && *right_rev == ':')
565 end = parse_one_rev(end_revision, right_rev, pool);
566 if (!end || *end != '\0')
569 else if (!right_rev || *right_rev != '\0')
577 svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
581 svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
583 range->start.kind = svn_opt_revision_unspecified;
584 range->end.kind = svn_opt_revision_unspecified;
586 if (svn_opt_parse_revision(&(range->start), &(range->end),
590 APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
595 svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
596 svn_opt_revision_t *op_rev,
597 svn_boolean_t is_url,
598 svn_boolean_t notice_local_mods,
601 if (peg_rev->kind == svn_opt_revision_unspecified)
605 peg_rev->kind = svn_opt_revision_head;
609 if (notice_local_mods)
610 peg_rev->kind = svn_opt_revision_working;
612 peg_rev->kind = svn_opt_revision_base;
616 if (op_rev->kind == svn_opt_revision_unspecified)
623 svn_opt__revision_to_string(const svn_opt_revision_t *revision,
624 apr_pool_t *result_pool)
626 switch (revision->kind)
628 case svn_opt_revision_unspecified:
629 return "unspecified";
630 case svn_opt_revision_number:
631 return apr_psprintf(result_pool, "%ld", revision->value.number);
632 case svn_opt_revision_date:
633 /* ### svn_time_to_human_cstring()? */
634 return svn_time_to_cstring(revision->value.date, result_pool);
635 case svn_opt_revision_committed:
637 case svn_opt_revision_previous:
639 case svn_opt_revision_base:
641 case svn_opt_revision_working:
643 case svn_opt_revision_head:
650 svn_opt_revision_range_t *
651 svn_opt__revision_range_create(const svn_opt_revision_t *start_revision,
652 const svn_opt_revision_t *end_revision,
653 apr_pool_t *result_pool)
655 svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
657 range->start = *start_revision;
658 range->end = *end_revision;
662 svn_opt_revision_range_t *
663 svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,
664 svn_revnum_t end_revnum,
665 apr_pool_t *result_pool)
667 svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
669 range->start.kind = svn_opt_revision_number;
670 range->start.value.number = start_revnum;
671 range->end.kind = svn_opt_revision_number;
672 range->end.value.number = end_revnum;
678 /*** Parsing arguments. ***/
679 #define DEFAULT_ARRAY_SIZE 5
682 /* Copy STR into POOL and push the copy onto ARRAY. */
684 array_push_str(apr_array_header_t *array,
688 /* ### Not sure if this function is still necessary. It used to
689 convert str to svn_stringbuf_t * and push it, but now it just
690 dups str in pool and pushes the copy. So its only effect is
691 transfer str's lifetime to pool. Is that something callers are
694 APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
699 svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
702 if (targets->nelts == 0)
703 APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */
704 assert(targets->nelts);
709 svn_opt_parse_num_args(apr_array_header_t **args_p,
715 apr_array_header_t *args
716 = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
718 /* loop for num_args and add each arg to the args array */
719 for (i = 0; i < num_args; i++)
721 if (os->ind >= os->argc)
723 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
725 array_push_str(args, os->argv[os->ind++], pool);
733 svn_opt_parse_all_args(apr_array_header_t **args_p,
737 apr_array_header_t *args
738 = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
740 if (os->ind > os->argc)
742 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
744 while (os->ind < os->argc)
746 array_push_str(args, os->argv[os->ind++], pool);
755 svn_opt_parse_path(svn_opt_revision_t *rev,
756 const char **truepath,
757 const char *path /* UTF-8! */,
762 SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool));
764 /* Parse the peg revision, if one was found */
768 svn_opt_revision_t start_revision, end_revision;
770 end_revision.kind = svn_opt_revision_unspecified;
772 if (peg_rev[1] == '\0') /* looking at empty peg revision */
775 start_revision.kind = svn_opt_revision_unspecified;
776 start_revision.value.number = 0;
778 else /* looking at non-empty peg revision */
780 const char *rev_str = &peg_rev[1];
782 /* URLs get treated differently from wc paths. */
783 if (svn_path_is_url(path))
785 /* URLs are URI-encoded, so we look for dates with
786 URI-encoded delimiters. */
787 size_t rev_len = strlen(rev_str);
791 && (rev_str[2] == 'B'
792 || rev_str[2] == 'b')
793 && rev_str[rev_len-3] == '%'
794 && rev_str[rev_len-2] == '7'
795 && (rev_str[rev_len-1] == 'D'
796 || rev_str[rev_len-1] == 'd'))
798 rev_str = svn_path_uri_decode(rev_str, pool);
801 ret = svn_opt_parse_revision(&start_revision,
806 if (ret || end_revision.kind != svn_opt_revision_unspecified)
808 /* If an svn+ssh URL was used and it contains only one @,
809 * provide an error message that presents a possible solution
810 * to the parsing error (issue #2349). */
811 if (strncmp(path, "svn+ssh://", 10) == 0)
815 at = strchr(path, '@');
816 if (at && strrchr(path, '@') == at)
817 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
818 _("Syntax error parsing peg revision "
819 "'%s'; did you mean '%s@'?"),
823 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
824 _("Syntax error parsing peg revision '%s'"),
827 rev->kind = start_revision.kind;
828 rev->value = start_revision.value;
832 /* Didn't find a peg revision. */
833 rev->kind = svn_opt_revision_unspecified;
840 /* Note: This is substantially copied into svn_client_args_to_target_array() in
841 * order to move to libsvn_client while maintaining backward compatibility. */
843 svn_opt__args_to_target_array(apr_array_header_t **targets_p,
845 const apr_array_header_t *known_targets,
849 svn_error_t *err = SVN_NO_ERROR;
850 apr_array_header_t *input_targets =
851 apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
852 apr_array_header_t *output_targets =
853 apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
855 /* Step 1: create a master array of targets that are in UTF-8
856 encoding, and come from concatenating the targets left by apr_getopt,
857 plus any extra targets (e.g., from the --targets switch.) */
859 for (; os->ind < os->argc; os->ind++)
861 /* The apr_getopt targets are still in native encoding. */
862 const char *raw_target = os->argv[os->ind];
863 SVN_ERR(svn_utf_cstring_to_utf8
864 ((const char **) apr_array_push(input_targets),
870 for (i = 0; i < known_targets->nelts; i++)
872 /* The --targets array have already been converted to UTF-8,
873 because we needed to split up the list with svn_cstring_split. */
874 const char *utf8_target = APR_ARRAY_IDX(known_targets,
876 APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
880 /* Step 2: process each target. */
882 for (i = 0; i < input_targets->nelts; i++)
884 const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
885 const char *true_target;
886 const char *target; /* after all processing is finished */
890 * This is needed so that the target can be properly canonicalized,
891 * otherwise the canonicalization does not treat a ".@BASE" as a "."
892 * with a BASE peg revision, and it is not canonicalized to "@BASE".
893 * If any peg revision exists, it is appended to the final
894 * canonicalized path or URL. Do not use svn_opt_parse_path()
895 * because the resulting peg revision is a structure that would have
896 * to be converted back into a string. Converting from a string date
897 * to the apr_time_t field in the svn_opt_revision_value_t and back to
898 * a string would not necessarily preserve the exact bytes of the
899 * input date, so its easier just to keep it in string form.
901 SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
904 /* URLs and wc-paths get treated differently. */
905 if (svn_path_is_url(true_target))
907 SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target,
910 else /* not a url, so treat as a path */
912 const char *base_name;
914 SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target,
917 /* If the target has the same name as a Subversion
918 working copy administrative dir, skip it. */
919 base_name = svn_dirent_basename(true_target, pool);
922 The canonical list of administrative directory names is
923 maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
924 That list can't be used here, because that use would
925 create a circular dependency between libsvn_wc and
926 libsvn_subr. Make sure changes to the lists are always
928 if (0 == strcmp(base_name, ".svn")
929 || 0 == strcmp(base_name, "_svn"))
931 err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
932 err, _("'%s' ends in a reserved name"),
938 target = apr_pstrcat(pool, true_target, peg_rev, SVN_VA_NULL);
940 APR_ARRAY_PUSH(output_targets, const char *) = target;
944 /* kff todo: need to remove redundancies from targets before
945 passing it to the cmd_func. */
947 *targets_p = output_targets;
953 svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
956 const char *sep, *propname;
957 svn_string_t *propval;
960 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
961 _("Revision property pair is empty"));
963 if (! *revprop_table_p)
964 *revprop_table_p = apr_hash_make(pool);
966 sep = strchr(revprop_spec, '=');
969 propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec);
970 SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool));
971 propval = svn_string_create(sep + 1, pool);
975 SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
976 propval = svn_string_create_empty(pool);
979 if (!svn_prop_name_is_valid(propname))
980 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
981 _("'%s' is not a valid Subversion property name"),
984 svn_hash_sets(*revprop_table_p, propname, propval);
990 svn_opt__split_arg_at_peg_revision(const char **true_target,
991 const char **peg_revision,
992 const char *utf8_target,
995 const char *peg_start = NULL; /* pointer to the peg revision, if any */
998 for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target;
1001 /* If we hit a path separator, stop looking. This is OK
1002 only because our revision specifiers can't contain '/'. */
1015 *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target);
1017 *peg_revision = apr_pstrdup(pool, peg_start);
1021 *true_target = utf8_target;
1026 return SVN_NO_ERROR;
1030 svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in,
1035 /* Convert to URI. */
1036 target = svn_path_uri_from_iri(url_in, pool);
1037 /* Auto-escape some ASCII characters. */
1038 target = svn_path_uri_autoescape(target, pool);
1040 #if '/' != SVN_PATH_LOCAL_SEPARATOR
1041 /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */
1042 if (strchr(target, SVN_PATH_LOCAL_SEPARATOR))
1044 char *p = apr_pstrdup(pool, target);
1047 /* Convert all local-style separators to the canonical ones. */
1048 for (; *p != '\0'; ++p)
1049 if (*p == SVN_PATH_LOCAL_SEPARATOR)
1054 /* Verify that no backpaths are present in the URL. */
1055 if (svn_path_is_backpath_present(target))
1056 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1057 _("URL '%s' contains a '..' element"),
1060 /* Strip any trailing '/' and collapse other redundant elements. */
1061 target = svn_uri_canonicalize(target, pool);
1064 return SVN_NO_ERROR;
1068 svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in,
1071 const char *apr_target;
1072 char *truenamed_target; /* APR-encoded */
1073 apr_status_t apr_err;
1075 /* canonicalize case, and change all separators to '/'. */
1076 SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool));
1077 apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
1078 APR_FILEPATH_TRUENAME, pool);
1081 /* We have a canonicalized APR-encoded target now. */
1082 apr_target = truenamed_target;
1083 else if (APR_STATUS_IS_ENOENT(apr_err))
1084 /* It's okay for the file to not exist, that just means we
1085 have to accept the case given to the client. We'll use
1086 the original APR-encoded target. */
1089 return svn_error_createf(apr_err, NULL,
1090 _("Error resolving case of '%s'"),
1091 svn_dirent_local_style(path_in, pool));
1093 /* convert back to UTF-8. */
1094 SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool));
1095 *path_out = svn_dirent_canonicalize(*path_out, pool);
1097 return SVN_NO_ERROR;
1102 svn_opt__print_version_info(const char *pgm_name,
1104 const svn_version_extended_t *info,
1105 svn_boolean_t quiet,
1106 svn_boolean_t verbose,
1110 return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER);
1112 SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
1113 " compiled on %s\n\n"),
1114 pgm_name, SVN_VERSION,
1115 svn_version_ext_build_host(info)));
1116 SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
1120 SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
1125 const apr_array_header_t *libs;
1127 SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
1128 SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
1129 svn_version_ext_runtime_host(info)));
1130 if (svn_version_ext_runtime_osname(info))
1132 SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"),
1133 svn_version_ext_runtime_osname(info)));
1136 libs = svn_version_ext_linked_libs(info);
1137 if (libs && libs->nelts)
1139 const svn_version_ext_linked_lib_t *lib;
1142 SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
1144 for (i = 0; i < libs->nelts; ++i)
1146 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
1147 if (lib->runtime_version)
1148 SVN_ERR(svn_cmdline_printf(pool,
1149 " - %s %s (compiled with %s)\n",
1151 lib->runtime_version,
1152 lib->compiled_version));
1154 SVN_ERR(svn_cmdline_printf(pool,
1155 " - %s %s (static)\n",
1157 lib->compiled_version));
1161 libs = svn_version_ext_loaded_libs(info);
1162 if (libs && libs->nelts)
1164 const svn_version_ext_loaded_lib_t *lib;
1167 SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
1169 for (i = 0; i < libs->nelts; ++i)
1171 lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
1173 SVN_ERR(svn_cmdline_printf(pool,
1175 lib->name, lib->version));
1177 SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name));
1182 return SVN_NO_ERROR;
1186 svn_opt_print_help4(apr_getopt_t *os,
1187 const char *pgm_name,
1188 svn_boolean_t print_version,
1189 svn_boolean_t quiet,
1190 svn_boolean_t verbose,
1191 const char *version_footer,
1193 const svn_opt_subcommand_desc2_t *cmd_table,
1194 const apr_getopt_option_t *option_table,
1195 const int *global_options,
1199 apr_array_header_t *targets = NULL;
1202 SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1204 if (os && targets->nelts) /* help on subcommand(s) requested */
1208 for (i = 0; i < targets->nelts; i++)
1210 svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *),
1211 cmd_table, option_table,
1212 global_options, pool);
1215 else if (print_version) /* just --version */
1217 SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
1218 svn_version_extended(verbose, pool),
1219 quiet, verbose, pool));
1221 else if (os && !targets->nelts) /* `-h', `--help', or `help' */
1222 svn_opt_print_generic_help2(header,
1228 else /* unknown option or cmd */
1229 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1230 _("Type '%s help' for usage.\n"), pgm_name));
1232 return SVN_NO_ERROR;