opt.c revision 269847
1245803Stheraven/*
2245803Stheraven * opt.c :  option and argument parsing for Subversion command lines
3245803Stheraven *
4245803Stheraven * ====================================================================
5245803Stheraven *    Licensed to the Apache Software Foundation (ASF) under one
6245803Stheraven *    or more contributor license agreements.  See the NOTICE file
7245803Stheraven *    distributed with this work for additional information
8245803Stheraven *    regarding copyright ownership.  The ASF licenses this file
9245803Stheraven *    to you under the Apache License, Version 2.0 (the
10245803Stheraven *    "License"); you may not use this file except in compliance
11245803Stheraven *    with the License.  You may obtain a copy of the License at
12245803Stheraven *
13245803Stheraven *      http://www.apache.org/licenses/LICENSE-2.0
14245803Stheraven *
15245803Stheraven *    Unless required by applicable law or agreed to in writing,
16245803Stheraven *    software distributed under the License is distributed on an
17245803Stheraven *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18245803Stheraven *    KIND, either express or implied.  See the License for the
19245803Stheraven *    specific language governing permissions and limitations
20245803Stheraven *    under the License.
21245803Stheraven * ====================================================================
22245803Stheraven */
23245803Stheraven
24245803Stheraven
25245803Stheraven
26245803Stheraven#define APR_WANT_STRFUNC
27245803Stheraven#include <apr_want.h>
28245803Stheraven
29245803Stheraven#include <stdio.h>
30245803Stheraven#include <string.h>
31245803Stheraven#include <assert.h>
32245803Stheraven#include <apr_pools.h>
33245803Stheraven#include <apr_general.h>
34245839Stheraven#include <apr_lib.h>
35245803Stheraven#include <apr_file_info.h>
36245839Stheraven
37245839Stheraven#include "svn_hash.h"
38245803Stheraven#include "svn_cmdline.h"
39245839Stheraven#include "svn_version.h"
40245803Stheraven#include "svn_types.h"
41245803Stheraven#include "svn_opt.h"
42245803Stheraven#include "svn_error.h"
43245803Stheraven#include "svn_dirent_uri.h"
44245803Stheraven#include "svn_path.h"
45245803Stheraven#include "svn_utf.h"
46245803Stheraven#include "svn_time.h"
47245803Stheraven#include "svn_props.h"
48245803Stheraven#include "svn_ctype.h"
49245803Stheraven
50245803Stheraven#include "private/svn_opt_private.h"
51245803Stheraven
52245803Stheraven#include "opt.h"
53245803Stheraven#include "svn_private_config.h"
54245803Stheraven
55245803Stheraven
56245803Stheraven/*** Code. ***/
57245803Stheraven
58245803Stheravenconst svn_opt_subcommand_desc2_t *
59245803Stheravensvn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table,
60245803Stheraven                                  const char *cmd_name)
61245803Stheraven{
62245803Stheraven  int i = 0;
63245803Stheraven
64245803Stheraven  if (cmd_name == NULL)
65245803Stheraven    return NULL;
66245803Stheraven
67245803Stheraven  while (table[i].name) {
68245803Stheraven    int j;
69245803Stheraven    if (strcmp(cmd_name, table[i].name) == 0)
70245803Stheraven      return table + i;
71245803Stheraven    for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
72245803Stheraven      if (strcmp(cmd_name, table[i].aliases[j]) == 0)
73245803Stheraven        return table + i;
74245803Stheraven
75245803Stheraven    i++;
76245803Stheraven  }
77245803Stheraven
78245803Stheraven  /* If we get here, there was no matching subcommand name or alias. */
79245803Stheraven  return NULL;
80245803Stheraven}
81245803Stheraven
82245803Stheravenconst apr_getopt_option_t *
83245803Stheravensvn_opt_get_option_from_code2(int code,
84245803Stheraven                              const apr_getopt_option_t *option_table,
85245803Stheraven                              const svn_opt_subcommand_desc2_t *command,
86245803Stheraven                              apr_pool_t *pool)
87245803Stheraven{
88245803Stheraven  apr_size_t i;
89245803Stheraven
90245803Stheraven  for (i = 0; option_table[i].optch; i++)
91245803Stheraven    if (option_table[i].optch == code)
92245803Stheraven      {
93245803Stheraven        if (command)
94245803Stheraven          {
95245803Stheraven            int j;
96245803Stheraven
97245803Stheraven            for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) &&
98245803Stheraven                         command->desc_overrides[j].optch); j++)
99245803Stheraven              if (command->desc_overrides[j].optch == code)
100245803Stheraven                {
101245803Stheraven                  apr_getopt_option_t *tmpopt =
102245803Stheraven                      apr_palloc(pool, sizeof(*tmpopt));
103245803Stheraven                  *tmpopt = option_table[i];
104245803Stheraven                  tmpopt->description = command->desc_overrides[j].desc;
105245803Stheraven                  return tmpopt;
106245803Stheraven                }
107245803Stheraven          }
108245803Stheraven        return &(option_table[i]);
109245803Stheraven      }
110245803Stheraven
111245803Stheraven  return NULL;
112245803Stheraven}
113245803Stheraven
114245803Stheraven
115245803Stheravenconst apr_getopt_option_t *
116245803Stheravensvn_opt_get_option_from_code(int code,
117245803Stheraven                             const apr_getopt_option_t *option_table)
118245803Stheraven{
119245803Stheraven  apr_size_t i;
120245803Stheraven
121245803Stheraven  for (i = 0; option_table[i].optch; i++)
122245803Stheraven    if (option_table[i].optch == code)
123245803Stheraven      return &(option_table[i]);
124245803Stheraven
125245803Stheraven  return NULL;
126245803Stheraven}
127245803Stheraven
128245803Stheraven
129245803Stheraven/* Like svn_opt_get_option_from_code2(), but also, if CODE appears a second
130245803Stheraven * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that
131245803Stheraven * second name, else set it to NULL. */
132245803Stheravenstatic const apr_getopt_option_t *
133245803Stheravenget_option_from_code(const char **long_alias,
134245803Stheraven                     int code,
135245803Stheraven                     const apr_getopt_option_t *option_table,
136245803Stheraven                     const svn_opt_subcommand_desc2_t *command,
137245803Stheraven                     apr_pool_t *pool)
138245803Stheraven{
139245803Stheraven  const apr_getopt_option_t *i;
140245803Stheraven  const apr_getopt_option_t *opt
141245803Stheraven    = svn_opt_get_option_from_code2(code, option_table, command, pool);
142245803Stheraven
143245803Stheraven  /* Find a long alias in the table, if there is one. */
144245803Stheraven  *long_alias = NULL;
145245803Stheraven  for (i = option_table; i->optch; i++)
146245803Stheraven    {
147245803Stheraven      if (i->optch == code && i->name != opt->name)
148245803Stheraven        {
149245803Stheraven          *long_alias = i->name;
150245803Stheraven          break;
151245803Stheraven        }
152245803Stheraven    }
153245803Stheraven
154245803Stheraven  return opt;
155245803Stheraven}
156245803Stheraven
157245803Stheraven
158245803Stheraven/* Print an option OPT nicely into a STRING allocated in POOL.
159245803Stheraven * If OPT has a single-character short form, then print OPT->name (if not
160245803Stheraven * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias.
161245803Stheraven * If DOC is set, include the generic documentation string of OPT,
162245803Stheraven * localized to the current locale if a translation is available.
163245803Stheraven */
164245803Stheravenstatic void
165245803Stheravenformat_option(const char **string,
166245803Stheraven              const apr_getopt_option_t *opt,
167245803Stheraven              const char *long_alias,
168245803Stheraven              svn_boolean_t doc,
169245803Stheraven              apr_pool_t *pool)
170245803Stheraven{
171245803Stheraven  char *opts;
172245803Stheraven
173245803Stheraven  if (opt == NULL)
174245803Stheraven    {
175245803Stheraven      *string = "?";
176245803Stheraven      return;
177245803Stheraven    }
178245803Stheraven
179245803Stheraven  /* We have a valid option which may or may not have a "short
180245803Stheraven     name" (a single-character alias for the long option). */
181245803Stheraven  if (opt->optch <= 255)
182245803Stheraven    opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name);
183245803Stheraven  else if (long_alias)
184245803Stheraven    opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias);
185245803Stheraven  else
186245803Stheraven    opts = apr_psprintf(pool, "--%s", opt->name);
187245803Stheraven
188245803Stheraven  if (opt->has_arg)
189245803Stheraven    opts = apr_pstrcat(pool, opts, _(" ARG"), (char *)NULL);
190245803Stheraven
191245803Stheraven  if (doc)
192245803Stheraven    opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description));
193245803Stheraven
194245803Stheraven  *string = opts;
195245803Stheraven}
196245803Stheraven
197245803Stheravenvoid
198245803Stheravensvn_opt_format_option(const char **string,
199245803Stheraven                      const apr_getopt_option_t *opt,
200245803Stheraven                      svn_boolean_t doc,
201245803Stheraven                      apr_pool_t *pool)
202245803Stheraven{
203245803Stheraven  format_option(string, opt, NULL, doc, pool);
204245803Stheraven}
205245803Stheraven
206245803Stheraven
207245803Stheravensvn_boolean_t
208245803Stheravensvn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command,
209245803Stheraven                                 int option_code,
210245803Stheraven                                 const int *global_options)
211245803Stheraven{
212245803Stheraven  apr_size_t i;
213245803Stheraven
214245803Stheraven  for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
215245803Stheraven    if (command->valid_options[i] == option_code)
216245803Stheraven      return TRUE;
217245803Stheraven
218245803Stheraven  if (global_options)
219245803Stheraven    for (i = 0; global_options[i]; i++)
220245803Stheraven      if (global_options[i] == option_code)
221245803Stheraven        return TRUE;
222245803Stheraven
223245803Stheraven  return FALSE;
224245803Stheraven}
225245803Stheraven
226245803Stheravensvn_boolean_t
227245803Stheravensvn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command,
228245803Stheraven                                 int option_code)
229245803Stheraven{
230245803Stheraven  return svn_opt_subcommand_takes_option3(command,
231245803Stheraven                                          option_code,
232245803Stheraven                                          NULL);
233245803Stheraven}
234245803Stheraven
235245803Stheraven
236245803Stheravensvn_boolean_t
237245803Stheravensvn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command,
238245803Stheraven                                int option_code)
239245803Stheraven{
240245803Stheraven  apr_size_t i;
241245803Stheraven
242245803Stheraven  for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
243245803Stheraven    if (command->valid_options[i] == option_code)
244245803Stheraven      return TRUE;
245245803Stheraven
246245803Stheraven  return FALSE;
247245803Stheraven}
248245803Stheraven
249245803Stheraven
250245803Stheraven/* Print the canonical command name for CMD, and all its aliases, to
251245803Stheraven   STREAM.  If HELP is set, print CMD's help string too, in which case
252245803Stheraven   obtain option usage from OPTIONS_TABLE. */
253245803Stheravenstatic svn_error_t *
254245803Stheravenprint_command_info2(const svn_opt_subcommand_desc2_t *cmd,
255245803Stheraven                    const apr_getopt_option_t *options_table,
256245803Stheraven                    const int *global_options,
257245803Stheraven                    svn_boolean_t help,
258245803Stheraven                    apr_pool_t *pool,
259245803Stheraven                    FILE *stream)
260245803Stheraven{
261245803Stheraven  svn_boolean_t first_time;
262245803Stheraven  apr_size_t i;
263245803Stheraven
264245803Stheraven  /* Print the canonical command name. */
265245803Stheraven  SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
266245803Stheraven
267245803Stheraven  /* Print the list of aliases. */
268245803Stheraven  first_time = TRUE;
269245803Stheraven  for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
270245803Stheraven    {
271245803Stheraven      if (cmd->aliases[i] == NULL)
272245803Stheraven        break;
273245803Stheraven
274245803Stheraven      if (first_time) {
275245803Stheraven        SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
276245803Stheraven        first_time = FALSE;
277245803Stheraven      }
278245803Stheraven      else
279245803Stheraven        SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
280245803Stheraven
281245803Stheraven      SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
282245803Stheraven    }
283245803Stheraven
284245803Stheraven  if (! first_time)
285245803Stheraven    SVN_ERR(svn_cmdline_fputs(")", stream, pool));
286245803Stheraven
287245803Stheraven  if (help)
288245803Stheraven    {
289245803Stheraven      const apr_getopt_option_t *option;
290245803Stheraven      const char *long_alias;
291245803Stheraven      svn_boolean_t have_options = FALSE;
292245803Stheraven
293245803Stheraven      SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help)));
294245803Stheraven
295245803Stheraven      /* Loop over all valid option codes attached to the subcommand */
296245803Stheraven      for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
297245803Stheraven        {
298245803Stheraven          if (cmd->valid_options[i])
299245803Stheraven            {
300245803Stheraven              if (!have_options)
301245803Stheraven                {
302245803Stheraven                  SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
303245803Stheraven                                            stream, pool));
304245803Stheraven                  have_options = TRUE;
305245803Stheraven                }
306245803Stheraven
307245803Stheraven              /* convert each option code into an option */
308245803Stheraven              option = get_option_from_code(&long_alias, cmd->valid_options[i],
309245803Stheraven                                            options_table, cmd, pool);
310245803Stheraven
311245803Stheraven              /* print the option's docstring */
312245803Stheraven              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
353void
354svn_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
401void
402svn_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
429/*** Parsing revision and date options. ***/
430
431
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 */
447static int
448revision_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. */
477static 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
550int
551svn_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
576int
577svn_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
594svn_error_t *
595svn_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
622const char *
623svn_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
650svn_opt_revision_range_t *
651svn_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
662svn_opt_revision_range_t *
663svn_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
678/*** Parsing arguments. ***/
679#define DEFAULT_ARRAY_SIZE 5
680
681
682/* Copy STR into POOL and push the copy onto ARRAY. */
683static void
684array_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
698void
699svn_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
708svn_error_t *
709svn_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
732svn_error_t *
733svn_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
754svn_error_t *
755svn_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. */
842svn_error_t *
843svn_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
952svn_error_t *
953svn_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
989svn_error_t *
990svn_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
1036svn_error_t *
1037svn_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
1074svn_error_t *
1075svn_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
1108svn_error_t *
1109svn_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
1192svn_error_t *
1193svn_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}
1241