2 * svnadmin.c: Subversion server administration tool main file.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
25 #include <apr_file_io.h>
28 #include "svn_pools.h"
29 #include "svn_cmdline.h"
30 #include "svn_error.h"
33 #include "svn_subst.h"
34 #include "svn_dirent_uri.h"
36 #include "svn_config.h"
37 #include "svn_repos.h"
38 #include "svn_cache_config.h"
39 #include "svn_version.h"
40 #include "svn_props.h"
41 #include "svn_sorts.h"
46 #include "private/svn_cmdline_private.h"
47 #include "private/svn_opt_private.h"
48 #include "private/svn_sorts_private.h"
49 #include "private/svn_subr_private.h"
50 #include "private/svn_cmdline_private.h"
51 #include "private/svn_fspath.h"
53 #include "svn_private_config.h"
58 /* FSFS format 7's "block-read" feature performs poorly with small caches.
59 * Enable it only if caches above this threshold have been configured.
60 * The current threshold is 64MB. */
61 #define BLOCK_READ_CACHE_THRESHOLD (0x40 * 0x100000)
63 static svn_cancel_func_t check_cancel = NULL;
65 /* Custom filesystem warning function. */
67 warning_func(void *baton,
72 svn_handle_warning2(stderr, err, "svnadmin: ");
76 /* Version compatibility check */
78 check_lib_versions(void)
80 static const svn_version_checklist_t checklist[] =
82 { "svn_subr", svn_subr_version },
83 { "svn_repos", svn_repos_version },
84 { "svn_fs", svn_fs_version },
85 { "svn_delta", svn_delta_version },
88 SVN_VERSION_DEFINE(my_version);
90 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
97 static svn_opt_subcommand_t
100 subcommand_delrevprop,
103 subcommand_dump_revprops,
109 subcommand_load_revprops,
110 subcommand_list_dblogs,
111 subcommand_list_unused_dblogs,
120 subcommand_setrevprop,
126 enum svnadmin__cmdline_options_t
128 svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
129 svnadmin__incremental,
130 svnadmin__keep_going,
132 svnadmin__ignore_uuid,
133 svnadmin__force_uuid,
135 svnadmin__parent_dir,
136 svnadmin__bdb_txn_nosync,
137 svnadmin__bdb_log_keep,
138 svnadmin__config_dir,
139 svnadmin__bypass_hooks,
140 svnadmin__bypass_prop_validation,
141 svnadmin__ignore_dates,
142 svnadmin__use_pre_commit_hook,
143 svnadmin__use_post_commit_hook,
144 svnadmin__use_pre_revprop_change_hook,
145 svnadmin__use_post_revprop_change_hook,
146 svnadmin__clean_logs,
148 svnadmin__pre_1_4_compatible,
149 svnadmin__pre_1_5_compatible,
150 svnadmin__pre_1_6_compatible,
151 svnadmin__compatible_version,
152 svnadmin__check_normalization,
153 svnadmin__metadata_only,
154 svnadmin__no_flush_to_disk,
155 svnadmin__normalize_props,
161 /* Option codes and descriptions.
163 * The entire list must be terminated with an entry of nulls.
165 static const apr_getopt_option_t options_table[] =
168 N_("show help on a subcommand")},
171 N_("show help on a subcommand")},
173 {"version", svnadmin__version, 0,
174 N_("show program version information")},
177 N_("specify revision number ARG (or X:Y range)")},
179 {"transaction", 't', 1,
180 N_("specify transaction name ARG")},
182 {"incremental", svnadmin__incremental, 0,
183 N_("dump or hotcopy incrementally")},
185 {"deltas", svnadmin__deltas, 0,
186 N_("use deltas in dump output")},
188 {"bypass-hooks", svnadmin__bypass_hooks, 0,
189 N_("bypass the repository hook system")},
191 {"bypass-prop-validation", svnadmin__bypass_prop_validation, 0,
192 N_("bypass property validation logic")},
194 {"ignore-dates", svnadmin__ignore_dates, 0,
195 N_("ignore revision datestamps found in the stream")},
198 N_("no progress (only errors to stderr)")},
200 {"ignore-uuid", svnadmin__ignore_uuid, 0,
201 N_("ignore any repos UUID found in the stream")},
203 {"force-uuid", svnadmin__force_uuid, 0,
204 N_("set repos UUID to that found in stream, if any")},
206 {"fs-type", svnadmin__fs_type, 1,
207 N_("type of repository:\n"
208 " 'fsfs' (default), 'bdb' or 'fsx'\n"
209 " CAUTION: FSX is for EXPERIMENTAL use only!")},
211 {"parent-dir", svnadmin__parent_dir, 1,
212 N_("load at specified directory in repository")},
214 {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
215 N_("disable fsync at transaction commit [Berkeley DB]")},
217 {"bdb-log-keep", svnadmin__bdb_log_keep, 0,
218 N_("disable automatic log file removal [Berkeley DB]")},
220 {"config-dir", svnadmin__config_dir, 1,
221 N_("read user configuration files from directory ARG")},
223 {"clean-logs", svnadmin__clean_logs, 0,
224 N_("remove redundant Berkeley DB log files\n"
225 " from source repository [Berkeley DB]")},
227 {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
228 N_("call pre-commit hook before committing revisions")},
230 {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
231 N_("call post-commit hook after committing revisions")},
233 {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
234 N_("call hook before changing revision property")},
236 {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
237 N_("call hook after changing revision property")},
239 {"wait", svnadmin__wait, 0,
240 N_("wait instead of exit if the repository is in\n"
241 " use by another process")},
243 {"pre-1.4-compatible", svnadmin__pre_1_4_compatible, 0,
244 N_("deprecated; see --compatible-version")},
246 {"pre-1.5-compatible", svnadmin__pre_1_5_compatible, 0,
247 N_("deprecated; see --compatible-version")},
249 {"pre-1.6-compatible", svnadmin__pre_1_6_compatible, 0,
250 N_("deprecated; see --compatible-version")},
252 {"keep-going", svnadmin__keep_going, 0,
253 N_("continue verification after detecting a corruption")},
255 {"memory-cache-size", 'M', 1,
256 N_("size of the extra in-memory cache in MB used to\n"
257 " minimize redundant operations. Default: 16.\n"
258 " [used for FSFS repositories only]")},
260 {"compatible-version", svnadmin__compatible_version, 1,
261 N_("use repository format compatible with Subversion\n"
262 " version ARG (\"1.5.5\", \"1.7\", etc.)")},
264 {"file", 'F', 1, N_("read repository paths from file ARG")},
266 {"check-normalization", svnadmin__check_normalization, 0,
267 N_("report any names within the same directory or\n"
268 " svn:mergeinfo property value that differ only\n"
269 " in character representation, but are otherwise\n"
272 {"metadata-only", svnadmin__metadata_only, 0,
273 N_("verify metadata only (ignored for BDB),\n"
274 " checking against external corruption in\n"
275 " Subversion 1.9+ format repositories.\n")},
277 {"no-flush-to-disk", svnadmin__no_flush_to_disk, 0,
278 N_("disable flushing to disk during the operation\n"
279 " (faster, but unsafe on power off)")},
281 {"normalize-props", svnadmin__normalize_props, 0,
282 N_("normalize property values found in the dumpstream\n"
283 " (currently, only translates non-LF line endings)")},
285 {"exclude", svnadmin__exclude, 1,
286 N_("filter out nodes with given prefix(es) from dump")},
288 {"include", svnadmin__include, 1,
289 N_("filter out nodes without given prefix(es) from dump")},
291 {"pattern", svnadmin__glob, 0,
292 N_("treat the path prefixes as file glob patterns.\n"
293 " Glob special characters are '*' '?' '[]' and '\\'.\n"
294 " Character '/' is not treated specially, so\n"
295 " pattern /*/foo matches paths /a/foo and /a/b/foo.") },
301 /* Array of available subcommands.
302 * The entire list must be terminated with an entry of nulls.
304 static const svn_opt_subcommand_desc2_t cmd_table[] =
306 {"crashtest", subcommand_crashtest, {0}, N_
307 ("usage: svnadmin crashtest REPOS_PATH\n\n"
308 "Open the repository at REPOS_PATH, then abort, thus simulating\n"
309 "a process that crashes while holding an open repository handle.\n"),
312 {"create", subcommand_create, {0}, N_
313 ("usage: svnadmin create REPOS_PATH\n\n"
314 "Create a new, empty repository at REPOS_PATH.\n"),
315 {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
316 svnadmin__config_dir, svnadmin__fs_type, svnadmin__compatible_version,
317 svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible,
318 svnadmin__pre_1_6_compatible
321 {"delrevprop", subcommand_delrevprop, {0}, N_
322 ("usage: 1. svnadmin delrevprop REPOS_PATH -r REVISION NAME\n"
323 " 2. svnadmin delrevprop REPOS_PATH -t TXN NAME\n\n"
324 "1. Delete the property NAME on revision REVISION.\n\n"
325 "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
326 "trigger the revision property-related hooks (for example, if you want\n"
327 "an email notification sent from your post-revprop-change hook).\n\n"
328 "NOTE: Revision properties are not versioned, so this command will\n"
329 "irreversibly destroy the previous value of the property.\n\n"
330 "2. Delete the property NAME on transaction TXN.\n"),
331 {'r', 't', svnadmin__use_pre_revprop_change_hook,
332 svnadmin__use_post_revprop_change_hook} },
334 {"deltify", subcommand_deltify, {0}, N_
335 ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n"
336 "Run over the requested revision range, performing predecessor delti-\n"
337 "fication on the paths changed in those revisions. Deltification in\n"
338 "essence compresses the repository by only storing the differences or\n"
339 "delta from the preceding revision. If no revisions are specified,\n"
340 "this will simply deltify the HEAD revision.\n"),
343 {"dump", subcommand_dump, {0}, N_
344 ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]]\n\n"
345 "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
346 "portable format, sending feedback to stderr. Dump revisions\n"
347 "LOWER rev through UPPER rev. If no revisions are given, dump all\n"
348 "revision trees. If only LOWER is given, dump that one revision tree.\n"
349 "If --incremental is passed, the first revision dumped will describe\n"
350 "only the paths changed in that revision; otherwise it will describe\n"
351 "every path present in the repository as of that revision. (In either\n"
352 "case, the second and subsequent revisions, if any, describe only paths\n"
353 "changed in those revisions.)\n"
355 "Using --exclude or --include gives results equivalent to authz-based\n"
356 "path exclusions. In particular, when the source of a copy is\n"
357 "excluded, the copy is transformed into an add (unlike in 'svndumpfilter').\n"),
358 {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M', 'F',
359 svnadmin__exclude, svnadmin__include, svnadmin__glob },
360 {{'F', N_("write to file ARG instead of stdout")}} },
362 {"dump-revprops", subcommand_dump_revprops, {0}, N_
363 ("usage: svnadmin dump-revprops REPOS_PATH [-r LOWER[:UPPER]]\n\n"
364 "Dump the revision properties of filesystem to stdout in a 'dumpfile'\n"
365 "portable format, sending feedback to stderr. Dump revisions\n"
366 "LOWER rev through UPPER rev. If no revisions are given, dump the\n"
367 "properties for all revisions. If only LOWER is given, dump the\n"
368 "properties for that one revision.\n"),
370 {{'F', N_("write to file ARG instead of stdout")}} },
372 {"freeze", subcommand_freeze, {0}, N_
373 ("usage: 1. svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n"
374 " 2. svnadmin freeze -F FILE PROGRAM [ARG...]\n\n"
375 "1. Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n"
376 " Allows safe use of third-party backup tools on a live repository.\n"
378 "2. Like 1 except all repositories listed in FILE are locked. The file\n"
379 " format is repository paths separated by newlines. Repositories are\n"
380 " locked in the same order as they are listed in the file.\n"),
382 {{'F', N_("read repository paths from file ARG")}} },
384 {"help", subcommand_help, {"?", "h"}, N_
385 ("usage: svnadmin help [SUBCOMMAND...]\n\n"
386 "Describe the usage of this program or its subcommands.\n"),
389 {"hotcopy", subcommand_hotcopy, {0}, N_
390 ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n"
391 "Make a hot copy of a repository.\n"
392 "If --incremental is passed, data which already exists at the destination\n"
393 "is not copied again. Incremental mode is implemented for FSFS repositories.\n"),
394 {svnadmin__clean_logs, svnadmin__incremental, 'q'} },
396 {"info", subcommand_info, {0}, N_
397 ("usage: svnadmin info REPOS_PATH\n\n"
398 "Print information about the repository at REPOS_PATH.\n"),
401 {"list-dblogs", subcommand_list_dblogs, {0}, N_
402 ("usage: svnadmin list-dblogs REPOS_PATH\n\n"
403 "List all Berkeley DB log files.\n\n"
404 "WARNING: Modifying or deleting logfiles which are still in use\n"
405 "will cause your repository to be corrupted.\n"),
408 {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_
409 ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n"
410 "List unused Berkeley DB log files.\n\n"),
413 {"load", subcommand_load, {0}, N_
414 ("usage: svnadmin load REPOS_PATH\n\n"
415 "Read a 'dumpfile'-formatted stream from stdin, committing\n"
416 "new revisions into the repository's filesystem. If the repository\n"
417 "was previously empty, its UUID will, by default, be changed to the\n"
418 "one specified in the stream. Progress feedback is sent to stdout.\n"
419 "If --revision is specified, limit the loaded revisions to only those\n"
420 "in the dump stream whose revision numbers match the specified range.\n"),
421 {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid,
422 svnadmin__ignore_dates,
423 svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
424 svnadmin__parent_dir, svnadmin__normalize_props,
425 svnadmin__bypass_prop_validation, 'M',
426 svnadmin__no_flush_to_disk, 'F'},
427 {{'F', N_("read from file ARG instead of stdin")}} },
429 {"load-revprops", subcommand_load_revprops, {0}, N_
430 ("usage: svnadmin load-revprops REPOS_PATH\n\n"
431 "Read a 'dumpfile'-formatted stream from stdin, setting the revision\n"
432 "properties in the repository's filesystem. Revisions not found in the\n"
433 "repository will cause an error. Progress feedback is sent to stdout.\n"
434 "If --revision is specified, limit the loaded revisions to only those\n"
435 "in the dump stream whose revision numbers match the specified range.\n"),
436 {'q', 'r', svnadmin__force_uuid, svnadmin__normalize_props,
437 svnadmin__bypass_prop_validation, svnadmin__no_flush_to_disk, 'F'},
438 {{'F', N_("read from file ARG instead of stdin")}} },
440 {"lock", subcommand_lock, {0}, N_
441 ("usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE [TOKEN]\n\n"
442 "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n"
443 "If provided, use TOKEN as lock token. Use --bypass-hooks to avoid\n"
444 "triggering the pre-lock and post-lock hook scripts.\n"),
445 {svnadmin__bypass_hooks, 'q'} },
447 {"lslocks", subcommand_lslocks, {0}, N_
448 ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n"
449 "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
450 "if not provided, is the root of the repository).\n"),
453 {"lstxns", subcommand_lstxns, {0}, N_
454 ("usage: svnadmin lstxns REPOS_PATH\n\n"
455 "Print the names of uncommitted transactions. With -rN skip the output\n"
456 "of those that have a base revision more recent than rN. Transactions\n"
457 "with base revisions much older than HEAD are likely to have been\n"
458 "abandonded and are candidates to be removed.\n"),
460 { {'r', "transaction base revision ARG"} } },
462 {"pack", subcommand_pack, {0}, N_
463 ("usage: svnadmin pack REPOS_PATH\n\n"
464 "Possibly compact the repository into a more efficient storage model.\n"
465 "This may not apply to all repositories, in which case, exit.\n"),
468 {"recover", subcommand_recover, {0}, N_
469 ("usage: svnadmin recover REPOS_PATH\n\n"
470 "Run the recovery procedure on a repository. Do this if you've\n"
471 "been getting errors indicating that recovery ought to be run.\n"
472 "Berkeley DB recovery requires exclusive access and will\n"
473 "exit if the repository is in use by another process.\n"),
476 {"rmlocks", subcommand_rmlocks, {0}, N_
477 ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n"
478 "Unconditionally remove lock from each LOCKED_PATH.\n"),
481 {"rmtxns", subcommand_rmtxns, {0}, N_
482 ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n"
483 "Delete the named transaction(s).\n"),
486 {"setlog", subcommand_setlog, {0}, N_
487 ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n"
488 "Set the log-message on revision REVISION to the contents of FILE. Use\n"
489 "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
490 "(for example, if you do not want an email notification sent\n"
491 "from your post-revprop-change hook, or because the modification of\n"
492 "revision properties has not been enabled in the pre-revprop-change\n"
494 "NOTE: Revision properties are not versioned, so this command will\n"
495 "overwrite the previous log message.\n"),
496 {'r', svnadmin__bypass_hooks} },
498 {"setrevprop", subcommand_setrevprop, {0}, N_
499 ("usage: 1. svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n"
500 " 2. svnadmin setrevprop REPOS_PATH -t TXN NAME FILE\n\n"
501 "1. Set the property NAME on revision REVISION to the contents of FILE.\n\n"
502 "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
503 "trigger the revision property-related hooks (for example, if you want\n"
504 "an email notification sent from your post-revprop-change hook).\n\n"
505 "NOTE: Revision properties are not versioned, so this command will\n"
506 "overwrite the previous value of the property.\n\n"
507 "2. Set the property NAME on transaction TXN to the contents of FILE.\n"),
508 {'r', 't', svnadmin__use_pre_revprop_change_hook,
509 svnadmin__use_post_revprop_change_hook} },
511 {"setuuid", subcommand_setuuid, {0}, N_
512 ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n"
513 "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
514 "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
515 "generate a brand new UUID for the repository.\n"),
518 {"unlock", subcommand_unlock, {0}, N_
519 ("usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n\n"
520 "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n"
521 "associated with the lock matches TOKEN. Use --bypass-hooks to avoid\n"
522 "triggering the pre-unlock and post-unlock hook scripts.\n"),
523 {svnadmin__bypass_hooks, 'q'} },
525 {"upgrade", subcommand_upgrade, {0}, N_
526 ("usage: svnadmin upgrade REPOS_PATH\n\n"
527 "Upgrade the repository located at REPOS_PATH to the latest supported\n"
528 "schema version.\n\n"
529 "This functionality is provided as a convenience for repository\n"
530 "administrators who wish to make use of new Subversion functionality\n"
531 "without having to undertake a potentially costly full repository dump\n"
532 "and load operation. As such, the upgrade performs only the minimum\n"
533 "amount of work needed to accomplish this while still maintaining the\n"
534 "integrity of the repository. It does not guarantee the most optimized\n"
535 "repository state as a dump and subsequent load would.\n"),
538 {"verify", subcommand_verify, {0}, N_
539 ("usage: svnadmin verify REPOS_PATH\n\n"
540 "Verify the data stored in the repository.\n"),
541 {'t', 'r', 'q', svnadmin__keep_going, 'M',
542 svnadmin__check_normalization, svnadmin__metadata_only} },
544 { NULL, NULL, {0}, NULL, {0} }
548 /* Baton for passing option/argument state to a subcommand function. */
549 struct svnadmin_opt_state
551 const char *repository_path;
552 const char *fs_type; /* --fs-type */
553 svn_version_t *compatible_version; /* --compatible-version */
554 svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */
555 const char *txn_id; /* -t TXN */
556 svn_boolean_t help; /* --help or -? */
557 svn_boolean_t version; /* --version */
558 svn_boolean_t incremental; /* --incremental */
559 svn_boolean_t use_deltas; /* --deltas */
560 svn_boolean_t use_pre_commit_hook; /* --use-pre-commit-hook */
561 svn_boolean_t use_post_commit_hook; /* --use-post-commit-hook */
562 svn_boolean_t use_pre_revprop_change_hook; /* --use-pre-revprop-change-hook */
563 svn_boolean_t use_post_revprop_change_hook; /* --use-post-revprop-change-hook */
564 svn_boolean_t quiet; /* --quiet */
565 svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync */
566 svn_boolean_t bdb_log_keep; /* --bdb-log-keep */
567 svn_boolean_t clean_logs; /* --clean-logs */
568 svn_boolean_t bypass_hooks; /* --bypass-hooks */
569 svn_boolean_t wait; /* --wait */
570 svn_boolean_t keep_going; /* --keep-going */
571 svn_boolean_t check_normalization; /* --check-normalization */
572 svn_boolean_t metadata_only; /* --metadata-only */
573 svn_boolean_t bypass_prop_validation; /* --bypass-prop-validation */
574 svn_boolean_t ignore_dates; /* --ignore-dates */
575 svn_boolean_t no_flush_to_disk; /* --no-flush-to-disk */
576 svn_boolean_t normalize_props; /* --normalize_props */
577 enum svn_repos_load_uuid uuid_action; /* --ignore-uuid,
579 apr_uint64_t memory_cache_size; /* --memory-cache-size M */
580 const char *parent_dir; /* --parent-dir */
581 const char *file; /* --file */
582 apr_array_header_t *exclude; /* --exclude */
583 apr_array_header_t *include; /* --include */
584 svn_boolean_t glob; /* --pattern */
586 const char *config_dir; /* Overriding Configuration Directory */
590 /* Helper to open a repository and set a warning func (so we don't
591 * SEGFAULT when libsvn_fs's default handler gets run). */
593 open_repos(svn_repos_t **repos,
595 struct svnadmin_opt_state *opt_state,
598 /* Enable the "block-read" feature (where it applies)? */
599 svn_boolean_t use_block_read
600 = svn_cache_config_get()->cache_size > BLOCK_READ_CACHE_THRESHOLD;
602 /* construct FS configuration parameters: enable caches for r/o data */
603 apr_hash_t *fs_config = apr_hash_make(pool);
604 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1");
605 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1");
606 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NODEPROPS, "1");
607 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2");
608 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
609 svn_uuid_generate(pool));
610 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ,
611 use_block_read ? "1" : "0");
612 svn_hash_sets(fs_config, SVN_FS_CONFIG_NO_FLUSH_TO_DISK,
613 opt_state->no_flush_to_disk ? "1" : "0");
615 /* now, open the requested repository */
616 SVN_ERR(svn_repos_open3(repos, path, fs_config, pool, pool));
617 svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
622 /* Set *REVNUM to the revision specified by REVISION (or to
623 SVN_INVALID_REVNUM if that has the type 'unspecified'),
624 possibly making use of the YOUNGEST revision number in REPOS. */
626 get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
627 svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
629 if (revision->kind == svn_opt_revision_number)
630 *revnum = revision->value.number;
631 else if (revision->kind == svn_opt_revision_head)
633 else if (revision->kind == svn_opt_revision_date)
634 SVN_ERR(svn_repos_dated_revision(revnum, repos, revision->value.date,
636 else if (revision->kind == svn_opt_revision_unspecified)
637 *revnum = SVN_INVALID_REVNUM;
639 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
640 _("Invalid revision specifier"));
642 if (*revnum > youngest)
643 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
644 _("Revisions must not be greater than the youngest revision (%ld)"),
650 /* Set *FSPATH to an internal-style fspath parsed from ARG. */
652 target_arg_to_fspath(const char **fspath,
654 apr_pool_t *result_pool,
655 apr_pool_t *scratch_pool)
657 /* ### Using a private API. This really shouldn't be needed. */
658 *fspath = svn_fspath__canonicalize(arg, result_pool);
662 /* Set *DIRENT to an internal-style, local dirent path
663 allocated from POOL and parsed from PATH. */
665 target_arg_to_dirent(const char **dirent,
669 if (svn_path_is_url(path))
670 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
671 _("Path '%s' is not a local path"), path);
672 *dirent = svn_dirent_internal_style(path, pool);
676 /* Parse the remaining command-line arguments from OS, returning them
677 in a new array *ARGS (allocated from POOL) and optionally verifying
678 that we got the expected number thereof. If MIN_EXPECTED is not
679 negative, return an error if the function would return fewer than
680 MIN_EXPECTED arguments. If MAX_EXPECTED is not negative, return an
681 error if the function would return more than MAX_EXPECTED
684 As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0,
685 allow ARGS to be NULL. */
687 parse_args(apr_array_header_t **args,
693 int num_args = os ? (os->argc - os->ind) : 0;
695 if (min_expected || max_expected)
696 SVN_ERR_ASSERT(args);
698 if ((min_expected >= 0) && (num_args < min_expected))
699 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
700 _("Not enough arguments"));
701 if ((max_expected >= 0) && (num_args > max_expected))
702 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
703 _("Too many arguments"));
706 *args = apr_array_make(pool, num_args, sizeof(const char *));
709 while (os->ind < os->argc)
713 SVN_ERR(svn_utf_cstring_to_utf8(&arg, os->argv[os->ind++], pool));
714 APR_ARRAY_PUSH(*args, const char *) = arg;
722 /* This implements 'svn_error_malfunction_handler_t. */
724 crashtest_malfunction_handler(svn_boolean_t can_return,
730 return SVN_NO_ERROR; /* Not reached. */
733 /* This implements `svn_opt_subcommand_t'. */
735 subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
737 struct svnadmin_opt_state *opt_state = baton;
740 (void)svn_error_set_malfunction_handler(crashtest_malfunction_handler);
741 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
742 SVN_ERR(svn_cmdline_printf(pool,
743 _("Successfully opened repository '%s'.\n"
744 "Will now crash to simulate a crashing "
745 "server process.\n"),
746 svn_dirent_local_style(opt_state->repository_path,
748 SVN_ERR_MALFUNCTION();
750 /* merely silence a compiler warning (this will never be executed) */
754 /* This implements `svn_opt_subcommand_t'. */
756 subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
758 struct svnadmin_opt_state *opt_state = baton;
760 apr_hash_t *fs_config = apr_hash_make(pool);
762 /* Expect no more arguments. */
763 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
765 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
766 (opt_state->bdb_txn_nosync ? "1" :"0"));
768 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
769 (opt_state->bdb_log_keep ? "0" :"1"));
771 if (opt_state->fs_type)
773 /* With 1.8 we are announcing that BDB is deprecated. No support
774 * has been removed and it will continue to work until some future
775 * date. The purpose here is to discourage people from creating
776 * new BDB repositories which they will need to dump/load into
777 * FSFS or some new FS type in the future. */
778 if (0 == strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB))
780 SVN_ERR(svn_cmdline_fprintf(
783 " The \"%s\" repository back-end is deprecated,"
784 " consider using \"%s\" instead.\n"),
785 "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS));
788 svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type);
791 if (opt_state->compatible_version)
793 if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0))
794 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
795 if (! svn_version__at_least(opt_state->compatible_version, 1, 5, 0))
796 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
797 if (! svn_version__at_least(opt_state->compatible_version, 1, 6, 0))
798 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
799 if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0))
800 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
801 /* In 1.9, we figured out that we didn't have to keep extending this
802 madness indefinitely. */
803 svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION,
804 apr_psprintf(pool, "%d.%d.%d%s%s",
805 opt_state->compatible_version->major,
806 opt_state->compatible_version->minor,
807 opt_state->compatible_version->patch,
808 opt_state->compatible_version->tag
810 opt_state->compatible_version->tag
811 ? opt_state->compatible_version->tag : ""));
814 if (opt_state->compatible_version)
816 if (! svn_version__at_least(opt_state->compatible_version, 1, 1, 0)
817 /* ### TODO: this NULL check hard-codes knowledge of the library's
818 default fs-type value */
819 && (opt_state->fs_type == NULL
820 || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS)))
822 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
823 _("Repositories compatible with 1.0.x must "
824 "use --fs-type=bdb"));
827 if (! svn_version__at_least(opt_state->compatible_version, 1, 9, 0)
828 && opt_state->fs_type && !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSX))
830 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
831 _("Repositories compatible with 1.8.x or "
832 "earlier cannot use --fs-type=%s"),
837 SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
838 NULL, NULL, NULL, fs_config, pool));
839 svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
844 /* This implements `svn_opt_subcommand_t'. */
846 subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
848 struct svnadmin_opt_state *opt_state = baton;
851 svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
852 svn_revnum_t youngest, revision;
853 apr_pool_t *subpool = svn_pool_create(pool);
855 /* Expect no more arguments. */
856 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
858 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
859 fs = svn_repos_fs(repos);
860 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
862 /* Find the revision numbers at which to start and end. */
863 SVN_ERR(get_revnum(&start, &opt_state->start_revision,
864 youngest, repos, pool));
865 SVN_ERR(get_revnum(&end, &opt_state->end_revision,
866 youngest, repos, pool));
868 /* Fill in implied revisions if necessary. */
869 if (start == SVN_INVALID_REVNUM)
871 if (end == SVN_INVALID_REVNUM)
875 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
876 _("First revision cannot be higher than second"));
878 /* Loop over the requested revision range, performing the
879 predecessor deltification on paths changed in each. */
880 for (revision = start; revision <= end; revision++)
882 svn_pool_clear(subpool);
883 SVN_ERR(check_cancel(NULL));
884 if (! opt_state->quiet)
885 SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
887 SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
888 if (! opt_state->quiet)
889 SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
891 svn_pool_destroy(subpool);
896 /* Structure for errors encountered during 'svnadmin verify --keep-going'. */
897 struct verification_error
903 /* Pool cleanup function to clear an svn_error_t *. */
905 err_cleanup(void *data)
907 svn_error_t *err = data;
909 svn_error_clear(err);
914 struct repos_verify_callback_baton
916 /* Should we continue after receiving a first verification error? */
917 svn_boolean_t keep_going;
919 /* List of errors encountered during 'svnadmin verify --keep-going'. */
920 apr_array_header_t *error_summary;
922 /* Pool for data collected during callback invocations. */
923 apr_pool_t *result_pool;
926 /* Implementation of svn_repos_verify_callback_t to handle errors coming
927 from svn_repos_verify_fs3(). */
929 repos_verify_callback(void *baton,
930 svn_revnum_t revision,
931 svn_error_t *verify_err,
932 apr_pool_t *scratch_pool)
934 struct repos_verify_callback_baton *b = baton;
936 if (revision == SVN_INVALID_REVNUM)
938 SVN_ERR(svn_cmdline_fputs(_("* Error verifying repository metadata.\n"),
939 stderr, scratch_pool));
943 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
944 _("* Error verifying revision %ld.\n"),
950 struct verification_error *verr;
952 svn_handle_error2(verify_err, stderr, FALSE, "svnadmin: ");
954 /* Remember the error in B->ERROR_SUMMARY. */
955 verr = apr_palloc(b->result_pool, sizeof(*verr));
956 verr->rev = revision;
957 verr->err = svn_error_dup(verify_err);
958 apr_pool_cleanup_register(b->result_pool, verr->err, err_cleanup,
959 apr_pool_cleanup_null);
960 APR_ARRAY_PUSH(b->error_summary, struct verification_error *) = verr;
965 return svn_error_trace(svn_error_dup(verify_err));
968 /* Implementation of svn_repos_notify_func_t to wrap the output to a
969 response stream for svn_repos_dump_fs2(), svn_repos_verify_fs(),
970 svn_repos_hotcopy3() and others. */
972 repos_notify_handler(void *baton,
973 const svn_repos_notify_t *notify,
974 apr_pool_t *scratch_pool)
976 svn_stream_t *feedback_stream = baton;
978 switch (notify->action)
980 case svn_repos_notify_warning:
981 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
982 "WARNING 0x%04x: %s\n", notify->warning,
983 notify->warning_str));
986 case svn_repos_notify_dump_rev_end:
987 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
988 _("* Dumped revision %ld.\n"),
992 case svn_repos_notify_verify_rev_end:
993 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
994 _("* Verified revision %ld.\n"),
998 case svn_repos_notify_verify_rev_structure:
999 if (notify->revision == SVN_INVALID_REVNUM)
1000 svn_error_clear(svn_stream_puts(feedback_stream,
1001 _("* Verifying repository metadata ...\n")));
1003 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1004 _("* Verifying metadata at revision %ld ...\n"),
1008 case svn_repos_notify_pack_shard_start:
1010 const char *shardstr = apr_psprintf(scratch_pool,
1011 "%" APR_INT64_T_FMT,
1013 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1014 _("Packing revisions in shard %s..."),
1019 case svn_repos_notify_pack_shard_end:
1020 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
1023 case svn_repos_notify_pack_shard_start_revprop:
1025 const char *shardstr = apr_psprintf(scratch_pool,
1026 "%" APR_INT64_T_FMT,
1028 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1029 _("Packing revprops in shard %s..."),
1034 case svn_repos_notify_pack_shard_end_revprop:
1035 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
1038 case svn_repos_notify_load_txn_committed:
1039 if (notify->old_revision == SVN_INVALID_REVNUM)
1041 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1042 _("\n------- Committed revision %ld >>>\n\n"),
1043 notify->new_revision));
1047 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1048 _("\n------- Committed new rev %ld"
1049 " (loaded from original rev %ld"
1050 ") >>>\n\n"), notify->new_revision,
1051 notify->old_revision));
1055 case svn_repos_notify_load_node_start:
1057 switch (notify->node_action)
1059 case svn_node_action_change:
1060 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1061 _(" * editing path : %s ..."),
1065 case svn_node_action_delete:
1066 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1067 _(" * deleting path : %s ..."),
1071 case svn_node_action_add:
1072 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1073 _(" * adding path : %s ..."),
1077 case svn_node_action_replace:
1078 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1079 _(" * replacing path : %s ..."),
1087 case svn_repos_notify_load_node_done:
1088 svn_error_clear(svn_stream_puts(feedback_stream, _(" done.\n")));
1091 case svn_repos_notify_load_copied_node:
1092 svn_error_clear(svn_stream_puts(feedback_stream, "COPIED..."));
1095 case svn_repos_notify_load_txn_start:
1096 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1097 _("<<< Started new transaction, based on "
1098 "original revision %ld\n"),
1099 notify->old_revision));
1102 case svn_repos_notify_load_skipped_rev:
1103 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1104 _("<<< Skipped original revision %ld\n"),
1105 notify->old_revision));
1108 case svn_repos_notify_load_normalized_mergeinfo:
1109 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1110 _(" removing '\\r' from %s ..."),
1111 SVN_PROP_MERGEINFO));
1114 case svn_repos_notify_mutex_acquired:
1115 svn_cmdline__setup_cancellation_handler();
1118 case svn_repos_notify_recover_start:
1119 svn_error_clear(svn_stream_puts(feedback_stream,
1120 _("Repository lock acquired.\n"
1121 "Please wait; recovering the"
1122 " repository may take some time...\n")));
1125 case svn_repos_notify_upgrade_start:
1126 svn_error_clear(svn_stream_puts(feedback_stream,
1127 _("Repository lock acquired.\n"
1128 "Please wait; upgrading the"
1129 " repository may take some time...\n")));
1132 case svn_repos_notify_pack_revprops:
1134 const char *shardstr = apr_psprintf(scratch_pool,
1135 "%" APR_INT64_T_FMT,
1137 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1138 _("Packed revision properties in shard %s\n"),
1143 case svn_repos_notify_cleanup_revprops:
1145 const char *shardstr = apr_psprintf(scratch_pool,
1146 "%" APR_INT64_T_FMT,
1148 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1149 _("Removed non-packed revision properties"
1155 case svn_repos_notify_format_bumped:
1156 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1157 _("Bumped repository format to %ld\n"),
1161 case svn_repos_notify_hotcopy_rev_range:
1162 if (notify->start_revision == notify->end_revision)
1164 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1165 _("* Copied revision %ld.\n"),
1166 notify->start_revision));
1170 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1171 _("* Copied revisions from %ld to %ld.\n"),
1172 notify->start_revision, notify->end_revision));
1176 case svn_repos_notify_pack_noop:
1177 /* For best backward compatibility, we keep silent if there were just
1178 no more shards to pack. */
1179 if (notify->shard == -1)
1181 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1182 _("svnadmin: Warning - this repository is not sharded."
1183 " Packing has no effect.\n")));
1187 case svn_repos_notify_load_revprop_set:
1188 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1189 _("Properties set on revision %ld.\n"),
1190 notify->new_revision));
1199 /* Baton for recode_write(). */
1200 struct recode_write_baton
1206 /* This implements the 'svn_write_fn_t' interface.
1208 Write DATA to ((struct recode_write_baton *) BATON)->out, in the
1209 console encoding, using svn_cmdline_fprintf(). DATA is a
1210 UTF8-encoded C string, therefore ignore LEN.
1212 ### This recoding mechanism might want to be abstracted into
1213 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
1214 static svn_error_t *recode_write(void *baton,
1218 struct recode_write_baton *rwb = baton;
1219 svn_pool_clear(rwb->pool);
1220 return svn_cmdline_fputs(data, rwb->out, rwb->pool);
1223 /* Create a stream, to write to STD_STREAM, that uses recode_write()
1224 to perform UTF-8 to console encoding translation. */
1225 static svn_stream_t *
1226 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
1228 struct recode_write_baton *std_stream_rwb =
1229 apr_palloc(pool, sizeof(struct recode_write_baton));
1231 svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
1232 std_stream_rwb->pool = svn_pool_create(pool);
1233 std_stream_rwb->out = std_stream;
1234 svn_stream_set_write(rw_stream, recode_write);
1238 /* Read the min / max revision from the OPT_STATE, verify them against REPOS
1239 and return them in *LOWER and *UPPER, respectively. Use SCRATCH_POOL
1240 for temporary allocations. */
1241 static svn_error_t *
1242 get_dump_range(svn_revnum_t *lower,
1243 svn_revnum_t *upper,
1245 struct svnadmin_opt_state *opt_state,
1246 apr_pool_t *scratch_pool)
1249 svn_revnum_t youngest;
1251 *lower = SVN_INVALID_REVNUM;
1252 *upper = SVN_INVALID_REVNUM;
1254 fs = svn_repos_fs(repos);
1255 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, scratch_pool));
1257 /* Find the revision numbers at which to start and end. */
1258 SVN_ERR(get_revnum(lower, &opt_state->start_revision,
1259 youngest, repos, scratch_pool));
1260 SVN_ERR(get_revnum(upper, &opt_state->end_revision,
1261 youngest, repos, scratch_pool));
1263 /* Fill in implied revisions if necessary. */
1264 if (*lower == SVN_INVALID_REVNUM)
1269 else if (*upper == SVN_INVALID_REVNUM)
1274 if (*lower > *upper)
1275 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1276 _("First revision cannot be higher than second"));
1278 return SVN_NO_ERROR;
1281 /* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
1282 * Return TRUE if any prefix is a prefix of PATH (matching whole path
1283 * components); FALSE otherwise.
1284 * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
1285 /* This function is a duplicate of svndumpfilter.c:ary_prefix_match(). */
1286 static svn_boolean_t
1287 ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
1290 size_t path_len = strlen(path);
1292 for (i = 0; i < pfxlist->nelts; i++)
1294 const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
1295 size_t pfx_len = strlen(pfx);
1297 if (path_len < pfx_len)
1299 if (strncmp(path, pfx, pfx_len) == 0
1300 && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
1307 /* Baton for dump_filter_func(). */
1308 struct dump_filter_baton_t
1310 apr_array_header_t *prefixes;
1312 svn_boolean_t do_exclude;
1315 /* Implements svn_repos_dump_filter_func_t. */
1316 static svn_error_t *
1317 dump_filter_func(svn_boolean_t *include,
1318 svn_fs_root_t *root,
1321 apr_pool_t *scratch_pool)
1323 struct dump_filter_baton_t *b = baton;
1324 const svn_boolean_t matches =
1326 ? svn_cstring_match_glob_list(path, b->prefixes)
1327 : ary_prefix_match(b->prefixes, path));
1329 *include = b->do_exclude ? !matches : matches;
1330 return SVN_NO_ERROR;
1333 /* This implements `svn_opt_subcommand_t'. */
1334 static svn_error_t *
1335 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1337 struct svnadmin_opt_state *opt_state = baton;
1339 svn_stream_t *out_stream;
1340 svn_revnum_t lower, upper;
1341 svn_stream_t *feedback_stream = NULL;
1342 struct dump_filter_baton_t filter_baton = {0};
1344 /* Expect no more arguments. */
1345 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1347 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1348 SVN_ERR(get_dump_range(&lower, &upper, repos, opt_state, pool));
1350 /* Open the file or STDOUT, depending on whether -F was specified. */
1351 if (opt_state->file)
1355 /* Overwrite existing files, same as with > redirection. */
1356 SVN_ERR(svn_io_file_open(&file, opt_state->file,
1357 APR_WRITE | APR_CREATE | APR_TRUNCATE
1358 | APR_BUFFERED, APR_OS_DEFAULT, pool));
1359 out_stream = svn_stream_from_aprfile2(file, FALSE, pool);
1362 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1364 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1365 if (! opt_state->quiet)
1366 feedback_stream = recode_stream_create(stderr, pool);
1368 /* Initialize the filter baton. */
1369 filter_baton.glob = opt_state->glob;
1371 if (opt_state->exclude && !opt_state->include)
1373 filter_baton.prefixes = opt_state->exclude;
1374 filter_baton.do_exclude = TRUE;
1376 else if (opt_state->include && !opt_state->exclude)
1378 filter_baton.prefixes = opt_state->include;
1379 filter_baton.do_exclude = FALSE;
1381 else if (opt_state->include && opt_state->exclude)
1383 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1384 _("'--exclude' and '--include' options "
1385 "cannot be used simultaneously"));
1388 SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper,
1389 opt_state->incremental, opt_state->use_deltas,
1391 !opt_state->quiet ? repos_notify_handler : NULL,
1393 filter_baton.prefixes ? dump_filter_func : NULL,
1395 check_cancel, NULL, pool));
1397 return SVN_NO_ERROR;
1400 /* This implements `svn_opt_subcommand_t'. */
1401 static svn_error_t *
1402 subcommand_dump_revprops(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1404 struct svnadmin_opt_state *opt_state = baton;
1406 svn_stream_t *out_stream;
1407 svn_revnum_t lower, upper;
1408 svn_stream_t *feedback_stream = NULL;
1410 /* Expect no more arguments. */
1411 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1413 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1414 SVN_ERR(get_dump_range(&lower, &upper, repos, opt_state, pool));
1416 /* Open the file or STDOUT, depending on whether -F was specified. */
1417 if (opt_state->file)
1421 /* Overwrite existing files, same as with > redirection. */
1422 SVN_ERR(svn_io_file_open(&file, opt_state->file,
1423 APR_WRITE | APR_CREATE | APR_TRUNCATE
1424 | APR_BUFFERED, APR_OS_DEFAULT, pool));
1425 out_stream = svn_stream_from_aprfile2(file, FALSE, pool);
1428 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1430 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1431 if (! opt_state->quiet)
1432 feedback_stream = recode_stream_create(stderr, pool);
1434 SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper,
1435 FALSE, FALSE, TRUE, FALSE,
1436 !opt_state->quiet ? repos_notify_handler : NULL,
1437 feedback_stream, NULL, NULL,
1438 check_cancel, NULL, pool));
1440 return SVN_NO_ERROR;
1443 struct freeze_baton_t {
1444 const char *command;
1449 /* Implements svn_repos_freeze_func_t */
1450 static svn_error_t *
1451 freeze_body(void *baton,
1454 struct freeze_baton_t *b = baton;
1455 apr_status_t apr_err;
1456 apr_file_t *infile, *outfile, *errfile;
1458 apr_err = apr_file_open_stdin(&infile, pool);
1460 return svn_error_wrap_apr(apr_err, "Can't open stdin");
1461 apr_err = apr_file_open_stdout(&outfile, pool);
1463 return svn_error_wrap_apr(apr_err, "Can't open stdout");
1464 apr_err = apr_file_open_stderr(&errfile, pool);
1466 return svn_error_wrap_apr(apr_err, "Can't open stderr");
1468 SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
1470 infile, outfile, errfile, pool));
1472 return SVN_NO_ERROR;
1475 static svn_error_t *
1476 subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1478 struct svnadmin_opt_state *opt_state = baton;
1479 apr_array_header_t *paths;
1480 apr_array_header_t *args;
1482 struct freeze_baton_t b;
1484 SVN_ERR(parse_args(&args, os, -1, -1, pool));
1487 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1488 _("No program provided"));
1490 if (!opt_state->file)
1492 /* One repository on the command line. */
1493 paths = apr_array_make(pool, 1, sizeof(const char *));
1494 APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
1498 svn_stringbuf_t *buf;
1500 /* Read repository paths from the -F file. */
1501 SVN_ERR(svn_stringbuf_from_file2(&buf, opt_state->file, pool));
1502 SVN_ERR(svn_utf_cstring_to_utf8(&utf8, buf->data, pool));
1503 paths = svn_cstring_split(utf8, "\r\n", FALSE, pool);
1506 b.command = APR_ARRAY_IDX(args, 0, const char *);
1507 b.args = apr_palloc(pool, sizeof(char *) * (args->nelts + 1));
1508 for (i = 0; i < args->nelts; ++i)
1509 b.args[i] = APR_ARRAY_IDX(args, i, const char *);
1510 b.args[args->nelts] = NULL;
1512 SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
1514 /* Make any non-zero status visible to the user. */
1518 return SVN_NO_ERROR;
1522 /* This implements `svn_opt_subcommand_t'. */
1523 static svn_error_t *
1524 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1526 struct svnadmin_opt_state *opt_state = baton;
1527 const char *header =
1528 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
1529 "Subversion repository administration tool.\n"
1530 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
1531 "Type 'svnadmin --version' to see the program version and FS modules.\n"
1533 "Available subcommands:\n");
1535 const char *fs_desc_start
1536 = _("The following repository back-end (FS) modules are available:\n\n");
1538 svn_stringbuf_t *version_footer;
1540 version_footer = svn_stringbuf_create(fs_desc_start, pool);
1541 SVN_ERR(svn_fs_print_modules(version_footer, pool));
1543 SVN_ERR(svn_opt_print_help4(os, "svnadmin",
1544 opt_state ? opt_state->version : FALSE,
1545 opt_state ? opt_state->quiet : FALSE,
1546 /*###opt_state ? opt_state->verbose :*/ FALSE,
1547 version_footer->data,
1548 header, cmd_table, options_table, NULL, NULL,
1551 return SVN_NO_ERROR;
1555 /* Set *REVNUM to the revision number of a numeric REV, or to
1556 SVN_INVALID_REVNUM if REV is unspecified. */
1557 static svn_error_t *
1558 optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
1560 if (opt_rev->kind == svn_opt_revision_number)
1562 *revnum = opt_rev->value.number;
1563 if (! SVN_IS_VALID_REVNUM(*revnum))
1564 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1565 _("Invalid revision number (%ld) specified"),
1568 else if (opt_rev->kind == svn_opt_revision_unspecified)
1570 *revnum = SVN_INVALID_REVNUM;
1574 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1575 _("Non-numeric revision specified"));
1577 return SVN_NO_ERROR;
1580 /* Read the min / max revision from the OPT_STATE, verify them and return
1581 them in *LOWER and *UPPER, respectively. */
1582 static svn_error_t *
1583 get_load_range(svn_revnum_t *lower,
1584 svn_revnum_t *upper,
1585 struct svnadmin_opt_state *opt_state)
1587 /* Find the revision numbers at which to start and end. We only
1588 support a limited set of revision kinds: number and unspecified. */
1589 SVN_ERR(optrev_to_revnum(lower, &opt_state->start_revision));
1590 SVN_ERR(optrev_to_revnum(upper, &opt_state->end_revision));
1592 /* Fill in implied revisions if necessary. */
1593 if ((*upper == SVN_INVALID_REVNUM) && (*lower != SVN_INVALID_REVNUM))
1597 else if ((*upper != SVN_INVALID_REVNUM) && (*lower == SVN_INVALID_REVNUM))
1602 /* Ensure correct range ordering. */
1603 if (*lower > *upper)
1605 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1606 _("First revision cannot be higher than second"));
1609 return SVN_NO_ERROR;
1613 /* This implements `svn_opt_subcommand_t'. */
1614 static svn_error_t *
1615 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1618 struct svnadmin_opt_state *opt_state = baton;
1620 svn_revnum_t lower, upper;
1621 svn_stream_t *in_stream;
1622 svn_stream_t *feedback_stream = NULL;
1624 /* Expect no more arguments. */
1625 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1627 /* Find the revision numbers at which to start and end. We only
1628 support a limited set of revision kinds: number and unspecified. */
1629 SVN_ERR(get_load_range(&lower, &upper, opt_state));
1631 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1633 /* Open the file or STDIN, depending on whether -F was specified. */
1634 if (opt_state->file)
1635 SVN_ERR(svn_stream_open_readonly(&in_stream, opt_state->file,
1638 SVN_ERR(svn_stream_for_stdin2(&in_stream, TRUE, pool));
1640 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1641 if (! opt_state->quiet)
1642 feedback_stream = recode_stream_create(stdout, pool);
1644 err = svn_repos_load_fs6(repos, in_stream, lower, upper,
1645 opt_state->uuid_action, opt_state->parent_dir,
1646 opt_state->use_pre_commit_hook,
1647 opt_state->use_post_commit_hook,
1648 !opt_state->bypass_prop_validation,
1649 opt_state->ignore_dates,
1650 opt_state->normalize_props,
1651 opt_state->quiet ? NULL : repos_notify_handler,
1652 feedback_stream, check_cancel, NULL, pool);
1654 if (svn_error_find_cause(err, SVN_ERR_BAD_PROPERTY_VALUE_EOL))
1656 return svn_error_quick_wrap(err,
1657 _("A property with invalid line ending "
1658 "found in dumpstream; consider using "
1659 "--normalize-props while loading."));
1661 else if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1663 return svn_error_quick_wrap(err,
1664 _("Invalid property value found in "
1665 "dumpstream; consider repairing the "
1666 "source or using --bypass-prop-validation "
1673 static svn_error_t *
1674 subcommand_load_revprops(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1677 struct svnadmin_opt_state *opt_state = baton;
1679 svn_revnum_t lower, upper;
1680 svn_stream_t *in_stream;
1682 svn_stream_t *feedback_stream = NULL;
1684 /* Expect no more arguments. */
1685 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1687 /* Find the revision numbers at which to start and end. We only
1688 support a limited set of revision kinds: number and unspecified. */
1689 SVN_ERR(get_load_range(&lower, &upper, opt_state));
1691 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1693 /* Open the file or STDIN, depending on whether -F was specified. */
1694 if (opt_state->file)
1695 SVN_ERR(svn_stream_open_readonly(&in_stream, opt_state->file,
1698 SVN_ERR(svn_stream_for_stdin2(&in_stream, TRUE, pool));
1700 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1701 if (! opt_state->quiet)
1702 feedback_stream = recode_stream_create(stdout, pool);
1704 err = svn_repos_load_fs_revprops(repos, in_stream, lower, upper,
1705 !opt_state->bypass_prop_validation,
1706 opt_state->ignore_dates,
1707 opt_state->normalize_props,
1708 opt_state->quiet ? NULL
1709 : repos_notify_handler,
1710 feedback_stream, check_cancel, NULL, pool);
1712 if (svn_error_find_cause(err, SVN_ERR_BAD_PROPERTY_VALUE_EOL))
1714 return svn_error_quick_wrap(err,
1715 _("A property with invalid line ending "
1716 "found in dumpstream; consider using "
1717 "--normalize-props while loading."));
1719 else if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1721 return svn_error_quick_wrap(err,
1722 _("Invalid property value found in "
1723 "dumpstream; consider repairing the "
1724 "source or using --bypass-prop-validation "
1731 /* This implements `svn_opt_subcommand_t'. */
1732 static svn_error_t *
1733 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1735 struct svnadmin_opt_state *opt_state = baton;
1738 apr_array_header_t *txns;
1739 apr_pool_t *iterpool;
1740 svn_revnum_t youngest, limit;
1743 /* Expect no more arguments. */
1744 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1745 if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1746 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1747 _("Revision range is not allowed"));
1749 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1750 fs = svn_repos_fs(repos);
1751 SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
1753 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1754 SVN_ERR(get_revnum(&limit, &opt_state->start_revision, youngest, repos,
1757 iterpool = svn_pool_create(pool);
1758 for (i = 0; i < txns->nelts; i++)
1760 const char *name = APR_ARRAY_IDX(txns, i, const char *);
1761 svn_boolean_t show = TRUE;
1763 svn_pool_clear(iterpool);
1764 if (limit != SVN_INVALID_REVNUM)
1769 SVN_ERR(svn_fs_open_txn(&txn, fs, name, iterpool));
1770 base = svn_fs_txn_base_revision(txn);
1776 SVN_ERR(svn_cmdline_printf(pool, "%s\n", name));
1778 svn_pool_destroy(iterpool);
1780 return SVN_NO_ERROR;
1784 /* This implements `svn_opt_subcommand_t'. */
1785 static svn_error_t *
1786 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1788 svn_revnum_t youngest_rev;
1791 struct svnadmin_opt_state *opt_state = baton;
1792 svn_stream_t *feedback_stream = NULL;
1794 /* Expect no more arguments. */
1795 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1797 SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
1799 /* Restore default signal handlers until after we have acquired the
1800 * exclusive lock so that the user interrupt before we actually
1801 * touch the repository. */
1802 svn_cmdline__disable_cancellation_handler();
1804 err = svn_repos_recover4(opt_state->repository_path, TRUE,
1805 repos_notify_handler, feedback_stream,
1806 check_cancel, NULL, pool);
1809 if (! APR_STATUS_IS_EAGAIN(err->apr_err))
1811 svn_error_clear(err);
1812 if (! opt_state->wait)
1813 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1814 _("Failed to get exclusive repository "
1815 "access; perhaps another process\n"
1816 "such as httpd, svnserve or svn "
1818 SVN_ERR(svn_cmdline_printf(pool,
1819 _("Waiting on repository lock; perhaps"
1820 " another process has it open?\n")));
1821 SVN_ERR(svn_cmdline_fflush(stdout));
1822 SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
1823 repos_notify_handler, feedback_stream,
1824 check_cancel, NULL, pool));
1827 SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
1829 /* Since db transactions may have been replayed, it's nice to tell
1830 people what the latest revision is. It also proves that the
1831 recovery actually worked. */
1832 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1833 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
1834 SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
1837 return SVN_NO_ERROR;
1841 /* This implements `svn_opt_subcommand_t'. */
1842 static svn_error_t *
1843 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
1846 struct svnadmin_opt_state *opt_state = baton;
1847 apr_array_header_t *logfiles;
1850 /* Expect no more arguments. */
1851 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1853 SVN_ERR(svn_repos_db_logfiles(&logfiles,
1854 opt_state->repository_path,
1858 /* Loop, printing log files. We append the log paths to the
1859 repository path, making sure to return everything to the native
1860 style before printing. */
1861 for (i = 0; i < logfiles->nelts; i++)
1863 const char *log_path;
1864 log_path = svn_dirent_join(opt_state->repository_path,
1865 APR_ARRAY_IDX(logfiles, i, const char *),
1867 log_path = svn_dirent_local_style(log_path, pool);
1868 SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_path));
1871 return SVN_NO_ERROR;
1875 /* This implements `svn_opt_subcommand_t'. */
1876 static svn_error_t *
1877 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1879 SVN_ERR(list_dblogs(os, baton, FALSE, pool));
1880 return SVN_NO_ERROR;
1884 /* This implements `svn_opt_subcommand_t'. */
1885 static svn_error_t *
1886 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1888 /* Expect no more arguments. */
1889 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1891 SVN_ERR(list_dblogs(os, baton, TRUE, pool));
1892 return SVN_NO_ERROR;
1896 /* This implements `svn_opt_subcommand_t'. */
1897 static svn_error_t *
1898 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1900 struct svnadmin_opt_state *opt_state = baton;
1904 apr_array_header_t *args;
1906 apr_pool_t *subpool = svn_pool_create(pool);
1908 SVN_ERR(parse_args(&args, os, -1, -1, pool));
1910 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1911 fs = svn_repos_fs(repos);
1913 /* All the rest of the arguments are transaction names. */
1914 for (i = 0; i < args->nelts; i++)
1916 const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
1919 svn_pool_clear(subpool);
1921 /* Try to open the txn. If that succeeds, try to abort it. */
1922 err = svn_fs_open_txn(&txn, fs, txn_name, subpool);
1924 err = svn_fs_abort_txn(txn, subpool);
1926 /* If either the open or the abort of the txn fails because that
1927 transaction is dead, just try to purge the thing. Else,
1928 there was either an error worth reporting, or not error at
1930 if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
1932 svn_error_clear(err);
1933 err = svn_fs_purge_txn(fs, txn_name, subpool);
1936 /* If we had a real from the txn open, abort, or purge, we clear
1937 that error and just report to the user that we had an issue
1938 with this particular txn. */
1941 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
1942 svn_error_clear(err);
1944 else if (! opt_state->quiet)
1946 SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
1951 svn_pool_destroy(subpool);
1953 return SVN_NO_ERROR;
1957 /* A helper for the 'setrevprop' and 'setlog' commands. Expects
1958 OPT_STATE->txn_id, OPT_STATE->use_pre_revprop_change_hook and
1959 OPT_STATE->use_post_revprop_change_hook to be set appropriately.
1960 If FILENAME is NULL, delete property PROP_NAME. */
1961 static svn_error_t *
1962 set_revprop(const char *prop_name, const char *filename,
1963 struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
1966 svn_string_t *prop_value;
1970 svn_stringbuf_t *file_contents;
1972 SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
1974 prop_value = svn_string_create_empty(pool);
1975 prop_value->data = file_contents->data;
1976 prop_value->len = file_contents->len;
1978 SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
1979 NULL, FALSE, pool, pool));
1986 /* Open the filesystem */
1987 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1989 if (opt_state->txn_id)
1991 svn_fs_t *fs = svn_repos_fs(repos);
1994 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
1995 SVN_ERR(svn_fs_change_txn_prop(txn, prop_name, prop_value, pool));
1998 SVN_ERR(svn_repos_fs_change_rev_prop4(
1999 repos, opt_state->start_revision.value.number,
2000 NULL, prop_name, NULL, prop_value,
2001 opt_state->use_pre_revprop_change_hook,
2002 opt_state->use_post_revprop_change_hook,
2005 return SVN_NO_ERROR;
2009 /* This implements `svn_opt_subcommand_t'. */
2010 static svn_error_t *
2011 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2013 struct svnadmin_opt_state *opt_state = baton;
2014 apr_array_header_t *args;
2015 const char *prop_name, *filename;
2017 /* Expect two more arguments: NAME FILE */
2018 SVN_ERR(parse_args(&args, os, 2, 2, pool));
2019 prop_name = APR_ARRAY_IDX(args, 0, const char *);
2020 filename = APR_ARRAY_IDX(args, 1, const char *);
2021 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
2023 if (opt_state->txn_id)
2025 if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2026 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2027 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2028 _("--revision (-r) and --transaction (-t) "
2029 "are mutually exclusive"));
2031 if (opt_state->use_pre_revprop_change_hook
2032 || opt_state->use_post_revprop_change_hook)
2033 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2034 _("Calling hooks is incompatible with "
2035 "--transaction (-t)"));
2037 else if (opt_state->start_revision.kind != svn_opt_revision_number)
2038 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2039 _("Missing revision"));
2040 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2041 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2042 _("Only one revision allowed"));
2044 return set_revprop(prop_name, filename, opt_state, pool);
2048 /* This implements `svn_opt_subcommand_t'. */
2049 static svn_error_t *
2050 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2052 struct svnadmin_opt_state *opt_state = baton;
2053 apr_array_header_t *args;
2056 const char *uuid = NULL;
2058 /* Expect zero or one more arguments: [UUID] */
2059 SVN_ERR(parse_args(&args, os, 0, 1, pool));
2060 if (args->nelts == 1)
2061 uuid = APR_ARRAY_IDX(args, 0, const char *);
2063 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2064 fs = svn_repos_fs(repos);
2065 return svn_fs_set_uuid(fs, uuid, pool);
2069 /* This implements `svn_opt_subcommand_t'. */
2070 static svn_error_t *
2071 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2073 struct svnadmin_opt_state *opt_state = baton;
2074 apr_array_header_t *args;
2075 const char *filename;
2077 /* Expect one more argument: FILE */
2078 SVN_ERR(parse_args(&args, os, 1, 1, pool));
2079 filename = APR_ARRAY_IDX(args, 0, const char *);
2080 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
2082 if (opt_state->start_revision.kind != svn_opt_revision_number)
2083 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2084 _("Missing revision"));
2085 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2086 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2087 _("Only one revision allowed"));
2089 /* set_revprop() responds only to pre-/post-revprop-change opts. */
2090 if (!opt_state->bypass_hooks)
2092 opt_state->use_pre_revprop_change_hook = TRUE;
2093 opt_state->use_post_revprop_change_hook = TRUE;
2096 return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
2100 /* This implements 'svn_opt_subcommand_t'. */
2101 static svn_error_t *
2102 subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2104 struct svnadmin_opt_state *opt_state = baton;
2106 svn_stream_t *feedback_stream = NULL;
2108 /* Expect no more arguments. */
2109 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2111 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2113 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
2114 if (! opt_state->quiet)
2115 feedback_stream = recode_stream_create(stdout, pool);
2117 return svn_error_trace(
2118 svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
2119 feedback_stream, check_cancel, NULL, pool));
2123 /* This implements `svn_opt_subcommand_t'. */
2124 static svn_error_t *
2125 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2127 struct svnadmin_opt_state *opt_state = baton;
2130 svn_revnum_t youngest, lower, upper;
2131 svn_stream_t *feedback_stream = NULL;
2132 struct repos_verify_callback_baton verify_baton = { 0 };
2134 /* Expect no more arguments. */
2135 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2137 if (opt_state->txn_id
2138 && (opt_state->start_revision.kind != svn_opt_revision_unspecified
2139 || opt_state->end_revision.kind != svn_opt_revision_unspecified))
2141 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2142 _("--revision (-r) and --transaction (-t) "
2143 "are mutually exclusive"));
2146 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2147 fs = svn_repos_fs(repos);
2148 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2151 if (opt_state->txn_id)
2154 svn_fs_root_t *root;
2156 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
2157 SVN_ERR(svn_fs_txn_root(&root, txn, pool));
2158 SVN_ERR(svn_fs_verify_root(root, pool));
2159 return SVN_NO_ERROR;
2165 /* Find the revision numbers at which to start and end. */
2166 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
2167 youngest, repos, pool));
2168 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
2169 youngest, repos, pool));
2171 if (upper == SVN_INVALID_REVNUM)
2176 if (!opt_state->quiet)
2177 feedback_stream = recode_stream_create(stdout, pool);
2179 verify_baton.keep_going = opt_state->keep_going;
2180 verify_baton.error_summary =
2181 apr_array_make(pool, 0, sizeof(struct verification_error *));
2182 verify_baton.result_pool = pool;
2184 SVN_ERR(svn_repos_verify_fs3(repos, lower, upper,
2185 opt_state->check_normalization,
2186 opt_state->metadata_only,
2188 ? repos_notify_handler : NULL,
2190 repos_verify_callback, &verify_baton,
2191 check_cancel, NULL, pool));
2193 /* Show the --keep-going error summary. */
2194 if (!opt_state->quiet
2195 && opt_state->keep_going
2196 && verify_baton.error_summary->nelts > 0)
2199 svn_revnum_t end_revnum;
2200 apr_pool_t *iterpool;
2204 svn_stream_puts(feedback_stream,
2205 _("\n-----Summary of corrupt revisions-----\n")));
2207 /* The standard column width for the revision number is 6 characters.
2208 If the revision number can potentially be larger (i.e. if end_revnum
2209 is larger than 1000000), we increase the column width as needed. */
2211 end_revnum = APR_ARRAY_IDX(verify_baton.error_summary,
2212 verify_baton.error_summary->nelts - 1,
2213 struct verification_error *)->rev;
2214 while (end_revnum >= 1000000)
2217 end_revnum = end_revnum / 10;
2220 iterpool = svn_pool_create(pool);
2221 for (i = 0; i < verify_baton.error_summary->nelts; i++)
2223 struct verification_error *verr;
2225 const char *rev_str;
2227 svn_pool_clear(iterpool);
2229 verr = APR_ARRAY_IDX(verify_baton.error_summary, i,
2230 struct verification_error *);
2232 if (verr->rev != SVN_INVALID_REVNUM)
2234 rev_str = apr_psprintf(iterpool, "r%ld", verr->rev);
2235 rev_str = apr_psprintf(iterpool, "%*s", rev_maxlength, rev_str);
2236 for (err = svn_error_purge_tracing(verr->err);
2237 err != SVN_NO_ERROR; err = err->child)
2240 const char *message;
2242 message = svn_err_best_message(err, buf, sizeof(buf));
2243 svn_error_clear(svn_stream_printf(feedback_stream, iterpool,
2245 rev_str, err->apr_err,
2251 svn_pool_destroy(iterpool);
2254 if (verify_baton.error_summary->nelts > 0)
2256 return svn_error_createf(SVN_ERR_CL_REPOS_VERIFY_FAILED, NULL,
2257 _("Failed to verify repository '%s'"),
2258 svn_dirent_local_style(
2259 opt_state->repository_path, pool));
2262 return SVN_NO_ERROR;
2265 /* This implements `svn_opt_subcommand_t'. */
2267 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2269 struct svnadmin_opt_state *opt_state = baton;
2270 svn_stream_t *feedback_stream = NULL;
2271 apr_array_header_t *targets;
2272 const char *new_repos_path;
2274 /* Expect one more argument: NEW_REPOS_PATH */
2275 SVN_ERR(parse_args(&targets, os, 1, 1, pool));
2276 new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
2277 SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
2279 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
2280 if (! opt_state->quiet)
2281 feedback_stream = recode_stream_create(stdout, pool);
2283 return svn_repos_hotcopy3(opt_state->repository_path, new_repos_path,
2284 opt_state->clean_logs, opt_state->incremental,
2285 !opt_state->quiet ? repos_notify_handler : NULL,
2286 feedback_stream, check_cancel, NULL, pool);
2290 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2292 struct svnadmin_opt_state *opt_state = baton;
2297 svn_revnum_t head_rev;
2299 /* Expect no more arguments. */
2300 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2302 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2303 fs = svn_repos_fs(repos);
2304 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
2305 svn_dirent_local_style(svn_repos_path(repos, pool),
2308 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2309 SVN_ERR(svn_cmdline_printf(pool, _("UUID: %s\n"), uuid));
2311 SVN_ERR(svn_fs_youngest_rev(&head_rev, fs, pool));
2312 SVN_ERR(svn_cmdline_printf(pool, _("Revisions: %ld\n"), head_rev));
2314 int repos_format, minor;
2315 svn_version_t *repos_version, *fs_version;
2316 SVN_ERR(svn_repos_info_format(&repos_format, &repos_version,
2317 repos, pool, pool));
2318 SVN_ERR(svn_cmdline_printf(pool, _("Repository Format: %d\n"),
2321 SVN_ERR(svn_fs_info_format(&fs_format, &fs_version,
2323 /* fs_format will be printed later. */
2325 SVN_ERR_ASSERT(repos_version->major == SVN_VER_MAJOR);
2326 SVN_ERR_ASSERT(fs_version->major == SVN_VER_MAJOR);
2327 SVN_ERR_ASSERT(repos_version->patch == 0);
2328 SVN_ERR_ASSERT(fs_version->patch == 0);
2330 minor = (repos_version->minor > fs_version->minor)
2331 ? repos_version->minor : fs_version->minor;
2332 SVN_ERR(svn_cmdline_printf(pool, _("Compatible With Version: %d.%d.0\n"),
2333 SVN_VER_MAJOR, minor));
2337 apr_hash_t *capabilities_set;
2338 apr_array_header_t *capabilities;
2341 SVN_ERR(svn_repos_capabilities(&capabilities_set, repos, pool, pool));
2342 capabilities = svn_sort__hash(capabilities_set,
2343 svn_sort_compare_items_lexically,
2346 for (i = 0; i < capabilities->nelts; i++)
2348 svn_sort__item_t *item = &APR_ARRAY_IDX(capabilities, i,
2350 const char *capability = item->key;
2351 SVN_ERR(svn_cmdline_printf(pool, _("Repository Capability: %s\n"),
2357 const svn_fs_info_placeholder_t *info;
2359 SVN_ERR(svn_fs_info(&info, fs, pool, pool));
2360 SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Type: %s\n"),
2362 SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Format: %d\n"),
2364 if (!strcmp(info->fs_type, SVN_FS_TYPE_FSFS))
2366 const svn_fs_fsfs_info_t *fsfs_info = (const void *)info;
2368 if (fsfs_info->shard_size)
2369 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: yes\n")));
2371 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: no\n")));
2373 if (fsfs_info->shard_size)
2374 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shard Size: %d\n"),
2375 fsfs_info->shard_size));
2377 /* Print packing statistics, if enabled on the FS. */
2378 if (fsfs_info->shard_size)
2380 const int shard_size = fsfs_info->shard_size;
2381 const long shards_packed = fsfs_info->min_unpacked_rev / shard_size;
2382 const long shards_full = (head_rev + 1) / shard_size;
2383 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shards Packed: %ld/%ld\n"),
2384 shards_packed, shards_full));
2387 if (fsfs_info->log_addressing)
2388 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: yes\n")));
2390 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: no\n")));
2392 else if (!strcmp(info->fs_type, SVN_FS_TYPE_FSX))
2394 const svn_fs_fsx_info_t *fsx_info = (const void *)info;
2396 const int shard_size = fsx_info->shard_size;
2397 const long shards_packed = fsx_info->min_unpacked_rev / shard_size;
2398 long shards_full = (head_rev + 1) / shard_size;
2400 SVN_ERR(svn_cmdline_printf(pool, _("FSX Shard Size: %d\n"),
2402 SVN_ERR(svn_cmdline_printf(pool, _("FSX Shards Packed: %ld/%ld\n"),
2403 shards_packed, shards_full));
2408 apr_array_header_t *files;
2411 SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool));
2412 for (i = 0; i < files->nelts; i++)
2413 SVN_ERR(svn_cmdline_printf(pool, _("Configuration File: %s\n"),
2414 svn_dirent_local_style(
2415 APR_ARRAY_IDX(files, i, const char *),
2419 /* 'svn info' prints an extra newline here, to support multiple targets.
2420 We'll do the same. */
2421 SVN_ERR(svn_cmdline_printf(pool, "\n"));
2423 return SVN_NO_ERROR;
2426 /* This implements `svn_opt_subcommand_t'. */
2427 static svn_error_t *
2428 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2430 struct svnadmin_opt_state *opt_state = baton;
2433 svn_fs_access_t *access;
2434 apr_array_header_t *args;
2435 const char *username;
2436 const char *lock_path;
2437 const char *comment_file_name;
2438 svn_stringbuf_t *file_contents;
2440 const char *lock_token = NULL;
2442 /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
2443 SVN_ERR(parse_args(&args, os, 3, 4, pool));
2444 lock_path = APR_ARRAY_IDX(args, 0, const char *);
2445 username = APR_ARRAY_IDX(args, 1, const char *);
2446 comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
2448 /* Expect one more optional argument: TOKEN */
2449 if (args->nelts == 4)
2450 lock_token = APR_ARRAY_IDX(args, 3, const char *);
2452 SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
2454 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2455 fs = svn_repos_fs(repos);
2457 /* Create an access context describing the user. */
2458 SVN_ERR(svn_fs_create_access(&access, username, pool));
2460 /* Attach the access context to the filesystem. */
2461 SVN_ERR(svn_fs_set_access(fs, access));
2463 SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
2465 SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, pool, pool));
2467 if (opt_state->bypass_hooks)
2468 SVN_ERR(svn_fs_lock(&lock, fs, lock_path,
2470 file_contents->data, /* comment */
2471 0, /* is_dav_comment */
2472 0, /* no expiration time. */
2476 SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path,
2478 file_contents->data, /* comment */
2479 0, /* is_dav_comment */
2480 0, /* no expiration time. */
2484 if (! opt_state->quiet)
2485 SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
2486 lock_path, username));
2488 return SVN_NO_ERROR;
2491 static svn_error_t *
2492 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2494 struct svnadmin_opt_state *opt_state = baton;
2495 apr_array_header_t *targets;
2497 const char *fs_path;
2499 apr_hash_index_t *hi;
2500 apr_pool_t *iterpool = svn_pool_create(pool);
2502 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
2503 apr_array_make(pool, 0,
2504 sizeof(const char *)),
2506 if (targets->nelts > 1)
2507 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2508 _("Too many arguments given"));
2510 fs_path = APR_ARRAY_IDX(targets, 0, const char *);
2513 SVN_ERR(target_arg_to_fspath(&fs_path, fs_path, pool, pool));
2515 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2517 /* Fetch all locks on or below the root directory. */
2518 SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
2521 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2523 const char *cr_date, *exp_date = "";
2524 const char *path = apr_hash_this_key(hi);
2525 svn_lock_t *lock = apr_hash_this_val(hi);
2526 int comment_lines = 0;
2528 svn_pool_clear(iterpool);
2530 SVN_ERR(check_cancel(NULL));
2532 cr_date = svn_time_to_human_cstring(lock->creation_date, iterpool);
2534 if (lock->expiration_date)
2535 exp_date = svn_time_to_human_cstring(lock->expiration_date, iterpool);
2538 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2540 SVN_ERR(svn_cmdline_printf(iterpool, _("Path: %s\n"), path));
2541 SVN_ERR(svn_cmdline_printf(iterpool, _("UUID Token: %s\n"), lock->token));
2542 SVN_ERR(svn_cmdline_printf(iterpool, _("Owner: %s\n"), lock->owner));
2543 SVN_ERR(svn_cmdline_printf(iterpool, _("Created: %s\n"), cr_date));
2544 SVN_ERR(svn_cmdline_printf(iterpool, _("Expires: %s\n"), exp_date));
2545 SVN_ERR(svn_cmdline_printf(iterpool,
2546 Q_("Comment (%i line):\n%s\n\n",
2547 "Comment (%i lines):\n%s\n\n",
2550 lock->comment ? lock->comment : ""));
2553 svn_pool_destroy(iterpool);
2555 return SVN_NO_ERROR;
2560 static svn_error_t *
2561 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2563 struct svnadmin_opt_state *opt_state = baton;
2566 svn_fs_access_t *access;
2568 apr_array_header_t *args;
2570 const char *username;
2571 apr_pool_t *subpool = svn_pool_create(pool);
2573 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2574 fs = svn_repos_fs(repos);
2576 /* svn_fs_unlock() demands that some username be associated with the
2577 filesystem, so just use the UID of the person running 'svnadmin'.*/
2578 username = svn_user_get_name(pool);
2580 username = "administrator";
2582 /* Create an access context describing the current user. */
2583 SVN_ERR(svn_fs_create_access(&access, username, pool));
2585 /* Attach the access context to the filesystem. */
2586 SVN_ERR(svn_fs_set_access(fs, access));
2588 /* Parse out any options. */
2589 SVN_ERR(parse_args(&args, os, -1, -1, pool));
2591 /* Our usage requires at least one FS path. */
2592 if (args->nelts == 0)
2593 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2594 _("No paths to unlock provided"));
2596 /* All the rest of the arguments are paths from which to remove locks. */
2597 for (i = 0; i < args->nelts; i++)
2599 const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
2602 SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, subpool, subpool));
2604 /* Fetch the path's svn_lock_t. */
2605 err = svn_fs_get_lock(&lock, fs, lock_path, subpool);
2610 if (! opt_state->quiet)
2611 SVN_ERR(svn_cmdline_printf(subpool,
2612 _("Path '%s' isn't locked.\n"),
2616 lock = NULL; /* Don't access LOCK after this point. */
2618 /* Now forcibly destroy the lock. */
2619 err = svn_fs_unlock(fs, lock_path,
2620 NULL, 1 /* force */, subpool);
2624 if (! opt_state->quiet)
2625 SVN_ERR(svn_cmdline_printf(subpool,
2626 _("Removed lock on '%s'.\n"),
2632 /* Print the error, but move on to the next lock. */
2633 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
2634 svn_error_clear(err);
2637 svn_pool_clear(subpool);
2640 svn_pool_destroy(subpool);
2641 return SVN_NO_ERROR;
2645 /* This implements `svn_opt_subcommand_t'. */
2646 static svn_error_t *
2647 subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2649 struct svnadmin_opt_state *opt_state = baton;
2652 svn_fs_access_t *access;
2653 apr_array_header_t *args;
2654 const char *username;
2655 const char *lock_path;
2656 const char *lock_token = NULL;
2658 /* Expect three more arguments: PATH USERNAME TOKEN */
2659 SVN_ERR(parse_args(&args, os, 3, 3, pool));
2660 lock_path = APR_ARRAY_IDX(args, 0, const char *);
2661 username = APR_ARRAY_IDX(args, 1, const char *);
2662 lock_token = APR_ARRAY_IDX(args, 2, const char *);
2664 /* Open the repos/FS, and associate an access context containing
2666 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2667 fs = svn_repos_fs(repos);
2668 SVN_ERR(svn_fs_create_access(&access, username, pool));
2669 SVN_ERR(svn_fs_set_access(fs, access));
2671 SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, pool, pool));
2672 if (opt_state->bypass_hooks)
2673 SVN_ERR(svn_fs_unlock(fs, lock_path, lock_token,
2676 SVN_ERR(svn_repos_fs_unlock(repos, lock_path, lock_token,
2679 if (! opt_state->quiet)
2680 SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
2681 lock_path, username));
2683 return SVN_NO_ERROR;
2687 /* This implements `svn_opt_subcommand_t'. */
2688 static svn_error_t *
2689 subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2692 struct svnadmin_opt_state *opt_state = baton;
2693 svn_stream_t *feedback_stream = NULL;
2695 /* Expect no more arguments. */
2696 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2698 SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
2700 /* Restore default signal handlers. */
2701 svn_cmdline__disable_cancellation_handler();
2703 err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
2704 repos_notify_handler, feedback_stream, pool);
2707 if (APR_STATUS_IS_EAGAIN(err->apr_err))
2709 svn_error_clear(err);
2711 if (! opt_state->wait)
2712 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
2713 _("Failed to get exclusive repository "
2714 "access; perhaps another process\n"
2715 "such as httpd, svnserve or svn "
2717 SVN_ERR(svn_cmdline_printf(pool,
2718 _("Waiting on repository lock; perhaps"
2719 " another process has it open?\n")));
2720 SVN_ERR(svn_cmdline_fflush(stdout));
2721 SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
2722 repos_notify_handler, feedback_stream,
2725 else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
2727 return svn_error_quick_wrap(err,
2728 _("Upgrade of this repository's underlying versioned "
2729 "filesystem is not supported; consider "
2730 "dumping and loading the data elsewhere"));
2732 else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
2734 return svn_error_quick_wrap(err,
2735 _("Upgrade of this repository is not supported; consider "
2736 "dumping and loading the data elsewhere"));
2741 SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
2742 return SVN_NO_ERROR;
2746 /* This implements `svn_opt_subcommand_t'. */
2747 static svn_error_t *
2748 subcommand_delrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2750 struct svnadmin_opt_state *opt_state = baton;
2751 apr_array_header_t *args;
2752 const char *prop_name;
2754 /* Expect one more argument: NAME */
2755 SVN_ERR(parse_args(&args, os, 1, 1, pool));
2756 prop_name = APR_ARRAY_IDX(args, 0, const char *);
2758 if (opt_state->txn_id)
2760 if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2761 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2762 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2763 _("--revision (-r) and --transaction (-t) "
2764 "are mutually exclusive"));
2766 if (opt_state->use_pre_revprop_change_hook
2767 || opt_state->use_post_revprop_change_hook)
2768 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2769 _("Calling hooks is incompatible with "
2770 "--transaction (-t)"));
2772 else if (opt_state->start_revision.kind != svn_opt_revision_number)
2773 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2774 _("Missing revision"));
2775 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2776 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2777 _("Only one revision allowed"));
2779 return set_revprop(prop_name, NULL, opt_state, pool);
2787 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2788 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2789 * return SVN_NO_ERROR.
2791 static svn_error_t *
2792 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2795 apr_status_t apr_err;
2797 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2798 struct svnadmin_opt_state opt_state = { 0 };
2801 apr_array_header_t *received_opts;
2803 svn_boolean_t dash_F_arg = FALSE;
2805 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2807 /* Check library versions */
2808 SVN_ERR(check_lib_versions());
2810 /* Initialize the FS library. */
2811 SVN_ERR(svn_fs_initialize(pool));
2815 SVN_ERR(subcommand_help(NULL, NULL, pool));
2816 *exit_code = EXIT_FAILURE;
2817 return SVN_NO_ERROR;
2820 /* Initialize opt_state. */
2821 opt_state.start_revision.kind = svn_opt_revision_unspecified;
2822 opt_state.end_revision.kind = svn_opt_revision_unspecified;
2823 opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2825 /* Parse options. */
2826 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2832 const char *opt_arg;
2833 const char *utf8_opt_arg;
2835 /* Parse the next option. */
2836 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2837 if (APR_STATUS_IS_EOF(apr_err))
2841 SVN_ERR(subcommand_help(NULL, NULL, pool));
2842 *exit_code = EXIT_FAILURE;
2843 return SVN_NO_ERROR;
2846 /* Stash the option code in an array before parsing it. */
2847 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2852 if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
2854 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2855 _("Multiple revision arguments encountered; "
2856 "try '-r N:M' instead of '-r N -r M'"));
2858 if (svn_opt_parse_revision(&(opt_state.start_revision),
2859 &(opt_state.end_revision),
2860 opt_arg, pool) != 0)
2862 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
2864 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2865 _("Syntax error in revision argument '%s'"),
2871 opt_state.txn_id = opt_arg;
2875 opt_state.quiet = TRUE;
2879 opt_state.help = TRUE;
2883 apr_uint64_t sz_val;
2884 SVN_ERR(svn_cstring_atoui64(&sz_val, opt_arg));
2886 opt_state.memory_cache_size = 0x100000 * sz_val;
2890 SVN_ERR(svn_utf_cstring_to_utf8(&(opt_state.file), opt_arg, pool));
2893 case svnadmin__version:
2894 opt_state.version = TRUE;
2896 case svnadmin__incremental:
2897 opt_state.incremental = TRUE;
2899 case svnadmin__deltas:
2900 opt_state.use_deltas = TRUE;
2902 case svnadmin__ignore_uuid:
2903 opt_state.uuid_action = svn_repos_load_uuid_ignore;
2905 case svnadmin__force_uuid:
2906 opt_state.uuid_action = svn_repos_load_uuid_force;
2908 case svnadmin__pre_1_4_compatible:
2909 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2910 opt_state.compatible_version->major = 1;
2911 opt_state.compatible_version->minor = 3;
2913 case svnadmin__pre_1_5_compatible:
2914 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2915 opt_state.compatible_version->major = 1;
2916 opt_state.compatible_version->minor = 4;
2918 case svnadmin__pre_1_6_compatible:
2919 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
2920 opt_state.compatible_version->major = 1;
2921 opt_state.compatible_version->minor = 5;
2923 case svnadmin__compatible_version:
2925 svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
2926 SVN_VER_PATCH, NULL };
2927 svn_version_t *compatible_version;
2929 /* Parse the version string which carries our target
2931 SVN_ERR(svn_version__parse_version_string(&compatible_version,
2934 /* We can't create repository with a version older than 1.0.0. */
2935 if (! svn_version__at_least(compatible_version, 1, 0, 0))
2937 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2938 _("Cannot create pre-1.0-compatible "
2942 /* We can't create repository with a version newer than what
2943 the running version of Subversion supports. */
2944 if (! svn_version__at_least(&latest,
2945 compatible_version->major,
2946 compatible_version->minor,
2947 compatible_version->patch))
2949 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2950 _("Cannot guarantee compatibility "
2951 "beyond the current running version "
2956 opt_state.compatible_version = compatible_version;
2959 case svnadmin__keep_going:
2960 opt_state.keep_going = TRUE;
2962 case svnadmin__check_normalization:
2963 opt_state.check_normalization = TRUE;
2965 case svnadmin__metadata_only:
2966 opt_state.metadata_only = TRUE;
2968 case svnadmin__fs_type:
2969 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
2971 case svnadmin__parent_dir:
2972 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
2974 opt_state.parent_dir
2975 = svn_dirent_internal_style(opt_state.parent_dir, pool);
2977 case svnadmin__use_pre_commit_hook:
2978 opt_state.use_pre_commit_hook = TRUE;
2980 case svnadmin__use_post_commit_hook:
2981 opt_state.use_post_commit_hook = TRUE;
2983 case svnadmin__use_pre_revprop_change_hook:
2984 opt_state.use_pre_revprop_change_hook = TRUE;
2986 case svnadmin__use_post_revprop_change_hook:
2987 opt_state.use_post_revprop_change_hook = TRUE;
2989 case svnadmin__bdb_txn_nosync:
2990 opt_state.bdb_txn_nosync = TRUE;
2992 case svnadmin__bdb_log_keep:
2993 opt_state.bdb_log_keep = TRUE;
2995 case svnadmin__bypass_hooks:
2996 opt_state.bypass_hooks = TRUE;
2998 case svnadmin__bypass_prop_validation:
2999 opt_state.bypass_prop_validation = TRUE;
3001 case svnadmin__ignore_dates:
3002 opt_state.ignore_dates = TRUE;
3004 case svnadmin__clean_logs:
3005 opt_state.clean_logs = TRUE;
3007 case svnadmin__config_dir:
3008 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3009 opt_state.config_dir =
3010 apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
3012 case svnadmin__wait:
3013 opt_state.wait = TRUE;
3015 case svnadmin__no_flush_to_disk:
3016 opt_state.no_flush_to_disk = TRUE;
3018 case svnadmin__normalize_props:
3019 opt_state.normalize_props = TRUE;
3021 case svnadmin__exclude:
3022 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3024 if (! opt_state.exclude)
3025 opt_state.exclude = apr_array_make(pool, 1, sizeof(const char *));
3026 APR_ARRAY_PUSH(opt_state.exclude, const char *) = utf8_opt_arg;
3028 case svnadmin__include:
3029 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3031 if (! opt_state.include)
3032 opt_state.include = apr_array_make(pool, 1, sizeof(const char *));
3033 APR_ARRAY_PUSH(opt_state.include, const char *) = utf8_opt_arg;
3035 case svnadmin__glob:
3036 opt_state.glob = TRUE;
3040 SVN_ERR(subcommand_help(NULL, NULL, pool));
3041 *exit_code = EXIT_FAILURE;
3042 return SVN_NO_ERROR;
3044 } /* close `switch' */
3045 } /* close `while' */
3047 /* If the user asked for help, then the rest of the arguments are
3048 the names of subcommands to get help on (if any), or else they're
3049 just typos/mistakes. Whatever the case, the subcommand to
3050 actually run is subcommand_help(). */
3052 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
3054 /* If we're not running the `help' subcommand, then look for a
3055 subcommand in the first argument. */
3056 if (subcommand == NULL)
3058 if (os->ind >= os->argc)
3060 if (opt_state.version)
3062 /* Use the "help" subcommand to handle the "--version" option. */
3063 static const svn_opt_subcommand_desc2_t pseudo_cmd =
3064 { "--version", subcommand_help, {0}, "",
3065 {svnadmin__version, /* must accept its own option */
3069 subcommand = &pseudo_cmd;
3073 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
3074 _("subcommand argument required\n")));
3075 SVN_ERR(subcommand_help(NULL, NULL, pool));
3076 *exit_code = EXIT_FAILURE;
3077 return SVN_NO_ERROR;
3082 const char *first_arg;
3084 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
3086 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
3087 if (subcommand == NULL)
3090 svn_cmdline_fprintf(stderr, pool,
3091 _("Unknown subcommand: '%s'\n"),
3093 SVN_ERR(subcommand_help(NULL, NULL, pool));
3094 *exit_code = EXIT_FAILURE;
3095 return SVN_NO_ERROR;
3100 /* Every subcommand except `help' and `freeze' with '-F' require a
3101 second argument -- the repository path. Parse it out here and
3102 store it in opt_state. */
3103 if (!(subcommand->cmd_func == subcommand_help
3104 || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
3106 const char *repos_path = NULL;
3108 if (os->ind >= os->argc)
3110 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3111 _("Repository argument required"));
3114 SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool));
3116 if (svn_path_is_url(repos_path))
3118 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3119 _("'%s' is a URL when it should be a "
3120 "local path"), repos_path);
3123 opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
3126 /* Check that the subcommand wasn't passed any inappropriate options. */
3127 for (i = 0; i < received_opts->nelts; i++)
3129 opt_id = APR_ARRAY_IDX(received_opts, i, int);
3131 /* All commands implicitly accept --help, so just skip over this
3132 when we see it. Note that we don't want to include this option
3133 in their "accepted options" list because it would be awfully
3134 redundant to display it in every commands' help text. */
3135 if (opt_id == 'h' || opt_id == '?')
3138 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
3141 const apr_getopt_option_t *badopt =
3142 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
3144 svn_opt_format_option(&optstr, badopt, FALSE, pool);
3145 if (subcommand->name[0] == '-')
3146 SVN_ERR(subcommand_help(NULL, NULL, pool));
3148 svn_error_clear(svn_cmdline_fprintf(stderr, pool
3149 , _("Subcommand '%s' doesn't accept option '%s'\n"
3150 "Type 'svnadmin help %s' for usage.\n"),
3151 subcommand->name, optstr, subcommand->name));
3152 *exit_code = EXIT_FAILURE;
3153 return SVN_NO_ERROR;
3157 check_cancel = svn_cmdline__setup_cancellation_handler();
3159 /* Configure FSFS caches for maximum efficiency with svnadmin.
3160 * Also, apply the respective command line parameters, if given. */
3162 svn_cache_config_t settings = *svn_cache_config_get();
3164 settings.cache_size = opt_state.memory_cache_size;
3165 settings.single_threaded = TRUE;
3167 svn_cache_config_set(&settings);
3170 /* Run the subcommand. */
3171 err = (*subcommand->cmd_func)(os, &opt_state, pool);
3174 /* For argument-related problems, suggest using the 'help'
3176 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
3177 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
3179 err = svn_error_quick_wrap(err,
3180 _("Try 'svnadmin help' for more info"));
3185 return SVN_NO_ERROR;
3189 main(int argc, const char *argv[])
3192 int exit_code = EXIT_SUCCESS;
3195 /* Initialize the app. */
3196 if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
3197 return EXIT_FAILURE;
3199 /* Create our top-level pool. Use a separate mutexless allocator,
3200 * given this application is single threaded.
3202 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
3204 err = sub_main(&exit_code, argc, argv, pool);
3206 /* Flush stdout and report if it fails. It would be flushed on exit anyway
3207 but this makes sure that output is not silently lost if it fails. */
3208 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
3212 exit_code = EXIT_FAILURE;
3213 svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ");
3216 svn_pool_destroy(pool);
3218 svn_cmdline__cancellation_exit();