1/*
2 * svnbench.c:  Subversion benchmark client.
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27
28/*** Includes. ***/
29
30#include <string.h>
31#include <assert.h>
32
33#include <apr_signal.h>
34
35#include "svn_cmdline.h"
36#include "svn_dirent_uri.h"
37#include "svn_pools.h"
38#include "svn_utf.h"
39#include "svn_version.h"
40
41#include "cl.h"
42
43#include "private/svn_opt_private.h"
44#include "private/svn_cmdline_private.h"
45
46#include "svn_private_config.h"
47
48
49/*** Option Processing ***/
50
51/* Add an identifier here for long options that don't have a short
52   option. Options that have both long and short options should just
53   use the short option letter as identifier.  */
54typedef enum svn_cl__longopt_t {
55  opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID,
56  opt_auth_username,
57  opt_config_dir,
58  opt_config_options,
59  opt_depth,
60  opt_no_auth_cache,
61  opt_non_interactive,
62  opt_stop_on_copy,
63  opt_strict,
64  opt_targets,
65  opt_version,
66  opt_with_revprop,
67  opt_with_all_revprops,
68  opt_with_no_revprops,
69  opt_trust_server_cert,
70  opt_trust_server_cert_failures,
71  opt_changelist
72} svn_cl__longopt_t;
73
74
75/* Option codes and descriptions for the command line client.
76 *
77 * The entire list must be terminated with an entry of nulls.
78 */
79const apr_getopt_option_t svn_cl__options[] =
80{
81  {"help",          'h', 0, N_("show help on a subcommand")},
82  {NULL,            '?', 0, N_("show help on a subcommand")},
83  {"quiet",         'q', 0, N_("print nothing, or only summary information")},
84  {"recursive",     'R', 0, N_("descend recursively, same as --depth=infinity")},
85  {"non-recursive", 'N', 0, N_("obsolete; try --depth=files or --depth=immediates")},
86  {"change",        'c', 1,
87                    N_("the change made by revision ARG (like -r ARG-1:ARG)\n"
88                       "                             "
89                       "If ARG is negative this is like -r ARG:ARG-1\n"
90                       "                             "
91                       "If ARG is of the form ARG1-ARG2 then this is like\n"
92                       "                             "
93                       "ARG1:ARG2, where ARG1 is inclusive")},
94  {"revision",      'r', 1,
95                    N_("ARG (some commands also take ARG1:ARG2 range)\n"
96                       "                             "
97                       "A revision argument can be one of:\n"
98                       "                             "
99                       "   NUMBER       revision number\n"
100                       "                             "
101                       "   '{' DATE '}' revision at start of the date\n"
102                       "                             "
103                       "   'HEAD'       latest in repository\n"
104                       "                             "
105                       "   'BASE'       base rev of item's working copy\n"
106                       "                             "
107                       "   'COMMITTED'  last commit at or before BASE\n"
108                       "                             "
109                       "   'PREV'       revision just before COMMITTED")},
110  {"version",       opt_version, 0, N_("show program version information")},
111  {"verbose",       'v', 0, N_("print extra information")},
112  {"username",      opt_auth_username, 1, N_("specify a username ARG")},
113  {"password",      opt_auth_password, 1, N_("specify a password ARG")},
114  {"targets",       opt_targets, 1,
115                    N_("pass contents of file ARG as additional args")},
116  {"depth",         opt_depth, 1,
117                    N_("limit operation by depth ARG ('empty', 'files',\n"
118                       "                             "
119                       "'immediates', or 'infinity')")},
120  {"strict",        opt_strict, 0, N_("use strict semantics")},
121  {"stop-on-copy",  opt_stop_on_copy, 0,
122                    N_("do not cross copies while traversing history")},
123  {"no-auth-cache", opt_no_auth_cache, 0,
124                    N_("do not cache authentication tokens")},
125  {"trust-server-cert", opt_trust_server_cert, 0,
126                    N_("deprecated; same as\n"
127                       "                             "
128                       "--trust-server-cert-failures=unknown-ca")},
129  {"trust-server-cert-failures", opt_trust_server_cert_failures, 1,
130                    N_("with --non-interactive, accept SSL server\n"
131                       "                             "
132                       "certificates with failures; ARG is comma-separated\n"
133                       "                             "
134                       "list of 'unknown-ca' (Unknown Authority),\n"
135                       "                             "
136                       "'cn-mismatch' (Hostname mismatch), 'expired'\n"
137                       "                             "
138                       "(Expired certificate), 'not-yet-valid' (Not yet\n"
139                       "                             "
140                       "valid certificate) and 'other' (all other not\n"
141                       "                             "
142                       "separately classified certificate errors).")},
143  {"non-interactive", opt_non_interactive, 0,
144                    N_("do no interactive prompting")},
145  {"config-dir",    opt_config_dir, 1,
146                    N_("read user configuration files from directory ARG")},
147  {"config-option", opt_config_options, 1,
148                    N_("set user configuration option in the format:\n"
149                       "                             "
150                       "    FILE:SECTION:OPTION=[VALUE]\n"
151                       "                             "
152                       "For example:\n"
153                       "                             "
154                       "    servers:global:http-library=serf")},
155  {"limit",         'l', 1, N_("maximum number of log entries")},
156  {"with-all-revprops",  opt_with_all_revprops, 0,
157                    N_("retrieve all revision properties")},
158  {"with-no-revprops",  opt_with_no_revprops, 0,
159                    N_("retrieve no revision properties")},
160  {"with-revprop",  opt_with_revprop, 1,
161                    N_("set revision property ARG in new revision\n"
162                       "                             "
163                       "using the name[=value] format")},
164  {"use-merge-history", 'g', 0,
165                    N_("use/display additional information from merge\n"
166                       "                             "
167                       "history")},
168
169  /* Long-opt Aliases
170   *
171   * These have NULL desriptions, but an option code that matches some
172   * other option (whose description should probably mention its aliases).
173  */
174
175  {0,               0, 0, 0},
176};
177
178
179
180/*** Command dispatch. ***/
181
182/* Our array of available subcommands.
183 *
184 * The entire list must be terminated with an entry of nulls.
185 *
186 * In most of the help text "PATH" is used where a working copy path is
187 * required, "URL" where a repository URL is required and "TARGET" when
188 * either a path or a url can be used.  Hmm, should this be part of the
189 * help text?
190 */
191
192/* Options that apply to all commands.  (While not every command may
193   currently require authentication or be interactive, allowing every
194   command to take these arguments allows scripts to just pass them
195   willy-nilly to every invocation of 'svn') . */
196const int svn_cl__global_options[] =
197{ opt_auth_username, opt_auth_password, opt_no_auth_cache, opt_non_interactive,
198  opt_trust_server_cert, opt_trust_server_cert_failures,
199  opt_config_dir, opt_config_options, 0
200};
201
202const svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
203{
204  { "help", svn_cl__help, {"?", "h"}, N_
205    ("Describe the usage of this program or its subcommands.\n"
206     "usage: help [SUBCOMMAND...]\n"),
207    {0} },
208  /* This command is also invoked if we see option "--help", "-h" or "-?". */
209
210  { "null-blame", svn_cl__null_blame, {0}, N_
211    ("Fetch all versions of a file in a batch.\n"
212     "usage: null-blame [-rM:N] TARGET[@REV]...\n"
213     "\n"
214     "  With no revision range (same as -r0:REV), or with '-r M:N' where M < N,\n"
215     "  annotate each line that is present in revision N of the file, with\n"
216     "  the last revision at or before rN that changed or added the line,\n"
217     "  looking back no further than rM.\n"
218     "\n"
219     "  With a reverse revision range '-r M:N' where M > N,\n"
220     "  annotate each line that is present in revision N of the file, with\n"
221     "  the next revision after rN that changed or deleted the line,\n"
222     "  looking forward no further than rM.\n"
223     "\n"
224     "  If specified, REV determines in which revision the target is first\n"
225     "  looked up.\n"
226     "\n"
227     "  Write the annotated result to standard output.\n"),
228    {'r', 'g'} },
229
230  { "null-export", svn_cl__null_export, {0}, N_
231    ("Create an unversioned copy of a tree.\n"
232     "usage: null-export [-r REV] URL[@PEGREV]\n"
233     "\n"
234     "  Exports a clean directory tree from the repository specified by\n"
235     "  URL, at revision REV if it is given, otherwise at HEAD.\n"
236     "\n"
237     "  If specified, PEGREV determines in which revision the target is first\n"
238     "  looked up.\n"),
239    {'r', 'q', 'N', opt_depth} },
240
241  { "null-list", svn_cl__null_list, {"ls"}, N_
242    ("List directory entries in the repository.\n"
243     "usage: null-list [TARGET[@REV]...]\n"
244     "\n"
245     "  List each TARGET file and the contents of each TARGET directory as\n"
246     "  they exist in the repository.  If TARGET is a working copy path, the\n"
247     "  corresponding repository URL will be used. If specified, REV determines\n"
248     "  in which revision the target is first looked up.\n"
249     "\n"
250     "  The default TARGET is '.', meaning the repository URL of the current\n"
251     "  working directory.\n"
252     "\n"
253     "  With --verbose, the following fields will be fetched for each item:\n"
254     "\n"
255     "    Revision number of the last commit\n"
256     "    Author of the last commit\n"
257     "    If locked, the letter 'O'.  (Use 'svn info URL' to see details)\n"
258     "    Size (in bytes)\n"
259     "    Date and time of the last commit\n"),
260    {'r', 'v', 'q', 'R', opt_depth} },
261
262  { "null-log", svn_cl__null_log, {0}, N_
263    ("Fetch the log messages for a set of revision(s) and/or path(s).\n"
264     "usage: 1. null-log [PATH][@REV]\n"
265     "       2. null-log URL[@REV] [PATH...]\n"
266     "\n"
267     "  1. Fetch the log messages for the URL corresponding to PATH\n"
268     "     (default: '.'). If specified, REV is the revision in which the\n"
269     "     URL is first looked up, and the default revision range is REV:1.\n"
270     "     If REV is not specified, the default revision range is BASE:1,\n"
271     "     since the URL might not exist in the HEAD revision.\n"
272     "\n"
273     "  2. Fetch the log messages for the PATHs (default: '.') under URL.\n"
274     "     If specified, REV is the revision in which the URL is first\n"
275     "     looked up, and the default revision range is REV:1; otherwise,\n"
276     "     the URL is looked up in HEAD, and the default revision range is\n"
277     "     HEAD:1.\n"
278     "\n"
279     "  Multiple '-c' or '-r' options may be specified (but not a\n"
280     "  combination of '-c' and '-r' options), and mixing of forward and\n"
281     "  reverse ranges is allowed.\n"
282     "\n"
283     "  With -v, also print all affected paths with each log message.\n"
284     "  With -q, don't print the log message body itself (note that this is\n"
285     "  compatible with -v).\n"
286     "\n"
287     "  Each log message is printed just once, even if more than one of the\n"
288     "  affected paths for that revision were explicitly requested.  Logs\n"
289     "  follow copy history by default.  Use --stop-on-copy to disable this\n"
290     "  behavior, which can be useful for determining branchpoints.\n"),
291    {'r', 'q', 'v', 'g', 'c', opt_targets, opt_stop_on_copy,
292     'l', opt_with_all_revprops, opt_with_no_revprops, opt_with_revprop,},
293    {{opt_with_revprop, N_("retrieve revision property ARG")},
294     {'c', N_("the change made in revision ARG")}} },
295
296  { "null-info", svn_cl__null_info, {0}, N_
297    ("Display information about a local or remote item.\n"
298     "usage: null-info [TARGET[@REV]...]\n"
299     "\n"
300     "  Print information about each TARGET (default: '.').\n"
301     "  TARGET may be either a working-copy path or URL.  If specified, REV\n"
302     "  determines in which revision the target is first looked up.\n"),
303    {'r', 'R', opt_depth, opt_targets, opt_changelist}
304  },
305
306  { NULL, NULL, {0}, NULL, {0} }
307};
308
309
310/* Version compatibility check */
311static svn_error_t *
312check_lib_versions(void)
313{
314  static const svn_version_checklist_t checklist[] =
315    {
316      { "svn_subr",   svn_subr_version },
317      { "svn_client", svn_client_version },
318      { "svn_wc",     svn_wc_version },
319      { "svn_ra",     svn_ra_version },
320      { "svn_delta",  svn_delta_version },
321      { NULL, NULL }
322    };
323  SVN_VERSION_DEFINE(my_version);
324
325  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
326}
327
328
329/* A flag to see if we've been cancelled by the client or not. */
330static volatile sig_atomic_t cancelled = FALSE;
331
332/* A signal handler to support cancellation. */
333static void
334signal_handler(int signum)
335{
336  apr_signal(signum, SIG_IGN);
337  cancelled = TRUE;
338}
339
340/* Our cancellation callback. */
341svn_error_t *
342svn_cl__check_cancel(void *baton)
343{
344  if (cancelled)
345    return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
346  else
347    return SVN_NO_ERROR;
348}
349
350
351/*** Main. ***/
352
353/*
354 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
355 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
356 * return SVN_NO_ERROR.
357 */
358static svn_error_t *
359sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
360{
361  svn_error_t *err;
362  int opt_id;
363  apr_getopt_t *os;
364  svn_cl__opt_state_t opt_state = { 0, { 0 } };
365  svn_client_ctx_t *ctx;
366  apr_array_header_t *received_opts;
367  int i;
368  const svn_opt_subcommand_desc2_t *subcommand = NULL;
369  svn_cl__cmd_baton_t command_baton;
370  svn_auth_baton_t *ab;
371  svn_config_t *cfg_config;
372  svn_boolean_t descend = TRUE;
373  svn_boolean_t use_notifier = TRUE;
374
375  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
376
377  /* Check library versions */
378  SVN_ERR(check_lib_versions());
379
380#if defined(WIN32) || defined(__CYGWIN__)
381  /* Set the working copy administrative directory name. */
382  if (getenv("SVN_ASP_DOT_NET_HACK"))
383    {
384      SVN_ERR(svn_wc_set_adm_dir("_svn", pool));
385    }
386#endif
387
388  /* Initialize the RA library. */
389  SVN_ERR(svn_ra_initialize(pool));
390
391  /* Begin processing arguments. */
392  opt_state.start_revision.kind = svn_opt_revision_unspecified;
393  opt_state.end_revision.kind = svn_opt_revision_unspecified;
394  opt_state.revision_ranges =
395    apr_array_make(pool, 0, sizeof(svn_opt_revision_range_t *));
396  opt_state.depth = svn_depth_unknown;
397
398  /* No args?  Show usage. */
399  if (argc <= 1)
400    {
401      SVN_ERR(svn_cl__help(NULL, NULL, pool));
402      *exit_code = EXIT_FAILURE;
403      return SVN_NO_ERROR;
404    }
405
406  /* Else, parse options. */
407  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
408
409  os->interleave = 1;
410  while (1)
411    {
412      const char *opt_arg;
413      const char *utf8_opt_arg;
414
415      /* Parse the next option. */
416      apr_status_t apr_err = apr_getopt_long(os, svn_cl__options, &opt_id,
417                                             &opt_arg);
418      if (APR_STATUS_IS_EOF(apr_err))
419        break;
420      else if (apr_err)
421        {
422          SVN_ERR(svn_cl__help(NULL, NULL, pool));
423          *exit_code = EXIT_FAILURE;
424          return SVN_NO_ERROR;
425        }
426
427      /* Stash the option code in an array before parsing it. */
428      APR_ARRAY_PUSH(received_opts, int) = opt_id;
429
430      switch (opt_id) {
431      case 'l':
432        {
433          err = svn_cstring_atoi(&opt_state.limit, opt_arg);
434          if (err)
435            {
436              return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err,
437                                      _("Non-numeric limit argument given"));
438            }
439          if (opt_state.limit <= 0)
440            {
441              return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
442                                      _("Argument to --limit must be positive"));
443            }
444        }
445        break;
446      case 'c':
447        {
448          apr_array_header_t *change_revs =
449            svn_cstring_split(opt_arg, ", \n\r\t\v", TRUE, pool);
450
451          for (i = 0; i < change_revs->nelts; i++)
452            {
453              char *end;
454              svn_revnum_t changeno, changeno_end;
455              const char *change_str =
456                APR_ARRAY_IDX(change_revs, i, const char *);
457              const char *s = change_str;
458              svn_boolean_t is_negative;
459
460              /* Check for a leading minus to allow "-c -r42".
461               * The is_negative flag is used to handle "-c -42" and "-c -r42".
462               * The "-c r-42" case is handled by strtol() returning a
463               * negative number. */
464              is_negative = (*s == '-');
465              if (is_negative)
466                s++;
467
468              /* Allow any number of 'r's to prefix a revision number. */
469              while (*s == 'r')
470                s++;
471              changeno = changeno_end = strtol(s, &end, 10);
472              if (end != s && *end == '-')
473                {
474                  if (changeno < 0 || is_negative)
475                    {
476                      return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
477                                               NULL,
478                                               _("Negative number in range (%s)"
479                                                 " not supported with -c"),
480                                               change_str);
481                    }
482                  s = end + 1;
483                  while (*s == 'r')
484                    s++;
485                  changeno_end = strtol(s, &end, 10);
486                }
487              if (end == change_str || *end != '\0')
488                {
489                  return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
490                                           _("Non-numeric change argument (%s) "
491                                             "given to -c"), change_str);
492                }
493
494              if (changeno == 0)
495                {
496                  return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
497                                          _("There is no change 0"));
498                }
499
500              if (is_negative)
501                changeno = -changeno;
502
503              /* Figure out the range:
504                    -c N  -> -r N-1:N
505                    -c -N -> -r N:N-1
506                    -c M-N -> -r M-1:N for M < N
507                    -c M-N -> -r M:N-1 for M > N
508                    -c -M-N -> error (too confusing/no valid use case)
509              */
510              if (changeno > 0)
511                {
512                  if (changeno <= changeno_end)
513                    changeno--;
514                  else
515                    changeno_end--;
516                }
517              else
518                {
519                  changeno = -changeno;
520                  changeno_end = changeno - 1;
521                }
522
523              opt_state.used_change_arg = TRUE;
524              APR_ARRAY_PUSH(opt_state.revision_ranges,
525                             svn_opt_revision_range_t *)
526                = svn_opt__revision_range_from_revnums(changeno, changeno_end,
527                                                       pool);
528            }
529        }
530        break;
531      case 'r':
532        opt_state.used_revision_arg = TRUE;
533        if (svn_opt_parse_revision_to_range(opt_state.revision_ranges,
534                                            opt_arg, pool) != 0)
535          {
536            SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
537            return svn_error_createf
538                (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
539                 _("Syntax error in revision argument '%s'"),
540                 utf8_opt_arg);
541          }
542        break;
543      case 'v':
544        opt_state.verbose = TRUE;
545        break;
546      case 'h':
547      case '?':
548        opt_state.help = TRUE;
549        break;
550      case 'q':
551        opt_state.quiet = TRUE;
552        break;
553      case opt_targets:
554        {
555          svn_stringbuf_t *buffer, *buffer_utf8;
556
557          /* We need to convert to UTF-8 now, even before we divide
558             the targets into an array, because otherwise we wouldn't
559             know what delimiter to use for svn_cstring_split().  */
560
561          SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
562          SVN_ERR(svn_stringbuf_from_file2(&buffer, utf8_opt_arg, pool));
563          SVN_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
564          opt_state.targets = svn_cstring_split(buffer_utf8->data, "\n\r",
565                                                TRUE, pool);
566        }
567        break;
568      case 'R':
569        opt_state.depth = svn_depth_infinity;
570        break;
571      case 'N':
572        descend = FALSE;
573        break;
574      case opt_depth:
575        err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
576        if (err)
577          return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
578                                   _("Error converting depth "
579                                     "from locale to UTF-8"));
580        opt_state.depth = svn_depth_from_word(utf8_opt_arg);
581        if (opt_state.depth == svn_depth_unknown
582            || opt_state.depth == svn_depth_exclude)
583          {
584            return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
585                                     _("'%s' is not a valid depth; try "
586                                       "'empty', 'files', 'immediates', "
587                                       "or 'infinity'"),
588                                     utf8_opt_arg);
589          }
590        break;
591      case opt_version:
592        opt_state.version = TRUE;
593        break;
594      case opt_auth_username:
595        SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_username,
596                                            opt_arg, pool));
597        break;
598      case opt_auth_password:
599        SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_password,
600                                            opt_arg, pool));
601        break;
602      case opt_stop_on_copy:
603        opt_state.stop_on_copy = TRUE;
604        break;
605      case opt_strict:
606        opt_state.strict = TRUE;
607        break;
608      case opt_no_auth_cache:
609        opt_state.no_auth_cache = TRUE;
610        break;
611      case opt_non_interactive:
612        opt_state.non_interactive = TRUE;
613        break;
614      case opt_trust_server_cert: /* backwards compat to 1.8 */
615        opt_state.trust_server_cert_unknown_ca = TRUE;
616        break;
617      case opt_trust_server_cert_failures:
618        SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
619        SVN_ERR(svn_cmdline__parse_trust_options(
620                      &opt_state.trust_server_cert_unknown_ca,
621                      &opt_state.trust_server_cert_cn_mismatch,
622                      &opt_state.trust_server_cert_expired,
623                      &opt_state.trust_server_cert_not_yet_valid,
624                      &opt_state.trust_server_cert_other_failure,
625                      utf8_opt_arg, pool));
626        break;
627      case opt_config_dir:
628        {
629          const char *path_utf8;
630          SVN_ERR(svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool));
631          opt_state.config_dir = svn_dirent_internal_style(path_utf8, pool);
632        }
633        break;
634      case opt_config_options:
635        if (!opt_state.config_options)
636          opt_state.config_options =
637                   apr_array_make(pool, 1,
638                                  sizeof(svn_cmdline__config_argument_t*));
639
640        SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
641        SVN_ERR(svn_cmdline__parse_config_option(opt_state.config_options,
642                                                 opt_arg, "svnbench: ", pool));
643        break;
644      case opt_with_all_revprops:
645        /* If --with-all-revprops is specified along with one or more
646         * --with-revprops options, --with-all-revprops takes precedence. */
647        opt_state.all_revprops = TRUE;
648        break;
649      case opt_with_no_revprops:
650        opt_state.no_revprops = TRUE;
651        break;
652      case opt_with_revprop:
653        SVN_ERR(svn_opt_parse_revprop(&opt_state.revprop_table,
654                                          opt_arg, pool));
655        break;
656      case 'g':
657        opt_state.use_merge_history = TRUE;
658        break;
659      default:
660        /* Hmmm. Perhaps this would be a good place to squirrel away
661           opts that commands like svn diff might need. Hmmm indeed. */
662        break;
663      }
664    }
665
666  /* ### This really belongs in libsvn_client.  The trouble is,
667     there's no one place there to run it from, no
668     svn_client_init().  We'd have to add it to all the public
669     functions that a client might call.  It's unmaintainable to do
670     initialization from within libsvn_client itself, but it seems
671     burdensome to demand that all clients call svn_client_init()
672     before calling any other libsvn_client function... On the other
673     hand, the alternative is effectively to demand that they call
674     svn_config_ensure() instead, so maybe we should have a generic
675     init function anyway.  Thoughts?  */
676  SVN_ERR(svn_config_ensure(opt_state.config_dir, pool));
677
678  /* If the user asked for help, then the rest of the arguments are
679     the names of subcommands to get help on (if any), or else they're
680     just typos/mistakes.  Whatever the case, the subcommand to
681     actually run is svn_cl__help(). */
682  if (opt_state.help)
683    subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table, "help");
684
685  /* If we're not running the `help' subcommand, then look for a
686     subcommand in the first argument. */
687  if (subcommand == NULL)
688    {
689      if (os->ind >= os->argc)
690        {
691          if (opt_state.version)
692            {
693              /* Use the "help" subcommand to handle the "--version" option. */
694              static const svn_opt_subcommand_desc2_t pseudo_cmd =
695                { "--version", svn_cl__help, {0}, "",
696                  {opt_version,    /* must accept its own option */
697                   'q',            /* brief output */
698                   'v',            /* verbose output */
699                   opt_config_dir  /* all commands accept this */
700                  } };
701
702              subcommand = &pseudo_cmd;
703            }
704          else
705            {
706              svn_error_clear
707                (svn_cmdline_fprintf(stderr, pool,
708                                     _("Subcommand argument required\n")));
709              SVN_ERR(svn_cl__help(NULL, NULL, pool));
710              *exit_code = EXIT_FAILURE;
711              return SVN_NO_ERROR;
712            }
713        }
714      else
715        {
716          const char *first_arg = os->argv[os->ind++];
717          subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table,
718                                                         first_arg);
719          if (subcommand == NULL)
720            {
721              const char *first_arg_utf8;
722              SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
723                                                  first_arg, pool));
724              svn_error_clear
725                (svn_cmdline_fprintf(stderr, pool,
726                                     _("Unknown subcommand: '%s'\n"),
727                                     first_arg_utf8));
728              SVN_ERR(svn_cl__help(NULL, NULL, pool));
729              *exit_code = EXIT_FAILURE;
730              return SVN_NO_ERROR;
731            }
732        }
733    }
734
735  /* Check that the subcommand wasn't passed any inappropriate options. */
736  for (i = 0; i < received_opts->nelts; i++)
737    {
738      opt_id = APR_ARRAY_IDX(received_opts, i, int);
739
740      /* All commands implicitly accept --help, so just skip over this
741         when we see it. Note that we don't want to include this option
742         in their "accepted options" list because it would be awfully
743         redundant to display it in every commands' help text. */
744      if (opt_id == 'h' || opt_id == '?')
745        continue;
746
747      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id,
748                                             svn_cl__global_options))
749        {
750          const char *optstr;
751          const apr_getopt_option_t *badopt =
752            svn_opt_get_option_from_code2(opt_id, svn_cl__options,
753                                          subcommand, pool);
754          svn_opt_format_option(&optstr, badopt, FALSE, pool);
755          if (subcommand->name[0] == '-')
756            SVN_ERR(svn_cl__help(NULL, NULL, pool));
757          else
758            svn_error_clear
759              (svn_cmdline_fprintf
760               (stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n"
761                                "Type 'svnbench help %s' for usage.\n"),
762                subcommand->name, optstr, subcommand->name));
763          *exit_code = EXIT_FAILURE;
764          return SVN_NO_ERROR;
765        }
766    }
767
768  /* Only merge and log support multiple revisions/revision ranges. */
769  if (subcommand->cmd_func != svn_cl__null_log)
770    {
771      if (opt_state.revision_ranges->nelts > 1)
772        {
773          return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
774                                  _("Multiple revision arguments "
775                                    "encountered; can't specify -c twice, "
776                                    "or both -c and -r"));
777        }
778    }
779
780  /* Disallow simultaneous use of both --with-all-revprops and
781     --with-no-revprops.  */
782  if (opt_state.all_revprops && opt_state.no_revprops)
783    {
784      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
785                              _("--with-all-revprops and --with-no-revprops "
786                                "are mutually exclusive"));
787    }
788
789  /* Disallow simultaneous use of both --with-revprop and
790     --with-no-revprops.  */
791  if (opt_state.revprop_table && opt_state.no_revprops)
792    {
793      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
794                              _("--with-revprop and --with-no-revprops "
795                                "are mutually exclusive"));
796    }
797
798  /* --trust-* options can only be used with --non-interactive */
799  if (!opt_state.non_interactive)
800    {
801      if (opt_state.trust_server_cert_unknown_ca
802          || opt_state.trust_server_cert_cn_mismatch
803          || opt_state.trust_server_cert_expired
804          || opt_state.trust_server_cert_not_yet_valid
805          || opt_state.trust_server_cert_other_failure)
806        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
807                                _("--trust-server-cert-failures requires "
808                                  "--non-interactive"));
809    }
810
811  /* Ensure that 'revision_ranges' has at least one item, and make
812     'start_revision' and 'end_revision' match that item. */
813  if (opt_state.revision_ranges->nelts == 0)
814    {
815      svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
816      range->start.kind = svn_opt_revision_unspecified;
817      range->end.kind = svn_opt_revision_unspecified;
818      APR_ARRAY_PUSH(opt_state.revision_ranges,
819                     svn_opt_revision_range_t *) = range;
820    }
821  opt_state.start_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
822                                           svn_opt_revision_range_t *)->start;
823  opt_state.end_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
824                                         svn_opt_revision_range_t *)->end;
825
826  /* Create a client context object. */
827  command_baton.opt_state = &opt_state;
828  SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
829  command_baton.ctx = ctx;
830
831  /* Only a few commands can accept a revision range; the rest can take at
832     most one revision number. */
833  if (subcommand->cmd_func != svn_cl__null_blame
834      && subcommand->cmd_func != svn_cl__null_log)
835    {
836      if (opt_state.end_revision.kind != svn_opt_revision_unspecified)
837        {
838          return svn_error_create(SVN_ERR_CLIENT_REVISION_RANGE, NULL, NULL);
839        }
840    }
841
842  /* -N has a different meaning depending on the command */
843  if (!descend)
844    opt_state.depth = svn_depth_files;
845
846  err = svn_config_get_config(&(ctx->config),
847                              opt_state.config_dir, pool);
848  if (err)
849    {
850      /* Fallback to default config if the config directory isn't readable
851         or is not a directory. */
852      if (APR_STATUS_IS_EACCES(err->apr_err)
853          || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
854        {
855          svn_handle_warning2(stderr, err, "svn: ");
856          svn_error_clear(err);
857        }
858      else
859        return err;
860    }
861
862  cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
863                            APR_HASH_KEY_STRING);
864
865  /* Update the options in the config */
866  if (opt_state.config_options)
867    {
868      svn_error_clear(
869          svn_cmdline__apply_config_options(ctx->config,
870                                            opt_state.config_options,
871                                            "svn: ", "--config-option"));
872    }
873
874  /* Set up the notifier.
875
876     In general, we use it any time we aren't in --quiet mode.  'svn
877     status' is unique, though, in that we don't want it in --quiet mode
878     unless we're also in --verbose mode.  When in --xml mode,
879     though, we never want it.  */
880  if (opt_state.quiet)
881    use_notifier = FALSE;
882  if (use_notifier)
883    {
884      SVN_ERR(svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2,
885                                       pool));
886    }
887
888  /* Set up our cancellation support. */
889  ctx->cancel_func = svn_cl__check_cancel;
890  apr_signal(SIGINT, signal_handler);
891#ifdef SIGBREAK
892  /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
893  apr_signal(SIGBREAK, signal_handler);
894#endif
895#ifdef SIGHUP
896  apr_signal(SIGHUP, signal_handler);
897#endif
898#ifdef SIGTERM
899  apr_signal(SIGTERM, signal_handler);
900#endif
901
902#ifdef SIGPIPE
903  /* Disable SIGPIPE generation for the platforms that have it. */
904  apr_signal(SIGPIPE, SIG_IGN);
905#endif
906
907#ifdef SIGXFSZ
908  /* Disable SIGXFSZ generation for the platforms that have it, otherwise
909   * working with large files when compiled against an APR that doesn't have
910   * large file support will crash the program, which is uncool. */
911  apr_signal(SIGXFSZ, SIG_IGN);
912#endif
913
914  /* Set up Authentication stuff. */
915  SVN_ERR(svn_cmdline_create_auth_baton2(
916            &ab,
917            opt_state.non_interactive,
918            opt_state.auth_username,
919            opt_state.auth_password,
920            opt_state.config_dir,
921            opt_state.no_auth_cache,
922            opt_state.trust_server_cert_unknown_ca,
923            opt_state.trust_server_cert_cn_mismatch,
924            opt_state.trust_server_cert_expired,
925            opt_state.trust_server_cert_not_yet_valid,
926            opt_state.trust_server_cert_other_failure,
927            cfg_config,
928            ctx->cancel_func,
929            ctx->cancel_baton,
930            pool));
931
932  ctx->auth_baton = ab;
933
934  /* The new svn behavior is to postpone everything until after the operation
935     completed */
936  ctx->conflict_func = NULL;
937  ctx->conflict_baton = NULL;
938  ctx->conflict_func2 = NULL;
939  ctx->conflict_baton2 = NULL;
940
941  /* And now we finally run the subcommand. */
942  err = (*subcommand->cmd_func)(os, &command_baton, pool);
943  if (err)
944    {
945      /* For argument-related problems, suggest using the 'help'
946         subcommand. */
947      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
948          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
949        {
950          err = svn_error_quick_wrapf(
951                  err, _("Try 'svnbench help %s' for more information"),
952                  subcommand->name);
953        }
954      if (err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
955        {
956          err = svn_error_quick_wrap(err,
957                                     _("Please see the 'svn upgrade' command"));
958        }
959
960      /* Tell the user about 'svn cleanup' if any error on the stack
961         was about locked working copies. */
962      if (svn_error_find_cause(err, SVN_ERR_WC_LOCKED))
963        {
964          err = svn_error_quick_wrap(
965                  err, _("Run 'svn cleanup' to remove locks "
966                         "(type 'svn help cleanup' for details)"));
967        }
968
969      return err;
970    }
971
972  return SVN_NO_ERROR;
973}
974
975int
976main(int argc, const char *argv[])
977{
978  apr_pool_t *pool;
979  int exit_code = EXIT_SUCCESS;
980  svn_error_t *err;
981
982  /* Initialize the app. */
983  if (svn_cmdline_init("svnbench", stderr) != EXIT_SUCCESS)
984    return EXIT_FAILURE;
985
986  /* Create our top-level pool.  Use a separate mutexless allocator,
987   * given this application is single threaded.
988   */
989  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
990
991  err = sub_main(&exit_code, argc, argv, pool);
992
993  /* Flush stdout and report if it fails. It would be flushed on exit anyway
994     but this makes sure that output is not silently lost if it fails. */
995  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
996
997  if (err)
998    {
999      exit_code = EXIT_FAILURE;
1000      svn_cmdline_handle_exit_error(err, NULL, "svnbench: ");
1001    }
1002
1003  svn_pool_destroy(pool);
1004  return exit_code;
1005}
1006