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