summaryrefslogtreecommitdiffstats
path: root/subversion/svnrdump/svnrdump.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svnrdump/svnrdump.c')
-rw-r--r--subversion/svnrdump/svnrdump.c1185
1 files changed, 1185 insertions, 0 deletions
diff --git a/subversion/svnrdump/svnrdump.c b/subversion/svnrdump/svnrdump.c
new file mode 100644
index 0000000..6bf409c
--- /dev/null
+++ b/subversion/svnrdump/svnrdump.c
@@ -0,0 +1,1185 @@
+/*
+ * svnrdump.c: Produce a dumpfile of a local or remote repository
+ * without touching the filesystem, but for temporary files.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <apr_signal.h>
+#include <apr_uri.h>
+
+#include "svn_pools.h"
+#include "svn_cmdline.h"
+#include "svn_client.h"
+#include "svn_hash.h"
+#include "svn_ra.h"
+#include "svn_repos.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_private_config.h"
+#include "svn_string.h"
+#include "svn_props.h"
+
+#include "svnrdump.h"
+
+#include "private/svn_cmdline_private.h"
+#include "private/svn_ra_private.h"
+
+
+
+/*** Cancellation ***/
+
+/* A flag to see if we've been cancelled by the client or not. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+/* A signal handler to support cancellation. */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+/* Our cancellation callback. */
+static svn_error_t *
+check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+
+
+static svn_opt_subcommand_t dump_cmd, load_cmd;
+
+enum svn_svnrdump__longopt_t
+ {
+ opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID,
+ opt_config_option,
+ opt_auth_username,
+ opt_auth_password,
+ opt_auth_nocache,
+ opt_non_interactive,
+ opt_force_interactive,
+ opt_incremental,
+ opt_trust_server_cert,
+ opt_version
+ };
+
+#define SVN_SVNRDUMP__BASE_OPTIONS opt_config_dir, \
+ opt_config_option, \
+ opt_auth_username, \
+ opt_auth_password, \
+ opt_auth_nocache, \
+ opt_trust_server_cert, \
+ opt_non_interactive, \
+ opt_force_interactive
+
+static const svn_opt_subcommand_desc2_t svnrdump__cmd_table[] =
+{
+ { "dump", dump_cmd, { 0 },
+ N_("usage: svnrdump dump URL [-r LOWER[:UPPER]]\n\n"
+ "Dump revisions LOWER to UPPER of repository at remote URL to stdout\n"
+ "in a 'dumpfile' portable format. If only LOWER is given, dump that\n"
+ "one revision.\n"),
+ { 'r', 'q', opt_incremental, SVN_SVNRDUMP__BASE_OPTIONS } },
+ { "load", load_cmd, { 0 },
+ N_("usage: svnrdump load URL\n\n"
+ "Load a 'dumpfile' given on stdin to a repository at remote URL.\n"),
+ { 'q', SVN_SVNRDUMP__BASE_OPTIONS } },
+ { "help", 0, { "?", "h" },
+ N_("usage: svnrdump help [SUBCOMMAND...]\n\n"
+ "Describe the usage of this program or its subcommands.\n"),
+ { 0 } },
+ { NULL, NULL, { 0 }, NULL, { 0 } }
+};
+
+static const apr_getopt_option_t svnrdump__options[] =
+ {
+ {"revision", 'r', 1,
+ N_("specify revision number ARG (or X:Y range)")},
+ {"quiet", 'q', 0,
+ N_("no progress (only errors) to stderr")},
+ {"incremental", opt_incremental, 0,
+ N_("dump incrementally")},
+ {"config-dir", opt_config_dir, 1,
+ N_("read user configuration files from directory ARG")},
+ {"username", opt_auth_username, 1,
+ N_("specify a username ARG")},
+ {"password", opt_auth_password, 1,
+ N_("specify a password ARG")},
+ {"non-interactive", opt_non_interactive, 0,
+ N_("do no interactive prompting (default is to prompt\n"
+ " "
+ "only if standard input is a terminal device)")},
+ {"force-interactive", opt_force_interactive, 0,
+ N_("do interactive prompting even if standard input\n"
+ " "
+ "is not a terminal device")},
+ {"no-auth-cache", opt_auth_nocache, 0,
+ N_("do not cache authentication tokens")},
+ {"help", 'h', 0,
+ N_("display this help")},
+ {"version", opt_version, 0,
+ N_("show program version information")},
+ {"config-option", opt_config_option, 1,
+ N_("set user configuration option in the format:\n"
+ " "
+ " FILE:SECTION:OPTION=[VALUE]\n"
+ " "
+ "For example:\n"
+ " "
+ " servers:global:http-library=serf")},
+ {"trust-server-cert", opt_trust_server_cert, 0,
+ N_("accept SSL server certificates from unknown\n"
+ " "
+ "certificate authorities without prompting (but only\n"
+ " "
+ "with '--non-interactive')") },
+ {0, 0, 0, 0}
+ };
+
+/* Baton for the RA replay session. */
+struct replay_baton {
+ /* A backdoor ra session for fetching information. */
+ svn_ra_session_t *extra_ra_session;
+
+ /* The output stream */
+ svn_stream_t *stdout_stream;
+
+ /* Whether to be quiet. */
+ svn_boolean_t quiet;
+};
+
+/* Option set */
+typedef struct opt_baton_t {
+ svn_client_ctx_t *ctx;
+ svn_ra_session_t *session;
+ const char *url;
+ svn_boolean_t help;
+ svn_boolean_t version;
+ svn_opt_revision_t start_revision;
+ svn_opt_revision_t end_revision;
+ svn_boolean_t quiet;
+ svn_boolean_t incremental;
+} opt_baton_t;
+
+/* Print dumpstream-formatted information about REVISION.
+ * Implements the `svn_ra_replay_revstart_callback_t' interface.
+ */
+static svn_error_t *
+replay_revstart(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t **editor,
+ void **edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ struct replay_baton *rb = replay_baton;
+ apr_hash_t *normal_props;
+ svn_stringbuf_t *propstring;
+ svn_stream_t *stdout_stream;
+ svn_stream_t *revprop_stream;
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ /* Revision-number: 19 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER
+ ": %ld\n", revision));
+ SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
+ propstring = svn_stringbuf_create_ensure(0, pool);
+ revprop_stream = svn_stream_from_stringbuf(propstring, pool);
+ SVN_ERR(svn_hash_write2(normal_props, revprop_stream, "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(revprop_stream));
+
+ /* Prop-content-length: 13 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n", propstring->len));
+
+ /* Content-length: 29 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n", propstring->len));
+
+ /* Property data. */
+ SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
+ &(propstring->len)));
+
+ SVN_ERR(svn_stream_puts(stdout_stream, "\n"));
+ SVN_ERR(svn_stream_close(stdout_stream));
+
+ SVN_ERR(svn_rdump__get_dump_editor(editor, edit_baton, revision,
+ rb->stdout_stream, rb->extra_ra_session,
+ NULL, check_cancel, NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Print progress information about the dump of REVISION.
+ Implements the `svn_ra_replay_revfinish_callback_t' interface. */
+static svn_error_t *
+replay_revend(svn_revnum_t revision,
+ void *replay_baton,
+ const svn_delta_editor_t *editor,
+ void *edit_baton,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ /* No resources left to free. */
+ struct replay_baton *rb = replay_baton;
+
+ SVN_ERR(editor->close_edit(edit_baton, pool));
+
+ if (! rb->quiet)
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
+ revision));
+ return SVN_NO_ERROR;
+}
+
+#ifdef USE_EV2_IMPL
+/* Print dumpstream-formatted information about REVISION.
+ * Implements the `svn_ra_replay_revstart_callback_t' interface.
+ */
+static svn_error_t *
+replay_revstart_v2(svn_revnum_t revision,
+ void *replay_baton,
+ svn_editor_t **editor,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ struct replay_baton *rb = replay_baton;
+ apr_hash_t *normal_props;
+ svn_stringbuf_t *propstring;
+ svn_stream_t *stdout_stream;
+ svn_stream_t *revprop_stream;
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ /* Revision-number: 19 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER
+ ": %ld\n", revision));
+ SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
+ propstring = svn_stringbuf_create_ensure(0, pool);
+ revprop_stream = svn_stream_from_stringbuf(propstring, pool);
+ SVN_ERR(svn_hash_write2(normal_props, revprop_stream, "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(revprop_stream));
+
+ /* Prop-content-length: 13 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n", propstring->len));
+
+ /* Content-length: 29 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n", propstring->len));
+
+ /* Property data. */
+ SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
+ &(propstring->len)));
+
+ SVN_ERR(svn_stream_puts(stdout_stream, "\n"));
+ SVN_ERR(svn_stream_close(stdout_stream));
+
+ SVN_ERR(svn_rdump__get_dump_editor_v2(editor, revision,
+ rb->stdout_stream,
+ rb->extra_ra_session,
+ NULL, check_cancel, NULL, pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Print progress information about the dump of REVISION.
+ Implements the `svn_ra_replay_revfinish_callback_t' interface. */
+static svn_error_t *
+replay_revend_v2(svn_revnum_t revision,
+ void *replay_baton,
+ svn_editor_t *editor,
+ apr_hash_t *rev_props,
+ apr_pool_t *pool)
+{
+ /* No resources left to free. */
+ struct replay_baton *rb = replay_baton;
+
+ SVN_ERR(svn_editor_complete(editor));
+
+ if (! rb->quiet)
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
+ revision));
+ return SVN_NO_ERROR;
+}
+#endif
+
+/* Initialize the RA layer, and set *CTX to a new client context baton
+ * allocated from POOL. Use CONFIG_DIR and pass USERNAME, PASSWORD,
+ * CONFIG_DIR and NO_AUTH_CACHE to initialize the authorization baton.
+ * CONFIG_OPTIONS (if not NULL) is a list of configuration overrides.
+ * REPOS_URL is used to fiddle with server-specific configuration
+ * options.
+ */
+static svn_error_t *
+init_client_context(svn_client_ctx_t **ctx_p,
+ svn_boolean_t non_interactive,
+ const char *username,
+ const char *password,
+ const char *config_dir,
+ const char *repos_url,
+ svn_boolean_t no_auth_cache,
+ svn_boolean_t trust_server_cert,
+ apr_array_header_t *config_options,
+ apr_pool_t *pool)
+{
+ svn_client_ctx_t *ctx = NULL;
+ svn_config_t *cfg_config, *cfg_servers;
+
+ SVN_ERR(svn_ra_initialize(pool));
+
+ SVN_ERR(svn_config_ensure(config_dir, pool));
+ SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
+
+ SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
+
+ if (config_options)
+ SVN_ERR(svn_cmdline__apply_config_options(ctx->config, config_options,
+ "svnrdump: ", "--config-option"));
+
+ cfg_config = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG);
+
+ /* ### FIXME: This is a hack to work around the fact that our dump
+ ### editor simply can't handle the way ra_serf violates the
+ ### editor v1 drive ordering requirements.
+ ###
+ ### We'll override both the global value and server-specific one
+ ### for the 'http-bulk-updates' and 'http-max-connections'
+ ### options in order to get ra_serf to try a bulk-update if the
+ ### server will allow it, or at least try to limit all its
+ ### auxiliary GETs/PROPFINDs to happening (well-ordered) on a
+ ### single server connection.
+ ###
+ ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
+ */
+ cfg_servers = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_SERVERS);
+ svn_config_set_bool(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
+ svn_config_set_int64(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
+ if (cfg_servers)
+ {
+ apr_status_t status;
+ apr_uri_t parsed_url;
+
+ status = apr_uri_parse(pool, repos_url, &parsed_url);
+ if (! status)
+ {
+ const char *server_group;
+
+ server_group = svn_config_find_group(cfg_servers, parsed_url.hostname,
+ SVN_CONFIG_SECTION_GROUPS, pool);
+ if (server_group)
+ {
+ svn_config_set_bool(cfg_servers, server_group,
+ SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
+ svn_config_set_int64(cfg_servers, server_group,
+ SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
+ }
+ }
+ }
+
+ /* Set up our cancellation support. */
+ ctx->cancel_func = check_cancel;
+
+ /* Default authentication providers for non-interactive use */
+ SVN_ERR(svn_cmdline_create_auth_baton(&(ctx->auth_baton), non_interactive,
+ username, password, config_dir,
+ no_auth_cache, trust_server_cert,
+ cfg_config, ctx->cancel_func,
+ ctx->cancel_baton, pool));
+ *ctx_p = ctx;
+ return SVN_NO_ERROR;
+}
+
+/* Print a revision record header for REVISION to STDOUT_STREAM. Use
+ * SESSION to contact the repository for revision properties and
+ * such.
+ */
+static svn_error_t *
+dump_revision_header(svn_ra_session_t *session,
+ svn_stream_t *stdout_stream,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ apr_hash_t *prophash;
+ svn_stringbuf_t *propstring;
+ svn_stream_t *propstream;
+
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_REVISION_NUMBER
+ ": %ld\n", revision));
+
+ prophash = apr_hash_make(pool);
+ propstring = svn_stringbuf_create_empty(pool);
+ SVN_ERR(svn_ra_rev_proplist(session, revision, &prophash, pool));
+
+ propstream = svn_stream_from_stringbuf(propstring, pool);
+ SVN_ERR(svn_hash_write2(prophash, propstream, "PROPS-END", pool));
+ SVN_ERR(svn_stream_close(propstream));
+
+ /* Property-content-length: 14; Content-length: 14 */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n",
+ propstring->len));
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_CONTENT_LENGTH
+ ": %" APR_SIZE_T_FMT "\n\n",
+ propstring->len));
+ /* The properties */
+ SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
+ &(propstring->len)));
+ SVN_ERR(svn_stream_puts(stdout_stream, "\n"));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+dump_initial_full_revision(svn_ra_session_t *session,
+ svn_ra_session_t *extra_ra_session,
+ svn_stream_t *stdout_stream,
+ svn_revnum_t revision,
+ svn_boolean_t quiet,
+ apr_pool_t *pool)
+{
+ const svn_ra_reporter3_t *reporter;
+ void *report_baton;
+ const svn_delta_editor_t *dump_editor;
+ void *dump_baton;
+ const char *session_url, *source_relpath;
+
+ /* Determine whether we're dumping the repository root URL or some
+ child thereof. If we're dumping a subtree of the repository
+ rather than the root, we have to jump through some hoops to make
+ our update-driven dump generation work the way a replay-driven
+ one would.
+
+ See http://subversion.tigris.org/issues/show_bug.cgi?id=4101
+ */
+ SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
+ SVN_ERR(svn_ra_get_path_relative_to_root(session, &source_relpath,
+ session_url, pool));
+
+ /* Start with a revision record header. */
+ SVN_ERR(dump_revision_header(session, stdout_stream, revision, pool));
+
+ /* Then, we'll drive the dump editor with what would look like a
+ full checkout of the repository as it looked in START_REVISION.
+ We do this by manufacturing a basic 'report' to the update
+ reporter, telling it that we have nothing to start with. The
+ delta between nothing and everything-at-REV is, effectively, a
+ full dump of REV. */
+ SVN_ERR(svn_rdump__get_dump_editor(&dump_editor, &dump_baton, revision,
+ stdout_stream, extra_ra_session,
+ source_relpath, check_cancel, NULL, pool));
+ SVN_ERR(svn_ra_do_update3(session, &reporter, &report_baton, revision,
+ "", svn_depth_infinity, FALSE, FALSE,
+ dump_editor, dump_baton, pool, pool));
+ SVN_ERR(reporter->set_path(report_baton, "", revision,
+ svn_depth_infinity, TRUE, NULL, pool));
+ SVN_ERR(reporter->finish_report(report_baton, pool));
+
+ /* All finished with START_REVISION! */
+ if (! quiet)
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
+ revision));
+
+ return SVN_NO_ERROR;
+}
+
+/* Replay revisions START_REVISION thru END_REVISION (inclusive) of
+ * the repository URL at which SESSION is rooted, using callbacks
+ * which generate Subversion repository dumpstreams describing the
+ * changes made in those revisions. If QUIET is set, don't generate
+ * progress messages.
+ */
+static svn_error_t *
+replay_revisions(svn_ra_session_t *session,
+ svn_ra_session_t *extra_ra_session,
+ svn_revnum_t start_revision,
+ svn_revnum_t end_revision,
+ svn_boolean_t quiet,
+ svn_boolean_t incremental,
+ apr_pool_t *pool)
+{
+ struct replay_baton *replay_baton;
+ const char *uuid;
+ svn_stream_t *stdout_stream;
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ replay_baton = apr_pcalloc(pool, sizeof(*replay_baton));
+ replay_baton->stdout_stream = stdout_stream;
+ replay_baton->extra_ra_session = extra_ra_session;
+ replay_baton->quiet = quiet;
+
+ /* Write the magic header and UUID */
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
+ SVN_REPOS_DUMPFILE_FORMAT_VERSION));
+ SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool));
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
+
+ /* Fake revision 0 if necessary */
+ if (start_revision == 0)
+ {
+ SVN_ERR(dump_revision_header(session, stdout_stream,
+ start_revision, pool));
+
+ /* Revision 0 has no tree changes, so we're done. */
+ if (! quiet)
+ SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
+ start_revision));
+ start_revision++;
+
+ /* If our first revision is 0, we can treat this as an
+ incremental dump. */
+ incremental = TRUE;
+ }
+
+ /* If what remains to be dumped is not going to be dumped
+ incrementally, then dump the first revision in full. */
+ if (!incremental)
+ {
+ SVN_ERR(dump_initial_full_revision(session, extra_ra_session,
+ stdout_stream, start_revision,
+ quiet, pool));
+ start_revision++;
+ }
+
+ /* If there are still revisions left to be dumped, do so. */
+ if (start_revision <= end_revision)
+ {
+#ifndef USE_EV2_IMPL
+ SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision,
+ 0, TRUE, replay_revstart, replay_revend,
+ replay_baton, pool));
+#else
+ SVN_ERR(svn_ra__replay_range_ev2(session, start_revision, end_revision,
+ 0, TRUE, replay_revstart_v2,
+ replay_revend_v2, replay_baton,
+ NULL, NULL, NULL, NULL, pool));
+#endif
+ }
+
+ SVN_ERR(svn_stream_close(stdout_stream));
+ return SVN_NO_ERROR;
+}
+
+/* Read a dumpstream from stdin, and use it to feed a loader capable
+ * of transmitting that information to the repository located at URL
+ * (to which SESSION has been opened). AUX_SESSION is a second RA
+ * session opened to the same URL for performing auxiliary out-of-band
+ * operations.
+ */
+static svn_error_t *
+load_revisions(svn_ra_session_t *session,
+ svn_ra_session_t *aux_session,
+ const char *url,
+ svn_boolean_t quiet,
+ apr_pool_t *pool)
+{
+ apr_file_t *stdin_file;
+ svn_stream_t *stdin_stream;
+
+ apr_file_open_stdin(&stdin_file, pool);
+ stdin_stream = svn_stream_from_aprfile2(stdin_file, FALSE, pool);
+
+ SVN_ERR(svn_rdump__load_dumpstream(stdin_stream, session, aux_session,
+ quiet, check_cancel, NULL, pool));
+
+ SVN_ERR(svn_stream_close(stdin_stream));
+
+ return SVN_NO_ERROR;
+}
+
+/* Return a program name for this program, the basename of the path
+ * represented by PROGNAME if not NULL; use "svnrdump" otherwise.
+ */
+static const char *
+ensure_appname(const char *progname,
+ apr_pool_t *pool)
+{
+ if (!progname)
+ return "svnrdump";
+
+ return svn_dirent_basename(svn_dirent_internal_style(progname, pool), NULL);
+}
+
+/* Print a simple usage string. */
+static svn_error_t *
+usage(const char *progname,
+ apr_pool_t *pool)
+{
+ return svn_cmdline_fprintf(stderr, pool,
+ _("Type '%s help' for usage.\n"),
+ ensure_appname(progname, pool));
+}
+
+/* Print information about the version of this program and dependent
+ * modules.
+ */
+static svn_error_t *
+version(const char *progname,
+ svn_boolean_t quiet,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *version_footer =
+ svn_stringbuf_create(_("The following repository access (RA) modules "
+ "are available:\n\n"),
+ pool);
+
+ SVN_ERR(svn_ra_print_modules(version_footer, pool));
+ return svn_opt_print_help4(NULL, ensure_appname(progname, pool),
+ TRUE, quiet, FALSE, version_footer->data,
+ NULL, NULL, NULL, NULL, NULL, pool);
+}
+
+
+/* A statement macro, similar to @c SVN_ERR, but returns an integer.
+ * Evaluate @a expr. If it yields an error, handle that error and
+ * return @c EXIT_FAILURE.
+ */
+#define SVNRDUMP_ERR(expr) \
+ do \
+ { \
+ svn_error_t *svn_err__temp = (expr); \
+ if (svn_err__temp) \
+ { \
+ svn_handle_error2(svn_err__temp, stderr, FALSE, "svnrdump: "); \
+ svn_error_clear(svn_err__temp); \
+ return EXIT_FAILURE; \
+ } \
+ } \
+ while (0)
+
+/* Handle the "dump" subcommand. Implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+dump_cmd(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ opt_baton_t *opt_baton = baton;
+ svn_ra_session_t *extra_ra_session;
+ const char *repos_root;
+
+ SVN_ERR(svn_client_open_ra_session2(&extra_ra_session,
+ opt_baton->url, NULL,
+ opt_baton->ctx, pool, pool));
+ SVN_ERR(svn_ra_get_repos_root2(extra_ra_session, &repos_root, pool));
+ SVN_ERR(svn_ra_reparent(extra_ra_session, repos_root, pool));
+
+ return replay_revisions(opt_baton->session, extra_ra_session,
+ opt_baton->start_revision.value.number,
+ opt_baton->end_revision.value.number,
+ opt_baton->quiet, opt_baton->incremental, pool);
+}
+
+/* Handle the "load" subcommand. Implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+load_cmd(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ opt_baton_t *opt_baton = baton;
+ svn_ra_session_t *aux_session;
+
+ SVN_ERR(svn_client_open_ra_session2(&aux_session, opt_baton->url, NULL,
+ opt_baton->ctx, pool, pool));
+ return load_revisions(opt_baton->session, aux_session, opt_baton->url,
+ opt_baton->quiet, pool);
+}
+
+/* Handle the "help" subcommand. Implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+help_cmd(apr_getopt_t *os,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const char *header =
+ _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n"
+ "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svnrdump --version' to see the program version and RA modules.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ return svn_opt_print_help4(os, "svnrdump", FALSE, FALSE, FALSE, NULL,
+ header, svnrdump__cmd_table, svnrdump__options,
+ NULL, NULL, pool);
+}
+
+/* Examine the OPT_BATON's 'start_revision' and 'end_revision'
+ * members, making sure that they make sense (in general, and as
+ * applied to a repository whose current youngest revision is
+ * LATEST_REVISION).
+ */
+static svn_error_t *
+validate_and_resolve_revisions(opt_baton_t *opt_baton,
+ svn_revnum_t latest_revision,
+ apr_pool_t *pool)
+{
+ svn_revnum_t provided_start_rev = SVN_INVALID_REVNUM;
+
+ /* Ensure that the start revision is something we can handle. We
+ want a number >= 0. If unspecified, make it a number (r0) --
+ anything else is bogus. */
+ if (opt_baton->start_revision.kind == svn_opt_revision_number)
+ {
+ provided_start_rev = opt_baton->start_revision.value.number;
+ }
+ else if (opt_baton->start_revision.kind == svn_opt_revision_head)
+ {
+ opt_baton->start_revision.kind = svn_opt_revision_number;
+ opt_baton->start_revision.value.number = latest_revision;
+ }
+ else if (opt_baton->start_revision.kind == svn_opt_revision_unspecified)
+ {
+ opt_baton->start_revision.kind = svn_opt_revision_number;
+ opt_baton->start_revision.value.number = 0;
+ }
+
+ if (opt_baton->start_revision.kind != svn_opt_revision_number)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Unsupported revision specifier used; use "
+ "only integer values or 'HEAD'"));
+ }
+
+ if ((opt_baton->start_revision.value.number < 0) ||
+ (opt_baton->start_revision.value.number > latest_revision))
+ {
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Revision '%ld' does not exist"),
+ opt_baton->start_revision.value.number);
+ }
+
+ /* Ensure that the end revision is something we can handle. We want
+ a number <= the youngest, and > the start revision. If
+ unspecified, make it a number (start_revision + 1 if that was
+ specified, the youngest revision in the repository otherwise) --
+ anything else is bogus. */
+ if (opt_baton->end_revision.kind == svn_opt_revision_unspecified)
+ {
+ opt_baton->end_revision.kind = svn_opt_revision_number;
+ if (SVN_IS_VALID_REVNUM(provided_start_rev))
+ opt_baton->end_revision.value.number = provided_start_rev;
+ else
+ opt_baton->end_revision.value.number = latest_revision;
+ }
+ else if (opt_baton->end_revision.kind == svn_opt_revision_head)
+ {
+ opt_baton->end_revision.kind = svn_opt_revision_number;
+ opt_baton->end_revision.value.number = latest_revision;
+ }
+
+ if (opt_baton->end_revision.kind != svn_opt_revision_number)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Unsupported revision specifier used; use "
+ "only integer values or 'HEAD'"));
+ }
+
+ if ((opt_baton->end_revision.value.number < 0) ||
+ (opt_baton->end_revision.value.number > latest_revision))
+ {
+ return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Revision '%ld' does not exist"),
+ opt_baton->end_revision.value.number);
+ }
+
+ /* Finally, make sure that the end revision is younger than the
+ start revision. We don't do "backwards" 'round here. */
+ if (opt_baton->end_revision.value.number <
+ opt_baton->start_revision.value.number)
+ {
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("LOWER revision cannot be greater than "
+ "UPPER revision; consider reversing your "
+ "revision range"));
+ }
+ return SVN_NO_ERROR;
+}
+
+int
+main(int argc, const char **argv)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ opt_baton_t *opt_baton;
+ svn_revnum_t latest_revision = SVN_INVALID_REVNUM;
+ apr_pool_t *pool = NULL;
+ const char *config_dir = NULL;
+ const char *username = NULL;
+ const char *password = NULL;
+ svn_boolean_t no_auth_cache = FALSE;
+ svn_boolean_t trust_server_cert = FALSE;
+ svn_boolean_t non_interactive = FALSE;
+ svn_boolean_t force_interactive = FALSE;
+ apr_array_header_t *config_options = NULL;
+ apr_getopt_t *os;
+ const char *first_arg;
+ apr_array_header_t *received_opts;
+ int i;
+
+ if (svn_cmdline_init ("svnrdump", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ opt_baton = apr_pcalloc(pool, sizeof(*opt_baton));
+ opt_baton->start_revision.kind = svn_opt_revision_unspecified;
+ opt_baton->end_revision.kind = svn_opt_revision_unspecified;
+ opt_baton->url = NULL;
+
+ SVNRDUMP_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
+
+ os->interleave = TRUE; /* Options and arguments can be interleaved */
+
+ /* Set up our cancellation support. */
+ apr_signal(SIGINT, signal_handler);
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, signal_handler);
+#endif
+#ifdef SIGHUP
+ apr_signal(SIGHUP, signal_handler);
+#endif
+#ifdef SIGTERM
+ apr_signal(SIGTERM, signal_handler);
+#endif
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ while (1)
+ {
+ int opt;
+ const char *opt_arg;
+ apr_status_t status = apr_getopt_long(os, svnrdump__options, &opt,
+ &opt_arg);
+
+ if (APR_STATUS_IS_EOF(status))
+ break;
+ if (status != APR_SUCCESS)
+ {
+ SVNRDUMP_ERR(usage(argv[0], pool));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Stash the option code in an array before parsing it. */
+ APR_ARRAY_PUSH(received_opts, int) = opt;
+
+ switch(opt)
+ {
+ case 'r':
+ {
+ /* Make sure we've not seen -r already. */
+ if (opt_baton->start_revision.kind != svn_opt_revision_unspecified)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Multiple revision arguments "
+ "encountered; try '-r N:M' instead "
+ "of '-r N -r M'"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
+ }
+ /* Parse the -r argument. */
+ if (svn_opt_parse_revision(&(opt_baton->start_revision),
+ &(opt_baton->end_revision),
+ opt_arg, pool) != 0)
+ {
+ const char *utf8_opt_arg;
+ err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool);
+ if (! err)
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Syntax error in revision "
+ "argument '%s'"), utf8_opt_arg);
+ return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
+ }
+ }
+ break;
+ case 'q':
+ opt_baton->quiet = TRUE;
+ break;
+ case opt_config_dir:
+ config_dir = opt_arg;
+ break;
+ case opt_version:
+ opt_baton->version = TRUE;
+ break;
+ case 'h':
+ opt_baton->help = TRUE;
+ break;
+ case opt_auth_username:
+ SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool));
+ break;
+ case opt_auth_password:
+ SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool));
+ break;
+ case opt_auth_nocache:
+ no_auth_cache = TRUE;
+ break;
+ case opt_non_interactive:
+ non_interactive = TRUE;
+ break;
+ case opt_force_interactive:
+ force_interactive = TRUE;
+ break;
+ case opt_incremental:
+ opt_baton->incremental = TRUE;
+ break;
+ case opt_trust_server_cert:
+ trust_server_cert = TRUE;
+ break;
+ case opt_config_option:
+ if (!config_options)
+ config_options =
+ apr_array_make(pool, 1,
+ sizeof(svn_cmdline__config_argument_t*));
+
+ SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
+ SVNRDUMP_ERR(svn_cmdline__parse_config_option(config_options,
+ opt_arg, pool));
+ }
+ }
+
+ /* The --non-interactive and --force-interactive options are mutually
+ * exclusive. */
+ if (non_interactive && force_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--non-interactive and --force-interactive "
+ "are mutually exclusive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
+ }
+
+ if (opt_baton->help)
+ {
+ subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
+ "help");
+ }
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_baton->version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", help_cmd, {0}, "",
+ {opt_version, /* must accept its own option */
+ 'q', /* --quiet */
+ } };
+ subcommand = &pseudo_cmd;
+ }
+
+ else
+ {
+ SVNRDUMP_ERR(help_cmd(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ exit(EXIT_FAILURE);
+ }
+ }
+ else
+ {
+ first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
+ first_arg);
+
+ if (subcommand == NULL)
+ {
+ const char *first_arg_utf8;
+ err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
+ svn_error_clear(
+ svn_cmdline_fprintf(stderr, pool,
+ _("Unknown subcommand: '%s'\n"),
+ first_arg_utf8));
+ SVNRDUMP_ERR(help_cmd(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+
+ /* Check that the subcommand wasn't passed any inappropriate options. */
+ for (i = 0; i < received_opts->nelts; i++)
+ {
+ int opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ /* All commands implicitly accept --help, so just skip over this
+ when we see it. Note that we don't want to include this option
+ in their "accepted options" list because it would be awfully
+ redundant to display it in every commands' help text. */
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, svnrdump__options,
+ subcommand, pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ SVN_INT_ERR(help_cmd(NULL, NULL, pool));
+ else
+ svn_error_clear(svn_cmdline_fprintf(
+ stderr, pool,
+ _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svnrdump help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (subcommand && strcmp(subcommand->name, "--version") == 0)
+ {
+ SVNRDUMP_ERR(version(argv[0], opt_baton->quiet, pool));
+ svn_pool_destroy(pool);
+ exit(EXIT_SUCCESS);
+ }
+
+ if (subcommand && strcmp(subcommand->name, "help") == 0)
+ {
+ SVNRDUMP_ERR(help_cmd(os, opt_baton, pool));
+ svn_pool_destroy(pool);
+ exit(EXIT_SUCCESS);
+ }
+
+ /* --trust-server-cert can only be used with --non-interactive */
+ if (trust_server_cert && !non_interactive)
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("--trust-server-cert requires "
+ "--non-interactive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
+ }
+
+ /* Expect one more non-option argument: the repository URL. */
+ if (os->ind != os->argc - 1)
+ {
+ SVNRDUMP_ERR(usage(argv[0], pool));
+ svn_pool_destroy(pool);
+ exit(EXIT_FAILURE);
+ }
+ else
+ {
+ const char *repos_url;
+
+ SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&repos_url,
+ os->argv[os->ind], pool));
+ if (! svn_path_is_url(repos_url))
+ {
+ err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
+ "Target '%s' is not a URL",
+ repos_url);
+ SVNRDUMP_ERR(err);
+ svn_pool_destroy(pool);
+ exit(EXIT_FAILURE);
+ }
+ opt_baton->url = svn_uri_canonicalize(repos_url, pool);
+ }
+
+ if (strcmp(subcommand->name, "load") == 0)
+ {
+ /*
+ * By default (no --*-interactive options given), the 'load' subcommand
+ * is interactive unless username and password were provided on the
+ * command line. This allows prompting for auth creds to work without
+ * requiring users to remember to use --force-interactive.
+ * See issue #3913, "svnrdump load is not working in interactive mode".
+ */
+ if (!non_interactive && !force_interactive)
+ force_interactive = (username == NULL || password == NULL);
+ }
+
+ non_interactive = !svn_cmdline__be_interactive(non_interactive,
+ force_interactive);
+
+ SVNRDUMP_ERR(init_client_context(&(opt_baton->ctx),
+ non_interactive,
+ username,
+ password,
+ config_dir,
+ opt_baton->url,
+ no_auth_cache,
+ trust_server_cert,
+ config_options,
+ pool));
+
+ err = svn_client_open_ra_session2(&(opt_baton->session),
+ opt_baton->url, NULL,
+ opt_baton->ctx, pool, pool);
+
+ /* Have sane opt_baton->start_revision and end_revision defaults if
+ unspecified. */
+ if (!err)
+ err = svn_ra_get_latest_revnum(opt_baton->session, &latest_revision, pool);
+
+ /* Make sure any provided revisions make sense. */
+ if (!err)
+ err = validate_and_resolve_revisions(opt_baton, latest_revision, pool);
+
+ /* Dispatch the subcommand */
+ if (!err)
+ err = (*subcommand->cmd_func)(os, opt_baton, pool);
+
+ if (err && err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Authentication failed and interactive"
+ " prompting is disabled; see the"
+ " --force-interactive option"));
+ }
+
+ SVNRDUMP_ERR(err);
+
+ svn_pool_destroy(pool);
+
+ return EXIT_SUCCESS;
+}
OpenPOWER on IntegriCloud