]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svnbench/svnbench.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / svnbench / svnbench.c
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 \f
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 \f
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.  */
54 typedef 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  */
79 const 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 \f
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') . */
196 const 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
202 const 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 */
311 static svn_error_t *
312 check_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. */
330 static volatile sig_atomic_t cancelled = FALSE;
331
332 /* A signal handler to support cancellation. */
333 static void
334 signal_handler(int signum)
335 {
336   apr_signal(signum, SIG_IGN);
337   cancelled = TRUE;
338 }
339
340 /* Our cancellation callback. */
341 svn_error_t *
342 svn_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 \f
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  */
358 static svn_error_t *
359 sub_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
975 int
976 main(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 }