]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_subr/opt.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_subr / opt.c
1 /*
2  * opt.c :  option and argument parsing for Subversion command lines
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24
25 \f
26 #define APR_WANT_STRFUNC
27 #include <apr_want.h>
28
29 #include <stdio.h>
30 #include <string.h>
31 #include <assert.h>
32 #include <apr_pools.h>
33 #include <apr_general.h>
34 #include <apr_lib.h>
35 #include <apr_file_info.h>
36
37 #include "svn_hash.h"
38 #include "svn_cmdline.h"
39 #include "svn_version.h"
40 #include "svn_types.h"
41 #include "svn_opt.h"
42 #include "svn_error.h"
43 #include "svn_dirent_uri.h"
44 #include "svn_path.h"
45 #include "svn_utf.h"
46 #include "svn_time.h"
47 #include "svn_props.h"
48 #include "svn_ctype.h"
49
50 #include "private/svn_opt_private.h"
51
52 #include "opt.h"
53 #include "svn_private_config.h"
54
55 \f
56 /*** Code. ***/
57
58 const svn_opt_subcommand_desc2_t *
59 svn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table,
60                                   const char *cmd_name)
61 {
62   int i = 0;
63
64   if (cmd_name == NULL)
65     return NULL;
66
67   while (table[i].name) {
68     int j;
69     if (strcmp(cmd_name, table[i].name) == 0)
70       return table + i;
71     for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
72       if (strcmp(cmd_name, table[i].aliases[j]) == 0)
73         return table + i;
74
75     i++;
76   }
77
78   /* If we get here, there was no matching subcommand name or alias. */
79   return NULL;
80 }
81
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,
86                               apr_pool_t *pool)
87 {
88   apr_size_t i;
89
90   for (i = 0; option_table[i].optch; i++)
91     if (option_table[i].optch == code)
92       {
93         if (command)
94           {
95             int j;
96
97             for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) &&
98                          command->desc_overrides[j].optch); j++)
99               if (command->desc_overrides[j].optch == code)
100                 {
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;
105                   return tmpopt;
106                 }
107           }
108         return &(option_table[i]);
109       }
110
111   return NULL;
112 }
113
114
115 const apr_getopt_option_t *
116 svn_opt_get_option_from_code(int code,
117                              const apr_getopt_option_t *option_table)
118 {
119   apr_size_t i;
120
121   for (i = 0; option_table[i].optch; i++)
122     if (option_table[i].optch == code)
123       return &(option_table[i]);
124
125   return NULL;
126 }
127
128
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,
134                      int code,
135                      const apr_getopt_option_t *option_table,
136                      const svn_opt_subcommand_desc2_t *command,
137                      apr_pool_t *pool)
138 {
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);
142
143   /* Find a long alias in the table, if there is one. */
144   *long_alias = NULL;
145   for (i = option_table; i->optch; i++)
146     {
147       if (i->optch == code && i->name != opt->name)
148         {
149           *long_alias = i->name;
150           break;
151         }
152     }
153
154   return opt;
155 }
156
157
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.
163  */
164 static void
165 format_option(const char **string,
166               const apr_getopt_option_t *opt,
167               const char *long_alias,
168               svn_boolean_t doc,
169               apr_pool_t *pool)
170 {
171   char *opts;
172
173   if (opt == NULL)
174     {
175       *string = "?";
176       return;
177     }
178
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);
183   else if (long_alias)
184     opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias);
185   else
186     opts = apr_psprintf(pool, "--%s", opt->name);
187
188   if (opt->has_arg)
189     opts = apr_pstrcat(pool, opts, _(" ARG"), (char *)NULL);
190
191   if (doc)
192     opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description));
193
194   *string = opts;
195 }
196
197 void
198 svn_opt_format_option(const char **string,
199                       const apr_getopt_option_t *opt,
200                       svn_boolean_t doc,
201                       apr_pool_t *pool)
202 {
203   format_option(string, opt, NULL, doc, pool);
204 }
205
206
207 svn_boolean_t
208 svn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command,
209                                  int option_code,
210                                  const int *global_options)
211 {
212   apr_size_t i;
213
214   for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
215     if (command->valid_options[i] == option_code)
216       return TRUE;
217
218   if (global_options)
219     for (i = 0; global_options[i]; i++)
220       if (global_options[i] == option_code)
221         return TRUE;
222
223   return FALSE;
224 }
225
226 svn_boolean_t
227 svn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command,
228                                  int option_code)
229 {
230   return svn_opt_subcommand_takes_option3(command,
231                                           option_code,
232                                           NULL);
233 }
234
235
236 svn_boolean_t
237 svn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command,
238                                 int option_code)
239 {
240   apr_size_t i;
241
242   for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
243     if (command->valid_options[i] == option_code)
244       return TRUE;
245
246   return FALSE;
247 }
248
249
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. */
253 static svn_error_t *
254 print_command_info2(const svn_opt_subcommand_desc2_t *cmd,
255                     const apr_getopt_option_t *options_table,
256                     const int *global_options,
257                     svn_boolean_t help,
258                     apr_pool_t *pool,
259                     FILE *stream)
260 {
261   svn_boolean_t first_time;
262   apr_size_t i;
263
264   /* Print the canonical command name. */
265   SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
266
267   /* Print the list of aliases. */
268   first_time = TRUE;
269   for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
270     {
271       if (cmd->aliases[i] == NULL)
272         break;
273
274       if (first_time) {
275         SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
276         first_time = FALSE;
277       }
278       else
279         SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
280
281       SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
282     }
283
284   if (! first_time)
285     SVN_ERR(svn_cmdline_fputs(")", stream, pool));
286
287   if (help)
288     {
289       const apr_getopt_option_t *option;
290       const char *long_alias;
291       svn_boolean_t have_options = FALSE;
292
293       SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help)));
294
295       /* Loop over all valid option codes attached to the subcommand */
296       for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
297         {
298           if (cmd->valid_options[i])
299             {
300               if (!have_options)
301                 {
302                   SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
303                                             stream, pool));
304                   have_options = TRUE;
305                 }
306
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);
310
311               /* print the option's docstring */
312               if (option && option->description)
313                 {
314                   const char *optstr;
315                   format_option(&optstr, option, long_alias, TRUE, pool);
316                   SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
317                                               optstr));
318                 }
319             }
320         }
321       /* And global options too */
322       if (global_options && *global_options)
323         {
324           SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"),
325                                     stream, pool));
326           have_options = TRUE;
327
328           for (i = 0; global_options[i]; i++)
329             {
330
331               /* convert each option code into an option */
332               option = get_option_from_code(&long_alias, global_options[i],
333                                             options_table, cmd, pool);
334
335               /* print the option's docstring */
336               if (option && option->description)
337                 {
338                   const char *optstr;
339                   format_option(&optstr, option, long_alias, TRUE, pool);
340                   SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
341                                               optstr));
342                 }
343             }
344         }
345
346       if (have_options)
347         SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
348     }
349
350   return SVN_NO_ERROR;
351 }
352
353 void
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,
357                             const char *footer,
358                             apr_pool_t *pool, FILE *stream)
359 {
360   int i = 0;
361   svn_error_t *err;
362
363   if (header)
364     if ((err = svn_cmdline_fputs(header, stream, pool)))
365       goto print_error;
366
367   while (cmd_table[i].name)
368     {
369       if ((err = svn_cmdline_fputs("   ", stream, pool))
370           || (err = print_command_info2(cmd_table + i, opt_table,
371                                         NULL, FALSE,
372                                         pool, stream))
373           || (err = svn_cmdline_fputs("\n", stream, pool)))
374         goto print_error;
375       i++;
376     }
377
378   if ((err = svn_cmdline_fputs("\n", stream, pool)))
379     goto print_error;
380
381   if (footer)
382     if ((err = svn_cmdline_fputs(footer, stream, pool)))
383       goto print_error;
384
385   return;
386
387  print_error:
388   /* Issue #3014:
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.
392    *
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);
398 }
399
400
401 void
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,
406                          apr_pool_t *pool)
407 {
408   const svn_opt_subcommand_desc2_t *cmd =
409     svn_opt_get_canonical_subcommand2(table, subcommand);
410   svn_error_t *err;
411
412   if (cmd)
413     err = print_command_info2(cmd, options_table, global_options,
414                               TRUE, pool, stdout);
415   else
416     err = svn_cmdline_fprintf(stderr, pool,
417                               _("\"%s\": unknown command.\n\n"), subcommand);
418
419   if (err) {
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);
424   }
425 }
426
427
428 \f
429 /*** Parsing revision and date options. ***/
430
431 \f
432 /** Parsing "X:Y"-style arguments. **/
433
434 /* If WORD matches one of the special revision descriptors,
435  * case-insensitively, set *REVISION accordingly:
436  *
437  *   - For "head", set REVISION->kind to svn_opt_revision_head.
438  *
439  *   - For "prev", set REVISION->kind to svn_opt_revision_previous.
440  *
441  *   - For "base", set REVISION->kind to svn_opt_revision_base.
442  *
443  *   - For "committed", set REVISION->kind to svn_opt_revision_committed.
444  *
445  * If match, return 0, else return -1 and don't touch REVISION.
446  */
447 static int
448 revision_from_word(svn_opt_revision_t *revision, const char *word)
449 {
450   if (svn_cstring_casecmp(word, "head") == 0)
451     {
452       revision->kind = svn_opt_revision_head;
453     }
454   else if (svn_cstring_casecmp(word, "prev") == 0)
455     {
456       revision->kind = svn_opt_revision_previous;
457     }
458   else if (svn_cstring_casecmp(word, "base") == 0)
459     {
460       revision->kind = svn_opt_revision_base;
461     }
462   else if (svn_cstring_casecmp(word, "committed") == 0)
463     {
464       revision->kind = svn_opt_revision_committed;
465     }
466   else
467     return -1;
468
469   return 0;
470 }
471
472
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,
478                            apr_pool_t *pool)
479 {
480   char *end, save;
481
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.
486
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.
489
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. */
493   while (*str == 'r')
494     str++;
495
496   if (*str == '{')
497     {
498       svn_boolean_t matched;
499       apr_time_t tm;
500       svn_error_t *err;
501
502       /* Brackets denote a date. */
503       str++;
504       end = strchr(str, '}');
505       if (!end)
506         return NULL;
507       *end = '\0';
508       err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
509       if (err)
510         {
511           svn_error_clear(err);
512           return NULL;
513         }
514       if (!matched)
515         return NULL;
516       revision->kind = svn_opt_revision_date;
517       revision->value.date = tm;
518       return end + 1;
519     }
520   else if (svn_ctype_isdigit(*str))
521     {
522       /* It's a number. */
523       end = str + 1;
524       while (svn_ctype_isdigit(*end))
525         end++;
526       save = *end;
527       *end = '\0';
528       revision->kind = svn_opt_revision_number;
529       revision->value.number = SVN_STR_TO_REV(str);
530       *end = save;
531       return end;
532     }
533   else if (svn_ctype_isalpha(*str))
534     {
535       end = str + 1;
536       while (svn_ctype_isalpha(*end))
537         end++;
538       save = *end;
539       *end = '\0';
540       if (revision_from_word(revision, str) != 0)
541         return NULL;
542       *end = save;
543       return end;
544     }
545   else
546     return NULL;
547 }
548
549
550 int
551 svn_opt_parse_revision(svn_opt_revision_t *start_revision,
552                        svn_opt_revision_t *end_revision,
553                        const char *arg,
554                        apr_pool_t *pool)
555 {
556   char *left_rev, *right_rev, *end;
557
558   /* Operate on a copy of the argument. */
559   left_rev = apr_pstrdup(pool, arg);
560
561   right_rev = parse_one_rev(start_revision, left_rev, pool);
562   if (right_rev && *right_rev == ':')
563     {
564       right_rev++;
565       end = parse_one_rev(end_revision, right_rev, pool);
566       if (!end || *end != '\0')
567         return -1;
568     }
569   else if (!right_rev || *right_rev != '\0')
570     return -1;
571
572   return 0;
573 }
574
575
576 int
577 svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
578                                 const char *arg,
579                                 apr_pool_t *pool)
580 {
581   svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
582
583   range->start.kind = svn_opt_revision_unspecified;
584   range->end.kind = svn_opt_revision_unspecified;
585
586   if (svn_opt_parse_revision(&(range->start), &(range->end),
587                              arg, pool) == -1)
588     return -1;
589
590   APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
591   return 0;
592 }
593
594 svn_error_t *
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,
599                           apr_pool_t *pool)
600 {
601   if (peg_rev->kind == svn_opt_revision_unspecified)
602     {
603       if (is_url)
604         {
605           peg_rev->kind = svn_opt_revision_head;
606         }
607       else
608         {
609           if (notice_local_mods)
610             peg_rev->kind = svn_opt_revision_working;
611           else
612             peg_rev->kind = svn_opt_revision_base;
613         }
614     }
615
616   if (op_rev->kind == svn_opt_revision_unspecified)
617     *op_rev = *peg_rev;
618
619   return SVN_NO_ERROR;
620 }
621
622 const char *
623 svn_opt__revision_to_string(const svn_opt_revision_t *revision,
624                             apr_pool_t *result_pool)
625 {
626   switch (revision->kind)
627     {
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:
636         return "committed";
637       case svn_opt_revision_previous:
638         return "previous";
639       case svn_opt_revision_base:
640         return "base";
641       case svn_opt_revision_working:
642         return "working";
643       case svn_opt_revision_head:
644         return "head";
645       default:
646         return NULL;
647     }
648 }
649
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)
654 {
655   svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
656
657   range->start = *start_revision;
658   range->end = *end_revision;
659   return range;
660 }
661
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)
666 {
667   svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
668
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;
673   return range;
674 }
675
676
677 \f
678 /*** Parsing arguments. ***/
679 #define DEFAULT_ARRAY_SIZE 5
680
681
682 /* Copy STR into POOL and push the copy onto ARRAY. */
683 static void
684 array_push_str(apr_array_header_t *array,
685                const char *str,
686                apr_pool_t *pool)
687 {
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
692      depending on? */
693
694   APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
695 }
696
697
698 void
699 svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
700                                  apr_pool_t *pool)
701 {
702   if (targets->nelts == 0)
703     APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */
704   assert(targets->nelts);
705 }
706
707
708 svn_error_t *
709 svn_opt_parse_num_args(apr_array_header_t **args_p,
710                        apr_getopt_t *os,
711                        int num_args,
712                        apr_pool_t *pool)
713 {
714   int i;
715   apr_array_header_t *args
716     = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
717
718   /* loop for num_args and add each arg to the args array */
719   for (i = 0; i < num_args; i++)
720     {
721       if (os->ind >= os->argc)
722         {
723           return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
724         }
725       array_push_str(args, os->argv[os->ind++], pool);
726     }
727
728   *args_p = args;
729   return SVN_NO_ERROR;
730 }
731
732 svn_error_t *
733 svn_opt_parse_all_args(apr_array_header_t **args_p,
734                        apr_getopt_t *os,
735                        apr_pool_t *pool)
736 {
737   apr_array_header_t *args
738     = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
739
740   if (os->ind > os->argc)
741     {
742       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
743     }
744   while (os->ind < os->argc)
745     {
746       array_push_str(args, os->argv[os->ind++], pool);
747     }
748
749   *args_p = args;
750   return SVN_NO_ERROR;
751 }
752
753
754 svn_error_t *
755 svn_opt_parse_path(svn_opt_revision_t *rev,
756                    const char **truepath,
757                    const char *path /* UTF-8! */,
758                    apr_pool_t *pool)
759 {
760   const char *peg_rev;
761
762   SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool));
763
764   /* Parse the peg revision, if one was found */
765   if (strlen(peg_rev))
766     {
767       int ret;
768       svn_opt_revision_t start_revision, end_revision;
769
770       end_revision.kind = svn_opt_revision_unspecified;
771
772       if (peg_rev[1] == '\0')  /* looking at empty peg revision */
773         {
774           ret = 0;
775           start_revision.kind = svn_opt_revision_unspecified;
776           start_revision.value.number = 0;
777         }
778       else  /* looking at non-empty peg revision */
779         {
780           const char *rev_str = &peg_rev[1];
781
782           /* URLs get treated differently from wc paths. */
783           if (svn_path_is_url(path))
784             {
785               /* URLs are URI-encoded, so we look for dates with
786                  URI-encoded delimeters.  */
787               size_t rev_len = strlen(rev_str);
788               if (rev_len > 6
789                   && rev_str[0] == '%'
790                   && rev_str[1] == '7'
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'))
797                 {
798                   rev_str = svn_path_uri_decode(rev_str, pool);
799                 }
800             }
801           ret = svn_opt_parse_revision(&start_revision,
802                                        &end_revision,
803                                        rev_str, pool);
804         }
805
806       if (ret || end_revision.kind != svn_opt_revision_unspecified)
807         {
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)
812             {
813               const char *at;
814
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@'?"),
820                                        &peg_rev[1], path);
821             }
822
823           return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
824                                    _("Syntax error parsing peg revision '%s'"),
825                                    &peg_rev[1]);
826         }
827       rev->kind = start_revision.kind;
828       rev->value = start_revision.value;
829     }
830   else
831     {
832       /* Didn't find a peg revision. */
833       rev->kind = svn_opt_revision_unspecified;
834     }
835
836   return SVN_NO_ERROR;
837 }
838
839
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. */
842 svn_error_t *
843 svn_opt__args_to_target_array(apr_array_header_t **targets_p,
844                               apr_getopt_t *os,
845                               const apr_array_header_t *known_targets,
846                               apr_pool_t *pool)
847 {
848   int i;
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 *));
854
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.) */
858
859   for (; os->ind < os->argc; os->ind++)
860     {
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),
865                raw_target, pool));
866     }
867
868   if (known_targets)
869     {
870       for (i = 0; i < known_targets->nelts; i++)
871         {
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,
875                                                   i, const char *);
876           APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
877         }
878     }
879
880   /* Step 2:  process each target.  */
881
882   for (i = 0; i < input_targets->nelts; i++)
883     {
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 */
887       const char *peg_rev;
888
889       /*
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.
900        */
901       SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
902                                                  utf8_target, pool));
903
904       /* URLs and wc-paths get treated differently. */
905       if (svn_path_is_url(true_target))
906         {
907           SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target,
908                                                  pool));
909         }
910       else  /* not a url, so treat as a path */
911         {
912           const char *base_name;
913
914           SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target,
915                                                  pool));
916
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);
920
921           /* FIXME:
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
927              synchronized! */
928           if (0 == strcmp(base_name, ".svn")
929               || 0 == strcmp(base_name, "_svn"))
930             {
931               err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
932                                       err, _("'%s' ends in a reserved name"),
933                                       utf8_target);
934               continue;
935             }
936         }
937
938       target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL);
939
940       APR_ARRAY_PUSH(output_targets, const char *) = target;
941     }
942
943
944   /* kff todo: need to remove redundancies from targets before
945      passing it to the cmd_func. */
946
947   *targets_p = output_targets;
948
949   return err;
950 }
951
952 svn_error_t *
953 svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
954                       apr_pool_t *pool)
955 {
956   const char *sep, *propname;
957   svn_string_t *propval;
958
959   if (! *revprop_spec)
960     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
961                             _("Revision property pair is empty"));
962
963   if (! *revprop_table_p)
964     *revprop_table_p = apr_hash_make(pool);
965
966   sep = strchr(revprop_spec, '=');
967   if (sep)
968     {
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);
972     }
973   else
974     {
975       SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
976       propval = svn_string_create_empty(pool);
977     }
978
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"),
982                              propname);
983
984   svn_hash_sets(*revprop_table_p, propname, propval);
985
986   return SVN_NO_ERROR;
987 }
988
989 svn_error_t *
990 svn_opt__split_arg_at_peg_revision(const char **true_target,
991                                    const char **peg_revision,
992                                    const char *utf8_target,
993                                    apr_pool_t *pool)
994 {
995   const char *peg_start = NULL; /* pointer to the peg revision, if any */
996   const char *ptr;
997
998   for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target;
999         --ptr)
1000     {
1001       /* If we hit a path separator, stop looking.  This is OK
1002           only because our revision specifiers can't contain '/'. */
1003       if (*ptr == '/')
1004         break;
1005
1006       if (*ptr == '@')
1007         {
1008           peg_start = ptr;
1009           break;
1010         }
1011     }
1012
1013   if (peg_start)
1014     {
1015       /* Error out if target is the empty string. */
1016       if (ptr == utf8_target)
1017         return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
1018                                  _("'%s' is just a peg revision. "
1019                                    "Maybe try '%s@' instead?"),
1020                                  utf8_target, utf8_target);
1021
1022       *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target);
1023       if (peg_revision)
1024         *peg_revision = apr_pstrdup(pool, peg_start);
1025     }
1026   else
1027     {
1028       *true_target = utf8_target;
1029       if (peg_revision)
1030         *peg_revision = "";
1031     }
1032
1033   return SVN_NO_ERROR;
1034 }
1035
1036 svn_error_t *
1037 svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in,
1038                               apr_pool_t *pool)
1039 {
1040   const char *target;
1041
1042   /* Convert to URI. */
1043   target = svn_path_uri_from_iri(url_in, pool);
1044   /* Auto-escape some ASCII characters. */
1045   target = svn_path_uri_autoescape(target, pool);
1046
1047 #if '/' != SVN_PATH_LOCAL_SEPARATOR
1048   /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */
1049   if (strchr(target, SVN_PATH_LOCAL_SEPARATOR))
1050     {
1051       char *p = apr_pstrdup(pool, target);
1052       target = p;
1053
1054       /* Convert all local-style separators to the canonical ones. */
1055       for (; *p != '\0'; ++p)
1056         if (*p == SVN_PATH_LOCAL_SEPARATOR)
1057           *p = '/';
1058     }
1059 #endif
1060
1061   /* Verify that no backpaths are present in the URL. */
1062   if (svn_path_is_backpath_present(target))
1063     return svn_error_createf(SVN_ERR_BAD_URL, 0,
1064                              _("URL '%s' contains a '..' element"),
1065                              target);
1066
1067   /* Strip any trailing '/' and collapse other redundant elements. */
1068   target = svn_uri_canonicalize(target, pool);
1069
1070   *url_out = target;
1071   return SVN_NO_ERROR;
1072 }
1073
1074 svn_error_t *
1075 svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in,
1076                                apr_pool_t *pool)
1077 {
1078   const char *apr_target;
1079   char *truenamed_target; /* APR-encoded */
1080   apr_status_t apr_err;
1081
1082   /* canonicalize case, and change all separators to '/'. */
1083   SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool));
1084   apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
1085                                APR_FILEPATH_TRUENAME, pool);
1086
1087   if (!apr_err)
1088     /* We have a canonicalized APR-encoded target now. */
1089     apr_target = truenamed_target;
1090   else if (APR_STATUS_IS_ENOENT(apr_err))
1091     /* It's okay for the file to not exist, that just means we
1092        have to accept the case given to the client. We'll use
1093        the original APR-encoded target. */
1094     ;
1095   else
1096     return svn_error_createf(apr_err, NULL,
1097                              _("Error resolving case of '%s'"),
1098                              svn_dirent_local_style(path_in, pool));
1099
1100   /* convert back to UTF-8. */
1101   SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool));
1102   *path_out = svn_dirent_canonicalize(*path_out, pool);
1103
1104   return SVN_NO_ERROR;
1105 }
1106
1107
1108 svn_error_t *
1109 svn_opt__print_version_info(const char *pgm_name,
1110                             const char *footer,
1111                             const svn_version_extended_t *info,
1112                             svn_boolean_t quiet,
1113                             svn_boolean_t verbose,
1114                             apr_pool_t *pool)
1115 {
1116   if (quiet)
1117     return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER);
1118
1119   SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
1120                                      "   compiled on %s\n\n"),
1121                              pgm_name, SVN_VERSION,
1122                              svn_version_ext_build_host(info)));
1123   SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
1124
1125   if (footer)
1126     {
1127       SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
1128     }
1129
1130   if (verbose)
1131     {
1132       const apr_array_header_t *libs;
1133
1134       SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
1135       SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
1136                                  svn_version_ext_runtime_host(info)));
1137       if (svn_version_ext_runtime_osname(info))
1138         {
1139           SVN_ERR(svn_cmdline_printf(pool, _("  - %s\n"),
1140                                      svn_version_ext_runtime_osname(info)));
1141         }
1142
1143       libs = svn_version_ext_linked_libs(info);
1144       if (libs && libs->nelts)
1145         {
1146           const svn_version_ext_linked_lib_t *lib;
1147           int i;
1148
1149           SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
1150                                     stdout, pool));
1151           for (i = 0; i < libs->nelts; ++i)
1152             {
1153               lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
1154               if (lib->runtime_version)
1155                 SVN_ERR(svn_cmdline_printf(pool,
1156                                            "  - %s %s (compiled with %s)\n",
1157                                            lib->name,
1158                                            lib->runtime_version,
1159                                            lib->compiled_version));
1160               else
1161                 SVN_ERR(svn_cmdline_printf(pool,
1162                                            "  - %s %s (static)\n",
1163                                            lib->name,
1164                                            lib->compiled_version));
1165             }
1166         }
1167
1168       libs = svn_version_ext_loaded_libs(info);
1169       if (libs && libs->nelts)
1170         {
1171           const svn_version_ext_loaded_lib_t *lib;
1172           int i;
1173
1174           SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
1175                                     stdout, pool));
1176           for (i = 0; i < libs->nelts; ++i)
1177             {
1178               lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
1179               if (lib->version)
1180                 SVN_ERR(svn_cmdline_printf(pool,
1181                                            "  - %s   (%s)\n",
1182                                            lib->name, lib->version));
1183               else
1184                 SVN_ERR(svn_cmdline_printf(pool, "  - %s\n", lib->name));
1185             }
1186         }
1187     }
1188
1189   return SVN_NO_ERROR;
1190 }
1191
1192 svn_error_t *
1193 svn_opt_print_help4(apr_getopt_t *os,
1194                     const char *pgm_name,
1195                     svn_boolean_t print_version,
1196                     svn_boolean_t quiet,
1197                     svn_boolean_t verbose,
1198                     const char *version_footer,
1199                     const char *header,
1200                     const svn_opt_subcommand_desc2_t *cmd_table,
1201                     const apr_getopt_option_t *option_table,
1202                     const int *global_options,
1203                     const char *footer,
1204                     apr_pool_t *pool)
1205 {
1206   apr_array_header_t *targets = NULL;
1207
1208   if (os)
1209     SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1210
1211   if (os && targets->nelts)  /* help on subcommand(s) requested */
1212     {
1213       int i;
1214
1215       for (i = 0; i < targets->nelts; i++)
1216         {
1217           svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *),
1218                                    cmd_table, option_table,
1219                                    global_options, pool);
1220         }
1221     }
1222   else if (print_version)   /* just --version */
1223     {
1224       SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
1225                                           svn_version_extended(verbose, pool),
1226                                           quiet, verbose, pool));
1227     }
1228   else if (os && !targets->nelts)            /* `-h', `--help', or `help' */
1229     svn_opt_print_generic_help2(header,
1230                                 cmd_table,
1231                                 option_table,
1232                                 footer,
1233                                 pool,
1234                                 stdout);
1235   else                                       /* unknown option or cmd */
1236     SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1237                                 _("Type '%s help' for usage.\n"), pgm_name));
1238
1239   return SVN_NO_ERROR;
1240 }