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 * ====================================================================
25 #include <apr_signal.h>
28 #include "svn_pools.h"
29 #include "svn_cmdline.h"
30 #include "svn_client.h"
33 #include "svn_repos.h"
36 #include "svn_private_config.h"
37 #include "svn_string.h"
38 #include "svn_props.h"
42 #include "private/svn_repos_private.h"
43 #include "private/svn_cmdline_private.h"
44 #include "private/svn_ra_private.h"
48 /*** Cancellation ***/
50 /* A flag to see if we've been cancelled by the client or not. */
51 static volatile sig_atomic_t cancelled = FALSE;
53 /* A signal handler to support cancellation. */
55 signal_handler(int signum)
57 apr_signal(signum, SIG_IGN);
61 /* Our cancellation callback. */
63 check_cancel(void *baton)
66 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
74 static svn_opt_subcommand_t dump_cmd, load_cmd;
76 enum svn_svnrdump__longopt_t
78 opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID,
85 opt_force_interactive,
87 opt_trust_server_cert,
88 opt_trust_server_cert_failures,
92 #define SVN_SVNRDUMP__BASE_OPTIONS opt_config_dir, \
97 opt_trust_server_cert, \
98 opt_trust_server_cert_failures, \
99 opt_non_interactive, \
100 opt_force_interactive
102 static const svn_opt_subcommand_desc2_t svnrdump__cmd_table[] =
104 { "dump", dump_cmd, { 0 },
105 N_("usage: svnrdump dump URL [-r LOWER[:UPPER]]\n\n"
106 "Dump revisions LOWER to UPPER of repository at remote URL to stdout\n"
107 "in a 'dumpfile' portable format. If only LOWER is given, dump that\n"
109 { 'r', 'q', opt_incremental, SVN_SVNRDUMP__BASE_OPTIONS } },
110 { "load", load_cmd, { 0 },
111 N_("usage: svnrdump load URL\n\n"
112 "Load a 'dumpfile' given on stdin to a repository at remote URL.\n"),
113 { 'q', opt_skip_revprop, SVN_SVNRDUMP__BASE_OPTIONS } },
114 { "help", 0, { "?", "h" },
115 N_("usage: svnrdump help [SUBCOMMAND...]\n\n"
116 "Describe the usage of this program or its subcommands.\n"),
118 { NULL, NULL, { 0 }, NULL, { 0 } }
121 static const apr_getopt_option_t svnrdump__options[] =
124 N_("specify revision number ARG (or X:Y range)")},
126 N_("no progress (only errors) to stderr")},
127 {"incremental", opt_incremental, 0,
128 N_("dump incrementally")},
129 {"skip-revprop", opt_skip_revprop, 1,
130 N_("skip revision property ARG (e.g., \"svn:author\")")},
131 {"config-dir", opt_config_dir, 1,
132 N_("read user configuration files from directory ARG")},
133 {"username", opt_auth_username, 1,
134 N_("specify a username ARG")},
135 {"password", opt_auth_password, 1,
136 N_("specify a password ARG")},
137 {"non-interactive", opt_non_interactive, 0,
138 N_("do no interactive prompting (default is to prompt\n"
140 "only if standard input is a terminal device)")},
141 {"force-interactive", opt_force_interactive, 0,
142 N_("do interactive prompting even if standard input\n"
144 "is not a terminal device")},
145 {"no-auth-cache", opt_auth_nocache, 0,
146 N_("do not cache authentication tokens")},
148 N_("display this help")},
149 {"version", opt_version, 0,
150 N_("show program version information")},
151 {"config-option", opt_config_option, 1,
152 N_("set user configuration option in the format:\n"
154 " FILE:SECTION:OPTION=[VALUE]\n"
158 " servers:global:http-library=serf")},
159 {"trust-server-cert", opt_trust_server_cert, 0,
160 N_("deprecated; same as\n"
162 "--trust-server-cert-failures=unknown-ca")},
163 {"trust-server-cert-failures", opt_trust_server_cert_failures, 1,
164 N_("with --non-interactive, accept SSL server\n"
166 "certificates with failures; ARG is comma-separated\n"
168 "list of 'unknown-ca' (Unknown Authority),\n"
170 "'cn-mismatch' (Hostname mismatch), 'expired'\n"
172 "(Expired certificate), 'not-yet-valid' (Not yet\n"
174 "valid certificate) and 'other' (all other not\n"
176 "separately classified certificate errors).")},
180 /* Baton for the RA replay session. */
181 struct replay_baton {
182 /* A backdoor ra session for fetching information. */
183 svn_ra_session_t *extra_ra_session;
185 /* The output stream */
186 svn_stream_t *stdout_stream;
188 /* Whether to be quiet. */
193 typedef struct opt_baton_t {
194 svn_client_ctx_t *ctx;
195 svn_ra_session_t *session;
198 svn_boolean_t version;
199 svn_opt_revision_t start_revision;
200 svn_opt_revision_t end_revision;
202 svn_boolean_t incremental;
203 apr_hash_t *skip_revprops;
206 /* Print dumpstream-formatted information about REVISION.
207 * Implements the `svn_ra_replay_revstart_callback_t' interface.
210 replay_revstart(svn_revnum_t revision,
212 const svn_delta_editor_t **editor,
214 apr_hash_t *rev_props,
217 struct replay_baton *rb = replay_baton;
218 apr_hash_t *normal_props;
220 /* Normalize and dump the revprops */
221 SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
222 SVN_ERR(svn_repos__dump_revision_record(rb->stdout_stream, revision, NULL,
224 TRUE /*props_section_always*/,
227 SVN_ERR(svn_rdump__get_dump_editor(editor, edit_baton, revision,
228 rb->stdout_stream, rb->extra_ra_session,
229 NULL, check_cancel, NULL, pool));
234 /* Print progress information about the dump of REVISION.
235 Implements the `svn_ra_replay_revfinish_callback_t' interface. */
237 replay_revend(svn_revnum_t revision,
239 const svn_delta_editor_t *editor,
241 apr_hash_t *rev_props,
244 /* No resources left to free. */
245 struct replay_baton *rb = replay_baton;
247 SVN_ERR(editor->close_edit(edit_baton, pool));
250 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
256 /* Print dumpstream-formatted information about REVISION.
257 * Implements the `svn_ra_replay_revstart_callback_t' interface.
260 replay_revstart_v2(svn_revnum_t revision,
262 svn_editor_t **editor,
263 apr_hash_t *rev_props,
266 struct replay_baton *rb = replay_baton;
267 apr_hash_t *normal_props;
269 /* Normalize and dump the revprops */
270 SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
271 SVN_ERR(svn_repos__dump_revision_record(rb->stdout_stream, revision,
273 TRUE /*props_section_always*/,
276 SVN_ERR(svn_rdump__get_dump_editor_v2(editor, revision,
278 rb->extra_ra_session,
279 NULL, check_cancel, NULL, pool, pool));
284 /* Print progress information about the dump of REVISION.
285 Implements the `svn_ra_replay_revfinish_callback_t' interface. */
287 replay_revend_v2(svn_revnum_t revision,
289 svn_editor_t *editor,
290 apr_hash_t *rev_props,
293 /* No resources left to free. */
294 struct replay_baton *rb = replay_baton;
296 SVN_ERR(svn_editor_complete(editor));
299 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
305 /* Initialize the RA layer, and set *CTX to a new client context baton
306 * allocated from POOL. Use CONFIG_DIR and pass USERNAME, PASSWORD,
307 * CONFIG_DIR and NO_AUTH_CACHE to initialize the authorization baton.
308 * CONFIG_OPTIONS (if not NULL) is a list of configuration overrides.
309 * REPOS_URL is used to fiddle with server-specific configuration
313 init_client_context(svn_client_ctx_t **ctx_p,
314 svn_boolean_t non_interactive,
315 const char *username,
316 const char *password,
317 const char *config_dir,
318 const char *repos_url,
319 svn_boolean_t no_auth_cache,
320 svn_boolean_t trust_unknown_ca,
321 svn_boolean_t trust_cn_mismatch,
322 svn_boolean_t trust_expired,
323 svn_boolean_t trust_not_yet_valid,
324 svn_boolean_t trust_other_failure,
325 apr_array_header_t *config_options,
328 svn_client_ctx_t *ctx = NULL;
329 svn_config_t *cfg_config, *cfg_servers;
331 SVN_ERR(svn_ra_initialize(pool));
333 SVN_ERR(svn_config_ensure(config_dir, pool));
334 SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
336 SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
339 SVN_ERR(svn_cmdline__apply_config_options(ctx->config, config_options,
340 "svnrdump: ", "--config-option"));
342 cfg_config = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG);
344 /* ### FIXME: This is a hack to work around the fact that our dump
345 ### editor simply can't handle the way ra_serf violates the
346 ### editor v1 drive ordering requirements.
348 ### We'll override both the global value and server-specific one
349 ### for the 'http-bulk-updates' and 'http-max-connections'
350 ### options in order to get ra_serf to try a bulk-update if the
351 ### server will allow it, or at least try to limit all its
352 ### auxiliary GETs/PROPFINDs to happening (well-ordered) on a
353 ### single server connection.
355 ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
357 cfg_servers = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_SERVERS);
358 svn_config_set_bool(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
359 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
360 svn_config_set_int64(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
361 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
365 apr_uri_t parsed_url;
367 status = apr_uri_parse(pool, repos_url, &parsed_url);
370 const char *server_group;
372 server_group = svn_config_find_group(cfg_servers, parsed_url.hostname,
373 SVN_CONFIG_SECTION_GROUPS, pool);
376 svn_config_set_bool(cfg_servers, server_group,
377 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
378 svn_config_set_int64(cfg_servers, server_group,
379 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
384 /* Set up our cancellation support. */
385 ctx->cancel_func = check_cancel;
387 /* Default authentication providers for non-interactive use */
388 SVN_ERR(svn_cmdline_create_auth_baton2(&(ctx->auth_baton), non_interactive,
389 username, password, config_dir,
390 no_auth_cache, trust_unknown_ca,
391 trust_cn_mismatch, trust_expired,
394 cfg_config, ctx->cancel_func,
395 ctx->cancel_baton, pool));
400 /* Print a revision record header for REVISION to STDOUT_STREAM. Use
401 * SESSION to contact the repository for revision properties and
405 dump_revision_header(svn_ra_session_t *session,
406 svn_stream_t *stdout_stream,
407 svn_revnum_t revision,
410 apr_hash_t *prophash;
412 SVN_ERR(svn_ra_rev_proplist(session, revision, &prophash, pool));
413 SVN_ERR(svn_repos__dump_revision_record(stdout_stream, revision, NULL,
415 TRUE /*props_section_always*/,
421 dump_initial_full_revision(svn_ra_session_t *session,
422 svn_ra_session_t *extra_ra_session,
423 svn_stream_t *stdout_stream,
424 svn_revnum_t revision,
428 const svn_ra_reporter3_t *reporter;
430 const svn_delta_editor_t *dump_editor;
432 const char *session_url, *source_relpath;
434 /* Determine whether we're dumping the repository root URL or some
435 child thereof. If we're dumping a subtree of the repository
436 rather than the root, we have to jump through some hoops to make
437 our update-driven dump generation work the way a replay-driven
440 See http://subversion.tigris.org/issues/show_bug.cgi?id=4101
442 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
443 SVN_ERR(svn_ra_get_path_relative_to_root(session, &source_relpath,
446 /* Start with a revision record header. */
447 SVN_ERR(dump_revision_header(session, stdout_stream, revision, pool));
449 /* Then, we'll drive the dump editor with what would look like a
450 full checkout of the repository as it looked in START_REVISION.
451 We do this by manufacturing a basic 'report' to the update
452 reporter, telling it that we have nothing to start with. The
453 delta between nothing and everything-at-REV is, effectively, a
455 SVN_ERR(svn_rdump__get_dump_editor(&dump_editor, &dump_baton, revision,
456 stdout_stream, extra_ra_session,
457 source_relpath, check_cancel, NULL, pool));
458 SVN_ERR(svn_ra_do_update3(session, &reporter, &report_baton, revision,
459 "", svn_depth_infinity, FALSE, FALSE,
460 dump_editor, dump_baton, pool, pool));
461 SVN_ERR(reporter->set_path(report_baton, "", revision,
462 svn_depth_infinity, TRUE, NULL, pool));
463 SVN_ERR(reporter->finish_report(report_baton, pool));
465 /* All finished with START_REVISION! */
467 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
473 /* Replay revisions START_REVISION thru END_REVISION (inclusive) of
474 * the repository URL at which SESSION is rooted, using callbacks
475 * which generate Subversion repository dumpstreams describing the
476 * changes made in those revisions. If QUIET is set, don't generate
480 replay_revisions(svn_ra_session_t *session,
481 svn_ra_session_t *extra_ra_session,
482 svn_revnum_t start_revision,
483 svn_revnum_t end_revision,
485 svn_boolean_t incremental,
488 struct replay_baton *replay_baton;
490 svn_stream_t *stdout_stream;
492 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
494 replay_baton = apr_pcalloc(pool, sizeof(*replay_baton));
495 replay_baton->stdout_stream = stdout_stream;
496 replay_baton->extra_ra_session = extra_ra_session;
497 replay_baton->quiet = quiet;
499 /* Write the magic header and UUID */
500 SVN_ERR(svn_stream_printf(stdout_stream, pool,
501 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
502 SVN_REPOS_DUMPFILE_FORMAT_VERSION));
503 SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool));
504 SVN_ERR(svn_stream_printf(stdout_stream, pool,
505 SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
507 /* Fake revision 0 if necessary */
508 if (start_revision == 0)
510 SVN_ERR(dump_revision_header(session, stdout_stream,
511 start_revision, pool));
513 /* Revision 0 has no tree changes, so we're done. */
515 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
519 /* If our first revision is 0, we can treat this as an
524 /* If what remains to be dumped is not going to be dumped
525 incrementally, then dump the first revision in full. */
528 SVN_ERR(dump_initial_full_revision(session, extra_ra_session,
529 stdout_stream, start_revision,
534 /* If there are still revisions left to be dumped, do so. */
535 if (start_revision <= end_revision)
538 SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision,
539 0, TRUE, replay_revstart, replay_revend,
540 replay_baton, pool));
542 SVN_ERR(svn_ra__replay_range_ev2(session, start_revision, end_revision,
543 0, TRUE, replay_revstart_v2,
544 replay_revend_v2, replay_baton,
545 NULL, NULL, NULL, NULL, pool));
549 SVN_ERR(svn_stream_close(stdout_stream));
553 /* Read a dumpstream from stdin, and use it to feed a loader capable
554 * of transmitting that information to the repository located at URL
555 * (to which SESSION has been opened). AUX_SESSION is a second RA
556 * session opened to the same URL for performing auxiliary out-of-band
560 load_revisions(svn_ra_session_t *session,
561 svn_ra_session_t *aux_session,
564 apr_hash_t *skip_revprops,
567 apr_file_t *stdin_file;
568 svn_stream_t *stdin_stream;
570 apr_file_open_stdin(&stdin_file, pool);
571 stdin_stream = svn_stream_from_aprfile2(stdin_file, FALSE, pool);
573 SVN_ERR(svn_rdump__load_dumpstream(stdin_stream, session, aux_session,
574 quiet, skip_revprops,
575 check_cancel, NULL, pool));
577 SVN_ERR(svn_stream_close(stdin_stream));
582 /* Return a program name for this program, the basename of the path
583 * represented by PROGNAME if not NULL; use "svnrdump" otherwise.
586 ensure_appname(const char *progname,
592 return svn_dirent_basename(svn_dirent_internal_style(progname, pool), NULL);
595 /* Print a simple usage string. */
597 usage(const char *progname,
600 return svn_cmdline_fprintf(stderr, pool,
601 _("Type '%s help' for usage.\n"),
602 ensure_appname(progname, pool));
605 /* Print information about the version of this program and dependent
609 version(const char *progname,
613 svn_stringbuf_t *version_footer =
614 svn_stringbuf_create(_("The following repository access (RA) modules "
615 "are available:\n\n"),
618 SVN_ERR(svn_ra_print_modules(version_footer, pool));
619 return svn_opt_print_help4(NULL, ensure_appname(progname, pool),
620 TRUE, quiet, FALSE, version_footer->data,
621 NULL, NULL, NULL, NULL, NULL, pool);
625 /* Handle the "dump" subcommand. Implements `svn_opt_subcommand_t'. */
627 dump_cmd(apr_getopt_t *os,
631 opt_baton_t *opt_baton = baton;
632 svn_ra_session_t *extra_ra_session;
633 const char *repos_root;
635 SVN_ERR(svn_client_open_ra_session2(&extra_ra_session,
636 opt_baton->url, NULL,
637 opt_baton->ctx, pool, pool));
638 SVN_ERR(svn_ra_get_repos_root2(extra_ra_session, &repos_root, pool));
639 SVN_ERR(svn_ra_reparent(extra_ra_session, repos_root, pool));
641 return replay_revisions(opt_baton->session, extra_ra_session,
642 opt_baton->start_revision.value.number,
643 opt_baton->end_revision.value.number,
644 opt_baton->quiet, opt_baton->incremental, pool);
647 /* Handle the "load" subcommand. Implements `svn_opt_subcommand_t'. */
649 load_cmd(apr_getopt_t *os,
653 opt_baton_t *opt_baton = baton;
654 svn_ra_session_t *aux_session;
656 SVN_ERR(svn_client_open_ra_session2(&aux_session, opt_baton->url, NULL,
657 opt_baton->ctx, pool, pool));
658 return load_revisions(opt_baton->session, aux_session, opt_baton->url,
659 opt_baton->quiet, opt_baton->skip_revprops, pool);
662 /* Handle the "help" subcommand. Implements `svn_opt_subcommand_t'. */
664 help_cmd(apr_getopt_t *os,
669 _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n"
670 "Subversion remote repository dump and load tool.\n"
671 "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n"
672 "Type 'svnrdump --version' to see the program version and RA modules.\n"
674 "Available subcommands:\n");
676 return svn_opt_print_help4(os, "svnrdump", FALSE, FALSE, FALSE, NULL,
677 header, svnrdump__cmd_table, svnrdump__options,
681 /* Examine the OPT_BATON's 'start_revision' and 'end_revision'
682 * members, making sure that they make sense (in general, and as
683 * applied to a repository whose current youngest revision is
687 validate_and_resolve_revisions(opt_baton_t *opt_baton,
688 svn_revnum_t latest_revision,
691 svn_revnum_t provided_start_rev = SVN_INVALID_REVNUM;
693 /* Ensure that the start revision is something we can handle. We
694 want a number >= 0. If unspecified, make it a number (r0) --
695 anything else is bogus. */
696 if (opt_baton->start_revision.kind == svn_opt_revision_number)
698 provided_start_rev = opt_baton->start_revision.value.number;
700 else if (opt_baton->start_revision.kind == svn_opt_revision_head)
702 opt_baton->start_revision.kind = svn_opt_revision_number;
703 opt_baton->start_revision.value.number = latest_revision;
705 else if (opt_baton->start_revision.kind == svn_opt_revision_unspecified)
707 opt_baton->start_revision.kind = svn_opt_revision_number;
708 opt_baton->start_revision.value.number = 0;
711 if (opt_baton->start_revision.kind != svn_opt_revision_number)
713 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
714 _("Unsupported revision specifier used; use "
715 "only integer values or 'HEAD'"));
718 if ((opt_baton->start_revision.value.number < 0) ||
719 (opt_baton->start_revision.value.number > latest_revision))
721 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
722 _("Revision '%ld' does not exist"),
723 opt_baton->start_revision.value.number);
726 /* Ensure that the end revision is something we can handle. We want
727 a number <= the youngest, and > the start revision. If
728 unspecified, make it a number (start_revision + 1 if that was
729 specified, the youngest revision in the repository otherwise) --
730 anything else is bogus. */
731 if (opt_baton->end_revision.kind == svn_opt_revision_unspecified)
733 opt_baton->end_revision.kind = svn_opt_revision_number;
734 if (SVN_IS_VALID_REVNUM(provided_start_rev))
735 opt_baton->end_revision.value.number = provided_start_rev;
737 opt_baton->end_revision.value.number = latest_revision;
739 else if (opt_baton->end_revision.kind == svn_opt_revision_head)
741 opt_baton->end_revision.kind = svn_opt_revision_number;
742 opt_baton->end_revision.value.number = latest_revision;
745 if (opt_baton->end_revision.kind != svn_opt_revision_number)
747 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
748 _("Unsupported revision specifier used; use "
749 "only integer values or 'HEAD'"));
752 if ((opt_baton->end_revision.value.number < 0) ||
753 (opt_baton->end_revision.value.number > latest_revision))
755 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
756 _("Revision '%ld' does not exist"),
757 opt_baton->end_revision.value.number);
760 /* Finally, make sure that the end revision is younger than the
761 start revision. We don't do "backwards" 'round here. */
762 if (opt_baton->end_revision.value.number <
763 opt_baton->start_revision.value.number)
765 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
766 _("LOWER revision cannot be greater than "
767 "UPPER revision; consider reversing your "
774 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
775 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
776 * return SVN_NO_ERROR.
779 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
781 svn_error_t *err = SVN_NO_ERROR;
782 const svn_opt_subcommand_desc2_t *subcommand = NULL;
783 opt_baton_t *opt_baton;
784 svn_revnum_t latest_revision = SVN_INVALID_REVNUM;
785 const char *config_dir = NULL;
786 const char *username = NULL;
787 const char *password = NULL;
788 svn_boolean_t no_auth_cache = FALSE;
789 svn_boolean_t trust_unknown_ca = FALSE;
790 svn_boolean_t trust_cn_mismatch = FALSE;
791 svn_boolean_t trust_expired = FALSE;
792 svn_boolean_t trust_not_yet_valid = FALSE;
793 svn_boolean_t trust_other_failure = FALSE;
794 svn_boolean_t non_interactive = FALSE;
795 svn_boolean_t force_interactive = FALSE;
796 apr_array_header_t *config_options = NULL;
798 const char *first_arg;
799 apr_array_header_t *received_opts;
802 opt_baton = apr_pcalloc(pool, sizeof(*opt_baton));
803 opt_baton->start_revision.kind = svn_opt_revision_unspecified;
804 opt_baton->end_revision.kind = svn_opt_revision_unspecified;
805 opt_baton->url = NULL;
806 opt_baton->skip_revprops = apr_hash_make(pool);
808 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
810 os->interleave = TRUE; /* Options and arguments can be interleaved */
812 /* Set up our cancellation support. */
813 apr_signal(SIGINT, signal_handler);
815 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
816 apr_signal(SIGBREAK, signal_handler);
819 apr_signal(SIGHUP, signal_handler);
822 apr_signal(SIGTERM, signal_handler);
825 /* Disable SIGPIPE generation for the platforms that have it. */
826 apr_signal(SIGPIPE, SIG_IGN);
829 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
830 * working with large files when compiled against an APR that doesn't have
831 * large file support will crash the program, which is uncool. */
832 apr_signal(SIGXFSZ, SIG_IGN);
835 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
841 apr_status_t status = apr_getopt_long(os, svnrdump__options, &opt,
844 if (APR_STATUS_IS_EOF(status))
846 if (status != APR_SUCCESS)
848 SVN_ERR(usage(argv[0], pool));
849 *exit_code = EXIT_FAILURE;
853 /* Stash the option code in an array before parsing it. */
854 APR_ARRAY_PUSH(received_opts, int) = opt;
860 /* Make sure we've not seen -r already. */
861 if (opt_baton->start_revision.kind != svn_opt_revision_unspecified)
863 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
864 _("Multiple revision arguments "
865 "encountered; try '-r N:M' instead "
868 /* Parse the -r argument. */
869 if (svn_opt_parse_revision(&(opt_baton->start_revision),
870 &(opt_baton->end_revision),
873 const char *utf8_opt_arg;
874 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
875 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
876 _("Syntax error in revision "
877 "argument '%s'"), utf8_opt_arg);
882 opt_baton->quiet = TRUE;
885 config_dir = opt_arg;
888 opt_baton->version = TRUE;
891 opt_baton->help = TRUE;
893 case opt_auth_username:
894 SVN_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool));
896 case opt_auth_password:
897 SVN_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool));
899 case opt_auth_nocache:
900 no_auth_cache = TRUE;
902 case opt_non_interactive:
903 non_interactive = TRUE;
905 case opt_force_interactive:
906 force_interactive = TRUE;
908 case opt_incremental:
909 opt_baton->incremental = TRUE;
911 case opt_skip_revprop:
912 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
913 svn_hash_sets(opt_baton->skip_revprops, opt_arg, opt_arg);
915 case opt_trust_server_cert: /* backward compat */
916 trust_unknown_ca = TRUE;
918 case opt_trust_server_cert_failures:
919 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
920 SVN_ERR(svn_cmdline__parse_trust_options(
924 &trust_not_yet_valid,
925 &trust_other_failure,
928 case opt_config_option:
931 apr_array_make(pool, 1,
932 sizeof(svn_cmdline__config_argument_t*));
934 SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
935 SVN_ERR(svn_cmdline__parse_config_option(config_options,
942 /* The --non-interactive and --force-interactive options are mutually
944 if (non_interactive && force_interactive)
946 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
947 _("--non-interactive and --force-interactive "
948 "are mutually exclusive"));
953 subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
956 if (subcommand == NULL)
958 if (os->ind >= os->argc)
960 if (opt_baton->version)
962 /* Use the "help" subcommand to handle the "--version" option. */
963 static const svn_opt_subcommand_desc2_t pseudo_cmd =
964 { "--version", help_cmd, {0}, "",
965 {opt_version, /* must accept its own option */
968 subcommand = &pseudo_cmd;
973 SVN_ERR(help_cmd(NULL, NULL, pool));
974 *exit_code = EXIT_FAILURE;
980 first_arg = os->argv[os->ind++];
981 subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
984 if (subcommand == NULL)
986 const char *first_arg_utf8;
987 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
990 svn_cmdline_fprintf(stderr, pool,
991 _("Unknown subcommand: '%s'\n"),
993 SVN_ERR(help_cmd(NULL, NULL, pool));
994 *exit_code = EXIT_FAILURE;
1000 /* Check that the subcommand wasn't passed any inappropriate options. */
1001 for (i = 0; i < received_opts->nelts; i++)
1003 int opt_id = APR_ARRAY_IDX(received_opts, i, int);
1005 /* All commands implicitly accept --help, so just skip over this
1006 when we see it. Note that we don't want to include this option
1007 in their "accepted options" list because it would be awfully
1008 redundant to display it in every commands' help text. */
1009 if (opt_id == 'h' || opt_id == '?')
1012 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1015 const apr_getopt_option_t *badopt =
1016 svn_opt_get_option_from_code2(opt_id, svnrdump__options,
1018 svn_opt_format_option(&optstr, badopt, FALSE, pool);
1019 if (subcommand->name[0] == '-')
1020 SVN_ERR(help_cmd(NULL, NULL, pool));
1022 svn_error_clear(svn_cmdline_fprintf(
1024 _("Subcommand '%s' doesn't accept option '%s'\n"
1025 "Type 'svnrdump help %s' for usage.\n"),
1026 subcommand->name, optstr, subcommand->name));
1027 *exit_code = EXIT_FAILURE;
1028 return SVN_NO_ERROR;
1032 if (strcmp(subcommand->name, "--version") == 0)
1034 SVN_ERR(version(argv[0], opt_baton->quiet, pool));
1035 return SVN_NO_ERROR;
1038 if (strcmp(subcommand->name, "help") == 0)
1040 SVN_ERR(help_cmd(os, opt_baton, pool));
1041 return SVN_NO_ERROR;
1044 /* --trust-* can only be used with --non-interactive */
1045 if (!non_interactive)
1047 if (trust_unknown_ca || trust_cn_mismatch || trust_expired
1048 || trust_not_yet_valid || trust_other_failure)
1049 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1050 _("--trust-server-cert-failures requires "
1051 "--non-interactive"));
1054 /* Expect one more non-option argument: the repository URL. */
1055 if (os->ind != os->argc - 1)
1057 SVN_ERR(usage(argv[0], pool));
1058 *exit_code = EXIT_FAILURE;
1059 return SVN_NO_ERROR;
1063 const char *repos_url;
1065 SVN_ERR(svn_utf_cstring_to_utf8(&repos_url, os->argv[os->ind], pool));
1066 if (! svn_path_is_url(repos_url))
1068 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1069 "Target '%s' is not a URL",
1072 opt_baton->url = svn_uri_canonicalize(repos_url, pool);
1075 if (strcmp(subcommand->name, "load") == 0)
1078 * By default (no --*-interactive options given), the 'load' subcommand
1079 * is interactive unless username and password were provided on the
1080 * command line. This allows prompting for auth creds to work without
1081 * requiring users to remember to use --force-interactive.
1082 * See issue #3913, "svnrdump load is not working in interactive mode".
1084 if (!non_interactive && !force_interactive)
1085 force_interactive = (username == NULL || password == NULL);
1088 non_interactive = !svn_cmdline__be_interactive(non_interactive,
1091 SVN_ERR(init_client_context(&(opt_baton->ctx),
1101 trust_not_yet_valid,
1102 trust_other_failure,
1106 err = svn_client_open_ra_session2(&(opt_baton->session),
1107 opt_baton->url, NULL,
1108 opt_baton->ctx, pool, pool);
1110 /* Have sane opt_baton->start_revision and end_revision defaults if
1113 err = svn_ra_get_latest_revnum(opt_baton->session, &latest_revision, pool);
1115 /* Make sure any provided revisions make sense. */
1117 err = validate_and_resolve_revisions(opt_baton, latest_revision, pool);
1119 /* Dispatch the subcommand */
1121 err = (*subcommand->cmd_func)(os, opt_baton, pool);
1123 if (err && err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
1125 return svn_error_quick_wrap(err,
1126 _("Authentication failed and interactive"
1127 " prompting is disabled; see the"
1128 " --force-interactive option"));
1133 return SVN_NO_ERROR;
1137 main(int argc, const char *argv[])
1140 int exit_code = EXIT_SUCCESS;
1143 /* Initialize the app. */
1144 if (svn_cmdline_init("svnrdump", stderr) != EXIT_SUCCESS)
1145 return EXIT_FAILURE;
1147 /* Create our top-level pool. Use a separate mutexless allocator,
1148 * given this application is single threaded.
1150 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1152 err = sub_main(&exit_code, argc, argv, pool);
1154 /* Flush stdout and report if it fails. It would be flushed on exit anyway
1155 but this makes sure that output is not silently lost if it fails. */
1156 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
1160 exit_code = EXIT_FAILURE;
1161 svn_cmdline_handle_exit_error(err, NULL, "svnrdump: ");
1164 svn_pool_destroy(pool);