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_cmdline_private.h"
43 #include "private/svn_ra_private.h"
47 /*** Cancellation ***/
49 /* A flag to see if we've been cancelled by the client or not. */
50 static volatile sig_atomic_t cancelled = FALSE;
52 /* A signal handler to support cancellation. */
54 signal_handler(int signum)
56 apr_signal(signum, SIG_IGN);
60 /* Our cancellation callback. */
62 check_cancel(void *baton)
65 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
73 static svn_opt_subcommand_t dump_cmd, load_cmd;
75 enum svn_svnrdump__longopt_t
77 opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID,
83 opt_force_interactive,
85 opt_trust_server_cert,
89 #define SVN_SVNRDUMP__BASE_OPTIONS opt_config_dir, \
94 opt_trust_server_cert, \
95 opt_non_interactive, \
98 static const svn_opt_subcommand_desc2_t svnrdump__cmd_table[] =
100 { "dump", dump_cmd, { 0 },
101 N_("usage: svnrdump dump URL [-r LOWER[:UPPER]]\n\n"
102 "Dump revisions LOWER to UPPER of repository at remote URL to stdout\n"
103 "in a 'dumpfile' portable format. If only LOWER is given, dump that\n"
105 { 'r', 'q', opt_incremental, SVN_SVNRDUMP__BASE_OPTIONS } },
106 { "load", load_cmd, { 0 },
107 N_("usage: svnrdump load URL\n\n"
108 "Load a 'dumpfile' given on stdin to a repository at remote URL.\n"),
109 { 'q', SVN_SVNRDUMP__BASE_OPTIONS } },
110 { "help", 0, { "?", "h" },
111 N_("usage: svnrdump help [SUBCOMMAND...]\n\n"
112 "Describe the usage of this program or its subcommands.\n"),
114 { NULL, NULL, { 0 }, NULL, { 0 } }
117 static const apr_getopt_option_t svnrdump__options[] =
120 N_("specify revision number ARG (or X:Y range)")},
122 N_("no progress (only errors) to stderr")},
123 {"incremental", opt_incremental, 0,
124 N_("dump incrementally")},
125 {"config-dir", opt_config_dir, 1,
126 N_("read user configuration files from directory ARG")},
127 {"username", opt_auth_username, 1,
128 N_("specify a username ARG")},
129 {"password", opt_auth_password, 1,
130 N_("specify a password ARG")},
131 {"non-interactive", opt_non_interactive, 0,
132 N_("do no interactive prompting (default is to prompt\n"
134 "only if standard input is a terminal device)")},
135 {"force-interactive", opt_force_interactive, 0,
136 N_("do interactive prompting even if standard input\n"
138 "is not a terminal device")},
139 {"no-auth-cache", opt_auth_nocache, 0,
140 N_("do not cache authentication tokens")},
142 N_("display this help")},
143 {"version", opt_version, 0,
144 N_("show program version information")},
145 {"config-option", opt_config_option, 1,
146 N_("set user configuration option in the format:\n"
148 " FILE:SECTION:OPTION=[VALUE]\n"
152 " servers:global:http-library=serf")},
153 {"trust-server-cert", opt_trust_server_cert, 0,
154 N_("accept SSL server certificates from unknown\n"
156 "certificate authorities without prompting (but only\n"
158 "with '--non-interactive')") },
162 /* Baton for the RA replay session. */
163 struct replay_baton {
164 /* A backdoor ra session for fetching information. */
165 svn_ra_session_t *extra_ra_session;
167 /* The output stream */
168 svn_stream_t *stdout_stream;
170 /* Whether to be quiet. */
175 typedef struct opt_baton_t {
176 svn_client_ctx_t *ctx;
177 svn_ra_session_t *session;
180 svn_boolean_t version;
181 svn_opt_revision_t start_revision;
182 svn_opt_revision_t end_revision;
184 svn_boolean_t incremental;
187 /* Print dumpstream-formatted information about REVISION.
188 * Implements the `svn_ra_replay_revstart_callback_t' interface.
191 replay_revstart(svn_revnum_t revision,
193 const svn_delta_editor_t **editor,
195 apr_hash_t *rev_props,
198 struct replay_baton *rb = replay_baton;
199 apr_hash_t *normal_props;
200 svn_stringbuf_t *propstring;
201 svn_stream_t *stdout_stream;
202 svn_stream_t *revprop_stream;
204 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
206 /* Revision-number: 19 */
207 SVN_ERR(svn_stream_printf(stdout_stream, pool,
208 SVN_REPOS_DUMPFILE_REVISION_NUMBER
209 ": %ld\n", revision));
210 SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
211 propstring = svn_stringbuf_create_ensure(0, pool);
212 revprop_stream = svn_stream_from_stringbuf(propstring, pool);
213 SVN_ERR(svn_hash_write2(normal_props, revprop_stream, "PROPS-END", pool));
214 SVN_ERR(svn_stream_close(revprop_stream));
216 /* Prop-content-length: 13 */
217 SVN_ERR(svn_stream_printf(stdout_stream, pool,
218 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
219 ": %" APR_SIZE_T_FMT "\n", propstring->len));
221 /* Content-length: 29 */
222 SVN_ERR(svn_stream_printf(stdout_stream, pool,
223 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
224 ": %" APR_SIZE_T_FMT "\n\n", propstring->len));
227 SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
228 &(propstring->len)));
230 SVN_ERR(svn_stream_puts(stdout_stream, "\n"));
231 SVN_ERR(svn_stream_close(stdout_stream));
233 SVN_ERR(svn_rdump__get_dump_editor(editor, edit_baton, revision,
234 rb->stdout_stream, rb->extra_ra_session,
235 NULL, check_cancel, NULL, pool));
240 /* Print progress information about the dump of REVISION.
241 Implements the `svn_ra_replay_revfinish_callback_t' interface. */
243 replay_revend(svn_revnum_t revision,
245 const svn_delta_editor_t *editor,
247 apr_hash_t *rev_props,
250 /* No resources left to free. */
251 struct replay_baton *rb = replay_baton;
253 SVN_ERR(editor->close_edit(edit_baton, pool));
256 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
262 /* Print dumpstream-formatted information about REVISION.
263 * Implements the `svn_ra_replay_revstart_callback_t' interface.
266 replay_revstart_v2(svn_revnum_t revision,
268 svn_editor_t **editor,
269 apr_hash_t *rev_props,
272 struct replay_baton *rb = replay_baton;
273 apr_hash_t *normal_props;
274 svn_stringbuf_t *propstring;
275 svn_stream_t *stdout_stream;
276 svn_stream_t *revprop_stream;
278 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
280 /* Revision-number: 19 */
281 SVN_ERR(svn_stream_printf(stdout_stream, pool,
282 SVN_REPOS_DUMPFILE_REVISION_NUMBER
283 ": %ld\n", revision));
284 SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
285 propstring = svn_stringbuf_create_ensure(0, pool);
286 revprop_stream = svn_stream_from_stringbuf(propstring, pool);
287 SVN_ERR(svn_hash_write2(normal_props, revprop_stream, "PROPS-END", pool));
288 SVN_ERR(svn_stream_close(revprop_stream));
290 /* Prop-content-length: 13 */
291 SVN_ERR(svn_stream_printf(stdout_stream, pool,
292 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
293 ": %" APR_SIZE_T_FMT "\n", propstring->len));
295 /* Content-length: 29 */
296 SVN_ERR(svn_stream_printf(stdout_stream, pool,
297 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
298 ": %" APR_SIZE_T_FMT "\n\n", propstring->len));
301 SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
302 &(propstring->len)));
304 SVN_ERR(svn_stream_puts(stdout_stream, "\n"));
305 SVN_ERR(svn_stream_close(stdout_stream));
307 SVN_ERR(svn_rdump__get_dump_editor_v2(editor, revision,
309 rb->extra_ra_session,
310 NULL, check_cancel, NULL, pool, pool));
315 /* Print progress information about the dump of REVISION.
316 Implements the `svn_ra_replay_revfinish_callback_t' interface. */
318 replay_revend_v2(svn_revnum_t revision,
320 svn_editor_t *editor,
321 apr_hash_t *rev_props,
324 /* No resources left to free. */
325 struct replay_baton *rb = replay_baton;
327 SVN_ERR(svn_editor_complete(editor));
330 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
336 /* Initialize the RA layer, and set *CTX to a new client context baton
337 * allocated from POOL. Use CONFIG_DIR and pass USERNAME, PASSWORD,
338 * CONFIG_DIR and NO_AUTH_CACHE to initialize the authorization baton.
339 * CONFIG_OPTIONS (if not NULL) is a list of configuration overrides.
340 * REPOS_URL is used to fiddle with server-specific configuration
344 init_client_context(svn_client_ctx_t **ctx_p,
345 svn_boolean_t non_interactive,
346 const char *username,
347 const char *password,
348 const char *config_dir,
349 const char *repos_url,
350 svn_boolean_t no_auth_cache,
351 svn_boolean_t trust_server_cert,
352 apr_array_header_t *config_options,
355 svn_client_ctx_t *ctx = NULL;
356 svn_config_t *cfg_config, *cfg_servers;
358 SVN_ERR(svn_ra_initialize(pool));
360 SVN_ERR(svn_config_ensure(config_dir, pool));
361 SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
363 SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
366 SVN_ERR(svn_cmdline__apply_config_options(ctx->config, config_options,
367 "svnrdump: ", "--config-option"));
369 cfg_config = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG);
371 /* ### FIXME: This is a hack to work around the fact that our dump
372 ### editor simply can't handle the way ra_serf violates the
373 ### editor v1 drive ordering requirements.
375 ### We'll override both the global value and server-specific one
376 ### for the 'http-bulk-updates' and 'http-max-connections'
377 ### options in order to get ra_serf to try a bulk-update if the
378 ### server will allow it, or at least try to limit all its
379 ### auxiliary GETs/PROPFINDs to happening (well-ordered) on a
380 ### single server connection.
382 ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
384 cfg_servers = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_SERVERS);
385 svn_config_set_bool(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
386 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
387 svn_config_set_int64(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
388 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
392 apr_uri_t parsed_url;
394 status = apr_uri_parse(pool, repos_url, &parsed_url);
397 const char *server_group;
399 server_group = svn_config_find_group(cfg_servers, parsed_url.hostname,
400 SVN_CONFIG_SECTION_GROUPS, pool);
403 svn_config_set_bool(cfg_servers, server_group,
404 SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
405 svn_config_set_int64(cfg_servers, server_group,
406 SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
411 /* Set up our cancellation support. */
412 ctx->cancel_func = check_cancel;
414 /* Default authentication providers for non-interactive use */
415 SVN_ERR(svn_cmdline_create_auth_baton(&(ctx->auth_baton), non_interactive,
416 username, password, config_dir,
417 no_auth_cache, trust_server_cert,
418 cfg_config, ctx->cancel_func,
419 ctx->cancel_baton, pool));
424 /* Print a revision record header for REVISION to STDOUT_STREAM. Use
425 * SESSION to contact the repository for revision properties and
429 dump_revision_header(svn_ra_session_t *session,
430 svn_stream_t *stdout_stream,
431 svn_revnum_t revision,
434 apr_hash_t *prophash;
435 svn_stringbuf_t *propstring;
436 svn_stream_t *propstream;
438 SVN_ERR(svn_stream_printf(stdout_stream, pool,
439 SVN_REPOS_DUMPFILE_REVISION_NUMBER
440 ": %ld\n", revision));
442 prophash = apr_hash_make(pool);
443 propstring = svn_stringbuf_create_empty(pool);
444 SVN_ERR(svn_ra_rev_proplist(session, revision, &prophash, pool));
446 propstream = svn_stream_from_stringbuf(propstring, pool);
447 SVN_ERR(svn_hash_write2(prophash, propstream, "PROPS-END", pool));
448 SVN_ERR(svn_stream_close(propstream));
450 /* Property-content-length: 14; Content-length: 14 */
451 SVN_ERR(svn_stream_printf(stdout_stream, pool,
452 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
453 ": %" APR_SIZE_T_FMT "\n",
455 SVN_ERR(svn_stream_printf(stdout_stream, pool,
456 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
457 ": %" APR_SIZE_T_FMT "\n\n",
460 SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
461 &(propstring->len)));
462 SVN_ERR(svn_stream_puts(stdout_stream, "\n"));
468 dump_initial_full_revision(svn_ra_session_t *session,
469 svn_ra_session_t *extra_ra_session,
470 svn_stream_t *stdout_stream,
471 svn_revnum_t revision,
475 const svn_ra_reporter3_t *reporter;
477 const svn_delta_editor_t *dump_editor;
479 const char *session_url, *source_relpath;
481 /* Determine whether we're dumping the repository root URL or some
482 child thereof. If we're dumping a subtree of the repository
483 rather than the root, we have to jump through some hoops to make
484 our update-driven dump generation work the way a replay-driven
487 See http://subversion.tigris.org/issues/show_bug.cgi?id=4101
489 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
490 SVN_ERR(svn_ra_get_path_relative_to_root(session, &source_relpath,
493 /* Start with a revision record header. */
494 SVN_ERR(dump_revision_header(session, stdout_stream, revision, pool));
496 /* Then, we'll drive the dump editor with what would look like a
497 full checkout of the repository as it looked in START_REVISION.
498 We do this by manufacturing a basic 'report' to the update
499 reporter, telling it that we have nothing to start with. The
500 delta between nothing and everything-at-REV is, effectively, a
502 SVN_ERR(svn_rdump__get_dump_editor(&dump_editor, &dump_baton, revision,
503 stdout_stream, extra_ra_session,
504 source_relpath, check_cancel, NULL, pool));
505 SVN_ERR(svn_ra_do_update3(session, &reporter, &report_baton, revision,
506 "", svn_depth_infinity, FALSE, FALSE,
507 dump_editor, dump_baton, pool, pool));
508 SVN_ERR(reporter->set_path(report_baton, "", revision,
509 svn_depth_infinity, TRUE, NULL, pool));
510 SVN_ERR(reporter->finish_report(report_baton, pool));
512 /* All finished with START_REVISION! */
514 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
520 /* Replay revisions START_REVISION thru END_REVISION (inclusive) of
521 * the repository URL at which SESSION is rooted, using callbacks
522 * which generate Subversion repository dumpstreams describing the
523 * changes made in those revisions. If QUIET is set, don't generate
527 replay_revisions(svn_ra_session_t *session,
528 svn_ra_session_t *extra_ra_session,
529 svn_revnum_t start_revision,
530 svn_revnum_t end_revision,
532 svn_boolean_t incremental,
535 struct replay_baton *replay_baton;
537 svn_stream_t *stdout_stream;
539 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
541 replay_baton = apr_pcalloc(pool, sizeof(*replay_baton));
542 replay_baton->stdout_stream = stdout_stream;
543 replay_baton->extra_ra_session = extra_ra_session;
544 replay_baton->quiet = quiet;
546 /* Write the magic header and UUID */
547 SVN_ERR(svn_stream_printf(stdout_stream, pool,
548 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
549 SVN_REPOS_DUMPFILE_FORMAT_VERSION));
550 SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool));
551 SVN_ERR(svn_stream_printf(stdout_stream, pool,
552 SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
554 /* Fake revision 0 if necessary */
555 if (start_revision == 0)
557 SVN_ERR(dump_revision_header(session, stdout_stream,
558 start_revision, pool));
560 /* Revision 0 has no tree changes, so we're done. */
562 SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
566 /* If our first revision is 0, we can treat this as an
571 /* If what remains to be dumped is not going to be dumped
572 incrementally, then dump the first revision in full. */
575 SVN_ERR(dump_initial_full_revision(session, extra_ra_session,
576 stdout_stream, start_revision,
581 /* If there are still revisions left to be dumped, do so. */
582 if (start_revision <= end_revision)
585 SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision,
586 0, TRUE, replay_revstart, replay_revend,
587 replay_baton, pool));
589 SVN_ERR(svn_ra__replay_range_ev2(session, start_revision, end_revision,
590 0, TRUE, replay_revstart_v2,
591 replay_revend_v2, replay_baton,
592 NULL, NULL, NULL, NULL, pool));
596 SVN_ERR(svn_stream_close(stdout_stream));
600 /* Read a dumpstream from stdin, and use it to feed a loader capable
601 * of transmitting that information to the repository located at URL
602 * (to which SESSION has been opened). AUX_SESSION is a second RA
603 * session opened to the same URL for performing auxiliary out-of-band
607 load_revisions(svn_ra_session_t *session,
608 svn_ra_session_t *aux_session,
613 apr_file_t *stdin_file;
614 svn_stream_t *stdin_stream;
616 apr_file_open_stdin(&stdin_file, pool);
617 stdin_stream = svn_stream_from_aprfile2(stdin_file, FALSE, pool);
619 SVN_ERR(svn_rdump__load_dumpstream(stdin_stream, session, aux_session,
620 quiet, check_cancel, NULL, pool));
622 SVN_ERR(svn_stream_close(stdin_stream));
627 /* Return a program name for this program, the basename of the path
628 * represented by PROGNAME if not NULL; use "svnrdump" otherwise.
631 ensure_appname(const char *progname,
637 return svn_dirent_basename(svn_dirent_internal_style(progname, pool), NULL);
640 /* Print a simple usage string. */
642 usage(const char *progname,
645 return svn_cmdline_fprintf(stderr, pool,
646 _("Type '%s help' for usage.\n"),
647 ensure_appname(progname, pool));
650 /* Print information about the version of this program and dependent
654 version(const char *progname,
658 svn_stringbuf_t *version_footer =
659 svn_stringbuf_create(_("The following repository access (RA) modules "
660 "are available:\n\n"),
663 SVN_ERR(svn_ra_print_modules(version_footer, pool));
664 return svn_opt_print_help4(NULL, ensure_appname(progname, pool),
665 TRUE, quiet, FALSE, version_footer->data,
666 NULL, NULL, NULL, NULL, NULL, pool);
670 /* A statement macro, similar to @c SVN_ERR, but returns an integer.
671 * Evaluate @a expr. If it yields an error, handle that error and
672 * return @c EXIT_FAILURE.
674 #define SVNRDUMP_ERR(expr) \
677 svn_error_t *svn_err__temp = (expr); \
680 svn_handle_error2(svn_err__temp, stderr, FALSE, "svnrdump: "); \
681 svn_error_clear(svn_err__temp); \
682 return EXIT_FAILURE; \
687 /* Handle the "dump" subcommand. Implements `svn_opt_subcommand_t'. */
689 dump_cmd(apr_getopt_t *os,
693 opt_baton_t *opt_baton = baton;
694 svn_ra_session_t *extra_ra_session;
695 const char *repos_root;
697 SVN_ERR(svn_client_open_ra_session2(&extra_ra_session,
698 opt_baton->url, NULL,
699 opt_baton->ctx, pool, pool));
700 SVN_ERR(svn_ra_get_repos_root2(extra_ra_session, &repos_root, pool));
701 SVN_ERR(svn_ra_reparent(extra_ra_session, repos_root, pool));
703 return replay_revisions(opt_baton->session, extra_ra_session,
704 opt_baton->start_revision.value.number,
705 opt_baton->end_revision.value.number,
706 opt_baton->quiet, opt_baton->incremental, pool);
709 /* Handle the "load" subcommand. Implements `svn_opt_subcommand_t'. */
711 load_cmd(apr_getopt_t *os,
715 opt_baton_t *opt_baton = baton;
716 svn_ra_session_t *aux_session;
718 SVN_ERR(svn_client_open_ra_session2(&aux_session, opt_baton->url, NULL,
719 opt_baton->ctx, pool, pool));
720 return load_revisions(opt_baton->session, aux_session, opt_baton->url,
721 opt_baton->quiet, pool);
724 /* Handle the "help" subcommand. Implements `svn_opt_subcommand_t'. */
726 help_cmd(apr_getopt_t *os,
731 _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n"
732 "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n"
733 "Type 'svnrdump --version' to see the program version and RA modules.\n"
735 "Available subcommands:\n");
737 return svn_opt_print_help4(os, "svnrdump", FALSE, FALSE, FALSE, NULL,
738 header, svnrdump__cmd_table, svnrdump__options,
742 /* Examine the OPT_BATON's 'start_revision' and 'end_revision'
743 * members, making sure that they make sense (in general, and as
744 * applied to a repository whose current youngest revision is
748 validate_and_resolve_revisions(opt_baton_t *opt_baton,
749 svn_revnum_t latest_revision,
752 svn_revnum_t provided_start_rev = SVN_INVALID_REVNUM;
754 /* Ensure that the start revision is something we can handle. We
755 want a number >= 0. If unspecified, make it a number (r0) --
756 anything else is bogus. */
757 if (opt_baton->start_revision.kind == svn_opt_revision_number)
759 provided_start_rev = opt_baton->start_revision.value.number;
761 else if (opt_baton->start_revision.kind == svn_opt_revision_head)
763 opt_baton->start_revision.kind = svn_opt_revision_number;
764 opt_baton->start_revision.value.number = latest_revision;
766 else if (opt_baton->start_revision.kind == svn_opt_revision_unspecified)
768 opt_baton->start_revision.kind = svn_opt_revision_number;
769 opt_baton->start_revision.value.number = 0;
772 if (opt_baton->start_revision.kind != svn_opt_revision_number)
774 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
775 _("Unsupported revision specifier used; use "
776 "only integer values or 'HEAD'"));
779 if ((opt_baton->start_revision.value.number < 0) ||
780 (opt_baton->start_revision.value.number > latest_revision))
782 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
783 _("Revision '%ld' does not exist"),
784 opt_baton->start_revision.value.number);
787 /* Ensure that the end revision is something we can handle. We want
788 a number <= the youngest, and > the start revision. If
789 unspecified, make it a number (start_revision + 1 if that was
790 specified, the youngest revision in the repository otherwise) --
791 anything else is bogus. */
792 if (opt_baton->end_revision.kind == svn_opt_revision_unspecified)
794 opt_baton->end_revision.kind = svn_opt_revision_number;
795 if (SVN_IS_VALID_REVNUM(provided_start_rev))
796 opt_baton->end_revision.value.number = provided_start_rev;
798 opt_baton->end_revision.value.number = latest_revision;
800 else if (opt_baton->end_revision.kind == svn_opt_revision_head)
802 opt_baton->end_revision.kind = svn_opt_revision_number;
803 opt_baton->end_revision.value.number = latest_revision;
806 if (opt_baton->end_revision.kind != svn_opt_revision_number)
808 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
809 _("Unsupported revision specifier used; use "
810 "only integer values or 'HEAD'"));
813 if ((opt_baton->end_revision.value.number < 0) ||
814 (opt_baton->end_revision.value.number > latest_revision))
816 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
817 _("Revision '%ld' does not exist"),
818 opt_baton->end_revision.value.number);
821 /* Finally, make sure that the end revision is younger than the
822 start revision. We don't do "backwards" 'round here. */
823 if (opt_baton->end_revision.value.number <
824 opt_baton->start_revision.value.number)
826 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
827 _("LOWER revision cannot be greater than "
828 "UPPER revision; consider reversing your "
835 main(int argc, const char **argv)
837 svn_error_t *err = SVN_NO_ERROR;
838 const svn_opt_subcommand_desc2_t *subcommand = NULL;
839 opt_baton_t *opt_baton;
840 svn_revnum_t latest_revision = SVN_INVALID_REVNUM;
841 apr_pool_t *pool = NULL;
842 const char *config_dir = NULL;
843 const char *username = NULL;
844 const char *password = NULL;
845 svn_boolean_t no_auth_cache = FALSE;
846 svn_boolean_t trust_server_cert = FALSE;
847 svn_boolean_t non_interactive = FALSE;
848 svn_boolean_t force_interactive = FALSE;
849 apr_array_header_t *config_options = NULL;
851 const char *first_arg;
852 apr_array_header_t *received_opts;
855 if (svn_cmdline_init ("svnrdump", stderr) != EXIT_SUCCESS)
858 /* Create our top-level pool. Use a separate mutexless allocator,
859 * given this application is single threaded.
861 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
863 opt_baton = apr_pcalloc(pool, sizeof(*opt_baton));
864 opt_baton->start_revision.kind = svn_opt_revision_unspecified;
865 opt_baton->end_revision.kind = svn_opt_revision_unspecified;
866 opt_baton->url = NULL;
868 SVNRDUMP_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
870 os->interleave = TRUE; /* Options and arguments can be interleaved */
872 /* Set up our cancellation support. */
873 apr_signal(SIGINT, signal_handler);
875 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
876 apr_signal(SIGBREAK, signal_handler);
879 apr_signal(SIGHUP, signal_handler);
882 apr_signal(SIGTERM, signal_handler);
885 /* Disable SIGPIPE generation for the platforms that have it. */
886 apr_signal(SIGPIPE, SIG_IGN);
889 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
890 * working with large files when compiled against an APR that doesn't have
891 * large file support will crash the program, which is uncool. */
892 apr_signal(SIGXFSZ, SIG_IGN);
895 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
901 apr_status_t status = apr_getopt_long(os, svnrdump__options, &opt,
904 if (APR_STATUS_IS_EOF(status))
906 if (status != APR_SUCCESS)
908 SVNRDUMP_ERR(usage(argv[0], pool));
912 /* Stash the option code in an array before parsing it. */
913 APR_ARRAY_PUSH(received_opts, int) = opt;
919 /* Make sure we've not seen -r already. */
920 if (opt_baton->start_revision.kind != svn_opt_revision_unspecified)
922 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
923 _("Multiple revision arguments "
924 "encountered; try '-r N:M' instead "
926 return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
928 /* Parse the -r argument. */
929 if (svn_opt_parse_revision(&(opt_baton->start_revision),
930 &(opt_baton->end_revision),
933 const char *utf8_opt_arg;
934 err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
936 err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
937 _("Syntax error in revision "
938 "argument '%s'"), utf8_opt_arg);
939 return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
944 opt_baton->quiet = TRUE;
947 config_dir = opt_arg;
950 opt_baton->version = TRUE;
953 opt_baton->help = TRUE;
955 case opt_auth_username:
956 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool));
958 case opt_auth_password:
959 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool));
961 case opt_auth_nocache:
962 no_auth_cache = TRUE;
964 case opt_non_interactive:
965 non_interactive = TRUE;
967 case opt_force_interactive:
968 force_interactive = TRUE;
970 case opt_incremental:
971 opt_baton->incremental = TRUE;
973 case opt_trust_server_cert:
974 trust_server_cert = TRUE;
976 case opt_config_option:
979 apr_array_make(pool, 1,
980 sizeof(svn_cmdline__config_argument_t*));
982 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
983 SVNRDUMP_ERR(svn_cmdline__parse_config_option(config_options,
988 /* The --non-interactive and --force-interactive options are mutually
990 if (non_interactive && force_interactive)
992 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
993 _("--non-interactive and --force-interactive "
994 "are mutually exclusive"));
995 return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
1000 subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
1003 if (subcommand == NULL)
1005 if (os->ind >= os->argc)
1007 if (opt_baton->version)
1009 /* Use the "help" subcommand to handle the "--version" option. */
1010 static const svn_opt_subcommand_desc2_t pseudo_cmd =
1011 { "--version", help_cmd, {0}, "",
1012 {opt_version, /* must accept its own option */
1015 subcommand = &pseudo_cmd;
1020 SVNRDUMP_ERR(help_cmd(NULL, NULL, pool));
1021 svn_pool_destroy(pool);
1027 first_arg = os->argv[os->ind++];
1028 subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
1031 if (subcommand == NULL)
1033 const char *first_arg_utf8;
1034 err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, pool);
1036 return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
1038 svn_cmdline_fprintf(stderr, pool,
1039 _("Unknown subcommand: '%s'\n"),
1041 SVNRDUMP_ERR(help_cmd(NULL, NULL, pool));
1042 svn_pool_destroy(pool);
1048 /* Check that the subcommand wasn't passed any inappropriate options. */
1049 for (i = 0; i < received_opts->nelts; i++)
1051 int opt_id = APR_ARRAY_IDX(received_opts, i, int);
1053 /* All commands implicitly accept --help, so just skip over this
1054 when we see it. Note that we don't want to include this option
1055 in their "accepted options" list because it would be awfully
1056 redundant to display it in every commands' help text. */
1057 if (opt_id == 'h' || opt_id == '?')
1060 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1063 const apr_getopt_option_t *badopt =
1064 svn_opt_get_option_from_code2(opt_id, svnrdump__options,
1066 svn_opt_format_option(&optstr, badopt, FALSE, pool);
1067 if (subcommand->name[0] == '-')
1068 SVN_INT_ERR(help_cmd(NULL, NULL, pool));
1070 svn_error_clear(svn_cmdline_fprintf(
1072 _("Subcommand '%s' doesn't accept option '%s'\n"
1073 "Type 'svnrdump help %s' for usage.\n"),
1074 subcommand->name, optstr, subcommand->name));
1075 svn_pool_destroy(pool);
1076 return EXIT_FAILURE;
1080 if (subcommand && strcmp(subcommand->name, "--version") == 0)
1082 SVNRDUMP_ERR(version(argv[0], opt_baton->quiet, pool));
1083 svn_pool_destroy(pool);
1087 if (subcommand && strcmp(subcommand->name, "help") == 0)
1089 SVNRDUMP_ERR(help_cmd(os, opt_baton, pool));
1090 svn_pool_destroy(pool);
1094 /* --trust-server-cert can only be used with --non-interactive */
1095 if (trust_server_cert && !non_interactive)
1097 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1098 _("--trust-server-cert requires "
1099 "--non-interactive"));
1100 return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
1103 /* Expect one more non-option argument: the repository URL. */
1104 if (os->ind != os->argc - 1)
1106 SVNRDUMP_ERR(usage(argv[0], pool));
1107 svn_pool_destroy(pool);
1112 const char *repos_url;
1114 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&repos_url,
1115 os->argv[os->ind], pool));
1116 if (! svn_path_is_url(repos_url))
1118 err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1119 "Target '%s' is not a URL",
1122 svn_pool_destroy(pool);
1125 opt_baton->url = svn_uri_canonicalize(repos_url, pool);
1128 if (strcmp(subcommand->name, "load") == 0)
1131 * By default (no --*-interactive options given), the 'load' subcommand
1132 * is interactive unless username and password were provided on the
1133 * command line. This allows prompting for auth creds to work without
1134 * requiring users to remember to use --force-interactive.
1135 * See issue #3913, "svnrdump load is not working in interactive mode".
1137 if (!non_interactive && !force_interactive)
1138 force_interactive = (username == NULL || password == NULL);
1141 non_interactive = !svn_cmdline__be_interactive(non_interactive,
1144 SVNRDUMP_ERR(init_client_context(&(opt_baton->ctx),
1155 err = svn_client_open_ra_session2(&(opt_baton->session),
1156 opt_baton->url, NULL,
1157 opt_baton->ctx, pool, pool);
1159 /* Have sane opt_baton->start_revision and end_revision defaults if
1162 err = svn_ra_get_latest_revnum(opt_baton->session, &latest_revision, pool);
1164 /* Make sure any provided revisions make sense. */
1166 err = validate_and_resolve_revisions(opt_baton, latest_revision, pool);
1168 /* Dispatch the subcommand */
1170 err = (*subcommand->cmd_func)(os, opt_baton, pool);
1172 if (err && err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
1174 err = svn_error_quick_wrap(err,
1175 _("Authentication failed and interactive"
1176 " prompting is disabled; see the"
1177 " --force-interactive option"));
1182 svn_pool_destroy(pool);
1184 return EXIT_SUCCESS;