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