1251881Speter/*
2251881Speter * opt.c :  option and argument parsing for Subversion command lines
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#define APR_WANT_STRFUNC
27251881Speter#include <apr_want.h>
28251881Speter
29251881Speter#include <stdio.h>
30251881Speter#include <string.h>
31251881Speter#include <assert.h>
32251881Speter#include <apr_pools.h>
33251881Speter#include <apr_general.h>
34251881Speter#include <apr_lib.h>
35251881Speter#include <apr_file_info.h>
36251881Speter
37251881Speter#include "svn_hash.h"
38251881Speter#include "svn_cmdline.h"
39251881Speter#include "svn_version.h"
40251881Speter#include "svn_types.h"
41251881Speter#include "svn_opt.h"
42251881Speter#include "svn_error.h"
43251881Speter#include "svn_dirent_uri.h"
44251881Speter#include "svn_path.h"
45251881Speter#include "svn_utf.h"
46251881Speter#include "svn_time.h"
47251881Speter#include "svn_props.h"
48251881Speter#include "svn_ctype.h"
49251881Speter
50251881Speter#include "private/svn_opt_private.h"
51251881Speter
52251881Speter#include "opt.h"
53251881Speter#include "svn_private_config.h"
54251881Speter
55251881Speter
56251881Speter/*** Code. ***/
57251881Speter
58251881Speterconst svn_opt_subcommand_desc2_t *
59251881Spetersvn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table,
60251881Speter                                  const char *cmd_name)
61251881Speter{
62251881Speter  int i = 0;
63251881Speter
64251881Speter  if (cmd_name == NULL)
65251881Speter    return NULL;
66251881Speter
67251881Speter  while (table[i].name) {
68251881Speter    int j;
69251881Speter    if (strcmp(cmd_name, table[i].name) == 0)
70251881Speter      return table + i;
71251881Speter    for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++)
72251881Speter      if (strcmp(cmd_name, table[i].aliases[j]) == 0)
73251881Speter        return table + i;
74251881Speter
75251881Speter    i++;
76251881Speter  }
77251881Speter
78251881Speter  /* If we get here, there was no matching subcommand name or alias. */
79251881Speter  return NULL;
80251881Speter}
81251881Speter
82251881Speterconst apr_getopt_option_t *
83251881Spetersvn_opt_get_option_from_code2(int code,
84251881Speter                              const apr_getopt_option_t *option_table,
85251881Speter                              const svn_opt_subcommand_desc2_t *command,
86251881Speter                              apr_pool_t *pool)
87251881Speter{
88251881Speter  apr_size_t i;
89251881Speter
90251881Speter  for (i = 0; option_table[i].optch; i++)
91251881Speter    if (option_table[i].optch == code)
92251881Speter      {
93251881Speter        if (command)
94251881Speter          {
95251881Speter            int j;
96251881Speter
97251881Speter            for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) &&
98251881Speter                         command->desc_overrides[j].optch); j++)
99251881Speter              if (command->desc_overrides[j].optch == code)
100251881Speter                {
101251881Speter                  apr_getopt_option_t *tmpopt =
102251881Speter                      apr_palloc(pool, sizeof(*tmpopt));
103251881Speter                  *tmpopt = option_table[i];
104251881Speter                  tmpopt->description = command->desc_overrides[j].desc;
105251881Speter                  return tmpopt;
106251881Speter                }
107251881Speter          }
108251881Speter        return &(option_table[i]);
109251881Speter      }
110251881Speter
111251881Speter  return NULL;
112251881Speter}
113251881Speter
114251881Speter
115251881Speterconst apr_getopt_option_t *
116251881Spetersvn_opt_get_option_from_code(int code,
117251881Speter                             const apr_getopt_option_t *option_table)
118251881Speter{
119251881Speter  apr_size_t i;
120251881Speter
121251881Speter  for (i = 0; option_table[i].optch; i++)
122251881Speter    if (option_table[i].optch == code)
123251881Speter      return &(option_table[i]);
124251881Speter
125251881Speter  return NULL;
126251881Speter}
127251881Speter
128251881Speter
129251881Speter/* Like svn_opt_get_option_from_code2(), but also, if CODE appears a second
130251881Speter * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that
131251881Speter * second name, else set it to NULL. */
132251881Speterstatic const apr_getopt_option_t *
133251881Speterget_option_from_code(const char **long_alias,
134251881Speter                     int code,
135251881Speter                     const apr_getopt_option_t *option_table,
136251881Speter                     const svn_opt_subcommand_desc2_t *command,
137251881Speter                     apr_pool_t *pool)
138251881Speter{
139251881Speter  const apr_getopt_option_t *i;
140251881Speter  const apr_getopt_option_t *opt
141251881Speter    = svn_opt_get_option_from_code2(code, option_table, command, pool);
142251881Speter
143251881Speter  /* Find a long alias in the table, if there is one. */
144251881Speter  *long_alias = NULL;
145251881Speter  for (i = option_table; i->optch; i++)
146251881Speter    {
147251881Speter      if (i->optch == code && i->name != opt->name)
148251881Speter        {
149251881Speter          *long_alias = i->name;
150251881Speter          break;
151251881Speter        }
152251881Speter    }
153251881Speter
154251881Speter  return opt;
155251881Speter}
156251881Speter
157251881Speter
158251881Speter/* Print an option OPT nicely into a STRING allocated in POOL.
159251881Speter * If OPT has a single-character short form, then print OPT->name (if not
160251881Speter * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias.
161251881Speter * If DOC is set, include the generic documentation string of OPT,
162251881Speter * localized to the current locale if a translation is available.
163251881Speter */
164251881Speterstatic void
165251881Speterformat_option(const char **string,
166251881Speter              const apr_getopt_option_t *opt,
167251881Speter              const char *long_alias,
168251881Speter              svn_boolean_t doc,
169251881Speter              apr_pool_t *pool)
170251881Speter{
171251881Speter  char *opts;
172251881Speter
173251881Speter  if (opt == NULL)
174251881Speter    {
175251881Speter      *string = "?";
176251881Speter      return;
177251881Speter    }
178251881Speter
179251881Speter  /* We have a valid option which may or may not have a "short
180251881Speter     name" (a single-character alias for the long option). */
181251881Speter  if (opt->optch <= 255)
182251881Speter    opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name);
183251881Speter  else if (long_alias)
184251881Speter    opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias);
185251881Speter  else
186251881Speter    opts = apr_psprintf(pool, "--%s", opt->name);
187251881Speter
188251881Speter  if (opt->has_arg)
189299742Sdim    opts = apr_pstrcat(pool, opts, _(" ARG"), SVN_VA_NULL);
190251881Speter
191251881Speter  if (doc)
192251881Speter    opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description));
193251881Speter
194251881Speter  *string = opts;
195251881Speter}
196251881Speter
197251881Spetervoid
198251881Spetersvn_opt_format_option(const char **string,
199251881Speter                      const apr_getopt_option_t *opt,
200251881Speter                      svn_boolean_t doc,
201251881Speter                      apr_pool_t *pool)
202251881Speter{
203251881Speter  format_option(string, opt, NULL, doc, pool);
204251881Speter}
205251881Speter
206251881Speter
207251881Spetersvn_boolean_t
208251881Spetersvn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command,
209251881Speter                                 int option_code,
210251881Speter                                 const int *global_options)
211251881Speter{
212251881Speter  apr_size_t i;
213251881Speter
214251881Speter  for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
215251881Speter    if (command->valid_options[i] == option_code)
216251881Speter      return TRUE;
217251881Speter
218251881Speter  if (global_options)
219251881Speter    for (i = 0; global_options[i]; i++)
220251881Speter      if (global_options[i] == option_code)
221251881Speter        return TRUE;
222251881Speter
223251881Speter  return FALSE;
224251881Speter}
225251881Speter
226251881Spetersvn_boolean_t
227251881Spetersvn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command,
228251881Speter                                 int option_code)
229251881Speter{
230251881Speter  return svn_opt_subcommand_takes_option3(command,
231251881Speter                                          option_code,
232251881Speter                                          NULL);
233251881Speter}
234251881Speter
235251881Speter
236251881Spetersvn_boolean_t
237251881Spetersvn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command,
238251881Speter                                int option_code)
239251881Speter{
240251881Speter  apr_size_t i;
241251881Speter
242251881Speter  for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
243251881Speter    if (command->valid_options[i] == option_code)
244251881Speter      return TRUE;
245251881Speter
246251881Speter  return FALSE;
247251881Speter}
248251881Speter
249251881Speter
250251881Speter/* Print the canonical command name for CMD, and all its aliases, to
251251881Speter   STREAM.  If HELP is set, print CMD's help string too, in which case
252251881Speter   obtain option usage from OPTIONS_TABLE. */
253251881Speterstatic svn_error_t *
254251881Speterprint_command_info2(const svn_opt_subcommand_desc2_t *cmd,
255251881Speter                    const apr_getopt_option_t *options_table,
256251881Speter                    const int *global_options,
257251881Speter                    svn_boolean_t help,
258251881Speter                    apr_pool_t *pool,
259251881Speter                    FILE *stream)
260251881Speter{
261251881Speter  svn_boolean_t first_time;
262251881Speter  apr_size_t i;
263251881Speter
264251881Speter  /* Print the canonical command name. */
265251881Speter  SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool));
266251881Speter
267251881Speter  /* Print the list of aliases. */
268251881Speter  first_time = TRUE;
269251881Speter  for (i = 0; i < SVN_OPT_MAX_ALIASES; i++)
270251881Speter    {
271251881Speter      if (cmd->aliases[i] == NULL)
272251881Speter        break;
273251881Speter
274251881Speter      if (first_time) {
275251881Speter        SVN_ERR(svn_cmdline_fputs(" (", stream, pool));
276251881Speter        first_time = FALSE;
277251881Speter      }
278251881Speter      else
279251881Speter        SVN_ERR(svn_cmdline_fputs(", ", stream, pool));
280251881Speter
281251881Speter      SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool));
282251881Speter    }
283251881Speter
284251881Speter  if (! first_time)
285251881Speter    SVN_ERR(svn_cmdline_fputs(")", stream, pool));
286251881Speter
287251881Speter  if (help)
288251881Speter    {
289251881Speter      const apr_getopt_option_t *option;
290251881Speter      const char *long_alias;
291251881Speter      svn_boolean_t have_options = FALSE;
292251881Speter
293251881Speter      SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help)));
294251881Speter
295251881Speter      /* Loop over all valid option codes attached to the subcommand */
296251881Speter      for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++)
297251881Speter        {
298251881Speter          if (cmd->valid_options[i])
299251881Speter            {
300251881Speter              if (!have_options)
301251881Speter                {
302251881Speter                  SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"),
303251881Speter                                            stream, pool));
304251881Speter                  have_options = TRUE;
305251881Speter                }
306251881Speter
307251881Speter              /* convert each option code into an option */
308251881Speter              option = get_option_from_code(&long_alias, cmd->valid_options[i],
309251881Speter                                            options_table, cmd, pool);
310251881Speter
311251881Speter              /* print the option's docstring */
312251881Speter              if (option && option->description)
313251881Speter                {
314251881Speter                  const char *optstr;
315251881Speter                  format_option(&optstr, option, long_alias, TRUE, pool);
316251881Speter                  SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
317251881Speter                                              optstr));
318251881Speter                }
319251881Speter            }
320251881Speter        }
321251881Speter      /* And global options too */
322251881Speter      if (global_options && *global_options)
323251881Speter        {
324251881Speter          SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"),
325251881Speter                                    stream, pool));
326251881Speter          have_options = TRUE;
327251881Speter
328251881Speter          for (i = 0; global_options[i]; i++)
329251881Speter            {
330251881Speter
331251881Speter              /* convert each option code into an option */
332251881Speter              option = get_option_from_code(&long_alias, global_options[i],
333251881Speter                                            options_table, cmd, pool);
334251881Speter
335251881Speter              /* print the option's docstring */
336251881Speter              if (option && option->description)
337251881Speter                {
338251881Speter                  const char *optstr;
339251881Speter                  format_option(&optstr, option, long_alias, TRUE, pool);
340251881Speter                  SVN_ERR(svn_cmdline_fprintf(stream, pool, "  %s\n",
341251881Speter                                              optstr));
342251881Speter                }
343251881Speter            }
344251881Speter        }
345251881Speter
346251881Speter      if (have_options)
347251881Speter        SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n"));
348251881Speter    }
349251881Speter
350251881Speter  return SVN_NO_ERROR;
351251881Speter}
352251881Speter
353251881Spetervoid
354251881Spetersvn_opt_print_generic_help2(const char *header,
355251881Speter                            const svn_opt_subcommand_desc2_t *cmd_table,
356251881Speter                            const apr_getopt_option_t *opt_table,
357251881Speter                            const char *footer,
358251881Speter                            apr_pool_t *pool, FILE *stream)
359251881Speter{
360251881Speter  int i = 0;
361251881Speter  svn_error_t *err;
362251881Speter
363251881Speter  if (header)
364251881Speter    if ((err = svn_cmdline_fputs(header, stream, pool)))
365251881Speter      goto print_error;
366251881Speter
367251881Speter  while (cmd_table[i].name)
368251881Speter    {
369251881Speter      if ((err = svn_cmdline_fputs("   ", stream, pool))
370251881Speter          || (err = print_command_info2(cmd_table + i, opt_table,
371251881Speter                                        NULL, FALSE,
372251881Speter                                        pool, stream))
373251881Speter          || (err = svn_cmdline_fputs("\n", stream, pool)))
374251881Speter        goto print_error;
375251881Speter      i++;
376251881Speter    }
377251881Speter
378251881Speter  if ((err = svn_cmdline_fputs("\n", stream, pool)))
379251881Speter    goto print_error;
380251881Speter
381251881Speter  if (footer)
382251881Speter    if ((err = svn_cmdline_fputs(footer, stream, pool)))
383251881Speter      goto print_error;
384251881Speter
385251881Speter  return;
386251881Speter
387251881Speter print_error:
388251881Speter  /* Issue #3014:
389251881Speter   * Don't print anything on broken pipes. The pipe was likely
390251881Speter   * closed by the process at the other end. We expect that
391251881Speter   * process to perform error reporting as necessary.
392251881Speter   *
393251881Speter   * ### This assumes that there is only one error in a chain for
394251881Speter   * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */
395251881Speter  if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
396251881Speter    svn_handle_error2(err, stderr, FALSE, "svn: ");
397251881Speter  svn_error_clear(err);
398251881Speter}
399251881Speter
400251881Speter
401251881Spetervoid
402251881Spetersvn_opt_subcommand_help3(const char *subcommand,
403251881Speter                         const svn_opt_subcommand_desc2_t *table,
404251881Speter                         const apr_getopt_option_t *options_table,
405251881Speter                         const int *global_options,
406251881Speter                         apr_pool_t *pool)
407251881Speter{
408251881Speter  const svn_opt_subcommand_desc2_t *cmd =
409251881Speter    svn_opt_get_canonical_subcommand2(table, subcommand);
410251881Speter  svn_error_t *err;
411251881Speter
412251881Speter  if (cmd)
413251881Speter    err = print_command_info2(cmd, options_table, global_options,
414251881Speter                              TRUE, pool, stdout);
415251881Speter  else
416251881Speter    err = svn_cmdline_fprintf(stderr, pool,
417251881Speter                              _("\"%s\": unknown command.\n\n"), subcommand);
418251881Speter
419251881Speter  if (err) {
420269847Speter    /* Issue #3014: Don't print anything on broken pipes. */
421269847Speter    if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR)
422269847Speter      svn_handle_error2(err, stderr, FALSE, "svn: ");
423251881Speter    svn_error_clear(err);
424251881Speter  }
425251881Speter}
426251881Speter
427251881Speter
428251881Speter
429251881Speter/*** Parsing revision and date options. ***/
430251881Speter
431251881Speter
432251881Speter/** Parsing "X:Y"-style arguments. **/
433251881Speter
434251881Speter/* If WORD matches one of the special revision descriptors,
435251881Speter * case-insensitively, set *REVISION accordingly:
436251881Speter *
437251881Speter *   - For "head", set REVISION->kind to svn_opt_revision_head.
438251881Speter *
439251881Speter *   - For "prev", set REVISION->kind to svn_opt_revision_previous.
440251881Speter *
441251881Speter *   - For "base", set REVISION->kind to svn_opt_revision_base.
442251881Speter *
443251881Speter *   - For "committed", set REVISION->kind to svn_opt_revision_committed.
444251881Speter *
445251881Speter * If match, return 0, else return -1 and don't touch REVISION.
446251881Speter */
447251881Speterstatic int
448251881Speterrevision_from_word(svn_opt_revision_t *revision, const char *word)
449251881Speter{
450251881Speter  if (svn_cstring_casecmp(word, "head") == 0)
451251881Speter    {
452251881Speter      revision->kind = svn_opt_revision_head;
453251881Speter    }
454251881Speter  else if (svn_cstring_casecmp(word, "prev") == 0)
455251881Speter    {
456251881Speter      revision->kind = svn_opt_revision_previous;
457251881Speter    }
458251881Speter  else if (svn_cstring_casecmp(word, "base") == 0)
459251881Speter    {
460251881Speter      revision->kind = svn_opt_revision_base;
461251881Speter    }
462251881Speter  else if (svn_cstring_casecmp(word, "committed") == 0)
463251881Speter    {
464251881Speter      revision->kind = svn_opt_revision_committed;
465251881Speter    }
466251881Speter  else
467251881Speter    return -1;
468251881Speter
469251881Speter  return 0;
470251881Speter}
471251881Speter
472251881Speter
473251881Speter/* Parse one revision specification.  Return pointer to character
474251881Speter   after revision, or NULL if the revision is invalid.  Modifies
475251881Speter   str, so make sure to pass a copy of anything precious.  Uses
476251881Speter   POOL for temporary allocation. */
477251881Speterstatic char *parse_one_rev(svn_opt_revision_t *revision, char *str,
478251881Speter                           apr_pool_t *pool)
479251881Speter{
480251881Speter  char *end, save;
481251881Speter
482251881Speter  /* Allow any number of 'r's to prefix a revision number, because
483251881Speter     that way if a script pastes svn output into another svn command
484251881Speter     (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work,
485251881Speter     even when compounded.
486251881Speter
487251881Speter     As it happens, none of our special revision words begins with
488251881Speter     "r".  If any ever do, then this code will have to get smarter.
489251881Speter
490251881Speter     Incidentally, this allows "r{DATE}".  We could avoid that with
491251881Speter     some trivial code rearrangement, but it's not clear what would
492251881Speter     be gained by doing so. */
493251881Speter  while (*str == 'r')
494251881Speter    str++;
495251881Speter
496251881Speter  if (*str == '{')
497251881Speter    {
498251881Speter      svn_boolean_t matched;
499251881Speter      apr_time_t tm;
500251881Speter      svn_error_t *err;
501251881Speter
502251881Speter      /* Brackets denote a date. */
503251881Speter      str++;
504251881Speter      end = strchr(str, '}');
505251881Speter      if (!end)
506251881Speter        return NULL;
507251881Speter      *end = '\0';
508251881Speter      err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool);
509251881Speter      if (err)
510251881Speter        {
511251881Speter          svn_error_clear(err);
512251881Speter          return NULL;
513251881Speter        }
514251881Speter      if (!matched)
515251881Speter        return NULL;
516251881Speter      revision->kind = svn_opt_revision_date;
517251881Speter      revision->value.date = tm;
518251881Speter      return end + 1;
519251881Speter    }
520251881Speter  else if (svn_ctype_isdigit(*str))
521251881Speter    {
522251881Speter      /* It's a number. */
523251881Speter      end = str + 1;
524251881Speter      while (svn_ctype_isdigit(*end))
525251881Speter        end++;
526251881Speter      save = *end;
527251881Speter      *end = '\0';
528251881Speter      revision->kind = svn_opt_revision_number;
529251881Speter      revision->value.number = SVN_STR_TO_REV(str);
530251881Speter      *end = save;
531251881Speter      return end;
532251881Speter    }
533251881Speter  else if (svn_ctype_isalpha(*str))
534251881Speter    {
535251881Speter      end = str + 1;
536251881Speter      while (svn_ctype_isalpha(*end))
537251881Speter        end++;
538251881Speter      save = *end;
539251881Speter      *end = '\0';
540251881Speter      if (revision_from_word(revision, str) != 0)
541251881Speter        return NULL;
542251881Speter      *end = save;
543251881Speter      return end;
544251881Speter    }
545251881Speter  else
546251881Speter    return NULL;
547251881Speter}
548251881Speter
549251881Speter
550251881Speterint
551251881Spetersvn_opt_parse_revision(svn_opt_revision_t *start_revision,
552251881Speter                       svn_opt_revision_t *end_revision,
553251881Speter                       const char *arg,
554251881Speter                       apr_pool_t *pool)
555251881Speter{
556251881Speter  char *left_rev, *right_rev, *end;
557251881Speter
558251881Speter  /* Operate on a copy of the argument. */
559251881Speter  left_rev = apr_pstrdup(pool, arg);
560251881Speter
561251881Speter  right_rev = parse_one_rev(start_revision, left_rev, pool);
562251881Speter  if (right_rev && *right_rev == ':')
563251881Speter    {
564251881Speter      right_rev++;
565251881Speter      end = parse_one_rev(end_revision, right_rev, pool);
566251881Speter      if (!end || *end != '\0')
567251881Speter        return -1;
568251881Speter    }
569251881Speter  else if (!right_rev || *right_rev != '\0')
570251881Speter    return -1;
571251881Speter
572251881Speter  return 0;
573251881Speter}
574251881Speter
575251881Speter
576251881Speterint
577251881Spetersvn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges,
578251881Speter                                const char *arg,
579251881Speter                                apr_pool_t *pool)
580251881Speter{
581251881Speter  svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
582251881Speter
583251881Speter  range->start.kind = svn_opt_revision_unspecified;
584251881Speter  range->end.kind = svn_opt_revision_unspecified;
585251881Speter
586251881Speter  if (svn_opt_parse_revision(&(range->start), &(range->end),
587251881Speter                             arg, pool) == -1)
588251881Speter    return -1;
589251881Speter
590251881Speter  APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range;
591251881Speter  return 0;
592251881Speter}
593251881Speter
594251881Spetersvn_error_t *
595251881Spetersvn_opt_resolve_revisions(svn_opt_revision_t *peg_rev,
596251881Speter                          svn_opt_revision_t *op_rev,
597251881Speter                          svn_boolean_t is_url,
598251881Speter                          svn_boolean_t notice_local_mods,
599251881Speter                          apr_pool_t *pool)
600251881Speter{
601251881Speter  if (peg_rev->kind == svn_opt_revision_unspecified)
602251881Speter    {
603251881Speter      if (is_url)
604251881Speter        {
605251881Speter          peg_rev->kind = svn_opt_revision_head;
606251881Speter        }
607251881Speter      else
608251881Speter        {
609251881Speter          if (notice_local_mods)
610251881Speter            peg_rev->kind = svn_opt_revision_working;
611251881Speter          else
612251881Speter            peg_rev->kind = svn_opt_revision_base;
613251881Speter        }
614251881Speter    }
615251881Speter
616251881Speter  if (op_rev->kind == svn_opt_revision_unspecified)
617251881Speter    *op_rev = *peg_rev;
618251881Speter
619251881Speter  return SVN_NO_ERROR;
620251881Speter}
621251881Speter
622251881Speterconst char *
623251881Spetersvn_opt__revision_to_string(const svn_opt_revision_t *revision,
624251881Speter                            apr_pool_t *result_pool)
625251881Speter{
626251881Speter  switch (revision->kind)
627251881Speter    {
628251881Speter      case svn_opt_revision_unspecified:
629251881Speter        return "unspecified";
630251881Speter      case svn_opt_revision_number:
631251881Speter        return apr_psprintf(result_pool, "%ld", revision->value.number);
632251881Speter      case svn_opt_revision_date:
633251881Speter        /* ### svn_time_to_human_cstring()? */
634251881Speter        return svn_time_to_cstring(revision->value.date, result_pool);
635251881Speter      case svn_opt_revision_committed:
636251881Speter        return "committed";
637251881Speter      case svn_opt_revision_previous:
638251881Speter        return "previous";
639251881Speter      case svn_opt_revision_base:
640251881Speter        return "base";
641251881Speter      case svn_opt_revision_working:
642251881Speter        return "working";
643251881Speter      case svn_opt_revision_head:
644251881Speter        return "head";
645251881Speter      default:
646251881Speter        return NULL;
647251881Speter    }
648251881Speter}
649251881Speter
650251881Spetersvn_opt_revision_range_t *
651251881Spetersvn_opt__revision_range_create(const svn_opt_revision_t *start_revision,
652251881Speter                               const svn_opt_revision_t *end_revision,
653251881Speter                               apr_pool_t *result_pool)
654251881Speter{
655251881Speter  svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
656251881Speter
657251881Speter  range->start = *start_revision;
658251881Speter  range->end = *end_revision;
659251881Speter  return range;
660251881Speter}
661251881Speter
662251881Spetersvn_opt_revision_range_t *
663251881Spetersvn_opt__revision_range_from_revnums(svn_revnum_t start_revnum,
664251881Speter                                     svn_revnum_t end_revnum,
665251881Speter                                     apr_pool_t *result_pool)
666251881Speter{
667251881Speter  svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range));
668251881Speter
669251881Speter  range->start.kind = svn_opt_revision_number;
670251881Speter  range->start.value.number = start_revnum;
671251881Speter  range->end.kind = svn_opt_revision_number;
672251881Speter  range->end.value.number = end_revnum;
673251881Speter  return range;
674251881Speter}
675251881Speter
676251881Speter
677251881Speter
678251881Speter/*** Parsing arguments. ***/
679251881Speter#define DEFAULT_ARRAY_SIZE 5
680251881Speter
681251881Speter
682251881Speter/* Copy STR into POOL and push the copy onto ARRAY. */
683251881Speterstatic void
684251881Speterarray_push_str(apr_array_header_t *array,
685251881Speter               const char *str,
686251881Speter               apr_pool_t *pool)
687251881Speter{
688251881Speter  /* ### Not sure if this function is still necessary.  It used to
689251881Speter     convert str to svn_stringbuf_t * and push it, but now it just
690251881Speter     dups str in pool and pushes the copy.  So its only effect is
691251881Speter     transfer str's lifetime to pool.  Is that something callers are
692251881Speter     depending on? */
693251881Speter
694251881Speter  APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str);
695251881Speter}
696251881Speter
697251881Speter
698251881Spetervoid
699251881Spetersvn_opt_push_implicit_dot_target(apr_array_header_t *targets,
700251881Speter                                 apr_pool_t *pool)
701251881Speter{
702251881Speter  if (targets->nelts == 0)
703251881Speter    APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */
704251881Speter  assert(targets->nelts);
705251881Speter}
706251881Speter
707251881Speter
708251881Spetersvn_error_t *
709251881Spetersvn_opt_parse_num_args(apr_array_header_t **args_p,
710251881Speter                       apr_getopt_t *os,
711251881Speter                       int num_args,
712251881Speter                       apr_pool_t *pool)
713251881Speter{
714251881Speter  int i;
715251881Speter  apr_array_header_t *args
716251881Speter    = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
717251881Speter
718251881Speter  /* loop for num_args and add each arg to the args array */
719251881Speter  for (i = 0; i < num_args; i++)
720251881Speter    {
721251881Speter      if (os->ind >= os->argc)
722251881Speter        {
723251881Speter          return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL);
724251881Speter        }
725251881Speter      array_push_str(args, os->argv[os->ind++], pool);
726251881Speter    }
727251881Speter
728251881Speter  *args_p = args;
729251881Speter  return SVN_NO_ERROR;
730251881Speter}
731251881Speter
732251881Spetersvn_error_t *
733251881Spetersvn_opt_parse_all_args(apr_array_header_t **args_p,
734251881Speter                       apr_getopt_t *os,
735251881Speter                       apr_pool_t *pool)
736251881Speter{
737251881Speter  apr_array_header_t *args
738251881Speter    = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
739251881Speter
740251881Speter  if (os->ind > os->argc)
741251881Speter    {
742251881Speter      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL);
743251881Speter    }
744251881Speter  while (os->ind < os->argc)
745251881Speter    {
746251881Speter      array_push_str(args, os->argv[os->ind++], pool);
747251881Speter    }
748251881Speter
749251881Speter  *args_p = args;
750251881Speter  return SVN_NO_ERROR;
751251881Speter}
752251881Speter
753251881Speter
754251881Spetersvn_error_t *
755251881Spetersvn_opt_parse_path(svn_opt_revision_t *rev,
756251881Speter                   const char **truepath,
757251881Speter                   const char *path /* UTF-8! */,
758251881Speter                   apr_pool_t *pool)
759251881Speter{
760251881Speter  const char *peg_rev;
761251881Speter
762251881Speter  SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool));
763251881Speter
764251881Speter  /* Parse the peg revision, if one was found */
765251881Speter  if (strlen(peg_rev))
766251881Speter    {
767251881Speter      int ret;
768251881Speter      svn_opt_revision_t start_revision, end_revision;
769251881Speter
770251881Speter      end_revision.kind = svn_opt_revision_unspecified;
771251881Speter
772251881Speter      if (peg_rev[1] == '\0')  /* looking at empty peg revision */
773251881Speter        {
774251881Speter          ret = 0;
775251881Speter          start_revision.kind = svn_opt_revision_unspecified;
776251881Speter          start_revision.value.number = 0;
777251881Speter        }
778251881Speter      else  /* looking at non-empty peg revision */
779251881Speter        {
780251881Speter          const char *rev_str = &peg_rev[1];
781251881Speter
782251881Speter          /* URLs get treated differently from wc paths. */
783251881Speter          if (svn_path_is_url(path))
784251881Speter            {
785251881Speter              /* URLs are URI-encoded, so we look for dates with
786299742Sdim                 URI-encoded delimiters.  */
787251881Speter              size_t rev_len = strlen(rev_str);
788251881Speter              if (rev_len > 6
789251881Speter                  && rev_str[0] == '%'
790251881Speter                  && rev_str[1] == '7'
791251881Speter                  && (rev_str[2] == 'B'
792251881Speter                      || rev_str[2] == 'b')
793251881Speter                  && rev_str[rev_len-3] == '%'
794251881Speter                  && rev_str[rev_len-2] == '7'
795251881Speter                  && (rev_str[rev_len-1] == 'D'
796251881Speter                      || rev_str[rev_len-1] == 'd'))
797251881Speter                {
798251881Speter                  rev_str = svn_path_uri_decode(rev_str, pool);
799251881Speter                }
800251881Speter            }
801251881Speter          ret = svn_opt_parse_revision(&start_revision,
802251881Speter                                       &end_revision,
803251881Speter                                       rev_str, pool);
804251881Speter        }
805251881Speter
806251881Speter      if (ret || end_revision.kind != svn_opt_revision_unspecified)
807251881Speter        {
808251881Speter          /* If an svn+ssh URL was used and it contains only one @,
809251881Speter           * provide an error message that presents a possible solution
810251881Speter           * to the parsing error (issue #2349). */
811251881Speter          if (strncmp(path, "svn+ssh://", 10) == 0)
812251881Speter            {
813251881Speter              const char *at;
814251881Speter
815251881Speter              at = strchr(path, '@');
816251881Speter              if (at && strrchr(path, '@') == at)
817251881Speter                return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
818251881Speter                                         _("Syntax error parsing peg revision "
819251881Speter                                           "'%s'; did you mean '%s@'?"),
820251881Speter                                       &peg_rev[1], path);
821251881Speter            }
822251881Speter
823251881Speter          return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
824251881Speter                                   _("Syntax error parsing peg revision '%s'"),
825251881Speter                                   &peg_rev[1]);
826251881Speter        }
827251881Speter      rev->kind = start_revision.kind;
828251881Speter      rev->value = start_revision.value;
829251881Speter    }
830251881Speter  else
831251881Speter    {
832251881Speter      /* Didn't find a peg revision. */
833251881Speter      rev->kind = svn_opt_revision_unspecified;
834251881Speter    }
835251881Speter
836251881Speter  return SVN_NO_ERROR;
837251881Speter}
838251881Speter
839251881Speter
840251881Speter/* Note: This is substantially copied into svn_client_args_to_target_array() in
841251881Speter * order to move to libsvn_client while maintaining backward compatibility. */
842251881Spetersvn_error_t *
843251881Spetersvn_opt__args_to_target_array(apr_array_header_t **targets_p,
844251881Speter                              apr_getopt_t *os,
845251881Speter                              const apr_array_header_t *known_targets,
846251881Speter                              apr_pool_t *pool)
847251881Speter{
848251881Speter  int i;
849251881Speter  svn_error_t *err = SVN_NO_ERROR;
850251881Speter  apr_array_header_t *input_targets =
851251881Speter    apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
852251881Speter  apr_array_header_t *output_targets =
853251881Speter    apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *));
854251881Speter
855251881Speter  /* Step 1:  create a master array of targets that are in UTF-8
856251881Speter     encoding, and come from concatenating the targets left by apr_getopt,
857251881Speter     plus any extra targets (e.g., from the --targets switch.) */
858251881Speter
859251881Speter  for (; os->ind < os->argc; os->ind++)
860251881Speter    {
861251881Speter      /* The apr_getopt targets are still in native encoding. */
862251881Speter      const char *raw_target = os->argv[os->ind];
863251881Speter      SVN_ERR(svn_utf_cstring_to_utf8
864251881Speter              ((const char **) apr_array_push(input_targets),
865251881Speter               raw_target, pool));
866251881Speter    }
867251881Speter
868251881Speter  if (known_targets)
869251881Speter    {
870251881Speter      for (i = 0; i < known_targets->nelts; i++)
871251881Speter        {
872251881Speter          /* The --targets array have already been converted to UTF-8,
873251881Speter             because we needed to split up the list with svn_cstring_split. */
874251881Speter          const char *utf8_target = APR_ARRAY_IDX(known_targets,
875251881Speter                                                  i, const char *);
876251881Speter          APR_ARRAY_PUSH(input_targets, const char *) = utf8_target;
877251881Speter        }
878251881Speter    }
879251881Speter
880251881Speter  /* Step 2:  process each target.  */
881251881Speter
882251881Speter  for (i = 0; i < input_targets->nelts; i++)
883251881Speter    {
884251881Speter      const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *);
885251881Speter      const char *true_target;
886251881Speter      const char *target;      /* after all processing is finished */
887251881Speter      const char *peg_rev;
888251881Speter
889251881Speter      /*
890251881Speter       * This is needed so that the target can be properly canonicalized,
891251881Speter       * otherwise the canonicalization does not treat a ".@BASE" as a "."
892251881Speter       * with a BASE peg revision, and it is not canonicalized to "@BASE".
893251881Speter       * If any peg revision exists, it is appended to the final
894251881Speter       * canonicalized path or URL.  Do not use svn_opt_parse_path()
895251881Speter       * because the resulting peg revision is a structure that would have
896251881Speter       * to be converted back into a string.  Converting from a string date
897251881Speter       * to the apr_time_t field in the svn_opt_revision_value_t and back to
898251881Speter       * a string would not necessarily preserve the exact bytes of the
899251881Speter       * input date, so its easier just to keep it in string form.
900251881Speter       */
901251881Speter      SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev,
902251881Speter                                                 utf8_target, pool));
903251881Speter
904251881Speter      /* URLs and wc-paths get treated differently. */
905251881Speter      if (svn_path_is_url(true_target))
906251881Speter        {
907251881Speter          SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target,
908251881Speter                                                 pool));
909251881Speter        }
910251881Speter      else  /* not a url, so treat as a path */
911251881Speter        {
912251881Speter          const char *base_name;
913251881Speter
914251881Speter          SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target,
915251881Speter                                                 pool));
916251881Speter
917251881Speter          /* If the target has the same name as a Subversion
918251881Speter             working copy administrative dir, skip it. */
919251881Speter          base_name = svn_dirent_basename(true_target, pool);
920251881Speter
921251881Speter          /* FIXME:
922251881Speter             The canonical list of administrative directory names is
923251881Speter             maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir().
924251881Speter             That list can't be used here, because that use would
925251881Speter             create a circular dependency between libsvn_wc and
926251881Speter             libsvn_subr.  Make sure changes to the lists are always
927251881Speter             synchronized! */
928251881Speter          if (0 == strcmp(base_name, ".svn")
929251881Speter              || 0 == strcmp(base_name, "_svn"))
930251881Speter            {
931251881Speter              err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED,
932251881Speter                                      err, _("'%s' ends in a reserved name"),
933251881Speter                                      utf8_target);
934251881Speter              continue;
935251881Speter            }
936251881Speter        }
937251881Speter
938299742Sdim      target = apr_pstrcat(pool, true_target, peg_rev, SVN_VA_NULL);
939251881Speter
940251881Speter      APR_ARRAY_PUSH(output_targets, const char *) = target;
941251881Speter    }
942251881Speter
943251881Speter
944251881Speter  /* kff todo: need to remove redundancies from targets before
945251881Speter     passing it to the cmd_func. */
946251881Speter
947251881Speter  *targets_p = output_targets;
948251881Speter
949251881Speter  return err;
950251881Speter}
951251881Speter
952251881Spetersvn_error_t *
953251881Spetersvn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec,
954251881Speter                      apr_pool_t *pool)
955251881Speter{
956251881Speter  const char *sep, *propname;
957251881Speter  svn_string_t *propval;
958251881Speter
959251881Speter  if (! *revprop_spec)
960251881Speter    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
961251881Speter                            _("Revision property pair is empty"));
962251881Speter
963251881Speter  if (! *revprop_table_p)
964251881Speter    *revprop_table_p = apr_hash_make(pool);
965251881Speter
966251881Speter  sep = strchr(revprop_spec, '=');
967251881Speter  if (sep)
968251881Speter    {
969251881Speter      propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec);
970251881Speter      SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool));
971251881Speter      propval = svn_string_create(sep + 1, pool);
972251881Speter    }
973251881Speter  else
974251881Speter    {
975251881Speter      SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool));
976251881Speter      propval = svn_string_create_empty(pool);
977251881Speter    }
978251881Speter
979251881Speter  if (!svn_prop_name_is_valid(propname))
980251881Speter    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
981251881Speter                             _("'%s' is not a valid Subversion property name"),
982251881Speter                             propname);
983251881Speter
984251881Speter  svn_hash_sets(*revprop_table_p, propname, propval);
985251881Speter
986251881Speter  return SVN_NO_ERROR;
987251881Speter}
988251881Speter
989251881Spetersvn_error_t *
990251881Spetersvn_opt__split_arg_at_peg_revision(const char **true_target,
991251881Speter                                   const char **peg_revision,
992251881Speter                                   const char *utf8_target,
993251881Speter                                   apr_pool_t *pool)
994251881Speter{
995251881Speter  const char *peg_start = NULL; /* pointer to the peg revision, if any */
996251881Speter  const char *ptr;
997251881Speter
998251881Speter  for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target;
999251881Speter        --ptr)
1000251881Speter    {
1001251881Speter      /* If we hit a path separator, stop looking.  This is OK
1002251881Speter          only because our revision specifiers can't contain '/'. */
1003251881Speter      if (*ptr == '/')
1004251881Speter        break;
1005251881Speter
1006251881Speter      if (*ptr == '@')
1007251881Speter        {
1008251881Speter          peg_start = ptr;
1009251881Speter          break;
1010251881Speter        }
1011251881Speter    }
1012251881Speter
1013251881Speter  if (peg_start)
1014251881Speter    {
1015251881Speter      *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target);
1016251881Speter      if (peg_revision)
1017251881Speter        *peg_revision = apr_pstrdup(pool, peg_start);
1018251881Speter    }
1019251881Speter  else
1020251881Speter    {
1021251881Speter      *true_target = utf8_target;
1022251881Speter      if (peg_revision)
1023251881Speter        *peg_revision = "";
1024251881Speter    }
1025251881Speter
1026251881Speter  return SVN_NO_ERROR;
1027251881Speter}
1028251881Speter
1029251881Spetersvn_error_t *
1030251881Spetersvn_opt__arg_canonicalize_url(const char **url_out, const char *url_in,
1031251881Speter                              apr_pool_t *pool)
1032251881Speter{
1033251881Speter  const char *target;
1034251881Speter
1035251881Speter  /* Convert to URI. */
1036251881Speter  target = svn_path_uri_from_iri(url_in, pool);
1037251881Speter  /* Auto-escape some ASCII characters. */
1038251881Speter  target = svn_path_uri_autoescape(target, pool);
1039251881Speter
1040251881Speter#if '/' != SVN_PATH_LOCAL_SEPARATOR
1041251881Speter  /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */
1042251881Speter  if (strchr(target, SVN_PATH_LOCAL_SEPARATOR))
1043251881Speter    {
1044251881Speter      char *p = apr_pstrdup(pool, target);
1045251881Speter      target = p;
1046251881Speter
1047251881Speter      /* Convert all local-style separators to the canonical ones. */
1048251881Speter      for (; *p != '\0'; ++p)
1049251881Speter        if (*p == SVN_PATH_LOCAL_SEPARATOR)
1050251881Speter          *p = '/';
1051251881Speter    }
1052251881Speter#endif
1053251881Speter
1054251881Speter  /* Verify that no backpaths are present in the URL. */
1055251881Speter  if (svn_path_is_backpath_present(target))
1056251881Speter    return svn_error_createf(SVN_ERR_BAD_URL, 0,
1057251881Speter                             _("URL '%s' contains a '..' element"),
1058251881Speter                             target);
1059251881Speter
1060251881Speter  /* Strip any trailing '/' and collapse other redundant elements. */
1061251881Speter  target = svn_uri_canonicalize(target, pool);
1062251881Speter
1063251881Speter  *url_out = target;
1064251881Speter  return SVN_NO_ERROR;
1065251881Speter}
1066251881Speter
1067251881Spetersvn_error_t *
1068251881Spetersvn_opt__arg_canonicalize_path(const char **path_out, const char *path_in,
1069251881Speter                               apr_pool_t *pool)
1070251881Speter{
1071251881Speter  const char *apr_target;
1072251881Speter  char *truenamed_target; /* APR-encoded */
1073251881Speter  apr_status_t apr_err;
1074251881Speter
1075251881Speter  /* canonicalize case, and change all separators to '/'. */
1076251881Speter  SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool));
1077251881Speter  apr_err = apr_filepath_merge(&truenamed_target, "", apr_target,
1078251881Speter                               APR_FILEPATH_TRUENAME, pool);
1079251881Speter
1080251881Speter  if (!apr_err)
1081251881Speter    /* We have a canonicalized APR-encoded target now. */
1082251881Speter    apr_target = truenamed_target;
1083251881Speter  else if (APR_STATUS_IS_ENOENT(apr_err))
1084251881Speter    /* It's okay for the file to not exist, that just means we
1085251881Speter       have to accept the case given to the client. We'll use
1086251881Speter       the original APR-encoded target. */
1087251881Speter    ;
1088251881Speter  else
1089251881Speter    return svn_error_createf(apr_err, NULL,
1090251881Speter                             _("Error resolving case of '%s'"),
1091251881Speter                             svn_dirent_local_style(path_in, pool));
1092251881Speter
1093251881Speter  /* convert back to UTF-8. */
1094251881Speter  SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool));
1095251881Speter  *path_out = svn_dirent_canonicalize(*path_out, pool);
1096251881Speter
1097251881Speter  return SVN_NO_ERROR;
1098251881Speter}
1099251881Speter
1100251881Speter
1101251881Spetersvn_error_t *
1102251881Spetersvn_opt__print_version_info(const char *pgm_name,
1103251881Speter                            const char *footer,
1104251881Speter                            const svn_version_extended_t *info,
1105251881Speter                            svn_boolean_t quiet,
1106251881Speter                            svn_boolean_t verbose,
1107251881Speter                            apr_pool_t *pool)
1108251881Speter{
1109251881Speter  if (quiet)
1110251881Speter    return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER);
1111251881Speter
1112251881Speter  SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n"
1113257286Scperciva                                     "   compiled on %s\n\n"),
1114251881Speter                             pgm_name, SVN_VERSION,
1115251881Speter                             svn_version_ext_build_host(info)));
1116251881Speter  SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info)));
1117251881Speter
1118251881Speter  if (footer)
1119251881Speter    {
1120251881Speter      SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer));
1121251881Speter    }
1122251881Speter
1123251881Speter  if (verbose)
1124251881Speter    {
1125251881Speter      const apr_array_header_t *libs;
1126251881Speter
1127251881Speter      SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool));
1128251881Speter      SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"),
1129251881Speter                                 svn_version_ext_runtime_host(info)));
1130251881Speter      if (svn_version_ext_runtime_osname(info))
1131251881Speter        {
1132251881Speter          SVN_ERR(svn_cmdline_printf(pool, _("  - %s\n"),
1133251881Speter                                     svn_version_ext_runtime_osname(info)));
1134251881Speter        }
1135251881Speter
1136251881Speter      libs = svn_version_ext_linked_libs(info);
1137251881Speter      if (libs && libs->nelts)
1138251881Speter        {
1139251881Speter          const svn_version_ext_linked_lib_t *lib;
1140251881Speter          int i;
1141251881Speter
1142251881Speter          SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"),
1143251881Speter                                    stdout, pool));
1144251881Speter          for (i = 0; i < libs->nelts; ++i)
1145251881Speter            {
1146251881Speter              lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t);
1147251881Speter              if (lib->runtime_version)
1148251881Speter                SVN_ERR(svn_cmdline_printf(pool,
1149251881Speter                                           "  - %s %s (compiled with %s)\n",
1150251881Speter                                           lib->name,
1151251881Speter                                           lib->runtime_version,
1152251881Speter                                           lib->compiled_version));
1153251881Speter              else
1154251881Speter                SVN_ERR(svn_cmdline_printf(pool,
1155251881Speter                                           "  - %s %s (static)\n",
1156251881Speter                                           lib->name,
1157251881Speter                                           lib->compiled_version));
1158251881Speter            }
1159251881Speter        }
1160251881Speter
1161251881Speter      libs = svn_version_ext_loaded_libs(info);
1162251881Speter      if (libs && libs->nelts)
1163251881Speter        {
1164251881Speter          const svn_version_ext_loaded_lib_t *lib;
1165251881Speter          int i;
1166251881Speter
1167251881Speter          SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"),
1168251881Speter                                    stdout, pool));
1169251881Speter          for (i = 0; i < libs->nelts; ++i)
1170251881Speter            {
1171251881Speter              lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t);
1172251881Speter              if (lib->version)
1173251881Speter                SVN_ERR(svn_cmdline_printf(pool,
1174251881Speter                                           "  - %s   (%s)\n",
1175251881Speter                                           lib->name, lib->version));
1176251881Speter              else
1177251881Speter                SVN_ERR(svn_cmdline_printf(pool, "  - %s\n", lib->name));
1178251881Speter            }
1179251881Speter        }
1180251881Speter    }
1181251881Speter
1182251881Speter  return SVN_NO_ERROR;
1183251881Speter}
1184251881Speter
1185251881Spetersvn_error_t *
1186251881Spetersvn_opt_print_help4(apr_getopt_t *os,
1187251881Speter                    const char *pgm_name,
1188251881Speter                    svn_boolean_t print_version,
1189251881Speter                    svn_boolean_t quiet,
1190251881Speter                    svn_boolean_t verbose,
1191251881Speter                    const char *version_footer,
1192251881Speter                    const char *header,
1193251881Speter                    const svn_opt_subcommand_desc2_t *cmd_table,
1194251881Speter                    const apr_getopt_option_t *option_table,
1195251881Speter                    const int *global_options,
1196251881Speter                    const char *footer,
1197251881Speter                    apr_pool_t *pool)
1198251881Speter{
1199251881Speter  apr_array_header_t *targets = NULL;
1200251881Speter
1201251881Speter  if (os)
1202251881Speter    SVN_ERR(svn_opt_parse_all_args(&targets, os, pool));
1203251881Speter
1204251881Speter  if (os && targets->nelts)  /* help on subcommand(s) requested */
1205251881Speter    {
1206251881Speter      int i;
1207251881Speter
1208251881Speter      for (i = 0; i < targets->nelts; i++)
1209251881Speter        {
1210251881Speter          svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *),
1211251881Speter                                   cmd_table, option_table,
1212251881Speter                                   global_options, pool);
1213251881Speter        }
1214251881Speter    }
1215251881Speter  else if (print_version)   /* just --version */
1216251881Speter    {
1217251881Speter      SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer,
1218251881Speter                                          svn_version_extended(verbose, pool),
1219251881Speter                                          quiet, verbose, pool));
1220251881Speter    }
1221251881Speter  else if (os && !targets->nelts)            /* `-h', `--help', or `help' */
1222251881Speter    svn_opt_print_generic_help2(header,
1223251881Speter                                cmd_table,
1224251881Speter                                option_table,
1225251881Speter                                footer,
1226251881Speter                                pool,
1227251881Speter                                stdout);
1228251881Speter  else                                       /* unknown option or cmd */
1229251881Speter    SVN_ERR(svn_cmdline_fprintf(stderr, pool,
1230251881Speter                                _("Type '%s help' for usage.\n"), pgm_name));
1231251881Speter
1232251881Speter  return SVN_NO_ERROR;
1233251881Speter}
1234