1251881Speter/* 2251881Speter * opt.c : option and argument parsing for Subversion command lines 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter 25251881Speter 26251881Speter#define APR_WANT_STRFUNC 27251881Speter#include <apr_want.h> 28251881Speter 29251881Speter#include <stdio.h> 30251881Speter#include <string.h> 31251881Speter#include <assert.h> 32251881Speter#include <apr_pools.h> 33251881Speter#include <apr_general.h> 34251881Speter#include <apr_lib.h> 35251881Speter#include <apr_file_info.h> 36251881Speter 37251881Speter#include "svn_hash.h" 38251881Speter#include "svn_cmdline.h" 39251881Speter#include "svn_version.h" 40251881Speter#include "svn_types.h" 41251881Speter#include "svn_opt.h" 42251881Speter#include "svn_error.h" 43251881Speter#include "svn_dirent_uri.h" 44251881Speter#include "svn_path.h" 45251881Speter#include "svn_utf.h" 46251881Speter#include "svn_time.h" 47251881Speter#include "svn_props.h" 48251881Speter#include "svn_ctype.h" 49251881Speter 50251881Speter#include "private/svn_opt_private.h" 51251881Speter 52251881Speter#include "opt.h" 53251881Speter#include "svn_private_config.h" 54251881Speter 55251881Speter 56251881Speter/*** Code. ***/ 57251881Speter 58251881Speterconst svn_opt_subcommand_desc2_t * 59251881Spetersvn_opt_get_canonical_subcommand2(const svn_opt_subcommand_desc2_t *table, 60251881Speter const char *cmd_name) 61251881Speter{ 62251881Speter int i = 0; 63251881Speter 64251881Speter if (cmd_name == NULL) 65251881Speter return NULL; 66251881Speter 67251881Speter while (table[i].name) { 68251881Speter int j; 69251881Speter if (strcmp(cmd_name, table[i].name) == 0) 70251881Speter return table + i; 71251881Speter for (j = 0; (j < SVN_OPT_MAX_ALIASES) && table[i].aliases[j]; j++) 72251881Speter if (strcmp(cmd_name, table[i].aliases[j]) == 0) 73251881Speter return table + i; 74251881Speter 75251881Speter i++; 76251881Speter } 77251881Speter 78251881Speter /* If we get here, there was no matching subcommand name or alias. */ 79251881Speter return NULL; 80251881Speter} 81251881Speter 82251881Speterconst apr_getopt_option_t * 83251881Spetersvn_opt_get_option_from_code2(int code, 84251881Speter const apr_getopt_option_t *option_table, 85251881Speter const svn_opt_subcommand_desc2_t *command, 86251881Speter apr_pool_t *pool) 87251881Speter{ 88251881Speter apr_size_t i; 89251881Speter 90251881Speter for (i = 0; option_table[i].optch; i++) 91251881Speter if (option_table[i].optch == code) 92251881Speter { 93251881Speter if (command) 94251881Speter { 95251881Speter int j; 96251881Speter 97251881Speter for (j = 0; ((j < SVN_OPT_MAX_OPTIONS) && 98251881Speter command->desc_overrides[j].optch); j++) 99251881Speter if (command->desc_overrides[j].optch == code) 100251881Speter { 101251881Speter apr_getopt_option_t *tmpopt = 102251881Speter apr_palloc(pool, sizeof(*tmpopt)); 103251881Speter *tmpopt = option_table[i]; 104251881Speter tmpopt->description = command->desc_overrides[j].desc; 105251881Speter return tmpopt; 106251881Speter } 107251881Speter } 108251881Speter return &(option_table[i]); 109251881Speter } 110251881Speter 111251881Speter return NULL; 112251881Speter} 113251881Speter 114251881Speter 115251881Speterconst apr_getopt_option_t * 116251881Spetersvn_opt_get_option_from_code(int code, 117251881Speter const apr_getopt_option_t *option_table) 118251881Speter{ 119251881Speter apr_size_t i; 120251881Speter 121251881Speter for (i = 0; option_table[i].optch; i++) 122251881Speter if (option_table[i].optch == code) 123251881Speter return &(option_table[i]); 124251881Speter 125251881Speter return NULL; 126251881Speter} 127251881Speter 128251881Speter 129251881Speter/* Like svn_opt_get_option_from_code2(), but also, if CODE appears a second 130251881Speter * time in OPTION_TABLE with a different name, then set *LONG_ALIAS to that 131251881Speter * second name, else set it to NULL. */ 132251881Speterstatic const apr_getopt_option_t * 133251881Speterget_option_from_code(const char **long_alias, 134251881Speter int code, 135251881Speter const apr_getopt_option_t *option_table, 136251881Speter const svn_opt_subcommand_desc2_t *command, 137251881Speter apr_pool_t *pool) 138251881Speter{ 139251881Speter const apr_getopt_option_t *i; 140251881Speter const apr_getopt_option_t *opt 141251881Speter = svn_opt_get_option_from_code2(code, option_table, command, pool); 142251881Speter 143251881Speter /* Find a long alias in the table, if there is one. */ 144251881Speter *long_alias = NULL; 145251881Speter for (i = option_table; i->optch; i++) 146251881Speter { 147251881Speter if (i->optch == code && i->name != opt->name) 148251881Speter { 149251881Speter *long_alias = i->name; 150251881Speter break; 151251881Speter } 152251881Speter } 153251881Speter 154251881Speter return opt; 155251881Speter} 156251881Speter 157251881Speter 158251881Speter/* Print an option OPT nicely into a STRING allocated in POOL. 159251881Speter * If OPT has a single-character short form, then print OPT->name (if not 160251881Speter * NULL) as an alias, else print LONG_ALIAS (if not NULL) as an alias. 161251881Speter * If DOC is set, include the generic documentation string of OPT, 162251881Speter * localized to the current locale if a translation is available. 163251881Speter */ 164251881Speterstatic void 165251881Speterformat_option(const char **string, 166251881Speter const apr_getopt_option_t *opt, 167251881Speter const char *long_alias, 168251881Speter svn_boolean_t doc, 169251881Speter apr_pool_t *pool) 170251881Speter{ 171251881Speter char *opts; 172251881Speter 173251881Speter if (opt == NULL) 174251881Speter { 175251881Speter *string = "?"; 176251881Speter return; 177251881Speter } 178251881Speter 179251881Speter /* We have a valid option which may or may not have a "short 180251881Speter name" (a single-character alias for the long option). */ 181251881Speter if (opt->optch <= 255) 182251881Speter opts = apr_psprintf(pool, "-%c [--%s]", opt->optch, opt->name); 183251881Speter else if (long_alias) 184251881Speter opts = apr_psprintf(pool, "--%s [--%s]", opt->name, long_alias); 185251881Speter else 186251881Speter opts = apr_psprintf(pool, "--%s", opt->name); 187251881Speter 188251881Speter if (opt->has_arg) 189299742Sdim opts = apr_pstrcat(pool, opts, _(" ARG"), SVN_VA_NULL); 190251881Speter 191251881Speter if (doc) 192251881Speter opts = apr_psprintf(pool, "%-24s : %s", opts, _(opt->description)); 193251881Speter 194251881Speter *string = opts; 195251881Speter} 196251881Speter 197251881Spetervoid 198251881Spetersvn_opt_format_option(const char **string, 199251881Speter const apr_getopt_option_t *opt, 200251881Speter svn_boolean_t doc, 201251881Speter apr_pool_t *pool) 202251881Speter{ 203251881Speter format_option(string, opt, NULL, doc, pool); 204251881Speter} 205251881Speter 206251881Speter 207251881Spetersvn_boolean_t 208251881Spetersvn_opt_subcommand_takes_option3(const svn_opt_subcommand_desc2_t *command, 209251881Speter int option_code, 210251881Speter const int *global_options) 211251881Speter{ 212251881Speter apr_size_t i; 213251881Speter 214251881Speter for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) 215251881Speter if (command->valid_options[i] == option_code) 216251881Speter return TRUE; 217251881Speter 218251881Speter if (global_options) 219251881Speter for (i = 0; global_options[i]; i++) 220251881Speter if (global_options[i] == option_code) 221251881Speter return TRUE; 222251881Speter 223251881Speter return FALSE; 224251881Speter} 225251881Speter 226251881Spetersvn_boolean_t 227251881Spetersvn_opt_subcommand_takes_option2(const svn_opt_subcommand_desc2_t *command, 228251881Speter int option_code) 229251881Speter{ 230251881Speter return svn_opt_subcommand_takes_option3(command, 231251881Speter option_code, 232251881Speter NULL); 233251881Speter} 234251881Speter 235251881Speter 236251881Spetersvn_boolean_t 237251881Spetersvn_opt_subcommand_takes_option(const svn_opt_subcommand_desc_t *command, 238251881Speter int option_code) 239251881Speter{ 240251881Speter apr_size_t i; 241251881Speter 242251881Speter for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) 243251881Speter if (command->valid_options[i] == option_code) 244251881Speter return TRUE; 245251881Speter 246251881Speter return FALSE; 247251881Speter} 248251881Speter 249251881Speter 250251881Speter/* Print the canonical command name for CMD, and all its aliases, to 251251881Speter STREAM. If HELP is set, print CMD's help string too, in which case 252251881Speter obtain option usage from OPTIONS_TABLE. */ 253251881Speterstatic svn_error_t * 254251881Speterprint_command_info2(const svn_opt_subcommand_desc2_t *cmd, 255251881Speter const apr_getopt_option_t *options_table, 256251881Speter const int *global_options, 257251881Speter svn_boolean_t help, 258251881Speter apr_pool_t *pool, 259251881Speter FILE *stream) 260251881Speter{ 261251881Speter svn_boolean_t first_time; 262251881Speter apr_size_t i; 263251881Speter 264251881Speter /* Print the canonical command name. */ 265251881Speter SVN_ERR(svn_cmdline_fputs(cmd->name, stream, pool)); 266251881Speter 267251881Speter /* Print the list of aliases. */ 268251881Speter first_time = TRUE; 269251881Speter for (i = 0; i < SVN_OPT_MAX_ALIASES; i++) 270251881Speter { 271251881Speter if (cmd->aliases[i] == NULL) 272251881Speter break; 273251881Speter 274251881Speter if (first_time) { 275251881Speter SVN_ERR(svn_cmdline_fputs(" (", stream, pool)); 276251881Speter first_time = FALSE; 277251881Speter } 278251881Speter else 279251881Speter SVN_ERR(svn_cmdline_fputs(", ", stream, pool)); 280251881Speter 281251881Speter SVN_ERR(svn_cmdline_fputs(cmd->aliases[i], stream, pool)); 282251881Speter } 283251881Speter 284251881Speter if (! first_time) 285251881Speter SVN_ERR(svn_cmdline_fputs(")", stream, pool)); 286251881Speter 287251881Speter if (help) 288251881Speter { 289251881Speter const apr_getopt_option_t *option; 290251881Speter const char *long_alias; 291251881Speter svn_boolean_t have_options = FALSE; 292251881Speter 293251881Speter SVN_ERR(svn_cmdline_fprintf(stream, pool, ": %s", _(cmd->help))); 294251881Speter 295251881Speter /* Loop over all valid option codes attached to the subcommand */ 296251881Speter for (i = 0; i < SVN_OPT_MAX_OPTIONS; i++) 297251881Speter { 298251881Speter if (cmd->valid_options[i]) 299251881Speter { 300251881Speter if (!have_options) 301251881Speter { 302251881Speter SVN_ERR(svn_cmdline_fputs(_("\nValid options:\n"), 303251881Speter stream, pool)); 304251881Speter have_options = TRUE; 305251881Speter } 306251881Speter 307251881Speter /* convert each option code into an option */ 308251881Speter option = get_option_from_code(&long_alias, cmd->valid_options[i], 309251881Speter options_table, cmd, pool); 310251881Speter 311251881Speter /* print the option's docstring */ 312251881Speter if (option && option->description) 313251881Speter { 314251881Speter const char *optstr; 315251881Speter format_option(&optstr, option, long_alias, TRUE, pool); 316251881Speter SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n", 317251881Speter optstr)); 318251881Speter } 319251881Speter } 320251881Speter } 321251881Speter /* And global options too */ 322251881Speter if (global_options && *global_options) 323251881Speter { 324251881Speter SVN_ERR(svn_cmdline_fputs(_("\nGlobal options:\n"), 325251881Speter stream, pool)); 326251881Speter have_options = TRUE; 327251881Speter 328251881Speter for (i = 0; global_options[i]; i++) 329251881Speter { 330251881Speter 331251881Speter /* convert each option code into an option */ 332251881Speter option = get_option_from_code(&long_alias, global_options[i], 333251881Speter options_table, cmd, pool); 334251881Speter 335251881Speter /* print the option's docstring */ 336251881Speter if (option && option->description) 337251881Speter { 338251881Speter const char *optstr; 339251881Speter format_option(&optstr, option, long_alias, TRUE, pool); 340251881Speter SVN_ERR(svn_cmdline_fprintf(stream, pool, " %s\n", 341251881Speter optstr)); 342251881Speter } 343251881Speter } 344251881Speter } 345251881Speter 346251881Speter if (have_options) 347251881Speter SVN_ERR(svn_cmdline_fprintf(stream, pool, "\n")); 348251881Speter } 349251881Speter 350251881Speter return SVN_NO_ERROR; 351251881Speter} 352251881Speter 353251881Spetervoid 354251881Spetersvn_opt_print_generic_help2(const char *header, 355251881Speter const svn_opt_subcommand_desc2_t *cmd_table, 356251881Speter const apr_getopt_option_t *opt_table, 357251881Speter const char *footer, 358251881Speter apr_pool_t *pool, FILE *stream) 359251881Speter{ 360251881Speter int i = 0; 361251881Speter svn_error_t *err; 362251881Speter 363251881Speter if (header) 364251881Speter if ((err = svn_cmdline_fputs(header, stream, pool))) 365251881Speter goto print_error; 366251881Speter 367251881Speter while (cmd_table[i].name) 368251881Speter { 369251881Speter if ((err = svn_cmdline_fputs(" ", stream, pool)) 370251881Speter || (err = print_command_info2(cmd_table + i, opt_table, 371251881Speter NULL, FALSE, 372251881Speter pool, stream)) 373251881Speter || (err = svn_cmdline_fputs("\n", stream, pool))) 374251881Speter goto print_error; 375251881Speter i++; 376251881Speter } 377251881Speter 378251881Speter if ((err = svn_cmdline_fputs("\n", stream, pool))) 379251881Speter goto print_error; 380251881Speter 381251881Speter if (footer) 382251881Speter if ((err = svn_cmdline_fputs(footer, stream, pool))) 383251881Speter goto print_error; 384251881Speter 385251881Speter return; 386251881Speter 387251881Speter print_error: 388251881Speter /* Issue #3014: 389251881Speter * Don't print anything on broken pipes. The pipe was likely 390251881Speter * closed by the process at the other end. We expect that 391251881Speter * process to perform error reporting as necessary. 392251881Speter * 393251881Speter * ### This assumes that there is only one error in a chain for 394251881Speter * ### SVN_ERR_IO_PIPE_WRITE_ERROR. See svn_cmdline_fputs(). */ 395251881Speter if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR) 396251881Speter svn_handle_error2(err, stderr, FALSE, "svn: "); 397251881Speter svn_error_clear(err); 398251881Speter} 399251881Speter 400251881Speter 401251881Spetervoid 402251881Spetersvn_opt_subcommand_help3(const char *subcommand, 403251881Speter const svn_opt_subcommand_desc2_t *table, 404251881Speter const apr_getopt_option_t *options_table, 405251881Speter const int *global_options, 406251881Speter apr_pool_t *pool) 407251881Speter{ 408251881Speter const svn_opt_subcommand_desc2_t *cmd = 409251881Speter svn_opt_get_canonical_subcommand2(table, subcommand); 410251881Speter svn_error_t *err; 411251881Speter 412251881Speter if (cmd) 413251881Speter err = print_command_info2(cmd, options_table, global_options, 414251881Speter TRUE, pool, stdout); 415251881Speter else 416251881Speter err = svn_cmdline_fprintf(stderr, pool, 417251881Speter _("\"%s\": unknown command.\n\n"), subcommand); 418251881Speter 419251881Speter if (err) { 420269847Speter /* Issue #3014: Don't print anything on broken pipes. */ 421269847Speter if (err->apr_err != SVN_ERR_IO_PIPE_WRITE_ERROR) 422269847Speter svn_handle_error2(err, stderr, FALSE, "svn: "); 423251881Speter svn_error_clear(err); 424251881Speter } 425251881Speter} 426251881Speter 427251881Speter 428251881Speter 429251881Speter/*** Parsing revision and date options. ***/ 430251881Speter 431251881Speter 432251881Speter/** Parsing "X:Y"-style arguments. **/ 433251881Speter 434251881Speter/* If WORD matches one of the special revision descriptors, 435251881Speter * case-insensitively, set *REVISION accordingly: 436251881Speter * 437251881Speter * - For "head", set REVISION->kind to svn_opt_revision_head. 438251881Speter * 439251881Speter * - For "prev", set REVISION->kind to svn_opt_revision_previous. 440251881Speter * 441251881Speter * - For "base", set REVISION->kind to svn_opt_revision_base. 442251881Speter * 443251881Speter * - For "committed", set REVISION->kind to svn_opt_revision_committed. 444251881Speter * 445251881Speter * If match, return 0, else return -1 and don't touch REVISION. 446251881Speter */ 447251881Speterstatic int 448251881Speterrevision_from_word(svn_opt_revision_t *revision, const char *word) 449251881Speter{ 450251881Speter if (svn_cstring_casecmp(word, "head") == 0) 451251881Speter { 452251881Speter revision->kind = svn_opt_revision_head; 453251881Speter } 454251881Speter else if (svn_cstring_casecmp(word, "prev") == 0) 455251881Speter { 456251881Speter revision->kind = svn_opt_revision_previous; 457251881Speter } 458251881Speter else if (svn_cstring_casecmp(word, "base") == 0) 459251881Speter { 460251881Speter revision->kind = svn_opt_revision_base; 461251881Speter } 462251881Speter else if (svn_cstring_casecmp(word, "committed") == 0) 463251881Speter { 464251881Speter revision->kind = svn_opt_revision_committed; 465251881Speter } 466251881Speter else 467251881Speter return -1; 468251881Speter 469251881Speter return 0; 470251881Speter} 471251881Speter 472251881Speter 473251881Speter/* Parse one revision specification. Return pointer to character 474251881Speter after revision, or NULL if the revision is invalid. Modifies 475251881Speter str, so make sure to pass a copy of anything precious. Uses 476251881Speter POOL for temporary allocation. */ 477251881Speterstatic char *parse_one_rev(svn_opt_revision_t *revision, char *str, 478251881Speter apr_pool_t *pool) 479251881Speter{ 480251881Speter char *end, save; 481251881Speter 482251881Speter /* Allow any number of 'r's to prefix a revision number, because 483251881Speter that way if a script pastes svn output into another svn command 484251881Speter (like "svn log -r${REV_COPIED_FROM_OUTPUT}"), it'll Just Work, 485251881Speter even when compounded. 486251881Speter 487251881Speter As it happens, none of our special revision words begins with 488251881Speter "r". If any ever do, then this code will have to get smarter. 489251881Speter 490251881Speter Incidentally, this allows "r{DATE}". We could avoid that with 491251881Speter some trivial code rearrangement, but it's not clear what would 492251881Speter be gained by doing so. */ 493251881Speter while (*str == 'r') 494251881Speter str++; 495251881Speter 496251881Speter if (*str == '{') 497251881Speter { 498251881Speter svn_boolean_t matched; 499251881Speter apr_time_t tm; 500251881Speter svn_error_t *err; 501251881Speter 502251881Speter /* Brackets denote a date. */ 503251881Speter str++; 504251881Speter end = strchr(str, '}'); 505251881Speter if (!end) 506251881Speter return NULL; 507251881Speter *end = '\0'; 508251881Speter err = svn_parse_date(&matched, &tm, str, apr_time_now(), pool); 509251881Speter if (err) 510251881Speter { 511251881Speter svn_error_clear(err); 512251881Speter return NULL; 513251881Speter } 514251881Speter if (!matched) 515251881Speter return NULL; 516251881Speter revision->kind = svn_opt_revision_date; 517251881Speter revision->value.date = tm; 518251881Speter return end + 1; 519251881Speter } 520251881Speter else if (svn_ctype_isdigit(*str)) 521251881Speter { 522251881Speter /* It's a number. */ 523251881Speter end = str + 1; 524251881Speter while (svn_ctype_isdigit(*end)) 525251881Speter end++; 526251881Speter save = *end; 527251881Speter *end = '\0'; 528251881Speter revision->kind = svn_opt_revision_number; 529251881Speter revision->value.number = SVN_STR_TO_REV(str); 530251881Speter *end = save; 531251881Speter return end; 532251881Speter } 533251881Speter else if (svn_ctype_isalpha(*str)) 534251881Speter { 535251881Speter end = str + 1; 536251881Speter while (svn_ctype_isalpha(*end)) 537251881Speter end++; 538251881Speter save = *end; 539251881Speter *end = '\0'; 540251881Speter if (revision_from_word(revision, str) != 0) 541251881Speter return NULL; 542251881Speter *end = save; 543251881Speter return end; 544251881Speter } 545251881Speter else 546251881Speter return NULL; 547251881Speter} 548251881Speter 549251881Speter 550251881Speterint 551251881Spetersvn_opt_parse_revision(svn_opt_revision_t *start_revision, 552251881Speter svn_opt_revision_t *end_revision, 553251881Speter const char *arg, 554251881Speter apr_pool_t *pool) 555251881Speter{ 556251881Speter char *left_rev, *right_rev, *end; 557251881Speter 558251881Speter /* Operate on a copy of the argument. */ 559251881Speter left_rev = apr_pstrdup(pool, arg); 560251881Speter 561251881Speter right_rev = parse_one_rev(start_revision, left_rev, pool); 562251881Speter if (right_rev && *right_rev == ':') 563251881Speter { 564251881Speter right_rev++; 565251881Speter end = parse_one_rev(end_revision, right_rev, pool); 566251881Speter if (!end || *end != '\0') 567251881Speter return -1; 568251881Speter } 569251881Speter else if (!right_rev || *right_rev != '\0') 570251881Speter return -1; 571251881Speter 572251881Speter return 0; 573251881Speter} 574251881Speter 575251881Speter 576251881Speterint 577251881Spetersvn_opt_parse_revision_to_range(apr_array_header_t *opt_ranges, 578251881Speter const char *arg, 579251881Speter apr_pool_t *pool) 580251881Speter{ 581251881Speter svn_opt_revision_range_t *range = apr_palloc(pool, sizeof(*range)); 582251881Speter 583251881Speter range->start.kind = svn_opt_revision_unspecified; 584251881Speter range->end.kind = svn_opt_revision_unspecified; 585251881Speter 586251881Speter if (svn_opt_parse_revision(&(range->start), &(range->end), 587251881Speter arg, pool) == -1) 588251881Speter return -1; 589251881Speter 590251881Speter APR_ARRAY_PUSH(opt_ranges, svn_opt_revision_range_t *) = range; 591251881Speter return 0; 592251881Speter} 593251881Speter 594251881Spetersvn_error_t * 595251881Spetersvn_opt_resolve_revisions(svn_opt_revision_t *peg_rev, 596251881Speter svn_opt_revision_t *op_rev, 597251881Speter svn_boolean_t is_url, 598251881Speter svn_boolean_t notice_local_mods, 599251881Speter apr_pool_t *pool) 600251881Speter{ 601251881Speter if (peg_rev->kind == svn_opt_revision_unspecified) 602251881Speter { 603251881Speter if (is_url) 604251881Speter { 605251881Speter peg_rev->kind = svn_opt_revision_head; 606251881Speter } 607251881Speter else 608251881Speter { 609251881Speter if (notice_local_mods) 610251881Speter peg_rev->kind = svn_opt_revision_working; 611251881Speter else 612251881Speter peg_rev->kind = svn_opt_revision_base; 613251881Speter } 614251881Speter } 615251881Speter 616251881Speter if (op_rev->kind == svn_opt_revision_unspecified) 617251881Speter *op_rev = *peg_rev; 618251881Speter 619251881Speter return SVN_NO_ERROR; 620251881Speter} 621251881Speter 622251881Speterconst char * 623251881Spetersvn_opt__revision_to_string(const svn_opt_revision_t *revision, 624251881Speter apr_pool_t *result_pool) 625251881Speter{ 626251881Speter switch (revision->kind) 627251881Speter { 628251881Speter case svn_opt_revision_unspecified: 629251881Speter return "unspecified"; 630251881Speter case svn_opt_revision_number: 631251881Speter return apr_psprintf(result_pool, "%ld", revision->value.number); 632251881Speter case svn_opt_revision_date: 633251881Speter /* ### svn_time_to_human_cstring()? */ 634251881Speter return svn_time_to_cstring(revision->value.date, result_pool); 635251881Speter case svn_opt_revision_committed: 636251881Speter return "committed"; 637251881Speter case svn_opt_revision_previous: 638251881Speter return "previous"; 639251881Speter case svn_opt_revision_base: 640251881Speter return "base"; 641251881Speter case svn_opt_revision_working: 642251881Speter return "working"; 643251881Speter case svn_opt_revision_head: 644251881Speter return "head"; 645251881Speter default: 646251881Speter return NULL; 647251881Speter } 648251881Speter} 649251881Speter 650251881Spetersvn_opt_revision_range_t * 651251881Spetersvn_opt__revision_range_create(const svn_opt_revision_t *start_revision, 652251881Speter const svn_opt_revision_t *end_revision, 653251881Speter apr_pool_t *result_pool) 654251881Speter{ 655251881Speter svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range)); 656251881Speter 657251881Speter range->start = *start_revision; 658251881Speter range->end = *end_revision; 659251881Speter return range; 660251881Speter} 661251881Speter 662251881Spetersvn_opt_revision_range_t * 663251881Spetersvn_opt__revision_range_from_revnums(svn_revnum_t start_revnum, 664251881Speter svn_revnum_t end_revnum, 665251881Speter apr_pool_t *result_pool) 666251881Speter{ 667251881Speter svn_opt_revision_range_t *range = apr_palloc(result_pool, sizeof(*range)); 668251881Speter 669251881Speter range->start.kind = svn_opt_revision_number; 670251881Speter range->start.value.number = start_revnum; 671251881Speter range->end.kind = svn_opt_revision_number; 672251881Speter range->end.value.number = end_revnum; 673251881Speter return range; 674251881Speter} 675251881Speter 676251881Speter 677251881Speter 678251881Speter/*** Parsing arguments. ***/ 679251881Speter#define DEFAULT_ARRAY_SIZE 5 680251881Speter 681251881Speter 682251881Speter/* Copy STR into POOL and push the copy onto ARRAY. */ 683251881Speterstatic void 684251881Speterarray_push_str(apr_array_header_t *array, 685251881Speter const char *str, 686251881Speter apr_pool_t *pool) 687251881Speter{ 688251881Speter /* ### Not sure if this function is still necessary. It used to 689251881Speter convert str to svn_stringbuf_t * and push it, but now it just 690251881Speter dups str in pool and pushes the copy. So its only effect is 691251881Speter transfer str's lifetime to pool. Is that something callers are 692251881Speter depending on? */ 693251881Speter 694251881Speter APR_ARRAY_PUSH(array, const char *) = apr_pstrdup(pool, str); 695251881Speter} 696251881Speter 697251881Speter 698251881Spetervoid 699251881Spetersvn_opt_push_implicit_dot_target(apr_array_header_t *targets, 700251881Speter apr_pool_t *pool) 701251881Speter{ 702251881Speter if (targets->nelts == 0) 703251881Speter APR_ARRAY_PUSH(targets, const char *) = ""; /* Ha! "", not ".", is the canonical */ 704251881Speter assert(targets->nelts); 705251881Speter} 706251881Speter 707251881Speter 708251881Spetersvn_error_t * 709251881Spetersvn_opt_parse_num_args(apr_array_header_t **args_p, 710251881Speter apr_getopt_t *os, 711251881Speter int num_args, 712251881Speter apr_pool_t *pool) 713251881Speter{ 714251881Speter int i; 715251881Speter apr_array_header_t *args 716251881Speter = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); 717251881Speter 718251881Speter /* loop for num_args and add each arg to the args array */ 719251881Speter for (i = 0; i < num_args; i++) 720251881Speter { 721251881Speter if (os->ind >= os->argc) 722251881Speter { 723251881Speter return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, NULL); 724251881Speter } 725251881Speter array_push_str(args, os->argv[os->ind++], pool); 726251881Speter } 727251881Speter 728251881Speter *args_p = args; 729251881Speter return SVN_NO_ERROR; 730251881Speter} 731251881Speter 732251881Spetersvn_error_t * 733251881Spetersvn_opt_parse_all_args(apr_array_header_t **args_p, 734251881Speter apr_getopt_t *os, 735251881Speter apr_pool_t *pool) 736251881Speter{ 737251881Speter apr_array_header_t *args 738251881Speter = apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); 739251881Speter 740251881Speter if (os->ind > os->argc) 741251881Speter { 742251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, NULL); 743251881Speter } 744251881Speter while (os->ind < os->argc) 745251881Speter { 746251881Speter array_push_str(args, os->argv[os->ind++], pool); 747251881Speter } 748251881Speter 749251881Speter *args_p = args; 750251881Speter return SVN_NO_ERROR; 751251881Speter} 752251881Speter 753251881Speter 754251881Spetersvn_error_t * 755251881Spetersvn_opt_parse_path(svn_opt_revision_t *rev, 756251881Speter const char **truepath, 757251881Speter const char *path /* UTF-8! */, 758251881Speter apr_pool_t *pool) 759251881Speter{ 760251881Speter const char *peg_rev; 761251881Speter 762251881Speter SVN_ERR(svn_opt__split_arg_at_peg_revision(truepath, &peg_rev, path, pool)); 763251881Speter 764251881Speter /* Parse the peg revision, if one was found */ 765251881Speter if (strlen(peg_rev)) 766251881Speter { 767251881Speter int ret; 768251881Speter svn_opt_revision_t start_revision, end_revision; 769251881Speter 770251881Speter end_revision.kind = svn_opt_revision_unspecified; 771251881Speter 772251881Speter if (peg_rev[1] == '\0') /* looking at empty peg revision */ 773251881Speter { 774251881Speter ret = 0; 775251881Speter start_revision.kind = svn_opt_revision_unspecified; 776251881Speter start_revision.value.number = 0; 777251881Speter } 778251881Speter else /* looking at non-empty peg revision */ 779251881Speter { 780251881Speter const char *rev_str = &peg_rev[1]; 781251881Speter 782251881Speter /* URLs get treated differently from wc paths. */ 783251881Speter if (svn_path_is_url(path)) 784251881Speter { 785251881Speter /* URLs are URI-encoded, so we look for dates with 786299742Sdim URI-encoded delimiters. */ 787251881Speter size_t rev_len = strlen(rev_str); 788251881Speter if (rev_len > 6 789251881Speter && rev_str[0] == '%' 790251881Speter && rev_str[1] == '7' 791251881Speter && (rev_str[2] == 'B' 792251881Speter || rev_str[2] == 'b') 793251881Speter && rev_str[rev_len-3] == '%' 794251881Speter && rev_str[rev_len-2] == '7' 795251881Speter && (rev_str[rev_len-1] == 'D' 796251881Speter || rev_str[rev_len-1] == 'd')) 797251881Speter { 798251881Speter rev_str = svn_path_uri_decode(rev_str, pool); 799251881Speter } 800251881Speter } 801251881Speter ret = svn_opt_parse_revision(&start_revision, 802251881Speter &end_revision, 803251881Speter rev_str, pool); 804251881Speter } 805251881Speter 806251881Speter if (ret || end_revision.kind != svn_opt_revision_unspecified) 807251881Speter { 808251881Speter /* If an svn+ssh URL was used and it contains only one @, 809251881Speter * provide an error message that presents a possible solution 810251881Speter * to the parsing error (issue #2349). */ 811251881Speter if (strncmp(path, "svn+ssh://", 10) == 0) 812251881Speter { 813251881Speter const char *at; 814251881Speter 815251881Speter at = strchr(path, '@'); 816251881Speter if (at && strrchr(path, '@') == at) 817251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 818251881Speter _("Syntax error parsing peg revision " 819251881Speter "'%s'; did you mean '%s@'?"), 820251881Speter &peg_rev[1], path); 821251881Speter } 822251881Speter 823251881Speter return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 824251881Speter _("Syntax error parsing peg revision '%s'"), 825251881Speter &peg_rev[1]); 826251881Speter } 827251881Speter rev->kind = start_revision.kind; 828251881Speter rev->value = start_revision.value; 829251881Speter } 830251881Speter else 831251881Speter { 832251881Speter /* Didn't find a peg revision. */ 833251881Speter rev->kind = svn_opt_revision_unspecified; 834251881Speter } 835251881Speter 836251881Speter return SVN_NO_ERROR; 837251881Speter} 838251881Speter 839251881Speter 840251881Speter/* Note: This is substantially copied into svn_client_args_to_target_array() in 841251881Speter * order to move to libsvn_client while maintaining backward compatibility. */ 842251881Spetersvn_error_t * 843251881Spetersvn_opt__args_to_target_array(apr_array_header_t **targets_p, 844251881Speter apr_getopt_t *os, 845251881Speter const apr_array_header_t *known_targets, 846251881Speter apr_pool_t *pool) 847251881Speter{ 848251881Speter int i; 849251881Speter svn_error_t *err = SVN_NO_ERROR; 850251881Speter apr_array_header_t *input_targets = 851251881Speter apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); 852251881Speter apr_array_header_t *output_targets = 853251881Speter apr_array_make(pool, DEFAULT_ARRAY_SIZE, sizeof(const char *)); 854251881Speter 855251881Speter /* Step 1: create a master array of targets that are in UTF-8 856251881Speter encoding, and come from concatenating the targets left by apr_getopt, 857251881Speter plus any extra targets (e.g., from the --targets switch.) */ 858251881Speter 859251881Speter for (; os->ind < os->argc; os->ind++) 860251881Speter { 861251881Speter /* The apr_getopt targets are still in native encoding. */ 862251881Speter const char *raw_target = os->argv[os->ind]; 863251881Speter SVN_ERR(svn_utf_cstring_to_utf8 864251881Speter ((const char **) apr_array_push(input_targets), 865251881Speter raw_target, pool)); 866251881Speter } 867251881Speter 868251881Speter if (known_targets) 869251881Speter { 870251881Speter for (i = 0; i < known_targets->nelts; i++) 871251881Speter { 872251881Speter /* The --targets array have already been converted to UTF-8, 873251881Speter because we needed to split up the list with svn_cstring_split. */ 874251881Speter const char *utf8_target = APR_ARRAY_IDX(known_targets, 875251881Speter i, const char *); 876251881Speter APR_ARRAY_PUSH(input_targets, const char *) = utf8_target; 877251881Speter } 878251881Speter } 879251881Speter 880251881Speter /* Step 2: process each target. */ 881251881Speter 882251881Speter for (i = 0; i < input_targets->nelts; i++) 883251881Speter { 884251881Speter const char *utf8_target = APR_ARRAY_IDX(input_targets, i, const char *); 885251881Speter const char *true_target; 886251881Speter const char *target; /* after all processing is finished */ 887251881Speter const char *peg_rev; 888251881Speter 889251881Speter /* 890251881Speter * This is needed so that the target can be properly canonicalized, 891251881Speter * otherwise the canonicalization does not treat a ".@BASE" as a "." 892251881Speter * with a BASE peg revision, and it is not canonicalized to "@BASE". 893251881Speter * If any peg revision exists, it is appended to the final 894251881Speter * canonicalized path or URL. Do not use svn_opt_parse_path() 895251881Speter * because the resulting peg revision is a structure that would have 896251881Speter * to be converted back into a string. Converting from a string date 897251881Speter * to the apr_time_t field in the svn_opt_revision_value_t and back to 898251881Speter * a string would not necessarily preserve the exact bytes of the 899251881Speter * input date, so its easier just to keep it in string form. 900251881Speter */ 901251881Speter SVN_ERR(svn_opt__split_arg_at_peg_revision(&true_target, &peg_rev, 902251881Speter utf8_target, pool)); 903251881Speter 904251881Speter /* URLs and wc-paths get treated differently. */ 905251881Speter if (svn_path_is_url(true_target)) 906251881Speter { 907251881Speter SVN_ERR(svn_opt__arg_canonicalize_url(&true_target, true_target, 908251881Speter pool)); 909251881Speter } 910251881Speter else /* not a url, so treat as a path */ 911251881Speter { 912251881Speter const char *base_name; 913251881Speter 914251881Speter SVN_ERR(svn_opt__arg_canonicalize_path(&true_target, true_target, 915251881Speter pool)); 916251881Speter 917251881Speter /* If the target has the same name as a Subversion 918251881Speter working copy administrative dir, skip it. */ 919251881Speter base_name = svn_dirent_basename(true_target, pool); 920251881Speter 921251881Speter /* FIXME: 922251881Speter The canonical list of administrative directory names is 923251881Speter maintained in libsvn_wc/adm_files.c:svn_wc_set_adm_dir(). 924251881Speter That list can't be used here, because that use would 925251881Speter create a circular dependency between libsvn_wc and 926251881Speter libsvn_subr. Make sure changes to the lists are always 927251881Speter synchronized! */ 928251881Speter if (0 == strcmp(base_name, ".svn") 929251881Speter || 0 == strcmp(base_name, "_svn")) 930251881Speter { 931251881Speter err = svn_error_createf(SVN_ERR_RESERVED_FILENAME_SPECIFIED, 932251881Speter err, _("'%s' ends in a reserved name"), 933251881Speter utf8_target); 934251881Speter continue; 935251881Speter } 936251881Speter } 937251881Speter 938299742Sdim target = apr_pstrcat(pool, true_target, peg_rev, SVN_VA_NULL); 939251881Speter 940251881Speter APR_ARRAY_PUSH(output_targets, const char *) = target; 941251881Speter } 942251881Speter 943251881Speter 944251881Speter /* kff todo: need to remove redundancies from targets before 945251881Speter passing it to the cmd_func. */ 946251881Speter 947251881Speter *targets_p = output_targets; 948251881Speter 949251881Speter return err; 950251881Speter} 951251881Speter 952251881Spetersvn_error_t * 953251881Spetersvn_opt_parse_revprop(apr_hash_t **revprop_table_p, const char *revprop_spec, 954251881Speter apr_pool_t *pool) 955251881Speter{ 956251881Speter const char *sep, *propname; 957251881Speter svn_string_t *propval; 958251881Speter 959251881Speter if (! *revprop_spec) 960251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 961251881Speter _("Revision property pair is empty")); 962251881Speter 963251881Speter if (! *revprop_table_p) 964251881Speter *revprop_table_p = apr_hash_make(pool); 965251881Speter 966251881Speter sep = strchr(revprop_spec, '='); 967251881Speter if (sep) 968251881Speter { 969251881Speter propname = apr_pstrndup(pool, revprop_spec, sep - revprop_spec); 970251881Speter SVN_ERR(svn_utf_cstring_to_utf8(&propname, propname, pool)); 971251881Speter propval = svn_string_create(sep + 1, pool); 972251881Speter } 973251881Speter else 974251881Speter { 975251881Speter SVN_ERR(svn_utf_cstring_to_utf8(&propname, revprop_spec, pool)); 976251881Speter propval = svn_string_create_empty(pool); 977251881Speter } 978251881Speter 979251881Speter if (!svn_prop_name_is_valid(propname)) 980251881Speter return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 981251881Speter _("'%s' is not a valid Subversion property name"), 982251881Speter propname); 983251881Speter 984251881Speter svn_hash_sets(*revprop_table_p, propname, propval); 985251881Speter 986251881Speter return SVN_NO_ERROR; 987251881Speter} 988251881Speter 989251881Spetersvn_error_t * 990251881Spetersvn_opt__split_arg_at_peg_revision(const char **true_target, 991251881Speter const char **peg_revision, 992251881Speter const char *utf8_target, 993251881Speter apr_pool_t *pool) 994251881Speter{ 995251881Speter const char *peg_start = NULL; /* pointer to the peg revision, if any */ 996251881Speter const char *ptr; 997251881Speter 998251881Speter for (ptr = (utf8_target + strlen(utf8_target) - 1); ptr >= utf8_target; 999251881Speter --ptr) 1000251881Speter { 1001251881Speter /* If we hit a path separator, stop looking. This is OK 1002251881Speter only because our revision specifiers can't contain '/'. */ 1003251881Speter if (*ptr == '/') 1004251881Speter break; 1005251881Speter 1006251881Speter if (*ptr == '@') 1007251881Speter { 1008251881Speter peg_start = ptr; 1009251881Speter break; 1010251881Speter } 1011251881Speter } 1012251881Speter 1013251881Speter if (peg_start) 1014251881Speter { 1015251881Speter *true_target = apr_pstrmemdup(pool, utf8_target, ptr - utf8_target); 1016251881Speter if (peg_revision) 1017251881Speter *peg_revision = apr_pstrdup(pool, peg_start); 1018251881Speter } 1019251881Speter else 1020251881Speter { 1021251881Speter *true_target = utf8_target; 1022251881Speter if (peg_revision) 1023251881Speter *peg_revision = ""; 1024251881Speter } 1025251881Speter 1026251881Speter return SVN_NO_ERROR; 1027251881Speter} 1028251881Speter 1029251881Spetersvn_error_t * 1030251881Spetersvn_opt__arg_canonicalize_url(const char **url_out, const char *url_in, 1031251881Speter apr_pool_t *pool) 1032251881Speter{ 1033251881Speter const char *target; 1034251881Speter 1035251881Speter /* Convert to URI. */ 1036251881Speter target = svn_path_uri_from_iri(url_in, pool); 1037251881Speter /* Auto-escape some ASCII characters. */ 1038251881Speter target = svn_path_uri_autoescape(target, pool); 1039251881Speter 1040251881Speter#if '/' != SVN_PATH_LOCAL_SEPARATOR 1041251881Speter /* Allow using file:///C:\users\me/repos on Windows, like we did in 1.6 */ 1042251881Speter if (strchr(target, SVN_PATH_LOCAL_SEPARATOR)) 1043251881Speter { 1044251881Speter char *p = apr_pstrdup(pool, target); 1045251881Speter target = p; 1046251881Speter 1047251881Speter /* Convert all local-style separators to the canonical ones. */ 1048251881Speter for (; *p != '\0'; ++p) 1049251881Speter if (*p == SVN_PATH_LOCAL_SEPARATOR) 1050251881Speter *p = '/'; 1051251881Speter } 1052251881Speter#endif 1053251881Speter 1054251881Speter /* Verify that no backpaths are present in the URL. */ 1055251881Speter if (svn_path_is_backpath_present(target)) 1056251881Speter return svn_error_createf(SVN_ERR_BAD_URL, 0, 1057251881Speter _("URL '%s' contains a '..' element"), 1058251881Speter target); 1059251881Speter 1060251881Speter /* Strip any trailing '/' and collapse other redundant elements. */ 1061251881Speter target = svn_uri_canonicalize(target, pool); 1062251881Speter 1063251881Speter *url_out = target; 1064251881Speter return SVN_NO_ERROR; 1065251881Speter} 1066251881Speter 1067251881Spetersvn_error_t * 1068251881Spetersvn_opt__arg_canonicalize_path(const char **path_out, const char *path_in, 1069251881Speter apr_pool_t *pool) 1070251881Speter{ 1071251881Speter const char *apr_target; 1072251881Speter char *truenamed_target; /* APR-encoded */ 1073251881Speter apr_status_t apr_err; 1074251881Speter 1075251881Speter /* canonicalize case, and change all separators to '/'. */ 1076251881Speter SVN_ERR(svn_path_cstring_from_utf8(&apr_target, path_in, pool)); 1077251881Speter apr_err = apr_filepath_merge(&truenamed_target, "", apr_target, 1078251881Speter APR_FILEPATH_TRUENAME, pool); 1079251881Speter 1080251881Speter if (!apr_err) 1081251881Speter /* We have a canonicalized APR-encoded target now. */ 1082251881Speter apr_target = truenamed_target; 1083251881Speter else if (APR_STATUS_IS_ENOENT(apr_err)) 1084251881Speter /* It's okay for the file to not exist, that just means we 1085251881Speter have to accept the case given to the client. We'll use 1086251881Speter the original APR-encoded target. */ 1087251881Speter ; 1088251881Speter else 1089251881Speter return svn_error_createf(apr_err, NULL, 1090251881Speter _("Error resolving case of '%s'"), 1091251881Speter svn_dirent_local_style(path_in, pool)); 1092251881Speter 1093251881Speter /* convert back to UTF-8. */ 1094251881Speter SVN_ERR(svn_path_cstring_to_utf8(path_out, apr_target, pool)); 1095251881Speter *path_out = svn_dirent_canonicalize(*path_out, pool); 1096251881Speter 1097251881Speter return SVN_NO_ERROR; 1098251881Speter} 1099251881Speter 1100251881Speter 1101251881Spetersvn_error_t * 1102251881Spetersvn_opt__print_version_info(const char *pgm_name, 1103251881Speter const char *footer, 1104251881Speter const svn_version_extended_t *info, 1105251881Speter svn_boolean_t quiet, 1106251881Speter svn_boolean_t verbose, 1107251881Speter apr_pool_t *pool) 1108251881Speter{ 1109251881Speter if (quiet) 1110251881Speter return svn_cmdline_printf(pool, "%s\n", SVN_VER_NUMBER); 1111251881Speter 1112251881Speter SVN_ERR(svn_cmdline_printf(pool, _("%s, version %s\n" 1113257286Scperciva " compiled on %s\n\n"), 1114251881Speter pgm_name, SVN_VERSION, 1115251881Speter svn_version_ext_build_host(info))); 1116251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s\n", svn_version_ext_copyright(info))); 1117251881Speter 1118251881Speter if (footer) 1119251881Speter { 1120251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s\n", footer)); 1121251881Speter } 1122251881Speter 1123251881Speter if (verbose) 1124251881Speter { 1125251881Speter const apr_array_header_t *libs; 1126251881Speter 1127251881Speter SVN_ERR(svn_cmdline_fputs(_("System information:\n\n"), stdout, pool)); 1128251881Speter SVN_ERR(svn_cmdline_printf(pool, _("* running on %s\n"), 1129251881Speter svn_version_ext_runtime_host(info))); 1130251881Speter if (svn_version_ext_runtime_osname(info)) 1131251881Speter { 1132251881Speter SVN_ERR(svn_cmdline_printf(pool, _(" - %s\n"), 1133251881Speter svn_version_ext_runtime_osname(info))); 1134251881Speter } 1135251881Speter 1136251881Speter libs = svn_version_ext_linked_libs(info); 1137251881Speter if (libs && libs->nelts) 1138251881Speter { 1139251881Speter const svn_version_ext_linked_lib_t *lib; 1140251881Speter int i; 1141251881Speter 1142251881Speter SVN_ERR(svn_cmdline_fputs(_("* linked dependencies:\n"), 1143251881Speter stdout, pool)); 1144251881Speter for (i = 0; i < libs->nelts; ++i) 1145251881Speter { 1146251881Speter lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_linked_lib_t); 1147251881Speter if (lib->runtime_version) 1148251881Speter SVN_ERR(svn_cmdline_printf(pool, 1149251881Speter " - %s %s (compiled with %s)\n", 1150251881Speter lib->name, 1151251881Speter lib->runtime_version, 1152251881Speter lib->compiled_version)); 1153251881Speter else 1154251881Speter SVN_ERR(svn_cmdline_printf(pool, 1155251881Speter " - %s %s (static)\n", 1156251881Speter lib->name, 1157251881Speter lib->compiled_version)); 1158251881Speter } 1159251881Speter } 1160251881Speter 1161251881Speter libs = svn_version_ext_loaded_libs(info); 1162251881Speter if (libs && libs->nelts) 1163251881Speter { 1164251881Speter const svn_version_ext_loaded_lib_t *lib; 1165251881Speter int i; 1166251881Speter 1167251881Speter SVN_ERR(svn_cmdline_fputs(_("* loaded shared libraries:\n"), 1168251881Speter stdout, pool)); 1169251881Speter for (i = 0; i < libs->nelts; ++i) 1170251881Speter { 1171251881Speter lib = &APR_ARRAY_IDX(libs, i, svn_version_ext_loaded_lib_t); 1172251881Speter if (lib->version) 1173251881Speter SVN_ERR(svn_cmdline_printf(pool, 1174251881Speter " - %s (%s)\n", 1175251881Speter lib->name, lib->version)); 1176251881Speter else 1177251881Speter SVN_ERR(svn_cmdline_printf(pool, " - %s\n", lib->name)); 1178251881Speter } 1179251881Speter } 1180251881Speter } 1181251881Speter 1182251881Speter return SVN_NO_ERROR; 1183251881Speter} 1184251881Speter 1185251881Spetersvn_error_t * 1186251881Spetersvn_opt_print_help4(apr_getopt_t *os, 1187251881Speter const char *pgm_name, 1188251881Speter svn_boolean_t print_version, 1189251881Speter svn_boolean_t quiet, 1190251881Speter svn_boolean_t verbose, 1191251881Speter const char *version_footer, 1192251881Speter const char *header, 1193251881Speter const svn_opt_subcommand_desc2_t *cmd_table, 1194251881Speter const apr_getopt_option_t *option_table, 1195251881Speter const int *global_options, 1196251881Speter const char *footer, 1197251881Speter apr_pool_t *pool) 1198251881Speter{ 1199251881Speter apr_array_header_t *targets = NULL; 1200251881Speter 1201251881Speter if (os) 1202251881Speter SVN_ERR(svn_opt_parse_all_args(&targets, os, pool)); 1203251881Speter 1204251881Speter if (os && targets->nelts) /* help on subcommand(s) requested */ 1205251881Speter { 1206251881Speter int i; 1207251881Speter 1208251881Speter for (i = 0; i < targets->nelts; i++) 1209251881Speter { 1210251881Speter svn_opt_subcommand_help3(APR_ARRAY_IDX(targets, i, const char *), 1211251881Speter cmd_table, option_table, 1212251881Speter global_options, pool); 1213251881Speter } 1214251881Speter } 1215251881Speter else if (print_version) /* just --version */ 1216251881Speter { 1217251881Speter SVN_ERR(svn_opt__print_version_info(pgm_name, version_footer, 1218251881Speter svn_version_extended(verbose, pool), 1219251881Speter quiet, verbose, pool)); 1220251881Speter } 1221251881Speter else if (os && !targets->nelts) /* `-h', `--help', or `help' */ 1222251881Speter svn_opt_print_generic_help2(header, 1223251881Speter cmd_table, 1224251881Speter option_table, 1225251881Speter footer, 1226251881Speter pool, 1227251881Speter stdout); 1228251881Speter else /* unknown option or cmd */ 1229251881Speter SVN_ERR(svn_cmdline_fprintf(stderr, pool, 1230251881Speter _("Type '%s help' for usage.\n"), pgm_name)); 1231251881Speter 1232251881Speter return SVN_NO_ERROR; 1233251881Speter} 1234