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