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