svnmucc.c revision 262253
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_hash.h" 44#include "svn_client.h" 45#include "svn_cmdline.h" 46#include "svn_config.h" 47#include "svn_error.h" 48#include "svn_path.h" 49#include "svn_pools.h" 50#include "svn_props.h" 51#include "svn_ra.h" 52#include "svn_string.h" 53#include "svn_subst.h" 54#include "svn_utf.h" 55#include "svn_version.h" 56 57#include "private/svn_cmdline_private.h" 58#include "private/svn_ra_private.h" 59#include "private/svn_string_private.h" 60#include "private/svn_subr_private.h" 61 62#include "svn_private_config.h" 63 64static void handle_error(svn_error_t *err, apr_pool_t *pool) 65{ 66 if (err) 67 svn_handle_error2(err, stderr, FALSE, "svnmucc: "); 68 svn_error_clear(err); 69 if (pool) 70 svn_pool_destroy(pool); 71 exit(EXIT_FAILURE); 72} 73 74static apr_pool_t * 75init(const char *application) 76{ 77 svn_error_t *err; 78 const svn_version_checklist_t checklist[] = { 79 {"svn_client", svn_client_version}, 80 {"svn_subr", svn_subr_version}, 81 {"svn_ra", svn_ra_version}, 82 {NULL, NULL} 83 }; 84 SVN_VERSION_DEFINE(my_version); 85 86 if (svn_cmdline_init(application, stderr)) 87 exit(EXIT_FAILURE); 88 89 err = svn_ver_check_list2(&my_version, checklist, svn_ver_equal); 90 if (err) 91 handle_error(err, NULL); 92 93 return apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 94} 95 96static svn_error_t * 97open_tmp_file(apr_file_t **fp, 98 void *callback_baton, 99 apr_pool_t *pool) 100{ 101 /* Open a unique file; use APR_DELONCLOSE. */ 102 return svn_io_open_unique_file3(fp, NULL, NULL, svn_io_file_del_on_close, 103 pool, pool); 104} 105 106static svn_error_t * 107create_ra_callbacks(svn_ra_callbacks2_t **callbacks, 108 const char *username, 109 const char *password, 110 const char *config_dir, 111 svn_config_t *cfg_config, 112 svn_boolean_t non_interactive, 113 svn_boolean_t trust_server_cert, 114 svn_boolean_t no_auth_cache, 115 apr_pool_t *pool) 116{ 117 SVN_ERR(svn_ra_create_callbacks(callbacks, pool)); 118 119 SVN_ERR(svn_cmdline_create_auth_baton(&(*callbacks)->auth_baton, 120 non_interactive, 121 username, password, config_dir, 122 no_auth_cache, 123 trust_server_cert, 124 cfg_config, NULL, NULL, pool)); 125 126 (*callbacks)->open_tmp_file = open_tmp_file; 127 128 return SVN_NO_ERROR; 129} 130 131 132 133static svn_error_t * 134commit_callback(const svn_commit_info_t *commit_info, 135 void *baton, 136 apr_pool_t *pool) 137{ 138 SVN_ERR(svn_cmdline_printf(pool, "r%ld committed by %s at %s\n", 139 commit_info->revision, 140 (commit_info->author 141 ? commit_info->author : "(no author)"), 142 commit_info->date)); 143 return SVN_NO_ERROR; 144} 145 146typedef enum action_code_t { 147 ACTION_MV, 148 ACTION_MKDIR, 149 ACTION_CP, 150 ACTION_PROPSET, 151 ACTION_PROPSETF, 152 ACTION_PROPDEL, 153 ACTION_PUT, 154 ACTION_RM 155} action_code_t; 156 157struct operation { 158 enum { 159 OP_OPEN, 160 OP_DELETE, 161 OP_ADD, 162 OP_REPLACE, 163 OP_PROPSET /* only for files for which no other operation is 164 occuring; directories are OP_OPEN with non-empty 165 props */ 166 } operation; 167 svn_node_kind_t kind; /* to copy, mkdir, put or set revprops */ 168 svn_revnum_t rev; /* to copy, valid for add and replace */ 169 const char *url; /* to copy, valid for add and replace */ 170 const char *src_file; /* for put, the source file for contents */ 171 apr_hash_t *children; /* const char *path -> struct operation * */ 172 apr_hash_t *prop_mods; /* const char *prop_name -> 173 const svn_string_t *prop_value */ 174 apr_array_header_t *prop_dels; /* const char *prop_name deletions */ 175 void *baton; /* as returned by the commit editor */ 176}; 177 178 179/* An iterator (for use via apr_table_do) which sets node properties. 180 REC is a pointer to a struct driver_state. */ 181static svn_error_t * 182change_props(const svn_delta_editor_t *editor, 183 void *baton, 184 struct operation *child, 185 apr_pool_t *pool) 186{ 187 apr_pool_t *iterpool = svn_pool_create(pool); 188 189 if (child->prop_dels) 190 { 191 int i; 192 for (i = 0; i < child->prop_dels->nelts; i++) 193 { 194 const char *prop_name; 195 196 svn_pool_clear(iterpool); 197 prop_name = APR_ARRAY_IDX(child->prop_dels, i, const char *); 198 if (child->kind == svn_node_dir) 199 SVN_ERR(editor->change_dir_prop(baton, prop_name, 200 NULL, iterpool)); 201 else 202 SVN_ERR(editor->change_file_prop(baton, prop_name, 203 NULL, iterpool)); 204 } 205 } 206 if (apr_hash_count(child->prop_mods)) 207 { 208 apr_hash_index_t *hi; 209 for (hi = apr_hash_first(pool, child->prop_mods); 210 hi; hi = apr_hash_next(hi)) 211 { 212 const char *propname = svn__apr_hash_index_key(hi); 213 const svn_string_t *val = svn__apr_hash_index_val(hi); 214 215 svn_pool_clear(iterpool); 216 if (child->kind == svn_node_dir) 217 SVN_ERR(editor->change_dir_prop(baton, propname, val, iterpool)); 218 else 219 SVN_ERR(editor->change_file_prop(baton, propname, val, iterpool)); 220 } 221 } 222 223 svn_pool_destroy(iterpool); 224 return SVN_NO_ERROR; 225} 226 227 228/* Drive EDITOR to affect the change represented by OPERATION. HEAD 229 is the last-known youngest revision in the repository. */ 230static svn_error_t * 231drive(struct operation *operation, 232 svn_revnum_t head, 233 const svn_delta_editor_t *editor, 234 apr_pool_t *pool) 235{ 236 apr_pool_t *subpool = svn_pool_create(pool); 237 apr_hash_index_t *hi; 238 239 for (hi = apr_hash_first(pool, operation->children); 240 hi; hi = apr_hash_next(hi)) 241 { 242 const char *key = svn__apr_hash_index_key(hi); 243 struct operation *child = svn__apr_hash_index_val(hi); 244 void *file_baton = NULL; 245 246 svn_pool_clear(subpool); 247 248 /* Deletes and replacements are simple -- delete something. */ 249 if (child->operation == OP_DELETE || child->operation == OP_REPLACE) 250 { 251 SVN_ERR(editor->delete_entry(key, head, operation->baton, subpool)); 252 } 253 /* Opens could be for directories or files. */ 254 if (child->operation == OP_OPEN || child->operation == OP_PROPSET) 255 { 256 if (child->kind == svn_node_dir) 257 { 258 SVN_ERR(editor->open_directory(key, operation->baton, head, 259 subpool, &child->baton)); 260 } 261 else 262 { 263 SVN_ERR(editor->open_file(key, operation->baton, head, 264 subpool, &file_baton)); 265 } 266 } 267 /* Adds and replacements could also be for directories or files. */ 268 if (child->operation == OP_ADD || child->operation == OP_REPLACE) 269 { 270 if (child->kind == svn_node_dir) 271 { 272 SVN_ERR(editor->add_directory(key, operation->baton, 273 child->url, child->rev, 274 subpool, &child->baton)); 275 } 276 else 277 { 278 SVN_ERR(editor->add_file(key, operation->baton, child->url, 279 child->rev, subpool, &file_baton)); 280 } 281 } 282 /* If there's a source file and an open file baton, we get to 283 change textual contents. */ 284 if ((child->src_file) && (file_baton)) 285 { 286 svn_txdelta_window_handler_t handler; 287 void *handler_baton; 288 svn_stream_t *contents; 289 290 SVN_ERR(editor->apply_textdelta(file_baton, NULL, subpool, 291 &handler, &handler_baton)); 292 if (strcmp(child->src_file, "-") != 0) 293 { 294 SVN_ERR(svn_stream_open_readonly(&contents, child->src_file, 295 pool, pool)); 296 } 297 else 298 { 299 SVN_ERR(svn_stream_for_stdin(&contents, pool)); 300 } 301 SVN_ERR(svn_txdelta_send_stream(contents, handler, 302 handler_baton, NULL, pool)); 303 } 304 /* If we opened a file, we need to apply outstanding propmods, 305 then close it. */ 306 if (file_baton) 307 { 308 if (child->kind == svn_node_file) 309 { 310 SVN_ERR(change_props(editor, file_baton, child, subpool)); 311 } 312 SVN_ERR(editor->close_file(file_baton, NULL, subpool)); 313 } 314 /* If we opened, added, or replaced a directory, we need to 315 recurse, apply outstanding propmods, and then close it. */ 316 if ((child->kind == svn_node_dir) 317 && child->operation != OP_DELETE) 318 { 319 SVN_ERR(change_props(editor, child->baton, child, subpool)); 320 321 SVN_ERR(drive(child, head, editor, subpool)); 322 323 SVN_ERR(editor->close_directory(child->baton, subpool)); 324 } 325 } 326 svn_pool_destroy(subpool); 327 return SVN_NO_ERROR; 328} 329 330 331/* Find the operation associated with PATH, which is a single-path 332 component representing a child of the path represented by 333 OPERATION. If no such child operation exists, create a new one of 334 type OP_OPEN. */ 335static struct operation * 336get_operation(const char *path, 337 struct operation *operation, 338 apr_pool_t *pool) 339{ 340 struct operation *child = svn_hash_gets(operation->children, path); 341 if (! child) 342 { 343 child = apr_pcalloc(pool, sizeof(*child)); 344 child->children = apr_hash_make(pool); 345 child->operation = OP_OPEN; 346 child->rev = SVN_INVALID_REVNUM; 347 child->kind = svn_node_dir; 348 child->prop_mods = apr_hash_make(pool); 349 child->prop_dels = apr_array_make(pool, 1, sizeof(const char *)); 350 svn_hash_sets(operation->children, path, child); 351 } 352 return child; 353} 354 355/* Return the portion of URL that is relative to ANCHOR (URI-decoded). */ 356static const char * 357subtract_anchor(const char *anchor, const char *url, apr_pool_t *pool) 358{ 359 return svn_uri_skip_ancestor(anchor, url, pool); 360} 361 362/* Add PATH to the operations tree rooted at OPERATION, creating any 363 intermediate nodes that are required. Here's what's expected for 364 each action type: 365 366 ACTION URL REV SRC-FILE PROPNAME 367 ------------ ----- ------- -------- -------- 368 ACTION_MKDIR NULL invalid NULL NULL 369 ACTION_CP valid valid NULL NULL 370 ACTION_PUT NULL invalid valid NULL 371 ACTION_RM NULL invalid NULL NULL 372 ACTION_PROPSET valid invalid NULL valid 373 ACTION_PROPDEL valid invalid NULL valid 374 375 Node type information is obtained for any copy source (to determine 376 whether to create a file or directory) and for any deleted path (to 377 ensure it exists since svn_delta_editor_t->delete_entry doesn't 378 return an error on non-existent nodes). */ 379static svn_error_t * 380build(action_code_t action, 381 const char *path, 382 const char *url, 383 svn_revnum_t rev, 384 const char *prop_name, 385 const svn_string_t *prop_value, 386 const char *src_file, 387 svn_revnum_t head, 388 const char *anchor, 389 svn_ra_session_t *session, 390 struct operation *operation, 391 apr_pool_t *pool) 392{ 393 apr_array_header_t *path_bits = svn_path_decompose(path, pool); 394 const char *path_so_far = ""; 395 const char *copy_src = NULL; 396 svn_revnum_t copy_rev = SVN_INVALID_REVNUM; 397 int i; 398 399 /* Look for any previous operations we've recognized for PATH. If 400 any of PATH's ancestors have not yet been traversed, we'll be 401 creating OP_OPEN operations for them as we walk down PATH's path 402 components. */ 403 for (i = 0; i < path_bits->nelts; ++i) 404 { 405 const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *); 406 path_so_far = svn_relpath_join(path_so_far, path_bit, pool); 407 operation = get_operation(path_so_far, operation, pool); 408 409 /* If we cross a replace- or add-with-history, remember the 410 source of those things in case we need to lookup the node kind 411 of one of their children. And if this isn't such a copy, 412 but we've already seen one in of our parent paths, we just need 413 to extend that copy source path by our current path 414 component. */ 415 if (operation->url 416 && SVN_IS_VALID_REVNUM(operation->rev) 417 && (operation->operation == OP_REPLACE 418 || operation->operation == OP_ADD)) 419 { 420 copy_src = subtract_anchor(anchor, operation->url, pool); 421 copy_rev = operation->rev; 422 } 423 else if (copy_src) 424 { 425 copy_src = svn_relpath_join(copy_src, path_bit, pool); 426 } 427 } 428 429 /* Handle property changes. */ 430 if (prop_name) 431 { 432 if (operation->operation == OP_DELETE) 433 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 434 "cannot set properties on a location being" 435 " deleted ('%s')", path); 436 /* If we're not adding this thing ourselves, check for existence. */ 437 if (! ((operation->operation == OP_ADD) || 438 (operation->operation == OP_REPLACE))) 439 { 440 SVN_ERR(svn_ra_check_path(session, 441 copy_src ? copy_src : path, 442 copy_src ? copy_rev : head, 443 &operation->kind, pool)); 444 if (operation->kind == svn_node_none) 445 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 446 "propset: '%s' not found", path); 447 else if ((operation->kind == svn_node_file) 448 && (operation->operation == OP_OPEN)) 449 operation->operation = OP_PROPSET; 450 } 451 if (! prop_value) 452 APR_ARRAY_PUSH(operation->prop_dels, const char *) = prop_name; 453 else 454 svn_hash_sets(operation->prop_mods, prop_name, prop_value); 455 if (!operation->rev) 456 operation->rev = rev; 457 return SVN_NO_ERROR; 458 } 459 460 /* We won't fuss about multiple operations on the same path in the 461 following cases: 462 463 - the prior operation was, in fact, a no-op (open) 464 - the prior operation was a propset placeholder 465 - the prior operation was a deletion 466 467 Note: while the operation structure certainly supports the 468 ability to do a copy of a file followed by a put of new contents 469 for the file, we don't let that happen (yet). 470 */ 471 if (operation->operation != OP_OPEN 472 && operation->operation != OP_PROPSET 473 && operation->operation != OP_DELETE) 474 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 475 "unsupported multiple operations on '%s'", path); 476 477 /* For deletions, we validate that there's actually something to 478 delete. If this is a deletion of the child of a copied 479 directory, we need to remember to look in the copy source tree to 480 verify that this thing actually exists. */ 481 if (action == ACTION_RM) 482 { 483 operation->operation = OP_DELETE; 484 SVN_ERR(svn_ra_check_path(session, 485 copy_src ? copy_src : path, 486 copy_src ? copy_rev : head, 487 &operation->kind, pool)); 488 if (operation->kind == svn_node_none) 489 { 490 if (copy_src && strcmp(path, copy_src)) 491 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 492 "'%s' (from '%s:%ld') not found", 493 path, copy_src, copy_rev); 494 else 495 return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' not found", 496 path); 497 } 498 } 499 /* Handle copy operations (which can be adds or replacements). */ 500 else if (action == ACTION_CP) 501 { 502 if (rev > head) 503 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 504 "Copy source revision cannot be younger " 505 "than base revision"); 506 operation->operation = 507 operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD; 508 if (operation->operation == OP_ADD) 509 { 510 /* There is a bug in the current version of mod_dav_svn 511 which incorrectly replaces existing directories. 512 Therefore we need to check if the target exists 513 and raise an error here. */ 514 SVN_ERR(svn_ra_check_path(session, 515 copy_src ? copy_src : path, 516 copy_src ? copy_rev : head, 517 &operation->kind, pool)); 518 if (operation->kind != svn_node_none) 519 { 520 if (copy_src && strcmp(path, copy_src)) 521 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 522 "'%s' (from '%s:%ld') already exists", 523 path, copy_src, copy_rev); 524 else 525 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 526 "'%s' already exists", path); 527 } 528 } 529 SVN_ERR(svn_ra_check_path(session, subtract_anchor(anchor, url, pool), 530 rev, &operation->kind, pool)); 531 if (operation->kind == svn_node_none) 532 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 533 "'%s' not found", 534 subtract_anchor(anchor, url, pool)); 535 operation->url = url; 536 operation->rev = rev; 537 } 538 /* Handle mkdir operations (which can be adds or replacements). */ 539 else if (action == ACTION_MKDIR) 540 { 541 operation->operation = 542 operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD; 543 operation->kind = svn_node_dir; 544 } 545 /* Handle put operations (which can be adds, replacements, or opens). */ 546 else if (action == ACTION_PUT) 547 { 548 if (operation->operation == OP_DELETE) 549 { 550 operation->operation = OP_REPLACE; 551 } 552 else 553 { 554 SVN_ERR(svn_ra_check_path(session, 555 copy_src ? copy_src : path, 556 copy_src ? copy_rev : head, 557 &operation->kind, pool)); 558 if (operation->kind == svn_node_file) 559 operation->operation = OP_OPEN; 560 else if (operation->kind == svn_node_none) 561 operation->operation = OP_ADD; 562 else 563 return svn_error_createf(SVN_ERR_BAD_URL, NULL, 564 "'%s' is not a file", path); 565 } 566 operation->kind = svn_node_file; 567 operation->src_file = src_file; 568 } 569 else 570 { 571 /* We shouldn't get here. */ 572 SVN_ERR_MALFUNCTION(); 573 } 574 575 return SVN_NO_ERROR; 576} 577 578struct action { 579 action_code_t action; 580 581 /* revision (copy-from-rev of path[0] for cp; base-rev for put) */ 582 svn_revnum_t rev; 583 584 /* action path[0] path[1] 585 * ------ ------- ------- 586 * mv source target 587 * mkdir target (null) 588 * cp source target 589 * put target source 590 * rm target (null) 591 * propset target (null) 592 */ 593 const char *path[2]; 594 595 /* property name/value */ 596 const char *prop_name; 597 const svn_string_t *prop_value; 598}; 599 600struct fetch_baton 601{ 602 svn_ra_session_t *session; 603 svn_revnum_t head; 604}; 605 606static svn_error_t * 607fetch_base_func(const char **filename, 608 void *baton, 609 const char *path, 610 svn_revnum_t base_revision, 611 apr_pool_t *result_pool, 612 apr_pool_t *scratch_pool) 613{ 614 struct fetch_baton *fb = baton; 615 svn_stream_t *fstream; 616 svn_error_t *err; 617 618 if (! SVN_IS_VALID_REVNUM(base_revision)) 619 base_revision = fb->head; 620 621 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, 622 svn_io_file_del_on_pool_cleanup, 623 result_pool, scratch_pool)); 624 625 err = svn_ra_get_file(fb->session, path, base_revision, fstream, NULL, NULL, 626 scratch_pool); 627 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 628 { 629 svn_error_clear(err); 630 SVN_ERR(svn_stream_close(fstream)); 631 632 *filename = NULL; 633 return SVN_NO_ERROR; 634 } 635 else if (err) 636 return svn_error_trace(err); 637 638 SVN_ERR(svn_stream_close(fstream)); 639 640 return SVN_NO_ERROR; 641} 642 643static svn_error_t * 644fetch_props_func(apr_hash_t **props, 645 void *baton, 646 const char *path, 647 svn_revnum_t base_revision, 648 apr_pool_t *result_pool, 649 apr_pool_t *scratch_pool) 650{ 651 struct fetch_baton *fb = baton; 652 svn_node_kind_t node_kind; 653 654 if (! SVN_IS_VALID_REVNUM(base_revision)) 655 base_revision = fb->head; 656 657 SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, &node_kind, 658 scratch_pool)); 659 660 if (node_kind == svn_node_file) 661 { 662 SVN_ERR(svn_ra_get_file(fb->session, path, base_revision, NULL, NULL, 663 props, result_pool)); 664 } 665 else if (node_kind == svn_node_dir) 666 { 667 apr_array_header_t *tmp_props; 668 669 SVN_ERR(svn_ra_get_dir2(fb->session, NULL, NULL, props, path, 670 base_revision, 0 /* Dirent fields */, 671 result_pool)); 672 tmp_props = svn_prop_hash_to_array(*props, result_pool); 673 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, 674 result_pool)); 675 *props = svn_prop_array_to_hash(tmp_props, result_pool); 676 } 677 else 678 { 679 *props = apr_hash_make(result_pool); 680 } 681 682 return SVN_NO_ERROR; 683} 684 685static svn_error_t * 686fetch_kind_func(svn_node_kind_t *kind, 687 void *baton, 688 const char *path, 689 svn_revnum_t base_revision, 690 apr_pool_t *scratch_pool) 691{ 692 struct fetch_baton *fb = baton; 693 694 if (! SVN_IS_VALID_REVNUM(base_revision)) 695 base_revision = fb->head; 696 697 SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, kind, 698 scratch_pool)); 699 700 return SVN_NO_ERROR; 701} 702 703static svn_delta_shim_callbacks_t * 704get_shim_callbacks(svn_ra_session_t *session, 705 svn_revnum_t head, 706 apr_pool_t *result_pool) 707{ 708 svn_delta_shim_callbacks_t *callbacks = 709 svn_delta_shim_callbacks_default(result_pool); 710 struct fetch_baton *fb = apr_pcalloc(result_pool, sizeof(*fb)); 711 712 fb->session = session; 713 fb->head = head; 714 715 callbacks->fetch_props_func = fetch_props_func; 716 callbacks->fetch_kind_func = fetch_kind_func; 717 callbacks->fetch_base_func = fetch_base_func; 718 callbacks->fetch_baton = fb; 719 720 return callbacks; 721} 722 723static svn_error_t * 724execute(const apr_array_header_t *actions, 725 const char *anchor, 726 apr_hash_t *revprops, 727 const char *username, 728 const char *password, 729 const char *config_dir, 730 const apr_array_header_t *config_options, 731 svn_boolean_t non_interactive, 732 svn_boolean_t trust_server_cert, 733 svn_boolean_t no_auth_cache, 734 svn_revnum_t base_revision, 735 apr_pool_t *pool) 736{ 737 svn_ra_session_t *session; 738 svn_ra_session_t *aux_session; 739 const char *repos_root; 740 svn_revnum_t head; 741 const svn_delta_editor_t *editor; 742 svn_ra_callbacks2_t *ra_callbacks; 743 void *editor_baton; 744 struct operation root; 745 svn_error_t *err; 746 apr_hash_t *config; 747 svn_config_t *cfg_config; 748 int i; 749 750 SVN_ERR(svn_config_get_config(&config, config_dir, pool)); 751 SVN_ERR(svn_cmdline__apply_config_options(config, config_options, 752 "svnmucc: ", "--config-option")); 753 cfg_config = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG); 754 755 if (! svn_hash_gets(revprops, SVN_PROP_REVISION_LOG)) 756 { 757 svn_string_t *msg = svn_string_create("", pool); 758 759 /* If we can do so, try to pop up $EDITOR to fetch a log message. */ 760 if (non_interactive) 761 { 762 return svn_error_create 763 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 764 _("Cannot invoke editor to get log message " 765 "when non-interactive")); 766 } 767 else 768 { 769 SVN_ERR(svn_cmdline__edit_string_externally( 770 &msg, NULL, NULL, "", msg, "svnmucc-commit", config, 771 TRUE, NULL, apr_hash_pool_get(revprops))); 772 } 773 774 svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, msg); 775 } 776 777 SVN_ERR(create_ra_callbacks(&ra_callbacks, username, password, config_dir, 778 cfg_config, non_interactive, trust_server_cert, 779 no_auth_cache, pool)); 780 SVN_ERR(svn_ra_open4(&session, NULL, anchor, NULL, ra_callbacks, 781 NULL, config, pool)); 782 /* Open, then reparent to avoid AUTHZ errors when opening the reposroot */ 783 SVN_ERR(svn_ra_open4(&aux_session, NULL, anchor, NULL, ra_callbacks, 784 NULL, config, pool)); 785 SVN_ERR(svn_ra_get_repos_root2(aux_session, &repos_root, pool)); 786 SVN_ERR(svn_ra_reparent(aux_session, repos_root, pool)); 787 SVN_ERR(svn_ra_get_latest_revnum(session, &head, pool)); 788 789 /* Reparent to ANCHOR's dir, if ANCHOR is not a directory. */ 790 { 791 svn_node_kind_t kind; 792 793 SVN_ERR(svn_ra_check_path(aux_session, 794 svn_uri_skip_ancestor(repos_root, anchor, pool), 795 head, &kind, pool)); 796 if (kind != svn_node_dir) 797 { 798 anchor = svn_uri_dirname(anchor, pool); 799 SVN_ERR(svn_ra_reparent(session, anchor, pool)); 800 } 801 } 802 803 if (SVN_IS_VALID_REVNUM(base_revision)) 804 { 805 if (base_revision > head) 806 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 807 "No such revision %ld (youngest is %ld)", 808 base_revision, head); 809 head = base_revision; 810 } 811 812 memset(&root, 0, sizeof(root)); 813 root.children = apr_hash_make(pool); 814 root.operation = OP_OPEN; 815 root.kind = svn_node_dir; /* For setting properties */ 816 root.prop_mods = apr_hash_make(pool); 817 root.prop_dels = apr_array_make(pool, 1, sizeof(const char *)); 818 819 for (i = 0; i < actions->nelts; ++i) 820 { 821 struct action *action = APR_ARRAY_IDX(actions, i, struct action *); 822 const char *path1, *path2; 823 switch (action->action) 824 { 825 case ACTION_MV: 826 path1 = subtract_anchor(anchor, action->path[0], pool); 827 path2 = subtract_anchor(anchor, action->path[1], pool); 828 SVN_ERR(build(ACTION_RM, path1, NULL, 829 SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor, 830 session, &root, pool)); 831 SVN_ERR(build(ACTION_CP, path2, action->path[0], 832 head, NULL, NULL, NULL, head, anchor, 833 session, &root, pool)); 834 break; 835 case ACTION_CP: 836 path2 = subtract_anchor(anchor, action->path[1], pool); 837 if (action->rev == SVN_INVALID_REVNUM) 838 action->rev = head; 839 SVN_ERR(build(ACTION_CP, path2, action->path[0], 840 action->rev, NULL, NULL, NULL, head, anchor, 841 session, &root, pool)); 842 break; 843 case ACTION_RM: 844 path1 = subtract_anchor(anchor, action->path[0], pool); 845 SVN_ERR(build(ACTION_RM, path1, NULL, 846 SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor, 847 session, &root, pool)); 848 break; 849 case ACTION_MKDIR: 850 path1 = subtract_anchor(anchor, action->path[0], pool); 851 SVN_ERR(build(ACTION_MKDIR, path1, action->path[0], 852 SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor, 853 session, &root, pool)); 854 break; 855 case ACTION_PUT: 856 path1 = subtract_anchor(anchor, action->path[0], pool); 857 SVN_ERR(build(ACTION_PUT, path1, action->path[0], 858 SVN_INVALID_REVNUM, NULL, NULL, action->path[1], 859 head, anchor, session, &root, pool)); 860 break; 861 case ACTION_PROPSET: 862 case ACTION_PROPDEL: 863 path1 = subtract_anchor(anchor, action->path[0], pool); 864 SVN_ERR(build(action->action, path1, action->path[0], 865 SVN_INVALID_REVNUM, 866 action->prop_name, action->prop_value, 867 NULL, head, anchor, session, &root, pool)); 868 break; 869 case ACTION_PROPSETF: 870 default: 871 SVN_ERR_MALFUNCTION_NO_RETURN(); 872 } 873 } 874 875 SVN_ERR(svn_ra__register_editor_shim_callbacks(session, 876 get_shim_callbacks(aux_session, head, pool))); 877 SVN_ERR(svn_ra_get_commit_editor3(session, &editor, &editor_baton, revprops, 878 commit_callback, NULL, NULL, FALSE, pool)); 879 880 SVN_ERR(editor->open_root(editor_baton, head, pool, &root.baton)); 881 err = change_props(editor, root.baton, &root, pool); 882 if (!err) 883 err = drive(&root, head, editor, pool); 884 if (!err) 885 err = editor->close_directory(root.baton, pool); 886 if (!err) 887 err = editor->close_edit(editor_baton, pool); 888 889 if (err) 890 err = svn_error_compose_create(err, 891 editor->abort_edit(editor_baton, pool)); 892 893 return err; 894} 895 896static svn_error_t * 897read_propvalue_file(const svn_string_t **value_p, 898 const char *filename, 899 apr_pool_t *pool) 900{ 901 svn_stringbuf_t *value; 902 apr_pool_t *scratch_pool = svn_pool_create(pool); 903 904 SVN_ERR(svn_stringbuf_from_file2(&value, filename, scratch_pool)); 905 *value_p = svn_string_create_from_buf(value, pool); 906 svn_pool_destroy(scratch_pool); 907 return SVN_NO_ERROR; 908} 909 910/* Perform the typical suite of manipulations for user-provided URLs 911 on URL, returning the result (allocated from POOL): IRI-to-URI 912 conversion, auto-escaping, and canonicalization. */ 913static const char * 914sanitize_url(const char *url, 915 apr_pool_t *pool) 916{ 917 url = svn_path_uri_from_iri(url, pool); 918 url = svn_path_uri_autoescape(url, pool); 919 return svn_uri_canonicalize(url, pool); 920} 921 922static void 923usage(apr_pool_t *pool, int exit_val) 924{ 925 FILE *stream = exit_val == EXIT_SUCCESS ? stdout : stderr; 926 svn_error_clear(svn_cmdline_fputs( 927 _("Subversion multiple URL command client\n" 928 "usage: svnmucc ACTION...\n" 929 "\n" 930 " Perform one or more Subversion repository URL-based ACTIONs, committing\n" 931 " the result as a (single) new revision.\n" 932 "\n" 933 "Actions:\n" 934 " cp REV SRC-URL DST-URL : copy SRC-URL@REV to DST-URL\n" 935 " mkdir URL : create new directory URL\n" 936 " mv SRC-URL DST-URL : move SRC-URL to DST-URL\n" 937 " rm URL : delete URL\n" 938 " put SRC-FILE URL : add or modify file URL with contents copied from\n" 939 " SRC-FILE (use \"-\" to read from standard input)\n" 940 " propset NAME VALUE URL : set property NAME on URL to VALUE\n" 941 " propsetf NAME FILE URL : set property NAME on URL to value read from FILE\n" 942 " propdel NAME URL : delete property NAME from URL\n" 943 "\n" 944 "Valid options:\n" 945 " -h, -? [--help] : display this text\n" 946 " -m [--message] ARG : use ARG as a log message\n" 947 " -F [--file] ARG : read log message from file ARG\n" 948 " -u [--username] ARG : commit the changes as username ARG\n" 949 " -p [--password] ARG : use ARG as the password\n" 950 " -U [--root-url] ARG : interpret all action URLs relative to ARG\n" 951 " -r [--revision] ARG : use revision ARG as baseline for changes\n" 952 " --with-revprop ARG : set revision property in the following format:\n" 953 " NAME[=VALUE]\n" 954 " --non-interactive : do no interactive prompting (default is to\n" 955 " prompt only if standard input is a terminal)\n" 956 " --force-interactive : do interactive prompting even if standard\n" 957 " input is not a terminal\n" 958 " --trust-server-cert : accept SSL server certificates from unknown\n" 959 " certificate authorities without prompting (but\n" 960 " only with '--non-interactive')\n" 961 " -X [--extra-args] ARG : append arguments from file ARG (one per line;\n" 962 " use \"-\" to read from standard input)\n" 963 " --config-dir ARG : use ARG to override the config directory\n" 964 " --config-option ARG : use ARG to override a configuration option\n" 965 " --no-auth-cache : do not cache authentication tokens\n" 966 " --version : print version information\n"), 967 stream, pool)); 968 svn_pool_destroy(pool); 969 exit(exit_val); 970} 971 972static void 973insufficient(apr_pool_t *pool) 974{ 975 handle_error(svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 976 "insufficient arguments"), 977 pool); 978} 979 980static svn_error_t * 981display_version(apr_getopt_t *os, apr_pool_t *pool) 982{ 983 const char *ra_desc_start 984 = "The following repository access (RA) modules are available:\n\n"; 985 svn_stringbuf_t *version_footer; 986 987 version_footer = svn_stringbuf_create(ra_desc_start, pool); 988 SVN_ERR(svn_ra_print_modules(version_footer, pool)); 989 990 SVN_ERR(svn_opt_print_help4(os, "svnmucc", TRUE, FALSE, FALSE, 991 version_footer->data, 992 NULL, NULL, NULL, NULL, NULL, pool)); 993 994 return SVN_NO_ERROR; 995} 996 997/* Return an error about the mutual exclusivity of the -m, -F, and 998 --with-revprop=svn:log command-line options. */ 999static svn_error_t * 1000mutually_exclusive_logs_error(void) 1001{ 1002 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1003 _("--message (-m), --file (-F), and " 1004 "--with-revprop=svn:log are mutually " 1005 "exclusive")); 1006} 1007 1008/* Ensure that the REVPROPS hash contains a command-line-provided log 1009 message, if any, and that there was but one source of such a thing 1010 provided on that command-line. */ 1011static svn_error_t * 1012sanitize_log_sources(apr_hash_t *revprops, 1013 const char *message, 1014 svn_stringbuf_t *filedata) 1015{ 1016 apr_pool_t *hash_pool = apr_hash_pool_get(revprops); 1017 1018 /* If we already have a log message in the revprop hash, then just 1019 make sure the user didn't try to also use -m or -F. Otherwise, 1020 we need to consult -m or -F to find a log message, if any. */ 1021 if (svn_hash_gets(revprops, SVN_PROP_REVISION_LOG)) 1022 { 1023 if (filedata || message) 1024 return mutually_exclusive_logs_error(); 1025 } 1026 else if (filedata) 1027 { 1028 if (message) 1029 return mutually_exclusive_logs_error(); 1030 1031 SVN_ERR(svn_utf_cstring_to_utf8(&message, filedata->data, hash_pool)); 1032 svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, 1033 svn_stringbuf__morph_into_string(filedata)); 1034 } 1035 else if (message) 1036 { 1037 svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, 1038 svn_string_create(message, hash_pool)); 1039 } 1040 1041 return SVN_NO_ERROR; 1042} 1043 1044int 1045main(int argc, const char **argv) 1046{ 1047 apr_pool_t *pool = init("svnmucc"); 1048 apr_array_header_t *actions = apr_array_make(pool, 1, 1049 sizeof(struct action *)); 1050 const char *anchor = NULL; 1051 svn_error_t *err = SVN_NO_ERROR; 1052 apr_getopt_t *opts; 1053 enum { 1054 config_dir_opt = SVN_OPT_FIRST_LONGOPT_ID, 1055 config_inline_opt, 1056 no_auth_cache_opt, 1057 version_opt, 1058 with_revprop_opt, 1059 non_interactive_opt, 1060 force_interactive_opt, 1061 trust_server_cert_opt 1062 }; 1063 static const apr_getopt_option_t options[] = { 1064 {"message", 'm', 1, ""}, 1065 {"file", 'F', 1, ""}, 1066 {"username", 'u', 1, ""}, 1067 {"password", 'p', 1, ""}, 1068 {"root-url", 'U', 1, ""}, 1069 {"revision", 'r', 1, ""}, 1070 {"with-revprop", with_revprop_opt, 1, ""}, 1071 {"extra-args", 'X', 1, ""}, 1072 {"help", 'h', 0, ""}, 1073 {NULL, '?', 0, ""}, 1074 {"non-interactive", non_interactive_opt, 0, ""}, 1075 {"force-interactive", force_interactive_opt, 0, ""}, 1076 {"trust-server-cert", trust_server_cert_opt, 0, ""}, 1077 {"config-dir", config_dir_opt, 1, ""}, 1078 {"config-option", config_inline_opt, 1, ""}, 1079 {"no-auth-cache", no_auth_cache_opt, 0, ""}, 1080 {"version", version_opt, 0, ""}, 1081 {NULL, 0, 0, NULL} 1082 }; 1083 const char *message = NULL; 1084 svn_stringbuf_t *filedata = NULL; 1085 const char *username = NULL, *password = NULL; 1086 const char *root_url = NULL, *extra_args_file = NULL; 1087 const char *config_dir = NULL; 1088 apr_array_header_t *config_options; 1089 svn_boolean_t non_interactive = FALSE; 1090 svn_boolean_t force_interactive = FALSE; 1091 svn_boolean_t trust_server_cert = FALSE; 1092 svn_boolean_t no_auth_cache = FALSE; 1093 svn_revnum_t base_revision = SVN_INVALID_REVNUM; 1094 apr_array_header_t *action_args; 1095 apr_hash_t *revprops = apr_hash_make(pool); 1096 int i; 1097 1098 config_options = apr_array_make(pool, 0, 1099 sizeof(svn_cmdline__config_argument_t*)); 1100 1101 apr_getopt_init(&opts, pool, argc, argv); 1102 opts->interleave = 1; 1103 while (1) 1104 { 1105 int opt; 1106 const char *arg; 1107 const char *opt_arg; 1108 1109 apr_status_t status = apr_getopt_long(opts, options, &opt, &arg); 1110 if (APR_STATUS_IS_EOF(status)) 1111 break; 1112 if (status != APR_SUCCESS) 1113 handle_error(svn_error_wrap_apr(status, "getopt failure"), pool); 1114 switch(opt) 1115 { 1116 case 'm': 1117 err = svn_utf_cstring_to_utf8(&message, arg, pool); 1118 if (err) 1119 handle_error(err, pool); 1120 break; 1121 case 'F': 1122 { 1123 const char *arg_utf8; 1124 err = svn_utf_cstring_to_utf8(&arg_utf8, arg, pool); 1125 if (! err) 1126 err = svn_stringbuf_from_file2(&filedata, arg, pool); 1127 if (err) 1128 handle_error(err, pool); 1129 } 1130 break; 1131 case 'u': 1132 username = apr_pstrdup(pool, arg); 1133 break; 1134 case 'p': 1135 password = apr_pstrdup(pool, arg); 1136 break; 1137 case 'U': 1138 err = svn_utf_cstring_to_utf8(&root_url, arg, pool); 1139 if (err) 1140 handle_error(err, pool); 1141 if (! svn_path_is_url(root_url)) 1142 handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 1143 "'%s' is not a URL\n", root_url), 1144 pool); 1145 root_url = sanitize_url(root_url, pool); 1146 break; 1147 case 'r': 1148 { 1149 char *digits_end = NULL; 1150 base_revision = strtol(arg, &digits_end, 10); 1151 if ((! SVN_IS_VALID_REVNUM(base_revision)) 1152 || (! digits_end) 1153 || *digits_end) 1154 handle_error(svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 1155 NULL, "Invalid revision number"), 1156 pool); 1157 } 1158 break; 1159 case with_revprop_opt: 1160 err = svn_opt_parse_revprop(&revprops, arg, pool); 1161 if (err != SVN_NO_ERROR) 1162 handle_error(err, pool); 1163 break; 1164 case 'X': 1165 extra_args_file = apr_pstrdup(pool, arg); 1166 break; 1167 case non_interactive_opt: 1168 non_interactive = TRUE; 1169 break; 1170 case force_interactive_opt: 1171 force_interactive = TRUE; 1172 break; 1173 case trust_server_cert_opt: 1174 trust_server_cert = TRUE; 1175 break; 1176 case config_dir_opt: 1177 err = svn_utf_cstring_to_utf8(&config_dir, arg, pool); 1178 if (err) 1179 handle_error(err, pool); 1180 break; 1181 case config_inline_opt: 1182 err = svn_utf_cstring_to_utf8(&opt_arg, arg, pool); 1183 if (err) 1184 handle_error(err, pool); 1185 1186 err = svn_cmdline__parse_config_option(config_options, opt_arg, 1187 pool); 1188 if (err) 1189 handle_error(err, pool); 1190 break; 1191 case no_auth_cache_opt: 1192 no_auth_cache = TRUE; 1193 break; 1194 case version_opt: 1195 SVN_INT_ERR(display_version(opts, pool)); 1196 exit(EXIT_SUCCESS); 1197 break; 1198 case 'h': 1199 case '?': 1200 usage(pool, EXIT_SUCCESS); 1201 break; 1202 } 1203 } 1204 1205 if (non_interactive && force_interactive) 1206 { 1207 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1208 _("--non-interactive and --force-interactive " 1209 "are mutually exclusive")); 1210 return svn_cmdline_handle_exit_error(err, pool, "svnmucc: "); 1211 } 1212 else 1213 non_interactive = !svn_cmdline__be_interactive(non_interactive, 1214 force_interactive); 1215 1216 if (trust_server_cert && !non_interactive) 1217 { 1218 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1219 _("--trust-server-cert requires " 1220 "--non-interactive")); 1221 return svn_cmdline_handle_exit_error(err, pool, "svnmucc: "); 1222 } 1223 1224 /* Make sure we have a log message to use. */ 1225 err = sanitize_log_sources(revprops, message, filedata); 1226 if (err) 1227 handle_error(err, pool); 1228 1229 /* Copy the rest of our command-line arguments to an array, 1230 UTF-8-ing them along the way. */ 1231 action_args = apr_array_make(pool, opts->argc, sizeof(const char *)); 1232 while (opts->ind < opts->argc) 1233 { 1234 const char *arg = opts->argv[opts->ind++]; 1235 if ((err = svn_utf_cstring_to_utf8(&(APR_ARRAY_PUSH(action_args, 1236 const char *)), 1237 arg, pool))) 1238 handle_error(err, pool); 1239 } 1240 1241 /* If there are extra arguments in a supplementary file, tack those 1242 on, too (again, in UTF8 form). */ 1243 if (extra_args_file) 1244 { 1245 const char *extra_args_file_utf8; 1246 svn_stringbuf_t *contents, *contents_utf8; 1247 1248 err = svn_utf_cstring_to_utf8(&extra_args_file_utf8, 1249 extra_args_file, pool); 1250 if (! err) 1251 err = svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool); 1252 if (! err) 1253 err = svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool); 1254 if (err) 1255 handle_error(err, pool); 1256 svn_cstring_split_append(action_args, contents_utf8->data, "\n\r", 1257 FALSE, pool); 1258 } 1259 1260 /* Now, we iterate over the combined set of arguments -- our actions. */ 1261 for (i = 0; i < action_args->nelts; ) 1262 { 1263 int j, num_url_args; 1264 const char *action_string = APR_ARRAY_IDX(action_args, i, const char *); 1265 struct action *action = apr_pcalloc(pool, sizeof(*action)); 1266 1267 /* First, parse the action. */ 1268 if (! strcmp(action_string, "mv")) 1269 action->action = ACTION_MV; 1270 else if (! strcmp(action_string, "cp")) 1271 action->action = ACTION_CP; 1272 else if (! strcmp(action_string, "mkdir")) 1273 action->action = ACTION_MKDIR; 1274 else if (! strcmp(action_string, "rm")) 1275 action->action = ACTION_RM; 1276 else if (! strcmp(action_string, "put")) 1277 action->action = ACTION_PUT; 1278 else if (! strcmp(action_string, "propset")) 1279 action->action = ACTION_PROPSET; 1280 else if (! strcmp(action_string, "propsetf")) 1281 action->action = ACTION_PROPSETF; 1282 else if (! strcmp(action_string, "propdel")) 1283 action->action = ACTION_PROPDEL; 1284 else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h") 1285 || ! strcmp(action_string, "help")) 1286 usage(pool, EXIT_SUCCESS); 1287 else 1288 handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 1289 "'%s' is not an action\n", 1290 action_string), pool); 1291 if (++i == action_args->nelts) 1292 insufficient(pool); 1293 1294 /* For copies, there should be a revision number next. */ 1295 if (action->action == ACTION_CP) 1296 { 1297 const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *); 1298 if (strcmp(rev_str, "head") == 0) 1299 action->rev = SVN_INVALID_REVNUM; 1300 else if (strcmp(rev_str, "HEAD") == 0) 1301 action->rev = SVN_INVALID_REVNUM; 1302 else 1303 { 1304 char *end; 1305 1306 while (*rev_str == 'r') 1307 ++rev_str; 1308 1309 action->rev = strtol(rev_str, &end, 0); 1310 if (*end) 1311 handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 1312 "'%s' is not a revision\n", 1313 rev_str), pool); 1314 } 1315 if (++i == action_args->nelts) 1316 insufficient(pool); 1317 } 1318 else 1319 { 1320 action->rev = SVN_INVALID_REVNUM; 1321 } 1322 1323 /* For puts, there should be a local file next. */ 1324 if (action->action == ACTION_PUT) 1325 { 1326 action->path[1] = 1327 svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i, 1328 const char *), pool); 1329 if (++i == action_args->nelts) 1330 insufficient(pool); 1331 } 1332 1333 /* For propset, propsetf, and propdel, a property name (and 1334 maybe a property value or file which contains one) comes next. */ 1335 if ((action->action == ACTION_PROPSET) 1336 || (action->action == ACTION_PROPSETF) 1337 || (action->action == ACTION_PROPDEL)) 1338 { 1339 action->prop_name = APR_ARRAY_IDX(action_args, i, const char *); 1340 if (++i == action_args->nelts) 1341 insufficient(pool); 1342 1343 if (action->action == ACTION_PROPDEL) 1344 { 1345 action->prop_value = NULL; 1346 } 1347 else if (action->action == ACTION_PROPSET) 1348 { 1349 action->prop_value = 1350 svn_string_create(APR_ARRAY_IDX(action_args, i, 1351 const char *), pool); 1352 if (++i == action_args->nelts) 1353 insufficient(pool); 1354 } 1355 else 1356 { 1357 const char *propval_file = 1358 svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i, 1359 const char *), pool); 1360 1361 if (++i == action_args->nelts) 1362 insufficient(pool); 1363 1364 err = read_propvalue_file(&(action->prop_value), 1365 propval_file, pool); 1366 if (err) 1367 handle_error(err, pool); 1368 1369 action->action = ACTION_PROPSET; 1370 } 1371 1372 if (action->prop_value 1373 && svn_prop_needs_translation(action->prop_name)) 1374 { 1375 svn_string_t *translated_value; 1376 err = svn_subst_translate_string2(&translated_value, NULL, 1377 NULL, action->prop_value, NULL, 1378 FALSE, pool, pool); 1379 if (err) 1380 handle_error( 1381 svn_error_quick_wrap(err, 1382 "Error normalizing property value"), 1383 pool); 1384 action->prop_value = translated_value; 1385 } 1386 } 1387 1388 /* How many URLs does this action expect? */ 1389 if (action->action == ACTION_RM 1390 || action->action == ACTION_MKDIR 1391 || action->action == ACTION_PUT 1392 || action->action == ACTION_PROPSET 1393 || action->action == ACTION_PROPSETF /* shouldn't see this one */ 1394 || action->action == ACTION_PROPDEL) 1395 num_url_args = 1; 1396 else 1397 num_url_args = 2; 1398 1399 /* Parse the required number of URLs. */ 1400 for (j = 0; j < num_url_args; ++j) 1401 { 1402 const char *url = APR_ARRAY_IDX(action_args, i, const char *); 1403 1404 /* If there's a ROOT_URL, we expect URL to be a path 1405 relative to ROOT_URL (and we build a full url from the 1406 combination of the two). Otherwise, it should be a full 1407 url. */ 1408 if (! svn_path_is_url(url)) 1409 { 1410 if (! root_url) 1411 handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 1412 "'%s' is not a URL, and " 1413 "--root-url (-U) not provided\n", 1414 url), pool); 1415 /* ### These relpaths are already URI-encoded. */ 1416 url = apr_pstrcat(pool, root_url, "/", 1417 svn_relpath_canonicalize(url, pool), 1418 (char *)NULL); 1419 } 1420 url = sanitize_url(url, pool); 1421 action->path[j] = url; 1422 1423 /* The first URL arguments to 'cp', 'pd', 'ps' could be the anchor, 1424 but the other URLs should be children of the anchor. */ 1425 if (! (action->action == ACTION_CP && j == 0) 1426 && action->action != ACTION_PROPDEL 1427 && action->action != ACTION_PROPSET 1428 && action->action != ACTION_PROPSETF) 1429 url = svn_uri_dirname(url, pool); 1430 if (! anchor) 1431 anchor = url; 1432 else 1433 { 1434 anchor = svn_uri_get_longest_ancestor(anchor, url, pool); 1435 if (!anchor || !anchor[0]) 1436 handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 1437 "URLs in the action list do not " 1438 "share a common ancestor"), 1439 pool); 1440 } 1441 1442 if ((++i == action_args->nelts) && (j + 1 < num_url_args)) 1443 insufficient(pool); 1444 } 1445 APR_ARRAY_PUSH(actions, struct action *) = action; 1446 } 1447 1448 if (! actions->nelts) 1449 usage(pool, EXIT_FAILURE); 1450 1451 if ((err = execute(actions, anchor, revprops, username, password, 1452 config_dir, config_options, non_interactive, 1453 trust_server_cert, no_auth_cache, base_revision, pool))) 1454 { 1455 if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive) 1456 err = svn_error_quick_wrap(err, 1457 _("Authentication failed and interactive" 1458 " prompting is disabled; see the" 1459 " --force-interactive option")); 1460 handle_error(err, pool); 1461 } 1462 1463 /* Ensure that stdout is flushed, so the user will see all results. */ 1464 svn_error_clear(svn_cmdline_fflush(stdout)); 1465 1466 svn_pool_destroy(pool); 1467 return EXIT_SUCCESS; 1468} 1469