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 *
check_lib_versions(void)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 *
commit_callback(const svn_commit_info_t * commit_info,void * baton,apr_pool_t * pool)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 *
subtract_anchor(const char * anchor,const char * url,apr_pool_t * pool)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 *
execute(const apr_array_header_t * actions,const char * anchor,apr_hash_t * revprops,svn_revnum_t base_revision,svn_client_ctx_t * ctx,apr_pool_t * pool)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 *
read_propvalue_file(const svn_string_t ** value_p,const char * filename,apr_pool_t * pool)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 *
sanitize_url(const char * url,apr_pool_t * pool)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
usage(apr_pool_t * pool)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
help(FILE * stream,apr_pool_t * pool)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 *
insufficient(void)329 insufficient(void)
330 {
331 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
332 "insufficient arguments");
333 }
334
335 static svn_error_t *
display_version(apr_pool_t * pool)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_help5(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 *
mutually_exclusive_logs_error(void)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 *
sanitize_log_sources(const char ** final_message,const char * message,apr_hash_t * revprops,svn_stringbuf_t * filedata,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
log_message_func(const char ** log_msg,const char ** tmp_file,const apr_array_header_t * commit_items,void * baton,apr_pool_t * pool)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 *
sub_main(int * exit_code,int argc,const char * argv[],apr_pool_t * pool)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
main(int argc,const char * argv[])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 }
1011