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