Support --with-revprop in svnmucc.
[svn.git] / subversion / libsvn_subr / opt.c
blobc9e3b3e7b33e59032a008930ecfbf9ea1517f60b
1 /*
2 * opt.c : option and argument parsing for Subversion command lines
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
21 #define APR_WANT_STRFUNC
22 #include <apr_want.h>
24 #include <stdio.h>
25 #include <assert.h>
26 #include <apr_pools.h>
27 #include <apr_general.h>
28 #include <apr_lib.h>
29 #include <apr_file_info.h>
31 #include "svn_cmdline.h"
32 #include "svn_version.h"
33 #include "svn_types.h"
34 #include "svn_opt.h"
35 #include "svn_error.h"
36 #include "svn_path.h"
37 #include "svn_utf.h"
38 #include "svn_time.h"
40 #include "svn_private_config.h"
43 /*** Code. ***/
45 const svn_opt_subcommand_desc2_t *
46 svn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table,
47 const char *cmd_name)
49 int i = 0;
51 if (cmd_name == NULL)
52 return NULL;
54 while (table[i].name) {
55 int j;
56 if (strcmp(cmd_name, table[i].name) == 0)
57 return table + i;
58 for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
59 if (strcmp(cmd_name, table[i].aliases[j]) == 0)
60 return table + i;
62 i++;
65 /* If we get here, there was no matching subcommand name or alias. */
66 return NULL;
70 const svn_opt_subcommand_desc_t *
71 svn_opt_get_canonical_subcommand(const svn_opt_subcommand_desc_t *table,
72 const char *cmd_name)
74 int i = 0;
76 if (cmd_name == NULL)
77 return NULL;
79 while (table[i].name) {
80 int j;
81 if (strcmp(cmd_name, table[i].name) == 0)
82 return table + i;
83 for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
84 if (strcmp(cmd_name, table[i].aliases[j]) == 0)
85 return table + i;
87 i++;
90 /* If we get here, there was no matching subcommand name or alias. */
91 return NULL;
95 const apr_getopt_option_t *
96 svn_opt_get_option_from_code2(int code,
97 const apr_getopt_option_t *option_table,
98 const svn_opt_subcommand_desc2_t *command,
99 apr_pool_t *pool)
101 apr_size_t i;
103 for (i = 0; option_table[i].optch; i++)
104 if (option_table[i].optch == code)
106 int j;
107 if (command)
108 for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) &&
109 command->desc_overrides[j].optch); j++)
110 if (command->desc_overrides[j].optch == code)
112 apr_getopt_option_t *tmpopt =
113 apr_palloc(pool, sizeof(*tmpopt));
114 *tmpopt = option_table[i];
115 tmpopt->description = command->desc_overrides[j].desc;
116 return tmpopt;
118 return &(option_table[i]);
121 return NULL;
125 const apr_getopt_option_t *
126 svn_opt_get_option_from_code(int code,
127 const apr_getopt_option_t *option_table)
129 apr_size_t i;
131 for (i = 0; option_table[i].optch; i++)
132 if (option_table[i].optch == code)
133 return &(option_table[i]);
135 return NULL;
139 svn_boolean_t
140 svn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command,
141 int option_code,
142 const int *global_options)
144 apr_size_t i;
146 for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
147 if (command->valid_options[i] == option_code)
148 return TRUE;
150 if (global_options)
151 for (i = 0; global_options[i]; i++)
152 if (global_options[i] == option_code)
153 return TRUE;
155 return FALSE;
158 svn_boolean_t
159 svn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command,
160 int option_code)
162 return svn_opt_subcommand_takes_option3(command,
163 option_code,
164 NULL);
168 svn_boolean_t
169 svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command,
170 int option_code)
172 apr_size_t i;
174 for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
175 if (command->valid_options[i] == option_code)
176 return TRUE;
178 return FALSE;
182 /* Print the canonical command name for CMD, and all its aliases, to
183 STREAM. If HELP is set, print CMD's help string too, in which case
184 obtain option usage from OPTIONS_TABLE. */
185 static svn_error_t *
186 print_command_info2(const svn_opt_subcommand_desc2_t *cmd,
187 const apr_getopt_option_t *options_table,
188 const int *global_options,
189 svn_boolean_t help,
190 apr_pool_t *pool,
191 FILE *stream)
193 svn_boolean_t first_time;
194 apr_size_t i;
196 /* Print the canonical command name. */
197 SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
199 /* Print the list of aliases. */
200 first_time = TRUE;
201 for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
203 if (cmd->aliases[i] == NULL)
204 break;
206 if (first_time) {
207 SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
208 first_time = FALSE;
210 else
211 SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
213 SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
216 if (! first_time)
217 SVN_ERR(svn_cmdline_fputs(")", stream, pool));
219 if (help)
221 const apr_getopt_option_t *option;
222 svn_boolean_t have_options = FALSE;
224 SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help)));
226 /* Loop over all valid option codes attached to the subcommand */
227 for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
229 if (cmd->valid_options[i])
231 if (have_options == FALSE)
233 SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
234 stream, pool));
235 have_options = TRUE;
238 /* convert each option code into an option */
239 option =
240 svn_opt_get_option_from_code2(cmd->valid_options[i],
241 options_table,
242 cmd, pool);
244 /* print the option's docstring */
245 if (option)
247 const char *optstr;
248 svn_opt_format_option(&optstr, option, TRUE, pool);
249 SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n",
250 optstr));
254 /* And global options too */
255 if (global_options && *global_options)
257 SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"),
258 stream, pool));
259 have_options = TRUE;
261 for (i = 0; global_options[i]; i++)
264 /* convert each option code into an option */
265 option =
266 svn_opt_get_option_from_code2(global_options[i],
267 options_table,
268 cmd, pool);
270 /* print the option's docstring */
271 if (option)
273 const char *optstr;
274 svn_opt_format_option(&optstr, option, TRUE, pool);
275 SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n",
276 optstr));
281 if (have_options)
282 SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
285 return SVN_NO_ERROR;
289 /* Same as print_command_info2(), but with deprecated struct revision. */
290 static svn_error_t *
291 print_command_info(const svn_opt_subcommand_desc_t *cmd,
292 const apr_getopt_option_t *options_table,
293 svn_boolean_t help,
294 apr_pool_t *pool,
295 FILE *stream)
297 svn_boolean_t first_time;
298 apr_size_t i;
300 /* Print the canonical command name. */
301 SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
303 /* Print the list of aliases. */
304 first_time = TRUE;
305 for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
307 if (cmd->aliases[i] == NULL)
308 break;
310 if (first_time) {
311 SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
312 first_time = FALSE;
314 else
315 SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
317 SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
320 if (! first_time)
321 SVN_ERR(svn_cmdline_fputs(")", stream, pool));
323 if (help)
325 const apr_getopt_option_t *option;
326 svn_boolean_t have_options = FALSE;
328 SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help)));
330 /* Loop over all valid option codes attached to the subcommand */
331 for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
333 if (cmd->valid_options[i])
335 if (have_options == FALSE)
337 SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
338 stream, pool));
339 have_options = TRUE;
342 /* convert each option code into an option */
343 option =
344 svn_opt_get_option_from_code(cmd->valid_options[i],
345 options_table);
347 /* print the option's docstring */
348 if (option)
350 const char *optstr;
351 svn_opt_format_option(&optstr, option, TRUE, pool);
352 SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n",
353 optstr));
358 if (have_options)
359 SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
362 return SVN_NO_ERROR;
366 void
367 svn_opt_print_generic_help2(const char *header,
368 const svn_opt_subcommand_desc2_t *cmd_table,
369 const apr_getopt_option_t *opt_table,
370 const char *footer,
371 apr_pool_t *pool, FILE *stream)
373 int i = 0;
374 svn_error_t *err;
376 if (header)
377 if ((err = svn_cmdline_fputs(header, stream, pool)))
378 goto print_error;
380 while (cmd_table[i].name)
382 if ((err = svn_cmdline_fputs(" ", stream, pool))
383 || (err = print_command_info2(cmd_table + i, opt_table,
384 NULL, FALSE,
385 pool, stream))
386 || (err = svn_cmdline_fputs("\n", stream, pool)))
387 goto print_error;
388 i++;
391 if ((err = svn_cmdline_fputs("\n", stream, pool)))
392 goto print_error;
394 if (footer)
395 if ((err = svn_cmdline_fputs(footer, stream, pool)))
396 goto print_error;
398 return;
400 print_error:
401 svn_handle_error2(err, stderr, FALSE, "svn: ");
402 svn_error_clear(err);
406 void
407 svn_opt_print_generic_help(const char *header,
408 const svn_opt_subcommand_desc_t *cmd_table,
409 const apr_getopt_option_t *opt_table,
410 const char *footer,
411 apr_pool_t *pool, FILE *stream)
413 int i = 0;
414 svn_error_t *err;
416 if (header)
417 if ((err = svn_cmdline_fputs(header, stream, pool)))
418 goto print_error;
420 while (cmd_table[i].name)
422 if ((err = svn_cmdline_fputs(" ", stream, pool))
423 || (err = print_command_info(cmd_table + i, opt_table, FALSE,
424 pool, stream))
425 || (err = svn_cmdline_fputs("\n", stream, pool)))
426 goto print_error;
427 i++;
430 if ((err = svn_cmdline_fputs("\n", stream, pool)))
431 goto print_error;
433 if (footer)
434 if ((err = svn_cmdline_fputs(footer, stream, pool)))
435 goto print_error;
437 return;
439 print_error:
440 svn_handle_error2(err, stderr, FALSE, "svn: ");
441 svn_error_clear(err);
445 void
446 svn_opt_format_option(const char **string,
447 const apr_getopt_option_t *opt,
448 svn_boolean_t doc,
449 apr_pool_t *pool)
451 char *opts;
453 if (opt == NULL)
455 *string = "?";
456 return;
459 /* We have a valid option which may or may not have a "short
460 name" (a single-character alias for the long option). */
461 if (opt->optch <= 255)
462 opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name);
463 else
464 opts = apr_psprintf(pool, "--%s", opt->name);
466 if (opt->has_arg)
467 opts = apr_pstrcat(pool, opts, _(" ARG"), NULL);
469 if (doc)
470 opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description));
472 *string = opts;
476 void
477 svn_opt_subcommand_help3(const char *subcommand,
478 const svn_opt_subcommand_desc2_t *table,
479 const apr_getopt_option_t *options_table,
480 const int *global_options,
481 apr_pool_t *pool)
483 const svn_opt_subcommand_desc2_t *cmd =
484 svn_opt_get_canonical_subcommand2(table, subcommand);
485 svn_error_t *err;
487 if (cmd)
488 err = print_command_info2(cmd, options_table, global_options,
489 TRUE, pool, stdout);
490 else
491 err = svn_cmdline_fprintf(stderr, pool,
492 _("\"%s\": unknown command.\n\n"), subcommand);
494 if (err) {
495 svn_handle_error2(err, stderr, FALSE, "svn: ");
496 svn_error_clear(err);
500 void
501 svn_opt_subcommand_help2(const char *subcommand,
502 const svn_opt_subcommand_desc2_t *table,
503 const apr_getopt_option_t *options_table,
504 apr_pool_t *pool)
506 svn_opt_subcommand_help3(subcommand, table, options_table,
507 NULL, pool);
511 void
512 svn_opt_subcommand_help(const char *subcommand,
513 const svn_opt_subcommand_desc_t *table,
514 const apr_getopt_option_t *options_table,
515 apr_pool_t *pool)
517 const svn_opt_subcommand_desc_t *cmd =
518 svn_opt_get_canonical_subcommand(table, subcommand);
519 svn_error_t *err;
521 if (cmd)
522 err = print_command_info(cmd, options_table, TRUE, pool, stdout);
523 else
524 err = svn_cmdline_fprintf(stderr, pool,
525 _("\"%s\": unknown command.\n\n"), subcommand);
527 if (err) {
528 svn_handle_error2(err, stderr, FALSE, "svn: ");
529 svn_error_clear(err);
535 /*** Parsing revision and date options. ***/
538 /** Parsing "X:Y"-style arguments. **/
540 /* If WORD matches one of the special revision descriptors,
541 * case-insensitively, set *REVISION accordingly:
543 * - For "head", set REVISION->kind to svn_opt_revision_head.
545 * - For "prev", set REVISION->kind to svn_opt_revision_previous.
547 * - For "base", set REVISION->kind to svn_opt_revision_base.
549 * - For "committed", set REVISION->kind to svn_opt_revision_committed.
551 * If match, return 0, else return -1 and don't touch REVISION.
553 static int
554 revision_from_word(svn_opt_revision_t *revision, const char *word)
556 if (svn_cstring_casecmp(word, "head") == 0)
558 revision->kind = svn_opt_revision_head;
560 else if (svn_cstring_casecmp(word, "prev") == 0)
562 revision->kind = svn_opt_revision_previous;
564 else if (svn_cstring_casecmp(word, "base") == 0)
566 revision->kind = svn_opt_revision_base;
568 else if (svn_cstring_casecmp(word, "committed") == 0)
570 revision->kind = svn_opt_revision_committed;
572 else
573 return -1;
575 return 0;
579 /* Parse one revision specification. Return pointer to character
580 after revision, or NULL if the revision is invalid. Modifies
581 str, so make sure to pass a copy of anything precious. Uses
582 POOL for temporary allocation. */
583 static char *parse_one_rev(svn_opt_revision_t *revision, char *str,
584 apr_pool_t *pool)
586 char *end, save;
588 if (*str == '{')
590 svn_boolean_t matched;
591 apr_time_t tm;
592 svn_error_t *err;
594 /* Brackets denote a date. */
595 str++;
596 end = strchr(str, '}');
597 if (!end)
598 return NULL;
599 *end = '\0';
600 err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
601 if (err)
603 svn_error_clear(err);
604 return NULL;
606 if (!matched)
607 return NULL;
608 revision->kind = svn_opt_revision_date;
609 revision->value.date = tm;
610 return end + 1;
612 else if (apr_isdigit(*str))
614 /* It's a number. */
615 end = str + 1;
616 while (apr_isdigit(*end))
617 end++;
618 save = *end;
619 *end = '\0';
620 revision->kind = svn_opt_revision_number;
621 revision->value.number = SVN_STR_TO_REV(str);
622 *end = save;
623 return end;
625 else if (apr_isalpha(*str))
627 end = str + 1;
628 while (apr_isalpha(*end))
629 end++;
630 save = *end;
631 *end = '\0';
632 if (revision_from_word(revision, str) != 0)
633 return NULL;
634 *end = save;
635 return end;
637 else
638 return NULL;
643 svn_opt_parse_revision(svn_opt_revision_t *start_revision,
644 svn_opt_revision_t *end_revision,
645 const char *arg,
646 apr_pool_t *pool)
648 char *left_rev, *right_rev, *end;
650 /* Operate on a copy of the argument. */
651 left_rev = apr_pstrdup(pool, arg);
653 right_rev = parse_one_rev(start_revision, left_rev, pool);
654 if (right_rev && *right_rev == ':')
656 right_rev++;
657 end = parse_one_rev(end_revision, right_rev, pool);
658 if (!end || *end != '\0')
659 return -1;
661 else if (!right_rev || *right_rev != '\0')
662 return -1;
664 return 0;
669 svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
670 const char *arg,
671 apr_pool_t *pool)
673 svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
675 range->start.kind = svn_opt_revision_unspecified;
676 range->end.kind = svn_opt_revision_unspecified;
678 if (svn_opt_parse_revision(&(range->start), &(range->end),
679 arg, pool) == -1)
680 return -1;
682 APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
683 return 0;
686 svn_error_t *
687 svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
688 svn_opt_revision_t *op_rev,
689 svn_boolean_t is_url,
690 svn_boolean_t notice_local_mods,
691 apr_pool_t *pool)
693 if (peg_rev->kind == svn_opt_revision_unspecified)
695 if (is_url)
697 peg_rev->kind = svn_opt_revision_head;
699 else
701 if (notice_local_mods)
702 peg_rev->kind = svn_opt_revision_working;
703 else
704 peg_rev->kind = svn_opt_revision_base;
708 if (op_rev->kind == svn_opt_revision_unspecified)
709 *op_rev = *peg_rev;
711 return SVN_NO_ERROR;
715 /*** Parsing arguments. ***/
716 #define DEFAULT_ARRAY_SIZE 5
719 /* Copy STR into POOL and push the copy onto ARRAY. */
720 static void
721 array_push_str(apr_array_header_t *array,
722 const char *str,
723 apr_pool_t *pool)
725 /* ### Not sure if this function is still necessary. It used to
726 convert str to svn_stringbuf_t * and push it, but now it just
727 dups str in pool and pushes the copy. So its only effect is
728 transfer str's lifetime to pool. Is that something callers are
729 depending on? */
731 APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
735 void
736 svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
737 apr_pool_t *pool)
739 if (targets->nelts == 0)
740 array_push_str(targets, "", pool); /* Ha! "", not ".", is the canonical */
741 assert(targets->nelts);
745 svn_error_t *
746 svn_opt_parse_num_args(apr_array_header_t **args_p,
747 apr_getopt_t *os,
748 int num_args,
749 apr_pool_t *pool)
751 int i;
752 apr_array_header_t *args
753 = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
755 /* loop for num_args and add each arg to the args array */
756 for (i = 0; i < num_args; i++)
758 if (os->ind >= os->argc)
760 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
762 array_push_str(args, os->argv[os->ind++], pool);
765 *args_p = args;
766 return SVN_NO_ERROR;
769 svn_error_t *
770 svn_opt_parse_all_args(apr_array_header_t **args_p,
771 apr_getopt_t *os,
772 apr_pool_t *pool)
774 apr_array_header_t *args
775 = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
777 if (os->ind > os->argc)
779 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
781 while (os->ind < os->argc)
783 array_push_str(args, os->argv[os->ind++], pool);
786 *args_p = args;
787 return SVN_NO_ERROR;
791 svn_error_t *
792 svn_opt_parse_path(svn_opt_revision_t *rev,
793 const char **truepath,
794 const char *path /* UTF-8! */,
795 apr_pool_t *pool)
797 int i;
799 /* scanning from right to left, just to be friendly to any
800 screwed-up filenames that might *actually* contain @-signs. :-) */
801 for (i = (strlen(path) - 1); i >= 0; i--)
803 /* If we hit a path separator, stop looking. */
804 /* This is OK only because our revision specifiers can't contain '/'. */
805 if (path[i] == '/')
806 break;
808 if (path[i] == '@')
810 int ret;
811 svn_opt_revision_t start_revision, end_revision;
813 end_revision.kind = svn_opt_revision_unspecified;
815 if (path[i + 1] == '\0') /* looking at empty peg revision */
817 ret = 0;
818 start_revision.kind = svn_opt_revision_unspecified;
820 else /* looking at non-empty peg revision */
822 const char *rev_str = path + i + 1;
824 /* URLs get treated differently from wc paths. */
825 if (svn_path_is_url(path))
827 /* URLs are URI-encoded, so we look for dates with
828 URI-encoded delimeters. */
829 int rev_len = strlen(rev_str);
830 if (rev_len > 6
831 && rev_str[0] == '%'
832 && rev_str[1] == '7'
833 && (rev_str[2] == 'B'
834 || rev_str[2] == 'b')
835 && rev_str[rev_len-3] == '%'
836 && rev_str[rev_len-2] == '7'
837 && (rev_str[rev_len-1] == 'D'
838 || rev_str[rev_len-1] == 'd'))
840 rev_str = svn_path_uri_decode(rev_str, pool);
843 ret = svn_opt_parse_revision(&start_revision,
844 &end_revision,
845 rev_str, pool);
848 if (ret || end_revision.kind != svn_opt_revision_unspecified)
849 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
850 _("Syntax error parsing revision '%s'"),
851 path + i + 1);
853 *truepath = apr_pstrmemdup(pool, path, i);
854 rev->kind = start_revision.kind;
855 rev->value = start_revision.value;
857 return SVN_NO_ERROR;
861 /* Didn't find an @-sign. */
862 *truepath = path;
863 rev->kind = svn_opt_revision_unspecified;
865 return SVN_NO_ERROR;
869 svn_error_t *
870 svn_opt_args_to_target_array2(apr_array_header_t **targets_p,
871 apr_getopt_t *os,
872 apr_array_header_t *known_targets,
873 apr_pool_t *pool)
875 svn_error_t *err = svn_opt_args_to_target_array3(targets_p, os,
876 known_targets, pool);
878 if (err && err->apr_err == SVN_ERR_RESERVED_FILENAME_SPECIFIED)
880 svn_error_clear(err);
881 return SVN_NO_ERROR;
884 return err;
888 svn_error_t *
889 svn_opt_args_to_target_array3(apr_array_header_t **targets_p,
890 apr_getopt_t *os,
891 apr_array_header_t *known_targets,
892 apr_pool_t *pool)
894 int i;
895 svn_error_t *err = SVN_NO_ERROR;
896 apr_array_header_t *input_targets =
897 apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
898 apr_array_header_t *output_targets =
899 apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
901 /* Step 1: create a master array of targets that are in UTF-8
902 encoding, and come from concatenating the targets left by apr_getopt,
903 plus any extra targets (e.g., from the --targets switch.) */
905 for (; os->ind < os->argc; os->ind++)
907 /* The apr_getopt targets are still in native encoding. */
908 const char *raw_target = os->argv[os->ind];
909 SVN_ERR(svn_utf_cstring_to_utf8
910 ((const char **) apr_array_push(input_targets),
911 raw_target, pool));
914 if (known_targets)
916 for (i = 0; i < known_targets->nelts; i++)
918 /* The --targets array have already been converted to UTF-8,
919 because we needed to split up the list with svn_cstring_split. */
920 const char *utf8_target = APR_ARRAY_IDX(known_targets,
921 i, const char *);
922 APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
926 /* Step 2: process each target. */
928 for (i = 0; i < input_targets->nelts; i++)
930 const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
931 const char *peg_start = NULL; /* pointer to the peg revision, if any */
932 const char *target; /* after all processing is finished */
933 int j;
935 /* Remove a peg revision, if any, in the target so that it can
936 be properly canonicalized, otherwise the canonicalization
937 does not treat a ".@BASE" as a "." with a BASE peg revision,
938 and it is not canonicalized to "@BASE". If any peg revision
939 exists, it is appended to the final canonicalized path or
940 URL. Do not use svn_opt_parse_path() because the resulting
941 peg revision is a structure that would have to be converted
942 back into a string. Converting from a string date to the
943 apr_time_t field in the svn_opt_revision_value_t and back to
944 a string would not necessarily preserve the exact bytes of
945 the input date, so its easier just to keep it in string
946 form. */
947 for (j = (strlen(utf8_target) - 1); j >= 0; --j)
949 /* If we hit a path separator, stop looking. This is OK
950 only because our revision specifiers can't contain
951 '/'. */
952 if (utf8_target[j] == '/')
953 break;
954 if (utf8_target[j] == '@')
956 peg_start = utf8_target + j;
957 break;
960 if (peg_start)
961 utf8_target = apr_pstrmemdup(pool,
962 utf8_target,
963 peg_start - utf8_target);
965 /* URLs and wc-paths get treated differently. */
966 if (svn_path_is_url(utf8_target))
968 /* No need to canonicalize a URL's case or path separators. */
970 /* Convert to URI. */
971 target = svn_path_uri_from_iri(utf8_target, pool);
972 /* Auto-escape some ASCII characters. */
973 target = svn_path_uri_autoescape(target, pool);
975 /* The above doesn't guarantee a valid URI. */
976 if (! svn_path_is_uri_safe(target))
977 return svn_error_createf(SVN_ERR_BAD_URL, 0,
978 _("URL '%s' is not properly URI-encoded"),
979 utf8_target);
981 /* Verify that no backpaths are present in the URL. */
982 if (svn_path_is_backpath_present(target))
983 return svn_error_createf(SVN_ERR_BAD_URL, 0,
984 _("URL '%s' contains a '..' element"),
985 utf8_target);
987 /* strip any trailing '/' */
988 target = svn_path_canonicalize(target, pool);
990 else /* not a url, so treat as a path */
992 const char *apr_target;
993 const char *base_name;
994 char *truenamed_target; /* APR-encoded */
995 apr_status_t apr_err;
997 /* canonicalize case, and change all separators to '/'. */
998 SVN_ERR(svn_path_cstring_from_utf8(&apr_target, utf8_target,
999 pool));
1000 apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
1001 APR_FILEPATH_TRUENAME, pool);
1003 if (!apr_err)
1004 /* We have a canonicalized APR-encoded target now. */
1005 apr_target = truenamed_target;
1006 else if (APR_STATUS_IS_ENOENT(apr_err))
1007 /* It's okay for the file to not exist, that just means we
1008 have to accept the case given to the client. We'll use
1009 the original APR-encoded target. */
1011 else
1012 return svn_error_createf(apr_err, NULL,
1013 _("Error resolving case of '%s'"),
1014 svn_path_local_style(utf8_target,
1015 pool));
1017 /* convert back to UTF-8. */
1018 SVN_ERR(svn_path_cstring_to_utf8(&target, apr_target, pool));
1019 target = svn_path_canonicalize(target, pool);
1021 /* If the target has the same name as a Subversion
1022 working copy administrative dir, skip it. */
1023 base_name = svn_path_basename(target, pool);
1024 /* FIXME:
1025 The canonical list of administrative directory names is
1026 maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
1027 That list can't be used here, because that use would
1028 create a circular dependency between libsvn_wc and
1029 libsvn_subr. Make sure changes to the lists are always
1030 synchronized! */
1031 if (0 == strcmp(base_name, ".svn")
1032 || 0 == strcmp(base_name, "_svn"))
1034 err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
1035 err, _("'%s' ends in a reserved name"),
1036 target);
1037 continue;
1041 /* Append the peg revision back to the canonicalized target if
1042 there was a peg revision. */
1043 if (peg_start)
1044 target = apr_pstrcat(pool, target, peg_start, NULL);
1046 APR_ARRAY_PUSH(output_targets, const char *) = target;
1050 /* kff todo: need to remove redundancies from targets before
1051 passing it to the cmd_func. */
1053 *targets_p = output_targets;
1055 return err;
1059 svn_error_t *
1060 svn_opt_args_to_target_array(apr_array_header_t **targets_p,
1061 apr_getopt_t *os,
1062 apr_array_header_t *known_targets,
1063 svn_opt_revision_t *start_revision,
1064 svn_opt_revision_t *end_revision,
1065 svn_boolean_t extract_revisions,
1066 apr_pool_t *pool)
1068 apr_array_header_t *output_targets;
1070 SVN_ERR(svn_opt_args_to_target_array2(&output_targets, os,
1071 known_targets, pool));
1073 if (extract_revisions)
1075 svn_opt_revision_t temprev;
1076 const char *path;
1078 if (output_targets->nelts > 0)
1080 path = APR_ARRAY_IDX(output_targets, 0, const char *);
1081 SVN_ERR(svn_opt_parse_path(&temprev, &path, path, pool));
1082 if (temprev.kind != svn_opt_revision_unspecified)
1084 APR_ARRAY_IDX(output_targets, 0, const char *) = path;
1085 start_revision->kind = temprev.kind;
1086 start_revision->value = temprev.value;
1089 if (output_targets->nelts > 1)
1091 path = APR_ARRAY_IDX(output_targets, 1, const char *);
1092 SVN_ERR(svn_opt_parse_path(&temprev, &path, path, pool));
1093 if (temprev.kind != svn_opt_revision_unspecified)
1095 APR_ARRAY_IDX(output_targets, 1, const char *) = path;
1096 end_revision->kind = temprev.kind;
1097 end_revision->value = temprev.value;
1102 *targets_p = output_targets;
1103 return SVN_NO_ERROR;
1107 svn_error_t *
1108 svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
1109 apr_pool_t *pool)
1111 const char *sep, *propname;
1112 svn_string_t *propval;
1114 if (! *revprop_spec)
1115 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1116 _("Revision property pair is empty"));
1118 if (! *revprop_table_p)
1119 *revprop_table_p = apr_hash_make(pool);
1121 sep = strchr(revprop_spec, '=');
1122 if (sep)
1124 propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec);
1125 SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool));
1126 propval = svn_string_create(sep + 1, pool);
1128 else
1130 SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
1131 propval = svn_string_create("", pool);
1134 if (!svn_prop_name_is_valid(propname))
1135 return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
1136 _("'%s' is not a valid Subversion property name"),
1137 propname);
1139 apr_hash_set(*revprop_table_p, propname, APR_HASH_KEY_STRING, propval);
1141 return SVN_NO_ERROR;
1145 /* Print version info for PGM_NAME. If QUIET is true, print in
1146 * brief. Else if QUIET is not true, print the version more
1147 * verbosely, and if FOOTER is non-null, print it following the
1148 * version information.
1150 * Use POOL for temporary allocations.
1152 static svn_error_t *
1153 print_version_info(const char *pgm_name,
1154 const char *footer,
1155 svn_boolean_t quiet,
1156 apr_pool_t *pool)
1158 if (quiet)
1160 SVN_ERR(svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER));
1161 return SVN_NO_ERROR;
1164 SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
1165 " compiled %s, %s\n\n"), pgm_name,
1166 SVN_VERSION, __DATE__, __TIME__));
1167 SVN_ERR(svn_cmdline_fputs(_("Copyright (C) 2000-2008 CollabNet.\n"
1168 "Subversion is open source software, see"
1169 " http://subversion.tigris.org/\n"
1170 "This product includes software developed by "
1171 "CollabNet (http://www.Collab.Net/).\n\n"),
1172 stdout, pool));
1174 if (footer)
1176 SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
1179 return SVN_NO_ERROR;
1183 svn_error_t *
1184 svn_opt_print_help3(apr_getopt_t *os,
1185 const char *pgm_name,
1186 svn_boolean_t print_version,
1187 svn_boolean_t quiet,
1188 const char *version_footer,
1189 const char *header,
1190 const svn_opt_subcommand_desc2_t *cmd_table,
1191 const apr_getopt_option_t *option_table,
1192 const int *global_options,
1193 const char *footer,
1194 apr_pool_t *pool)
1196 apr_array_header_t *targets = NULL;
1197 int i;
1199 if (os)
1200 SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1202 if (os && targets->nelts) /* help on subcommand(s) requested */
1203 for (i = 0; i < targets->nelts; i++)
1205 svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *),
1206 cmd_table, option_table,
1207 global_options, pool);
1209 else if (print_version) /* just --version */
1210 SVN_ERR(print_version_info(pgm_name, version_footer, quiet, pool));
1211 else if (os && !targets->nelts) /* `-h', `--help', or `help' */
1212 svn_opt_print_generic_help2(header,
1213 cmd_table,
1214 option_table,
1215 footer,
1216 pool,
1217 stdout);
1218 else /* unknown option or cmd */
1219 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1220 _("Type '%s help' for usage.\n"), pgm_name));
1222 return SVN_NO_ERROR;
1226 svn_error_t *
1227 svn_opt_print_help2(apr_getopt_t *os,
1228 const char *pgm_name,
1229 svn_boolean_t print_version,
1230 svn_boolean_t quiet,
1231 const char *version_footer,
1232 const char *header,
1233 const svn_opt_subcommand_desc2_t *cmd_table,
1234 const apr_getopt_option_t *option_table,
1235 const char *footer,
1236 apr_pool_t *pool)
1238 return svn_opt_print_help3(os,
1239 pgm_name,
1240 print_version,
1241 quiet,
1242 version_footer,
1243 header,
1244 cmd_table,
1245 option_table,
1246 NULL,
1247 footer,
1248 pool);
1252 svn_error_t *
1253 svn_opt_print_help(apr_getopt_t *os,
1254 const char *pgm_name,
1255 svn_boolean_t print_version,
1256 svn_boolean_t quiet,
1257 const char *version_footer,
1258 const char *header,
1259 const svn_opt_subcommand_desc_t *cmd_table,
1260 const apr_getopt_option_t *option_table,
1261 const char *footer,
1262 apr_pool_t *pool)
1264 apr_array_header_t *targets = NULL;
1265 int i;
1267 if (os)
1268 SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1270 if (os && targets->nelts) /* help on subcommand(s) requested */
1271 for (i = 0; i < targets->nelts; i++)
1273 svn_opt_subcommand_help(APR_ARRAY_IDX(targets, i, const char *),
1274 cmd_table, option_table, pool);
1276 else if (print_version) /* just --version */
1277 SVN_ERR(print_version_info(pgm_name, version_footer, quiet, pool));
1278 else if (os && !targets->nelts) /* `-h', `--help', or `help' */
1279 svn_opt_print_generic_help(header,
1280 cmd_table,
1281 option_table,
1282 footer,
1283 pool,
1284 stdout);
1285 else /* unknown option or cmd */
1286 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1287 _("Type '%s help' for usage.\n"), pgm_name));
1289 return SVN_NO_ERROR;