1289177Speter/*
2289177Speter * svnbench.c:  Subversion benchmark client.
3289177Speter *
4289177Speter * ====================================================================
5289177Speter *    Licensed to the Apache Software Foundation (ASF) under one
6289177Speter *    or more contributor license agreements.  See the NOTICE file
7289177Speter *    distributed with this work for additional information
8289177Speter *    regarding copyright ownership.  The ASF licenses this file
9289177Speter *    to you under the Apache License, Version 2.0 (the
10289177Speter *    "License"); you may not use this file except in compliance
11289177Speter *    with the License.  You may obtain a copy of the License at
12289177Speter *
13289177Speter *      http://www.apache.org/licenses/LICENSE-2.0
14289177Speter *
15289177Speter *    Unless required by applicable law or agreed to in writing,
16289177Speter *    software distributed under the License is distributed on an
17289177Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18289177Speter *    KIND, either express or implied.  See the License for the
19289177Speter *    specific language governing permissions and limitations
20289177Speter *    under the License.
21289177Speter * ====================================================================
22289177Speter */
23289177Speter
24289177Speter/* ==================================================================== */
25289177Speter
26289177Speter
27289177Speter
28289177Speter/*** Includes. ***/
29289177Speter
30289177Speter#include <string.h>
31289177Speter#include <assert.h>
32289177Speter
33289177Speter#include <apr_signal.h>
34289177Speter
35289177Speter#include "svn_cmdline.h"
36289177Speter#include "svn_dirent_uri.h"
37289177Speter#include "svn_pools.h"
38289177Speter#include "svn_utf.h"
39289177Speter#include "svn_version.h"
40289177Speter
41289177Speter#include "cl.h"
42289177Speter
43289177Speter#include "private/svn_opt_private.h"
44289177Speter#include "private/svn_cmdline_private.h"
45289177Speter
46289177Speter#include "svn_private_config.h"
47289177Speter
48289177Speter
49289177Speter/*** Option Processing ***/
50289177Speter
51289177Speter/* Add an identifier here for long options that don't have a short
52289177Speter   option. Options that have both long and short options should just
53289177Speter   use the short option letter as identifier.  */
54289177Spetertypedef enum svn_cl__longopt_t {
55289177Speter  opt_auth_password = SVN_OPT_FIRST_LONGOPT_ID,
56289177Speter  opt_auth_username,
57289177Speter  opt_config_dir,
58289177Speter  opt_config_options,
59289177Speter  opt_depth,
60289177Speter  opt_no_auth_cache,
61289177Speter  opt_non_interactive,
62289177Speter  opt_stop_on_copy,
63289177Speter  opt_strict,
64289177Speter  opt_targets,
65289177Speter  opt_version,
66289177Speter  opt_with_revprop,
67289177Speter  opt_with_all_revprops,
68289177Speter  opt_with_no_revprops,
69289177Speter  opt_trust_server_cert,
70289177Speter  opt_trust_server_cert_failures,
71289177Speter  opt_changelist
72289177Speter} svn_cl__longopt_t;
73289177Speter
74289177Speter
75289177Speter/* Option codes and descriptions for the command line client.
76289177Speter *
77289177Speter * The entire list must be terminated with an entry of nulls.
78289177Speter */
79289177Speterconst apr_getopt_option_t svn_cl__options[] =
80289177Speter{
81289177Speter  {"help",          'h', 0, N_("show help on a subcommand")},
82289177Speter  {NULL,            '?', 0, N_("show help on a subcommand")},
83289177Speter  {"quiet",         'q', 0, N_("print nothing, or only summary information")},
84289177Speter  {"recursive",     'R', 0, N_("descend recursively, same as --depth=infinity")},
85289177Speter  {"non-recursive", 'N', 0, N_("obsolete; try --depth=files or --depth=immediates")},
86289177Speter  {"change",        'c', 1,
87289177Speter                    N_("the change made by revision ARG (like -r ARG-1:ARG)\n"
88289177Speter                       "                             "
89289177Speter                       "If ARG is negative this is like -r ARG:ARG-1\n"
90289177Speter                       "                             "
91289177Speter                       "If ARG is of the form ARG1-ARG2 then this is like\n"
92289177Speter                       "                             "
93289177Speter                       "ARG1:ARG2, where ARG1 is inclusive")},
94289177Speter  {"revision",      'r', 1,
95289177Speter                    N_("ARG (some commands also take ARG1:ARG2 range)\n"
96289177Speter                       "                             "
97289177Speter                       "A revision argument can be one of:\n"
98289177Speter                       "                             "
99289177Speter                       "   NUMBER       revision number\n"
100289177Speter                       "                             "
101289177Speter                       "   '{' DATE '}' revision at start of the date\n"
102289177Speter                       "                             "
103289177Speter                       "   'HEAD'       latest in repository\n"
104289177Speter                       "                             "
105289177Speter                       "   'BASE'       base rev of item's working copy\n"
106289177Speter                       "                             "
107289177Speter                       "   'COMMITTED'  last commit at or before BASE\n"
108289177Speter                       "                             "
109289177Speter                       "   'PREV'       revision just before COMMITTED")},
110289177Speter  {"version",       opt_version, 0, N_("show program version information")},
111289177Speter  {"verbose",       'v', 0, N_("print extra information")},
112289177Speter  {"username",      opt_auth_username, 1, N_("specify a username ARG")},
113289177Speter  {"password",      opt_auth_password, 1, N_("specify a password ARG")},
114289177Speter  {"targets",       opt_targets, 1,
115289177Speter                    N_("pass contents of file ARG as additional args")},
116289177Speter  {"depth",         opt_depth, 1,
117289177Speter                    N_("limit operation by depth ARG ('empty', 'files',\n"
118289177Speter                       "                             "
119289177Speter                       "'immediates', or 'infinity')")},
120289177Speter  {"strict",        opt_strict, 0, N_("use strict semantics")},
121289177Speter  {"stop-on-copy",  opt_stop_on_copy, 0,
122289177Speter                    N_("do not cross copies while traversing history")},
123289177Speter  {"no-auth-cache", opt_no_auth_cache, 0,
124289177Speter                    N_("do not cache authentication tokens")},
125289177Speter  {"trust-server-cert", opt_trust_server_cert, 0,
126289177Speter                    N_("deprecated; same as\n"
127289177Speter                       "                             "
128289177Speter                       "--trust-server-cert-failures=unknown-ca")},
129289177Speter  {"trust-server-cert-failures", opt_trust_server_cert_failures, 1,
130289177Speter                    N_("with --non-interactive, accept SSL server\n"
131289177Speter                       "                             "
132289177Speter                       "certificates with failures; ARG is comma-separated\n"
133289177Speter                       "                             "
134289177Speter                       "list of 'unknown-ca' (Unknown Authority),\n"
135289177Speter                       "                             "
136289177Speter                       "'cn-mismatch' (Hostname mismatch), 'expired'\n"
137289177Speter                       "                             "
138289177Speter                       "(Expired certificate), 'not-yet-valid' (Not yet\n"
139289177Speter                       "                             "
140289177Speter                       "valid certificate) and 'other' (all other not\n"
141289177Speter                       "                             "
142289177Speter                       "separately classified certificate errors).")},
143289177Speter  {"non-interactive", opt_non_interactive, 0,
144289177Speter                    N_("do no interactive prompting")},
145289177Speter  {"config-dir",    opt_config_dir, 1,
146289177Speter                    N_("read user configuration files from directory ARG")},
147289177Speter  {"config-option", opt_config_options, 1,
148289177Speter                    N_("set user configuration option in the format:\n"
149289177Speter                       "                             "
150289177Speter                       "    FILE:SECTION:OPTION=[VALUE]\n"
151289177Speter                       "                             "
152289177Speter                       "For example:\n"
153289177Speter                       "                             "
154289177Speter                       "    servers:global:http-library=serf")},
155289177Speter  {"limit",         'l', 1, N_("maximum number of log entries")},
156289177Speter  {"with-all-revprops",  opt_with_all_revprops, 0,
157289177Speter                    N_("retrieve all revision properties")},
158289177Speter  {"with-no-revprops",  opt_with_no_revprops, 0,
159289177Speter                    N_("retrieve no revision properties")},
160289177Speter  {"with-revprop",  opt_with_revprop, 1,
161289177Speter                    N_("set revision property ARG in new revision\n"
162289177Speter                       "                             "
163289177Speter                       "using the name[=value] format")},
164289177Speter  {"use-merge-history", 'g', 0,
165289177Speter                    N_("use/display additional information from merge\n"
166289177Speter                       "                             "
167289177Speter                       "history")},
168289177Speter
169289177Speter  /* Long-opt Aliases
170289177Speter   *
171289177Speter   * These have NULL desriptions, but an option code that matches some
172289177Speter   * other option (whose description should probably mention its aliases).
173289177Speter  */
174289177Speter
175289177Speter  {0,               0, 0, 0},
176289177Speter};
177289177Speter
178289177Speter
179289177Speter
180289177Speter/*** Command dispatch. ***/
181289177Speter
182289177Speter/* Our array of available subcommands.
183289177Speter *
184289177Speter * The entire list must be terminated with an entry of nulls.
185289177Speter *
186289177Speter * In most of the help text "PATH" is used where a working copy path is
187289177Speter * required, "URL" where a repository URL is required and "TARGET" when
188289177Speter * either a path or a url can be used.  Hmm, should this be part of the
189289177Speter * help text?
190289177Speter */
191289177Speter
192289177Speter/* Options that apply to all commands.  (While not every command may
193289177Speter   currently require authentication or be interactive, allowing every
194289177Speter   command to take these arguments allows scripts to just pass them
195289177Speter   willy-nilly to every invocation of 'svn') . */
196289177Speterconst int svn_cl__global_options[] =
197289177Speter{ opt_auth_username, opt_auth_password, opt_no_auth_cache, opt_non_interactive,
198289177Speter  opt_trust_server_cert, opt_trust_server_cert_failures,
199289177Speter  opt_config_dir, opt_config_options, 0
200289177Speter};
201289177Speter
202289177Speterconst svn_opt_subcommand_desc2_t svn_cl__cmd_table[] =
203289177Speter{
204289177Speter  { "help", svn_cl__help, {"?", "h"}, N_
205289177Speter    ("Describe the usage of this program or its subcommands.\n"
206289177Speter     "usage: help [SUBCOMMAND...]\n"),
207289177Speter    {0} },
208289177Speter  /* This command is also invoked if we see option "--help", "-h" or "-?". */
209289177Speter
210289177Speter  { "null-blame", svn_cl__null_blame, {0}, N_
211289177Speter    ("Fetch all versions of a file in a batch.\n"
212289177Speter     "usage: null-blame [-rM:N] TARGET[@REV]...\n"
213289177Speter     "\n"
214289177Speter     "  With no revision range (same as -r0:REV), or with '-r M:N' where M < N,\n"
215289177Speter     "  annotate each line that is present in revision N of the file, with\n"
216289177Speter     "  the last revision at or before rN that changed or added the line,\n"
217289177Speter     "  looking back no further than rM.\n"
218289177Speter     "\n"
219289177Speter     "  With a reverse revision range '-r M:N' where M > N,\n"
220289177Speter     "  annotate each line that is present in revision N of the file, with\n"
221289177Speter     "  the next revision after rN that changed or deleted the line,\n"
222289177Speter     "  looking forward no further than rM.\n"
223289177Speter     "\n"
224289177Speter     "  If specified, REV determines in which revision the target is first\n"
225289177Speter     "  looked up.\n"
226289177Speter     "\n"
227289177Speter     "  Write the annotated result to standard output.\n"),
228289177Speter    {'r', 'g'} },
229289177Speter
230289177Speter  { "null-export", svn_cl__null_export, {0}, N_
231289177Speter    ("Create an unversioned copy of a tree.\n"
232289177Speter     "usage: null-export [-r REV] URL[@PEGREV]\n"
233289177Speter     "\n"
234289177Speter     "  Exports a clean directory tree from the repository specified by\n"
235289177Speter     "  URL, at revision REV if it is given, otherwise at HEAD.\n"
236289177Speter     "\n"
237289177Speter     "  If specified, PEGREV determines in which revision the target is first\n"
238289177Speter     "  looked up.\n"),
239289177Speter    {'r', 'q', 'N', opt_depth} },
240289177Speter
241289177Speter  { "null-list", svn_cl__null_list, {"ls"}, N_
242289177Speter    ("List directory entries in the repository.\n"
243289177Speter     "usage: null-list [TARGET[@REV]...]\n"
244289177Speter     "\n"
245289177Speter     "  List each TARGET file and the contents of each TARGET directory as\n"
246289177Speter     "  they exist in the repository.  If TARGET is a working copy path, the\n"
247289177Speter     "  corresponding repository URL will be used. If specified, REV determines\n"
248289177Speter     "  in which revision the target is first looked up.\n"
249289177Speter     "\n"
250289177Speter     "  The default TARGET is '.', meaning the repository URL of the current\n"
251289177Speter     "  working directory.\n"
252289177Speter     "\n"
253289177Speter     "  With --verbose, the following fields will be fetched for each item:\n"
254289177Speter     "\n"
255289177Speter     "    Revision number of the last commit\n"
256289177Speter     "    Author of the last commit\n"
257289177Speter     "    If locked, the letter 'O'.  (Use 'svn info URL' to see details)\n"
258289177Speter     "    Size (in bytes)\n"
259289177Speter     "    Date and time of the last commit\n"),
260289177Speter    {'r', 'v', 'q', 'R', opt_depth} },
261289177Speter
262289177Speter  { "null-log", svn_cl__null_log, {0}, N_
263289177Speter    ("Fetch the log messages for a set of revision(s) and/or path(s).\n"
264289177Speter     "usage: 1. null-log [PATH][@REV]\n"
265289177Speter     "       2. null-log URL[@REV] [PATH...]\n"
266289177Speter     "\n"
267289177Speter     "  1. Fetch the log messages for the URL corresponding to PATH\n"
268289177Speter     "     (default: '.'). If specified, REV is the revision in which the\n"
269289177Speter     "     URL is first looked up, and the default revision range is REV:1.\n"
270289177Speter     "     If REV is not specified, the default revision range is BASE:1,\n"
271289177Speter     "     since the URL might not exist in the HEAD revision.\n"
272289177Speter     "\n"
273289177Speter     "  2. Fetch the log messages for the PATHs (default: '.') under URL.\n"
274289177Speter     "     If specified, REV is the revision in which the URL is first\n"
275289177Speter     "     looked up, and the default revision range is REV:1; otherwise,\n"
276289177Speter     "     the URL is looked up in HEAD, and the default revision range is\n"
277289177Speter     "     HEAD:1.\n"
278289177Speter     "\n"
279289177Speter     "  Multiple '-c' or '-r' options may be specified (but not a\n"
280289177Speter     "  combination of '-c' and '-r' options), and mixing of forward and\n"
281289177Speter     "  reverse ranges is allowed.\n"
282289177Speter     "\n"
283289177Speter     "  With -v, also print all affected paths with each log message.\n"
284289177Speter     "  With -q, don't print the log message body itself (note that this is\n"
285289177Speter     "  compatible with -v).\n"
286289177Speter     "\n"
287289177Speter     "  Each log message is printed just once, even if more than one of the\n"
288289177Speter     "  affected paths for that revision were explicitly requested.  Logs\n"
289289177Speter     "  follow copy history by default.  Use --stop-on-copy to disable this\n"
290289177Speter     "  behavior, which can be useful for determining branchpoints.\n"),
291289177Speter    {'r', 'q', 'v', 'g', 'c', opt_targets, opt_stop_on_copy,
292289177Speter     'l', opt_with_all_revprops, opt_with_no_revprops, opt_with_revprop,},
293289177Speter    {{opt_with_revprop, N_("retrieve revision property ARG")},
294289177Speter     {'c', N_("the change made in revision ARG")}} },
295289177Speter
296289177Speter  { "null-info", svn_cl__null_info, {0}, N_
297289177Speter    ("Display information about a local or remote item.\n"
298289177Speter     "usage: null-info [TARGET[@REV]...]\n"
299289177Speter     "\n"
300289177Speter     "  Print information about each TARGET (default: '.').\n"
301289177Speter     "  TARGET may be either a working-copy path or URL.  If specified, REV\n"
302289177Speter     "  determines in which revision the target is first looked up.\n"),
303289177Speter    {'r', 'R', opt_depth, opt_targets, opt_changelist}
304289177Speter  },
305289177Speter
306289177Speter  { NULL, NULL, {0}, NULL, {0} }
307289177Speter};
308289177Speter
309289177Speter
310289177Speter/* Version compatibility check */
311289177Speterstatic svn_error_t *
312289177Spetercheck_lib_versions(void)
313289177Speter{
314289177Speter  static const svn_version_checklist_t checklist[] =
315289177Speter    {
316289177Speter      { "svn_subr",   svn_subr_version },
317289177Speter      { "svn_client", svn_client_version },
318289177Speter      { "svn_wc",     svn_wc_version },
319289177Speter      { "svn_ra",     svn_ra_version },
320289177Speter      { "svn_delta",  svn_delta_version },
321289177Speter      { NULL, NULL }
322289177Speter    };
323289177Speter  SVN_VERSION_DEFINE(my_version);
324289177Speter
325289177Speter  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
326289177Speter}
327289177Speter
328289177Speter
329289177Speter/* A flag to see if we've been cancelled by the client or not. */
330289177Speterstatic volatile sig_atomic_t cancelled = FALSE;
331289177Speter
332289177Speter/* A signal handler to support cancellation. */
333289177Speterstatic void
334289177Spetersignal_handler(int signum)
335289177Speter{
336289177Speter  apr_signal(signum, SIG_IGN);
337289177Speter  cancelled = TRUE;
338289177Speter}
339289177Speter
340289177Speter/* Our cancellation callback. */
341289177Spetersvn_error_t *
342289177Spetersvn_cl__check_cancel(void *baton)
343289177Speter{
344289177Speter  if (cancelled)
345289177Speter    return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
346289177Speter  else
347289177Speter    return SVN_NO_ERROR;
348289177Speter}
349289177Speter
350289177Speter
351289177Speter/*** Main. ***/
352289177Speter
353289177Speter/*
354289177Speter * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
355289177Speter * either return an error to be displayed, or set *EXIT_CODE to non-zero and
356289177Speter * return SVN_NO_ERROR.
357289177Speter */
358289177Speterstatic svn_error_t *
359289177Spetersub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
360289177Speter{
361289177Speter  svn_error_t *err;
362289177Speter  int opt_id;
363289177Speter  apr_getopt_t *os;
364289177Speter  svn_cl__opt_state_t opt_state = { 0, { 0 } };
365289177Speter  svn_client_ctx_t *ctx;
366289177Speter  apr_array_header_t *received_opts;
367289177Speter  int i;
368289177Speter  const svn_opt_subcommand_desc2_t *subcommand = NULL;
369289177Speter  svn_cl__cmd_baton_t command_baton;
370289177Speter  svn_auth_baton_t *ab;
371289177Speter  svn_config_t *cfg_config;
372289177Speter  svn_boolean_t descend = TRUE;
373289177Speter  svn_boolean_t use_notifier = TRUE;
374289177Speter
375289177Speter  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
376289177Speter
377289177Speter  /* Check library versions */
378289177Speter  SVN_ERR(check_lib_versions());
379289177Speter
380289177Speter#if defined(WIN32) || defined(__CYGWIN__)
381289177Speter  /* Set the working copy administrative directory name. */
382289177Speter  if (getenv("SVN_ASP_DOT_NET_HACK"))
383289177Speter    {
384289177Speter      SVN_ERR(svn_wc_set_adm_dir("_svn", pool));
385289177Speter    }
386289177Speter#endif
387289177Speter
388289177Speter  /* Initialize the RA library. */
389289177Speter  SVN_ERR(svn_ra_initialize(pool));
390289177Speter
391289177Speter  /* Begin processing arguments. */
392289177Speter  opt_state.start_revision.kind = svn_opt_revision_unspecified;
393289177Speter  opt_state.end_revision.kind = svn_opt_revision_unspecified;
394289177Speter  opt_state.revision_ranges =
395289177Speter    apr_array_make(pool, 0, sizeof(svn_opt_revision_range_t *));
396289177Speter  opt_state.depth = svn_depth_unknown;
397289177Speter
398289177Speter  /* No args?  Show usage. */
399289177Speter  if (argc <= 1)
400289177Speter    {
401289177Speter      SVN_ERR(svn_cl__help(NULL, NULL, pool));
402289177Speter      *exit_code = EXIT_FAILURE;
403289177Speter      return SVN_NO_ERROR;
404289177Speter    }
405289177Speter
406289177Speter  /* Else, parse options. */
407289177Speter  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
408289177Speter
409289177Speter  os->interleave = 1;
410289177Speter  while (1)
411289177Speter    {
412289177Speter      const char *opt_arg;
413289177Speter      const char *utf8_opt_arg;
414289177Speter
415289177Speter      /* Parse the next option. */
416289177Speter      apr_status_t apr_err = apr_getopt_long(os, svn_cl__options, &opt_id,
417289177Speter                                             &opt_arg);
418289177Speter      if (APR_STATUS_IS_EOF(apr_err))
419289177Speter        break;
420289177Speter      else if (apr_err)
421289177Speter        {
422289177Speter          SVN_ERR(svn_cl__help(NULL, NULL, pool));
423289177Speter          *exit_code = EXIT_FAILURE;
424289177Speter          return SVN_NO_ERROR;
425289177Speter        }
426289177Speter
427289177Speter      /* Stash the option code in an array before parsing it. */
428289177Speter      APR_ARRAY_PUSH(received_opts, int) = opt_id;
429289177Speter
430289177Speter      switch (opt_id) {
431289177Speter      case 'l':
432289177Speter        {
433289177Speter          err = svn_cstring_atoi(&opt_state.limit, opt_arg);
434289177Speter          if (err)
435289177Speter            {
436289177Speter              return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, err,
437289177Speter                                      _("Non-numeric limit argument given"));
438289177Speter            }
439289177Speter          if (opt_state.limit <= 0)
440289177Speter            {
441289177Speter              return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
442289177Speter                                      _("Argument to --limit must be positive"));
443289177Speter            }
444289177Speter        }
445289177Speter        break;
446289177Speter      case 'c':
447289177Speter        {
448289177Speter          apr_array_header_t *change_revs =
449289177Speter            svn_cstring_split(opt_arg, ", \n\r\t\v", TRUE, pool);
450289177Speter
451289177Speter          for (i = 0; i < change_revs->nelts; i++)
452289177Speter            {
453289177Speter              char *end;
454289177Speter              svn_revnum_t changeno, changeno_end;
455289177Speter              const char *change_str =
456289177Speter                APR_ARRAY_IDX(change_revs, i, const char *);
457289177Speter              const char *s = change_str;
458289177Speter              svn_boolean_t is_negative;
459289177Speter
460289177Speter              /* Check for a leading minus to allow "-c -r42".
461289177Speter               * The is_negative flag is used to handle "-c -42" and "-c -r42".
462289177Speter               * The "-c r-42" case is handled by strtol() returning a
463289177Speter               * negative number. */
464289177Speter              is_negative = (*s == '-');
465289177Speter              if (is_negative)
466289177Speter                s++;
467289177Speter
468289177Speter              /* Allow any number of 'r's to prefix a revision number. */
469289177Speter              while (*s == 'r')
470289177Speter                s++;
471289177Speter              changeno = changeno_end = strtol(s, &end, 10);
472289177Speter              if (end != s && *end == '-')
473289177Speter                {
474289177Speter                  if (changeno < 0 || is_negative)
475289177Speter                    {
476289177Speter                      return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR,
477289177Speter                                               NULL,
478289177Speter                                               _("Negative number in range (%s)"
479289177Speter                                                 " not supported with -c"),
480289177Speter                                               change_str);
481289177Speter                    }
482289177Speter                  s = end + 1;
483289177Speter                  while (*s == 'r')
484289177Speter                    s++;
485289177Speter                  changeno_end = strtol(s, &end, 10);
486289177Speter                }
487289177Speter              if (end == change_str || *end != '\0')
488289177Speter                {
489289177Speter                  return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
490289177Speter                                           _("Non-numeric change argument (%s) "
491289177Speter                                             "given to -c"), change_str);
492289177Speter                }
493289177Speter
494289177Speter              if (changeno == 0)
495289177Speter                {
496289177Speter                  return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
497289177Speter                                          _("There is no change 0"));
498289177Speter                }
499289177Speter
500289177Speter              if (is_negative)
501289177Speter                changeno = -changeno;
502289177Speter
503289177Speter              /* Figure out the range:
504289177Speter                    -c N  -> -r N-1:N
505289177Speter                    -c -N -> -r N:N-1
506289177Speter                    -c M-N -> -r M-1:N for M < N
507289177Speter                    -c M-N -> -r M:N-1 for M > N
508289177Speter                    -c -M-N -> error (too confusing/no valid use case)
509289177Speter              */
510289177Speter              if (changeno > 0)
511289177Speter                {
512289177Speter                  if (changeno <= changeno_end)
513289177Speter                    changeno--;
514289177Speter                  else
515289177Speter                    changeno_end--;
516289177Speter                }
517289177Speter              else
518289177Speter                {
519289177Speter                  changeno = -changeno;
520289177Speter                  changeno_end = changeno - 1;
521289177Speter                }
522289177Speter
523289177Speter              opt_state.used_change_arg = TRUE;
524289177Speter              APR_ARRAY_PUSH(opt_state.revision_ranges,
525289177Speter                             svn_opt_revision_range_t *)
526289177Speter                = svn_opt__revision_range_from_revnums(changeno, changeno_end,
527289177Speter                                                       pool);
528289177Speter            }
529289177Speter        }
530289177Speter        break;
531289177Speter      case 'r':
532289177Speter        opt_state.used_revision_arg = TRUE;
533289177Speter        if (svn_opt_parse_revision_to_range(opt_state.revision_ranges,
534289177Speter                                            opt_arg, pool) != 0)
535289177Speter          {
536289177Speter            SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
537289177Speter            return svn_error_createf
538289177Speter                (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
539289177Speter                 _("Syntax error in revision argument '%s'"),
540289177Speter                 utf8_opt_arg);
541289177Speter          }
542289177Speter        break;
543289177Speter      case 'v':
544289177Speter        opt_state.verbose = TRUE;
545289177Speter        break;
546289177Speter      case 'h':
547289177Speter      case '?':
548289177Speter        opt_state.help = TRUE;
549289177Speter        break;
550289177Speter      case 'q':
551289177Speter        opt_state.quiet = TRUE;
552289177Speter        break;
553289177Speter      case opt_targets:
554289177Speter        {
555289177Speter          svn_stringbuf_t *buffer, *buffer_utf8;
556289177Speter
557289177Speter          /* We need to convert to UTF-8 now, even before we divide
558289177Speter             the targets into an array, because otherwise we wouldn't
559289177Speter             know what delimiter to use for svn_cstring_split().  */
560289177Speter
561289177Speter          SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
562289177Speter          SVN_ERR(svn_stringbuf_from_file2(&buffer, utf8_opt_arg, pool));
563289177Speter          SVN_ERR(svn_utf_stringbuf_to_utf8(&buffer_utf8, buffer, pool));
564289177Speter          opt_state.targets = svn_cstring_split(buffer_utf8->data, "\n\r",
565289177Speter                                                TRUE, pool);
566289177Speter        }
567289177Speter        break;
568289177Speter      case 'R':
569289177Speter        opt_state.depth = svn_depth_infinity;
570289177Speter        break;
571289177Speter      case 'N':
572289177Speter        descend = FALSE;
573289177Speter        break;
574289177Speter      case opt_depth:
575289177Speter        err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
576289177Speter        if (err)
577289177Speter          return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, err,
578289177Speter                                   _("Error converting depth "
579289177Speter                                     "from locale to UTF-8"));
580289177Speter        opt_state.depth = svn_depth_from_word(utf8_opt_arg);
581289177Speter        if (opt_state.depth == svn_depth_unknown
582289177Speter            || opt_state.depth == svn_depth_exclude)
583289177Speter          {
584289177Speter            return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
585289177Speter                                     _("'%s' is not a valid depth; try "
586289177Speter                                       "'empty', 'files', 'immediates', "
587289177Speter                                       "or 'infinity'"),
588289177Speter                                     utf8_opt_arg);
589289177Speter          }
590289177Speter        break;
591289177Speter      case opt_version:
592289177Speter        opt_state.version = TRUE;
593289177Speter        break;
594289177Speter      case opt_auth_username:
595289177Speter        SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_username,
596289177Speter                                            opt_arg, pool));
597289177Speter        break;
598289177Speter      case opt_auth_password:
599289177Speter        SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.auth_password,
600289177Speter                                            opt_arg, pool));
601289177Speter        break;
602289177Speter      case opt_stop_on_copy:
603289177Speter        opt_state.stop_on_copy = TRUE;
604289177Speter        break;
605289177Speter      case opt_strict:
606289177Speter        opt_state.strict = TRUE;
607289177Speter        break;
608289177Speter      case opt_no_auth_cache:
609289177Speter        opt_state.no_auth_cache = TRUE;
610289177Speter        break;
611289177Speter      case opt_non_interactive:
612289177Speter        opt_state.non_interactive = TRUE;
613289177Speter        break;
614289177Speter      case opt_trust_server_cert: /* backwards compat to 1.8 */
615289177Speter        opt_state.trust_server_cert_unknown_ca = TRUE;
616289177Speter        break;
617289177Speter      case opt_trust_server_cert_failures:
618289177Speter        SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
619289177Speter        SVN_ERR(svn_cmdline__parse_trust_options(
620289177Speter                      &opt_state.trust_server_cert_unknown_ca,
621289177Speter                      &opt_state.trust_server_cert_cn_mismatch,
622289177Speter                      &opt_state.trust_server_cert_expired,
623289177Speter                      &opt_state.trust_server_cert_not_yet_valid,
624289177Speter                      &opt_state.trust_server_cert_other_failure,
625289177Speter                      utf8_opt_arg, pool));
626289177Speter        break;
627289177Speter      case opt_config_dir:
628289177Speter        {
629289177Speter          const char *path_utf8;
630289177Speter          SVN_ERR(svn_utf_cstring_to_utf8(&path_utf8, opt_arg, pool));
631289177Speter          opt_state.config_dir = svn_dirent_internal_style(path_utf8, pool);
632289177Speter        }
633289177Speter        break;
634289177Speter      case opt_config_options:
635289177Speter        if (!opt_state.config_options)
636289177Speter          opt_state.config_options =
637289177Speter                   apr_array_make(pool, 1,
638289177Speter                                  sizeof(svn_cmdline__config_argument_t*));
639289177Speter
640289177Speter        SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
641289177Speter        SVN_ERR(svn_cmdline__parse_config_option(opt_state.config_options,
642289177Speter                                                 opt_arg, "svnbench: ", pool));
643289177Speter        break;
644289177Speter      case opt_with_all_revprops:
645289177Speter        /* If --with-all-revprops is specified along with one or more
646289177Speter         * --with-revprops options, --with-all-revprops takes precedence. */
647289177Speter        opt_state.all_revprops = TRUE;
648289177Speter        break;
649289177Speter      case opt_with_no_revprops:
650289177Speter        opt_state.no_revprops = TRUE;
651289177Speter        break;
652289177Speter      case opt_with_revprop:
653289177Speter        SVN_ERR(svn_opt_parse_revprop(&opt_state.revprop_table,
654289177Speter                                          opt_arg, pool));
655289177Speter        break;
656289177Speter      case 'g':
657289177Speter        opt_state.use_merge_history = TRUE;
658289177Speter        break;
659289177Speter      default:
660289177Speter        /* Hmmm. Perhaps this would be a good place to squirrel away
661289177Speter           opts that commands like svn diff might need. Hmmm indeed. */
662289177Speter        break;
663289177Speter      }
664289177Speter    }
665289177Speter
666289177Speter  /* ### This really belongs in libsvn_client.  The trouble is,
667289177Speter     there's no one place there to run it from, no
668289177Speter     svn_client_init().  We'd have to add it to all the public
669289177Speter     functions that a client might call.  It's unmaintainable to do
670289177Speter     initialization from within libsvn_client itself, but it seems
671289177Speter     burdensome to demand that all clients call svn_client_init()
672289177Speter     before calling any other libsvn_client function... On the other
673289177Speter     hand, the alternative is effectively to demand that they call
674289177Speter     svn_config_ensure() instead, so maybe we should have a generic
675289177Speter     init function anyway.  Thoughts?  */
676289177Speter  SVN_ERR(svn_config_ensure(opt_state.config_dir, pool));
677289177Speter
678289177Speter  /* If the user asked for help, then the rest of the arguments are
679289177Speter     the names of subcommands to get help on (if any), or else they're
680289177Speter     just typos/mistakes.  Whatever the case, the subcommand to
681289177Speter     actually run is svn_cl__help(). */
682289177Speter  if (opt_state.help)
683289177Speter    subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table, "help");
684289177Speter
685289177Speter  /* If we're not running the `help' subcommand, then look for a
686289177Speter     subcommand in the first argument. */
687289177Speter  if (subcommand == NULL)
688289177Speter    {
689289177Speter      if (os->ind >= os->argc)
690289177Speter        {
691289177Speter          if (opt_state.version)
692289177Speter            {
693289177Speter              /* Use the "help" subcommand to handle the "--version" option. */
694289177Speter              static const svn_opt_subcommand_desc2_t pseudo_cmd =
695289177Speter                { "--version", svn_cl__help, {0}, "",
696289177Speter                  {opt_version,    /* must accept its own option */
697289177Speter                   'q',            /* brief output */
698289177Speter                   'v',            /* verbose output */
699289177Speter                   opt_config_dir  /* all commands accept this */
700289177Speter                  } };
701289177Speter
702289177Speter              subcommand = &pseudo_cmd;
703289177Speter            }
704289177Speter          else
705289177Speter            {
706289177Speter              svn_error_clear
707289177Speter                (svn_cmdline_fprintf(stderr, pool,
708289177Speter                                     _("Subcommand argument required\n")));
709289177Speter              SVN_ERR(svn_cl__help(NULL, NULL, pool));
710289177Speter              *exit_code = EXIT_FAILURE;
711289177Speter              return SVN_NO_ERROR;
712289177Speter            }
713289177Speter        }
714289177Speter      else
715289177Speter        {
716289177Speter          const char *first_arg = os->argv[os->ind++];
717289177Speter          subcommand = svn_opt_get_canonical_subcommand2(svn_cl__cmd_table,
718289177Speter                                                         first_arg);
719289177Speter          if (subcommand == NULL)
720289177Speter            {
721289177Speter              const char *first_arg_utf8;
722289177Speter              SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8,
723289177Speter                                                  first_arg, pool));
724289177Speter              svn_error_clear
725289177Speter                (svn_cmdline_fprintf(stderr, pool,
726289177Speter                                     _("Unknown subcommand: '%s'\n"),
727289177Speter                                     first_arg_utf8));
728289177Speter              SVN_ERR(svn_cl__help(NULL, NULL, pool));
729289177Speter              *exit_code = EXIT_FAILURE;
730289177Speter              return SVN_NO_ERROR;
731289177Speter            }
732289177Speter        }
733289177Speter    }
734289177Speter
735289177Speter  /* Check that the subcommand wasn't passed any inappropriate options. */
736289177Speter  for (i = 0; i < received_opts->nelts; i++)
737289177Speter    {
738289177Speter      opt_id = APR_ARRAY_IDX(received_opts, i, int);
739289177Speter
740289177Speter      /* All commands implicitly accept --help, so just skip over this
741289177Speter         when we see it. Note that we don't want to include this option
742289177Speter         in their "accepted options" list because it would be awfully
743289177Speter         redundant to display it in every commands' help text. */
744289177Speter      if (opt_id == 'h' || opt_id == '?')
745289177Speter        continue;
746289177Speter
747289177Speter      if (! svn_opt_subcommand_takes_option3(subcommand, opt_id,
748289177Speter                                             svn_cl__global_options))
749289177Speter        {
750289177Speter          const char *optstr;
751289177Speter          const apr_getopt_option_t *badopt =
752289177Speter            svn_opt_get_option_from_code2(opt_id, svn_cl__options,
753289177Speter                                          subcommand, pool);
754289177Speter          svn_opt_format_option(&optstr, badopt, FALSE, pool);
755289177Speter          if (subcommand->name[0] == '-')
756289177Speter            SVN_ERR(svn_cl__help(NULL, NULL, pool));
757289177Speter          else
758289177Speter            svn_error_clear
759289177Speter              (svn_cmdline_fprintf
760289177Speter               (stderr, pool, _("Subcommand '%s' doesn't accept option '%s'\n"
761289177Speter                                "Type 'svnbench help %s' for usage.\n"),
762289177Speter                subcommand->name, optstr, subcommand->name));
763289177Speter          *exit_code = EXIT_FAILURE;
764289177Speter          return SVN_NO_ERROR;
765289177Speter        }
766289177Speter    }
767289177Speter
768289177Speter  /* Only merge and log support multiple revisions/revision ranges. */
769289177Speter  if (subcommand->cmd_func != svn_cl__null_log)
770289177Speter    {
771289177Speter      if (opt_state.revision_ranges->nelts > 1)
772289177Speter        {
773289177Speter          return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
774289177Speter                                  _("Multiple revision arguments "
775289177Speter                                    "encountered; can't specify -c twice, "
776289177Speter                                    "or both -c and -r"));
777289177Speter        }
778289177Speter    }
779289177Speter
780289177Speter  /* Disallow simultaneous use of both --with-all-revprops and
781289177Speter     --with-no-revprops.  */
782289177Speter  if (opt_state.all_revprops && opt_state.no_revprops)
783289177Speter    {
784289177Speter      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
785289177Speter                              _("--with-all-revprops and --with-no-revprops "
786289177Speter                                "are mutually exclusive"));
787289177Speter    }
788289177Speter
789289177Speter  /* Disallow simultaneous use of both --with-revprop and
790289177Speter     --with-no-revprops.  */
791289177Speter  if (opt_state.revprop_table && opt_state.no_revprops)
792289177Speter    {
793289177Speter      return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
794289177Speter                              _("--with-revprop and --with-no-revprops "
795289177Speter                                "are mutually exclusive"));
796289177Speter    }
797289177Speter
798289177Speter  /* --trust-* options can only be used with --non-interactive */
799289177Speter  if (!opt_state.non_interactive)
800289177Speter    {
801289177Speter      if (opt_state.trust_server_cert_unknown_ca
802289177Speter          || opt_state.trust_server_cert_cn_mismatch
803289177Speter          || opt_state.trust_server_cert_expired
804289177Speter          || opt_state.trust_server_cert_not_yet_valid
805289177Speter          || opt_state.trust_server_cert_other_failure)
806289177Speter        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
807289177Speter                                _("--trust-server-cert-failures requires "
808289177Speter                                  "--non-interactive"));
809289177Speter    }
810289177Speter
811289177Speter  /* Ensure that 'revision_ranges' has at least one item, and make
812289177Speter     'start_revision' and 'end_revision' match that item. */
813289177Speter  if (opt_state.revision_ranges->nelts == 0)
814289177Speter    {
815289177Speter      svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range));
816289177Speter      range->start.kind = svn_opt_revision_unspecified;
817289177Speter      range->end.kind = svn_opt_revision_unspecified;
818289177Speter      APR_ARRAY_PUSH(opt_state.revision_ranges,
819289177Speter                     svn_opt_revision_range_t *) = range;
820289177Speter    }
821289177Speter  opt_state.start_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
822289177Speter                                           svn_opt_revision_range_t *)->start;
823289177Speter  opt_state.end_revision = APR_ARRAY_IDX(opt_state.revision_ranges, 0,
824289177Speter                                         svn_opt_revision_range_t *)->end;
825289177Speter
826289177Speter  /* Create a client context object. */
827289177Speter  command_baton.opt_state = &opt_state;
828289177Speter  SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
829289177Speter  command_baton.ctx = ctx;
830289177Speter
831289177Speter  /* Only a few commands can accept a revision range; the rest can take at
832289177Speter     most one revision number. */
833289177Speter  if (subcommand->cmd_func != svn_cl__null_blame
834289177Speter      && subcommand->cmd_func != svn_cl__null_log)
835289177Speter    {
836289177Speter      if (opt_state.end_revision.kind != svn_opt_revision_unspecified)
837289177Speter        {
838289177Speter          return svn_error_create(SVN_ERR_CLIENT_REVISION_RANGE, NULL, NULL);
839289177Speter        }
840289177Speter    }
841289177Speter
842289177Speter  /* -N has a different meaning depending on the command */
843289177Speter  if (!descend)
844289177Speter    opt_state.depth = svn_depth_files;
845289177Speter
846289177Speter  err = svn_config_get_config(&(ctx->config),
847289177Speter                              opt_state.config_dir, pool);
848289177Speter  if (err)
849289177Speter    {
850289177Speter      /* Fallback to default config if the config directory isn't readable
851289177Speter         or is not a directory. */
852289177Speter      if (APR_STATUS_IS_EACCES(err->apr_err)
853289177Speter          || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
854289177Speter        {
855289177Speter          svn_handle_warning2(stderr, err, "svn: ");
856289177Speter          svn_error_clear(err);
857289177Speter        }
858289177Speter      else
859289177Speter        return err;
860289177Speter    }
861289177Speter
862289177Speter  cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
863289177Speter                            APR_HASH_KEY_STRING);
864289177Speter
865289177Speter  /* Update the options in the config */
866289177Speter  if (opt_state.config_options)
867289177Speter    {
868289177Speter      svn_error_clear(
869289177Speter          svn_cmdline__apply_config_options(ctx->config,
870289177Speter                                            opt_state.config_options,
871289177Speter                                            "svn: ", "--config-option"));
872289177Speter    }
873289177Speter
874289177Speter  /* Set up the notifier.
875289177Speter
876289177Speter     In general, we use it any time we aren't in --quiet mode.  'svn
877289177Speter     status' is unique, though, in that we don't want it in --quiet mode
878289177Speter     unless we're also in --verbose mode.  When in --xml mode,
879289177Speter     though, we never want it.  */
880289177Speter  if (opt_state.quiet)
881289177Speter    use_notifier = FALSE;
882289177Speter  if (use_notifier)
883289177Speter    {
884289177Speter      SVN_ERR(svn_cl__get_notifier(&ctx->notify_func2, &ctx->notify_baton2,
885289177Speter                                       pool));
886289177Speter    }
887289177Speter
888289177Speter  /* Set up our cancellation support. */
889289177Speter  ctx->cancel_func = svn_cl__check_cancel;
890289177Speter  apr_signal(SIGINT, signal_handler);
891289177Speter#ifdef SIGBREAK
892289177Speter  /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
893289177Speter  apr_signal(SIGBREAK, signal_handler);
894289177Speter#endif
895289177Speter#ifdef SIGHUP
896289177Speter  apr_signal(SIGHUP, signal_handler);
897289177Speter#endif
898289177Speter#ifdef SIGTERM
899289177Speter  apr_signal(SIGTERM, signal_handler);
900289177Speter#endif
901289177Speter
902289177Speter#ifdef SIGPIPE
903289177Speter  /* Disable SIGPIPE generation for the platforms that have it. */
904289177Speter  apr_signal(SIGPIPE, SIG_IGN);
905289177Speter#endif
906289177Speter
907289177Speter#ifdef SIGXFSZ
908289177Speter  /* Disable SIGXFSZ generation for the platforms that have it, otherwise
909289177Speter   * working with large files when compiled against an APR that doesn't have
910289177Speter   * large file support will crash the program, which is uncool. */
911289177Speter  apr_signal(SIGXFSZ, SIG_IGN);
912289177Speter#endif
913289177Speter
914289177Speter  /* Set up Authentication stuff. */
915289177Speter  SVN_ERR(svn_cmdline_create_auth_baton2(
916289177Speter            &ab,
917289177Speter            opt_state.non_interactive,
918289177Speter            opt_state.auth_username,
919289177Speter            opt_state.auth_password,
920289177Speter            opt_state.config_dir,
921289177Speter            opt_state.no_auth_cache,
922289177Speter            opt_state.trust_server_cert_unknown_ca,
923289177Speter            opt_state.trust_server_cert_cn_mismatch,
924289177Speter            opt_state.trust_server_cert_expired,
925289177Speter            opt_state.trust_server_cert_not_yet_valid,
926289177Speter            opt_state.trust_server_cert_other_failure,
927289177Speter            cfg_config,
928289177Speter            ctx->cancel_func,
929289177Speter            ctx->cancel_baton,
930289177Speter            pool));
931289177Speter
932289177Speter  ctx->auth_baton = ab;
933289177Speter
934289177Speter  /* The new svn behavior is to postpone everything until after the operation
935289177Speter     completed */
936289177Speter  ctx->conflict_func = NULL;
937289177Speter  ctx->conflict_baton = NULL;
938289177Speter  ctx->conflict_func2 = NULL;
939289177Speter  ctx->conflict_baton2 = NULL;
940289177Speter
941289177Speter  /* And now we finally run the subcommand. */
942289177Speter  err = (*subcommand->cmd_func)(os, &command_baton, pool);
943289177Speter  if (err)
944289177Speter    {
945289177Speter      /* For argument-related problems, suggest using the 'help'
946289177Speter         subcommand. */
947289177Speter      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
948289177Speter          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
949289177Speter        {
950289177Speter          err = svn_error_quick_wrapf(
951289177Speter                  err, _("Try 'svnbench help %s' for more information"),
952289177Speter                  subcommand->name);
953289177Speter        }
954289177Speter      if (err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED)
955289177Speter        {
956289177Speter          err = svn_error_quick_wrap(err,
957289177Speter                                     _("Please see the 'svn upgrade' command"));
958289177Speter        }
959289177Speter
960289177Speter      /* Tell the user about 'svn cleanup' if any error on the stack
961289177Speter         was about locked working copies. */
962289177Speter      if (svn_error_find_cause(err, SVN_ERR_WC_LOCKED))
963289177Speter        {
964289177Speter          err = svn_error_quick_wrap(
965289177Speter                  err, _("Run 'svn cleanup' to remove locks "
966289177Speter                         "(type 'svn help cleanup' for details)"));
967289177Speter        }
968289177Speter
969289177Speter      return err;
970289177Speter    }
971289177Speter
972289177Speter  return SVN_NO_ERROR;
973289177Speter}
974289177Speter
975289177Speterint
976289177Spetermain(int argc, const char *argv[])
977289177Speter{
978289177Speter  apr_pool_t *pool;
979289177Speter  int exit_code = EXIT_SUCCESS;
980289177Speter  svn_error_t *err;
981289177Speter
982289177Speter  /* Initialize the app. */
983289177Speter  if (svn_cmdline_init("svnbench", stderr) != EXIT_SUCCESS)
984289177Speter    return EXIT_FAILURE;
985289177Speter
986289177Speter  /* Create our top-level pool.  Use a separate mutexless allocator,
987289177Speter   * given this application is single threaded.
988289177Speter   */
989289177Speter  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
990289177Speter
991289177Speter  err = sub_main(&exit_code, argc, argv, pool);
992289177Speter
993289177Speter  /* Flush stdout and report if it fails. It would be flushed on exit anyway
994289177Speter     but this makes sure that output is not silently lost if it fails. */
995289177Speter  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
996289177Speter
997289177Speter  if (err)
998289177Speter    {
999289177Speter      exit_code = EXIT_FAILURE;
1000289177Speter      svn_cmdline_handle_exit_error(err, NULL, "svnbench: ");
1001289177Speter    }
1002289177Speter
1003289177Speter  svn_pool_destroy(pool);
1004289177Speter  return exit_code;
1005289177Speter}
1006