2 * svnrdump.c: Produce a dumpfile of a local or remote repository
3 * without touching the filesystem, but for temporary files.
5 * ====================================================================
6 * Licensed to the Apache Software Foundation (ASF) under one
7 * or more contributor license agreements. See the NOTICE file
8 * distributed with this work for additional information
9 * regarding copyright ownership. The ASF licenses this file
10 * to you under the Apache License, Version 2.0 (the
11 * "License"); you may not use this file except in compliance
12 * with the License. You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing,
17 * software distributed under the License is distributed on an
18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 * KIND, either express or implied. See the License for the
20 * specific language governing permissions and limitations
22 * ====================================================================
27 #include "svn_pools.h"
28 #include "svn_cmdline.h"
29 #include "svn_client.h"
32 #include "svn_repos.h"
35 #include "svn_private_config.h"
36 #include "svn_string.h"
37 #include "svn_props.h"
41 #include "private/svn_repos_private.h"
42 #include "private/svn_cmdline_private.h"
43 #include "private/svn_ra_private.h"
47 /*** Cancellation ***/
49 /* Our cancellation callback. */
50 static svn_cancel_func_t check_cancel = NULL;
54 static svn_opt_subcommand_t dump_cmd, load_cmd;
56 enum svn_svnrdump__longopt_t
58 opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID,
62 opt_auth_password_from_stdin,
66 opt_force_interactive,
68 opt_trust_server_cert,
69 opt_trust_server_cert_failures,
73 #define SVN_SVNRDUMP__BASE_OPTIONS opt_config_dir, \
77 opt_auth_password_from_stdin, \
79 opt_trust_server_cert, \
80 opt_trust_server_cert_failures, \
81 opt_non_interactive, \
84 static const svn_opt_subcommand_desc2_t svnrdump__cmd_table[] =
86 { "dump", dump_cmd, { 0 },
87 N_("usage: svnrdump dump URL [-r LOWER[:UPPER]]\n\n"
88 "Dump revisions LOWER to UPPER of repository at remote URL to stdout\n"
89 "in a 'dumpfile' portable format. If only LOWER is given, dump that\n"
91 { 'r', 'q', opt_incremental, SVN_SVNRDUMP__BASE_OPTIONS } },
92 { "load", load_cmd, { 0 },
93 N_("usage: svnrdump load URL\n\n"
94 "Load a 'dumpfile' given on stdin to a repository at remote URL.\n"),
95 { 'q', opt_skip_revprop, SVN_SVNRDUMP__BASE_OPTIONS } },
96 { "help", 0, { "?", "h" },
97 N_("usage: svnrdump help [SUBCOMMAND...]\n\n"
98 "Describe the usage of this program or its subcommands.\n"),
100 { NULL, NULL, { 0 }, NULL, { 0 } }
103 static const apr_getopt_option_t svnrdump__options[] =
106 N_("specify revision number ARG (or X:Y range)")},
108 N_("no progress (only errors) to stderr")},
109 {"incremental", opt_incremental, 0,
110 N_("dump incrementally")},
111 {"skip-revprop", opt_skip_revprop, 1,
112 N_("skip revision property ARG (e.g., \"svn:author\")")},
113 {"config-dir", opt_config_dir, 1,
114 N_("read user configuration files from directory ARG")},
115 {"username", opt_auth_username, 1,
116 N_("specify a username ARG")},
117 {"password", opt_auth_password, 1,
118 N_("specify a password ARG")},
119 {"password-from-stdin", opt_auth_password_from_stdin, 0,
120 N_("read password from stdin")},
121 {"non-interactive", opt_non_interactive, 0,
122 N_("do no interactive prompting (default is to prompt\n"
124 "only if standard input is a terminal device)")},
125 {"force-interactive", opt_force_interactive, 0,
126 N_("do interactive prompting even if standard input\n"
128 "is not a terminal device")},
129 {"no-auth-cache", opt_auth_nocache, 0,
130 N_("do not cache authentication tokens")},
132 N_("display this help")},
133 {"version", opt_version, 0,
134 N_("show program version information")},
135 {"config-option", opt_config_option, 1,
136 N_("set user configuration option in the format:\n"
138 " FILE:SECTION:OPTION=[VALUE]\n"
142 " servers:global:http-library=serf")},
143 {"trust-server-cert", opt_trust_server_cert, 0,
144 N_("deprecated; same as\n"
146 "--trust-server-cert-failures=unknown-ca")},
147 {"trust-server-cert-failures", opt_trust_server_cert_failures, 1,
148 N_("with --non-interactive, accept SSL server\n"
150 "certificates with failures; ARG is comma-separated\n"
152 "list of 'unknown-ca' (Unknown Authority),\n"
154 "'cn-mismatch' (Hostname mismatch), 'expired'\n"
156 "(Expired certificate), 'not-yet-valid' (Not yet\n"
158 "valid certificate) and 'other' (all other not\n"
160 "separately classified certificate errors).")},
161 {"dumpfile", 'F', 1, N_("Read or write to a dumpfile instead of stdin/stdout")},
165 /* Baton for the RA replay session. */
166 struct replay_baton {
167 /* A backdoor ra session for fetching information. */
168 svn_ra_session_t *extra_ra_session;
170 /* The output stream */
171 svn_stream_t *stdout_stream;
173 /* Whether to be quiet. */
178 typedef struct opt_baton_t {
179 svn_client_ctx_t *ctx;
180 svn_ra_session_t *session;
182 const char *dumpfile;
184 svn_boolean_t version;
185 svn_opt_revision_t start_revision;
186 svn_opt_revision_t end_revision;
188 svn_boolean_t incremental;
189 apr_hash_t *skip_revprops;
192 /* Print dumpstream-formatted information about REVISION.
193 * Implements the `svn_ra_replay_revstart_callback_t' interface.
196 replay_revstart(svn_revnum_t revision,
198 const svn_delta_editor_t **editor,
200 apr_hash_t *rev_props,
203 struct replay_baton *rb = replay_baton;
204 apr_hash_t *normal_props;
206 /* Normalize and dump the revprops */
207 SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
208 SVN_ERR(svn_repos__dump_revision_record(rb->stdout_stream, revision, NULL,
210 TRUE /*props_section_always*/,
213 SVN_ERR(svn_rdump__get_dump_editor(editor, edit_baton, revision,
214 rb->stdout_stream, rb->extra_ra_session,
215 NULL, check_cancel, NULL, pool));
220 /* Print progress information about the dump of REVISION.
221 Implements the `svn_ra_replay_revfinish_callback_t' interface. */
223 replay_revend(svn_revnum_t revision,
225 const svn_delta_editor_t *editor,
227 apr_hash_t *rev_props,
230 /* No resources left to free. */
231 struct replay_baton *rb = replay_baton;
233 SVN_ERR(editor->close_edit(edit_baton, pool));
236 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
242 /* Print dumpstream-formatted information about REVISION.
243 * Implements the `svn_ra_replay_revstart_callback_t' interface.
246 replay_revstart_v2(svn_revnum_t revision,
248 svn_editor_t **editor,
249 apr_hash_t *rev_props,
252 struct replay_baton *rb = replay_baton;
253 apr_hash_t *normal_props;
255 /* Normalize and dump the revprops */
256 SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
257 SVN_ERR(svn_repos__dump_revision_record(rb->stdout_stream, revision,
259 TRUE /*props_section_always*/,
262 SVN_ERR(svn_rdump__get_dump_editor_v2(editor, revision,
264 rb->extra_ra_session,
265 NULL, check_cancel, NULL, pool, pool));
270 /* Print progress information about the dump of REVISION.
271 Implements the `svn_ra_replay_revfinish_callback_t' interface. */
273 replay_revend_v2(svn_revnum_t revision,
275 svn_editor_t *editor,
276 apr_hash_t *rev_props,
279 /* No resources left to free. */
280 struct replay_baton *rb = replay_baton;
282 SVN_ERR(svn_editor_complete(editor));
285 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
291 /* Initialize the RA layer, and set *CTX to a new client context baton
292 * allocated from POOL. Use CONFIG_DIR and pass USERNAME, PASSWORD,
293 * CONFIG_DIR and NO_AUTH_CACHE to initialize the authorization baton.
294 * CONFIG_OPTIONS (if not NULL) is a list of configuration overrides.
295 * REPOS_URL is used to fiddle with server-specific configuration
299 init_client_context(svn_client_ctx_t **ctx_p,
300 svn_boolean_t non_interactive,
301 const char *username,
302 const char *password,
303 const char *config_dir,
304 const char *repos_url,
305 svn_boolean_t no_auth_cache,
306 svn_boolean_t trust_unknown_ca,
307 svn_boolean_t trust_cn_mismatch,
308 svn_boolean_t trust_expired,
309 svn_boolean_t trust_not_yet_valid,
310 svn_boolean_t trust_other_failure,
311 apr_array_header_t *config_options,
314 svn_client_ctx_t *ctx = NULL;
315 svn_config_t *cfg_config, *cfg_servers;
317 SVN_ERR(svn_ra_initialize(pool));
319 SVN_ERR(svn_config_ensure(config_dir, pool));
320 SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
322 SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
325 SVN_ERR(svn_cmdline__apply_config_options(ctx->config, config_options,
326 "svnrdump: ", "--config-option"));
328 cfg_config = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG);
330 /* ### FIXME: This is a hack to work around the fact that our dump
331 ### editor simply can't handle the way ra_serf violates the
332 ### editor v1 drive ordering requirements.
334 ### We'll override both the global value and server-specific one
335 ### for the 'http-bulk-updates' and 'http-max-connections'
336 ### options in order to get ra_serf to try a bulk-update if the
337 ### server will allow it, or at least try to limit all its
338 ### auxiliary GETs/PROPFINDs to happening (well-ordered) on a
339 ### single server connection.
341 ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
343 cfg_servers = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_SERVERS);
344 svn_config_set_bool(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
345 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
346 svn_config_set_int64(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
347 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
351 apr_uri_t parsed_url;
353 status = apr_uri_parse(pool, repos_url, &parsed_url);
356 const char *server_group;
358 server_group = svn_config_find_group(cfg_servers, parsed_url.hostname,
359 SVN_CONFIG_SECTION_GROUPS, pool);
362 svn_config_set_bool(cfg_servers, server_group,
363 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
364 svn_config_set_int64(cfg_servers, server_group,
365 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
370 /* Set up our cancellation support. */
371 ctx->cancel_func = check_cancel;
373 /* Default authentication providers for non-interactive use */
374 SVN_ERR(svn_cmdline_create_auth_baton2(&(ctx->auth_baton), non_interactive,
375 username, password, config_dir,
376 no_auth_cache, trust_unknown_ca,
377 trust_cn_mismatch, trust_expired,
380 cfg_config, ctx->cancel_func,
381 ctx->cancel_baton, pool));
386 /* Print a revision record header for REVISION to STDOUT_STREAM. Use
387 * SESSION to contact the repository for revision properties and
391 dump_revision_header(svn_ra_session_t *session,
392 svn_stream_t *stdout_stream,
393 svn_revnum_t revision,
396 apr_hash_t *prophash;
398 SVN_ERR(svn_ra_rev_proplist(session, revision, &prophash, pool));
399 SVN_ERR(svn_repos__dump_revision_record(stdout_stream, revision, NULL,
401 TRUE /*props_section_always*/,
407 dump_initial_full_revision(svn_ra_session_t *session,
408 svn_ra_session_t *extra_ra_session,
409 svn_stream_t *stdout_stream,
410 svn_revnum_t revision,
414 const svn_ra_reporter3_t *reporter;
416 const svn_delta_editor_t *dump_editor;
418 const char *session_url, *source_relpath;
420 /* Determine whether we're dumping the repository root URL or some
421 child thereof. If we're dumping a subtree of the repository
422 rather than the root, we have to jump through some hoops to make
423 our update-driven dump generation work the way a replay-driven
426 See http://subversion.tigris.org/issues/show_bug.cgi?id=4101
428 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
429 SVN_ERR(svn_ra_get_path_relative_to_root(session, &source_relpath,
432 /* Start with a revision record header. */
433 SVN_ERR(dump_revision_header(session, stdout_stream, revision, pool));
435 /* Then, we'll drive the dump editor with what would look like a
436 full checkout of the repository as it looked in START_REVISION.
437 We do this by manufacturing a basic 'report' to the update
438 reporter, telling it that we have nothing to start with. The
439 delta between nothing and everything-at-REV is, effectively, a
441 SVN_ERR(svn_rdump__get_dump_editor(&dump_editor, &dump_baton, revision,
442 stdout_stream, extra_ra_session,
443 source_relpath, check_cancel, NULL, pool));
444 SVN_ERR(svn_ra_do_update3(session, &reporter, &report_baton, revision,
445 "", svn_depth_infinity, FALSE, FALSE,
446 dump_editor, dump_baton, pool, pool));
447 SVN_ERR(reporter->set_path(report_baton, "", revision,
448 svn_depth_infinity, TRUE, NULL, pool));
449 SVN_ERR(reporter->finish_report(report_baton, pool));
451 /* All finished with START_REVISION! */
453 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
459 /* Replay revisions START_REVISION thru END_REVISION (inclusive) of
460 * the repository URL at which SESSION is rooted, using callbacks
461 * which generate Subversion repository dumpstreams describing the
462 * changes made in those revisions. If QUIET is set, don't generate
466 replay_revisions(svn_ra_session_t *session,
467 svn_ra_session_t *extra_ra_session,
468 svn_revnum_t start_revision,
469 svn_revnum_t end_revision,
471 svn_boolean_t incremental,
472 const char *dumpfile,
475 struct replay_baton *replay_baton;
477 svn_stream_t *output_stream;
481 SVN_ERR(svn_stream_open_writable(&output_stream, dumpfile, pool, pool));
485 SVN_ERR(svn_stream_for_stdout(&output_stream, pool));
488 replay_baton = apr_pcalloc(pool, sizeof(*replay_baton));
489 replay_baton->stdout_stream = output_stream;
490 replay_baton->extra_ra_session = extra_ra_session;
491 replay_baton->quiet = quiet;
493 /* Write the magic header and UUID */
494 SVN_ERR(svn_stream_printf(output_stream, pool,
495 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
496 SVN_REPOS_DUMPFILE_FORMAT_VERSION));
497 SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool));
498 SVN_ERR(svn_stream_printf(output_stream, pool,
499 SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
501 /* Fake revision 0 if necessary */
502 if (start_revision == 0)
504 SVN_ERR(dump_revision_header(session, output_stream,
505 start_revision, pool));
507 /* Revision 0 has no tree changes, so we're done. */
509 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
513 /* If our first revision is 0, we can treat this as an
518 /* If what remains to be dumped is not going to be dumped
519 incrementally, then dump the first revision in full. */
522 SVN_ERR(dump_initial_full_revision(session, extra_ra_session,
523 output_stream, start_revision,
528 /* If there are still revisions left to be dumped, do so. */
529 if (start_revision <= end_revision)
532 SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision,
533 0, TRUE, replay_revstart, replay_revend,
534 replay_baton, pool));
536 SVN_ERR(svn_ra__replay_range_ev2(session, start_revision, end_revision,
537 0, TRUE, replay_revstart_v2,
538 replay_revend_v2, replay_baton,
539 NULL, NULL, NULL, NULL, pool));
546 /* Read a dumpstream from stdin, and use it to feed a loader capable
547 * of transmitting that information to the repository located at URL
548 * (to which SESSION has been opened). AUX_SESSION is a second RA
549 * session opened to the same URL for performing auxiliary out-of-band
553 load_revisions(svn_ra_session_t *session,
554 svn_ra_session_t *aux_session,
555 const char *dumpfile,
557 apr_hash_t *skip_revprops,
560 svn_stream_t *output_stream;
564 SVN_ERR(svn_stream_open_readonly(&output_stream, dumpfile, pool, pool));
568 SVN_ERR(svn_stream_for_stdin2(&output_stream, TRUE, pool));
571 SVN_ERR(svn_rdump__load_dumpstream(output_stream, session, aux_session,
572 quiet, skip_revprops,
573 check_cancel, NULL, pool));
578 /* Return a program name for this program, the basename of the path
579 * represented by PROGNAME if not NULL; use "svnrdump" otherwise.
582 ensure_appname(const char *progname,
588 return svn_dirent_basename(svn_dirent_internal_style(progname, pool), NULL);
591 /* Print a simple usage string. */
593 usage(const char *progname,
596 return svn_cmdline_fprintf(stderr, pool,
597 _("Type '%s help' for usage.\n"),
598 ensure_appname(progname, pool));
601 /* Print information about the version of this program and dependent
605 version(const char *progname,
609 svn_stringbuf_t *version_footer =
610 svn_stringbuf_create(_("The following repository access (RA) modules "
611 "are available:\n\n"),
614 SVN_ERR(svn_ra_print_modules(version_footer, pool));
615 return svn_opt_print_help4(NULL, ensure_appname(progname, pool),
616 TRUE, quiet, FALSE, version_footer->data,
617 NULL, NULL, NULL, NULL, NULL, pool);
621 /* Handle the "dump" subcommand. Implements `svn_opt_subcommand_t'. */
623 dump_cmd(apr_getopt_t *os,
627 opt_baton_t *opt_baton = baton;
628 svn_ra_session_t *extra_ra_session;
629 const char *repos_root;
631 SVN_ERR(svn_client_open_ra_session2(&extra_ra_session,
632 opt_baton->url, NULL,
633 opt_baton->ctx, pool, pool));
634 SVN_ERR(svn_ra_get_repos_root2(extra_ra_session, &repos_root, pool));
635 SVN_ERR(svn_ra_reparent(extra_ra_session, repos_root, pool));
637 return replay_revisions(opt_baton->session, extra_ra_session,
638 opt_baton->start_revision.value.number,
639 opt_baton->end_revision.value.number,
640 opt_baton->quiet, opt_baton->incremental,
641 opt_baton->dumpfile, pool);
644 /* Handle the "load" subcommand. Implements `svn_opt_subcommand_t'. */
646 load_cmd(apr_getopt_t *os,
650 opt_baton_t *opt_baton = baton;
651 svn_ra_session_t *aux_session;
653 SVN_ERR(svn_client_open_ra_session2(&aux_session, opt_baton->url, NULL,
654 opt_baton->ctx, pool, pool));
655 return load_revisions(opt_baton->session, aux_session,
656 opt_baton->dumpfile, opt_baton->quiet,
657 opt_baton->skip_revprops, pool);
660 /* Handle the "help" subcommand. Implements `svn_opt_subcommand_t'. */
662 help_cmd(apr_getopt_t *os,
667 _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n"
668 "Subversion remote repository dump and load tool.\n"
669 "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n"
670 "Type 'svnrdump --version' to see the program version and RA modules.\n"
672 "Available subcommands:\n");
674 return svn_opt_print_help4(os, "svnrdump", FALSE, FALSE, FALSE, NULL,
675 header, svnrdump__cmd_table, svnrdump__options,
679 /* Examine the OPT_BATON's 'start_revision' and 'end_revision'
680 * members, making sure that they make sense (in general, and as
681 * applied to a repository whose current youngest revision is
685 validate_and_resolve_revisions(opt_baton_t *opt_baton,
686 svn_revnum_t latest_revision,
689 svn_revnum_t provided_start_rev = SVN_INVALID_REVNUM;
691 /* Ensure that the start revision is something we can handle. We
692 want a number >= 0. If unspecified, make it a number (r0) --
693 anything else is bogus. */
694 if (opt_baton->start_revision.kind == svn_opt_revision_number)
696 provided_start_rev = opt_baton->start_revision.value.number;
698 else if (opt_baton->start_revision.kind == svn_opt_revision_head)
700 opt_baton->start_revision.kind = svn_opt_revision_number;
701 opt_baton->start_revision.value.number = latest_revision;
703 else if (opt_baton->start_revision.kind == svn_opt_revision_unspecified)
705 opt_baton->start_revision.kind = svn_opt_revision_number;
706 opt_baton->start_revision.value.number = 0;
709 if (opt_baton->start_revision.kind != svn_opt_revision_number)
711 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
712 _("Unsupported revision specifier used; use "
713 "only integer values or 'HEAD'"));
716 if ((opt_baton->start_revision.value.number < 0) ||
717 (opt_baton->start_revision.value.number > latest_revision))
719 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
720 _("Revision '%ld' does not exist"),
721 opt_baton->start_revision.value.number);
724 /* Ensure that the end revision is something we can handle. We want
725 a number <= the youngest, and > the start revision. If
726 unspecified, make it a number (start_revision + 1 if that was
727 specified, the youngest revision in the repository otherwise) --
728 anything else is bogus. */
729 if (opt_baton->end_revision.kind == svn_opt_revision_unspecified)
731 opt_baton->end_revision.kind = svn_opt_revision_number;
732 if (SVN_IS_VALID_REVNUM(provided_start_rev))
733 opt_baton->end_revision.value.number = provided_start_rev;
735 opt_baton->end_revision.value.number = latest_revision;
737 else if (opt_baton->end_revision.kind == svn_opt_revision_head)
739 opt_baton->end_revision.kind = svn_opt_revision_number;
740 opt_baton->end_revision.value.number = latest_revision;
743 if (opt_baton->end_revision.kind != svn_opt_revision_number)
745 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
746 _("Unsupported revision specifier used; use "
747 "only integer values or 'HEAD'"));
750 if ((opt_baton->end_revision.value.number < 0) ||
751 (opt_baton->end_revision.value.number > latest_revision))
753 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
754 _("Revision '%ld' does not exist"),
755 opt_baton->end_revision.value.number);
758 /* Finally, make sure that the end revision is younger than the
759 start revision. We don't do "backwards" 'round here. */
760 if (opt_baton->end_revision.value.number <
761 opt_baton->start_revision.value.number)
763 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
764 _("LOWER revision cannot be greater than "
765 "UPPER revision; consider reversing your "
772 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
773 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
774 * return SVN_NO_ERROR.
777 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
779 svn_error_t *err = SVN_NO_ERROR;
780 const svn_opt_subcommand_desc2_t *subcommand = NULL;
781 opt_baton_t *opt_baton;
782 svn_revnum_t latest_revision = SVN_INVALID_REVNUM;
783 const char *config_dir = NULL;
784 const char *username = NULL;
785 const char *password = NULL;
786 svn_boolean_t no_auth_cache = FALSE;
787 svn_boolean_t trust_unknown_ca = FALSE;
788 svn_boolean_t trust_cn_mismatch = FALSE;
789 svn_boolean_t trust_expired = FALSE;
790 svn_boolean_t trust_not_yet_valid = FALSE;
791 svn_boolean_t trust_other_failure = FALSE;
792 svn_boolean_t non_interactive = FALSE;
793 svn_boolean_t force_interactive = FALSE;
794 apr_array_header_t *config_options = NULL;
796 apr_array_header_t *received_opts;
798 svn_boolean_t read_pass_from_stdin = FALSE;
800 opt_baton = apr_pcalloc(pool, sizeof(*opt_baton));
801 opt_baton->start_revision.kind = svn_opt_revision_unspecified;
802 opt_baton->end_revision.kind = svn_opt_revision_unspecified;
803 opt_baton->url = NULL;
804 opt_baton->skip_revprops = apr_hash_make(pool);
805 opt_baton->dumpfile = NULL;
807 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
809 os->interleave = TRUE; /* Options and arguments can be interleaved */
811 /* Set up our cancellation support. */
812 check_cancel = svn_cmdline__setup_cancellation_handler();
814 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
820 apr_status_t status = apr_getopt_long(os, svnrdump__options, &opt,
823 if (APR_STATUS_IS_EOF(status))
825 if (status != APR_SUCCESS)
827 SVN_ERR(usage(argv[0], pool));
828 *exit_code = EXIT_FAILURE;
832 /* Stash the option code in an array before parsing it. */
833 APR_ARRAY_PUSH(received_opts, int) = opt;
839 /* Make sure we've not seen -r already. */
840 if (opt_baton->start_revision.kind != svn_opt_revision_unspecified)
842 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
843 _("Multiple revision arguments "
844 "encountered; try '-r N:M' instead "
847 /* Parse the -r argument. */
848 if (svn_opt_parse_revision(&(opt_baton->start_revision),
849 &(opt_baton->end_revision),
852 const char *utf8_opt_arg;
853 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
854 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
855 _("Syntax error in revision "
856 "argument '%s'"), utf8_opt_arg);
861 opt_baton->quiet = TRUE;
864 config_dir = opt_arg;
867 opt_baton->version = TRUE;
870 opt_baton->help = TRUE;
872 case opt_auth_username:
873 SVN_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool));
875 case opt_auth_password:
876 SVN_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool));
878 case opt_auth_password_from_stdin:
879 read_pass_from_stdin = TRUE;
881 case opt_auth_nocache:
882 no_auth_cache = TRUE;
884 case opt_non_interactive:
885 non_interactive = TRUE;
887 case opt_force_interactive:
888 force_interactive = TRUE;
890 case opt_incremental:
891 opt_baton->incremental = TRUE;
893 case opt_skip_revprop:
894 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
895 svn_hash_sets(opt_baton->skip_revprops, opt_arg, opt_arg);
897 case opt_trust_server_cert: /* backward compat */
898 trust_unknown_ca = TRUE;
900 case opt_trust_server_cert_failures:
901 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
902 SVN_ERR(svn_cmdline__parse_trust_options(
906 &trust_not_yet_valid,
907 &trust_other_failure,
910 case opt_config_option:
913 apr_array_make(pool, 1,
914 sizeof(svn_cmdline__config_argument_t*));
916 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
917 SVN_ERR(svn_cmdline__parse_config_option(config_options,
923 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
924 opt_baton->dumpfile = opt_arg;
929 /* The --non-interactive and --force-interactive options are mutually
931 if (non_interactive && force_interactive)
933 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
934 _("--non-interactive and --force-interactive "
935 "are mutually exclusive"));
940 subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
943 if (subcommand == NULL)
945 if (os->ind >= os->argc)
947 if (opt_baton->version)
949 /* Use the "help" subcommand to handle the "--version" option. */
950 static const svn_opt_subcommand_desc2_t pseudo_cmd =
951 { "--version", help_cmd, {0}, "",
952 {opt_version, /* must accept its own option */
955 subcommand = &pseudo_cmd;
960 SVN_ERR(help_cmd(NULL, NULL, pool));
961 *exit_code = EXIT_FAILURE;
967 const char *first_arg;
969 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
971 subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
974 if (subcommand == NULL)
977 svn_cmdline_fprintf(stderr, pool,
978 _("Unknown subcommand: '%s'\n"),
980 SVN_ERR(help_cmd(NULL, NULL, pool));
981 *exit_code = EXIT_FAILURE;
987 /* Check that the subcommand wasn't passed any inappropriate options. */
988 for (i = 0; i < received_opts->nelts; i++)
990 int opt_id = APR_ARRAY_IDX(received_opts, i, int);
992 /* All commands implicitly accept --help, so just skip over this
993 when we see it. Note that we don't want to include this option
994 in their "accepted options" list because it would be awfully
995 redundant to display it in every commands' help text. */
996 if (opt_id == 'h' || opt_id == '?')
999 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1002 const apr_getopt_option_t *badopt =
1003 svn_opt_get_option_from_code2(opt_id, svnrdump__options,
1005 svn_opt_format_option(&optstr, badopt, FALSE, pool);
1006 if (subcommand->name[0] == '-')
1007 SVN_ERR(help_cmd(NULL, NULL, pool));
1009 svn_error_clear(svn_cmdline_fprintf(
1011 _("Subcommand '%s' doesn't accept option '%s'\n"
1012 "Type 'svnrdump help %s' for usage.\n"),
1013 subcommand->name, optstr, subcommand->name));
1014 *exit_code = EXIT_FAILURE;
1015 return SVN_NO_ERROR;
1019 if (strcmp(subcommand->name, "--version") == 0)
1021 SVN_ERR(version(argv[0], opt_baton->quiet, pool));
1022 return SVN_NO_ERROR;
1025 if (strcmp(subcommand->name, "help") == 0)
1027 SVN_ERR(help_cmd(os, opt_baton, pool));
1028 return SVN_NO_ERROR;
1031 /* --trust-* can only be used with --non-interactive */
1032 if (!non_interactive)
1034 if (trust_unknown_ca || trust_cn_mismatch || trust_expired
1035 || trust_not_yet_valid || trust_other_failure)
1036 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1037 _("--trust-server-cert-failures requires "
1038 "--non-interactive"));
1041 if (read_pass_from_stdin && !non_interactive)
1043 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1044 _("--password-from-stdin requires "
1045 "--non-interactive"));
1048 if (strcmp(subcommand->name, "load") == 0)
1050 if (read_pass_from_stdin && opt_baton->dumpfile == NULL)
1052 /* error here, since load cannot process a password over stdin */
1053 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1054 _("load subcommand with "
1055 "--password-from-stdin requires -F"));
1059 /* Expect one more non-option argument: the repository URL. */
1060 if (os->ind != os->argc - 1)
1062 SVN_ERR(usage(argv[0], pool));
1063 *exit_code = EXIT_FAILURE;
1064 return SVN_NO_ERROR;
1068 const char *repos_url;
1070 SVN_ERR(svn_utf_cstring_to_utf8(&repos_url, os->argv[os->ind], pool));
1071 if (! svn_path_is_url(repos_url))
1073 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1074 "Target '%s' is not a URL",
1077 opt_baton->url = svn_uri_canonicalize(repos_url, pool);
1080 if (strcmp(subcommand->name, "load") == 0)
1083 * By default (no --*-interactive options given), the 'load' subcommand
1084 * is interactive unless username and password were provided on the
1085 * command line. This allows prompting for auth creds to work without
1086 * requiring users to remember to use --force-interactive.
1087 * See issue #3913, "svnrdump load is not working in interactive mode".
1089 if (!non_interactive && !force_interactive)
1090 force_interactive = (username == NULL || password == NULL);
1093 /* Get password from stdin if necessary */
1094 if (read_pass_from_stdin)
1096 SVN_ERR(svn_cmdline__stdin_readline(&password, pool, pool));
1099 non_interactive = !svn_cmdline__be_interactive(non_interactive,
1102 SVN_ERR(init_client_context(&(opt_baton->ctx),
1112 trust_not_yet_valid,
1113 trust_other_failure,
1117 err = svn_client_open_ra_session2(&(opt_baton->session),
1118 opt_baton->url, NULL,
1119 opt_baton->ctx, pool, pool);
1121 /* Have sane opt_baton->start_revision and end_revision defaults if
1124 err = svn_ra_get_latest_revnum(opt_baton->session, &latest_revision, pool);
1126 /* Make sure any provided revisions make sense. */
1128 err = validate_and_resolve_revisions(opt_baton, latest_revision, pool);
1130 /* Dispatch the subcommand */
1132 err = (*subcommand->cmd_func)(os, opt_baton, pool);
1134 if (err && err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
1136 return svn_error_quick_wrap(err,
1137 _("Authentication failed and interactive"
1138 " prompting is disabled; see the"
1139 " --force-interactive option"));
1144 return SVN_NO_ERROR;
1148 main(int argc, const char *argv[])
1151 int exit_code = EXIT_SUCCESS;
1154 /* Initialize the app. */
1155 if (svn_cmdline_init("svnrdump", stderr) != EXIT_SUCCESS)
1156 return EXIT_FAILURE;
1158 /* Create our top-level pool. Use a separate mutexless allocator,
1159 * given this application is single threaded.
1161 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1163 err = sub_main(&exit_code, argc, argv, pool);
1165 /* Flush stdout and report if it fails. It would be flushed on exit anyway
1166 but this makes sure that output is not silently lost if it fails. */
1167 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
1171 exit_code = EXIT_FAILURE;
1172 svn_cmdline_handle_exit_error(err, NULL, "svnrdump: ");
1175 svn_pool_destroy(pool);
1177 svn_cmdline__cancellation_exit();