]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/svnrdump/svnrdump.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / svnrdump / svnrdump.c
1 /*
2  *  svnrdump.c: Produce a dumpfile of a local or remote repository
3  *              without touching the filesystem, but for temporary files.
4  *
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
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
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
21  *    under the License.
22  * ====================================================================
23  */
24
25 #include <apr_uri.h>
26
27 #include "svn_pools.h"
28 #include "svn_cmdline.h"
29 #include "svn_client.h"
30 #include "svn_hash.h"
31 #include "svn_ra.h"
32 #include "svn_repos.h"
33 #include "svn_path.h"
34 #include "svn_utf.h"
35 #include "svn_private_config.h"
36 #include "svn_string.h"
37 #include "svn_props.h"
38
39 #include "svnrdump.h"
40
41 #include "private/svn_repos_private.h"
42 #include "private/svn_cmdline_private.h"
43 #include "private/svn_ra_private.h"
44
45
46 \f
47 /*** Cancellation ***/
48
49 /* Our cancellation callback. */
50 static svn_cancel_func_t check_cancel = NULL;
51
52 \f
53
54 static svn_opt_subcommand_t dump_cmd, load_cmd;
55
56 enum svn_svnrdump__longopt_t
57   {
58     opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID,
59     opt_config_option,
60     opt_auth_username,
61     opt_auth_password,
62     opt_auth_password_from_stdin,
63     opt_auth_nocache,
64     opt_non_interactive,
65     opt_skip_revprop,
66     opt_force_interactive,
67     opt_incremental,
68     opt_trust_server_cert,
69     opt_trust_server_cert_failures,
70     opt_version
71   };
72
73 #define SVN_SVNRDUMP__BASE_OPTIONS opt_config_dir, \
74                                    opt_config_option, \
75                                    opt_auth_username, \
76                                    opt_auth_password, \
77                                    opt_auth_password_from_stdin, \
78                                    opt_auth_nocache, \
79                                    opt_trust_server_cert, \
80                                    opt_trust_server_cert_failures, \
81                                    opt_non_interactive, \
82                                    opt_force_interactive
83
84 static const svn_opt_subcommand_desc2_t svnrdump__cmd_table[] =
85 {
86   { "dump", dump_cmd, { 0 },
87     N_("usage: svnrdump dump URL [-r LOWER[:UPPER]]\n\n"
88        "Dump revisions LOWER to UPPER of repository at remote URL to stdout\n"
89        "in a 'dumpfile' portable format.  If only LOWER is given, dump that\n"
90        "one revision.\n"),
91     { 'r', 'q', opt_incremental, SVN_SVNRDUMP__BASE_OPTIONS } },
92   { "load", load_cmd, { 0 },
93     N_("usage: svnrdump load URL\n\n"
94        "Load a 'dumpfile' given on stdin to a repository at remote URL.\n"),
95     { 'q', opt_skip_revprop, SVN_SVNRDUMP__BASE_OPTIONS } },
96   { "help", 0, { "?", "h" },
97     N_("usage: svnrdump help [SUBCOMMAND...]\n\n"
98        "Describe the usage of this program or its subcommands.\n"),
99     { 0 } },
100   { NULL, NULL, { 0 }, NULL, { 0 } }
101 };
102
103 static const apr_getopt_option_t svnrdump__options[] =
104   {
105     {"revision",     'r', 1,
106                       N_("specify revision number ARG (or X:Y range)")},
107     {"quiet",         'q', 0,
108                       N_("no progress (only errors) to stderr")},
109     {"incremental",   opt_incremental, 0,
110                       N_("dump incrementally")},
111     {"skip-revprop",  opt_skip_revprop, 1,
112                       N_("skip revision property ARG (e.g., \"svn:author\")")},
113     {"config-dir",    opt_config_dir, 1,
114                       N_("read user configuration files from directory ARG")},
115     {"username",      opt_auth_username, 1,
116                       N_("specify a username ARG")},
117     {"password",      opt_auth_password, 1,
118                       N_("specify a password ARG")},
119     {"password-from-stdin",   opt_auth_password_from_stdin, 0,
120                       N_("read password from stdin")},
121     {"non-interactive", opt_non_interactive, 0,
122                       N_("do no interactive prompting (default is to prompt\n"
123                          "                             "
124                          "only if standard input is a terminal device)")},
125     {"force-interactive", opt_force_interactive, 0,
126                       N_("do interactive prompting even if standard input\n"
127                          "                             "
128                          "is not a terminal device")},
129     {"no-auth-cache", opt_auth_nocache, 0,
130                       N_("do not cache authentication tokens")},
131     {"help",          'h', 0,
132                       N_("display this help")},
133     {"version",       opt_version, 0,
134                       N_("show program version information")},
135     {"config-option", opt_config_option, 1,
136                       N_("set user configuration option in the format:\n"
137                          "                             "
138                          "    FILE:SECTION:OPTION=[VALUE]\n"
139                          "                             "
140                          "For example:\n"
141                          "                             "
142                          "    servers:global:http-library=serf")},
143   {"trust-server-cert", opt_trust_server_cert, 0,
144                     N_("deprecated; same as\n"
145                        "                             "
146                        "--trust-server-cert-failures=unknown-ca")},
147   {"trust-server-cert-failures", opt_trust_server_cert_failures, 1,
148                     N_("with --non-interactive, accept SSL server\n"
149                        "                             "
150                        "certificates with failures; ARG is comma-separated\n"
151                        "                             "
152                        "list of 'unknown-ca' (Unknown Authority),\n"
153                        "                             "
154                        "'cn-mismatch' (Hostname mismatch), 'expired'\n"
155                        "                             "
156                        "(Expired certificate), 'not-yet-valid' (Not yet\n"
157                        "                             "
158                        "valid certificate) and 'other' (all other not\n"
159                        "                             "
160                        "separately classified certificate errors).")},
161     {"dumpfile", 'F', 1, N_("Read or write to a dumpfile instead of stdin/stdout")},
162     {0, 0, 0, 0}
163   };
164
165 /* Baton for the RA replay session. */
166 struct replay_baton {
167   /* A backdoor ra session for fetching information. */
168   svn_ra_session_t *extra_ra_session;
169
170   /* The output stream */
171   svn_stream_t *stdout_stream;
172
173   /* Whether to be quiet. */
174   svn_boolean_t quiet;
175 };
176
177 /* Option set */
178 typedef struct opt_baton_t {
179   svn_client_ctx_t *ctx;
180   svn_ra_session_t *session;
181   const char *url;
182   const char *dumpfile;
183   svn_boolean_t help;
184   svn_boolean_t version;
185   svn_opt_revision_t start_revision;
186   svn_opt_revision_t end_revision;
187   svn_boolean_t quiet;
188   svn_boolean_t incremental;
189   apr_hash_t *skip_revprops;
190 } opt_baton_t;
191
192 /* Print dumpstream-formatted information about REVISION.
193  * Implements the `svn_ra_replay_revstart_callback_t' interface.
194  */
195 static svn_error_t *
196 replay_revstart(svn_revnum_t revision,
197                 void *replay_baton,
198                 const svn_delta_editor_t **editor,
199                 void **edit_baton,
200                 apr_hash_t *rev_props,
201                 apr_pool_t *pool)
202 {
203   struct replay_baton *rb = replay_baton;
204   apr_hash_t *normal_props;
205
206   /* Normalize and dump the revprops */
207   SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
208   SVN_ERR(svn_repos__dump_revision_record(rb->stdout_stream, revision, NULL,
209                                           normal_props,
210                                           TRUE /*props_section_always*/,
211                                           pool));
212
213   SVN_ERR(svn_rdump__get_dump_editor(editor, edit_baton, revision,
214                                      rb->stdout_stream, rb->extra_ra_session,
215                                      NULL, check_cancel, NULL, pool));
216
217   return SVN_NO_ERROR;
218 }
219
220 /* Print progress information about the dump of REVISION.
221    Implements the `svn_ra_replay_revfinish_callback_t' interface. */
222 static svn_error_t *
223 replay_revend(svn_revnum_t revision,
224               void *replay_baton,
225               const svn_delta_editor_t *editor,
226               void *edit_baton,
227               apr_hash_t *rev_props,
228               apr_pool_t *pool)
229 {
230   /* No resources left to free. */
231   struct replay_baton *rb = replay_baton;
232
233   SVN_ERR(editor->close_edit(edit_baton, pool));
234
235   if (! rb->quiet)
236     SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
237                                 revision));
238   return SVN_NO_ERROR;
239 }
240
241 #ifdef USE_EV2_IMPL
242 /* Print dumpstream-formatted information about REVISION.
243  * Implements the `svn_ra_replay_revstart_callback_t' interface.
244  */
245 static svn_error_t *
246 replay_revstart_v2(svn_revnum_t revision,
247                    void *replay_baton,
248                    svn_editor_t **editor,
249                    apr_hash_t *rev_props,
250                    apr_pool_t *pool)
251 {
252   struct replay_baton *rb = replay_baton;
253   apr_hash_t *normal_props;
254
255   /* Normalize and dump the revprops */
256   SVN_ERR(svn_rdump__normalize_props(&normal_props, rev_props, pool));
257   SVN_ERR(svn_repos__dump_revision_record(rb->stdout_stream, revision,
258                                           normal_props,
259                                           TRUE /*props_section_always*/,
260                                           pool));
261
262   SVN_ERR(svn_rdump__get_dump_editor_v2(editor, revision,
263                                         rb->stdout_stream,
264                                         rb->extra_ra_session,
265                                         NULL, check_cancel, NULL, pool, pool));
266
267   return SVN_NO_ERROR;
268 }
269
270 /* Print progress information about the dump of REVISION.
271    Implements the `svn_ra_replay_revfinish_callback_t' interface. */
272 static svn_error_t *
273 replay_revend_v2(svn_revnum_t revision,
274                  void *replay_baton,
275                  svn_editor_t *editor,
276                  apr_hash_t *rev_props,
277                  apr_pool_t *pool)
278 {
279   /* No resources left to free. */
280   struct replay_baton *rb = replay_baton;
281
282   SVN_ERR(svn_editor_complete(editor));
283
284   if (! rb->quiet)
285     SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
286                                 revision));
287   return SVN_NO_ERROR;
288 }
289 #endif
290
291 /* Initialize the RA layer, and set *CTX to a new client context baton
292  * allocated from POOL.  Use CONFIG_DIR and pass USERNAME, PASSWORD,
293  * CONFIG_DIR and NO_AUTH_CACHE to initialize the authorization baton.
294  * CONFIG_OPTIONS (if not NULL) is a list of configuration overrides.
295  * REPOS_URL is used to fiddle with server-specific configuration
296  * options.
297  */
298 static svn_error_t *
299 init_client_context(svn_client_ctx_t **ctx_p,
300                     svn_boolean_t non_interactive,
301                     const char *username,
302                     const char *password,
303                     const char *config_dir,
304                     const char *repos_url,
305                     svn_boolean_t no_auth_cache,
306                     svn_boolean_t trust_unknown_ca,
307                     svn_boolean_t trust_cn_mismatch,
308                     svn_boolean_t trust_expired,
309                     svn_boolean_t trust_not_yet_valid,
310                     svn_boolean_t trust_other_failure,
311                     apr_array_header_t *config_options,
312                     apr_pool_t *pool)
313 {
314   svn_client_ctx_t *ctx = NULL;
315   svn_config_t *cfg_config, *cfg_servers;
316
317   SVN_ERR(svn_ra_initialize(pool));
318
319   SVN_ERR(svn_config_ensure(config_dir, pool));
320   SVN_ERR(svn_client_create_context2(&ctx, NULL, pool));
321
322   SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
323
324   if (config_options)
325     SVN_ERR(svn_cmdline__apply_config_options(ctx->config, config_options,
326                                               "svnrdump: ", "--config-option"));
327
328   cfg_config = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG);
329
330   /* ### FIXME: This is a hack to work around the fact that our dump
331      ### editor simply can't handle the way ra_serf violates the
332      ### editor v1 drive ordering requirements.
333      ###
334      ### We'll override both the global value and server-specific one
335      ### for the 'http-bulk-updates' and 'http-max-connections'
336      ### options in order to get ra_serf to try a bulk-update if the
337      ### server will allow it, or at least try to limit all its
338      ### auxiliary GETs/PROPFINDs to happening (well-ordered) on a
339      ### single server connection.
340      ###
341      ### See http://subversion.tigris.org/issues/show_bug.cgi?id=4116.
342   */
343   cfg_servers = svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_SERVERS);
344   svn_config_set_bool(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
345                       SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
346   svn_config_set_int64(cfg_servers, SVN_CONFIG_SECTION_GLOBAL,
347                        SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
348   if (cfg_servers)
349     {
350       apr_status_t status;
351       apr_uri_t parsed_url;
352
353       status = apr_uri_parse(pool, repos_url, &parsed_url);
354       if (! status)
355         {
356           const char *server_group;
357
358           server_group = svn_config_find_group(cfg_servers, parsed_url.hostname,
359                                                SVN_CONFIG_SECTION_GROUPS, pool);
360           if (server_group)
361             {
362               svn_config_set_bool(cfg_servers, server_group,
363                                   SVN_CONFIG_OPTION_HTTP_BULK_UPDATES, TRUE);
364               svn_config_set_int64(cfg_servers, server_group,
365                                    SVN_CONFIG_OPTION_HTTP_MAX_CONNECTIONS, 2);
366             }
367         }
368     }
369
370   /* Set up our cancellation support. */
371   ctx->cancel_func = check_cancel;
372
373   /* Default authentication providers for non-interactive use */
374   SVN_ERR(svn_cmdline_create_auth_baton2(&(ctx->auth_baton), non_interactive,
375                                          username, password, config_dir,
376                                          no_auth_cache, trust_unknown_ca,
377                                          trust_cn_mismatch, trust_expired,
378                                          trust_not_yet_valid,
379                                          trust_other_failure,
380                                          cfg_config, ctx->cancel_func,
381                                          ctx->cancel_baton, pool));
382   *ctx_p = ctx;
383   return SVN_NO_ERROR;
384 }
385
386 /* Print a revision record header for REVISION to STDOUT_STREAM.  Use
387  * SESSION to contact the repository for revision properties and
388  * such.
389  */
390 static svn_error_t *
391 dump_revision_header(svn_ra_session_t *session,
392                      svn_stream_t *stdout_stream,
393                      svn_revnum_t revision,
394                      apr_pool_t *pool)
395 {
396   apr_hash_t *prophash;
397
398   SVN_ERR(svn_ra_rev_proplist(session, revision, &prophash, pool));
399   SVN_ERR(svn_repos__dump_revision_record(stdout_stream, revision, NULL,
400                                           prophash,
401                                           TRUE /*props_section_always*/,
402                                           pool));
403   return SVN_NO_ERROR;
404 }
405
406 static svn_error_t *
407 dump_initial_full_revision(svn_ra_session_t *session,
408                            svn_ra_session_t *extra_ra_session,
409                            svn_stream_t *stdout_stream,
410                            svn_revnum_t revision,
411                            svn_boolean_t quiet,
412                            apr_pool_t *pool)
413 {
414   const svn_ra_reporter3_t *reporter;
415   void *report_baton;
416   const svn_delta_editor_t *dump_editor;
417   void *dump_baton;
418   const char *session_url, *source_relpath;
419
420   /* Determine whether we're dumping the repository root URL or some
421      child thereof.  If we're dumping a subtree of the repository
422      rather than the root, we have to jump through some hoops to make
423      our update-driven dump generation work the way a replay-driven
424      one would.
425
426      See http://subversion.tigris.org/issues/show_bug.cgi?id=4101
427   */
428   SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
429   SVN_ERR(svn_ra_get_path_relative_to_root(session, &source_relpath,
430                                            session_url, pool));
431
432   /* Start with a revision record header. */
433   SVN_ERR(dump_revision_header(session, stdout_stream, revision, pool));
434
435   /* Then, we'll drive the dump editor with what would look like a
436      full checkout of the repository as it looked in START_REVISION.
437      We do this by manufacturing a basic 'report' to the update
438      reporter, telling it that we have nothing to start with.  The
439      delta between nothing and everything-at-REV is, effectively, a
440      full dump of REV. */
441   SVN_ERR(svn_rdump__get_dump_editor(&dump_editor, &dump_baton, revision,
442                                      stdout_stream, extra_ra_session,
443                                      source_relpath, check_cancel, NULL, pool));
444   SVN_ERR(svn_ra_do_update3(session, &reporter, &report_baton, revision,
445                             "", svn_depth_infinity, FALSE, FALSE,
446                             dump_editor, dump_baton, pool, pool));
447   SVN_ERR(reporter->set_path(report_baton, "", revision,
448                              svn_depth_infinity, TRUE, NULL, pool));
449   SVN_ERR(reporter->finish_report(report_baton, pool));
450
451   /* All finished with START_REVISION! */
452   if (! quiet)
453     SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
454                                 revision));
455
456   return SVN_NO_ERROR;
457 }
458
459 /* Replay revisions START_REVISION thru END_REVISION (inclusive) of
460  * the repository URL at which SESSION is rooted, using callbacks
461  * which generate Subversion repository dumpstreams describing the
462  * changes made in those revisions.  If QUIET is set, don't generate
463  * progress messages.
464  */
465 static svn_error_t *
466 replay_revisions(svn_ra_session_t *session,
467                  svn_ra_session_t *extra_ra_session,
468                  svn_revnum_t start_revision,
469                  svn_revnum_t end_revision,
470                  svn_boolean_t quiet,
471                  svn_boolean_t incremental,
472                  const char *dumpfile,
473                  apr_pool_t *pool)
474 {
475   struct replay_baton *replay_baton;
476   const char *uuid;
477   svn_stream_t *output_stream;
478
479   if (dumpfile)
480     {
481       SVN_ERR(svn_stream_open_writable(&output_stream, dumpfile, pool, pool));
482     }
483   else
484     {
485       SVN_ERR(svn_stream_for_stdout(&output_stream, pool));
486     }
487
488   replay_baton = apr_pcalloc(pool, sizeof(*replay_baton));
489   replay_baton->stdout_stream = output_stream;
490   replay_baton->extra_ra_session = extra_ra_session;
491   replay_baton->quiet = quiet;
492
493   /* Write the magic header and UUID */
494   SVN_ERR(svn_stream_printf(output_stream, pool,
495                             SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
496                             SVN_REPOS_DUMPFILE_FORMAT_VERSION));
497   SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool));
498   SVN_ERR(svn_stream_printf(output_stream, pool,
499                             SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
500
501   /* Fake revision 0 if necessary */
502   if (start_revision == 0)
503     {
504       SVN_ERR(dump_revision_header(session, output_stream,
505                                    start_revision, pool));
506
507       /* Revision 0 has no tree changes, so we're done. */
508       if (! quiet)
509         SVN_ERR(svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu.\n",
510                                     start_revision));
511       start_revision++;
512
513       /* If our first revision is 0, we can treat this as an
514          incremental dump. */
515       incremental = TRUE;
516     }
517
518   /* If what remains to be dumped is not going to be dumped
519      incrementally, then dump the first revision in full. */
520   if (!incremental)
521     {
522       SVN_ERR(dump_initial_full_revision(session, extra_ra_session,
523                                          output_stream, start_revision,
524                                          quiet, pool));
525       start_revision++;
526     }
527
528   /* If there are still revisions left to be dumped, do so. */
529   if (start_revision <= end_revision)
530     {
531 #ifndef USE_EV2_IMPL
532       SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision,
533                                   0, TRUE, replay_revstart, replay_revend,
534                                   replay_baton, pool));
535 #else
536       SVN_ERR(svn_ra__replay_range_ev2(session, start_revision, end_revision,
537                                        0, TRUE, replay_revstart_v2,
538                                        replay_revend_v2, replay_baton,
539                                        NULL, NULL, NULL, NULL, pool));
540 #endif
541     }
542
543   return SVN_NO_ERROR;
544 }
545
546 /* Read a dumpstream from stdin, and use it to feed a loader capable
547  * of transmitting that information to the repository located at URL
548  * (to which SESSION has been opened).  AUX_SESSION is a second RA
549  * session opened to the same URL for performing auxiliary out-of-band
550  * operations.
551  */
552 static svn_error_t *
553 load_revisions(svn_ra_session_t *session,
554                svn_ra_session_t *aux_session,
555                const char *dumpfile,
556                svn_boolean_t quiet,
557                apr_hash_t *skip_revprops,
558                apr_pool_t *pool)
559 {
560   svn_stream_t *output_stream;
561
562   if (dumpfile)
563     {
564       SVN_ERR(svn_stream_open_readonly(&output_stream, dumpfile, pool, pool));
565     }
566   else
567     {
568       SVN_ERR(svn_stream_for_stdin2(&output_stream, TRUE, pool));
569     }
570
571   SVN_ERR(svn_rdump__load_dumpstream(output_stream, session, aux_session,
572                                      quiet, skip_revprops,
573                                      check_cancel, NULL, pool));
574
575   return SVN_NO_ERROR;
576 }
577
578 /* Return a program name for this program, the basename of the path
579  * represented by PROGNAME if not NULL; use "svnrdump" otherwise.
580  */
581 static const char *
582 ensure_appname(const char *progname,
583                apr_pool_t *pool)
584 {
585   if (!progname)
586     return "svnrdump";
587
588   return svn_dirent_basename(svn_dirent_internal_style(progname, pool), NULL);
589 }
590
591 /* Print a simple usage string. */
592 static svn_error_t *
593 usage(const char *progname,
594       apr_pool_t *pool)
595 {
596   return svn_cmdline_fprintf(stderr, pool,
597                              _("Type '%s help' for usage.\n"),
598                              ensure_appname(progname, pool));
599 }
600
601 /* Print information about the version of this program and dependent
602  * modules.
603  */
604 static svn_error_t *
605 version(const char *progname,
606         svn_boolean_t quiet,
607         apr_pool_t *pool)
608 {
609   svn_stringbuf_t *version_footer =
610     svn_stringbuf_create(_("The following repository access (RA) modules "
611                            "are available:\n\n"),
612                          pool);
613
614   SVN_ERR(svn_ra_print_modules(version_footer, pool));
615   return svn_opt_print_help4(NULL, ensure_appname(progname, pool),
616                              TRUE, quiet, FALSE, version_footer->data,
617                              NULL, NULL, NULL, NULL, NULL, pool);
618 }
619
620
621 /* Handle the "dump" subcommand.  Implements `svn_opt_subcommand_t'.  */
622 static svn_error_t *
623 dump_cmd(apr_getopt_t *os,
624          void *baton,
625          apr_pool_t *pool)
626 {
627   opt_baton_t *opt_baton = baton;
628   svn_ra_session_t *extra_ra_session;
629   const char *repos_root;
630
631   SVN_ERR(svn_client_open_ra_session2(&extra_ra_session,
632                                       opt_baton->url, NULL,
633                                       opt_baton->ctx, pool, pool));
634   SVN_ERR(svn_ra_get_repos_root2(extra_ra_session, &repos_root, pool));
635   SVN_ERR(svn_ra_reparent(extra_ra_session, repos_root, pool));
636
637   return replay_revisions(opt_baton->session, extra_ra_session,
638                           opt_baton->start_revision.value.number,
639                           opt_baton->end_revision.value.number,
640                           opt_baton->quiet, opt_baton->incremental,
641                           opt_baton->dumpfile, pool);
642 }
643
644 /* Handle the "load" subcommand.  Implements `svn_opt_subcommand_t'.  */
645 static svn_error_t *
646 load_cmd(apr_getopt_t *os,
647          void *baton,
648          apr_pool_t *pool)
649 {
650   opt_baton_t *opt_baton = baton;
651   svn_ra_session_t *aux_session;
652
653   SVN_ERR(svn_client_open_ra_session2(&aux_session, opt_baton->url, NULL,
654                                       opt_baton->ctx, pool, pool));
655   return load_revisions(opt_baton->session, aux_session,
656                         opt_baton->dumpfile, opt_baton->quiet,
657                         opt_baton->skip_revprops, pool);
658 }
659
660 /* Handle the "help" subcommand.  Implements `svn_opt_subcommand_t'.  */
661 static svn_error_t *
662 help_cmd(apr_getopt_t *os,
663          void *baton,
664          apr_pool_t *pool)
665 {
666   const char *header =
667     _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n"
668       "Subversion remote repository dump and load tool.\n"
669       "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n"
670       "Type 'svnrdump --version' to see the program version and RA modules.\n"
671       "\n"
672       "Available subcommands:\n");
673
674   return svn_opt_print_help4(os, "svnrdump", FALSE, FALSE, FALSE, NULL,
675                              header, svnrdump__cmd_table, svnrdump__options,
676                              NULL, NULL, pool);
677 }
678
679 /* Examine the OPT_BATON's 'start_revision' and 'end_revision'
680  * members, making sure that they make sense (in general, and as
681  * applied to a repository whose current youngest revision is
682  * LATEST_REVISION).
683  */
684 static svn_error_t *
685 validate_and_resolve_revisions(opt_baton_t *opt_baton,
686                                svn_revnum_t latest_revision,
687                                apr_pool_t *pool)
688 {
689   svn_revnum_t provided_start_rev = SVN_INVALID_REVNUM;
690
691   /* Ensure that the start revision is something we can handle.  We
692      want a number >= 0.  If unspecified, make it a number (r0) --
693      anything else is bogus.  */
694   if (opt_baton->start_revision.kind == svn_opt_revision_number)
695     {
696       provided_start_rev = opt_baton->start_revision.value.number;
697     }
698   else if (opt_baton->start_revision.kind == svn_opt_revision_head)
699     {
700       opt_baton->start_revision.kind = svn_opt_revision_number;
701       opt_baton->start_revision.value.number = latest_revision;
702     }
703   else if (opt_baton->start_revision.kind == svn_opt_revision_unspecified)
704     {
705       opt_baton->start_revision.kind = svn_opt_revision_number;
706       opt_baton->start_revision.value.number = 0;
707     }
708
709   if (opt_baton->start_revision.kind != svn_opt_revision_number)
710     {
711       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
712                               _("Unsupported revision specifier used; use "
713                                 "only integer values or 'HEAD'"));
714     }
715
716   if ((opt_baton->start_revision.value.number < 0) ||
717       (opt_baton->start_revision.value.number > latest_revision))
718     {
719       return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
720                                _("Revision '%ld' does not exist"),
721                                opt_baton->start_revision.value.number);
722     }
723
724   /* Ensure that the end revision is something we can handle.  We want
725      a number <= the youngest, and > the start revision.  If
726      unspecified, make it a number (start_revision + 1 if that was
727      specified, the youngest revision in the repository otherwise) --
728      anything else is bogus.  */
729   if (opt_baton->end_revision.kind == svn_opt_revision_unspecified)
730     {
731       opt_baton->end_revision.kind = svn_opt_revision_number;
732       if (SVN_IS_VALID_REVNUM(provided_start_rev))
733         opt_baton->end_revision.value.number = provided_start_rev;
734       else
735         opt_baton->end_revision.value.number = latest_revision;
736     }
737   else if (opt_baton->end_revision.kind == svn_opt_revision_head)
738     {
739       opt_baton->end_revision.kind = svn_opt_revision_number;
740       opt_baton->end_revision.value.number = latest_revision;
741     }
742
743   if (opt_baton->end_revision.kind != svn_opt_revision_number)
744     {
745       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
746                               _("Unsupported revision specifier used; use "
747                                 "only integer values or 'HEAD'"));
748     }
749
750   if ((opt_baton->end_revision.value.number < 0) ||
751       (opt_baton->end_revision.value.number > latest_revision))
752     {
753       return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
754                                _("Revision '%ld' does not exist"),
755                                opt_baton->end_revision.value.number);
756     }
757
758   /* Finally, make sure that the end revision is younger than the
759      start revision.  We don't do "backwards" 'round here.  */
760   if (opt_baton->end_revision.value.number <
761       opt_baton->start_revision.value.number)
762     {
763       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
764                               _("LOWER revision cannot be greater than "
765                                 "UPPER revision; consider reversing your "
766                                 "revision range"));
767     }
768   return SVN_NO_ERROR;
769 }
770
771 /*
772  * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
773  * either return an error to be displayed, or set *EXIT_CODE to non-zero and
774  * return SVN_NO_ERROR.
775  */
776 static svn_error_t *
777 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
778 {
779   svn_error_t *err = SVN_NO_ERROR;
780   const svn_opt_subcommand_desc2_t *subcommand = NULL;
781   opt_baton_t *opt_baton;
782   svn_revnum_t latest_revision = SVN_INVALID_REVNUM;
783   const char *config_dir = NULL;
784   const char *username = NULL;
785   const char *password = NULL;
786   svn_boolean_t no_auth_cache = FALSE;
787   svn_boolean_t trust_unknown_ca = FALSE;
788   svn_boolean_t trust_cn_mismatch = FALSE;
789   svn_boolean_t trust_expired = FALSE;
790   svn_boolean_t trust_not_yet_valid = FALSE;
791   svn_boolean_t trust_other_failure = FALSE;
792   svn_boolean_t non_interactive = FALSE;
793   svn_boolean_t force_interactive = FALSE;
794   apr_array_header_t *config_options = NULL;
795   apr_getopt_t *os;
796   apr_array_header_t *received_opts;
797   int i;
798   svn_boolean_t read_pass_from_stdin = FALSE;
799
800   opt_baton = apr_pcalloc(pool, sizeof(*opt_baton));
801   opt_baton->start_revision.kind = svn_opt_revision_unspecified;
802   opt_baton->end_revision.kind = svn_opt_revision_unspecified;
803   opt_baton->url = NULL;
804   opt_baton->skip_revprops = apr_hash_make(pool);
805   opt_baton->dumpfile = NULL;
806
807   SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
808
809   os->interleave = TRUE; /* Options and arguments can be interleaved */
810
811   /* Set up our cancellation support. */
812   check_cancel = svn_cmdline__setup_cancellation_handler();
813
814   received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
815
816   while (1)
817     {
818       int opt;
819       const char *opt_arg;
820       apr_status_t status = apr_getopt_long(os, svnrdump__options, &opt,
821                                             &opt_arg);
822
823       if (APR_STATUS_IS_EOF(status))
824         break;
825       if (status != APR_SUCCESS)
826         {
827           SVN_ERR(usage(argv[0], pool));
828           *exit_code = EXIT_FAILURE;
829           return SVN_NO_ERROR;
830         }
831
832       /* Stash the option code in an array before parsing it. */
833       APR_ARRAY_PUSH(received_opts, int) = opt;
834
835       switch(opt)
836         {
837         case 'r':
838           {
839             /* Make sure we've not seen -r already. */
840             if (opt_baton->start_revision.kind != svn_opt_revision_unspecified)
841               {
842                 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
843                                         _("Multiple revision arguments "
844                                           "encountered; try '-r N:M' instead "
845                                           "of '-r N -r M'"));
846               }
847             /* Parse the -r argument. */
848             if (svn_opt_parse_revision(&(opt_baton->start_revision),
849                                        &(opt_baton->end_revision),
850                                        opt_arg, pool) != 0)
851               {
852                 const char *utf8_opt_arg;
853                 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
854                 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
855                                          _("Syntax error in revision "
856                                            "argument '%s'"), utf8_opt_arg);
857               }
858           }
859           break;
860         case 'q':
861           opt_baton->quiet = TRUE;
862           break;
863         case opt_config_dir:
864           config_dir = opt_arg;
865           break;
866         case opt_version:
867           opt_baton->version = TRUE;
868           break;
869         case 'h':
870           opt_baton->help = TRUE;
871           break;
872         case opt_auth_username:
873           SVN_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool));
874           break;
875         case opt_auth_password:
876           SVN_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool));
877           break;
878         case opt_auth_password_from_stdin:
879           read_pass_from_stdin = TRUE;
880           break;
881         case opt_auth_nocache:
882           no_auth_cache = TRUE;
883           break;
884         case opt_non_interactive:
885           non_interactive = TRUE;
886           break;
887         case opt_force_interactive:
888           force_interactive = TRUE;
889           break;
890         case opt_incremental:
891           opt_baton->incremental = TRUE;
892           break;
893         case opt_skip_revprop:
894           SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
895           svn_hash_sets(opt_baton->skip_revprops, opt_arg, opt_arg);
896           break;
897         case opt_trust_server_cert: /* backward compat */
898           trust_unknown_ca = TRUE;
899           break;
900         case opt_trust_server_cert_failures:
901           SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
902           SVN_ERR(svn_cmdline__parse_trust_options(
903                       &trust_unknown_ca,
904                       &trust_cn_mismatch,
905                       &trust_expired,
906                       &trust_not_yet_valid,
907                       &trust_other_failure,
908                       opt_arg, pool));
909           break;
910         case opt_config_option:
911           if (!config_options)
912               config_options =
913                     apr_array_make(pool, 1,
914                                    sizeof(svn_cmdline__config_argument_t*));
915
916             SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
917             SVN_ERR(svn_cmdline__parse_config_option(config_options,
918                                                      opt_arg, 
919                                                      "svnrdump: ",
920                                                      pool));
921           break;
922         case 'F':
923           SVN_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
924           opt_baton->dumpfile = opt_arg;
925           break;
926         }
927     }
928
929   /* The --non-interactive and --force-interactive options are mutually
930    * exclusive. */
931   if (non_interactive && force_interactive)
932     {
933       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
934                               _("--non-interactive and --force-interactive "
935                                 "are mutually exclusive"));
936     }
937
938   if (opt_baton->help)
939     {
940       subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
941                                                      "help");
942     }
943   if (subcommand == NULL)
944     {
945       if (os->ind >= os->argc)
946         {
947           if (opt_baton->version)
948             {
949               /* Use the "help" subcommand to handle the "--version" option. */
950               static const svn_opt_subcommand_desc2_t pseudo_cmd =
951                 { "--version", help_cmd, {0}, "",
952                   {opt_version,  /* must accept its own option */
953                    'q',  /* --quiet */
954                   } };
955               subcommand = &pseudo_cmd;
956             }
957
958           else
959             {
960               SVN_ERR(help_cmd(NULL, NULL, pool));
961               *exit_code = EXIT_FAILURE;
962               return SVN_NO_ERROR;
963             }
964         }
965       else
966         {
967           const char *first_arg;
968
969           SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
970                                           pool));
971           subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
972                                                          first_arg);
973
974           if (subcommand == NULL)
975             {
976               svn_error_clear(
977                 svn_cmdline_fprintf(stderr, pool,
978                                     _("Unknown subcommand: '%s'\n"),
979                                     first_arg));
980               SVN_ERR(help_cmd(NULL, NULL, pool));
981               *exit_code = EXIT_FAILURE;
982               return SVN_NO_ERROR;
983             }
984         }
985     }
986
987   /* Check that the subcommand wasn't passed any inappropriate options. */
988   for (i = 0; i < received_opts->nelts; i++)
989     {
990       int opt_id = APR_ARRAY_IDX(received_opts, i, int);
991
992       /* All commands implicitly accept --help, so just skip over this
993          when we see it. Note that we don't want to include this option
994          in their "accepted options" list because it would be awfully
995          redundant to display it in every commands' help text. */
996       if (opt_id == 'h' || opt_id == '?')
997         continue;
998
999       if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
1000         {
1001           const char *optstr;
1002           const apr_getopt_option_t *badopt =
1003             svn_opt_get_option_from_code2(opt_id, svnrdump__options,
1004                                           subcommand, pool);
1005           svn_opt_format_option(&optstr, badopt, FALSE, pool);
1006           if (subcommand->name[0] == '-')
1007             SVN_ERR(help_cmd(NULL, NULL, pool));
1008           else
1009             svn_error_clear(svn_cmdline_fprintf(
1010                                 stderr, pool,
1011                                 _("Subcommand '%s' doesn't accept option '%s'\n"
1012                                   "Type 'svnrdump help %s' for usage.\n"),
1013                                 subcommand->name, optstr, subcommand->name));
1014           *exit_code = EXIT_FAILURE;
1015           return SVN_NO_ERROR;
1016         }
1017     }
1018
1019   if (strcmp(subcommand->name, "--version") == 0)
1020     {
1021       SVN_ERR(version(argv[0], opt_baton->quiet, pool));
1022       return SVN_NO_ERROR;
1023     }
1024
1025   if (strcmp(subcommand->name, "help") == 0)
1026     {
1027       SVN_ERR(help_cmd(os, opt_baton, pool));
1028       return SVN_NO_ERROR;
1029     }
1030
1031   /* --trust-* can only be used with --non-interactive */
1032   if (!non_interactive)
1033     {
1034       if (trust_unknown_ca || trust_cn_mismatch || trust_expired
1035           || trust_not_yet_valid || trust_other_failure)
1036         return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1037                                 _("--trust-server-cert-failures requires "
1038                                   "--non-interactive"));
1039     }
1040
1041   if (read_pass_from_stdin && !non_interactive)
1042     {
1043       return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1044                               _("--password-from-stdin requires "
1045                                 "--non-interactive"));
1046     }
1047
1048   if (strcmp(subcommand->name, "load") == 0)
1049     {
1050       if (read_pass_from_stdin && opt_baton->dumpfile == NULL)
1051         {
1052           /* error here, since load cannot process a password over stdin */
1053           return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1054                                   _("load subcommand with "
1055                                     "--password-from-stdin requires -F"));
1056         }
1057     }
1058
1059   /* Expect one more non-option argument:  the repository URL. */
1060   if (os->ind != os->argc - 1)
1061     {
1062       SVN_ERR(usage(argv[0], pool));
1063       *exit_code = EXIT_FAILURE;
1064       return SVN_NO_ERROR;
1065     }
1066   else
1067     {
1068       const char *repos_url;
1069
1070       SVN_ERR(svn_utf_cstring_to_utf8(&repos_url, os->argv[os->ind], pool));
1071       if (! svn_path_is_url(repos_url))
1072         {
1073           return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1074                                    "Target '%s' is not a URL",
1075                                    repos_url);
1076         }
1077       opt_baton->url = svn_uri_canonicalize(repos_url, pool);
1078     }
1079
1080   if (strcmp(subcommand->name, "load") == 0)
1081     {
1082       /*
1083        * By default (no --*-interactive options given), the 'load' subcommand
1084        * is interactive unless username and password were provided on the
1085        * command line. This allows prompting for auth creds to work without
1086        * requiring users to remember to use --force-interactive.
1087        * See issue #3913, "svnrdump load is not working in interactive mode".
1088        */
1089       if (!non_interactive && !force_interactive)
1090         force_interactive = (username == NULL || password == NULL);
1091     }
1092
1093   /* Get password from stdin if necessary */
1094   if (read_pass_from_stdin)
1095     {
1096       SVN_ERR(svn_cmdline__stdin_readline(&password, pool, pool));
1097     }
1098
1099   non_interactive = !svn_cmdline__be_interactive(non_interactive,
1100                                                  force_interactive);
1101
1102   SVN_ERR(init_client_context(&(opt_baton->ctx),
1103                               non_interactive,
1104                               username,
1105                               password,
1106                               config_dir,
1107                               opt_baton->url,
1108                               no_auth_cache,
1109                               trust_unknown_ca,
1110                               trust_cn_mismatch,
1111                               trust_expired,
1112                               trust_not_yet_valid,
1113                               trust_other_failure,
1114                               config_options,
1115                               pool));
1116
1117   err = svn_client_open_ra_session2(&(opt_baton->session),
1118                                     opt_baton->url, NULL,
1119                                     opt_baton->ctx, pool, pool);
1120
1121   /* Have sane opt_baton->start_revision and end_revision defaults if
1122      unspecified.  */
1123   if (!err)
1124     err = svn_ra_get_latest_revnum(opt_baton->session, &latest_revision, pool);
1125
1126   /* Make sure any provided revisions make sense. */
1127   if (!err)
1128     err = validate_and_resolve_revisions(opt_baton, latest_revision, pool);
1129
1130   /* Dispatch the subcommand */
1131   if (!err)
1132     err = (*subcommand->cmd_func)(os, opt_baton, pool);
1133
1134   if (err && err->apr_err == SVN_ERR_AUTHN_FAILED && non_interactive)
1135     {
1136       return svn_error_quick_wrap(err,
1137                                   _("Authentication failed and interactive"
1138                                     " prompting is disabled; see the"
1139                                     " --force-interactive option"));
1140     }
1141   else if (err)
1142     return err;
1143   else
1144     return SVN_NO_ERROR;
1145 }
1146
1147 int
1148 main(int argc, const char *argv[])
1149 {
1150   apr_pool_t *pool;
1151   int exit_code = EXIT_SUCCESS;
1152   svn_error_t *err;
1153
1154   /* Initialize the app. */
1155   if (svn_cmdline_init("svnrdump", stderr) != EXIT_SUCCESS)
1156     return EXIT_FAILURE;
1157
1158   /* Create our top-level pool.  Use a separate mutexless allocator,
1159    * given this application is single threaded.
1160    */
1161   pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
1162
1163   err = sub_main(&exit_code, argc, argv, pool);
1164
1165   /* Flush stdout and report if it fails. It would be flushed on exit anyway
1166      but this makes sure that output is not silently lost if it fails. */
1167   err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
1168
1169   if (err)
1170     {
1171       exit_code = EXIT_FAILURE;
1172       svn_cmdline_handle_exit_error(err, NULL, "svnrdump: ");
1173     }
1174
1175   svn_pool_destroy(pool);
1176
1177   svn_cmdline__cancellation_exit();
1178
1179   return exit_code;
1180 }