diff options
Diffstat (limited to 'tools/client-side/svnmucc/svnmucc.c')
-rw-r--r-- | tools/client-side/svnmucc/svnmucc.c | 1206 |
1 files changed, 0 insertions, 1206 deletions
diff --git a/tools/client-side/svnmucc/svnmucc.c b/tools/client-side/svnmucc/svnmucc.c deleted file mode 100644 index b33d6a9..0000000 --- a/tools/client-side/svnmucc/svnmucc.c +++ /dev/null @@ -1,1206 +0,0 @@ -/* - * svnmucc.c: Subversion Multiple URL Client - * - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - * ==================================================================== - * - */ - -/* Multiple URL Command Client - - Combine a list of mv, cp and rm commands on URLs into a single commit. - - How it works: the command line arguments are parsed into an array of - action structures. The action structures are interpreted to build a - tree of operation structures. The tree of operation structures is - used to drive an RA commit editor to produce a single commit. - - To build this client, type 'make svnmucc' from the root of your - Subversion source directory. -*/ - -#include <stdio.h> -#include <string.h> - -#include <apr_lib.h> - -#include "svn_client.h" -#include "svn_cmdline.h" -#include "svn_config.h" -#include "svn_error.h" -#include "svn_path.h" -#include "svn_pools.h" -#include "svn_props.h" -#include "svn_ra.h" -#include "svn_string.h" -#include "svn_subst.h" -#include "svn_utf.h" -#include "svn_version.h" -#include "private/svn_cmdline_private.h" - -static void handle_error(svn_error_t *err, apr_pool_t *pool) -{ - if (err) - svn_handle_error2(err, stderr, FALSE, "svnmucc: "); - svn_error_clear(err); - if (pool) - svn_pool_destroy(pool); - exit(EXIT_FAILURE); -} - -static apr_pool_t * -init(const char *application) -{ - apr_allocator_t *allocator; - apr_pool_t *pool; - svn_error_t *err; - const svn_version_checklist_t checklist[] = { - {"svn_client", svn_client_version}, - {"svn_subr", svn_subr_version}, - {"svn_ra", svn_ra_version}, - {NULL, NULL} - }; - - SVN_VERSION_DEFINE(my_version); - - if (svn_cmdline_init(application, stderr) - || apr_allocator_create(&allocator)) - exit(EXIT_FAILURE); - - err = svn_ver_check_list(&my_version, checklist); - if (err) - handle_error(err, NULL); - - apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE); - pool = svn_pool_create_ex(NULL, allocator); - apr_allocator_owner_set(allocator, pool); - - return pool; -} - -static svn_error_t * -open_tmp_file(apr_file_t **fp, - void *callback_baton, - apr_pool_t *pool) -{ - /* Open a unique file; use APR_DELONCLOSE. */ - return svn_io_open_unique_file3(fp, NULL, NULL, svn_io_file_del_on_close, - pool, pool); -} - -static svn_error_t * -create_ra_callbacks(svn_ra_callbacks2_t **callbacks, - const char *username, - const char *password, - const char *config_dir, - svn_config_t *cfg_config, - svn_boolean_t non_interactive, - svn_boolean_t no_auth_cache, - apr_pool_t *pool) -{ - SVN_ERR(svn_ra_create_callbacks(callbacks, pool)); - - SVN_ERR(svn_cmdline_create_auth_baton(&(*callbacks)->auth_baton, - non_interactive, - username, password, config_dir, - no_auth_cache, - FALSE /* trust_server_certs */, - cfg_config, NULL, NULL, pool)); - - (*callbacks)->open_tmp_file = open_tmp_file; - - return SVN_NO_ERROR; -} - - - -static svn_error_t * -commit_callback(const svn_commit_info_t *commit_info, - void *baton, - apr_pool_t *pool) -{ - SVN_ERR(svn_cmdline_printf(pool, "r%ld committed by %s at %s\n", - commit_info->revision, - (commit_info->author - ? commit_info->author : "(no author)"), - commit_info->date)); - return SVN_NO_ERROR; -} - -typedef enum action_code_t { - ACTION_MV, - ACTION_MKDIR, - ACTION_CP, - ACTION_PROPSET, - ACTION_PROPSETF, - ACTION_PROPDEL, - ACTION_PUT, - ACTION_RM -} action_code_t; - -struct operation { - enum { - OP_OPEN, - OP_DELETE, - OP_ADD, - OP_REPLACE, - OP_PROPSET /* only for files for which no other operation is - occuring; directories are OP_OPEN with non-empty - props */ - } operation; - svn_node_kind_t kind; /* to copy, mkdir, put or set revprops */ - svn_revnum_t rev; /* to copy, valid for add and replace */ - const char *url; /* to copy, valid for add and replace */ - const char *src_file; /* for put, the source file for contents */ - apr_hash_t *children; /* const char *path -> struct operation * */ - apr_hash_t *prop_mods; /* const char *prop_name -> - const svn_string_t *prop_value */ - apr_array_header_t *prop_dels; /* const char *prop_name deletions */ - void *baton; /* as returned by the commit editor */ -}; - - -/* An iterator (for use via apr_table_do) which sets node properties. - REC is a pointer to a struct driver_state. */ -static svn_error_t * -change_props(const svn_delta_editor_t *editor, - void *baton, - struct operation *child, - apr_pool_t *pool) -{ - apr_pool_t *iterpool = svn_pool_create(pool); - - if (child->prop_dels) - { - int i; - for (i = 0; i < child->prop_dels->nelts; i++) - { - const char *prop_name; - - svn_pool_clear(iterpool); - prop_name = APR_ARRAY_IDX(child->prop_dels, i, const char *); - if (child->kind == svn_node_dir) - SVN_ERR(editor->change_dir_prop(baton, prop_name, - NULL, iterpool)); - else - SVN_ERR(editor->change_file_prop(baton, prop_name, - NULL, iterpool)); - } - } - if (apr_hash_count(child->prop_mods)) - { - apr_hash_index_t *hi; - for (hi = apr_hash_first(pool, child->prop_mods); - hi; hi = apr_hash_next(hi)) - { - const void *key; - void *val; - - svn_pool_clear(iterpool); - apr_hash_this(hi, &key, NULL, &val); - if (child->kind == svn_node_dir) - SVN_ERR(editor->change_dir_prop(baton, key, val, iterpool)); - else - SVN_ERR(editor->change_file_prop(baton, key, val, iterpool)); - } - } - - svn_pool_destroy(iterpool); - return SVN_NO_ERROR; -} - - -/* Drive EDITOR to affect the change represented by OPERATION. HEAD - is the last-known youngest revision in the repository. */ -static svn_error_t * -drive(struct operation *operation, - svn_revnum_t head, - const svn_delta_editor_t *editor, - apr_pool_t *pool) -{ - apr_pool_t *subpool = svn_pool_create(pool); - apr_hash_index_t *hi; - - for (hi = apr_hash_first(pool, operation->children); - hi; hi = apr_hash_next(hi)) - { - const void *key; - void *val; - struct operation *child; - void *file_baton = NULL; - - svn_pool_clear(subpool); - apr_hash_this(hi, &key, NULL, &val); - child = val; - - /* Deletes and replacements are simple -- delete something. */ - if (child->operation == OP_DELETE || child->operation == OP_REPLACE) - { - SVN_ERR(editor->delete_entry(key, head, operation->baton, subpool)); - } - /* Opens could be for directories or files. */ - if (child->operation == OP_OPEN || child->operation == OP_PROPSET) - { - if (child->kind == svn_node_dir) - { - SVN_ERR(editor->open_directory(key, operation->baton, head, - subpool, &child->baton)); - } - else - { - SVN_ERR(editor->open_file(key, operation->baton, head, - subpool, &file_baton)); - } - } - /* Adds and replacements could also be for directories or files. */ - if (child->operation == OP_ADD || child->operation == OP_REPLACE) - { - if (child->kind == svn_node_dir) - { - SVN_ERR(editor->add_directory(key, operation->baton, - child->url, child->rev, - subpool, &child->baton)); - } - else - { - SVN_ERR(editor->add_file(key, operation->baton, child->url, - child->rev, subpool, &file_baton)); - } - } - /* If there's a source file and an open file baton, we get to - change textual contents. */ - if ((child->src_file) && (file_baton)) - { - svn_txdelta_window_handler_t handler; - void *handler_baton; - svn_stream_t *contents; - apr_file_t *f = NULL; - - SVN_ERR(editor->apply_textdelta(file_baton, NULL, subpool, - &handler, &handler_baton)); - if (strcmp(child->src_file, "-")) - { - SVN_ERR(svn_io_file_open(&f, child->src_file, APR_READ, - APR_OS_DEFAULT, pool)); - } - else - { - apr_status_t apr_err = apr_file_open_stdin(&f, pool); - if (apr_err) - return svn_error_wrap_apr(apr_err, "Can't open stdin"); - } - contents = svn_stream_from_aprfile2(f, FALSE, pool); - SVN_ERR(svn_txdelta_send_stream(contents, handler, - handler_baton, NULL, pool)); - } - /* If we opened a file, we need to apply outstanding propmods, - then close it. */ - if (file_baton) - { - if (child->kind == svn_node_file) - { - SVN_ERR(change_props(editor, file_baton, child, subpool)); - } - SVN_ERR(editor->close_file(file_baton, NULL, subpool)); - } - /* If we opened, added, or replaced a directory, we need to - recurse, apply outstanding propmods, and then close it. */ - if ((child->kind == svn_node_dir) - && (child->operation == OP_OPEN - || child->operation == OP_ADD - || child->operation == OP_REPLACE)) - { - SVN_ERR(drive(child, head, editor, subpool)); - if (child->kind == svn_node_dir) - { - SVN_ERR(change_props(editor, child->baton, child, subpool)); - } - SVN_ERR(editor->close_directory(child->baton, subpool)); - } - } - svn_pool_destroy(subpool); - return SVN_NO_ERROR; -} - - -/* Find the operation associated with PATH, which is a single-path - component representing a child of the path represented by - OPERATION. If no such child operation exists, create a new one of - type OP_OPEN. */ -static struct operation * -get_operation(const char *path, - struct operation *operation, - apr_pool_t *pool) -{ - struct operation *child = apr_hash_get(operation->children, path, - APR_HASH_KEY_STRING); - if (! child) - { - child = apr_pcalloc(pool, sizeof(*child)); - child->children = apr_hash_make(pool); - child->operation = OP_OPEN; - child->rev = SVN_INVALID_REVNUM; - child->kind = svn_node_dir; - child->prop_mods = apr_hash_make(pool); - child->prop_dels = apr_array_make(pool, 1, sizeof(const char *)); - apr_hash_set(operation->children, path, APR_HASH_KEY_STRING, child); - } - return child; -} - -/* Return the portion of URL that is relative to ANCHOR (URI-decoded). */ -static const char * -subtract_anchor(const char *anchor, const char *url, apr_pool_t *pool) -{ - if (! strcmp(url, anchor)) - return ""; - else - return svn_uri__is_child(anchor, url, pool); -} - -/* Add PATH to the operations tree rooted at OPERATION, creating any - intermediate nodes that are required. Here's what's expected for - each action type: - - ACTION URL REV SRC-FILE PROPNAME - ------------ ----- ------- -------- -------- - ACTION_MKDIR NULL invalid NULL NULL - ACTION_CP valid valid NULL NULL - ACTION_PUT NULL invalid valid NULL - ACTION_RM NULL invalid NULL NULL - ACTION_PROPSET valid invalid NULL valid - ACTION_PROPDEL valid invalid NULL valid - - Node type information is obtained for any copy source (to determine - whether to create a file or directory) and for any deleted path (to - ensure it exists since svn_delta_editor_t->delete_entry doesn't - return an error on non-existent nodes). */ -static svn_error_t * -build(action_code_t action, - const char *path, - const char *url, - svn_revnum_t rev, - const char *prop_name, - const svn_string_t *prop_value, - const char *src_file, - svn_revnum_t head, - const char *anchor, - svn_ra_session_t *session, - struct operation *operation, - apr_pool_t *pool) -{ - apr_array_header_t *path_bits = svn_path_decompose(path, pool); - const char *path_so_far = ""; - const char *copy_src = NULL; - svn_revnum_t copy_rev = SVN_INVALID_REVNUM; - int i; - - /* Look for any previous operations we've recognized for PATH. If - any of PATH's ancestors have not yet been traversed, we'll be - creating OP_OPEN operations for them as we walk down PATH's path - components. */ - for (i = 0; i < path_bits->nelts; ++i) - { - const char *path_bit = APR_ARRAY_IDX(path_bits, i, const char *); - path_so_far = svn_relpath_join(path_so_far, path_bit, pool); - operation = get_operation(path_so_far, operation, pool); - - /* If we cross a replace- or add-with-history, remember the - source of those things in case we need to lookup the node kind - of one of their children. And if this isn't such a copy, - but we've already seen one in of our parent paths, we just need - to extend that copy source path by our current path - component. */ - if (operation->url - && SVN_IS_VALID_REVNUM(operation->rev) - && (operation->operation == OP_REPLACE - || operation->operation == OP_ADD)) - { - copy_src = subtract_anchor(anchor, operation->url, pool); - copy_rev = operation->rev; - } - else if (copy_src) - { - copy_src = svn_relpath_join(copy_src, path_bit, pool); - } - } - - /* Handle property changes. */ - if (prop_name) - { - if (operation->operation == OP_DELETE) - return svn_error_createf(SVN_ERR_BAD_URL, NULL, - "cannot set properties on a location being" - " deleted ('%s')", path); - /* If we're not adding this thing ourselves, check for existence. */ - if (! ((operation->operation == OP_ADD) || - (operation->operation == OP_REPLACE))) - { - SVN_ERR(svn_ra_check_path(session, - copy_src ? copy_src : path, - copy_src ? copy_rev : head, - &operation->kind, pool)); - if (operation->kind == svn_node_none) - return svn_error_createf(SVN_ERR_BAD_URL, NULL, - "propset: '%s' not found", path); - else if ((operation->kind == svn_node_file) - && (operation->operation == OP_OPEN)) - operation->operation = OP_PROPSET; - } - if (! prop_value) - APR_ARRAY_PUSH(operation->prop_dels, const char *) = prop_name; - else - apr_hash_set(operation->prop_mods, prop_name, - APR_HASH_KEY_STRING, prop_value); - if (!operation->rev) - operation->rev = rev; - return SVN_NO_ERROR; - } - - /* We won't fuss about multiple operations on the same path in the - following cases: - - - the prior operation was, in fact, a no-op (open) - - the prior operation was a propset placeholder - - the prior operation was a deletion - - Note: while the operation structure certainly supports the - ability to do a copy of a file followed by a put of new contents - for the file, we don't let that happen (yet). - */ - if (operation->operation != OP_OPEN - && operation->operation != OP_PROPSET - && operation->operation != OP_DELETE) - return svn_error_createf(SVN_ERR_BAD_URL, NULL, - "unsupported multiple operations on '%s'", path); - - /* For deletions, we validate that there's actually something to - delete. If this is a deletion of the child of a copied - directory, we need to remember to look in the copy source tree to - verify that this thing actually exists. */ - if (action == ACTION_RM) - { - operation->operation = OP_DELETE; - SVN_ERR(svn_ra_check_path(session, - copy_src ? copy_src : path, - copy_src ? copy_rev : head, - &operation->kind, pool)); - if (operation->kind == svn_node_none) - { - if (copy_src && strcmp(path, copy_src)) - return svn_error_createf(SVN_ERR_BAD_URL, NULL, - "'%s' (from '%s:%ld') not found", - path, copy_src, copy_rev); - else - return svn_error_createf(SVN_ERR_BAD_URL, NULL, "'%s' not found", - path); - } - } - /* Handle copy operations (which can be adds or replacements). */ - else if (action == ACTION_CP) - { - if (rev > head) - return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, - "Copy source revision cannot be younger " - "than base revision"); - operation->operation = - operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD; - if (operation->operation == OP_ADD) - { - /* There is a bug in the current version of mod_dav_svn - which incorrectly replaces existing directories. - Therefore we need to check if the target exists - and raise an error here. */ - SVN_ERR(svn_ra_check_path(session, - copy_src ? copy_src : path, - copy_src ? copy_rev : head, - &operation->kind, pool)); - if (operation->kind != svn_node_none) - { - if (copy_src && strcmp(path, copy_src)) - return svn_error_createf(SVN_ERR_BAD_URL, NULL, - "'%s' (from '%s:%ld') already exists", - path, copy_src, copy_rev); - else - return svn_error_createf(SVN_ERR_BAD_URL, NULL, - "'%s' already exists", path); - } - } - SVN_ERR(svn_ra_check_path(session, subtract_anchor(anchor, url, pool), - rev, &operation->kind, pool)); - if (operation->kind == svn_node_none) - return svn_error_createf(SVN_ERR_BAD_URL, NULL, - "'%s' not found", - subtract_anchor(anchor, url, pool)); - operation->url = url; - operation->rev = rev; - } - /* Handle mkdir operations (which can be adds or replacements). */ - else if (action == ACTION_MKDIR) - { - operation->operation = - operation->operation == OP_DELETE ? OP_REPLACE : OP_ADD; - operation->kind = svn_node_dir; - } - /* Handle put operations (which can be adds, replacements, or opens). */ - else if (action == ACTION_PUT) - { - if (operation->operation == OP_DELETE) - { - operation->operation = OP_REPLACE; - } - else - { - SVN_ERR(svn_ra_check_path(session, - copy_src ? copy_src : path, - copy_src ? copy_rev : head, - &operation->kind, pool)); - if (operation->kind == svn_node_file) - operation->operation = OP_OPEN; - else if (operation->kind == svn_node_none) - operation->operation = OP_ADD; - else - return svn_error_createf(SVN_ERR_BAD_URL, NULL, - "'%s' is not a file", path); - } - operation->kind = svn_node_file; - operation->src_file = src_file; - } - else - { - /* We shouldn't get here. */ - SVN_ERR_MALFUNCTION(); - } - - return SVN_NO_ERROR; -} - -struct action { - action_code_t action; - - /* revision (copy-from-rev of path[0] for cp; base-rev for put) */ - svn_revnum_t rev; - - /* action path[0] path[1] - * ------ ------- ------- - * mv source target - * mkdir target (null) - * cp source target - * put target source - * rm target (null) - * propset target (null) - */ - const char *path[2]; - - /* property name/value */ - const char *prop_name; - const svn_string_t *prop_value; -}; - -static svn_error_t * -execute(const apr_array_header_t *actions, - const char *anchor, - apr_hash_t *revprops, - const char *username, - const char *password, - const char *config_dir, - const apr_array_header_t *config_options, - svn_boolean_t non_interactive, - svn_boolean_t no_auth_cache, - svn_revnum_t base_revision, - apr_pool_t *pool) -{ - svn_ra_session_t *session; - svn_revnum_t head; - const svn_delta_editor_t *editor; - svn_ra_callbacks2_t *ra_callbacks; - void *editor_baton; - struct operation root; - svn_error_t *err; - apr_hash_t *config; - svn_config_t *cfg_config; - int i; - - SVN_ERR(svn_config_get_config(&config, config_dir, pool)); - SVN_ERR(svn_cmdline__apply_config_options(config, config_options, - "svnmucc: ", "--config-option")); - cfg_config = apr_hash_get(config, SVN_CONFIG_CATEGORY_CONFIG, - APR_HASH_KEY_STRING); - SVN_ERR(create_ra_callbacks(&ra_callbacks, username, password, config_dir, - cfg_config, non_interactive, no_auth_cache, - pool)); - SVN_ERR(svn_ra_open4(&session, NULL, anchor, NULL, ra_callbacks, - NULL, config, pool)); - - SVN_ERR(svn_ra_get_latest_revnum(session, &head, pool)); - if (SVN_IS_VALID_REVNUM(base_revision)) - { - if (base_revision > head) - return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, - "No such revision %ld (youngest is %ld)", - base_revision, head); - head = base_revision; - } - - root.children = apr_hash_make(pool); - root.operation = OP_OPEN; - for (i = 0; i < actions->nelts; ++i) - { - struct action *action = APR_ARRAY_IDX(actions, i, struct action *); - switch (action->action) - { - const char *path1, *path2; - case ACTION_MV: - path1 = subtract_anchor(anchor, action->path[0], pool); - path2 = subtract_anchor(anchor, action->path[1], pool); - SVN_ERR(build(ACTION_RM, path1, NULL, - SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor, - session, &root, pool)); - SVN_ERR(build(ACTION_CP, path2, action->path[0], - head, NULL, NULL, NULL, head, anchor, - session, &root, pool)); - break; - case ACTION_CP: - path2 = subtract_anchor(anchor, action->path[1], pool); - if (action->rev == SVN_INVALID_REVNUM) - action->rev = head; - SVN_ERR(build(ACTION_CP, path2, action->path[0], - action->rev, NULL, NULL, NULL, head, anchor, - session, &root, pool)); - break; - case ACTION_RM: - path1 = subtract_anchor(anchor, action->path[0], pool); - SVN_ERR(build(ACTION_RM, path1, NULL, - SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor, - session, &root, pool)); - break; - case ACTION_MKDIR: - path1 = subtract_anchor(anchor, action->path[0], pool); - SVN_ERR(build(ACTION_MKDIR, path1, action->path[0], - SVN_INVALID_REVNUM, NULL, NULL, NULL, head, anchor, - session, &root, pool)); - break; - case ACTION_PUT: - path1 = subtract_anchor(anchor, action->path[0], pool); - SVN_ERR(build(ACTION_PUT, path1, action->path[0], - SVN_INVALID_REVNUM, NULL, NULL, action->path[1], - head, anchor, session, &root, pool)); - break; - case ACTION_PROPSET: - case ACTION_PROPDEL: - path1 = subtract_anchor(anchor, action->path[0], pool); - SVN_ERR(build(action->action, path1, action->path[0], - SVN_INVALID_REVNUM, - action->prop_name, action->prop_value, - NULL, head, anchor, session, &root, pool)); - break; - case ACTION_PROPSETF: - default: - SVN_ERR_MALFUNCTION_NO_RETURN(); - } - } - - SVN_ERR(svn_ra_get_commit_editor3(session, &editor, &editor_baton, revprops, - commit_callback, NULL, NULL, FALSE, pool)); - - SVN_ERR(editor->open_root(editor_baton, head, pool, &root.baton)); - err = drive(&root, head, editor, pool); - if (!err) - err = editor->close_edit(editor_baton, pool); - if (err) - svn_error_clear(editor->abort_edit(editor_baton, pool)); - - return err; -} - -static svn_error_t * -read_propvalue_file(const svn_string_t **value_p, - const char *filename, - apr_pool_t *pool) -{ - svn_stringbuf_t *value; - apr_pool_t *scratch_pool = svn_pool_create(pool); - apr_file_t *f; - - SVN_ERR(svn_io_file_open(&f, filename, APR_READ | APR_BINARY | APR_BUFFERED, - APR_OS_DEFAULT, scratch_pool)); - SVN_ERR(svn_stringbuf_from_aprfile(&value, f, scratch_pool)); - *value_p = svn_string_create_from_buf(value, pool); - svn_pool_destroy(scratch_pool); - return SVN_NO_ERROR; -} - -/* Perform the typical suite of manipulations for user-provided URLs - on URL, returning the result (allocated from POOL): IRI-to-URI - conversion, auto-escaping, and canonicalization. */ -static const char * -sanitize_url(const char *url, - apr_pool_t *pool) -{ - url = svn_path_uri_from_iri(url, pool); - url = svn_path_uri_autoescape(url, pool); - return svn_uri_canonicalize(url, pool); -} - -static void -usage(apr_pool_t *pool, int exit_val) -{ - FILE *stream = exit_val == EXIT_SUCCESS ? stdout : stderr; - const char msg[] = - "Multiple URL Command Client (for Subversion)\n" - "\nUsage: svnmucc [OPTION]... [ACTION]...\n" - "\nActions:\n" - " cp REV URL1 URL2 copy URL1@REV to URL2\n" - " mkdir URL create new directory URL\n" - " mv URL1 URL2 move URL1 to URL2\n" - " rm URL delete URL\n" - " put SRC-FILE URL add or modify file URL with contents copied from\n" - " SRC-FILE (use \"-\" to read from standard input)\n" - " propset NAME VAL URL set property NAME on URL to value VAL\n" - " propsetf NAME VAL URL set property NAME on URL to value from file VAL\n" - " propdel NAME URL delete property NAME from URL\n" - "\nOptions:\n" - " -h, --help display this text\n" - " -m, --message ARG use ARG as a log message\n" - " -F, --file ARG read log message from file ARG\n" - " -u, --username ARG commit the changes as username ARG\n" - " -p, --password ARG use ARG as the password\n" - " -U, --root-url ARG interpret all action URLs are relative to ARG\n" - " -r, --revision ARG use revision ARG as baseline for changes\n" - " --with-revprop A[=B] set revision property A in new revision to B\n" - " if specified, else to the empty string\n" - " -n, --non-interactive don't prompt the user about anything\n" - " -X, --extra-args ARG append arguments from file ARG (one per line;\n" - " use \"-\" to read from standard input)\n" - " --config-dir ARG use ARG to override the config directory\n" - " --config-option ARG use ARG so override a configuration option\n" - " --no-auth-cache do not cache authentication tokens\n" - " --version print version information\n"; - svn_error_clear(svn_cmdline_fputs(msg, stream, pool)); - apr_pool_destroy(pool); - exit(exit_val); -} - -static void -insufficient(apr_pool_t *pool) -{ - handle_error(svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL, - "insufficient arguments"), - pool); -} - -static svn_error_t * -display_version(apr_getopt_t *os, apr_pool_t *pool) -{ - const char *ra_desc_start - = "The following repository access (RA) modules are available:\n\n"; - svn_stringbuf_t *version_footer; - - version_footer = svn_stringbuf_create(ra_desc_start, pool); - SVN_ERR(svn_ra_print_modules(version_footer, pool)); - - SVN_ERR(svn_opt_print_help3(os, "svnmucc", TRUE, FALSE, version_footer->data, - NULL, NULL, NULL, NULL, NULL, pool)); - - return SVN_NO_ERROR; -} - -int -main(int argc, const char **argv) -{ - apr_pool_t *pool = init("svnmucc"); - apr_array_header_t *actions = apr_array_make(pool, 1, - sizeof(struct action *)); - const char *anchor = NULL; - svn_error_t *err = SVN_NO_ERROR; - apr_getopt_t *getopt; - enum { - config_dir_opt = SVN_OPT_FIRST_LONGOPT_ID, - config_inline_opt, - no_auth_cache_opt, - version_opt, - with_revprop_opt - }; - const apr_getopt_option_t options[] = { - {"message", 'm', 1, ""}, - {"file", 'F', 1, ""}, - {"username", 'u', 1, ""}, - {"password", 'p', 1, ""}, - {"root-url", 'U', 1, ""}, - {"revision", 'r', 1, ""}, - {"with-revprop", with_revprop_opt, 1, ""}, - {"extra-args", 'X', 1, ""}, - {"help", 'h', 0, ""}, - {"non-interactive", 'n', 0, ""}, - {"config-dir", config_dir_opt, 1, ""}, - {"config-option", config_inline_opt, 1, ""}, - {"no-auth-cache", no_auth_cache_opt, 0, ""}, - {"version", version_opt, 0, ""}, - {NULL, 0, 0, NULL} - }; - const char *message = NULL; - const char *username = NULL, *password = NULL; - const char *root_url = NULL, *extra_args_file = NULL; - const char *config_dir = NULL; - apr_array_header_t *config_options; - svn_boolean_t non_interactive = FALSE; - svn_boolean_t no_auth_cache = FALSE; - svn_revnum_t base_revision = SVN_INVALID_REVNUM; - apr_array_header_t *action_args; - apr_hash_t *revprops = apr_hash_make(pool); - int i; - - config_options = apr_array_make(pool, 0, - sizeof(svn_cmdline__config_argument_t*)); - - apr_getopt_init(&getopt, pool, argc, argv); - getopt->interleave = 1; - while (1) - { - int opt; - const char *arg; - const char *opt_arg; - - apr_status_t status = apr_getopt_long(getopt, options, &opt, &arg); - if (APR_STATUS_IS_EOF(status)) - break; - if (status != APR_SUCCESS) - handle_error(svn_error_wrap_apr(status, "getopt failure"), pool); - switch(opt) - { - case 'm': - err = svn_utf_cstring_to_utf8(&message, arg, pool); - if (err) - handle_error(err, pool); - break; - case 'F': - { - const char *arg_utf8; - svn_stringbuf_t *contents; - err = svn_utf_cstring_to_utf8(&arg_utf8, arg, pool); - if (! err) - err = svn_stringbuf_from_file2(&contents, arg, pool); - if (! err) - err = svn_utf_cstring_to_utf8(&message, contents->data, pool); - if (err) - handle_error(err, pool); - } - break; - case 'u': - username = apr_pstrdup(pool, arg); - break; - case 'p': - password = apr_pstrdup(pool, arg); - break; - case 'U': - err = svn_utf_cstring_to_utf8(&root_url, arg, pool); - if (err) - handle_error(err, pool); - if (! svn_path_is_url(root_url)) - handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, - "'%s' is not a URL\n", root_url), - pool); - root_url = sanitize_url(root_url, pool); - break; - case 'r': - { - char *digits_end = NULL; - base_revision = strtol(arg, &digits_end, 10); - if ((! SVN_IS_VALID_REVNUM(base_revision)) - || (! digits_end) - || *digits_end) - handle_error(svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, - NULL, "Invalid revision number"), - pool); - } - break; - case with_revprop_opt: - err = svn_opt_parse_revprop(&revprops, arg, pool); - if (err != SVN_NO_ERROR) - handle_error(err, pool); - break; - case 'X': - extra_args_file = apr_pstrdup(pool, arg); - break; - case 'n': - non_interactive = TRUE; - break; - case config_dir_opt: - err = svn_utf_cstring_to_utf8(&config_dir, arg, pool); - if (err) - handle_error(err, pool); - break; - case config_inline_opt: - err = svn_utf_cstring_to_utf8(&opt_arg, arg, pool); - if (err) - handle_error(err, pool); - - err = svn_cmdline__parse_config_option(config_options, opt_arg, - pool); - if (err) - handle_error(err, pool); - break; - case no_auth_cache_opt: - no_auth_cache = TRUE; - break; - case version_opt: - SVN_INT_ERR(display_version(getopt, pool)); - exit(EXIT_SUCCESS); - break; - case 'h': - usage(pool, EXIT_SUCCESS); - break; - } - } - - /* Copy the rest of our command-line arguments to an array, - UTF-8-ing them along the way. */ - action_args = apr_array_make(pool, getopt->argc, sizeof(const char *)); - while (getopt->ind < getopt->argc) - { - const char *arg = getopt->argv[getopt->ind++]; - if ((err = svn_utf_cstring_to_utf8(&(APR_ARRAY_PUSH(action_args, - const char *)), - arg, pool))) - handle_error(err, pool); - } - - /* If there are extra arguments in a supplementary file, tack those - on, too (again, in UTF8 form). */ - if (extra_args_file) - { - const char *extra_args_file_utf8; - svn_stringbuf_t *contents, *contents_utf8; - - err = svn_utf_cstring_to_utf8(&extra_args_file_utf8, - extra_args_file, pool); - if (! err) - err = svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool); - if (! err) - err = svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool); - if (err) - handle_error(err, pool); - svn_cstring_split_append(action_args, contents_utf8->data, "\n\r", - FALSE, pool); - } - - /* Now, we iterate over the combined set of arguments -- our actions. */ - for (i = 0; i < action_args->nelts; ) - { - int j, num_url_args; - const char *action_string = APR_ARRAY_IDX(action_args, i, const char *); - struct action *action = apr_palloc(pool, sizeof(*action)); - - /* First, parse the action. */ - if (! strcmp(action_string, "mv")) - action->action = ACTION_MV; - else if (! strcmp(action_string, "cp")) - action->action = ACTION_CP; - else if (! strcmp(action_string, "mkdir")) - action->action = ACTION_MKDIR; - else if (! strcmp(action_string, "rm")) - action->action = ACTION_RM; - else if (! strcmp(action_string, "put")) - action->action = ACTION_PUT; - else if (! strcmp(action_string, "propset")) - action->action = ACTION_PROPSET; - else if (! strcmp(action_string, "propsetf")) - action->action = ACTION_PROPSETF; - else if (! strcmp(action_string, "propdel")) - action->action = ACTION_PROPDEL; - else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h") - || ! strcmp(action_string, "help")) - usage(pool, EXIT_SUCCESS); - else - handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, - "'%s' is not an action\n", - action_string), pool); - if (++i == action_args->nelts) - insufficient(pool); - - /* For copies, there should be a revision number next. */ - if (action->action == ACTION_CP) - { - const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *); - if (strcmp(rev_str, "head") == 0) - action->rev = SVN_INVALID_REVNUM; - else if (strcmp(rev_str, "HEAD") == 0) - action->rev = SVN_INVALID_REVNUM; - else - { - char *end; - - while (*rev_str == 'r') - ++rev_str; - - action->rev = strtol(rev_str, &end, 0); - if (*end) - handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, - "'%s' is not a revision\n", - rev_str), pool); - } - if (++i == action_args->nelts) - insufficient(pool); - } - else - { - action->rev = SVN_INVALID_REVNUM; - } - - /* For puts, there should be a local file next. */ - if (action->action == ACTION_PUT) - { - action->path[1] = - svn_dirent_canonicalize(APR_ARRAY_IDX(action_args, i, - const char *), pool); - if (++i == action_args->nelts) - insufficient(pool); - } - - /* For propset, propsetf, and propdel, a property name (and - maybe a property value or file which contains one) comes next. */ - if ((action->action == ACTION_PROPSET) - || (action->action == ACTION_PROPSETF) - || (action->action == ACTION_PROPDEL)) - { - action->prop_name = APR_ARRAY_IDX(action_args, i, const char *); - if (++i == action_args->nelts) - insufficient(pool); - - if (action->action == ACTION_PROPDEL) - { - action->prop_value = NULL; - } - else if (action->action == ACTION_PROPSET) - { - action->prop_value = - svn_string_create(APR_ARRAY_IDX(action_args, i, - const char *), pool); - if (++i == action_args->nelts) - insufficient(pool); - } - else - { - const char *propval_file = - svn_dirent_canonicalize(APR_ARRAY_IDX(action_args, i, - const char *), pool); - - if (++i == action_args->nelts) - insufficient(pool); - - err = read_propvalue_file(&(action->prop_value), - propval_file, pool); - if (err) - handle_error(err, pool); - - action->action = ACTION_PROPSET; - } - - if (action->prop_value - && svn_prop_needs_translation(action->prop_name)) - { - svn_string_t *translated_value; - err = svn_subst_translate_string2(&translated_value, NULL, - NULL, action->prop_value, NULL, - FALSE, pool, pool); - if (err) - handle_error( - svn_error_quick_wrap(err, - "Error normalizing property value"), - pool); - action->prop_value = translated_value; - } - } - - /* How many URLs does this action expect? */ - if (action->action == ACTION_RM - || action->action == ACTION_MKDIR - || action->action == ACTION_PUT - || action->action == ACTION_PROPSET - || action->action == ACTION_PROPSETF /* shouldn't see this one */ - || action->action == ACTION_PROPDEL) - num_url_args = 1; - else - num_url_args = 2; - - /* Parse the required number of URLs. */ - for (j = 0; j < num_url_args; ++j) - { - const char *url = APR_ARRAY_IDX(action_args, i, const char *); - - /* If there's a ROOT_URL, we expect URL to be a path - relative to ROOT_URL (and we build a full url from the - combination of the two). Otherwise, it should be a full - url. */ - if (! svn_path_is_url(url)) - { - if (! root_url) - handle_error(svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL, - "'%s' is not a URL, and " - "--root-url (-U) not provided\n", - url), pool); - /* ### These relpaths are already URI-encoded. */ - url = apr_pstrcat(pool, root_url, "/", - svn_relpath_canonicalize(url, pool), - (char *)NULL); - } - url = sanitize_url(url, pool); - action->path[j] = url; - - /* The cp source could be the anchor, but the other URLs should be - children of the anchor. */ - if (! (action->action == ACTION_CP && j == 0)) - url = svn_uri_dirname(url, pool); - if (! anchor) - anchor = url; - else - anchor = svn_uri_get_longest_ancestor(anchor, url, pool); - - if ((++i == action_args->nelts) && (j >= num_url_args)) - insufficient(pool); - } - APR_ARRAY_PUSH(actions, struct action *) = action; - } - - if (! actions->nelts) - usage(pool, EXIT_FAILURE); - - if (message == NULL) - { - if (apr_hash_get(revprops, SVN_PROP_REVISION_LOG, - APR_HASH_KEY_STRING) == NULL) - /* None of -F, -m, or --with-revprop=svn:log specified; default. */ - apr_hash_set(revprops, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING, - svn_string_create("committed using svnmucc", pool)); - } - else - { - /* -F or -m specified; use that even if --with-revprop=svn:log. */ - apr_hash_set(revprops, SVN_PROP_REVISION_LOG, APR_HASH_KEY_STRING, - svn_string_create(message, pool)); - } - - if ((err = execute(actions, anchor, revprops, username, password, - config_dir, config_options, non_interactive, - no_auth_cache, base_revision, pool))) - handle_error(err, pool); - - svn_pool_destroy(pool); - return EXIT_SUCCESS; -} |