]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_subr/opt.c
Copy head (r256279) to stable/10 as part of the 10.0-RELEASE cycle.
[FreeBSD/stable/10.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     svn_handle_error2(err, stderr, FALSE, "svn: ");
421     svn_error_clear(err);
422   }
423 }
424
425
426 \f
427 /*** Parsing revision and date options. ***/
428
429 \f
430 /** Parsing "X:Y"-style arguments. **/
431
432 /* If WORD matches one of the special revision descriptors,
433  * case-insensitively, set *REVISION accordingly:
434  *
435  *   - For "head", set REVISION->kind to svn_opt_revision_head.
436  *
437  *   - For "prev", set REVISION->kind to svn_opt_revision_previous.
438  *
439  *   - For "base", set REVISION->kind to svn_opt_revision_base.
440  *
441  *   - For "committed", set REVISION->kind to svn_opt_revision_committed.
442  *
443  * If match, return 0, else return -1 and don't touch REVISION.
444  */
445 static int
446 revision_from_word(svn_opt_revision_t *revision, const char *word)
447 {
448   if (svn_cstring_casecmp(word, "head") == 0)
449     {
450       revision->kind = svn_opt_revision_head;
451     }
452   else if (svn_cstring_casecmp(word, "prev") == 0)
453     {
454       revision->kind = svn_opt_revision_previous;
455     }
456   else if (svn_cstring_casecmp(word, "base") == 0)
457     {
458       revision->kind = svn_opt_revision_base;
459     }
460   else if (svn_cstring_casecmp(word, "committed") == 0)
461     {
462       revision->kind = svn_opt_revision_committed;
463     }
464   else
465     return -1;
466
467   return 0;
468 }
469
470
471 /* Parse one revision specification.  Return pointer to character
472    after revision, or NULL if the revision is invalid.  Modifies
473    str, so make sure to pass a copy of anything precious.  Uses
474    POOL for temporary allocation. */
475 static char *parse_one_rev(svn_opt_revision_t *revision, char *str,
476                            apr_pool_t *pool)
477 {
478   char *end, save;
479
480   /* Allow any number of 'r's to prefix a revision number, because
481      that way if a script pastes svn output into another svn command
482      (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work,
483      even when compounded.
484
485      As it happens, none of our special revision words begins with
486      "r".  If any ever do, then this code will have to get smarter.
487
488      Incidentally, this allows "r{DATE}".  We could avoid that with
489      some trivial code rearrangement, but it's not clear what would
490      be gained by doing so. */
491   while (*str == 'r')
492     str++;
493
494   if (*str == '{')
495     {
496       svn_boolean_t matched;
497       apr_time_t tm;
498       svn_error_t *err;
499
500       /* Brackets denote a date. */
501       str++;
502       end = strchr(str, '}');
503       if (!end)
504         return NULL;
505       *end = '\0';
506       err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
507       if (err)
508         {
509           svn_error_clear(err);
510           return NULL;
511         }
512       if (!matched)
513         return NULL;
514       revision->kind = svn_opt_revision_date;
515       revision->value.date = tm;
516       return end + 1;
517     }
518   else if (svn_ctype_isdigit(*str))
519     {
520       /* It's a number. */
521       end = str + 1;
522       while (svn_ctype_isdigit(*end))
523         end++;
524       save = *end;
525       *end = '\0';
526       revision->kind = svn_opt_revision_number;
527       revision->value.number = SVN_STR_TO_REV(str);
528       *end = save;
529       return end;
530     }
531   else if (svn_ctype_isalpha(*str))
532     {
533       end = str + 1;
534       while (svn_ctype_isalpha(*end))
535         end++;
536       save = *end;
537       *end = '\0';
538       if (revision_from_word(revision, str) != 0)
539         return NULL;
540       *end = save;
541       return end;
542     }
543   else
544     return NULL;
545 }
546
547
548 int
549 svn_opt_parse_revision(svn_opt_revision_t *start_revision,
550                        svn_opt_revision_t *end_revision,
551                        const char *arg,
552                        apr_pool_t *pool)
553 {
554   char *left_rev, *right_rev, *end;
555
556   /* Operate on a copy of the argument. */
557   left_rev = apr_pstrdup(pool, arg);
558
559   right_rev = parse_one_rev(start_revision, left_rev, pool);
560   if (right_rev && *right_rev == ':')
561     {
562       right_rev++;
563       end = parse_one_rev(end_revision, right_rev, pool);
564       if (!end || *end != '\0')
565         return -1;
566     }
567   else if (!right_rev || *right_rev != '\0')
568     return -1;
569
570   return 0;
571 }
572
573
574 int
575 svn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
576                                 const char *arg,
577                                 apr_pool_t *pool)
578 {
579   svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
580
581   range->start.kind = svn_opt_revision_unspecified;
582   range->end.kind = svn_opt_revision_unspecified;
583
584   if (svn_opt_parse_revision(&(range->start), &(range->end),
585                              arg, pool) == -1)
586     return -1;
587
588   APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
589   return 0;
590 }
591
592 svn_error_t *
593 svn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
594                           svn_opt_revision_t *op_rev,
595                           svn_boolean_t is_url,
596                           svn_boolean_t notice_local_mods,
597                           apr_pool_t *pool)
598 {
599   if (peg_rev->kind == svn_opt_revision_unspecified)
600     {
601       if (is_url)
602         {
603           peg_rev->kind = svn_opt_revision_head;
604         }
605       else
606         {
607           if (notice_local_mods)
608             peg_rev->kind = svn_opt_revision_working;
609           else
610             peg_rev->kind = svn_opt_revision_base;
611         }
612     }
613
614   if (op_rev->kind == svn_opt_revision_unspecified)
615     *op_rev = *peg_rev;
616
617   return SVN_NO_ERROR;
618 }
619
620 const char *
621 svn_opt__revision_to_string(const svn_opt_revision_t *revision,
622                             apr_pool_t *result_pool)
623 {
624   switch (revision->kind)
625     {
626       case svn_opt_revision_unspecified:
627         return "unspecified";
628       case svn_opt_revision_number:
629         return apr_psprintf(result_pool, "%ld", revision->value.number);
630       case svn_opt_revision_date:
631         /* ### svn_time_to_human_cstring()? */
632         return svn_time_to_cstring(revision->value.date, result_pool);
633       case svn_opt_revision_committed:
634         return "committed";
635       case svn_opt_revision_previous:
636         return "previous";
637       case svn_opt_revision_base:
638         return "base";
639       case svn_opt_revision_working:
640         return "working";
641       case svn_opt_revision_head:
642         return "head";
643       default:
644         return NULL;
645     }
646 }
647
648 svn_opt_revision_range_t *
649 svn_opt__revision_range_create(const svn_opt_revision_t *start_revision,
650                                const svn_opt_revision_t *end_revision,
651                                apr_pool_t *result_pool)
652 {
653   svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
654
655   range->start = *start_revision;
656   range->end = *end_revision;
657   return range;
658 }
659
660 svn_opt_revision_range_t *
661 svn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,
662                                      svn_revnum_t end_revnum,
663                                      apr_pool_t *result_pool)
664 {
665   svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
666
667   range->start.kind = svn_opt_revision_number;
668   range->start.value.number = start_revnum;
669   range->end.kind = svn_opt_revision_number;
670   range->end.value.number = end_revnum;
671   return range;
672 }
673
674
675 \f
676 /*** Parsing arguments. ***/
677 #define DEFAULT_ARRAY_SIZE 5
678
679
680 /* Copy STR into POOL and push the copy onto ARRAY. */
681 static void
682 array_push_str(apr_array_header_t *array,
683                const char *str,
684                apr_pool_t *pool)
685 {
686   /* ### Not sure if this function is still necessary.  It used to
687      convert str to svn_stringbuf_t * and push it, but now it just
688      dups str in pool and pushes the copy.  So its only effect is
689      transfer str's lifetime to pool.  Is that something callers are
690      depending on? */
691
692   APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
693 }
694
695
696 void
697 svn_opt_push_implicit_dot_target(apr_array_header_t *targets,
698                                  apr_pool_t *pool)
699 {
700   if (targets->nelts == 0)
701     APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */
702   assert(targets->nelts);
703 }
704
705
706 svn_error_t *
707 svn_opt_parse_num_args(apr_array_header_t **args_p,
708                        apr_getopt_t *os,
709                        int num_args,
710                        apr_pool_t *pool)
711 {
712   int i;
713   apr_array_header_t *args
714     = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
715
716   /* loop for num_args and add each arg to the args array */
717   for (i = 0; i < num_args; i++)
718     {
719       if (os->ind >= os->argc)
720         {
721           return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
722         }
723       array_push_str(args, os->argv[os->ind++], pool);
724     }
725
726   *args_p = args;
727   return SVN_NO_ERROR;
728 }
729
730 svn_error_t *
731 svn_opt_parse_all_args(apr_array_header_t **args_p,
732                        apr_getopt_t *os,
733                        apr_pool_t *pool)
734 {
735   apr_array_header_t *args
736     = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
737
738   if (os->ind > os->argc)
739     {
740       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
741     }
742   while (os->ind < os->argc)
743     {
744       array_push_str(args, os->argv[os->ind++], pool);
745     }
746
747   *args_p = args;
748   return SVN_NO_ERROR;
749 }
750
751
752 svn_error_t *
753 svn_opt_parse_path(svn_opt_revision_t *rev,
754                    const char **truepath,
755                    const char *path /* UTF-8! */,
756                    apr_pool_t *pool)
757 {
758   const char *peg_rev;
759
760   SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool));
761
762   /* Parse the peg revision, if one was found */
763   if (strlen(peg_rev))
764     {
765       int ret;
766       svn_opt_revision_t start_revision, end_revision;
767
768       end_revision.kind = svn_opt_revision_unspecified;
769
770       if (peg_rev[1] == '\0')  /* looking at empty peg revision */
771         {
772           ret = 0;
773           start_revision.kind = svn_opt_revision_unspecified;
774           start_revision.value.number = 0;
775         }
776       else  /* looking at non-empty peg revision */
777         {
778           const char *rev_str = &peg_rev[1];
779
780           /* URLs get treated differently from wc paths. */
781           if (svn_path_is_url(path))
782             {
783               /* URLs are URI-encoded, so we look for dates with
784                  URI-encoded delimeters.  */
785               size_t rev_len = strlen(rev_str);
786               if (rev_len > 6
787                   && rev_str[0] == '%'
788                   && rev_str[1] == '7'
789                   && (rev_str[2] == 'B'
790                       || rev_str[2] == 'b')
791                   && rev_str[rev_len-3] == '%'
792                   && rev_str[rev_len-2] == '7'
793                   && (rev_str[rev_len-1] == 'D'
794                       || rev_str[rev_len-1] == 'd'))
795                 {
796                   rev_str = svn_path_uri_decode(rev_str, pool);
797                 }
798             }
799           ret = svn_opt_parse_revision(&start_revision,
800                                        &end_revision,
801                                        rev_str, pool);
802         }
803
804       if (ret || end_revision.kind != svn_opt_revision_unspecified)
805         {
806           /* If an svn+ssh URL was used and it contains only one @,
807            * provide an error message that presents a possible solution
808            * to the parsing error (issue #2349). */
809           if (strncmp(path, "svn+ssh://", 10) == 0)
810             {
811               const char *at;
812
813               at = strchr(path, '@');
814               if (at && strrchr(path, '@') == at)
815                 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
816                                          _("Syntax error parsing peg revision "
817                                            "'%s'; did you mean '%s@'?"),
818                                        &peg_rev[1], path);
819             }
820
821           return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
822                                    _("Syntax error parsing peg revision '%s'"),
823                                    &peg_rev[1]);
824         }
825       rev->kind = start_revision.kind;
826       rev->value = start_revision.value;
827     }
828   else
829     {
830       /* Didn't find a peg revision. */
831       rev->kind = svn_opt_revision_unspecified;
832     }
833
834   return SVN_NO_ERROR;
835 }
836
837
838 /* Note: This is substantially copied into svn_client_args_to_target_array() in
839  * order to move to libsvn_client while maintaining backward compatibility. */
840 svn_error_t *
841 svn_opt__args_to_target_array(apr_array_header_t **targets_p,
842                               apr_getopt_t *os,
843                               const apr_array_header_t *known_targets,
844                               apr_pool_t *pool)
845 {
846   int i;
847   svn_error_t *err = SVN_NO_ERROR;
848   apr_array_header_t *input_targets =
849     apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
850   apr_array_header_t *output_targets =
851     apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
852
853   /* Step 1:  create a master array of targets that are in UTF-8
854      encoding, and come from concatenating the targets left by apr_getopt,
855      plus any extra targets (e.g., from the --targets switch.) */
856
857   for (; os->ind < os->argc; os->ind++)
858     {
859       /* The apr_getopt targets are still in native encoding. */
860       const char *raw_target = os->argv[os->ind];
861       SVN_ERR(svn_utf_cstring_to_utf8
862               ((const char **) apr_array_push(input_targets),
863                raw_target, pool));
864     }
865
866   if (known_targets)
867     {
868       for (i = 0; i < known_targets->nelts; i++)
869         {
870           /* The --targets array have already been converted to UTF-8,
871              because we needed to split up the list with svn_cstring_split. */
872           const char *utf8_target = APR_ARRAY_IDX(known_targets,
873                                                   i, const char *);
874           APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
875         }
876     }
877
878   /* Step 2:  process each target.  */
879
880   for (i = 0; i < input_targets->nelts; i++)
881     {
882       const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
883       const char *true_target;
884       const char *target;      /* after all processing is finished */
885       const char *peg_rev;
886
887       /*
888        * This is needed so that the target can be properly canonicalized,
889        * otherwise the canonicalization does not treat a ".@BASE" as a "."
890        * with a BASE peg revision, and it is not canonicalized to "@BASE".
891        * If any peg revision exists, it is appended to the final
892        * canonicalized path or URL.  Do not use svn_opt_parse_path()
893        * because the resulting peg revision is a structure that would have
894        * to be converted back into a string.  Converting from a string date
895        * to the apr_time_t field in the svn_opt_revision_value_t and back to
896        * a string would not necessarily preserve the exact bytes of the
897        * input date, so its easier just to keep it in string form.
898        */
899       SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
900                                                  utf8_target, pool));
901
902       /* URLs and wc-paths get treated differently. */
903       if (svn_path_is_url(true_target))
904         {
905           SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target,
906                                                  pool));
907         }
908       else  /* not a url, so treat as a path */
909         {
910           const char *base_name;
911
912           SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target,
913                                                  pool));
914
915           /* If the target has the same name as a Subversion
916              working copy administrative dir, skip it. */
917           base_name = svn_dirent_basename(true_target, pool);
918
919           /* FIXME:
920              The canonical list of administrative directory names is
921              maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
922              That list can't be used here, because that use would
923              create a circular dependency between libsvn_wc and
924              libsvn_subr.  Make sure changes to the lists are always
925              synchronized! */
926           if (0 == strcmp(base_name, ".svn")
927               || 0 == strcmp(base_name, "_svn"))
928             {
929               err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
930                                       err, _("'%s' ends in a reserved name"),
931                                       utf8_target);
932               continue;
933             }
934         }
935
936       target = apr_pstrcat(pool, true_target, peg_rev, (char *)NULL);
937
938       APR_ARRAY_PUSH(output_targets, const char *) = target;
939     }
940
941
942   /* kff todo: need to remove redundancies from targets before
943      passing it to the cmd_func. */
944
945   *targets_p = output_targets;
946
947   return err;
948 }
949
950 svn_error_t *
951 svn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
952                       apr_pool_t *pool)
953 {
954   const char *sep, *propname;
955   svn_string_t *propval;
956
957   if (! *revprop_spec)
958     return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
959                             _("Revision property pair is empty"));
960
961   if (! *revprop_table_p)
962     *revprop_table_p = apr_hash_make(pool);
963
964   sep = strchr(revprop_spec, '=');
965   if (sep)
966     {
967       propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec);
968       SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool));
969       propval = svn_string_create(sep + 1, pool);
970     }
971   else
972     {
973       SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
974       propval = svn_string_create_empty(pool);
975     }
976
977   if (!svn_prop_name_is_valid(propname))
978     return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
979                              _("'%s' is not a valid Subversion property name"),
980                              propname);
981
982   svn_hash_sets(*revprop_table_p, propname, propval);
983
984   return SVN_NO_ERROR;
985 }
986
987 svn_error_t *
988 svn_opt__split_arg_at_peg_revision(const char **true_target,
989                                    const char **peg_revision,
990                                    const char *utf8_target,
991                                    apr_pool_t *pool)
992 {
993   const char *peg_start = NULL; /* pointer to the peg revision, if any */
994   const char *ptr;
995
996   for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target;
997         --ptr)
998     {
999       /* If we hit a path separator, stop looking.  This is OK
1000           only because our revision specifiers can't contain '/'. */
1001       if (*ptr == '/')
1002         break;
1003
1004       if (*ptr == '@')
1005         {
1006           peg_start = ptr;
1007           break;
1008         }
1009     }
1010
1011   if (peg_start)
1012     {
1013       /* Error out if target is the empty string. */
1014       if (ptr == utf8_target)
1015         return svn_error_createf(SVN_ERR_BAD_FILENAME, NULL,
1016                                  _("'%s' is just a peg revision. "
1017                                    "Maybe try '%s@' instead?"),
1018                                  utf8_target, utf8_target);
1019
1020       *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target);
1021       if (peg_revision)
1022         *peg_revision = apr_pstrdup(pool, peg_start);
1023     }
1024   else
1025     {
1026       *true_target = utf8_target;
1027       if (peg_revision)
1028         *peg_revision = "";
1029     }
1030
1031   return SVN_NO_ERROR;
1032 }
1033
1034 svn_error_t *
1035 svn_opt__arg_canonicalize_url(const char **url_out, const char *url_in,
1036                               apr_pool_t *pool)
1037 {
1038   const char *target;
1039
1040   /* Convert to URI. */
1041   target = svn_path_uri_from_iri(url_in, pool);
1042   /* Auto-escape some ASCII characters. */
1043   target = svn_path_uri_autoescape(target, pool);
1044
1045 #if '/' != SVN_PATH_LOCAL_SEPARATOR
1046   /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */
1047   if (strchr(target, SVN_PATH_LOCAL_SEPARATOR))
1048     {
1049       char *p = apr_pstrdup(pool, target);
1050       target = p;
1051
1052       /* Convert all local-style separators to the canonical ones. */
1053       for (; *p != '\0'; ++p)
1054         if (*p == SVN_PATH_LOCAL_SEPARATOR)
1055           *p = '/';
1056     }
1057 #endif
1058
1059   /* Verify that no backpaths are present in the URL. */
1060   if (svn_path_is_backpath_present(target))
1061     return svn_error_createf(SVN_ERR_BAD_URL, 0,
1062                              _("URL '%s' contains a '..' element"),
1063                              target);
1064
1065   /* Strip any trailing '/' and collapse other redundant elements. */
1066   target = svn_uri_canonicalize(target, pool);
1067
1068   *url_out = target;
1069   return SVN_NO_ERROR;
1070 }
1071
1072 svn_error_t *
1073 svn_opt__arg_canonicalize_path(const char **path_out, const char *path_in,
1074                                apr_pool_t *pool)
1075 {
1076   const char *apr_target;
1077   char *truenamed_target; /* APR-encoded */
1078   apr_status_t apr_err;
1079
1080   /* canonicalize case, and change all separators to '/'. */
1081   SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool));
1082   apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
1083                                APR_FILEPATH_TRUENAME, pool);
1084
1085   if (!apr_err)
1086     /* We have a canonicalized APR-encoded target now. */
1087     apr_target = truenamed_target;
1088   else if (APR_STATUS_IS_ENOENT(apr_err))
1089     /* It's okay for the file to not exist, that just means we
1090        have to accept the case given to the client. We'll use
1091        the original APR-encoded target. */
1092     ;
1093   else
1094     return svn_error_createf(apr_err, NULL,
1095                              _("Error resolving case of '%s'"),
1096                              svn_dirent_local_style(path_in, pool));
1097
1098   /* convert back to UTF-8. */
1099   SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool));
1100   *path_out = svn_dirent_canonicalize(*path_out, pool);
1101
1102   return SVN_NO_ERROR;
1103 }
1104
1105
1106 svn_error_t *
1107 svn_opt__print_version_info(const char *pgm_name,
1108                             const char *footer,
1109                             const svn_version_extended_t *info,
1110                             svn_boolean_t quiet,
1111                             svn_boolean_t verbose,
1112                             apr_pool_t *pool)
1113 {
1114   if (quiet)
1115     return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER);
1116
1117   SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
1118                                      "   compiled %s, %s on %s\n\n"),
1119                              pgm_name, SVN_VERSION,
1120                              svn_version_ext_build_date(info),
1121                              svn_version_ext_build_time(info),
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 }