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>
27 #include "svn_pools.h"
28 #include "svn_cmdline.h"
29 #include "svn_client.h"
32 #include "svn_repos.h"
35 #include "svn_private_config.h"
36 #include "svn_string.h"
37 #include "svn_props.h"
39 #include "dump_editor.h"
40 #include "load_editor.h"
42 #include "private/svn_cmdline_private.h"
46 /*** Cancellation ***/
48 /* A flag to see if we've been cancelled by the client or not. */
49 static volatile sig_atomic_t cancelled
= FALSE
;
51 /* A signal handler to support cancellation. */
53 signal_handler(int signum
)
55 apr_signal(signum
, SIG_IGN
);
59 /* Our cancellation callback. */
61 check_cancel(void *baton
)
64 return svn_error_create(SVN_ERR_CANCELLED
, NULL
, _("Caught signal"));
72 static svn_opt_subcommand_t dump_cmd
, load_cmd
;
74 enum svn_svnrdump__longopt_t
76 opt_config_dir
= SVN_OPT_FIRST_LONGOPT_ID
,
85 static const svn_opt_subcommand_desc2_t svnrdump__cmd_table
[] =
87 { "dump", dump_cmd
, { 0 },
88 N_("usage: svnrdump dump URL [-r LOWER[:UPPER]]\n\n"
89 "Dump revisions LOWER to UPPER of repository at remote URL "
90 "to stdout in a 'dumpfile' portable format.\n"
91 "If only LOWER is given, dump that one revision.\n"),
93 { "load", load_cmd
, { 0 },
94 N_("usage: svnrdump load URL\n\n"
95 "Load a 'dumpfile' given on stdin to a repository "
98 { "help", 0, { "?", "h" },
99 N_("usage: svnrdump help [SUBCOMMAND...]\n\n"
100 "Describe the usage of this program or its subcommands.\n"),
102 { NULL
, NULL
, { 0 }, NULL
, { 0 } }
105 static const apr_getopt_option_t svnrdump__options
[] =
108 N_("specify revision number ARG (or X:Y range)")},
110 N_("no progress (only errors) to stderr")},
111 {"config-dir", opt_config_dir
, 1,
112 N_("read user configuration files from directory ARG")},
113 {"username", opt_auth_username
, 1,
114 N_("specify a username ARG")},
115 {"password", opt_auth_password
, 1,
116 N_("specify a password ARG")},
117 {"non-interactive", opt_non_interactive
, 0,
118 N_("do no interactive prompting")},
119 {"no-auth-cache", opt_auth_nocache
, 0,
120 N_("do not cache authentication tokens")},
122 N_("display this help")},
123 {"version", opt_version
, 0,
124 N_("show program version information")},
125 {"config-option", opt_config_option
, 1,
126 N_("set user configuration option in the format:\n"
128 " FILE:SECTION:OPTION=[VALUE]\n"
132 " servers:global:http-library=serf")},
136 /* Baton for the RA replay session. */
137 struct replay_baton
{
138 /* The editor producing diffs. */
139 const svn_delta_editor_t
*editor
;
141 /* Baton for the editor. */
144 /* Whether to be quiet. */
149 typedef struct opt_baton_t
{
150 svn_ra_session_t
*session
;
152 svn_revnum_t start_revision
;
153 svn_revnum_t end_revision
;
157 /* Print dumpstream-formatted information about REVISION.
158 * Implements the `svn_ra_replay_revstart_callback_t' interface.
161 replay_revstart(svn_revnum_t revision
,
163 const svn_delta_editor_t
**editor
,
165 apr_hash_t
*rev_props
,
168 struct replay_baton
*rb
= replay_baton
;
169 svn_stringbuf_t
*propstring
;
170 svn_stream_t
*stdout_stream
;
171 svn_stream_t
*revprop_stream
;
173 svn_stream_for_stdout(&stdout_stream
, pool
);
175 /* Revision-number: 19 */
176 SVN_ERR(svn_stream_printf(stdout_stream
, pool
,
177 SVN_REPOS_DUMPFILE_REVISION_NUMBER
178 ": %ld\n", revision
));
179 SVN_ERR(normalize_props(rev_props
, pool
));
180 propstring
= svn_stringbuf_create_ensure(0, pool
);
181 revprop_stream
= svn_stream_from_stringbuf(propstring
, pool
);
182 SVN_ERR(svn_hash_write2(rev_props
, revprop_stream
, "PROPS-END", pool
));
183 SVN_ERR(svn_stream_close(revprop_stream
));
185 /* Prop-content-length: 13 */
186 SVN_ERR(svn_stream_printf(stdout_stream
, pool
,
187 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
188 ": %" APR_SIZE_T_FMT
"\n", propstring
->len
));
190 /* Content-length: 29 */
191 SVN_ERR(svn_stream_printf(stdout_stream
, pool
,
192 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
193 ": %" APR_SIZE_T_FMT
"\n\n", propstring
->len
));
196 SVN_ERR(svn_stream_write(stdout_stream
, propstring
->data
,
197 &(propstring
->len
)));
199 SVN_ERR(svn_stream_printf(stdout_stream
, pool
, "\n"));
200 SVN_ERR(svn_stream_close(stdout_stream
));
202 /* Extract editor and editor_baton from the replay_baton and
203 set them so that the editor callbacks can use them. */
204 *editor
= rb
->editor
;
205 *edit_baton
= rb
->edit_baton
;
210 /* Print progress information about the dump of REVISION.
211 Implements the `svn_ra_replay_revfinish_callback_t' interface. */
213 replay_revend(svn_revnum_t revision
,
215 const svn_delta_editor_t
*editor
,
217 apr_hash_t
*rev_props
,
220 /* No resources left to free. */
221 struct replay_baton
*rb
= replay_baton
;
223 svn_cmdline_fprintf(stderr
, pool
, "* Dumped revision %lu.\n", revision
);
227 /* Set *SESSION to a new RA session opened to URL. Allocate *SESSION
228 * and related data structures in POOL. Use CONFIG_DIR and pass
229 * USERNAME, PASSWORD, CONFIG_DIR and NO_AUTH_CACHE to initialize the
230 * authorization baton. CONFIG_OPTIONS (if not NULL) is a list of
231 * configuration overrides.
234 open_connection(svn_ra_session_t
**session
,
236 svn_boolean_t non_interactive
,
237 const char *username
,
238 const char *password
,
239 const char *config_dir
,
240 svn_boolean_t no_auth_cache
,
241 apr_array_header_t
*config_options
,
244 svn_client_ctx_t
*ctx
= NULL
;
245 svn_config_t
*cfg_config
;
247 SVN_ERR(svn_ra_initialize(pool
));
249 SVN_ERR(svn_config_ensure(config_dir
, pool
));
250 SVN_ERR(svn_client_create_context(&ctx
, pool
));
252 SVN_ERR(svn_config_get_config(&(ctx
->config
), config_dir
, pool
));
255 SVN_ERR(svn_cmdline__apply_config_options(ctx
->config
, config_options
,
256 "svnrdump: ", "--config-option"));
258 cfg_config
= apr_hash_get(ctx
->config
, SVN_CONFIG_CATEGORY_CONFIG
,
259 APR_HASH_KEY_STRING
);
261 /* Set up our cancellation support. */
262 ctx
->cancel_func
= check_cancel
;
264 /* Default authentication providers for non-interactive use */
265 SVN_ERR(svn_cmdline_create_auth_baton(&(ctx
->auth_baton
), non_interactive
,
266 username
, password
, config_dir
,
267 no_auth_cache
, FALSE
, cfg_config
,
268 ctx
->cancel_func
, ctx
->cancel_baton
,
270 SVN_ERR(svn_client_open_ra_session(session
, url
, ctx
, pool
));
274 /* Replay revisions START_REVISION thru END_REVISION (inclusive) of
275 * the repository located at URL, using callbacks which generate
276 * Subversion repository dumpstreams describing the changes made in
277 * those revisions. If QUIET is set, don't generate progress
281 replay_revisions(svn_ra_session_t
*session
,
283 svn_revnum_t start_revision
,
284 svn_revnum_t end_revision
,
288 const svn_delta_editor_t
*dump_editor
;
289 struct replay_baton
*replay_baton
;
292 svn_stream_t
*stdout_stream
;
294 SVN_ERR(svn_stream_for_stdout(&stdout_stream
, pool
));
296 SVN_ERR(get_dump_editor(&dump_editor
, &dump_baton
, stdout_stream
,
297 check_cancel
, NULL
, pool
));
299 replay_baton
= apr_pcalloc(pool
, sizeof(*replay_baton
));
300 replay_baton
->editor
= dump_editor
;
301 replay_baton
->edit_baton
= dump_baton
;
302 replay_baton
->quiet
= quiet
;
304 /* Write the magic header and UUID */
305 SVN_ERR(svn_stream_printf(stdout_stream
, pool
,
306 SVN_REPOS_DUMPFILE_MAGIC_HEADER
": %d\n\n",
307 SVN_REPOS_DUMPFILE_FORMAT_VERSION
));
308 SVN_ERR(svn_ra_get_uuid2(session
, &uuid
, pool
));
309 SVN_ERR(svn_stream_printf(stdout_stream
, pool
,
310 SVN_REPOS_DUMPFILE_UUID
": %s\n\n", uuid
));
312 /* Fake revision 0 if necessary */
313 if (start_revision
== 0)
315 apr_hash_t
*prophash
;
316 svn_stringbuf_t
*propstring
;
317 svn_stream_t
*propstream
;
318 SVN_ERR(svn_stream_printf(stdout_stream
, pool
,
319 SVN_REPOS_DUMPFILE_REVISION_NUMBER
320 ": %ld\n", start_revision
));
322 prophash
= apr_hash_make(pool
);
323 propstring
= svn_stringbuf_create("", pool
);
325 SVN_ERR(svn_ra_rev_proplist(session
, start_revision
,
328 propstream
= svn_stream_from_stringbuf(propstring
, pool
);
329 SVN_ERR(svn_hash_write2(prophash
, propstream
, "PROPS-END", pool
));
330 SVN_ERR(svn_stream_close(propstream
));
332 /* Property-content-length: 14; Content-length: 14 */
333 SVN_ERR(svn_stream_printf(stdout_stream
, pool
,
334 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
335 ": %" APR_SIZE_T_FMT
"\n",
337 SVN_ERR(svn_stream_printf(stdout_stream
, pool
,
338 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
339 ": %" APR_SIZE_T_FMT
"\n\n",
342 SVN_ERR(svn_stream_write(stdout_stream
, propstring
->data
,
343 &(propstring
->len
)));
344 SVN_ERR(svn_stream_printf(stdout_stream
, pool
, "\n"));
346 svn_cmdline_fprintf(stderr
, pool
, "* Dumped revision %lu.\n",
352 SVN_ERR(svn_ra_replay_range(session
, start_revision
, end_revision
,
353 0, TRUE
, replay_revstart
, replay_revend
,
354 replay_baton
, pool
));
355 SVN_ERR(svn_stream_close(stdout_stream
));
359 /* Read a dumpstream from stdin, and use it to feed a loader capable
360 * of transmitting that information to the repository located at URL
361 * (to which SESSION has been opened).
364 load_revisions(svn_ra_session_t
*session
,
369 apr_file_t
*stdin_file
;
370 svn_stream_t
*stdin_stream
;
371 const svn_repos_parse_fns2_t
*parser
;
374 apr_file_open_stdin(&stdin_file
, pool
);
375 stdin_stream
= svn_stream_from_aprfile2(stdin_file
, FALSE
, pool
);
377 SVN_ERR(get_dumpstream_loader(&parser
, &parse_baton
, session
, pool
));
378 SVN_ERR(drive_dumpstream_loader(stdin_stream
, parser
, parse_baton
,
379 session
, check_cancel
, NULL
, pool
));
381 svn_stream_close(stdin_stream
);
386 /* Return a program name for this program, the basename of the path
387 * represented by PROGNAME if not NULL; use "svnrdump" otherwise.
390 ensure_appname(const char *progname
,
396 return svn_dirent_basename(svn_dirent_internal_style(progname
, pool
), NULL
);
399 /* Print a simple usage string. */
401 usage(const char *progname
,
404 return svn_cmdline_fprintf(stderr
, pool
,
405 _("Type '%s help' for usage.\n"),
406 ensure_appname(progname
, pool
));
409 /* Print information about the version of this program and dependent
413 version(const char *progname
,
416 svn_stringbuf_t
*version_footer
=
417 svn_stringbuf_create(_("The following repository access (RA) modules "
418 "are available:\n\n"),
421 SVN_ERR(svn_ra_print_modules(version_footer
, pool
));
422 return svn_opt_print_help3(NULL
, ensure_appname(progname
, pool
),
423 TRUE
, FALSE
, version_footer
->data
,
424 NULL
, NULL
, NULL
, NULL
, NULL
, pool
);
428 /* A statement macro, similar to @c SVN_ERR, but returns an integer.
429 * Evaluate @a expr. If it yields an error, handle that error and
430 * return @c EXIT_FAILURE.
432 #define SVNRDUMP_ERR(expr) \
435 svn_error_t *svn_err__temp = (expr); \
438 svn_handle_error2(svn_err__temp, stderr, FALSE, "svnrdump: "); \
439 svn_error_clear(svn_err__temp); \
440 return EXIT_FAILURE; \
445 /* Handle the "dump" subcommand. Implements `svn_opt_subcommand_t'. */
447 dump_cmd(apr_getopt_t
*os
,
451 opt_baton_t
*opt_baton
= baton
;
452 return replay_revisions(opt_baton
->session
, opt_baton
->url
,
453 opt_baton
->start_revision
, opt_baton
->end_revision
,
454 opt_baton
->quiet
, pool
);
457 /* Handle the "load" subcommand. Implements `svn_opt_subcommand_t'. */
459 load_cmd(apr_getopt_t
*os
,
463 opt_baton_t
*opt_baton
= baton
;
464 return load_revisions(opt_baton
->session
, opt_baton
->url
,
465 opt_baton
->quiet
, pool
);
468 /* Handle the "help" subcommand. Implements `svn_opt_subcommand_t'. */
470 help_cmd(apr_getopt_t
*os
,
475 _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n"
476 "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n"
478 "Available subcommands:\n");
480 return svn_opt_print_help3(os
, "svnrdump", FALSE
, FALSE
, NULL
, header
,
481 svnrdump__cmd_table
, svnrdump__options
, NULL
,
486 main(int argc
, const char **argv
)
488 const svn_opt_subcommand_desc2_t
*subcommand
= NULL
;
489 opt_baton_t
*opt_baton
;
490 char *revision_cut
= NULL
;
491 svn_revnum_t latest_revision
= svn_opt_revision_unspecified
;
492 apr_pool_t
*pool
= NULL
;
493 const char *config_dir
= NULL
;
494 const char *username
= NULL
;
495 const char *password
= NULL
;
496 svn_boolean_t no_auth_cache
= FALSE
;
497 svn_boolean_t non_interactive
= FALSE
;
498 apr_array_header_t
*config_options
= NULL
;
500 const char *first_arg
;
502 if (svn_cmdline_init ("svnrdump", stderr
) != EXIT_SUCCESS
)
505 pool
= svn_pool_create(NULL
);
506 opt_baton
= apr_pcalloc(pool
, sizeof(*opt_baton
));
507 opt_baton
->start_revision
= svn_opt_revision_unspecified
;
508 opt_baton
->end_revision
= svn_opt_revision_unspecified
;
509 opt_baton
->url
= NULL
;
511 SVNRDUMP_ERR(svn_cmdline__getopt_init(&os
, argc
, argv
, pool
));
513 os
->interleave
= TRUE
; /* Options and arguments can be interleaved */
515 /* Set up our cancellation support. */
516 apr_signal(SIGINT
, signal_handler
);
518 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
519 apr_signal(SIGBREAK
, signal_handler
);
522 apr_signal(SIGHUP
, signal_handler
);
525 apr_signal(SIGTERM
, signal_handler
);
528 /* Disable SIGPIPE generation for the platforms that have it. */
529 apr_signal(SIGPIPE
, SIG_IGN
);
532 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
533 * working with large files when compiled against an APR that doesn't have
534 * large file support will crash the program, which is uncool. */
535 apr_signal(SIGXFSZ
, SIG_IGN
);
542 apr_status_t status
= apr_getopt_long(os
, svnrdump__options
, &opt
,
545 if (APR_STATUS_IS_EOF(status
))
547 if (status
!= APR_SUCCESS
)
549 SVNRDUMP_ERR(usage(argv
[0], pool
));
557 revision_cut
= strchr(opt_arg
, ':');
560 opt_baton
->start_revision
=
561 (svn_revnum_t
)strtoul(opt_arg
, &revision_cut
, 10);
562 opt_baton
->end_revision
=
563 (svn_revnum_t
)strtoul(revision_cut
+ 1, NULL
, 10);
567 opt_baton
->start_revision
=
568 (svn_revnum_t
)strtoul(opt_arg
, NULL
, 10);
569 opt_baton
->end_revision
= opt_baton
->start_revision
;
574 opt_baton
->quiet
= TRUE
;
577 config_dir
= opt_arg
;
580 SVNRDUMP_ERR(version(argv
[0], pool
));
584 SVNRDUMP_ERR(help_cmd(os
, opt_baton
, pool
));
587 case opt_auth_username
:
588 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&username
, opt_arg
, pool
));
590 case opt_auth_password
:
591 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&password
, opt_arg
, pool
));
593 case opt_auth_nocache
:
594 no_auth_cache
= TRUE
;
596 case opt_non_interactive
:
597 non_interactive
= TRUE
;
599 case opt_config_option
:
602 apr_array_make(pool
, 1,
603 sizeof(svn_cmdline__config_argument_t
*));
605 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&opt_arg
, opt_arg
, pool
));
606 SVNRDUMP_ERR(svn_cmdline__parse_config_option(config_options
,
611 if (os
->ind
>= os
->argc
)
613 svn_error_clear(svn_cmdline_fprintf(stderr
, pool
,
614 _("Subcommand argument required\n")));
615 SVNRDUMP_ERR(help_cmd(NULL
, NULL
, pool
));
616 svn_pool_destroy(pool
);
620 first_arg
= os
->argv
[os
->ind
++];
622 subcommand
= svn_opt_get_canonical_subcommand2(svnrdump__cmd_table
,
625 if (subcommand
== NULL
)
627 const char *first_arg_utf8
;
628 svn_error_t
*err
= svn_utf_cstring_to_utf8(&first_arg_utf8
,
631 return svn_cmdline_handle_exit_error(err
, pool
, "svnrdump: ");
632 svn_error_clear(svn_cmdline_fprintf(stderr
, pool
,
633 _("Unknown command: '%s'\n"),
635 SVNRDUMP_ERR(help_cmd(NULL
, NULL
, pool
));
636 svn_pool_destroy(pool
);
640 if (subcommand
&& strcmp(subcommand
->name
, "help") == 0)
642 SVNRDUMP_ERR(help_cmd(os
, opt_baton
, pool
));
646 /* Only continue if the only not option argument is a url */
647 if ((os
->ind
!= os
->argc
-1)
648 || !svn_path_is_url(os
->argv
[os
->ind
]))
650 SVNRDUMP_ERR(usage(argv
[0], pool
));
654 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&(opt_baton
->url
),
655 os
->argv
[os
->ind
], pool
));
657 opt_baton
->url
= svn_uri_canonicalize(os
->argv
[os
->ind
], pool
);
659 SVNRDUMP_ERR(open_connection(&(opt_baton
->session
),
669 /* Have sane opt_baton->start_revision and end_revision defaults if
671 SVNRDUMP_ERR(svn_ra_get_latest_revnum(opt_baton
->session
,
672 &latest_revision
, pool
));
673 if (opt_baton
->start_revision
== svn_opt_revision_unspecified
)
674 opt_baton
->start_revision
= 0;
675 if (opt_baton
->end_revision
== svn_opt_revision_unspecified
)
676 opt_baton
->end_revision
= latest_revision
;
677 if (opt_baton
->end_revision
> latest_revision
)
679 SVN_INT_ERR(svn_cmdline_fprintf(stderr
, pool
,
680 _("Revision %ld does not exist.\n"),
681 opt_baton
->end_revision
));
684 if (opt_baton
->end_revision
< opt_baton
->start_revision
)
686 SVN_INT_ERR(svn_cmdline_fprintf(stderr
, pool
,
687 _("LOWER cannot be greater "
692 /* Dispatch the subcommand */
693 SVNRDUMP_ERR((*subcommand
->cmd_func
)(os
, opt_baton
, pool
));
695 svn_pool_destroy(pool
);