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