1/* 2 * svnmucc.c: Subversion Multiple URL 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/* Multiple URL Command Client 26 27 Combine a list of mv, cp and rm commands on URLs into a single commit. 28 29 How it works: the command line arguments are parsed into an array of 30 action structures. The action structures are interpreted to build a 31 tree of operation structures. The tree of operation structures is 32 used to drive an RA commit editor to produce a single commit. 33 34 To build this client, type 'make svnmucc' from the root of your 35 Subversion source directory. 36*/ 37 38#include <stdio.h> 39#include <string.h> 40 41#include <apr_lib.h> 42 43#include "svn_private_config.h" 44#include "svn_hash.h" 45#include "svn_client.h" 46#include "private/svn_client_mtcc.h" 47#include "svn_cmdline.h" 48#include "svn_config.h" 49#include "svn_error.h" 50#include "svn_path.h" 51#include "svn_pools.h" 52#include "svn_props.h" 53#include "svn_string.h" 54#include "svn_subst.h" 55#include "svn_utf.h" 56#include "svn_version.h" 57 58#include "private/svn_cmdline_private.h" 59#include "private/svn_subr_private.h" 60 61/* Version compatibility check */ 62static svn_error_t * 63check_lib_versions(void) 64{ 65 static const svn_version_checklist_t checklist[] = 66 { 67 { "svn_client", svn_client_version }, 68 { "svn_subr", svn_subr_version }, 69 { "svn_ra", svn_ra_version }, 70 { NULL, NULL } 71 }; 72 SVN_VERSION_DEFINE(my_version); 73 74 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); 75} 76 77static svn_error_t * 78commit_callback(const svn_commit_info_t *commit_info, 79 void *baton, 80 apr_pool_t *pool) 81{ 82 SVN_ERR(svn_cmdline_printf(pool, "r%ld committed by %s at %s\n", 83 commit_info->revision, 84 (commit_info->author 85 ? commit_info->author : "(no author)"), 86 commit_info->date)); 87 return SVN_NO_ERROR; 88} 89 90typedef enum action_code_t { 91 ACTION_MV, 92 ACTION_MKDIR, 93 ACTION_CP, 94 ACTION_PROPSET, 95 ACTION_PROPSETF, 96 ACTION_PROPDEL, 97 ACTION_PUT, 98 ACTION_RM 99} action_code_t; 100 101/* Return the portion of URL that is relative to ANCHOR (URI-decoded). */ 102static const char * 103subtract_anchor(const char *anchor, const char *url, apr_pool_t *pool) 104{ 105 return svn_uri_skip_ancestor(anchor, url, pool); 106} 107 108 109struct action { 110 action_code_t action; 111 112 /* revision (copy-from-rev of path[0] for cp; base-rev for put) */ 113 svn_revnum_t rev; 114 115 /* action path[0] path[1] 116 * ------ ------- ------- 117 * mv source target 118 * mkdir target (null) 119 * cp source target 120 * put target source 121 * rm target (null) 122 * propset target (null) 123 */ 124 const char *path[2]; 125 126 /* property name/value */ 127 const char *prop_name; 128 const svn_string_t *prop_value; 129}; 130 131static svn_error_t * 132execute(const apr_array_header_t *actions, 133 const char *anchor, 134 apr_hash_t *revprops, 135 svn_revnum_t base_revision, 136 svn_client_ctx_t *ctx, 137 apr_pool_t *pool) 138{ 139 svn_client__mtcc_t *mtcc; 140 apr_pool_t *iterpool = svn_pool_create(pool); 141 svn_error_t *err; 142 int i; 143 144 SVN_ERR(svn_client__mtcc_create(&mtcc, anchor, 145 SVN_IS_VALID_REVNUM(base_revision) 146 ? base_revision 147 : SVN_INVALID_REVNUM, 148 ctx, pool, iterpool)); 149 150 for (i = 0; i < actions->nelts; ++i) 151 { 152 struct action *action = APR_ARRAY_IDX(actions, i, struct action *); 153 const char *path1, *path2; 154 svn_node_kind_t kind; 155 156 svn_pool_clear(iterpool); 157 158 switch (action->action) 159 { 160 case ACTION_MV: 161 path1 = subtract_anchor(anchor, action->path[0], pool); 162 path2 = subtract_anchor(anchor, action->path[1], pool); 163 SVN_ERR(svn_client__mtcc_add_move(path1, path2, mtcc, iterpool)); 164 break; 165 case ACTION_CP: 166 path1 = subtract_anchor(anchor, action->path[0], pool); 167 path2 = subtract_anchor(anchor, action->path[1], pool); 168 SVN_ERR(svn_client__mtcc_add_copy(path1, action->rev, path2, 169 mtcc, iterpool)); 170 break; 171 case ACTION_RM: 172 path1 = subtract_anchor(anchor, action->path[0], pool); 173 SVN_ERR(svn_client__mtcc_add_delete(path1, mtcc, iterpool)); 174 break; 175 case ACTION_MKDIR: 176 path1 = subtract_anchor(anchor, action->path[0], pool); 177 SVN_ERR(svn_client__mtcc_add_mkdir(path1, mtcc, iterpool)); 178 break; 179 case ACTION_PUT: 180 path1 = subtract_anchor(anchor, action->path[0], pool); 181 SVN_ERR(svn_client__mtcc_check_path(&kind, path1, TRUE, mtcc, pool)); 182 183 if (kind == svn_node_dir) 184 { 185 SVN_ERR(svn_client__mtcc_add_delete(path1, mtcc, pool)); 186 kind = svn_node_none; 187 } 188 189 { 190 svn_stream_t *src; 191 192 if (strcmp(action->path[1], "-") != 0) 193 SVN_ERR(svn_stream_open_readonly(&src, action->path[1], 194 pool, iterpool)); 195 else 196 SVN_ERR(svn_stream_for_stdin(&src, pool)); 197 198 199 if (kind == svn_node_file) 200 SVN_ERR(svn_client__mtcc_add_update_file(path1, src, NULL, 201 NULL, NULL, 202 mtcc, iterpool)); 203 else if (kind == svn_node_none) 204 SVN_ERR(svn_client__mtcc_add_add_file(path1, src, NULL, 205 mtcc, iterpool)); 206 } 207 break; 208 case ACTION_PROPSET: 209 case ACTION_PROPDEL: 210 path1 = subtract_anchor(anchor, action->path[0], pool); 211 SVN_ERR(svn_client__mtcc_add_propset(path1, action->prop_name, 212 action->prop_value, FALSE, 213 mtcc, iterpool)); 214 break; 215 case ACTION_PROPSETF: 216 default: 217 SVN_ERR_MALFUNCTION_NO_RETURN(); 218 } 219 } 220 221 err = svn_client__mtcc_commit(revprops, commit_callback, NULL, 222 mtcc, iterpool); 223 224 svn_pool_destroy(iterpool); 225 return svn_error_trace(err); 226} 227 228static svn_error_t * 229read_propvalue_file(const svn_string_t **value_p, 230 const char *filename, 231 apr_pool_t *pool) 232{ 233 svn_stringbuf_t *value; 234 apr_pool_t *scratch_pool = svn_pool_create(pool); 235 236 SVN_ERR(svn_stringbuf_from_file2(&value, filename, scratch_pool)); 237 *value_p = svn_string_create_from_buf(value, pool); 238 svn_pool_destroy(scratch_pool); 239 return SVN_NO_ERROR; 240} 241 242/* Perform the typical suite of manipulations for user-provided URLs 243 on URL, returning the result (allocated from POOL): IRI-to-URI 244 conversion, auto-escaping, and canonicalization. */ 245static const char * 246sanitize_url(const char *url, 247 apr_pool_t *pool) 248{ 249 url = svn_path_uri_from_iri(url, pool); 250 url = svn_path_uri_autoescape(url, pool); 251 return svn_uri_canonicalize(url, pool); 252} 253 254static void 255usage(apr_pool_t *pool) 256{ 257 svn_error_clear(svn_cmdline_fprintf 258 (stderr, pool, _("Type 'svnmucc --help' for usage.\n"))); 259} 260 261/* Print a usage message on STREAM. */ 262static void 263help(FILE *stream, apr_pool_t *pool) 264{ 265 svn_error_clear(svn_cmdline_fputs( 266 _("usage: svnmucc ACTION...\n" 267 "Subversion multiple URL command client.\n" 268 "Type 'svnmucc --version' to see the program version and RA modules.\n" 269 "\n" 270 " Perform one or more Subversion repository URL-based ACTIONs, committing\n" 271 " the result as a (single) new revision.\n" 272 "\n" 273 "Actions:\n" 274 " cp REV SRC-URL DST-URL : copy SRC-URL@REV to DST-URL\n" 275 " mkdir URL : create new directory URL\n" 276 " mv SRC-URL DST-URL : move SRC-URL to DST-URL\n" 277 " rm URL : delete URL\n" 278 " put SRC-FILE URL : add or modify file URL with contents copied from\n" 279 " SRC-FILE (use \"-\" to read from standard input)\n" 280 " propset NAME VALUE URL : set property NAME on URL to VALUE\n" 281 " propsetf NAME FILE URL : set property NAME on URL to value read from FILE\n" 282 " propdel NAME URL : delete property NAME from URL\n" 283 "\n" 284 "Valid options:\n" 285 " -h, -? [--help] : display this text\n" 286 " -m [--message] ARG : use ARG as a log message\n" 287 " -F [--file] ARG : read log message from file ARG\n" 288 " -u [--username] ARG : commit the changes as username ARG\n" 289 " -p [--password] ARG : use ARG as the password\n" 290 " -U [--root-url] ARG : interpret all action URLs relative to ARG\n" 291 " -r [--revision] ARG : use revision ARG as baseline for changes\n" 292 " --with-revprop ARG : set revision property in the following format:\n" 293 " NAME[=VALUE]\n" 294 " --non-interactive : do no interactive prompting (default is to\n" 295 " prompt only if standard input is a terminal)\n" 296 " --force-interactive : do interactive prompting even if standard\n" 297 " input is not a terminal\n" 298 " --trust-server-cert : deprecated;\n" 299 " same as --trust-server-cert-failures=unknown-ca\n" 300 " --trust-server-cert-failures ARG\n" 301 " with --non-interactive, accept SSL server\n" 302 " certificates with failures; ARG is comma-separated\n" 303 " list of 'unknown-ca' (Unknown Authority),\n" 304 " 'cn-mismatch' (Hostname mismatch), 'expired'\n" 305 " (Expired certificate),'not-yet-valid' (Not yet\n" 306 " valid certificate) and 'other' (all other not\n" 307 " separately classified certificate errors).\n" 308 " -X [--extra-args] ARG : append arguments from file ARG (one per line;\n" 309 " use \"-\" to read from standard input)\n" 310 " --config-dir ARG : use ARG to override the config directory\n" 311 " --config-option ARG : use ARG to override a configuration option\n" 312 " --no-auth-cache : do not cache authentication tokens\n" 313 " --version : print version information\n"), 314 stream, pool)); 315} 316 317static svn_error_t * 318insufficient(void) 319{ 320 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 321 "insufficient arguments"); 322} 323 324static svn_error_t * 325display_version(apr_pool_t *pool) 326{ 327 const char *ra_desc_start 328 = "The following repository access (RA) modules are available:\n\n"; 329 svn_stringbuf_t *version_footer; 330 331 version_footer = svn_stringbuf_create(ra_desc_start, pool); 332 SVN_ERR(svn_ra_print_modules(version_footer, pool)); 333 334 SVN_ERR(svn_opt_print_help4(NULL, "svnmucc", TRUE, FALSE, FALSE, 335 version_footer->data, 336 NULL, NULL, NULL, NULL, NULL, pool)); 337 338 return SVN_NO_ERROR; 339} 340 341/* Return an error about the mutual exclusivity of the -m, -F, and 342 --with-revprop=svn:log command-line options. */ 343static svn_error_t * 344mutually_exclusive_logs_error(void) 345{ 346 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 347 _("--message (-m), --file (-F), and " 348 "--with-revprop=svn:log are mutually " 349 "exclusive")); 350} 351 352/* Obtain the log message from multiple sources, producing an error 353 if there are multiple sources. Store the result in *FINAL_MESSAGE. */ 354static svn_error_t * 355sanitize_log_sources(const char **final_message, 356 const char *message, 357 apr_hash_t *revprops, 358 svn_stringbuf_t *filedata, 359 apr_pool_t *result_pool, 360 apr_pool_t *scratch_pool) 361{ 362 svn_string_t *msg; 363 364 *final_message = NULL; 365 /* If we already have a log message in the revprop hash, then just 366 make sure the user didn't try to also use -m or -F. Otherwise, 367 we need to consult -m or -F to find a log message, if any. */ 368 msg = svn_hash_gets(revprops, SVN_PROP_REVISION_LOG); 369 if (msg) 370 { 371 if (filedata || message) 372 return mutually_exclusive_logs_error(); 373 374 *final_message = apr_pstrdup(result_pool, msg->data); 375 376 /* Will be re-added by libsvn_client */ 377 svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, NULL); 378 } 379 else if (filedata) 380 { 381 if (message) 382 return mutually_exclusive_logs_error(); 383 384 *final_message = apr_pstrdup(result_pool, filedata->data); 385 } 386 else if (message) 387 { 388 *final_message = apr_pstrdup(result_pool, message); 389 } 390 391 return SVN_NO_ERROR; 392} 393 394/* Baton for log_message_func */ 395struct log_message_baton 396{ 397 svn_boolean_t non_interactive; 398 const char *log_message; 399 svn_client_ctx_t *ctx; 400}; 401 402/* Implements svn_client_get_commit_log3_t */ 403static svn_error_t * 404log_message_func(const char **log_msg, 405 const char **tmp_file, 406 const apr_array_header_t *commit_items, 407 void *baton, 408 apr_pool_t *pool) 409{ 410 struct log_message_baton *lmb = baton; 411 412 *tmp_file = NULL; 413 414 if (lmb->log_message) 415 { 416 svn_string_t *message = svn_string_create(lmb->log_message, pool); 417 418 SVN_ERR_W(svn_subst_translate_string2(&message, NULL, NULL, 419 message, NULL, FALSE, 420 pool, pool), 421 _("Error normalizing log message to internal format")); 422 423 *log_msg = message->data; 424 425 return SVN_NO_ERROR; 426 } 427 428 if (lmb->non_interactive) 429 { 430 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 431 _("Cannot invoke editor to get log message " 432 "when non-interactive")); 433 } 434 else 435 { 436 svn_string_t *msg = svn_string_create("", pool); 437 438 SVN_ERR(svn_cmdline__edit_string_externally( 439 &msg, NULL, NULL, "", msg, "svnmucc-commit", 440 lmb->ctx->config, TRUE, NULL, pool)); 441 442 if (msg && msg->data) 443 *log_msg = msg->data; 444 else 445 *log_msg = NULL; 446 447 return SVN_NO_ERROR; 448 } 449} 450 451/* 452 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error, 453 * either return an error to be displayed, or set *EXIT_CODE to non-zero and 454 * return SVN_NO_ERROR. 455 */ 456static svn_error_t * 457sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) 458{ 459 apr_array_header_t *actions = apr_array_make(pool, 1, 460 sizeof(struct action *)); 461 const char *anchor = NULL; 462 svn_error_t *err = SVN_NO_ERROR; 463 apr_getopt_t *opts; 464 enum { 465 config_dir_opt = SVN_OPT_FIRST_LONGOPT_ID, 466 config_inline_opt, 467 no_auth_cache_opt, 468 version_opt, 469 with_revprop_opt, 470 non_interactive_opt, 471 force_interactive_opt, 472 trust_server_cert_opt, 473 trust_server_cert_failures_opt, 474 }; 475 static const apr_getopt_option_t options[] = { 476 {"message", 'm', 1, ""}, 477 {"file", 'F', 1, ""}, 478 {"username", 'u', 1, ""}, 479 {"password", 'p', 1, ""}, 480 {"root-url", 'U', 1, ""}, 481 {"revision", 'r', 1, ""}, 482 {"with-revprop", with_revprop_opt, 1, ""}, 483 {"extra-args", 'X', 1, ""}, 484 {"help", 'h', 0, ""}, 485 {NULL, '?', 0, ""}, 486 {"non-interactive", non_interactive_opt, 0, ""}, 487 {"force-interactive", force_interactive_opt, 0, ""}, 488 {"trust-server-cert", trust_server_cert_opt, 0, ""}, 489 {"trust-server-cert-failures", trust_server_cert_failures_opt, 1, ""}, 490 {"config-dir", config_dir_opt, 1, ""}, 491 {"config-option", config_inline_opt, 1, ""}, 492 {"no-auth-cache", no_auth_cache_opt, 0, ""}, 493 {"version", version_opt, 0, ""}, 494 {NULL, 0, 0, NULL} 495 }; 496 const char *message = NULL; 497 svn_stringbuf_t *filedata = NULL; 498 const char *username = NULL, *password = NULL; 499 const char *root_url = NULL, *extra_args_file = NULL; 500 const char *config_dir = NULL; 501 apr_array_header_t *config_options; 502 svn_boolean_t non_interactive = FALSE; 503 svn_boolean_t force_interactive = FALSE; 504 svn_boolean_t trust_unknown_ca = FALSE; 505 svn_boolean_t trust_cn_mismatch = FALSE; 506 svn_boolean_t trust_expired = FALSE; 507 svn_boolean_t trust_not_yet_valid = FALSE; 508 svn_boolean_t trust_other_failure = FALSE; 509 svn_boolean_t no_auth_cache = FALSE; 510 svn_boolean_t show_version = FALSE; 511 svn_boolean_t show_help = FALSE; 512 svn_revnum_t base_revision = SVN_INVALID_REVNUM; 513 apr_array_header_t *action_args; 514 apr_hash_t *revprops = apr_hash_make(pool); 515 apr_hash_t *cfg_hash; 516 svn_config_t *cfg_config; 517 svn_client_ctx_t *ctx; 518 struct log_message_baton lmb; 519 int i; 520 521 /* Check library versions */ 522 SVN_ERR(check_lib_versions()); 523 524 config_options = apr_array_make(pool, 0, 525 sizeof(svn_cmdline__config_argument_t*)); 526 527 apr_getopt_init(&opts, pool, argc, argv); 528 opts->interleave = 1; 529 while (1) 530 { 531 int opt; 532 const char *arg; 533 const char *opt_arg; 534 535 apr_status_t status = apr_getopt_long(opts, options, &opt, &arg); 536 if (APR_STATUS_IS_EOF(status)) 537 break; 538 if (status != APR_SUCCESS) 539 { 540 usage(pool); 541 *exit_code = EXIT_FAILURE; 542 return SVN_NO_ERROR; 543 } 544 switch(opt) 545 { 546 case 'm': 547 SVN_ERR(svn_utf_cstring_to_utf8(&message, arg, pool)); 548 break; 549 case 'F': 550 { 551 const char *arg_utf8; 552 SVN_ERR(svn_utf_cstring_to_utf8(&arg_utf8, arg, pool)); 553 SVN_ERR(svn_stringbuf_from_file2(&filedata, arg, pool)); 554 } 555 break; 556 case 'u': 557 username = apr_pstrdup(pool, arg); 558 break; 559 case 'p': 560 password = apr_pstrdup(pool, arg); 561 break; 562 case 'U': 563 SVN_ERR(svn_utf_cstring_to_utf8(&root_url, arg, pool)); 564 if (! svn_path_is_url(root_url)) 565 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 566 "'%s' is not a URL\n", root_url); 567 root_url = sanitize_url(root_url, pool); 568 break; 569 case 'r': 570 { 571 const char *saved_arg = arg; 572 char *digits_end = NULL; 573 while (*arg == 'r') 574 arg++; 575 base_revision = strtol(arg, &digits_end, 10); 576 if ((! SVN_IS_VALID_REVNUM(base_revision)) 577 || (! digits_end) 578 || *digits_end) 579 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 580 _("Invalid revision number '%s'"), 581 saved_arg); 582 } 583 break; 584 case with_revprop_opt: 585 SVN_ERR(svn_opt_parse_revprop(&revprops, arg, pool)); 586 break; 587 case 'X': 588 extra_args_file = apr_pstrdup(pool, arg); 589 break; 590 case non_interactive_opt: 591 non_interactive = TRUE; 592 break; 593 case force_interactive_opt: 594 force_interactive = TRUE; 595 break; 596 case trust_server_cert_opt: /* backward compat */ 597 trust_unknown_ca = TRUE; 598 break; 599 case trust_server_cert_failures_opt: 600 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool)); 601 SVN_ERR(svn_cmdline__parse_trust_options( 602 &trust_unknown_ca, 603 &trust_cn_mismatch, 604 &trust_expired, 605 &trust_not_yet_valid, 606 &trust_other_failure, 607 opt_arg, pool)); 608 break; 609 case config_dir_opt: 610 SVN_ERR(svn_utf_cstring_to_utf8(&config_dir, arg, pool)); 611 break; 612 case config_inline_opt: 613 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool)); 614 SVN_ERR(svn_cmdline__parse_config_option(config_options, opt_arg, 615 "svnmucc: ", 616 pool)); 617 break; 618 case no_auth_cache_opt: 619 no_auth_cache = TRUE; 620 break; 621 case version_opt: 622 show_version = TRUE; 623 break; 624 case 'h': 625 case '?': 626 show_help = TRUE; 627 break; 628 } 629 } 630 631 if (show_help) 632 { 633 help(stdout, pool); 634 return SVN_NO_ERROR; 635 } 636 637 if (show_version) 638 { 639 SVN_ERR(display_version(pool)); 640 return SVN_NO_ERROR; 641 } 642 643 if (non_interactive && force_interactive) 644 { 645 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 646 _("--non-interactive and --force-interactive " 647 "are mutually exclusive")); 648 } 649 else 650 non_interactive = !svn_cmdline__be_interactive(non_interactive, 651 force_interactive); 652 653 if (!non_interactive) 654 { 655 if (trust_unknown_ca || trust_cn_mismatch || trust_expired 656 || trust_not_yet_valid || trust_other_failure) 657 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 658 _("--trust-server-cert-failures requires " 659 "--non-interactive")); 660 } 661 662 /* Copy the rest of our command-line arguments to an array, 663 UTF-8-ing them along the way. */ 664 action_args = apr_array_make(pool, opts->argc, sizeof(const char *)); 665 while (opts->ind < opts->argc) 666 { 667 const char *arg = opts->argv[opts->ind++]; 668 SVN_ERR(svn_utf_cstring_to_utf8(&APR_ARRAY_PUSH(action_args, 669 const char *), 670 arg, pool)); 671 } 672 673 /* If there are extra arguments in a supplementary file, tack those 674 on, too (again, in UTF8 form). */ 675 if (extra_args_file) 676 { 677 const char *extra_args_file_utf8; 678 svn_stringbuf_t *contents, *contents_utf8; 679 680 SVN_ERR(svn_utf_cstring_to_utf8(&extra_args_file_utf8, 681 extra_args_file, pool)); 682 SVN_ERR(svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool)); 683 SVN_ERR(svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool)); 684 svn_cstring_split_append(action_args, contents_utf8->data, "\n\r", 685 FALSE, pool); 686 } 687 688 /* Now initialize the client context */ 689 690 err = svn_config_get_config(&cfg_hash, config_dir, pool); 691 if (err) 692 { 693 /* Fallback to default config if the config directory isn't readable 694 or is not a directory. */ 695 if (APR_STATUS_IS_EACCES(err->apr_err) 696 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err)) 697 { 698 svn_handle_warning2(stderr, err, "svnmucc: "); 699 svn_error_clear(err); 700 701 SVN_ERR(svn_config__get_default_config(&cfg_hash, pool)); 702 } 703 else 704 return err; 705 } 706 707 if (config_options) 708 { 709 svn_error_clear( 710 svn_cmdline__apply_config_options(cfg_hash, config_options, 711 "svnmucc: ", "--config-option")); 712 } 713 714 SVN_ERR(svn_client_create_context2(&ctx, cfg_hash, pool)); 715 716 cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG); 717 SVN_ERR(svn_cmdline_create_auth_baton2( 718 &ctx->auth_baton, 719 non_interactive, 720 username, 721 password, 722 config_dir, 723 no_auth_cache, 724 trust_unknown_ca, 725 trust_cn_mismatch, 726 trust_expired, 727 trust_not_yet_valid, 728 trust_other_failure, 729 cfg_config, 730 ctx->cancel_func, 731 ctx->cancel_baton, 732 pool)); 733 734 lmb.non_interactive = non_interactive; 735 lmb.ctx = ctx; 736 /* Make sure we have a log message to use. */ 737 SVN_ERR(sanitize_log_sources(&lmb.log_message, message, revprops, filedata, 738 pool, pool)); 739 740 ctx->log_msg_func3 = log_message_func; 741 ctx->log_msg_baton3 = &lmb; 742 743 /* Now, we iterate over the combined set of arguments -- our actions. */ 744 for (i = 0; i < action_args->nelts; ) 745 { 746 int j, num_url_args; 747 const char *action_string = APR_ARRAY_IDX(action_args, i, const char *); 748 struct action *action = apr_pcalloc(pool, sizeof(*action)); 749 750 /* First, parse the action. */ 751 if (! strcmp(action_string, "mv")) 752 action->action = ACTION_MV; 753 else if (! strcmp(action_string, "cp")) 754 action->action = ACTION_CP; 755 else if (! strcmp(action_string, "mkdir")) 756 action->action = ACTION_MKDIR; 757 else if (! strcmp(action_string, "rm")) 758 action->action = ACTION_RM; 759 else if (! strcmp(action_string, "put")) 760 action->action = ACTION_PUT; 761 else if (! strcmp(action_string, "propset")) 762 action->action = ACTION_PROPSET; 763 else if (! strcmp(action_string, "propsetf")) 764 action->action = ACTION_PROPSETF; 765 else if (! strcmp(action_string, "propdel")) 766 action->action = ACTION_PROPDEL; 767 else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h") 768 || ! strcmp(action_string, "help")) 769 { 770 help(stdout, pool); 771 return SVN_NO_ERROR; 772 } 773 else 774 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 775 "'%s' is not an action\n", 776 action_string); 777 if (++i == action_args->nelts) 778 return insufficient(); 779 780 /* For copies, there should be a revision number next. */ 781 if (action->action == ACTION_CP) 782 { 783 const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *); 784 if (strcmp(rev_str, "head") == 0) 785 action->rev = SVN_INVALID_REVNUM; 786 else if (strcmp(rev_str, "HEAD") == 0) 787 action->rev = SVN_INVALID_REVNUM; 788 else 789 { 790 char *end; 791 792 while (*rev_str == 'r') 793 ++rev_str; 794 795 action->rev = strtol(rev_str, &end, 0); 796 if (*end) 797 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 798 "'%s' is not a revision\n", 799 rev_str); 800 } 801 if (++i == action_args->nelts) 802 return insufficient(); 803 } 804 else 805 { 806 action->rev = SVN_INVALID_REVNUM; 807 } 808 809 /* For puts, there should be a local file next. */ 810 if (action->action == ACTION_PUT) 811 { 812 action->path[1] = 813 svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i, 814 const char *), pool); 815 if (++i == action_args->nelts) 816 return insufficient(); 817 } 818 819 /* For propset, propsetf, and propdel, a property name (and 820 maybe a property value or file which contains one) comes next. */ 821 if ((action->action == ACTION_PROPSET) 822 || (action->action == ACTION_PROPSETF) 823 || (action->action == ACTION_PROPDEL)) 824 { 825 action->prop_name = APR_ARRAY_IDX(action_args, i, const char *); 826 if (++i == action_args->nelts) 827 return insufficient(); 828 829 if (action->action == ACTION_PROPDEL) 830 { 831 action->prop_value = NULL; 832 } 833 else if (action->action == ACTION_PROPSET) 834 { 835 action->prop_value = 836 svn_string_create(APR_ARRAY_IDX(action_args, i, 837 const char *), pool); 838 if (++i == action_args->nelts) 839 return insufficient(); 840 } 841 else 842 { 843 const char *propval_file = 844 svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i, 845 const char *), pool); 846 847 if (++i == action_args->nelts) 848 return insufficient(); 849 850 SVN_ERR(read_propvalue_file(&(action->prop_value), 851 propval_file, pool)); 852 853 action->action = ACTION_PROPSET; 854 } 855 856 if (action->prop_value 857 && svn_prop_needs_translation(action->prop_name)) 858 { 859 svn_string_t *translated_value; 860 SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL, 861 NULL, action->prop_value, 862 NULL, FALSE, pool, pool), 863 "Error normalizing property value"); 864 action->prop_value = translated_value; 865 } 866 } 867 868 /* How many URLs does this action expect? */ 869 if (action->action == ACTION_RM 870 || action->action == ACTION_MKDIR 871 || action->action == ACTION_PUT 872 || action->action == ACTION_PROPSET 873 || action->action == ACTION_PROPSETF /* shouldn't see this one */ 874 || action->action == ACTION_PROPDEL) 875 num_url_args = 1; 876 else 877 num_url_args = 2; 878 879 /* Parse the required number of URLs. */ 880 for (j = 0; j < num_url_args; ++j) 881 { 882 const char *url = APR_ARRAY_IDX(action_args, i, const char *); 883 884 /* If there's a ROOT_URL, we expect URL to be a path 885 relative to ROOT_URL (and we build a full url from the 886 combination of the two). Otherwise, it should be a full 887 url. */ 888 if (! svn_path_is_url(url)) 889 { 890 if (! root_url) 891 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 892 "'%s' is not a URL, and " 893 "--root-url (-U) not provided\n", 894 url); 895 /* ### These relpaths are already URI-encoded. */ 896 url = apr_pstrcat(pool, root_url, "/", 897 svn_relpath_canonicalize(url, pool), 898 SVN_VA_NULL); 899 } 900 url = sanitize_url(url, pool); 901 action->path[j] = url; 902 903 /* The first URL arguments to 'cp', 'pd', 'ps' could be the anchor, 904 but the other URLs should be children of the anchor. */ 905 if (! (action->action == ACTION_CP && j == 0) 906 && action->action != ACTION_PROPDEL 907 && action->action != ACTION_PROPSET 908 && action->action != ACTION_PROPSETF) 909 url = svn_uri_dirname(url, pool); 910 if (! anchor) 911 anchor = url; 912 else 913 { 914 anchor = svn_uri_get_longest_ancestor(anchor, url, pool); 915 if (!anchor || !anchor[0]) 916 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 917 "URLs in the action list do not " 918 "share a common ancestor"); 919 } 920 921 if ((++i == action_args->nelts) && (j + 1 < num_url_args)) 922 return insufficient(); 923 } 924 925 APR_ARRAY_PUSH(actions, struct action *) = action; 926 } 927 928 if (! actions->nelts) 929 { 930 *exit_code = EXIT_FAILURE; 931 help(stderr, pool); 932 return SVN_NO_ERROR; 933 } 934 935 if ((err = execute(actions, anchor, revprops, base_revision, ctx, pool))) 936 { 937 if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive) 938 err = svn_error_quick_wrap(err, 939 _("Authentication failed and interactive" 940 " prompting is disabled; see the" 941 " --force-interactive option")); 942 return err; 943 } 944 945 return SVN_NO_ERROR; 946} 947 948int 949main(int argc, const char *argv[]) 950{ 951 apr_pool_t *pool; 952 int exit_code = EXIT_SUCCESS; 953 svn_error_t *err; 954 955 /* Initialize the app. */ 956 if (svn_cmdline_init("svnmucc", stderr) != EXIT_SUCCESS) 957 return EXIT_FAILURE; 958 959 /* Create our top-level pool. Use a separate mutexless allocator, 960 * given this application is single threaded. 961 */ 962 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 963 964 err = sub_main(&exit_code, argc, argv, pool); 965 966 /* Flush stdout and report if it fails. It would be flushed on exit anyway 967 but this makes sure that output is not silently lost if it fails. */ 968 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout)); 969 970 if (err) 971 { 972 exit_code = EXIT_FAILURE; 973 svn_cmdline_handle_exit_error(err, NULL, "svnmucc: "); 974 } 975 976 svn_pool_destroy(pool); 977 return exit_code; 978} 979