2 * svnmucc.c: Subversion Multiple URL Client
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
25 /* Multiple URL Command Client
27 Combine a list of mv, cp and rm commands on URLs into a single commit.
29 How it works: the command line arguments are parsed into an array of
30 action structures. The action structures are interpreted to build a
31 tree of operation structures. The tree of operation structures is
32 used to drive an RA commit editor to produce a single commit.
34 To build this client, type 'make svnmucc' from the root of your
35 Subversion source directory.
43 #include "svn_private_config.h"
45 #include "svn_client.h"
46 #include "private/svn_client_mtcc.h"
47 #include "svn_cmdline.h"
48 #include "svn_config.h"
49 #include "svn_error.h"
51 #include "svn_pools.h"
52 #include "svn_props.h"
53 #include "svn_string.h"
54 #include "svn_subst.h"
56 #include "svn_version.h"
58 #include "private/svn_cmdline_private.h"
59 #include "private/svn_subr_private.h"
61 /* Version compatibility check */
63 check_lib_versions(void)
65 static const svn_version_checklist_t checklist[] =
67 { "svn_client", svn_client_version },
68 { "svn_subr", svn_subr_version },
69 { "svn_ra", svn_ra_version },
72 SVN_VERSION_DEFINE(my_version);
74 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
78 commit_callback(const svn_commit_info_t *commit_info,
82 SVN_ERR(svn_cmdline_printf(pool, "r%ld committed by %s at %s\n",
83 commit_info->revision,
85 ? commit_info->author : "(no author)"),
90 typedef enum action_code_t {
101 /* Return the portion of URL that is relative to ANCHOR (URI-decoded). */
103 subtract_anchor(const char *anchor, const char *url, apr_pool_t *pool)
105 return svn_uri_skip_ancestor(anchor, url, pool);
110 action_code_t action;
112 /* revision (copy-from-rev of path[0] for cp; base-rev for put) */
115 /* action path[0] path[1]
116 * ------ ------- -------
118 * mkdir target (null)
122 * propset target (null)
126 /* property name/value */
127 const char *prop_name;
128 const svn_string_t *prop_value;
132 execute(const apr_array_header_t *actions,
134 apr_hash_t *revprops,
135 svn_revnum_t base_revision,
136 svn_client_ctx_t *ctx,
139 svn_client__mtcc_t *mtcc;
140 apr_pool_t *iterpool = svn_pool_create(pool);
144 SVN_ERR(svn_client__mtcc_create(&mtcc, anchor,
145 SVN_IS_VALID_REVNUM(base_revision)
147 : SVN_INVALID_REVNUM,
148 ctx, pool, iterpool));
150 for (i = 0; i < actions->nelts; ++i)
152 struct action *action = APR_ARRAY_IDX(actions, i, struct action *);
153 const char *path1, *path2;
154 svn_node_kind_t kind;
156 svn_pool_clear(iterpool);
158 switch (action->action)
161 path1 = subtract_anchor(anchor, action->path[0], pool);
162 path2 = subtract_anchor(anchor, action->path[1], pool);
163 SVN_ERR(svn_client__mtcc_add_move(path1, path2, mtcc, iterpool));
166 path1 = subtract_anchor(anchor, action->path[0], pool);
167 path2 = subtract_anchor(anchor, action->path[1], pool);
168 SVN_ERR(svn_client__mtcc_add_copy(path1, action->rev, path2,
172 path1 = subtract_anchor(anchor, action->path[0], pool);
173 SVN_ERR(svn_client__mtcc_add_delete(path1, mtcc, iterpool));
176 path1 = subtract_anchor(anchor, action->path[0], pool);
177 SVN_ERR(svn_client__mtcc_add_mkdir(path1, mtcc, iterpool));
180 path1 = subtract_anchor(anchor, action->path[0], pool);
181 SVN_ERR(svn_client__mtcc_check_path(&kind, path1, TRUE, mtcc, pool));
183 if (kind == svn_node_dir)
185 SVN_ERR(svn_client__mtcc_add_delete(path1, mtcc, pool));
186 kind = svn_node_none;
192 if (strcmp(action->path[1], "-") != 0)
193 SVN_ERR(svn_stream_open_readonly(&src, action->path[1],
196 SVN_ERR(svn_stream_for_stdin(&src, pool));
199 if (kind == svn_node_file)
200 SVN_ERR(svn_client__mtcc_add_update_file(path1, src, NULL,
203 else if (kind == svn_node_none)
204 SVN_ERR(svn_client__mtcc_add_add_file(path1, src, NULL,
210 path1 = subtract_anchor(anchor, action->path[0], pool);
211 SVN_ERR(svn_client__mtcc_add_propset(path1, action->prop_name,
212 action->prop_value, FALSE,
215 case ACTION_PROPSETF:
217 SVN_ERR_MALFUNCTION_NO_RETURN();
221 err = svn_client__mtcc_commit(revprops, commit_callback, NULL,
224 svn_pool_destroy(iterpool);
225 return svn_error_trace(err);
229 read_propvalue_file(const svn_string_t **value_p,
230 const char *filename,
233 svn_stringbuf_t *value;
234 apr_pool_t *scratch_pool = svn_pool_create(pool);
236 SVN_ERR(svn_stringbuf_from_file2(&value, filename, scratch_pool));
237 *value_p = svn_string_create_from_buf(value, pool);
238 svn_pool_destroy(scratch_pool);
242 /* Perform the typical suite of manipulations for user-provided URLs
243 on URL, returning the result (allocated from POOL): IRI-to-URI
244 conversion, auto-escaping, and canonicalization. */
246 sanitize_url(const char *url,
249 url = svn_path_uri_from_iri(url, pool);
250 url = svn_path_uri_autoescape(url, pool);
251 return svn_uri_canonicalize(url, pool);
255 usage(apr_pool_t *pool)
257 svn_error_clear(svn_cmdline_fprintf
258 (stderr, pool, _("Type 'svnmucc --help' for usage.\n")));
261 /* Print a usage message on STREAM. */
263 help(FILE *stream, apr_pool_t *pool)
265 svn_error_clear(svn_cmdline_fputs(
266 _("usage: svnmucc ACTION...\n"
267 "Subversion multiple URL command client.\n"
268 "Type 'svnmucc --version' to see the program version and RA modules.\n"
270 " Perform one or more Subversion repository URL-based ACTIONs, committing\n"
271 " the result as a (single) new revision.\n"
274 " cp REV SRC-URL DST-URL : copy SRC-URL@REV to DST-URL\n"
275 " mkdir URL : create new directory URL\n"
276 " mv SRC-URL DST-URL : move SRC-URL to DST-URL\n"
277 " rm URL : delete URL\n"
278 " put SRC-FILE URL : add or modify file URL with contents copied from\n"
279 " SRC-FILE (use \"-\" to read from standard input)\n"
280 " propset NAME VALUE URL : set property NAME on URL to VALUE\n"
281 " propsetf NAME FILE URL : set property NAME on URL to value read from FILE\n"
282 " propdel NAME URL : delete property NAME from URL\n"
285 " -h, -? [--help] : display this text\n"
286 " -m [--message] ARG : use ARG as a log message\n"
287 " -F [--file] ARG : read log message from file ARG\n"
288 " -u [--username] ARG : commit the changes as username ARG\n"
289 " -p [--password] ARG : use ARG as the password\n"
290 " -U [--root-url] ARG : interpret all action URLs relative to ARG\n"
291 " -r [--revision] ARG : use revision ARG as baseline for changes\n"
292 " --with-revprop ARG : set revision property in the following format:\n"
294 " --non-interactive : do no interactive prompting (default is to\n"
295 " prompt only if standard input is a terminal)\n"
296 " --force-interactive : do interactive prompting even if standard\n"
297 " input is not a terminal\n"
298 " --trust-server-cert : deprecated;\n"
299 " same as --trust-server-cert-failures=unknown-ca\n"
300 " --trust-server-cert-failures ARG\n"
301 " with --non-interactive, accept SSL server\n"
302 " certificates with failures; ARG is comma-separated\n"
303 " list of 'unknown-ca' (Unknown Authority),\n"
304 " 'cn-mismatch' (Hostname mismatch), 'expired'\n"
305 " (Expired certificate),'not-yet-valid' (Not yet\n"
306 " valid certificate) and 'other' (all other not\n"
307 " separately classified certificate errors).\n"
308 " -X [--extra-args] ARG : append arguments from file ARG (one per line;\n"
309 " use \"-\" to read from standard input)\n"
310 " --config-dir ARG : use ARG to override the config directory\n"
311 " --config-option ARG : use ARG to override a configuration option\n"
312 " --no-auth-cache : do not cache authentication tokens\n"
313 " --version : print version information\n"),
320 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
321 "insufficient arguments");
325 display_version(apr_pool_t *pool)
327 const char *ra_desc_start
328 = "The following repository access (RA) modules are available:\n\n";
329 svn_stringbuf_t *version_footer;
331 version_footer = svn_stringbuf_create(ra_desc_start, pool);
332 SVN_ERR(svn_ra_print_modules(version_footer, pool));
334 SVN_ERR(svn_opt_print_help4(NULL, "svnmucc", TRUE, FALSE, FALSE,
335 version_footer->data,
336 NULL, NULL, NULL, NULL, NULL, pool));
341 /* Return an error about the mutual exclusivity of the -m, -F, and
342 --with-revprop=svn:log command-line options. */
344 mutually_exclusive_logs_error(void)
346 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
347 _("--message (-m), --file (-F), and "
348 "--with-revprop=svn:log are mutually "
352 /* Obtain the log message from multiple sources, producing an error
353 if there are multiple sources. Store the result in *FINAL_MESSAGE. */
355 sanitize_log_sources(const char **final_message,
357 apr_hash_t *revprops,
358 svn_stringbuf_t *filedata,
359 apr_pool_t *result_pool,
360 apr_pool_t *scratch_pool)
364 *final_message = NULL;
365 /* If we already have a log message in the revprop hash, then just
366 make sure the user didn't try to also use -m or -F. Otherwise,
367 we need to consult -m or -F to find a log message, if any. */
368 msg = svn_hash_gets(revprops, SVN_PROP_REVISION_LOG);
371 if (filedata || message)
372 return mutually_exclusive_logs_error();
374 *final_message = apr_pstrdup(result_pool, msg->data);
376 /* Will be re-added by libsvn_client */
377 svn_hash_sets(revprops, SVN_PROP_REVISION_LOG, NULL);
382 return mutually_exclusive_logs_error();
384 *final_message = apr_pstrdup(result_pool, filedata->data);
388 *final_message = apr_pstrdup(result_pool, message);
394 /* Baton for log_message_func */
395 struct log_message_baton
397 svn_boolean_t non_interactive;
398 const char *log_message;
399 svn_client_ctx_t *ctx;
402 /* Implements svn_client_get_commit_log3_t */
404 log_message_func(const char **log_msg,
405 const char **tmp_file,
406 const apr_array_header_t *commit_items,
410 struct log_message_baton *lmb = baton;
414 if (lmb->log_message)
416 svn_string_t *message = svn_string_create(lmb->log_message, pool);
418 SVN_ERR_W(svn_subst_translate_string2(&message, NULL, NULL,
419 message, NULL, FALSE,
421 _("Error normalizing log message to internal format"));
423 *log_msg = message->data;
428 if (lmb->non_interactive)
430 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
431 _("Cannot invoke editor to get log message "
432 "when non-interactive"));
436 svn_string_t *msg = svn_string_create("", pool);
438 SVN_ERR(svn_cmdline__edit_string_externally(
439 &msg, NULL, NULL, "", msg, "svnmucc-commit",
440 lmb->ctx->config, TRUE, NULL, pool));
442 if (msg && msg->data)
443 *log_msg = msg->data;
452 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
453 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
454 * return SVN_NO_ERROR.
457 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
459 apr_array_header_t *actions = apr_array_make(pool, 1,
460 sizeof(struct action *));
461 const char *anchor = NULL;
462 svn_error_t *err = SVN_NO_ERROR;
465 config_dir_opt = SVN_OPT_FIRST_LONGOPT_ID,
471 force_interactive_opt,
472 trust_server_cert_opt,
473 trust_server_cert_failures_opt,
475 static const apr_getopt_option_t options[] = {
476 {"message", 'm', 1, ""},
477 {"file", 'F', 1, ""},
478 {"username", 'u', 1, ""},
479 {"password", 'p', 1, ""},
480 {"root-url", 'U', 1, ""},
481 {"revision", 'r', 1, ""},
482 {"with-revprop", with_revprop_opt, 1, ""},
483 {"extra-args", 'X', 1, ""},
484 {"help", 'h', 0, ""},
486 {"non-interactive", non_interactive_opt, 0, ""},
487 {"force-interactive", force_interactive_opt, 0, ""},
488 {"trust-server-cert", trust_server_cert_opt, 0, ""},
489 {"trust-server-cert-failures", trust_server_cert_failures_opt, 1, ""},
490 {"config-dir", config_dir_opt, 1, ""},
491 {"config-option", config_inline_opt, 1, ""},
492 {"no-auth-cache", no_auth_cache_opt, 0, ""},
493 {"version", version_opt, 0, ""},
496 const char *message = NULL;
497 svn_stringbuf_t *filedata = NULL;
498 const char *username = NULL, *password = NULL;
499 const char *root_url = NULL, *extra_args_file = NULL;
500 const char *config_dir = NULL;
501 apr_array_header_t *config_options;
502 svn_boolean_t non_interactive = FALSE;
503 svn_boolean_t force_interactive = FALSE;
504 svn_boolean_t trust_unknown_ca = FALSE;
505 svn_boolean_t trust_cn_mismatch = FALSE;
506 svn_boolean_t trust_expired = FALSE;
507 svn_boolean_t trust_not_yet_valid = FALSE;
508 svn_boolean_t trust_other_failure = FALSE;
509 svn_boolean_t no_auth_cache = FALSE;
510 svn_boolean_t show_version = FALSE;
511 svn_boolean_t show_help = FALSE;
512 svn_revnum_t base_revision = SVN_INVALID_REVNUM;
513 apr_array_header_t *action_args;
514 apr_hash_t *revprops = apr_hash_make(pool);
515 apr_hash_t *cfg_hash;
516 svn_config_t *cfg_config;
517 svn_client_ctx_t *ctx;
518 struct log_message_baton lmb;
521 /* Check library versions */
522 SVN_ERR(check_lib_versions());
524 config_options = apr_array_make(pool, 0,
525 sizeof(svn_cmdline__config_argument_t*));
527 apr_getopt_init(&opts, pool, argc, argv);
528 opts->interleave = 1;
535 apr_status_t status = apr_getopt_long(opts, options, &opt, &arg);
536 if (APR_STATUS_IS_EOF(status))
538 if (status != APR_SUCCESS)
541 *exit_code = EXIT_FAILURE;
547 SVN_ERR(svn_utf_cstring_to_utf8(&message, arg, pool));
551 const char *arg_utf8;
552 SVN_ERR(svn_utf_cstring_to_utf8(&arg_utf8, arg, pool));
553 SVN_ERR(svn_stringbuf_from_file2(&filedata, arg, pool));
557 username = apr_pstrdup(pool, arg);
560 password = apr_pstrdup(pool, arg);
563 SVN_ERR(svn_utf_cstring_to_utf8(&root_url, arg, pool));
564 if (! svn_path_is_url(root_url))
565 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
566 "'%s' is not a URL\n", root_url);
567 root_url = sanitize_url(root_url, pool);
571 const char *saved_arg = arg;
572 char *digits_end = NULL;
575 base_revision = strtol(arg, &digits_end, 10);
576 if ((! SVN_IS_VALID_REVNUM(base_revision))
579 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
580 _("Invalid revision number '%s'"),
584 case with_revprop_opt:
585 SVN_ERR(svn_opt_parse_revprop(&revprops, arg, pool));
588 extra_args_file = apr_pstrdup(pool, arg);
590 case non_interactive_opt:
591 non_interactive = TRUE;
593 case force_interactive_opt:
594 force_interactive = TRUE;
596 case trust_server_cert_opt: /* backward compat */
597 trust_unknown_ca = TRUE;
599 case trust_server_cert_failures_opt:
600 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
601 SVN_ERR(svn_cmdline__parse_trust_options(
605 &trust_not_yet_valid,
606 &trust_other_failure,
610 SVN_ERR(svn_utf_cstring_to_utf8(&config_dir, arg, pool));
612 case config_inline_opt:
613 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, arg, pool));
614 SVN_ERR(svn_cmdline__parse_config_option(config_options, opt_arg,
618 case no_auth_cache_opt:
619 no_auth_cache = TRUE;
639 SVN_ERR(display_version(pool));
643 if (non_interactive && force_interactive)
645 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
646 _("--non-interactive and --force-interactive "
647 "are mutually exclusive"));
650 non_interactive = !svn_cmdline__be_interactive(non_interactive,
653 if (!non_interactive)
655 if (trust_unknown_ca || trust_cn_mismatch || trust_expired
656 || trust_not_yet_valid || trust_other_failure)
657 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
658 _("--trust-server-cert-failures requires "
659 "--non-interactive"));
662 /* Copy the rest of our command-line arguments to an array,
663 UTF-8-ing them along the way. */
664 action_args = apr_array_make(pool, opts->argc, sizeof(const char *));
665 while (opts->ind < opts->argc)
667 const char *arg = opts->argv[opts->ind++];
668 SVN_ERR(svn_utf_cstring_to_utf8(&APR_ARRAY_PUSH(action_args,
673 /* If there are extra arguments in a supplementary file, tack those
674 on, too (again, in UTF8 form). */
677 const char *extra_args_file_utf8;
678 svn_stringbuf_t *contents, *contents_utf8;
680 SVN_ERR(svn_utf_cstring_to_utf8(&extra_args_file_utf8,
681 extra_args_file, pool));
682 SVN_ERR(svn_stringbuf_from_file2(&contents, extra_args_file_utf8, pool));
683 SVN_ERR(svn_utf_stringbuf_to_utf8(&contents_utf8, contents, pool));
684 svn_cstring_split_append(action_args, contents_utf8->data, "\n\r",
688 /* Now initialize the client context */
690 err = svn_config_get_config(&cfg_hash, config_dir, pool);
693 /* Fallback to default config if the config directory isn't readable
694 or is not a directory. */
695 if (APR_STATUS_IS_EACCES(err->apr_err)
696 || SVN__APR_STATUS_IS_ENOTDIR(err->apr_err))
698 svn_handle_warning2(stderr, err, "svnmucc: ");
699 svn_error_clear(err);
701 SVN_ERR(svn_config__get_default_config(&cfg_hash, pool));
710 svn_cmdline__apply_config_options(cfg_hash, config_options,
711 "svnmucc: ", "--config-option"));
714 SVN_ERR(svn_client_create_context2(&ctx, cfg_hash, pool));
716 cfg_config = svn_hash_gets(cfg_hash, SVN_CONFIG_CATEGORY_CONFIG);
717 SVN_ERR(svn_cmdline_create_auth_baton2(
734 lmb.non_interactive = non_interactive;
736 /* Make sure we have a log message to use. */
737 SVN_ERR(sanitize_log_sources(&lmb.log_message, message, revprops, filedata,
740 ctx->log_msg_func3 = log_message_func;
741 ctx->log_msg_baton3 = &lmb;
743 /* Now, we iterate over the combined set of arguments -- our actions. */
744 for (i = 0; i < action_args->nelts; )
747 const char *action_string = APR_ARRAY_IDX(action_args, i, const char *);
748 struct action *action = apr_pcalloc(pool, sizeof(*action));
750 /* First, parse the action. */
751 if (! strcmp(action_string, "mv"))
752 action->action = ACTION_MV;
753 else if (! strcmp(action_string, "cp"))
754 action->action = ACTION_CP;
755 else if (! strcmp(action_string, "mkdir"))
756 action->action = ACTION_MKDIR;
757 else if (! strcmp(action_string, "rm"))
758 action->action = ACTION_RM;
759 else if (! strcmp(action_string, "put"))
760 action->action = ACTION_PUT;
761 else if (! strcmp(action_string, "propset"))
762 action->action = ACTION_PROPSET;
763 else if (! strcmp(action_string, "propsetf"))
764 action->action = ACTION_PROPSETF;
765 else if (! strcmp(action_string, "propdel"))
766 action->action = ACTION_PROPDEL;
767 else if (! strcmp(action_string, "?") || ! strcmp(action_string, "h")
768 || ! strcmp(action_string, "help"))
774 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
775 "'%s' is not an action\n",
777 if (++i == action_args->nelts)
778 return insufficient();
780 /* For copies, there should be a revision number next. */
781 if (action->action == ACTION_CP)
783 const char *rev_str = APR_ARRAY_IDX(action_args, i, const char *);
784 if (strcmp(rev_str, "head") == 0)
785 action->rev = SVN_INVALID_REVNUM;
786 else if (strcmp(rev_str, "HEAD") == 0)
787 action->rev = SVN_INVALID_REVNUM;
792 while (*rev_str == 'r')
795 action->rev = strtol(rev_str, &end, 0);
797 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
798 "'%s' is not a revision\n",
801 if (++i == action_args->nelts)
802 return insufficient();
806 action->rev = SVN_INVALID_REVNUM;
809 /* For puts, there should be a local file next. */
810 if (action->action == ACTION_PUT)
813 svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i,
814 const char *), pool);
815 if (++i == action_args->nelts)
816 return insufficient();
819 /* For propset, propsetf, and propdel, a property name (and
820 maybe a property value or file which contains one) comes next. */
821 if ((action->action == ACTION_PROPSET)
822 || (action->action == ACTION_PROPSETF)
823 || (action->action == ACTION_PROPDEL))
825 action->prop_name = APR_ARRAY_IDX(action_args, i, const char *);
826 if (++i == action_args->nelts)
827 return insufficient();
829 if (action->action == ACTION_PROPDEL)
831 action->prop_value = NULL;
833 else if (action->action == ACTION_PROPSET)
836 svn_string_create(APR_ARRAY_IDX(action_args, i,
837 const char *), pool);
838 if (++i == action_args->nelts)
839 return insufficient();
843 const char *propval_file =
844 svn_dirent_internal_style(APR_ARRAY_IDX(action_args, i,
845 const char *), pool);
847 if (++i == action_args->nelts)
848 return insufficient();
850 SVN_ERR(read_propvalue_file(&(action->prop_value),
851 propval_file, pool));
853 action->action = ACTION_PROPSET;
856 if (action->prop_value
857 && svn_prop_needs_translation(action->prop_name))
859 svn_string_t *translated_value;
860 SVN_ERR_W(svn_subst_translate_string2(&translated_value, NULL,
861 NULL, action->prop_value,
862 NULL, FALSE, pool, pool),
863 "Error normalizing property value");
864 action->prop_value = translated_value;
868 /* How many URLs does this action expect? */
869 if (action->action == ACTION_RM
870 || action->action == ACTION_MKDIR
871 || action->action == ACTION_PUT
872 || action->action == ACTION_PROPSET
873 || action->action == ACTION_PROPSETF /* shouldn't see this one */
874 || action->action == ACTION_PROPDEL)
879 /* Parse the required number of URLs. */
880 for (j = 0; j < num_url_args; ++j)
882 const char *url = APR_ARRAY_IDX(action_args, i, const char *);
884 /* If there's a ROOT_URL, we expect URL to be a path
885 relative to ROOT_URL (and we build a full url from the
886 combination of the two). Otherwise, it should be a full
888 if (! svn_path_is_url(url))
891 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
892 "'%s' is not a URL, and "
893 "--root-url (-U) not provided\n",
895 /* ### These relpaths are already URI-encoded. */
896 url = apr_pstrcat(pool, root_url, "/",
897 svn_relpath_canonicalize(url, pool),
900 url = sanitize_url(url, pool);
901 action->path[j] = url;
903 /* The first URL arguments to 'cp', 'pd', 'ps' could be the anchor,
904 but the other URLs should be children of the anchor. */
905 if (! (action->action == ACTION_CP && j == 0)
906 && action->action != ACTION_PROPDEL
907 && action->action != ACTION_PROPSET
908 && action->action != ACTION_PROPSETF)
909 url = svn_uri_dirname(url, pool);
914 anchor = svn_uri_get_longest_ancestor(anchor, url, pool);
915 if (!anchor || !anchor[0])
916 return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
917 "URLs in the action list do not "
918 "share a common ancestor");
921 if ((++i == action_args->nelts) && (j + 1 < num_url_args))
922 return insufficient();
925 APR_ARRAY_PUSH(actions, struct action *) = action;
928 if (! actions->nelts)
930 *exit_code = EXIT_FAILURE;
935 if ((err = execute(actions, anchor, revprops, base_revision, ctx, pool)))
937 if (err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
938 err = svn_error_quick_wrap(err,
939 _("Authentication failed and interactive"
940 " prompting is disabled; see the"
941 " --force-interactive option"));
949 main(int argc, const char *argv[])
952 int exit_code = EXIT_SUCCESS;
955 /* Initialize the app. */
956 if (svn_cmdline_init("svnmucc", stderr) != EXIT_SUCCESS)
959 /* Create our top-level pool. Use a separate mutexless allocator,
960 * given this application is single threaded.
962 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
964 err = sub_main(&exit_code, argc, argv, pool);
966 /* Flush stdout and report if it fails. It would be flushed on exit anyway
967 but this makes sure that output is not silently lost if it fails. */
968 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
972 exit_code = EXIT_FAILURE;
973 svn_cmdline_handle_exit_error(err, NULL, "svnmucc: ");
976 svn_pool_destroy(pool);