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