Tweak “svnrdump load” tests to pass with svnadmin 1.6
[svnrdump.git] / svnrdump.c
bloba320b2436948306677aba1268d90506cd60bda82
1 /*
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
21 * under the License.
22 * ====================================================================
25 #include "svn_pools.h"
26 #include "svn_cmdline.h"
27 #include "svn_client.h"
28 #include "svn_hash.h"
29 #include "svn_ra.h"
30 #include "svn_repos.h"
31 #include "svn_path.h"
32 #include "svn_utf.h"
33 #include "svn_string.h"
34 #include "svn_props.h"
35 #include "svn_dirent_uri.h"
37 #include "svn17_compat.h"
38 #include "dump_editor.h"
39 #include "load_editor.h"
41 static svn_opt_subcommand_t dump_cmd, load_cmd;
43 enum svn_svnrdump__longopt_t
45 opt_config_dir = SVN_OPT_FIRST_LONGOPT_ID,
46 opt_auth_username,
47 opt_auth_password,
48 opt_non_interactive,
49 opt_auth_nocache,
50 opt_version,
51 opt_config_option,
54 static const svn_opt_subcommand_desc2_t svnrdump__cmd_table[] =
56 { "dump", dump_cmd, { "d" },
57 N_("usage: svnrdump dump URL [-r LOWER[:UPPER]]\n\n"
58 "Dump revisions LOWER to UPPER of repository at remote URL "
59 "to stdout in a 'dumpfile' portable format.\n"
60 "If omitted, LOWER defaults to zero and UPPER to the latest "
61 "latest revision.\n"),
62 { 0 } },
63 { "load", load_cmd, { "l" },
64 N_("usage: svnrdump load URL\n\n"
65 "Load a 'dumpfile' given on stdin to a repository "
66 "at remote URL.\n"
67 "## This feature is not yet available.\n"),
68 { 0 } },
69 { "help", 0, { "?", "h" },
70 N_("usage: svnrdump help [SUBCOMMAND...]\n\n"
71 "Describe the usage of this program or its subcommands.\n"),
72 { 0 } },
73 { NULL, NULL, { 0 }, NULL, { 0 } }
76 static const apr_getopt_option_t svnrdump__options[] =
78 {"revision", 'r', 1, N_("REV1[:REV2] range of revisions to dump")},
79 {"quiet", 'q', 0, N_("no progress (only errors) to stderr")},
80 {"config-dir", opt_config_dir, 1, N_("read user configuration files from"
81 " directory ARG") },
82 {"username", opt_auth_username, 1, N_("specify a username ARG")},
83 {"password", opt_auth_password, 1, N_("specify a password ARG")},
84 {"non-interactive", opt_non_interactive, 0, N_("do no interactive"
85 " prompting")},
86 {"no-auth-cache", opt_auth_nocache, 0, N_("do not cache authentication"
87 " tokens")},
89 {"help", 'h', 0, N_("display this help")},
90 {"version", opt_version, 0, N_("show program version information")},
91 {"config-option", opt_config_option, 1,
92 N_("set user configuration option in the format:\n"
93 " "
94 " FILE:SECTION:OPTION=[VALUE]\n"
95 " "
96 "For example:\n"
97 " "
98 " servers:global:http-library=serf")},
99 {0, 0, 0, 0}
102 /* Baton for the RA replay session. */
103 struct replay_baton {
104 /* The editor producing diffs. */
105 const svn_delta_editor_t *editor;
107 /* Baton for the editor. */
108 void *edit_baton;
110 /* Whether to be quiet. */
111 svn_boolean_t quiet;
114 typedef struct {
115 svn_ra_session_t *session;
116 const char *url;
117 svn_revnum_t start_revision;
118 svn_revnum_t end_revision;
119 svn_boolean_t quiet;
120 } opt_baton_t;
122 static svn_error_t *
123 replay_revstart(svn_revnum_t revision,
124 void *replay_baton,
125 const svn_delta_editor_t **editor,
126 void **edit_baton,
127 apr_hash_t *rev_props,
128 apr_pool_t *pool)
130 struct replay_baton *rb = replay_baton;
131 svn_stringbuf_t *propstring;
132 svn_stream_t *stdout_stream;
133 svn_stream_t *revprop_stream;
135 svn_stream_for_stdout(&stdout_stream, pool);
137 /* Revision-number: 19 */
138 SVN_ERR(svn_stream_printf(stdout_stream, pool,
139 SVN_REPOS_DUMPFILE_REVISION_NUMBER
140 ": %ld\n", revision));
141 propstring = svn_stringbuf_create_ensure(0, pool);
142 revprop_stream = svn_stream_from_stringbuf(propstring, pool);
143 SVN_ERR(svn_hash_write2(rev_props, revprop_stream, "PROPS-END", pool));
144 SVN_ERR(svn_stream_close(revprop_stream));
146 /* Prop-content-length: 13 */
147 SVN_ERR(svn_stream_printf(stdout_stream, pool,
148 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
149 ": %" APR_SIZE_T_FMT "\n", propstring->len));
151 /* Content-length: 29 */
152 SVN_ERR(svn_stream_printf(stdout_stream, pool,
153 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
154 ": %" APR_SIZE_T_FMT "\n\n", propstring->len));
156 /* Property data. */
157 SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
158 &(propstring->len)));
160 SVN_ERR(svn_stream_printf(stdout_stream, pool, "\n"));
161 SVN_ERR(svn_stream_close(stdout_stream));
163 /* Extract editor and editor_baton from the replay_baton and
164 set them so that the editor callbacks can use them. */
165 *editor = rb->editor;
166 *edit_baton = rb->edit_baton;
168 return SVN_NO_ERROR;
171 static svn_error_t *
172 replay_revend(svn_revnum_t revision,
173 void *replay_baton,
174 const svn_delta_editor_t *editor,
175 void *edit_baton,
176 apr_hash_t *rev_props,
177 apr_pool_t *pool)
179 /* No resources left to free. */
180 struct replay_baton *rb = replay_baton;
181 if (! rb->quiet)
182 svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu\n", revision);
183 return SVN_NO_ERROR;
186 /* Return in *SESSION a new RA session to URL.
187 * Allocate *SESSION and related data structures in POOL.
188 * Use CONFIG_DIR and pass USERNAME, PASSWORD, CONFIG_DIR and
189 * NO_AUTH_CACHE to initialize the authorization baton.
190 * CONFIG_OPTIONS (if not NULL) is a list of configuration overrides. */
191 static svn_error_t *
192 open_connection(svn_ra_session_t **session,
193 const char *url,
194 svn_boolean_t non_interactive,
195 const char *username,
196 const char *password,
197 const char *config_dir,
198 svn_boolean_t no_auth_cache,
199 apr_array_header_t *config_options,
200 apr_pool_t *pool)
202 svn_client_ctx_t *ctx = NULL;
203 svn_config_t *cfg_config;
205 SVN_ERR(svn_ra_initialize(pool));
207 SVN_ERR(svn_config_ensure(config_dir, pool));
208 SVN_ERR(svn_client_create_context(&ctx, pool));
210 SVN_ERR(svn_config_get_config(&(ctx->config), config_dir, pool));
212 if (config_options)
213 SVN_ERR(svn_cmdline__apply_config_options(ctx->config, config_options,
214 "svnrdump: ", "--config-option"));
216 cfg_config = apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG,
217 APR_HASH_KEY_STRING);
219 /* Default authentication providers for non-interactive use */
220 SVN_ERR(svn_cmdline_create_auth_baton(&(ctx->auth_baton), non_interactive,
221 username, password, config_dir,
222 no_auth_cache, FALSE, cfg_config,
223 ctx->cancel_func, ctx->cancel_baton,
224 pool));
225 SVN_ERR(svn_client_open_ra_session(session, url, ctx, pool));
226 return SVN_NO_ERROR;
229 static svn_error_t *
230 replay_revisions(svn_ra_session_t *session, const char *url,
231 svn_revnum_t start_revision, svn_revnum_t end_revision,
232 svn_boolean_t quiet, apr_pool_t *pool)
234 const svn_delta_editor_t *dump_editor;
235 struct replay_baton *replay_baton;
236 void *dump_baton;
237 const char *uuid;
238 svn_stream_t *stdout_stream;
240 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
242 SVN_ERR(get_dump_editor(&dump_editor, &dump_baton, stdout_stream, pool));
244 replay_baton = apr_pcalloc(pool, sizeof(*replay_baton));
245 replay_baton->editor = dump_editor;
246 replay_baton->edit_baton = dump_baton;
247 replay_baton->quiet = quiet;
249 /* Write the magic header and UUID */
250 SVN_ERR(svn_stream_printf(stdout_stream, pool,
251 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
252 SVN_REPOS_DUMPFILE_FORMAT_VERSION));
253 SVN_ERR(svn_ra_get_uuid2(session, &uuid, pool));
254 SVN_ERR(svn_stream_printf(stdout_stream, pool,
255 SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid));
257 /* Fake revision 0 if necessary */
258 if (start_revision == 0)
260 apr_hash_t *prophash;
261 svn_stringbuf_t *propstring;
262 svn_stream_t *propstream;
263 SVN_ERR(svn_stream_printf(stdout_stream, pool,
264 SVN_REPOS_DUMPFILE_REVISION_NUMBER
265 ": %ld\n", start_revision));
267 prophash = apr_hash_make(pool);
268 propstring = svn_stringbuf_create("", pool);
270 SVN_ERR(svn_ra_rev_proplist(session, start_revision,
271 &prophash, pool));
273 propstream = svn_stream_from_stringbuf(propstring, pool);
274 SVN_ERR(svn_hash_write2(prophash, propstream, "PROPS-END", pool));
275 SVN_ERR(svn_stream_close(propstream));
277 /* Property-content-length: 14; Content-length: 14 */
278 SVN_ERR(svn_stream_printf(stdout_stream, pool,
279 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
280 ": %" APR_SIZE_T_FMT "\n",
281 propstring->len));
282 SVN_ERR(svn_stream_printf(stdout_stream, pool,
283 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
284 ": %" APR_SIZE_T_FMT "\n\n",
285 propstring->len));
286 /* The properties */
287 SVN_ERR(svn_stream_write(stdout_stream, propstring->data,
288 &(propstring->len)));
289 SVN_ERR(svn_stream_printf(stdout_stream, pool, "\n"));
290 if (! quiet)
291 svn_cmdline_fprintf(stderr, pool, "* Dumped revision %lu\n", start_revision);
293 start_revision++;
296 SVN_ERR(svn_ra_replay_range(session, start_revision, end_revision,
297 0, TRUE, replay_revstart, replay_revend,
298 replay_baton, pool));
299 SVN_ERR(svn_stream_close(stdout_stream));
300 return SVN_NO_ERROR;
303 static svn_error_t *
304 load_revisions(svn_ra_session_t *session, const char *url,
305 svn_boolean_t quiet, apr_pool_t *pool)
307 apr_file_t *stdin_file;
308 svn_stream_t *stdin_stream;
309 const svn_repos_parse_fns2_t *parser;
310 void *parse_baton;
312 apr_file_open_stdin(&stdin_file, pool);
313 stdin_stream = svn_stream_from_aprfile2(stdin_file, FALSE, pool);
315 SVN_ERR(get_dumpstream_loader(&parser, &parse_baton, session, pool));
316 SVN_ERR(drive_dumpstream_loader(stdin_stream, parser, parse_baton, session, pool));
318 svn_stream_close(stdin_stream);
320 return SVN_NO_ERROR;
324 static const char *
325 ensure_appname(const char *progname, apr_pool_t *pool)
327 if (!progname)
328 return "svnrdump";
330 progname = svn_dirent_internal_style(progname, pool);
331 return svn_dirent_basename(progname, NULL);
334 static svn_error_t *
335 usage(const char *progname, apr_pool_t *pool)
337 progname = ensure_appname(progname, pool);
339 SVN_ERR(svn_cmdline_fprintf(stderr, pool,
340 _("Type '%s help' for usage.\n"),
341 progname));
342 return SVN_NO_ERROR;
345 static svn_error_t *
346 version(const char *progname, apr_pool_t *pool)
348 const char *ra_desc_start
349 = _("The following repository access (RA) modules are available:\n\n");
351 svn_stringbuf_t *version_footer = svn_stringbuf_create(ra_desc_start,
352 pool);
354 SVN_ERR(svn_ra_print_modules(version_footer, pool));
356 progname = ensure_appname(progname, pool);
358 return svn_opt_print_help3(NULL, progname, TRUE, FALSE,
359 version_footer->data, NULL, NULL,
360 NULL, NULL, NULL, pool);
364 /** A statement macro, similar to @c SVN_ERR, but returns an integer.
366 * Evaluate @a expr. If it yields an error, handle that error and
367 * return @c EXIT_FAILURE.
369 #define SVNRDUMP_ERR(expr) \
370 do \
372 svn_error_t *svn_err__temp = (expr); \
373 if (svn_err__temp) \
375 svn_handle_error2(svn_err__temp, stderr, FALSE, "svnrdump: "); \
376 svn_error_clear(svn_err__temp); \
377 return EXIT_FAILURE; \
380 while (0)
382 static svn_error_t *
383 dump_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
385 opt_baton_t *opt_baton = baton;
386 SVN_ERR(replay_revisions(opt_baton->session, opt_baton->url,
387 opt_baton->start_revision, opt_baton->end_revision,
388 opt_baton->quiet, pool));
389 return SVN_NO_ERROR;
392 static svn_error_t *
393 load_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
395 opt_baton_t *opt_baton = baton;
396 SVN_ERR(load_revisions(opt_baton->session, opt_baton->url,
397 opt_baton->quiet, pool));
398 return SVN_NO_ERROR;
401 static svn_error_t *
402 help_cmd(apr_getopt_t *os, void *baton, apr_pool_t *pool)
404 const char *header =
405 _("general usage: svnrdump SUBCOMMAND URL [-r LOWER[:UPPER]]\n"
406 "Type 'svnrdump help <subcommand>' for help on a specific subcommand.\n\n"
407 "Available subcommands:\n");
409 SVN_ERR(svn_opt_print_help3(os, "svnrdump", FALSE, FALSE, NULL, header,
410 svnrdump__cmd_table, svnrdump__options, NULL,
411 NULL, pool));
413 return SVN_NO_ERROR;
417 main(int argc, const char **argv)
419 const svn_opt_subcommand_desc2_t *subcommand = NULL;
420 opt_baton_t *opt_baton;
421 char *revision_cut = NULL;
422 svn_revnum_t latest_revision = svn_opt_revision_unspecified;
423 apr_pool_t *pool = NULL;
424 const char *config_dir = NULL;
425 const char *username = NULL;
426 const char *password = NULL;
427 svn_boolean_t no_auth_cache = FALSE;
428 svn_boolean_t non_interactive = FALSE;
429 apr_array_header_t *config_options = NULL;
430 apr_getopt_t *os;
431 const char *first_arg;
433 if (svn_cmdline_init ("svnrdump", stderr) != EXIT_SUCCESS)
434 return EXIT_FAILURE;
436 pool = svn_pool_create(NULL);
437 opt_baton = apr_pcalloc(pool, sizeof(*opt_baton));
438 opt_baton->start_revision = svn_opt_revision_unspecified;
439 opt_baton->end_revision = svn_opt_revision_unspecified;
440 opt_baton->url = NULL;
442 SVNRDUMP_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
444 os->interleave = TRUE; /* Options and arguments can be interleaved */
446 while (1)
448 int opt;
449 const char *opt_arg;
450 apr_status_t status = apr_getopt_long(os, svnrdump__options, &opt,
451 &opt_arg);
453 if (APR_STATUS_IS_EOF(status))
454 break;
455 if (status != APR_SUCCESS)
457 SVNRDUMP_ERR(usage(argv[0], pool));
458 exit(EXIT_FAILURE);
461 switch(opt)
463 case 'r':
465 revision_cut = strchr(opt_arg, ':');
466 if (revision_cut)
468 opt_baton->start_revision = (svn_revnum_t)strtoul(opt_arg,
469 &revision_cut, 10);
470 opt_baton->end_revision = (svn_revnum_t)strtoul(revision_cut + 1,
471 NULL, 10);
473 else
474 opt_baton->start_revision = (svn_revnum_t)strtoul(opt_arg, NULL, 10);
476 break;
477 case 'q':
478 opt_baton->quiet = TRUE;
479 break;
480 case opt_config_dir:
481 config_dir = opt_arg;
482 break;
483 case opt_version:
484 SVNRDUMP_ERR(version(argv[0], pool));
485 exit(EXIT_SUCCESS);
486 break;
487 case 'h':
488 SVNRDUMP_ERR(help_cmd(os, opt_baton, pool));
489 exit(EXIT_SUCCESS);
490 break;
491 case opt_auth_username:
492 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&username, opt_arg, pool));
493 break;
494 case opt_auth_password:
495 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&password, opt_arg, pool));
496 break;
497 case opt_auth_nocache:
498 no_auth_cache = TRUE;
499 break;
500 case opt_non_interactive:
501 non_interactive = TRUE;
502 break;
503 case opt_config_option:
504 if (!config_options)
505 config_options =
506 apr_array_make(pool, 1,
507 sizeof(svn_cmdline__config_argument_t*));
509 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&opt_arg, opt_arg, pool));
510 SVNRDUMP_ERR(svn_cmdline__parse_config_option(config_options,
511 opt_arg, pool));
515 if (os->ind >= os->argc)
517 svn_error_clear
518 (svn_cmdline_fprintf(stderr, pool,
519 _("Subcommand argument required\n")));
520 SVNRDUMP_ERR(help_cmd(NULL, NULL, pool));
521 svn_pool_destroy(pool);
522 exit(EXIT_FAILURE);
525 first_arg = os->argv[os->ind++];
527 subcommand = svn_opt_get_canonical_subcommand2(svnrdump__cmd_table,
528 first_arg);
530 if (subcommand == NULL)
532 const char *first_arg_utf8;
533 svn_error_t *err = svn_utf_cstring_to_utf8(&first_arg_utf8,
534 first_arg, pool);
535 if (err)
536 return svn_cmdline_handle_exit_error(err, pool, "svnrdump: ");
537 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
538 _("Unknown command: '%s'\n"),
539 first_arg_utf8));
540 SVNRDUMP_ERR(help_cmd(NULL, NULL, pool));
541 svn_pool_destroy(pool);
542 exit(EXIT_FAILURE);
545 if (subcommand && strcmp(subcommand->name, "help") == 0)
547 SVNRDUMP_ERR(help_cmd(os, opt_baton, pool));
548 exit(EXIT_SUCCESS);
551 /* Only continue if the only not option argument is a url */
552 if ((os->ind != os->argc-1)
553 || !svn_path_is_url(os->argv[os->ind]))
555 SVNRDUMP_ERR(usage(argv[0], pool));
556 exit(EXIT_FAILURE);
559 SVNRDUMP_ERR(svn_utf_cstring_to_utf8(&(opt_baton->url), os->argv[os->ind], pool));
561 opt_baton->url = svn_uri_canonicalize(os->argv[os->ind], pool);
563 SVNRDUMP_ERR(open_connection(&(opt_baton->session),
564 opt_baton->url,
565 non_interactive,
566 username,
567 password,
568 config_dir,
569 no_auth_cache,
570 config_options,
571 pool));
573 /* Have sane opt_baton->start_revision and end_revision defaults if unspecified */
574 SVNRDUMP_ERR(svn_ra_get_latest_revnum(opt_baton->session, &latest_revision, pool));
575 if (opt_baton->start_revision == svn_opt_revision_unspecified)
576 opt_baton->start_revision = 0;
577 if (opt_baton->end_revision == svn_opt_revision_unspecified)
578 opt_baton->end_revision = latest_revision;
579 if (opt_baton->end_revision > latest_revision)
581 SVN_INT_ERR(svn_cmdline_fprintf(stderr, pool,
582 _("Revision %ld does not exist.\n"),
583 opt_baton->end_revision));
584 exit(EXIT_FAILURE);
586 if (opt_baton->end_revision < opt_baton->start_revision)
588 SVN_INT_ERR(svn_cmdline_fprintf(stderr, pool,
589 _("LOWER cannot be greater "
590 "than UPPER.\n")));
591 exit(EXIT_FAILURE);
594 /* Dispatch the subcommand */
595 SVNRDUMP_ERR((*subcommand->cmd_func)(os, opt_baton, pool));
597 svn_pool_destroy(pool);
599 return 0;