1251881Speter/* 2251881Speter * svnmucc.c: Subversion Multiple URL Client 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/* Multiple URL Command Client 26251881Speter 27251881Speter Combine a list of mv, cp and rm commands on URLs into a single commit. 28251881Speter 29251881Speter How it works: the command line arguments are parsed into an array of 30251881Speter action structures. The action structures are interpreted to build a 31251881Speter tree of operation structures. The tree of operation structures is 32251881Speter used to drive an RA commit editor to produce a single commit. 33251881Speter 34251881Speter To build this client, type 'make svnmucc' from the root of your 35251881Speter Subversion source directory. 36251881Speter*/ 37251881Speter 38251881Speter#include <stdio.h> 39251881Speter#include <string.h> 40251881Speter 41251881Speter#include <apr_lib.h> 42251881Speter 43251881Speter#include "svn_hash.h" 44251881Speter#include "svn_client.h" 45251881Speter#include "svn_cmdline.h" 46251881Speter#include "svn_config.h" 47251881Speter#include "svn_error.h" 48251881Speter#include "svn_path.h" 49251881Speter#include "svn_pools.h" 50251881Speter#include "svn_props.h" 51251881Speter#include "svn_ra.h" 52251881Speter#include "svn_string.h" 53251881Speter#include "svn_subst.h" 54251881Speter#include "svn_utf.h" 55251881Speter#include "svn_version.h" 56251881Speter 57251881Speter#include "private/svn_cmdline_private.h" 58251881Speter#include "private/svn_ra_private.h" 59251881Speter#include "private/svn_string_private.h" 60262253Speter#include "private/svn_subr_private.h" 61251881Speter 62251881Speter#include "svn_private_config.h" 63251881Speter 64251881Speterstatic void handle_error(svn_error_t *err, apr_pool_t *pool) 65251881Speter{ 66251881Speter if (err) 67251881Speter svn_handle_error2(err, stderr, FALSE, "svnmucc: "); 68251881Speter svn_error_clear(err); 69251881Speter if (pool) 70251881Speter svn_pool_destroy(pool); 71251881Speter exit(EXIT_FAILURE); 72251881Speter} 73251881Speter 74251881Speterstatic apr_pool_t * 75251881Speterinit(const char *application) 76251881Speter{ 77251881Speter svn_error_t *err; 78251881Speter const svn_version_checklist_t checklist[] = { 79251881Speter {"svn_client", svn_client_version}, 80251881Speter {"svn_subr", svn_subr_version}, 81251881Speter {"svn_ra", svn_ra_version}, 82251881Speter {NULL, NULL} 83251881Speter }; 84251881Speter SVN_VERSION_DEFINE(my_version); 85251881Speter 86251881Speter if (svn_cmdline_init(application, stderr)) 87251881Speter exit(EXIT_FAILURE); 88251881Speter 89262253Speter err = svn_ver_check_list2(&my_version, checklist, svn_ver_equal); 90251881Speter if (err) 91251881Speter handle_error(err, NULL); 92251881Speter 93251881Speter return apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); 94251881Speter} 95251881Speter 96251881Speterstatic svn_error_t * 97251881Speteropen_tmp_file(apr_file_t **fp, 98251881Speter void *callback_baton, 99251881Speter apr_pool_t *pool) 100251881Speter{ 101251881Speter /* Open a unique file; use APR_DELONCLOSE. */ 102251881Speter return svn_io_open_unique_file3(fp, NULL, NULL, svn_io_file_del_on_close, 103251881Speter pool, pool); 104251881Speter} 105251881Speter 106251881Speterstatic svn_error_t * 107251881Spetercreate_ra_callbacks(svn_ra_callbacks2_t **callbacks, 108251881Speter const char *username, 109251881Speter const char *password, 110251881Speter const char *config_dir, 111251881Speter svn_config_t *cfg_config, 112251881Speter svn_boolean_t non_interactive, 113251881Speter svn_boolean_t trust_server_cert, 114251881Speter svn_boolean_t no_auth_cache, 115251881Speter apr_pool_t *pool) 116251881Speter{ 117251881Speter SVN_ERR(svn_ra_create_callbacks(callbacks, pool)); 118251881Speter 119251881Speter SVN_ERR(svn_cmdline_create_auth_baton(&(*callbacks)->auth_baton, 120251881Speter non_interactive, 121251881Speter username, password, config_dir, 122251881Speter no_auth_cache, 123251881Speter trust_server_cert, 124251881Speter cfg_config, NULL, NULL, pool)); 125251881Speter 126251881Speter (*callbacks)->open_tmp_file = open_tmp_file; 127251881Speter 128251881Speter return SVN_NO_ERROR; 129251881Speter} 130251881Speter 131251881Speter 132251881Speter 133251881Speterstatic svn_error_t * 134251881Spetercommit_callback(const svn_commit_info_t *commit_info, 135251881Speter void *baton, 136251881Speter apr_pool_t *pool) 137251881Speter{ 138251881Speter SVN_ERR(svn_cmdline_printf(pool, "r%ld committed by %s at %s\n", 139251881Speter commit_info->revision, 140251881Speter (commit_info->author 141251881Speter ? commit_info->author : "(no author)"), 142251881Speter commit_info->date)); 143251881Speter return SVN_NO_ERROR; 144251881Speter} 145251881Speter 146251881Spetertypedef enum action_code_t { 147251881Speter ACTION_MV, 148251881Speter ACTION_MKDIR, 149251881Speter ACTION_CP, 150251881Speter ACTION_PROPSET, 151251881Speter ACTION_PROPSETF, 152251881Speter ACTION_PROPDEL, 153251881Speter ACTION_PUT, 154251881Speter ACTION_RM 155251881Speter} action_code_t; 156251881Speter 157251881Speterstruct operation { 158251881Speter enum { 159251881Speter OP_OPEN, 160251881Speter OP_DELETE, 161251881Speter OP_ADD, 162251881Speter OP_REPLACE, 163251881Speter OP_PROPSET /* only for files for which no other operation is 164251881Speter occuring; directories are OP_OPEN with non-empty 165251881Speter props */ 166251881Speter } operation; 167251881Speter svn_node_kind_t kind; /* to copy, mkdir, put or set revprops */ 168251881Speter svn_revnum_t rev; /* to copy, valid for add and replace */ 169251881Speter const char *url; /* to copy, valid for add and replace */ 170251881Speter const char *src_file; /* for put, the source file for contents */ 171251881Speter apr_hash_t *children; /* const char *path -> struct operation * */ 172251881Speter apr_hash_t *prop_mods; /* const char *prop_name -> 173251881Speter const svn_string_t *prop_value */ 174251881Speter apr_array_header_t *prop_dels; /* const char *prop_name deletions */ 175251881Speter void *baton; /* as returned by the commit editor */ 176251881Speter}; 177251881Speter 178251881Speter 179251881Speter/* An iterator (for use via apr_table_do) which sets node properties. 180251881Speter REC is a pointer to a struct driver_state. */ 181251881Speterstatic svn_error_t * 182251881Speterchange_props(const svn_delta_editor_t *editor, 183251881Speter void *baton, 184251881Speter struct operation *child, 185251881Speter apr_pool_t *pool) 186251881Speter{ 187251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 188251881Speter 189251881Speter if (child->prop_dels) 190251881Speter { 191251881Speter int i; 192251881Speter for (i = 0; i < child->prop_dels->nelts; i++) 193251881Speter { 194251881Speter const char *prop_name; 195251881Speter 196251881Speter svn_pool_clear(iterpool); 197251881Speter prop_name = APR_ARRAY_IDX(child->prop_dels, i, const char *); 198251881Speter if (child->kind == svn_node_dir) 199251881Speter SVN_ERR(editor->change_dir_prop(baton, prop_name, 200251881Speter NULL, iterpool)); 201251881Speter else 202251881Speter SVN_ERR(editor->change_file_prop(baton, prop_name, 203251881Speter NULL, iterpool)); 204251881Speter } 205251881Speter } 206251881Speter if (apr_hash_count(child->prop_mods)) 207251881Speter { 208251881Speter apr_hash_index_t *hi; 209251881Speter for (hi = apr_hash_first(pool, child->prop_mods); 210251881Speter hi; hi = apr_hash_next(hi)) 211251881Speter { 212251881Speter const char *propname = svn__apr_hash_index_key(hi); 213251881Speter const svn_string_t *val = svn__apr_hash_index_val(hi); 214251881Speter 215251881Speter svn_pool_clear(iterpool); 216251881Speter if (child->kind == svn_node_dir) 217251881Speter SVN_ERR(editor->change_dir_prop(baton, propname, val, iterpool)); 218251881Speter else 219251881Speter SVN_ERR(editor->change_file_prop(baton, propname, val, iterpool)); 220251881Speter } 221251881Speter } 222251881Speter 223251881Speter svn_pool_destroy(iterpool); 224251881Speter return SVN_NO_ERROR; 225251881Speter} 226251881Speter 227251881Speter 228251881Speter/* Drive EDITOR to affect the change represented by OPERATION. HEAD 229251881Speter is the last-known youngest revision in the repository. */ 230251881Speterstatic svn_error_t * 231251881Speterdrive(struct operation *operation, 232251881Speter svn_revnum_t head, 233251881Speter const svn_delta_editor_t *editor, 234251881Speter apr_pool_t *pool) 235251881Speter{ 236251881Speter apr_pool_t *subpool = svn_pool_create(pool); 237251881Speter apr_hash_index_t *hi; 238251881Speter 239251881Speter for (hi = apr_hash_first(pool, operation->children); 240251881Speter hi; hi = apr_hash_next(hi)) 241251881Speter { 242251881Speter const char *key = svn__apr_hash_index_key(hi); 243251881Speter struct operation *child = svn__apr_hash_index_val(hi); 244251881Speter void *file_baton = NULL; 245251881Speter 246251881Speter svn_pool_clear(subpool); 247251881Speter 248251881Speter /* Deletes and replacements are simple -- delete something. */ 249251881Speter if (child->operation == OP_DELETE || child->operation == OP_REPLACE) 250251881Speter { 251251881Speter SVN_ERR(editor->delete_entry(key, head, operation->baton, subpool)); 252251881Speter } 253251881Speter /* Opens could be for directories or files. */ 254251881Speter if (child->operation == OP_OPEN || child->operation == OP_PROPSET) 255251881Speter { 256251881Speter if (child->kind == svn_node_dir) 257251881Speter { 258251881Speter SVN_ERR(editor->open_directory(key, operation->baton, head, 259251881Speter subpool, &child->baton)); 260251881Speter } 261251881Speter else 262251881Speter { 263251881Speter SVN_ERR(editor->open_file(key, operation->baton, head, 264251881Speter subpool, &file_baton)); 265251881Speter } 266251881Speter } 267251881Speter /* Adds and replacements could also be for directories or files. */ 268251881Speter if (child->operation == OP_ADD || child->operation == OP_REPLACE) 269251881Speter { 270251881Speter if (child->kind == svn_node_dir) 271251881Speter { 272251881Speter SVN_ERR(editor->add_directory(key, operation->baton, 273251881Speter child->url, child->rev, 274251881Speter subpool, &child->baton)); 275251881Speter } 276251881Speter else 277251881Speter { 278251881Speter SVN_ERR(editor->add_file(key, operation->baton, child->url, 279251881Speter child->rev, subpool, &file_baton)); 280251881Speter } 281251881Speter } 282251881Speter /* If there's a source file and an open file baton, we get to 283251881Speter change textual contents. */ 284251881Speter if ((child->src_file) && (file_baton)) 285251881Speter { 286251881Speter svn_txdelta_window_handler_t handler; 287251881Speter void *handler_baton; 288251881Speter svn_stream_t *contents; 289251881Speter 290251881Speter SVN_ERR(editor->apply_textdelta(file_baton, NULL, subpool, 291251881Speter &handler, &handler_baton)); 292251881Speter if (strcmp(child->src_file, "-") != 0) 293251881Speter { 294251881Speter SVN_ERR(svn_stream_open_readonly(&contents, child->src_file, 295251881Speter pool, pool)); 296251881Speter } 297251881Speter else 298251881Speter { 299251881Speter SVN_ERR(svn_stream_for_stdin(&contents, pool)); 300251881Speter } 301251881Speter SVN_ERR(svn_txdelta_send_stream(contents, handler, 302251881Speter handler_baton, NULL, pool)); 303251881Speter } 304251881Speter /* If we opened a file, we need to apply outstanding propmods, 305251881Speter then close it. */ 306251881Speter if (file_baton) 307251881Speter { 308251881Speter if (child->kind == svn_node_file) 309251881Speter { 310251881Speter SVN_ERR(change_props(editor, file_baton, child, subpool)); 311251881Speter } 312251881Speter SVN_ERR(editor->close_file(file_baton, NULL, subpool)); 313251881Speter } 314251881Speter /* If we opened, added, or replaced a directory, we need to 315251881Speter recurse, apply outstanding propmods, and then close it. */ 316251881Speter if ((child->kind == svn_node_dir) 317251881Speter && child->operation != OP_DELETE) 318251881Speter { 319251881Speter SVN_ERR(change_props(editor, child->baton, child, subpool)); 320251881Speter 321251881Speter SVN_ERR(drive(child, head, editor, subpool)); 322251881Speter 323251881Speter SVN_ERR(editor->close_directory(child->baton, subpool)); 324251881Speter } 325251881Speter } 326251881Speter svn_pool_destroy(subpool); 327251881Speter return SVN_NO_ERROR; 328251881Speter} 329251881Speter 330251881Speter 331251881Speter/* Find the operation associated with PATH, which is a single-path 332251881Speter component representing a child of the path represented by 333251881Speter OPERATION. If no such child operation exists, create a new one of 334251881Speter type OP_OPEN. */ 335251881Speterstatic struct operation * 336251881Speterget_operation(const char *path, 337251881Speter struct operation *operation, 338251881Speter apr_pool_t *pool) 339251881Speter{ 340251881Speter struct operation *child = svn_hash_gets(operation->children, path); 341251881Speter if (! child) 342251881Speter { 343251881Speter child = apr_pcalloc(pool, sizeof(*child)); 344251881Speter child->children = apr_hash_make(pool); 345251881Speter child->operation = OP_OPEN; 346251881Speter child->rev = SVN_INVALID_REVNUM; 347251881Speter child->kind = svn_node_dir; 348251881Speter child->prop_mods = apr_hash_make(pool); 349251881Speter child->prop_dels = apr_array_make(pool, 1, sizeof(const char *)); 350251881Speter svn_hash_sets(operation->children, path, child); 351251881Speter } 352251881Speter return child; 353251881Speter} 354251881Speter 355251881Speter/* Return the portion of URL that is relative to ANCHOR (URI-decoded). */ 356251881Speterstatic const char * 357251881Spetersubtract_anchor(const char *anchor, const char *url, apr_pool_t *pool) 358251881Speter{ 359251881Speter return svn_uri_skip_ancestor(anchor, url, pool); 360251881Speter} 361251881Speter 362251881Speter/* Add PATH to the operations tree rooted at OPERATION, creating any 363251881Speter intermediate nodes that are required. Here's what's expected for 364251881Speter each action type: 365251881Speter 366251881Speter ACTION URL REV SRC-FILE PROPNAME 367251881Speter ------------ ----- ------- -------- -------- 368251881Speter ACTION_MKDIR NULL invalid NULL NULL 369251881Speter ACTION_CP valid valid NULL NULL 370251881Speter ACTION_PUT NULL invalid valid NULL 371251881Speter ACTION_RM NULL invalid NULL NULL 372251881Speter ACTION_PROPSET valid invalid NULL valid 373251881Speter ACTION_PROPDEL valid invalid NULL valid 374251881Speter 375251881Speter Node type information is obtained for any copy source (to determine 376251881Speter whether to create a file or directory) and for any deleted path (to 377251881Speter ensure it exists since svn_delta_editor_t->delete_entry doesn't 378251881Speter return an error on non-existent nodes). */ 379251881Speterstatic svn_error_t * 380251881Speterbuild(action_code_t action, 381251881Speter const char *path, 382251881Speter const char *url, 383251881Speter svn_revnum_t rev, 384251881Speter const char *prop_name, 385251881Speter const svn_string_t *prop_value, 386251881Speter const char *src_file, 387251881Speter svn_revnum_t head, 388251881Speter const char *anchor, 389251881Speter svn_ra_session_t *session, 390251881Speter struct operation *operation, 391251881Speter apr_pool_t *pool) 392251881Speter{ 393251881Speter apr_array_header_t *path_bits = svn_path_decompose(path, pool); 394251881Speter const char *path_so_far = ""; 395251881Speter const char *copy_src = NULL; 396251881Speter svn_revnum_t copy_rev = SVN_INVALID_REVNUM; 397251881Speter int i; 398251881Speter 399251881Speter /* Look for any previous operations we've recognized for PATH. If 400251881Speter any of PATH's ancestors have not yet been traversed, we'll be 401251881Speter creating OP_OPEN operations for them as we walk down PATH's path 402251881Speter components. */ 403251881Speter for (i = 0; i < path_bits->nelts; ++i) 404251881Speter { 405251881Speter const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *); 406251881Speter path_so_far = svn_relpath_join(path_so_far, path_bit, pool); 407251881Speter operation = get_operation(path_so_far, operation, pool); 408251881Speter 409251881Speter /* If we cross a replace- or add-with-history, remember the 410251881Speter source of those things in case we need to lookup the node kind 411251881Speter of one of their children. And if this isn't such a copy, 412251881Speter but we've already seen one in of our parent paths, we just need 413251881Speter to extend that copy source path by our current path 414251881Speter component. */ 415251881Speter if (operation->url 416251881Speter && SVN_IS_VALID_REVNUM(operation->rev) 417251881Speter && (operation->operation == OP_REPLACE 418251881Speter || operation->operation == OP_ADD)) 419251881Speter { 420251881Speter copy_src = subtract_anchor(anchor, operation->url, pool); 421251881Speter copy_rev = operation->rev; 422251881Speter } 423251881Speter else if (copy_src) 424251881Speter { 425251881Speter copy_src = svn_relpath_join(copy_src, path_bit, pool); 426251881Speter } 427251881Speter } 428251881Speter 429251881Speter /* Handle property changes. */ 430251881Speter if (prop_name) 431251881Speter { 432251881Speter if (operation->operation == OP_DELETE) 433251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 434251881Speter "cannot set properties on a location being" 435251881Speter " deleted ('%s')", path); 436251881Speter /* If we're not adding this thing ourselves, check for existence. */ 437251881Speter if (! ((operation->operation == OP_ADD) || 438251881Speter (operation->operation == OP_REPLACE))) 439251881Speter { 440251881Speter SVN_ERR(svn_ra_check_path(session, 441251881Speter copy_src ? copy_src : path, 442251881Speter copy_src ? copy_rev : head, 443251881Speter &operation->kind, pool)); 444251881Speter if (operation->kind == svn_node_none) 445251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 446251881Speter "propset: '%s' not found", path); 447251881Speter else if ((operation->kind == svn_node_file) 448251881Speter && (operation->operation == OP_OPEN)) 449251881Speter operation->operation = OP_PROPSET; 450251881Speter } 451251881Speter if (! prop_value) 452251881Speter APR_ARRAY_PUSH(operation->prop_dels, const char *) = prop_name; 453251881Speter else 454251881Speter svn_hash_sets(operation->prop_mods, prop_name, prop_value); 455251881Speter if (!operation->rev) 456251881Speter operation->rev = rev; 457251881Speter return SVN_NO_ERROR; 458251881Speter } 459251881Speter 460251881Speter /* We won't fuss about multiple operations on the same path in the 461251881Speter following cases: 462251881Speter 463251881Speter - the prior operation was, in fact, a no-op (open) 464251881Speter - the prior operation was a propset placeholder 465251881Speter - the prior operation was a deletion 466251881Speter 467251881Speter Note: while the operation structure certainly supports the 468251881Speter ability to do a copy of a file followed by a put of new contents 469251881Speter for the file, we don't let that happen (yet). 470251881Speter */ 471251881Speter if (operation->operation != OP_OPEN 472251881Speter && operation->operation != OP_PROPSET 473251881Speter && operation->operation != OP_DELETE) 474251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 475251881Speter "unsupported multiple operations on '%s'", path); 476251881Speter 477251881Speter /* For deletions, we validate that there's actually something to 478251881Speter delete. If this is a deletion of the child of a copied 479251881Speter directory, we need to remember to look in the copy source tree to 480251881Speter verify that this thing actually exists. */ 481251881Speter if (action == ACTION_RM) 482251881Speter { 483251881Speter operation->operation = OP_DELETE; 484251881Speter SVN_ERR(svn_ra_check_path(session, 485251881Speter copy_src ? copy_src : path, 486251881Speter copy_src ? copy_rev : head, 487251881Speter &operation->kind, pool)); 488251881Speter if (operation->kind == svn_node_none) 489251881Speter { 490251881Speter if (copy_src && strcmp(path, copy_src)) 491251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 492251881Speter "'%s' (from '%s:%ld') not found", 493251881Speter path, copy_src, copy_rev); 494251881Speter else 495251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' not found", 496251881Speter path); 497251881Speter } 498251881Speter } 499251881Speter /* Handle copy operations (which can be adds or replacements). */ 500251881Speter else if (action == ACTION_CP) 501251881Speter { 502251881Speter if (rev > head) 503251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 504251881Speter "Copy source revision cannot be younger " 505251881Speter "than base revision"); 506251881Speter operation->operation = 507251881Speter operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD; 508251881Speter if (operation->operation == OP_ADD) 509251881Speter { 510251881Speter /* There is a bug in the current version of mod_dav_svn 511251881Speter which incorrectly replaces existing directories. 512251881Speter Therefore we need to check if the target exists 513251881Speter and raise an error here. */ 514251881Speter SVN_ERR(svn_ra_check_path(session, 515251881Speter copy_src ? copy_src : path, 516251881Speter copy_src ? copy_rev : head, 517251881Speter &operation->kind, pool)); 518251881Speter if (operation->kind != svn_node_none) 519251881Speter { 520251881Speter if (copy_src && strcmp(path, copy_src)) 521251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 522251881Speter "'%s' (from '%s:%ld') already exists", 523251881Speter path, copy_src, copy_rev); 524251881Speter else 525251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 526251881Speter "'%s' already exists", path); 527251881Speter } 528251881Speter } 529251881Speter SVN_ERR(svn_ra_check_path(session, subtract_anchor(anchor, url, pool), 530251881Speter rev, &operation->kind, pool)); 531251881Speter if (operation->kind == svn_node_none) 532251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 533251881Speter "'%s' not found", 534251881Speter subtract_anchor(anchor, url, pool)); 535251881Speter operation->url = url; 536251881Speter operation->rev = rev; 537251881Speter } 538251881Speter /* Handle mkdir operations (which can be adds or replacements). */ 539251881Speter else if (action == ACTION_MKDIR) 540251881Speter { 541251881Speter operation->operation = 542251881Speter operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD; 543251881Speter operation->kind = svn_node_dir; 544251881Speter } 545251881Speter /* Handle put operations (which can be adds, replacements, or opens). */ 546251881Speter else if (action == ACTION_PUT) 547251881Speter { 548251881Speter if (operation->operation == OP_DELETE) 549251881Speter { 550251881Speter operation->operation = OP_REPLACE; 551251881Speter } 552251881Speter else 553251881Speter { 554251881Speter SVN_ERR(svn_ra_check_path(session, 555251881Speter copy_src ? copy_src : path, 556251881Speter copy_src ? copy_rev : head, 557251881Speter &operation->kind, pool)); 558251881Speter if (operation->kind == svn_node_file) 559251881Speter operation->operation = OP_OPEN; 560251881Speter else if (operation->kind == svn_node_none) 561251881Speter operation->operation = OP_ADD; 562251881Speter else 563251881Speter return svn_error_createf(SVN_ERR_BAD_URL, NULL, 564251881Speter "'%s' is not a file", path); 565251881Speter } 566251881Speter operation->kind = svn_node_file; 567251881Speter operation->src_file = src_file; 568251881Speter } 569251881Speter else 570251881Speter { 571251881Speter /* We shouldn't get here. */ 572251881Speter SVN_ERR_MALFUNCTION(); 573251881Speter } 574251881Speter 575251881Speter return SVN_NO_ERROR; 576251881Speter} 577251881Speter 578251881Speterstruct action { 579251881Speter action_code_t action; 580251881Speter 581251881Speter /* revision (copy-from-rev of path[0] for cp; base-rev for put) */ 582251881Speter svn_revnum_t rev; 583251881Speter 584251881Speter /* action path[0] path[1] 585251881Speter * ------ ------- ------- 586251881Speter * mv source target 587251881Speter * mkdir target (null) 588251881Speter * cp source target 589251881Speter * put target source 590251881Speter * rm target (null) 591251881Speter * propset target (null) 592251881Speter */ 593251881Speter const char *path[2]; 594251881Speter 595251881Speter /* property name/value */ 596251881Speter const char *prop_name; 597251881Speter const svn_string_t *prop_value; 598251881Speter}; 599251881Speter 600251881Speterstruct fetch_baton 601251881Speter{ 602251881Speter svn_ra_session_t *session; 603251881Speter svn_revnum_t head; 604251881Speter}; 605251881Speter 606251881Speterstatic svn_error_t * 607251881Speterfetch_base_func(const char **filename, 608251881Speter void *baton, 609251881Speter const char *path, 610251881Speter svn_revnum_t base_revision, 611251881Speter apr_pool_t *result_pool, 612251881Speter apr_pool_t *scratch_pool) 613251881Speter{ 614251881Speter struct fetch_baton *fb = baton; 615251881Speter svn_stream_t *fstream; 616251881Speter svn_error_t *err; 617251881Speter 618251881Speter if (! SVN_IS_VALID_REVNUM(base_revision)) 619251881Speter base_revision = fb->head; 620251881Speter 621251881Speter SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, 622251881Speter svn_io_file_del_on_pool_cleanup, 623251881Speter result_pool, scratch_pool)); 624251881Speter 625251881Speter err = svn_ra_get_file(fb->session, path, base_revision, fstream, NULL, NULL, 626251881Speter scratch_pool); 627251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 628251881Speter { 629251881Speter svn_error_clear(err); 630251881Speter SVN_ERR(svn_stream_close(fstream)); 631251881Speter 632251881Speter *filename = NULL; 633251881Speter return SVN_NO_ERROR; 634251881Speter } 635251881Speter else if (err) 636251881Speter return svn_error_trace(err); 637251881Speter 638251881Speter SVN_ERR(svn_stream_close(fstream)); 639251881Speter 640251881Speter return SVN_NO_ERROR; 641251881Speter} 642251881Speter 643251881Speterstatic svn_error_t * 644251881Speterfetch_props_func(apr_hash_t **props, 645251881Speter void *baton, 646251881Speter const char *path, 647251881Speter svn_revnum_t base_revision, 648251881Speter apr_pool_t *result_pool, 649251881Speter apr_pool_t *scratch_pool) 650251881Speter{ 651251881Speter struct fetch_baton *fb = baton; 652251881Speter svn_node_kind_t node_kind; 653251881Speter 654251881Speter if (! SVN_IS_VALID_REVNUM(base_revision)) 655251881Speter base_revision = fb->head; 656251881Speter 657251881Speter SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, &node_kind, 658251881Speter scratch_pool)); 659251881Speter 660251881Speter if (node_kind == svn_node_file) 661251881Speter { 662251881Speter SVN_ERR(svn_ra_get_file(fb->session, path, base_revision, NULL, NULL, 663251881Speter props, result_pool)); 664251881Speter } 665251881Speter else if (node_kind == svn_node_dir) 666251881Speter { 667251881Speter apr_array_header_t *tmp_props; 668251881Speter 669251881Speter SVN_ERR(svn_ra_get_dir2(fb->session, NULL, NULL, props, path, 670251881Speter base_revision, 0 /* Dirent fields */, 671251881Speter result_pool)); 672251881Speter tmp_props = svn_prop_hash_to_array(*props, result_pool); 673251881Speter SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, 674251881Speter result_pool)); 675251881Speter *props = svn_prop_array_to_hash(tmp_props, result_pool); 676251881Speter } 677251881Speter else 678251881Speter { 679251881Speter *props = apr_hash_make(result_pool); 680251881Speter } 681251881Speter 682251881Speter return SVN_NO_ERROR; 683251881Speter} 684251881Speter 685251881Speterstatic svn_error_t * 686251881Speterfetch_kind_func(svn_node_kind_t *kind, 687251881Speter void *baton, 688251881Speter const char *path, 689251881Speter svn_revnum_t base_revision, 690251881Speter apr_pool_t *scratch_pool) 691251881Speter{ 692251881Speter struct fetch_baton *fb = baton; 693251881Speter 694251881Speter if (! SVN_IS_VALID_REVNUM(base_revision)) 695251881Speter base_revision = fb->head; 696251881Speter 697251881Speter SVN_ERR(svn_ra_check_path(fb->session, path, base_revision, kind, 698251881Speter scratch_pool)); 699251881Speter 700251881Speter return SVN_NO_ERROR; 701251881Speter} 702251881Speter 703251881Speterstatic svn_delta_shim_callbacks_t * 704251881Speterget_shim_callbacks(svn_ra_session_t *session, 705251881Speter svn_revnum_t head, 706251881Speter apr_pool_t *result_pool) 707251881Speter{ 708251881Speter svn_delta_shim_callbacks_t *callbacks = 709251881Speter svn_delta_shim_callbacks_default(result_pool); 710251881Speter struct fetch_baton *fb = apr_pcalloc(result_pool, sizeof(*fb)); 711251881Speter 712251881Speter fb->session = session; 713251881Speter fb->head = head; 714251881Speter 715251881Speter callbacks->fetch_props_func = fetch_props_func; 716251881Speter callbacks->fetch_kind_func = fetch_kind_func; 717251881Speter callbacks->fetch_base_func = fetch_base_func; 718251881Speter callbacks->fetch_baton = fb; 719251881Speter 720251881Speter return callbacks; 721251881Speter} 722251881Speter 723251881Speterstatic svn_error_t * 724251881Speterexecute(const apr_array_header_t *actions, 725251881Speter const char *anchor, 726251881Speter apr_hash_t *revprops, 727251881Speter const char *username, 728251881Speter const char *password, 729251881Speter const char *config_dir, 730251881Speter const apr_array_header_t *config_options, 731251881Speter svn_boolean_t non_interactive, 732251881Speter svn_boolean_t trust_server_cert, 733251881Speter svn_boolean_t no_auth_cache, 734251881Speter svn_revnum_t base_revision, 735251881Speter apr_pool_t *pool) 736251881Speter{ 737251881Speter svn_ra_session_t *session; 738251881Speter svn_ra_session_t *aux_session; 739251881Speter const char *repos_root; 740251881Speter svn_revnum_t head; 741251881Speter const svn_delta_editor_t *editor; 742251881Speter svn_ra_callbacks2_t *ra_callbacks; 743251881Speter void *editor_baton; 744251881Speter struct operation root; 745251881Speter svn_error_t *err; 746251881Speter apr_hash_t *config; 747251881Speter svn_config_t *cfg_config; 748251881Speter int i; 749251881Speter 750251881Speter SVN_ERR(svn_config_get_config(&config, config_dir, pool)); 751251881Speter SVN_ERR(svn_cmdline__apply_config_options(config, config_options, 752251881Speter "svnmucc: ", "--config-option")); 753251881Speter cfg_config = svn_hash_gets(config, SVN_CONFIG_CATEGORY_CONFIG); 754251881Speter 755251881Speter if (! svn_hash_gets(revprops, SVN_PROP_REVISION_LOG)) 756251881Speter { 757251881Speter svn_string_t *msg = svn_string_create("", pool); 758251881Speter 759251881Speter /* If we can do so, try to pop up $EDITOR to fetch a log message. */ 760251881Speter if (non_interactive) 761251881Speter { 762251881Speter return svn_error_create 763251881Speter (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL, 764251881Speter _("Cannot invoke editor to get log message " 765251881Speter "when non-interactive")); 766251881Speter } 767251881Speter else 768251881Speter { 769251881Speter SVN_ERR(svn_cmdline__edit_string_externally( 770251881Speter &msg, NULL, NULL, "", msg, "svnmucc-commit", config, 771251881Speter TRUE, NULL, apr_hash_pool_get(revprops))); 772251881Speter } 773251881Speter 774251881Speter svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, msg); 775251881Speter } 776251881Speter 777251881Speter SVN_ERR(create_ra_callbacks(&ra_callbacks, username, password, config_dir, 778251881Speter cfg_config, non_interactive, trust_server_cert, 779251881Speter no_auth_cache, pool)); 780251881Speter SVN_ERR(svn_ra_open4(&session, NULL, anchor, NULL, ra_callbacks, 781251881Speter NULL, config, pool)); 782251881Speter /* Open, then reparent to avoid AUTHZ errors when opening the reposroot */ 783251881Speter SVN_ERR(svn_ra_open4(&aux_session, NULL, anchor, NULL, ra_callbacks, 784251881Speter NULL, config, pool)); 785251881Speter SVN_ERR(svn_ra_get_repos_root2(aux_session, &repos_root, pool)); 786251881Speter SVN_ERR(svn_ra_reparent(aux_session, repos_root, pool)); 787251881Speter SVN_ERR(svn_ra_get_latest_revnum(session, &head, pool)); 788251881Speter 789251881Speter /* Reparent to ANCHOR's dir, if ANCHOR is not a directory. */ 790251881Speter { 791251881Speter svn_node_kind_t kind; 792251881Speter 793251881Speter SVN_ERR(svn_ra_check_path(aux_session, 794251881Speter svn_uri_skip_ancestor(repos_root, anchor, pool), 795251881Speter head, &kind, pool)); 796251881Speter if (kind != svn_node_dir) 797251881Speter { 798251881Speter anchor = svn_uri_dirname(anchor, pool); 799251881Speter SVN_ERR(svn_ra_reparent(session, anchor, pool)); 800251881Speter } 801251881Speter } 802251881Speter 803251881Speter if (SVN_IS_VALID_REVNUM(base_revision)) 804251881Speter { 805251881Speter if (base_revision > head) 806251881Speter return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 807251881Speter "No such revision %ld (youngest is %ld)", 808251881Speter base_revision, head); 809251881Speter head = base_revision; 810251881Speter } 811251881Speter 812251881Speter memset(&root, 0, sizeof(root)); 813251881Speter root.children = apr_hash_make(pool); 814251881Speter root.operation = OP_OPEN; 815251881Speter root.kind = svn_node_dir; /* For setting properties */ 816251881Speter root.prop_mods = apr_hash_make(pool); 817251881Speter root.prop_dels = apr_array_make(pool, 1, sizeof(const char *)); 818251881Speter 819251881Speter for (i = 0; i < actions->nelts; ++i) 820251881Speter { 821251881Speter struct action *action = APR_ARRAY_IDX(actions, i, struct action *); 822251881Speter const char *path1, *path2; 823251881Speter switch (action->action) 824251881Speter { 825251881Speter case ACTION_MV: 826251881Speter path1 = subtract_anchor(anchor, action->path[0], pool); 827251881Speter path2 = subtract_anchor(anchor, action->path[1], pool); 828251881Speter SVN_ERR(build(ACTION_RM, path1, NULL, 829251881Speter SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor, 830251881Speter session, &root, pool)); 831251881Speter SVN_ERR(build(ACTION_CP, path2, action->path[0], 832251881Speter head, NULL, NULL, NULL, head, anchor, 833251881Speter session, &root, pool)); 834251881Speter break; 835251881Speter case ACTION_CP: 836251881Speter path2 = subtract_anchor(anchor, action->path[1], pool); 837251881Speter if (action->rev == SVN_INVALID_REVNUM) 838251881Speter action->rev = head; 839251881Speter SVN_ERR(build(ACTION_CP, path2, action->path[0], 840251881Speter action->rev, NULL, NULL, NULL, head, anchor, 841251881Speter session, &root, pool)); 842251881Speter break; 843251881Speter case ACTION_RM: 844251881Speter path1 = subtract_anchor(anchor, action->path[0], pool); 845251881Speter SVN_ERR(build(ACTION_RM, path1, NULL, 846251881Speter SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor, 847251881Speter session, &root, pool)); 848251881Speter break; 849251881Speter case ACTION_MKDIR: 850251881Speter path1 = subtract_anchor(anchor, action->path[0], pool); 851251881Speter SVN_ERR(build(ACTION_MKDIR, path1, action->path[0], 852251881Speter SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor, 853251881Speter session, &root, pool)); 854251881Speter break; 855251881Speter case ACTION_PUT: 856251881Speter path1 = subtract_anchor(anchor, action->path[0], pool); 857251881Speter SVN_ERR(build(ACTION_PUT, path1, action->path[0], 858251881Speter SVN_INVALID_REVNUM, NULL, NULL, action->path[1], 859251881Speter head, anchor, session, &root, pool)); 860251881Speter break; 861251881Speter case ACTION_PROPSET: 862251881Speter case ACTION_PROPDEL: 863251881Speter path1 = subtract_anchor(anchor, action->path[0], pool); 864251881Speter SVN_ERR(build(action->action, path1, action->path[0], 865251881Speter SVN_INVALID_REVNUM, 866251881Speter action->prop_name, action->prop_value, 867251881Speter NULL, head, anchor, session, &root, pool)); 868251881Speter break; 869251881Speter case ACTION_PROPSETF: 870251881Speter default: 871251881Speter SVN_ERR_MALFUNCTION_NO_RETURN(); 872251881Speter } 873251881Speter } 874251881Speter 875251881Speter SVN_ERR(svn_ra__register_editor_shim_callbacks(session, 876251881Speter get_shim_callbacks(aux_session, head, pool))); 877251881Speter SVN_ERR(svn_ra_get_commit_editor3(session, &editor, &editor_baton, revprops, 878251881Speter commit_callback, NULL, NULL, FALSE, pool)); 879251881Speter 880251881Speter SVN_ERR(editor->open_root(editor_baton, head, pool, &root.baton)); 881251881Speter err = change_props(editor, root.baton, &root, pool); 882251881Speter if (!err) 883251881Speter err = drive(&root, head, editor, pool); 884251881Speter if (!err) 885251881Speter err = editor->close_directory(root.baton, pool); 886251881Speter if (!err) 887251881Speter err = editor->close_edit(editor_baton, pool); 888251881Speter 889251881Speter if (err) 890251881Speter err = svn_error_compose_create(err, 891251881Speter editor->abort_edit(editor_baton, pool)); 892251881Speter 893251881Speter return err; 894251881Speter} 895251881Speter 896251881Speterstatic svn_error_t * 897251881Speterread_propvalue_file(const svn_string_t **value_p, 898251881Speter const char *filename, 899251881Speter apr_pool_t *pool) 900251881Speter{ 901251881Speter svn_stringbuf_t *value; 902251881Speter apr_pool_t *scratch_pool = svn_pool_create(pool); 903251881Speter 904251881Speter SVN_ERR(svn_stringbuf_from_file2(&value, filename, scratch_pool)); 905251881Speter *value_p = svn_string_create_from_buf(value, pool); 906251881Speter svn_pool_destroy(scratch_pool); 907251881Speter return SVN_NO_ERROR; 908251881Speter} 909251881Speter 910251881Speter/* Perform the typical suite of manipulations for user-provided URLs 911251881Speter on URL, returning the result (allocated from POOL): IRI-to-URI 912251881Speter conversion, auto-escaping, and canonicalization. */ 913251881Speterstatic const char * 914251881Spetersanitize_url(const char *url, 915251881Speter apr_pool_t *pool) 916251881Speter{ 917251881Speter url = svn_path_uri_from_iri(url, pool); 918251881Speter url = svn_path_uri_autoescape(url, pool); 919251881Speter return svn_uri_canonicalize(url, pool); 920251881Speter} 921251881Speter 922251881Speterstatic void 923251881Speterusage(apr_pool_t *pool, int exit_val) 924251881Speter{ 925251881Speter FILE *stream = exit_val == EXIT_SUCCESS ? stdout : stderr; 926251881Speter svn_error_clear(svn_cmdline_fputs( 927251881Speter _("Subversion multiple URL command client\n" 928251881Speter "usage: svnmucc ACTION...\n" 929251881Speter "\n" 930251881Speter " Perform one or more Subversion repository URL-based ACTIONs, committing\n" 931251881Speter " the result as a (single) new revision.\n" 932251881Speter "\n" 933251881Speter "Actions:\n" 934251881Speter " cp REV SRC-URL DST-URL : copy SRC-URL@REV to DST-URL\n" 935251881Speter " mkdir URL : create new directory URL\n" 936251881Speter " mv SRC-URL DST-URL : move SRC-URL to DST-URL\n" 937251881Speter " rm URL : delete URL\n" 938251881Speter " put SRC-FILE URL : add or modify file URL with contents copied from\n" 939251881Speter " SRC-FILE (use \"-\" to read from standard input)\n" 940251881Speter " propset NAME VALUE URL : set property NAME on URL to VALUE\n" 941251881Speter " propsetf NAME FILE URL : set property NAME on URL to value read from FILE\n" 942251881Speter " propdel NAME URL : delete property NAME from URL\n" 943251881Speter "\n" 944251881Speter "Valid options:\n" 945251881Speter " -h, -? [--help] : display this text\n" 946251881Speter " -m [--message] ARG : use ARG as a log message\n" 947251881Speter " -F [--file] ARG : read log message from file ARG\n" 948251881Speter " -u [--username] ARG : commit the changes as username ARG\n" 949251881Speter " -p [--password] ARG : use ARG as the password\n" 950251881Speter " -U [--root-url] ARG : interpret all action URLs relative to ARG\n" 951251881Speter " -r [--revision] ARG : use revision ARG as baseline for changes\n" 952251881Speter " --with-revprop ARG : set revision property in the following format:\n" 953251881Speter " NAME[=VALUE]\n" 954251881Speter " --non-interactive : do no interactive prompting (default is to\n" 955251881Speter " prompt only if standard input is a terminal)\n" 956251881Speter " --force-interactive : do interactive prompting even if standard\n" 957251881Speter " input is not a terminal\n" 958251881Speter " --trust-server-cert : accept SSL server certificates from unknown\n" 959251881Speter " certificate authorities without prompting (but\n" 960251881Speter " only with '--non-interactive')\n" 961251881Speter " -X [--extra-args] ARG : append arguments from file ARG (one per line;\n" 962251881Speter " use \"-\" to read from standard input)\n" 963251881Speter " --config-dir ARG : use ARG to override the config directory\n" 964251881Speter " --config-option ARG : use ARG to override a configuration option\n" 965251881Speter " --no-auth-cache : do not cache authentication tokens\n" 966251881Speter " --version : print version information\n"), 967251881Speter stream, pool)); 968251881Speter svn_pool_destroy(pool); 969251881Speter exit(exit_val); 970251881Speter} 971251881Speter 972251881Speterstatic void 973251881Speterinsufficient(apr_pool_t *pool) 974251881Speter{ 975251881Speter handle_error(svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, 976251881Speter "insufficient arguments"), 977251881Speter pool); 978251881Speter} 979251881Speter 980251881Speterstatic svn_error_t * 981251881Speterdisplay_version(apr_getopt_t *os, apr_pool_t *pool) 982251881Speter{ 983251881Speter const char *ra_desc_start 984251881Speter = "The following repository access (RA) modules are available:\n\n"; 985251881Speter svn_stringbuf_t *version_footer; 986251881Speter 987251881Speter version_footer = svn_stringbuf_create(ra_desc_start, pool); 988251881Speter SVN_ERR(svn_ra_print_modules(version_footer, pool)); 989251881Speter 990251881Speter SVN_ERR(svn_opt_print_help4(os, "svnmucc", TRUE, FALSE, FALSE, 991251881Speter version_footer->data, 992251881Speter NULL, NULL, NULL, NULL, NULL, pool)); 993251881Speter 994251881Speter return SVN_NO_ERROR; 995251881Speter} 996251881Speter 997251881Speter/* Return an error about the mutual exclusivity of the -m, -F, and 998251881Speter --with-revprop=svn:log command-line options. */ 999251881Speterstatic svn_error_t * 1000251881Spetermutually_exclusive_logs_error(void) 1001251881Speter{ 1002251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1003251881Speter _("--message (-m), --file (-F), and " 1004251881Speter "--with-revprop=svn:log are mutually " 1005251881Speter "exclusive")); 1006251881Speter} 1007251881Speter 1008251881Speter/* Ensure that the REVPROPS hash contains a command-line-provided log 1009251881Speter message, if any, and that there was but one source of such a thing 1010251881Speter provided on that command-line. */ 1011251881Speterstatic svn_error_t * 1012251881Spetersanitize_log_sources(apr_hash_t *revprops, 1013251881Speter const char *message, 1014251881Speter svn_stringbuf_t *filedata) 1015251881Speter{ 1016251881Speter apr_pool_t *hash_pool = apr_hash_pool_get(revprops); 1017251881Speter 1018251881Speter /* If we already have a log message in the revprop hash, then just 1019251881Speter make sure the user didn't try to also use -m or -F. Otherwise, 1020251881Speter we need to consult -m or -F to find a log message, if any. */ 1021251881Speter if (svn_hash_gets(revprops, SVN_PROP_REVISION_LOG)) 1022251881Speter { 1023251881Speter if (filedata || message) 1024251881Speter return mutually_exclusive_logs_error(); 1025251881Speter } 1026251881Speter else if (filedata) 1027251881Speter { 1028251881Speter if (message) 1029251881Speter return mutually_exclusive_logs_error(); 1030251881Speter 1031251881Speter SVN_ERR(svn_utf_cstring_to_utf8(&message, filedata->data, hash_pool)); 1032251881Speter svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, 1033251881Speter svn_stringbuf__morph_into_string(filedata)); 1034251881Speter } 1035251881Speter else if (message) 1036251881Speter { 1037251881Speter svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, 1038251881Speter svn_string_create(message, hash_pool)); 1039251881Speter } 1040251881Speter 1041251881Speter return SVN_NO_ERROR; 1042251881Speter} 1043251881Speter 1044251881Speterint 1045251881Spetermain(int argc, const char **argv) 1046251881Speter{ 1047251881Speter apr_pool_t *pool = init("svnmucc"); 1048251881Speter apr_array_header_t *actions = apr_array_make(pool, 1, 1049251881Speter sizeof(struct action *)); 1050251881Speter const char *anchor = NULL; 1051251881Speter svn_error_t *err = SVN_NO_ERROR; 1052251881Speter apr_getopt_t *opts; 1053251881Speter enum { 1054251881Speter config_dir_opt = SVN_OPT_FIRST_LONGOPT_ID, 1055251881Speter config_inline_opt, 1056251881Speter no_auth_cache_opt, 1057251881Speter version_opt, 1058251881Speter with_revprop_opt, 1059251881Speter non_interactive_opt, 1060251881Speter force_interactive_opt, 1061251881Speter trust_server_cert_opt 1062251881Speter }; 1063251881Speter static const apr_getopt_option_t options[] = { 1064251881Speter {"message", 'm', 1, ""}, 1065251881Speter {"file", 'F', 1, ""}, 1066251881Speter {"username", 'u', 1, ""}, 1067251881Speter {"password", 'p', 1, ""}, 1068251881Speter {"root-url", 'U', 1, ""}, 1069251881Speter {"revision", 'r', 1, ""}, 1070251881Speter {"with-revprop", with_revprop_opt, 1, ""}, 1071251881Speter {"extra-args", 'X', 1, ""}, 1072251881Speter {"help", 'h', 0, ""}, 1073251881Speter {NULL, '?', 0, ""}, 1074251881Speter {"non-interactive", non_interactive_opt, 0, ""}, 1075251881Speter {"force-interactive", force_interactive_opt, 0, ""}, 1076251881Speter {"trust-server-cert", trust_server_cert_opt, 0, ""}, 1077251881Speter {"config-dir", config_dir_opt, 1, ""}, 1078251881Speter {"config-option", config_inline_opt, 1, ""}, 1079251881Speter {"no-auth-cache", no_auth_cache_opt, 0, ""}, 1080251881Speter {"version", version_opt, 0, ""}, 1081251881Speter {NULL, 0, 0, NULL} 1082251881Speter }; 1083251881Speter const char *message = NULL; 1084251881Speter svn_stringbuf_t *filedata = NULL; 1085251881Speter const char *username = NULL, *password = NULL; 1086251881Speter const char *root_url = NULL, *extra_args_file = NULL; 1087251881Speter const char *config_dir = NULL; 1088251881Speter apr_array_header_t *config_options; 1089251881Speter svn_boolean_t non_interactive = FALSE; 1090251881Speter svn_boolean_t force_interactive = FALSE; 1091251881Speter svn_boolean_t trust_server_cert = FALSE; 1092251881Speter svn_boolean_t no_auth_cache = FALSE; 1093251881Speter svn_revnum_t base_revision = SVN_INVALID_REVNUM; 1094251881Speter apr_array_header_t *action_args; 1095251881Speter apr_hash_t *revprops = apr_hash_make(pool); 1096251881Speter int i; 1097251881Speter 1098251881Speter config_options = apr_array_make(pool, 0, 1099251881Speter sizeof(svn_cmdline__config_argument_t*)); 1100251881Speter 1101251881Speter apr_getopt_init(&opts, pool, argc, argv); 1102251881Speter opts->interleave = 1; 1103251881Speter while (1) 1104251881Speter { 1105251881Speter int opt; 1106251881Speter const char *arg; 1107251881Speter const char *opt_arg; 1108251881Speter 1109251881Speter apr_status_t status = apr_getopt_long(opts, options, &opt, &arg); 1110251881Speter if (APR_STATUS_IS_EOF(status)) 1111251881Speter break; 1112251881Speter if (status != APR_SUCCESS) 1113251881Speter handle_error(svn_error_wrap_apr(status, "getopt failure"), pool); 1114251881Speter switch(opt) 1115251881Speter { 1116251881Speter case 'm': 1117251881Speter err = svn_utf_cstring_to_utf8(&message, arg, pool); 1118251881Speter if (err) 1119251881Speter handle_error(err, pool); 1120251881Speter break; 1121251881Speter case 'F': 1122251881Speter { 1123251881Speter const char *arg_utf8; 1124251881Speter err = svn_utf_cstring_to_utf8(&arg_utf8, arg, pool); 1125251881Speter if (! err) 1126251881Speter err = svn_stringbuf_from_file2(&filedata, arg, pool); 1127251881Speter if (err) 1128251881Speter handle_error(err, pool); 1129251881Speter } 1130251881Speter break; 1131251881Speter case 'u': 1132251881Speter username = apr_pstrdup(pool, arg); 1133251881Speter break; 1134251881Speter case 'p': 1135251881Speter password = apr_pstrdup(pool, arg); 1136251881Speter break; 1137251881Speter case 'U': 1138251881Speter err = svn_utf_cstring_to_utf8(&root_url, arg, pool); 1139251881Speter if (err) 1140251881Speter handle_error(err, pool); 1141251881Speter if (! svn_path_is_url(root_url)) 1142251881Speter handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 1143251881Speter "'%s' is not a URL\n", root_url), 1144251881Speter pool); 1145251881Speter root_url = sanitize_url(root_url, pool); 1146251881Speter break; 1147251881Speter case 'r': 1148251881Speter { 1149251881Speter char *digits_end = NULL; 1150251881Speter base_revision = strtol(arg, &digits_end, 10); 1151251881Speter if ((! SVN_IS_VALID_REVNUM(base_revision)) 1152251881Speter || (! digits_end) 1153251881Speter || *digits_end) 1154251881Speter handle_error(svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 1155251881Speter NULL, "Invalid revision number"), 1156251881Speter pool); 1157251881Speter } 1158251881Speter break; 1159251881Speter case with_revprop_opt: 1160251881Speter err = svn_opt_parse_revprop(&revprops, arg, pool); 1161251881Speter if (err != SVN_NO_ERROR) 1162251881Speter handle_error(err, pool); 1163251881Speter break; 1164251881Speter case 'X': 1165251881Speter extra_args_file = apr_pstrdup(pool, arg); 1166251881Speter break; 1167251881Speter case non_interactive_opt: 1168251881Speter non_interactive = TRUE; 1169251881Speter break; 1170251881Speter case force_interactive_opt: 1171251881Speter force_interactive = TRUE; 1172251881Speter break; 1173251881Speter case trust_server_cert_opt: 1174251881Speter trust_server_cert = TRUE; 1175251881Speter break; 1176251881Speter case config_dir_opt: 1177251881Speter err = svn_utf_cstring_to_utf8(&config_dir, arg, pool); 1178251881Speter if (err) 1179251881Speter handle_error(err, pool); 1180251881Speter break; 1181251881Speter case config_inline_opt: 1182251881Speter err = svn_utf_cstring_to_utf8(&opt_arg, arg, pool); 1183251881Speter if (err) 1184251881Speter handle_error(err, pool); 1185251881Speter 1186251881Speter err = svn_cmdline__parse_config_option(config_options, opt_arg, 1187251881Speter pool); 1188251881Speter if (err) 1189251881Speter handle_error(err, pool); 1190251881Speter break; 1191251881Speter case no_auth_cache_opt: 1192251881Speter no_auth_cache = TRUE; 1193251881Speter break; 1194251881Speter case version_opt: 1195251881Speter SVN_INT_ERR(display_version(opts, pool)); 1196251881Speter exit(EXIT_SUCCESS); 1197251881Speter break; 1198251881Speter case 'h': 1199251881Speter case '?': 1200251881Speter usage(pool, EXIT_SUCCESS); 1201251881Speter break; 1202251881Speter } 1203251881Speter } 1204251881Speter 1205251881Speter if (non_interactive && force_interactive) 1206251881Speter { 1207251881Speter err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1208251881Speter _("--non-interactive and --force-interactive " 1209251881Speter "are mutually exclusive")); 1210251881Speter return svn_cmdline_handle_exit_error(err, pool, "svnmucc: "); 1211251881Speter } 1212251881Speter else 1213251881Speter non_interactive = !svn_cmdline__be_interactive(non_interactive, 1214251881Speter force_interactive); 1215251881Speter 1216251881Speter if (trust_server_cert && !non_interactive) 1217251881Speter { 1218251881Speter err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 1219251881Speter _("--trust-server-cert requires " 1220251881Speter "--non-interactive")); 1221251881Speter return svn_cmdline_handle_exit_error(err, pool, "svnmucc: "); 1222251881Speter } 1223251881Speter 1224251881Speter /* Make sure we have a log message to use. */ 1225251881Speter err = sanitize_log_sources(revprops, message, filedata); 1226251881Speter if (err) 1227251881Speter handle_error(err, pool); 1228251881Speter 1229251881Speter /* Copy the rest of our command-line arguments to an array, 1230251881Speter UTF-8-ing them along the way. */ 1231251881Speter action_args = apr_array_make(pool, opts->argc, sizeof(const char *)); 1232251881Speter while (opts->ind < opts->argc) 1233251881Speter { 1234251881Speter const char *arg = opts->argv[opts->ind++]; 1235251881Speter if ((err = svn_utf_cstring_to_utf8(&(APR_ARRAY_PUSH(action_args, 1236251881Speter const char *)), 1237251881Speter arg, pool))) 1238251881Speter handle_error(err, pool); 1239251881Speter } 1240251881Speter 1241251881Speter /* If there are extra arguments in a supplementary file, tack those 1242251881Speter on, too (again, in UTF8 form). */ 1243251881Speter if (extra_args_file) 1244251881Speter { 1245251881Speter const char *extra_args_file_utf8; 1246251881Speter svn_stringbuf_t *contents, *contents_utf8; 1247251881Speter 1248251881Speter err = svn_utf_cstring_to_utf8(&extra_args_file_utf8, 1249251881Speter extra_args_file, pool); 1250251881Speter if (! err) 1251251881Speter err = svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool); 1252251881Speter if (! err) 1253251881Speter err = svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool); 1254251881Speter if (err) 1255251881Speter handle_error(err, pool); 1256251881Speter svn_cstring_split_append(action_args, contents_utf8->data, "\n\r", 1257251881Speter FALSE, pool); 1258251881Speter } 1259251881Speter 1260251881Speter /* Now, we iterate over the combined set of arguments -- our actions. */ 1261251881Speter for (i = 0; i < action_args->nelts; ) 1262251881Speter { 1263251881Speter int j, num_url_args; 1264251881Speter const char *action_string = APR_ARRAY_IDX(action_args, i, const char *); 1265251881Speter struct action *action = apr_pcalloc(pool, sizeof(*action)); 1266251881Speter 1267251881Speter /* First, parse the action. */ 1268251881Speter if (! strcmp(action_string, "mv")) 1269251881Speter action->action = ACTION_MV; 1270251881Speter else if (! strcmp(action_string, "cp")) 1271251881Speter action->action = ACTION_CP; 1272251881Speter else if (! strcmp(action_string, "mkdir")) 1273251881Speter action->action = ACTION_MKDIR; 1274251881Speter else if (! strcmp(action_string, "rm")) 1275251881Speter action->action = ACTION_RM; 1276251881Speter else if (! strcmp(action_string, "put")) 1277251881Speter action->action = ACTION_PUT; 1278251881Speter else if (! strcmp(action_string, "propset")) 1279251881Speter action->action = ACTION_PROPSET; 1280251881Speter else if (! strcmp(action_string, "propsetf")) 1281251881Speter action->action = ACTION_PROPSETF; 1282251881Speter else if (! strcmp(action_string, "propdel")) 1283251881Speter action->action = ACTION_PROPDEL; 1284251881Speter else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h") 1285251881Speter || ! strcmp(action_string, "help")) 1286251881Speter usage(pool, EXIT_SUCCESS); 1287251881Speter else 1288251881Speter handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 1289251881Speter "'%s' is not an action\n", 1290251881Speter action_string), pool); 1291251881Speter if (++i == action_args->nelts) 1292251881Speter insufficient(pool); 1293251881Speter 1294251881Speter /* For copies, there should be a revision number next. */ 1295251881Speter if (action->action == ACTION_CP) 1296251881Speter { 1297251881Speter const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *); 1298251881Speter if (strcmp(rev_str, "head") == 0) 1299251881Speter action->rev = SVN_INVALID_REVNUM; 1300251881Speter else if (strcmp(rev_str, "HEAD") == 0) 1301251881Speter action->rev = SVN_INVALID_REVNUM; 1302251881Speter else 1303251881Speter { 1304251881Speter char *end; 1305251881Speter 1306251881Speter while (*rev_str == 'r') 1307251881Speter ++rev_str; 1308251881Speter 1309251881Speter action->rev = strtol(rev_str, &end, 0); 1310251881Speter if (*end) 1311251881Speter handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 1312251881Speter "'%s' is not a revision\n", 1313251881Speter rev_str), pool); 1314251881Speter } 1315251881Speter if (++i == action_args->nelts) 1316251881Speter insufficient(pool); 1317251881Speter } 1318251881Speter else 1319251881Speter { 1320251881Speter action->rev = SVN_INVALID_REVNUM; 1321251881Speter } 1322251881Speter 1323251881Speter /* For puts, there should be a local file next. */ 1324251881Speter if (action->action == ACTION_PUT) 1325251881Speter { 1326251881Speter action->path[1] = 1327251881Speter svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i, 1328251881Speter const char *), pool); 1329251881Speter if (++i == action_args->nelts) 1330251881Speter insufficient(pool); 1331251881Speter } 1332251881Speter 1333251881Speter /* For propset, propsetf, and propdel, a property name (and 1334251881Speter maybe a property value or file which contains one) comes next. */ 1335251881Speter if ((action->action == ACTION_PROPSET) 1336251881Speter || (action->action == ACTION_PROPSETF) 1337251881Speter || (action->action == ACTION_PROPDEL)) 1338251881Speter { 1339251881Speter action->prop_name = APR_ARRAY_IDX(action_args, i, const char *); 1340251881Speter if (++i == action_args->nelts) 1341251881Speter insufficient(pool); 1342251881Speter 1343251881Speter if (action->action == ACTION_PROPDEL) 1344251881Speter { 1345251881Speter action->prop_value = NULL; 1346251881Speter } 1347251881Speter else if (action->action == ACTION_PROPSET) 1348251881Speter { 1349251881Speter action->prop_value = 1350251881Speter svn_string_create(APR_ARRAY_IDX(action_args, i, 1351251881Speter const char *), pool); 1352251881Speter if (++i == action_args->nelts) 1353251881Speter insufficient(pool); 1354251881Speter } 1355251881Speter else 1356251881Speter { 1357251881Speter const char *propval_file = 1358251881Speter svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i, 1359251881Speter const char *), pool); 1360251881Speter 1361251881Speter if (++i == action_args->nelts) 1362251881Speter insufficient(pool); 1363251881Speter 1364251881Speter err = read_propvalue_file(&(action->prop_value), 1365251881Speter propval_file, pool); 1366251881Speter if (err) 1367251881Speter handle_error(err, pool); 1368251881Speter 1369251881Speter action->action = ACTION_PROPSET; 1370251881Speter } 1371251881Speter 1372251881Speter if (action->prop_value 1373251881Speter && svn_prop_needs_translation(action->prop_name)) 1374251881Speter { 1375251881Speter svn_string_t *translated_value; 1376251881Speter err = svn_subst_translate_string2(&translated_value, NULL, 1377251881Speter NULL, action->prop_value, NULL, 1378251881Speter FALSE, pool, pool); 1379251881Speter if (err) 1380251881Speter handle_error( 1381251881Speter svn_error_quick_wrap(err, 1382251881Speter "Error normalizing property value"), 1383251881Speter pool); 1384251881Speter action->prop_value = translated_value; 1385251881Speter } 1386251881Speter } 1387251881Speter 1388251881Speter /* How many URLs does this action expect? */ 1389251881Speter if (action->action == ACTION_RM 1390251881Speter || action->action == ACTION_MKDIR 1391251881Speter || action->action == ACTION_PUT 1392251881Speter || action->action == ACTION_PROPSET 1393251881Speter || action->action == ACTION_PROPSETF /* shouldn't see this one */ 1394251881Speter || action->action == ACTION_PROPDEL) 1395251881Speter num_url_args = 1; 1396251881Speter else 1397251881Speter num_url_args = 2; 1398251881Speter 1399251881Speter /* Parse the required number of URLs. */ 1400251881Speter for (j = 0; j < num_url_args; ++j) 1401251881Speter { 1402251881Speter const char *url = APR_ARRAY_IDX(action_args, i, const char *); 1403251881Speter 1404251881Speter /* If there's a ROOT_URL, we expect URL to be a path 1405251881Speter relative to ROOT_URL (and we build a full url from the 1406251881Speter combination of the two). Otherwise, it should be a full 1407251881Speter url. */ 1408251881Speter if (! svn_path_is_url(url)) 1409251881Speter { 1410251881Speter if (! root_url) 1411251881Speter handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 1412251881Speter "'%s' is not a URL, and " 1413251881Speter "--root-url (-U) not provided\n", 1414251881Speter url), pool); 1415251881Speter /* ### These relpaths are already URI-encoded. */ 1416251881Speter url = apr_pstrcat(pool, root_url, "/", 1417251881Speter svn_relpath_canonicalize(url, pool), 1418251881Speter (char *)NULL); 1419251881Speter } 1420251881Speter url = sanitize_url(url, pool); 1421251881Speter action->path[j] = url; 1422251881Speter 1423251881Speter /* The first URL arguments to 'cp', 'pd', 'ps' could be the anchor, 1424251881Speter but the other URLs should be children of the anchor. */ 1425251881Speter if (! (action->action == ACTION_CP && j == 0) 1426251881Speter && action->action != ACTION_PROPDEL 1427251881Speter && action->action != ACTION_PROPSET 1428251881Speter && action->action != ACTION_PROPSETF) 1429251881Speter url = svn_uri_dirname(url, pool); 1430251881Speter if (! anchor) 1431251881Speter anchor = url; 1432251881Speter else 1433262253Speter { 1434262253Speter anchor = svn_uri_get_longest_ancestor(anchor, url, pool); 1435262253Speter if (!anchor || !anchor[0]) 1436262253Speter handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, 1437262253Speter "URLs in the action list do not " 1438262253Speter "share a common ancestor"), 1439262253Speter pool); 1440262253Speter } 1441251881Speter 1442251881Speter if ((++i == action_args->nelts) && (j + 1 < num_url_args)) 1443251881Speter insufficient(pool); 1444251881Speter } 1445251881Speter APR_ARRAY_PUSH(actions, struct action *) = action; 1446251881Speter } 1447251881Speter 1448251881Speter if (! actions->nelts) 1449251881Speter usage(pool, EXIT_FAILURE); 1450251881Speter 1451251881Speter if ((err = execute(actions, anchor, revprops, username, password, 1452251881Speter config_dir, config_options, non_interactive, 1453251881Speter trust_server_cert, no_auth_cache, base_revision, pool))) 1454251881Speter { 1455251881Speter if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive) 1456251881Speter err = svn_error_quick_wrap(err, 1457251881Speter _("Authentication failed and interactive" 1458251881Speter " prompting is disabled; see the" 1459251881Speter " --force-interactive option")); 1460251881Speter handle_error(err, pool); 1461251881Speter } 1462251881Speter 1463251881Speter /* Ensure that stdout is flushed, so the user will see all results. */ 1464251881Speter svn_error_clear(svn_cmdline_fflush(stdout)); 1465251881Speter 1466251881Speter svn_pool_destroy(pool); 1467251881Speter return EXIT_SUCCESS; 1468251881Speter} 1469