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"
47 #include "private/svn_cmdline_private.h"
48 #include "private/svn_opt_private.h"
49 #include "private/svn_sorts_private.h"
50 #include "private/svn_subr_private.h"
51 #include "private/svn_cmdline_private.h"
52 #include "private/svn_fspath.h"
53 #include "private/svn_fs_fs_private.h"
55 #include "svn_private_config.h"
60 /* FSFS format 7's "block-read" feature performs poorly with small caches.
61 * Enable it only if caches above this threshold have been configured.
62 * The current threshold is 64MB. */
63 #define BLOCK_READ_CACHE_THRESHOLD (0x40 * 0x100000)
65 static svn_cancel_func_t check_cancel = NULL;
67 /* Custom filesystem warning function. */
69 warning_func(void *baton,
74 svn_handle_warning2(stderr, err, "svnadmin: ");
78 /* Version compatibility check */
80 check_lib_versions(void)
82 static const svn_version_checklist_t checklist[] =
84 { "svn_subr", svn_subr_version },
85 { "svn_repos", svn_repos_version },
86 { "svn_fs", svn_fs_version },
87 { "svn_delta", svn_delta_version },
90 SVN_VERSION_DEFINE(my_version);
92 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
99 static svn_opt_subcommand_t
100 subcommand_build_repcache,
101 subcommand_crashtest,
103 subcommand_delrevprop,
106 subcommand_dump_revprops,
112 subcommand_load_revprops,
113 subcommand_list_dblogs,
114 subcommand_list_unused_dblogs,
124 subcommand_setrevprop,
130 enum svnadmin__cmdline_options_t
132 svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID,
133 svnadmin__incremental,
134 svnadmin__keep_going,
136 svnadmin__ignore_uuid,
137 svnadmin__force_uuid,
139 svnadmin__parent_dir,
140 svnadmin__bdb_txn_nosync,
141 svnadmin__bdb_log_keep,
142 svnadmin__config_dir,
143 svnadmin__bypass_hooks,
144 svnadmin__bypass_prop_validation,
145 svnadmin__ignore_dates,
146 svnadmin__use_pre_commit_hook,
147 svnadmin__use_post_commit_hook,
148 svnadmin__use_pre_revprop_change_hook,
149 svnadmin__use_post_revprop_change_hook,
150 svnadmin__clean_logs,
152 svnadmin__pre_1_4_compatible,
153 svnadmin__pre_1_5_compatible,
154 svnadmin__pre_1_6_compatible,
155 svnadmin__compatible_version,
156 svnadmin__check_normalization,
157 svnadmin__metadata_only,
158 svnadmin__no_flush_to_disk,
159 svnadmin__normalize_props,
165 /* Option codes and descriptions.
167 * The entire list must be terminated with an entry of nulls.
169 static const apr_getopt_option_t options_table[] =
172 N_("show help on a subcommand")},
175 N_("show help on a subcommand")},
177 {"version", svnadmin__version, 0,
178 N_("show program version information")},
181 N_("specify revision number ARG (or X:Y range)")},
183 {"transaction", 't', 1,
184 N_("specify transaction name ARG")},
186 {"incremental", svnadmin__incremental, 0,
187 N_("dump or hotcopy incrementally")},
189 {"deltas", svnadmin__deltas, 0,
190 N_("use deltas in dump output")},
192 {"bypass-hooks", svnadmin__bypass_hooks, 0,
193 N_("bypass the repository hook system")},
195 {"bypass-prop-validation", svnadmin__bypass_prop_validation, 0,
196 N_("bypass property validation logic")},
198 {"ignore-dates", svnadmin__ignore_dates, 0,
199 N_("ignore revision datestamps found in the stream")},
202 N_("no progress (only errors to stderr)")},
204 {"ignore-uuid", svnadmin__ignore_uuid, 0,
205 N_("ignore any repos UUID found in the stream")},
207 {"force-uuid", svnadmin__force_uuid, 0,
208 N_("set repos UUID to that found in stream, if any")},
210 {"fs-type", svnadmin__fs_type, 1,
211 N_("type of repository:\n"
212 " 'fsfs' (default), 'bdb' or 'fsx'\n"
213 " CAUTION: FSX is for EXPERIMENTAL use only!")},
215 {"parent-dir", svnadmin__parent_dir, 1,
216 N_("load at specified directory in repository")},
218 {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0,
219 N_("disable fsync at transaction commit [Berkeley DB]")},
221 {"bdb-log-keep", svnadmin__bdb_log_keep, 0,
222 N_("disable automatic log file removal [Berkeley DB]")},
224 {"config-dir", svnadmin__config_dir, 1,
225 N_("read user configuration files from directory ARG")},
227 {"clean-logs", svnadmin__clean_logs, 0,
228 N_("remove redundant Berkeley DB log files\n"
229 " from source repository [Berkeley DB]")},
231 {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0,
232 N_("call pre-commit hook before committing revisions")},
234 {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0,
235 N_("call post-commit hook after committing revisions")},
237 {"use-pre-revprop-change-hook", svnadmin__use_pre_revprop_change_hook, 0,
238 N_("call hook before changing revision property")},
240 {"use-post-revprop-change-hook", svnadmin__use_post_revprop_change_hook, 0,
241 N_("call hook after changing revision property")},
243 {"wait", svnadmin__wait, 0,
244 N_("wait instead of exit if the repository is in\n"
245 " use by another process")},
247 {"pre-1.4-compatible", svnadmin__pre_1_4_compatible, 0,
248 N_("deprecated; see --compatible-version")},
250 {"pre-1.5-compatible", svnadmin__pre_1_5_compatible, 0,
251 N_("deprecated; see --compatible-version")},
253 {"pre-1.6-compatible", svnadmin__pre_1_6_compatible, 0,
254 N_("deprecated; see --compatible-version")},
256 {"keep-going", svnadmin__keep_going, 0,
257 N_("continue verification after detecting a corruption")},
259 {"memory-cache-size", 'M', 1,
260 N_("size of the extra in-memory cache in MB used to\n"
261 " minimize redundant operations. Default: 16.\n"
262 " [used for FSFS repositories only]")},
264 {"compatible-version", svnadmin__compatible_version, 1,
265 N_("use repository format compatible with Subversion\n"
266 " version ARG (\"1.5.5\", \"1.7\", etc.)")},
268 {"file", 'F', 1, N_("read repository paths from file ARG")},
270 {"check-normalization", svnadmin__check_normalization, 0,
271 N_("report any names within the same directory or\n"
272 " svn:mergeinfo property value that differ only\n"
273 " in character representation, but are otherwise\n"
276 {"metadata-only", svnadmin__metadata_only, 0,
277 N_("verify metadata only (ignored for BDB),\n"
278 " checking against external corruption in\n"
279 " Subversion 1.9+ format repositories.\n")},
281 {"no-flush-to-disk", svnadmin__no_flush_to_disk, 0,
282 N_("disable flushing to disk during the operation\n"
283 " (faster, but unsafe on power off)")},
285 {"normalize-props", svnadmin__normalize_props, 0,
286 N_("normalize property values found in the dumpstream\n"
287 " (currently, only translates non-LF line endings)")},
289 {"exclude", svnadmin__exclude, 1,
290 N_("filter out nodes with given prefix(es) from dump")},
292 {"include", svnadmin__include, 1,
293 N_("filter out nodes without given prefix(es) from dump")},
295 {"pattern", svnadmin__glob, 0,
296 N_("treat the path prefixes as file glob patterns.\n"
297 " Glob special characters are '*' '?' '[]' and '\\'.\n"
298 " Character '/' is not treated specially, so\n"
299 " pattern /*/foo matches paths /a/foo and /a/b/foo.") },
305 /* Array of available subcommands.
306 * The entire list must be terminated with an entry of nulls.
308 static const svn_opt_subcommand_desc3_t cmd_table[] =
310 {"build-repcache", subcommand_build_repcache, {0}, {N_(
311 "usage: svnadmin build-repcache REPOS_PATH [-r LOWER[:UPPER]]\n"
313 "Add missing entries to the representation cache for the repository\n"
314 "at REPOS_PATH. Process data in revisions LOWER through UPPER.\n"
315 "If no revision arguments are given, process all revisions. If only\n"
316 "LOWER revision argument is given, process only that single revision.\n"
320 {"crashtest", subcommand_crashtest, {0}, {N_(
321 "usage: svnadmin crashtest REPOS_PATH\n"
323 "Open the repository at REPOS_PATH, then abort, thus simulating\n"
324 "a process that crashes while holding an open repository handle.\n"
328 {"create", subcommand_create, {0}, {N_(
329 "usage: svnadmin create REPOS_PATH\n"
331 "Create a new, empty repository at REPOS_PATH.\n"
333 {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep,
334 svnadmin__config_dir, svnadmin__fs_type, svnadmin__compatible_version,
335 svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible,
336 svnadmin__pre_1_6_compatible
339 {"delrevprop", subcommand_delrevprop, {0}, {N_(
340 "usage: 1. svnadmin delrevprop REPOS_PATH -r REVISION NAME\n"
341 " 2. svnadmin delrevprop REPOS_PATH -t TXN NAME\n"
343 "1. Delete the property NAME on revision REVISION.\n"
345 "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
346 "trigger the revision property-related hooks (for example, if you want\n"
347 "an email notification sent from your post-revprop-change hook).\n"
349 "NOTE: Revision properties are not versioned, so this command will\n"
350 "irreversibly destroy the previous value of the property.\n"
352 "2. Delete the property NAME on transaction TXN.\n"
354 {'r', 't', svnadmin__use_pre_revprop_change_hook,
355 svnadmin__use_post_revprop_change_hook} },
357 {"deltify", subcommand_deltify, {0}, {N_(
358 "usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n"
360 "Run over the requested revision range, performing predecessor delti-\n"
361 "fication on the paths changed in those revisions. Deltification in\n"
362 "essence compresses the repository by only storing the differences or\n"
363 "delta from the preceding revision. If no revisions are specified,\n"
364 "this will simply deltify the HEAD revision.\n"
368 {"dump", subcommand_dump, {0}, {N_(
369 "usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]]\n"
371 "Dump the contents of filesystem to stdout in a 'dumpfile'\n"
372 "portable format, sending feedback to stderr. Dump revisions\n"
373 "LOWER rev through UPPER rev. If no revisions are given, dump all\n"
374 "revision trees. If only LOWER is given, dump that one revision tree.\n"
375 "If --incremental is passed, the first revision dumped will describe\n"
376 "only the paths changed in that revision; otherwise it will describe\n"
377 "every path present in the repository as of that revision. (In either\n"
378 "case, the second and subsequent revisions, if any, describe only paths\n"
379 "changed in those revisions.)\n"
381 "Using --exclude or --include gives results equivalent to authz-based\n"
382 "path exclusions. In particular, when the source of a copy is\n"
383 "excluded, the copy is transformed into an add (unlike in 'svndumpfilter').\n"
385 {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M', 'F',
386 svnadmin__exclude, svnadmin__include, svnadmin__glob },
387 {{'F', N_("write to file ARG instead of stdout")}} },
389 {"dump-revprops", subcommand_dump_revprops, {0}, {N_(
390 "usage: svnadmin dump-revprops REPOS_PATH [-r LOWER[:UPPER]]\n"
392 "Dump the revision properties of filesystem to stdout in a 'dumpfile'\n"
393 "portable format, sending feedback to stderr. Dump revisions\n"
394 "LOWER rev through UPPER rev. If no revisions are given, dump the\n"
395 "properties for all revisions. If only LOWER is given, dump the\n"
396 "properties for that one revision.\n"
399 {{'F', N_("write to file ARG instead of stdout")}} },
401 {"freeze", subcommand_freeze, {0}, {N_(
402 "usage: 1. svnadmin freeze REPOS_PATH -- PROGRAM [ARG...]\n"
403 " 2. svnadmin freeze -F FILE -- PROGRAM [ARG...]\n"
405 "1. Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n"
406 " Allows safe use of third-party backup tools on a live repository.\n"
408 "2. Like 1 except all repositories listed in FILE are locked. The file\n"
409 " format is repository paths separated by newlines. Repositories are\n"
410 " locked in the same order as they are listed in the file.\n"
412 "The '--' tells svnadmin to stop looking for svnadmin options and pass\n"
413 "all later arguments to PROGRAM even if they begin with '-'.\n"
416 {{'F', N_("read repository paths from file ARG")}} },
418 {"help", subcommand_help, {"?", "h"}, {N_(
419 "usage: svnadmin help [SUBCOMMAND...]\n"
421 "Describe the usage of this program or its subcommands.\n"
425 {"hotcopy", subcommand_hotcopy, {0}, {N_(
426 "usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n"
428 "Make a hot copy of a repository.\n"
429 "If --incremental is passed, data which already exists at the destination\n"
430 "is not copied again. Incremental mode is implemented for FSFS repositories.\n"
432 {svnadmin__clean_logs, svnadmin__incremental, 'q'} },
434 {"info", subcommand_info, {0}, {N_(
435 "usage: svnadmin info REPOS_PATH\n"
437 "Print information about the repository at REPOS_PATH.\n"
441 {"list-dblogs", subcommand_list_dblogs, {0}, {N_(
442 "usage: svnadmin list-dblogs REPOS_PATH\n"
444 "List all Berkeley DB log files.\n"
446 "WARNING: Modifying or deleting logfiles which are still in use\n"
447 "will cause your repository to be corrupted.\n"
451 {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, {N_(
452 "usage: svnadmin list-unused-dblogs REPOS_PATH\n"
454 "List unused Berkeley DB log files.\n"
458 {"load", subcommand_load, {0}, {N_(
459 "usage: svnadmin load REPOS_PATH\n"
461 "Read a 'dumpfile'-formatted stream from stdin, committing\n"
462 "new revisions into the repository's filesystem. If the repository\n"
463 "was previously empty, its UUID will, by default, be changed to the\n"
464 "one specified in the stream. Progress feedback is sent to stdout.\n"
465 "If --revision is specified, limit the loaded revisions to only those\n"
466 "in the dump stream whose revision numbers match the specified range.\n"
468 {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid,
469 svnadmin__ignore_dates,
470 svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook,
471 svnadmin__parent_dir, svnadmin__normalize_props,
472 svnadmin__bypass_prop_validation, 'M',
473 svnadmin__no_flush_to_disk, 'F'},
474 {{'F', N_("read from file ARG instead of stdin")}} },
476 {"load-revprops", subcommand_load_revprops, {0}, {N_(
477 "usage: svnadmin load-revprops REPOS_PATH\n"
479 "Read a 'dumpfile'-formatted stream from stdin, setting the revision\n"
480 "properties in the repository's filesystem. Revisions not found in the\n"
481 "repository will cause an error. Progress feedback is sent to stdout.\n"
482 "If --revision is specified, limit the loaded revisions to only those\n"
483 "in the dump stream whose revision numbers match the specified range.\n"
485 {'q', 'r', svnadmin__force_uuid, svnadmin__normalize_props,
486 svnadmin__bypass_prop_validation, svnadmin__no_flush_to_disk, 'F'},
487 {{'F', N_("read from file ARG instead of stdin")}} },
489 {"lock", subcommand_lock, {0}, {N_(
490 "usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE [TOKEN]\n"
492 "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n"
493 "If provided, use TOKEN as lock token. Use --bypass-hooks to avoid\n"
494 "triggering the pre-lock and post-lock hook scripts.\n"
496 {svnadmin__bypass_hooks, 'q'} },
498 {"lslocks", subcommand_lslocks, {0}, {N_(
499 "usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n"
501 "Print descriptions of all locks on or under PATH-IN-REPOS (which,\n"
502 "if not provided, is the root of the repository).\n"
506 {"lstxns", subcommand_lstxns, {0}, {N_(
507 "usage: svnadmin lstxns REPOS_PATH\n"
509 "Print the names of uncommitted transactions. With -rN skip the output\n"
510 "of those that have a base revision more recent than rN. Transactions\n"
511 "with base revisions much older than HEAD are likely to have been\n"
512 "abandoned and are candidates to be removed.\n"
515 { {'r', "transaction base revision ARG"} } },
517 {"pack", subcommand_pack, {0}, {N_(
518 "usage: svnadmin pack REPOS_PATH\n"
520 "Possibly compact the repository into a more efficient storage model.\n"
521 "This may not apply to all repositories, in which case, exit.\n"
525 {"recover", subcommand_recover, {0}, {N_(
526 "usage: svnadmin recover REPOS_PATH\n"
528 "Run the recovery procedure on a repository. Do this if you've\n"
529 "been getting errors indicating that recovery ought to be run.\n"
530 "Berkeley DB recovery requires exclusive access and will\n"
531 "exit if the repository is in use by another process.\n"
535 {"rev-size", subcommand_rev_size, {0}, {N_(
536 "usage: svnadmin rev-size REPOS_PATH -r REVISION\n"
538 "Print the total size in bytes of the representation on disk of\n"
539 "revision REVISION.\n"
541 "The size includes revision properties and excludes FSFS indexes.\n"
544 { {'q', "print only the size and a newline"} } },
546 {"rmlocks", subcommand_rmlocks, {0}, {N_(
547 "usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n"
549 "Unconditionally remove lock from each LOCKED_PATH.\n"
553 {"rmtxns", subcommand_rmtxns, {0}, {N_(
554 "usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n"
556 "Delete the named transaction(s).\n"
560 {"setlog", subcommand_setlog, {0}, {N_(
561 "usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n"
563 "Set the log-message on revision REVISION to the contents of FILE. Use\n"
564 "--bypass-hooks to avoid triggering the revision-property-related hooks\n"
565 "(for example, if you do not want an email notification sent\n"
566 "from your post-revprop-change hook, or because the modification of\n"
567 "revision properties has not been enabled in the pre-revprop-change\n"
570 "NOTE: Revision properties are not versioned, so this command will\n"
571 "overwrite the previous log message.\n"
573 {'r', svnadmin__bypass_hooks} },
575 {"setrevprop", subcommand_setrevprop, {0}, {N_(
576 "usage: 1. svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n"
577 " 2. svnadmin setrevprop REPOS_PATH -t TXN NAME FILE\n"
579 "1. Set the property NAME on revision REVISION to the contents of FILE.\n"
581 "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n"
582 "trigger the revision property-related hooks (for example, if you want\n"
583 "an email notification sent from your post-revprop-change hook).\n"
585 "NOTE: Revision properties are not versioned, so this command will\n"
586 "overwrite the previous value of the property.\n"
588 "2. Set the property NAME on transaction TXN to the contents of FILE.\n"
590 {'r', 't', svnadmin__use_pre_revprop_change_hook,
591 svnadmin__use_post_revprop_change_hook} },
593 {"setuuid", subcommand_setuuid, {0}, {N_(
594 "usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n"
596 "Reset the repository UUID for the repository located at REPOS_PATH. If\n"
597 "NEW_UUID is provided, use that as the new repository UUID; otherwise,\n"
598 "generate a brand new UUID for the repository.\n"
602 {"unlock", subcommand_unlock, {0}, {N_(
603 "usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n"
605 "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n"
606 "associated with the lock matches TOKEN. Use --bypass-hooks to avoid\n"
607 "triggering the pre-unlock and post-unlock hook scripts.\n"
609 {svnadmin__bypass_hooks, 'q'} },
611 {"upgrade", subcommand_upgrade, {0}, {N_(
612 "usage: svnadmin upgrade REPOS_PATH\n"
614 "Upgrade the repository located at REPOS_PATH to the latest supported\n"
617 "This functionality is provided as a convenience for repository\n"
618 "administrators who wish to make use of new Subversion functionality\n"
619 "without having to undertake a potentially costly full repository dump\n"
620 "and load operation. As such, the upgrade performs only the minimum\n"
621 "amount of work needed to accomplish this while still maintaining the\n"
622 "integrity of the repository. It does not guarantee the most optimized\n"
623 "repository state as a dump and subsequent load would.\n"
627 {"verify", subcommand_verify, {0}, {N_(
628 "usage: svnadmin verify REPOS_PATH\n"
630 "Verify the data stored in the repository.\n"
632 {'t', 'r', 'q', svnadmin__keep_going, 'M',
633 svnadmin__check_normalization, svnadmin__metadata_only} },
635 { NULL, NULL, {0}, {NULL}, {0} }
639 /* Baton for passing option/argument state to a subcommand function. */
640 struct svnadmin_opt_state
642 const char *repository_path;
643 const char *fs_type; /* --fs-type */
644 svn_version_t *compatible_version; /* --compatible-version */
645 svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */
646 const char *txn_id; /* -t TXN */
647 svn_boolean_t help; /* --help or -? */
648 svn_boolean_t version; /* --version */
649 svn_boolean_t incremental; /* --incremental */
650 svn_boolean_t use_deltas; /* --deltas */
651 svn_boolean_t use_pre_commit_hook; /* --use-pre-commit-hook */
652 svn_boolean_t use_post_commit_hook; /* --use-post-commit-hook */
653 svn_boolean_t use_pre_revprop_change_hook; /* --use-pre-revprop-change-hook */
654 svn_boolean_t use_post_revprop_change_hook; /* --use-post-revprop-change-hook */
655 svn_boolean_t quiet; /* --quiet */
656 svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync */
657 svn_boolean_t bdb_log_keep; /* --bdb-log-keep */
658 svn_boolean_t clean_logs; /* --clean-logs */
659 svn_boolean_t bypass_hooks; /* --bypass-hooks */
660 svn_boolean_t wait; /* --wait */
661 svn_boolean_t keep_going; /* --keep-going */
662 svn_boolean_t check_normalization; /* --check-normalization */
663 svn_boolean_t metadata_only; /* --metadata-only */
664 svn_boolean_t bypass_prop_validation; /* --bypass-prop-validation */
665 svn_boolean_t ignore_dates; /* --ignore-dates */
666 svn_boolean_t no_flush_to_disk; /* --no-flush-to-disk */
667 svn_boolean_t normalize_props; /* --normalize_props */
668 enum svn_repos_load_uuid uuid_action; /* --ignore-uuid,
670 apr_uint64_t memory_cache_size; /* --memory-cache-size M */
671 const char *parent_dir; /* --parent-dir */
672 const char *file; /* --file */
673 apr_array_header_t *exclude; /* --exclude */
674 apr_array_header_t *include; /* --include */
675 svn_boolean_t glob; /* --pattern */
677 const char *config_dir; /* Overriding Configuration Directory */
681 /* Helper to open a repository and set a warning func (so we don't
682 * SEGFAULT when libsvn_fs's default handler gets run). */
684 open_repos(svn_repos_t **repos,
686 struct svnadmin_opt_state *opt_state,
689 /* Enable the "block-read" feature (where it applies)? */
690 svn_boolean_t use_block_read
691 = svn_cache_config_get()->cache_size > BLOCK_READ_CACHE_THRESHOLD;
693 /* construct FS configuration parameters: enable caches for r/o data */
694 apr_hash_t *fs_config = apr_hash_make(pool);
695 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1");
696 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1");
697 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NODEPROPS, "1");
698 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2");
699 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS,
700 svn_uuid_generate(pool));
701 svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ,
702 use_block_read ? "1" : "0");
703 svn_hash_sets(fs_config, SVN_FS_CONFIG_NO_FLUSH_TO_DISK,
704 opt_state->no_flush_to_disk ? "1" : "0");
706 /* now, open the requested repository */
707 SVN_ERR(svn_repos_open3(repos, path, fs_config, pool, pool));
708 svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL);
713 /* Set *REVNUM to the revision specified by REVISION (or to
714 SVN_INVALID_REVNUM if that has the type 'unspecified'),
715 possibly making use of the YOUNGEST revision number in REPOS. */
717 get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision,
718 svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool)
720 if (revision->kind == svn_opt_revision_number)
721 *revnum = revision->value.number;
722 else if (revision->kind == svn_opt_revision_head)
724 else if (revision->kind == svn_opt_revision_date)
725 SVN_ERR(svn_repos_dated_revision(revnum, repos, revision->value.date,
727 else if (revision->kind == svn_opt_revision_unspecified)
728 *revnum = SVN_INVALID_REVNUM;
730 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
731 _("Invalid revision specifier"));
733 if (*revnum > youngest)
734 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
735 _("Revisions must not be greater than the youngest revision (%ld)"),
741 /* Set *FSPATH to an internal-style fspath parsed from ARG. */
743 target_arg_to_fspath(const char **fspath,
745 apr_pool_t *result_pool,
746 apr_pool_t *scratch_pool)
748 /* ### Using a private API. This really shouldn't be needed. */
749 *fspath = svn_fspath__canonicalize(arg, result_pool);
753 /* Set *DIRENT to an internal-style, local dirent path
754 allocated from POOL and parsed from PATH. */
756 target_arg_to_dirent(const char **dirent,
760 if (svn_path_is_url(path))
761 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
762 _("Path '%s' is not a local path"), path);
763 *dirent = svn_dirent_internal_style(path, pool);
767 /* Parse the remaining command-line arguments from OS, returning them
768 in a new array *ARGS (allocated from POOL) and optionally verifying
769 that we got the expected number thereof. If MIN_EXPECTED is not
770 negative, return an error if the function would return fewer than
771 MIN_EXPECTED arguments. If MAX_EXPECTED is not negative, return an
772 error if the function would return more than MAX_EXPECTED
775 As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0,
776 allow ARGS to be NULL. */
778 parse_args(apr_array_header_t **args,
784 int num_args = os ? (os->argc - os->ind) : 0;
786 if (min_expected || max_expected)
787 SVN_ERR_ASSERT(args);
789 if ((min_expected >= 0) && (num_args < min_expected))
790 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0,
791 _("Not enough arguments"));
792 if ((max_expected >= 0) && (num_args > max_expected))
793 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
794 _("Too many arguments"));
797 *args = apr_array_make(pool, num_args, sizeof(const char *));
800 while (os->ind < os->argc)
804 SVN_ERR(svn_utf_cstring_to_utf8(&arg, os->argv[os->ind++], pool));
805 APR_ARRAY_PUSH(*args, const char *) = arg;
813 /* This implements 'svn_error_malfunction_handler_t. */
815 crashtest_malfunction_handler(svn_boolean_t can_return,
821 return SVN_NO_ERROR; /* Not reached. */
824 /* This implements `svn_opt_subcommand_t'. */
826 subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
828 struct svnadmin_opt_state *opt_state = baton;
831 (void)svn_error_set_malfunction_handler(crashtest_malfunction_handler);
832 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
833 SVN_ERR(svn_cmdline_printf(pool,
834 _("Successfully opened repository '%s'.\n"
835 "Will now crash to simulate a crashing "
836 "server process.\n"),
837 svn_dirent_local_style(opt_state->repository_path,
839 SVN_ERR_MALFUNCTION();
841 /* merely silence a compiler warning (this will never be executed) */
845 /* This implements `svn_opt_subcommand_t'. */
847 subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool)
849 struct svnadmin_opt_state *opt_state = baton;
851 apr_hash_t *fs_config = apr_hash_make(pool);
853 /* Expect no more arguments. */
854 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
856 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
857 (opt_state->bdb_txn_nosync ? "1" :"0"));
859 svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
860 (opt_state->bdb_log_keep ? "0" :"1"));
862 if (opt_state->fs_type)
864 /* With 1.8 we are announcing that BDB is deprecated. No support
865 * has been removed and it will continue to work until some future
866 * date. The purpose here is to discourage people from creating
867 * new BDB repositories which they will need to dump/load into
868 * FSFS or some new FS type in the future. */
869 if (0 == strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB))
871 SVN_ERR(svn_cmdline_fprintf(
874 " The \"%s\" repository back-end is deprecated,"
875 " consider using \"%s\" instead.\n"),
876 "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS));
879 svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type);
882 if (opt_state->compatible_version)
884 if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0))
885 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1");
886 if (! svn_version__at_least(opt_state->compatible_version, 1, 5, 0))
887 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1");
888 if (! svn_version__at_least(opt_state->compatible_version, 1, 6, 0))
889 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1");
890 if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0))
891 svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1");
892 /* In 1.9, we figured out that we didn't have to keep extending this
893 madness indefinitely. */
894 svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION,
895 apr_psprintf(pool, "%d.%d.%d%s%s",
896 opt_state->compatible_version->major,
897 opt_state->compatible_version->minor,
898 opt_state->compatible_version->patch,
899 opt_state->compatible_version->tag
901 opt_state->compatible_version->tag
902 ? opt_state->compatible_version->tag : ""));
905 if (opt_state->compatible_version)
907 if (! svn_version__at_least(opt_state->compatible_version, 1, 1, 0)
908 /* ### TODO: this NULL check hard-codes knowledge of the library's
909 default fs-type value */
910 && (opt_state->fs_type == NULL
911 || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS)))
913 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
914 _("Repositories compatible with 1.0.x must "
915 "use --fs-type=bdb"));
918 if (! svn_version__at_least(opt_state->compatible_version, 1, 9, 0)
919 && opt_state->fs_type && !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSX))
921 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
922 _("Repositories compatible with 1.8.x or "
923 "earlier cannot use --fs-type=%s"),
928 SVN_ERR(svn_repos_create(&repos, opt_state->repository_path,
929 NULL, NULL, NULL, fs_config, pool));
930 svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL);
935 /* This implements `svn_opt_subcommand_t'. */
937 subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
939 struct svnadmin_opt_state *opt_state = baton;
942 svn_revnum_t start = SVN_INVALID_REVNUM, end = SVN_INVALID_REVNUM;
943 svn_revnum_t youngest, revision;
944 apr_pool_t *subpool = svn_pool_create(pool);
946 /* Expect no more arguments. */
947 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
949 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
950 fs = svn_repos_fs(repos);
951 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
953 /* Find the revision numbers at which to start and end. */
954 SVN_ERR(get_revnum(&start, &opt_state->start_revision,
955 youngest, repos, pool));
956 SVN_ERR(get_revnum(&end, &opt_state->end_revision,
957 youngest, repos, pool));
959 /* Fill in implied revisions if necessary. */
960 if (start == SVN_INVALID_REVNUM)
962 if (end == SVN_INVALID_REVNUM)
966 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
967 _("First revision cannot be higher than second"));
969 /* Loop over the requested revision range, performing the
970 predecessor deltification on paths changed in each. */
971 for (revision = start; revision <= end; revision++)
973 svn_pool_clear(subpool);
974 SVN_ERR(check_cancel(NULL));
975 if (! opt_state->quiet)
976 SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision %ld..."),
978 SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool));
979 if (! opt_state->quiet)
980 SVN_ERR(svn_cmdline_printf(subpool, _("done.\n")));
982 svn_pool_destroy(subpool);
987 /* Structure for errors encountered during 'svnadmin verify --keep-going'. */
988 struct verification_error
994 /* Pool cleanup function to clear an svn_error_t *. */
996 err_cleanup(void *data)
998 svn_error_t *err = data;
1000 svn_error_clear(err);
1005 struct repos_verify_callback_baton
1007 /* Should we continue after receiving a first verification error? */
1008 svn_boolean_t keep_going;
1010 /* List of errors encountered during 'svnadmin verify --keep-going'. */
1011 apr_array_header_t *error_summary;
1013 /* Pool for data collected during callback invocations. */
1014 apr_pool_t *result_pool;
1017 /* Implementation of svn_repos_verify_callback_t to handle errors coming
1018 from svn_repos_verify_fs3(). */
1019 static svn_error_t *
1020 repos_verify_callback(void *baton,
1021 svn_revnum_t revision,
1022 svn_error_t *verify_err,
1023 apr_pool_t *scratch_pool)
1025 struct repos_verify_callback_baton *b = baton;
1027 if (revision == SVN_INVALID_REVNUM)
1029 SVN_ERR(svn_cmdline_fputs(_("* Error verifying repository metadata.\n"),
1030 stderr, scratch_pool));
1034 SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool,
1035 _("* Error verifying revision %ld.\n"),
1041 struct verification_error *verr;
1043 svn_handle_error2(verify_err, stderr, FALSE, "svnadmin: ");
1045 /* Remember the error in B->ERROR_SUMMARY. */
1046 verr = apr_palloc(b->result_pool, sizeof(*verr));
1047 verr->rev = revision;
1048 verr->err = svn_error_dup(verify_err);
1049 apr_pool_cleanup_register(b->result_pool, verr->err, err_cleanup,
1050 apr_pool_cleanup_null);
1051 APR_ARRAY_PUSH(b->error_summary, struct verification_error *) = verr;
1053 return SVN_NO_ERROR;
1056 return svn_error_trace(svn_error_dup(verify_err));
1059 /* Implementation of svn_repos_notify_func_t to wrap the output to a
1060 response stream for svn_repos_dump_fs2(), svn_repos_verify_fs(),
1061 svn_repos_hotcopy3() and others. */
1063 repos_notify_handler(void *baton,
1064 const svn_repos_notify_t *notify,
1065 apr_pool_t *scratch_pool)
1067 svn_stream_t *feedback_stream = baton;
1069 switch (notify->action)
1071 case svn_repos_notify_warning:
1072 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1073 "WARNING 0x%04x: %s\n", notify->warning,
1074 notify->warning_str));
1077 case svn_repos_notify_dump_rev_end:
1078 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1079 _("* Dumped revision %ld.\n"),
1083 case svn_repos_notify_verify_rev_end:
1084 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1085 _("* Verified revision %ld.\n"),
1089 case svn_repos_notify_verify_rev_structure:
1090 if (notify->revision == SVN_INVALID_REVNUM)
1091 svn_error_clear(svn_stream_puts(feedback_stream,
1092 _("* Verifying repository metadata ...\n")));
1094 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1095 _("* Verifying metadata at revision %ld ...\n"),
1099 case svn_repos_notify_pack_shard_start:
1101 const char *shardstr = apr_psprintf(scratch_pool,
1102 "%" APR_INT64_T_FMT,
1104 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1105 _("Packing revisions in shard %s..."),
1110 case svn_repos_notify_pack_shard_end:
1111 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
1114 case svn_repos_notify_pack_shard_start_revprop:
1116 const char *shardstr = apr_psprintf(scratch_pool,
1117 "%" APR_INT64_T_FMT,
1119 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1120 _("Packing revprops in shard %s..."),
1125 case svn_repos_notify_pack_shard_end_revprop:
1126 svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n")));
1129 case svn_repos_notify_load_txn_committed:
1130 if (notify->old_revision == SVN_INVALID_REVNUM)
1132 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1133 _("\n------- Committed revision %ld >>>\n\n"),
1134 notify->new_revision));
1138 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1139 _("\n------- Committed new rev %ld"
1140 " (loaded from original rev %ld"
1141 ") >>>\n\n"), notify->new_revision,
1142 notify->old_revision));
1146 case svn_repos_notify_load_node_start:
1148 switch (notify->node_action)
1150 case svn_node_action_change:
1151 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1152 _(" * editing path : %s ..."),
1156 case svn_node_action_delete:
1157 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1158 _(" * deleting path : %s ..."),
1162 case svn_node_action_add:
1163 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1164 _(" * adding path : %s ..."),
1168 case svn_node_action_replace:
1169 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1170 _(" * replacing path : %s ..."),
1178 case svn_repos_notify_load_node_done:
1179 svn_error_clear(svn_stream_puts(feedback_stream, _(" done.\n")));
1182 case svn_repos_notify_load_copied_node:
1183 svn_error_clear(svn_stream_puts(feedback_stream, "COPIED..."));
1186 case svn_repos_notify_load_txn_start:
1187 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1188 _("<<< Started new transaction, based on "
1189 "original revision %ld\n"),
1190 notify->old_revision));
1193 case svn_repos_notify_load_skipped_rev:
1194 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1195 _("<<< Skipped original revision %ld\n"),
1196 notify->old_revision));
1199 case svn_repos_notify_load_normalized_mergeinfo:
1200 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1201 _(" removing '\\r' from %s ..."),
1202 SVN_PROP_MERGEINFO));
1205 case svn_repos_notify_mutex_acquired:
1206 svn_cmdline__setup_cancellation_handler();
1209 case svn_repos_notify_recover_start:
1210 svn_error_clear(svn_stream_puts(feedback_stream,
1211 _("Repository lock acquired.\n"
1212 "Please wait; recovering the"
1213 " repository may take some time...\n")));
1216 case svn_repos_notify_upgrade_start:
1217 svn_error_clear(svn_stream_puts(feedback_stream,
1218 _("Repository lock acquired.\n"
1219 "Please wait; upgrading the"
1220 " repository may take some time...\n")));
1223 case svn_repos_notify_pack_revprops:
1225 const char *shardstr = apr_psprintf(scratch_pool,
1226 "%" APR_INT64_T_FMT,
1228 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1229 _("Packed revision properties in shard %s\n"),
1234 case svn_repos_notify_cleanup_revprops:
1236 const char *shardstr = apr_psprintf(scratch_pool,
1237 "%" APR_INT64_T_FMT,
1239 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1240 _("Removed non-packed revision properties"
1246 case svn_repos_notify_format_bumped:
1247 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1248 _("Bumped repository format to %ld\n"),
1252 case svn_repos_notify_hotcopy_rev_range:
1253 if (notify->start_revision == notify->end_revision)
1255 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1256 _("* Copied revision %ld.\n"),
1257 notify->start_revision));
1261 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1262 _("* Copied revisions from %ld to %ld.\n"),
1263 notify->start_revision, notify->end_revision));
1267 case svn_repos_notify_pack_noop:
1268 /* For best backward compatibility, we keep silent if there were just
1269 no more shards to pack. */
1270 if (notify->shard == -1)
1272 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1273 _("svnadmin: Warning - this repository is not sharded."
1274 " Packing has no effect.\n")));
1278 case svn_repos_notify_load_revprop_set:
1279 svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool,
1280 _("Properties set on revision %ld.\n"),
1281 notify->new_revision));
1290 /* Baton for recode_write(). */
1291 struct recode_write_baton
1297 /* This implements the 'svn_write_fn_t' interface.
1299 Write DATA to ((struct recode_write_baton *) BATON)->out, in the
1300 console encoding, using svn_cmdline_fprintf(). DATA is a
1301 UTF8-encoded C string, therefore ignore LEN.
1303 ### This recoding mechanism might want to be abstracted into
1304 ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */
1305 static svn_error_t *recode_write(void *baton,
1309 struct recode_write_baton *rwb = baton;
1310 svn_pool_clear(rwb->pool);
1311 return svn_cmdline_fputs(data, rwb->out, rwb->pool);
1314 /* Create a stream, to write to STD_STREAM, that uses recode_write()
1315 to perform UTF-8 to console encoding translation. */
1316 static svn_stream_t *
1317 recode_stream_create(FILE *std_stream, apr_pool_t *pool)
1319 struct recode_write_baton *std_stream_rwb =
1320 apr_palloc(pool, sizeof(struct recode_write_baton));
1322 svn_stream_t *rw_stream = svn_stream_create(std_stream_rwb, pool);
1323 std_stream_rwb->pool = svn_pool_create(pool);
1324 std_stream_rwb->out = std_stream;
1325 svn_stream_set_write(rw_stream, recode_write);
1329 /* Read the min / max revision from the OPT_STATE, verify them against REPOS
1330 and return them in *LOWER and *UPPER, respectively. Use SCRATCH_POOL
1331 for temporary allocations. */
1332 static svn_error_t *
1333 get_dump_range(svn_revnum_t *lower,
1334 svn_revnum_t *upper,
1336 struct svnadmin_opt_state *opt_state,
1337 apr_pool_t *scratch_pool)
1340 svn_revnum_t youngest;
1342 *lower = SVN_INVALID_REVNUM;
1343 *upper = SVN_INVALID_REVNUM;
1345 fs = svn_repos_fs(repos);
1346 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, scratch_pool));
1348 /* Find the revision numbers at which to start and end. */
1349 SVN_ERR(get_revnum(lower, &opt_state->start_revision,
1350 youngest, repos, scratch_pool));
1351 SVN_ERR(get_revnum(upper, &opt_state->end_revision,
1352 youngest, repos, scratch_pool));
1354 /* Fill in implied revisions if necessary. */
1355 if (*lower == SVN_INVALID_REVNUM)
1360 else if (*upper == SVN_INVALID_REVNUM)
1365 if (*lower > *upper)
1366 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1367 _("First revision cannot be higher than second"));
1369 return SVN_NO_ERROR;
1372 /* Compare the node-path PATH with the (const char *) prefixes in PFXLIST.
1373 * Return TRUE if any prefix is a prefix of PATH (matching whole path
1374 * components); FALSE otherwise.
1375 * PATH starts with a '/', as do the (const char *) paths in PREFIXES. */
1376 /* This function is a duplicate of svndumpfilter.c:ary_prefix_match(). */
1377 static svn_boolean_t
1378 ary_prefix_match(const apr_array_header_t *pfxlist, const char *path)
1381 size_t path_len = strlen(path);
1383 for (i = 0; i < pfxlist->nelts; i++)
1385 const char *pfx = APR_ARRAY_IDX(pfxlist, i, const char *);
1386 size_t pfx_len = strlen(pfx);
1388 if (path_len < pfx_len)
1390 if (strncmp(path, pfx, pfx_len) == 0
1391 && (pfx_len == 1 || path[pfx_len] == '\0' || path[pfx_len] == '/'))
1398 /* Baton for dump_filter_func(). */
1399 struct dump_filter_baton_t
1401 apr_array_header_t *prefixes;
1403 svn_boolean_t do_exclude;
1406 /* Implements svn_repos_dump_filter_func_t. */
1407 static svn_error_t *
1408 dump_filter_func(svn_boolean_t *include,
1409 svn_fs_root_t *root,
1412 apr_pool_t *scratch_pool)
1414 struct dump_filter_baton_t *b = baton;
1415 const svn_boolean_t matches =
1417 ? svn_cstring_match_glob_list(path, b->prefixes)
1418 : ary_prefix_match(b->prefixes, path));
1420 *include = b->do_exclude ? !matches : matches;
1421 return SVN_NO_ERROR;
1424 /* This implements `svn_opt_subcommand_t'. */
1425 static svn_error_t *
1426 subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1428 struct svnadmin_opt_state *opt_state = baton;
1430 svn_stream_t *out_stream;
1431 svn_revnum_t lower, upper;
1432 svn_stream_t *feedback_stream = NULL;
1433 struct dump_filter_baton_t filter_baton = {0};
1435 /* Expect no more arguments. */
1436 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1438 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1439 SVN_ERR(get_dump_range(&lower, &upper, repos, opt_state, pool));
1441 /* Open the file or STDOUT, depending on whether -F was specified. */
1442 if (opt_state->file)
1446 /* Overwrite existing files, same as with > redirection. */
1447 SVN_ERR(svn_io_file_open(&file, opt_state->file,
1448 APR_WRITE | APR_CREATE | APR_TRUNCATE
1449 | APR_BUFFERED, APR_OS_DEFAULT, pool));
1450 out_stream = svn_stream_from_aprfile2(file, FALSE, pool);
1453 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1455 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1456 if (! opt_state->quiet)
1457 feedback_stream = recode_stream_create(stderr, pool);
1459 /* Initialize the filter baton. */
1460 filter_baton.glob = opt_state->glob;
1462 if (opt_state->exclude && !opt_state->include)
1464 filter_baton.prefixes = opt_state->exclude;
1465 filter_baton.do_exclude = TRUE;
1467 else if (opt_state->include && !opt_state->exclude)
1469 filter_baton.prefixes = opt_state->include;
1470 filter_baton.do_exclude = FALSE;
1472 else if (opt_state->include && opt_state->exclude)
1474 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1475 _("'--exclude' and '--include' options "
1476 "cannot be used simultaneously"));
1479 SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper,
1480 opt_state->incremental, opt_state->use_deltas,
1482 !opt_state->quiet ? repos_notify_handler : NULL,
1484 filter_baton.prefixes ? dump_filter_func : NULL,
1486 check_cancel, NULL, pool));
1488 return SVN_NO_ERROR;
1491 /* This implements `svn_opt_subcommand_t'. */
1492 static svn_error_t *
1493 subcommand_dump_revprops(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1495 struct svnadmin_opt_state *opt_state = baton;
1497 svn_stream_t *out_stream;
1498 svn_revnum_t lower, upper;
1499 svn_stream_t *feedback_stream = NULL;
1501 /* Expect no more arguments. */
1502 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1504 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1505 SVN_ERR(get_dump_range(&lower, &upper, repos, opt_state, pool));
1507 /* Open the file or STDOUT, depending on whether -F was specified. */
1508 if (opt_state->file)
1512 /* Overwrite existing files, same as with > redirection. */
1513 SVN_ERR(svn_io_file_open(&file, opt_state->file,
1514 APR_WRITE | APR_CREATE | APR_TRUNCATE
1515 | APR_BUFFERED, APR_OS_DEFAULT, pool));
1516 out_stream = svn_stream_from_aprfile2(file, FALSE, pool);
1519 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1521 /* Progress feedback goes to STDERR, unless they asked to suppress it. */
1522 if (! opt_state->quiet)
1523 feedback_stream = recode_stream_create(stderr, pool);
1525 SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper,
1526 FALSE, FALSE, TRUE, FALSE,
1527 !opt_state->quiet ? repos_notify_handler : NULL,
1528 feedback_stream, NULL, NULL,
1529 check_cancel, NULL, pool));
1531 return SVN_NO_ERROR;
1534 struct freeze_baton_t {
1535 const char *command;
1540 /* Implements svn_repos_freeze_func_t */
1541 static svn_error_t *
1542 freeze_body(void *baton,
1545 struct freeze_baton_t *b = baton;
1546 apr_status_t apr_err;
1547 apr_file_t *infile, *outfile, *errfile;
1549 apr_err = apr_file_open_stdin(&infile, pool);
1551 return svn_error_wrap_apr(apr_err, "Can't open stdin");
1552 apr_err = apr_file_open_stdout(&outfile, pool);
1554 return svn_error_wrap_apr(apr_err, "Can't open stdout");
1555 apr_err = apr_file_open_stderr(&errfile, pool);
1557 return svn_error_wrap_apr(apr_err, "Can't open stderr");
1559 SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status,
1561 infile, outfile, errfile, pool));
1563 return SVN_NO_ERROR;
1566 static svn_error_t *
1567 subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1569 struct svnadmin_opt_state *opt_state = baton;
1570 apr_array_header_t *paths;
1571 apr_array_header_t *args;
1573 struct freeze_baton_t b;
1575 SVN_ERR(parse_args(&args, os, -1, -1, pool));
1578 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
1579 _("No program provided"));
1581 if (!opt_state->file)
1583 /* One repository on the command line. */
1584 paths = apr_array_make(pool, 1, sizeof(const char *));
1585 APR_ARRAY_PUSH(paths, const char *) = opt_state->repository_path;
1589 svn_stringbuf_t *buf;
1591 /* Read repository paths from the -F file. */
1592 SVN_ERR(svn_stringbuf_from_file2(&buf, opt_state->file, pool));
1593 SVN_ERR(svn_utf_cstring_to_utf8(&utf8, buf->data, pool));
1594 paths = svn_cstring_split(utf8, "\r\n", FALSE, pool);
1597 b.command = APR_ARRAY_IDX(args, 0, const char *);
1598 b.args = apr_palloc(pool, sizeof(char *) * (args->nelts + 1));
1599 for (i = 0; i < args->nelts; ++i)
1600 b.args[i] = APR_ARRAY_IDX(args, i, const char *);
1601 b.args[args->nelts] = NULL;
1603 SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool));
1605 /* Make any non-zero status visible to the user. */
1609 return SVN_NO_ERROR;
1613 /* This implements `svn_opt_subcommand_t'. */
1614 static svn_error_t *
1615 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1617 struct svnadmin_opt_state *opt_state = baton;
1618 const char *header =
1619 _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
1620 "Subversion repository administration tool.\n"
1621 "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n"
1622 "Type 'svnadmin --version' to see the program version and FS modules.\n"
1624 "Available subcommands:\n");
1626 const char *fs_desc_start
1627 = _("The following repository back-end (FS) modules are available:\n\n");
1629 svn_stringbuf_t *version_footer;
1631 version_footer = svn_stringbuf_create(fs_desc_start, pool);
1632 SVN_ERR(svn_fs_print_modules(version_footer, pool));
1634 SVN_ERR(svn_opt_print_help5(os, "svnadmin",
1635 opt_state ? opt_state->version : FALSE,
1636 opt_state ? opt_state->quiet : FALSE,
1637 /*###opt_state ? opt_state->verbose :*/ FALSE,
1638 version_footer->data,
1639 header, cmd_table, options_table, NULL, NULL,
1642 return SVN_NO_ERROR;
1646 /* Set *REVNUM to the revision number of a numeric REV, or to
1647 SVN_INVALID_REVNUM if REV is unspecified. */
1648 static svn_error_t *
1649 optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *opt_rev)
1651 if (opt_rev->kind == svn_opt_revision_number)
1653 *revnum = opt_rev->value.number;
1654 if (! SVN_IS_VALID_REVNUM(*revnum))
1655 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1656 _("Invalid revision number (%ld) specified"),
1659 else if (opt_rev->kind == svn_opt_revision_unspecified)
1661 *revnum = SVN_INVALID_REVNUM;
1665 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1666 _("Non-numeric revision specified"));
1668 return SVN_NO_ERROR;
1671 /* Read the min / max revision from the OPT_STATE, verify them and return
1672 them in *LOWER and *UPPER, respectively. */
1673 static svn_error_t *
1674 get_load_range(svn_revnum_t *lower,
1675 svn_revnum_t *upper,
1676 struct svnadmin_opt_state *opt_state)
1678 /* Find the revision numbers at which to start and end. We only
1679 support a limited set of revision kinds: number and unspecified. */
1680 SVN_ERR(optrev_to_revnum(lower, &opt_state->start_revision));
1681 SVN_ERR(optrev_to_revnum(upper, &opt_state->end_revision));
1683 /* Fill in implied revisions if necessary. */
1684 if ((*upper == SVN_INVALID_REVNUM) && (*lower != SVN_INVALID_REVNUM))
1688 else if ((*upper != SVN_INVALID_REVNUM) && (*lower == SVN_INVALID_REVNUM))
1693 /* Ensure correct range ordering. */
1694 if (*lower > *upper)
1696 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1697 _("First revision cannot be higher than second"));
1700 return SVN_NO_ERROR;
1704 /* This implements `svn_opt_subcommand_t'. */
1705 static svn_error_t *
1706 subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1709 struct svnadmin_opt_state *opt_state = baton;
1711 svn_revnum_t lower, upper;
1712 svn_stream_t *in_stream;
1713 svn_stream_t *feedback_stream = NULL;
1715 /* Expect no more arguments. */
1716 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1718 /* Find the revision numbers at which to start and end. We only
1719 support a limited set of revision kinds: number and unspecified. */
1720 SVN_ERR(get_load_range(&lower, &upper, opt_state));
1722 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1724 /* Open the file or STDIN, depending on whether -F was specified. */
1725 if (opt_state->file)
1726 SVN_ERR(svn_stream_open_readonly(&in_stream, opt_state->file,
1729 SVN_ERR(svn_stream_for_stdin2(&in_stream, TRUE, pool));
1731 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1732 if (! opt_state->quiet)
1733 feedback_stream = recode_stream_create(stdout, pool);
1735 err = svn_repos_load_fs6(repos, in_stream, lower, upper,
1736 opt_state->uuid_action, opt_state->parent_dir,
1737 opt_state->use_pre_commit_hook,
1738 opt_state->use_post_commit_hook,
1739 !opt_state->bypass_prop_validation,
1740 opt_state->ignore_dates,
1741 opt_state->normalize_props,
1742 opt_state->quiet ? NULL : repos_notify_handler,
1743 feedback_stream, check_cancel, NULL, pool);
1745 if (svn_error_find_cause(err, SVN_ERR_BAD_PROPERTY_VALUE_EOL))
1747 return svn_error_quick_wrap(err,
1748 _("A property with invalid line ending "
1749 "found in dumpstream; consider using "
1750 "--normalize-props while loading."));
1752 else if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1754 return svn_error_quick_wrap(err,
1755 _("Invalid property value found in "
1756 "dumpstream; consider repairing the "
1757 "source or using --bypass-prop-validation "
1764 static svn_error_t *
1765 subcommand_load_revprops(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1768 struct svnadmin_opt_state *opt_state = baton;
1770 svn_revnum_t lower, upper;
1771 svn_stream_t *in_stream;
1773 svn_stream_t *feedback_stream = NULL;
1775 /* Expect no more arguments. */
1776 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1778 /* Find the revision numbers at which to start and end. We only
1779 support a limited set of revision kinds: number and unspecified. */
1780 SVN_ERR(get_load_range(&lower, &upper, opt_state));
1782 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1784 /* Open the file or STDIN, depending on whether -F was specified. */
1785 if (opt_state->file)
1786 SVN_ERR(svn_stream_open_readonly(&in_stream, opt_state->file,
1789 SVN_ERR(svn_stream_for_stdin2(&in_stream, TRUE, pool));
1791 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
1792 if (! opt_state->quiet)
1793 feedback_stream = recode_stream_create(stdout, pool);
1795 err = svn_repos_load_fs_revprops(repos, in_stream, lower, upper,
1796 !opt_state->bypass_prop_validation,
1797 opt_state->ignore_dates,
1798 opt_state->normalize_props,
1799 opt_state->quiet ? NULL
1800 : repos_notify_handler,
1801 feedback_stream, check_cancel, NULL, pool);
1803 if (svn_error_find_cause(err, SVN_ERR_BAD_PROPERTY_VALUE_EOL))
1805 return svn_error_quick_wrap(err,
1806 _("A property with invalid line ending "
1807 "found in dumpstream; consider using "
1808 "--normalize-props while loading."));
1810 else if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE)
1812 return svn_error_quick_wrap(err,
1813 _("Invalid property value found in "
1814 "dumpstream; consider repairing the "
1815 "source or using --bypass-prop-validation "
1822 /* This implements `svn_opt_subcommand_t'. */
1823 static svn_error_t *
1824 subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1826 struct svnadmin_opt_state *opt_state = baton;
1829 apr_array_header_t *txns;
1830 apr_pool_t *iterpool;
1831 svn_revnum_t youngest, limit;
1834 /* Expect no more arguments. */
1835 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1836 if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
1837 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
1838 _("Revision range is not allowed"));
1840 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1841 fs = svn_repos_fs(repos);
1842 SVN_ERR(svn_fs_list_transactions(&txns, fs, pool));
1844 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1845 SVN_ERR(get_revnum(&limit, &opt_state->start_revision, youngest, repos,
1848 iterpool = svn_pool_create(pool);
1849 for (i = 0; i < txns->nelts; i++)
1851 const char *name = APR_ARRAY_IDX(txns, i, const char *);
1852 svn_boolean_t show = TRUE;
1854 svn_pool_clear(iterpool);
1855 if (limit != SVN_INVALID_REVNUM)
1860 SVN_ERR(svn_fs_open_txn(&txn, fs, name, iterpool));
1861 base = svn_fs_txn_base_revision(txn);
1867 SVN_ERR(svn_cmdline_printf(pool, "%s\n", name));
1869 svn_pool_destroy(iterpool);
1871 return SVN_NO_ERROR;
1875 /* This implements `svn_opt_subcommand_t'. */
1876 static svn_error_t *
1877 subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1879 svn_revnum_t youngest_rev;
1882 struct svnadmin_opt_state *opt_state = baton;
1883 svn_stream_t *feedback_stream = NULL;
1885 /* Expect no more arguments. */
1886 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1888 SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
1890 /* Restore default signal handlers until after we have acquired the
1891 * exclusive lock so that the user interrupt before we actually
1892 * touch the repository. */
1893 svn_cmdline__disable_cancellation_handler();
1895 err = svn_repos_recover4(opt_state->repository_path, TRUE,
1896 repos_notify_handler, feedback_stream,
1897 check_cancel, NULL, pool);
1900 if (! APR_STATUS_IS_EAGAIN(err->apr_err))
1902 svn_error_clear(err);
1903 if (! opt_state->wait)
1904 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
1905 _("Failed to get exclusive repository "
1906 "access; perhaps another process\n"
1907 "such as httpd, svnserve or svn "
1909 SVN_ERR(svn_cmdline_printf(pool,
1910 _("Waiting on repository lock; perhaps"
1911 " another process has it open?\n")));
1912 SVN_ERR(svn_cmdline_fflush(stdout));
1913 SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE,
1914 repos_notify_handler, feedback_stream,
1915 check_cancel, NULL, pool));
1918 SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n")));
1920 /* Since db transactions may have been replayed, it's nice to tell
1921 people what the latest revision is. It also proves that the
1922 recovery actually worked. */
1923 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
1924 SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), pool));
1925 SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is %ld.\n"),
1928 return SVN_NO_ERROR;
1932 /* This implements `svn_opt_subcommand_t'. */
1933 static svn_error_t *
1934 list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused,
1937 struct svnadmin_opt_state *opt_state = baton;
1938 apr_array_header_t *logfiles;
1941 /* Expect no more arguments. */
1942 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1944 SVN_ERR(svn_repos_db_logfiles(&logfiles,
1945 opt_state->repository_path,
1949 /* Loop, printing log files. We append the log paths to the
1950 repository path, making sure to return everything to the native
1951 style before printing. */
1952 for (i = 0; i < logfiles->nelts; i++)
1954 const char *log_path;
1955 log_path = svn_dirent_join(opt_state->repository_path,
1956 APR_ARRAY_IDX(logfiles, i, const char *),
1958 log_path = svn_dirent_local_style(log_path, pool);
1959 SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_path));
1962 return SVN_NO_ERROR;
1966 /* This implements `svn_opt_subcommand_t'. */
1967 static svn_error_t *
1968 subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1970 SVN_ERR(list_dblogs(os, baton, FALSE, pool));
1971 return SVN_NO_ERROR;
1975 /* This implements `svn_opt_subcommand_t'. */
1976 static svn_error_t *
1977 subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1979 /* Expect no more arguments. */
1980 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
1982 SVN_ERR(list_dblogs(os, baton, TRUE, pool));
1983 return SVN_NO_ERROR;
1987 /* This implements `svn_opt_subcommand_t'. */
1988 static svn_error_t *
1989 subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1991 struct svnadmin_opt_state *opt_state = baton;
1995 apr_array_header_t *args;
1997 apr_pool_t *subpool = svn_pool_create(pool);
1999 SVN_ERR(parse_args(&args, os, -1, -1, pool));
2001 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2002 fs = svn_repos_fs(repos);
2004 /* All the rest of the arguments are transaction names. */
2005 for (i = 0; i < args->nelts; i++)
2007 const char *txn_name = APR_ARRAY_IDX(args, i, const char *);
2010 svn_pool_clear(subpool);
2012 /* Try to open the txn. If that succeeds, try to abort it. */
2013 err = svn_fs_open_txn(&txn, fs, txn_name, subpool);
2015 err = svn_fs_abort_txn(txn, subpool);
2017 /* If either the open or the abort of the txn fails because that
2018 transaction is dead, just try to purge the thing. Else,
2019 there was either an error worth reporting, or not error at
2021 if (err && (err->apr_err == SVN_ERR_FS_TRANSACTION_DEAD))
2023 svn_error_clear(err);
2024 err = svn_fs_purge_txn(fs, txn_name, subpool);
2027 /* If we had a real from the txn open, abort, or purge, we clear
2028 that error and just report to the user that we had an issue
2029 with this particular txn. */
2032 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
2033 svn_error_clear(err);
2035 else if (! opt_state->quiet)
2037 SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' removed.\n"),
2042 svn_pool_destroy(subpool);
2044 return SVN_NO_ERROR;
2048 /* A helper for the 'setrevprop' and 'setlog' commands. Expects
2049 OPT_STATE->txn_id, OPT_STATE->use_pre_revprop_change_hook and
2050 OPT_STATE->use_post_revprop_change_hook to be set appropriately.
2051 If FILENAME is NULL, delete property PROP_NAME. */
2052 static svn_error_t *
2053 set_revprop(const char *prop_name, const char *filename,
2054 struct svnadmin_opt_state *opt_state, apr_pool_t *pool)
2057 svn_string_t *prop_value;
2061 svn_stringbuf_t *file_contents;
2063 SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool));
2065 prop_value = svn_string_create_empty(pool);
2066 prop_value->data = file_contents->data;
2067 prop_value->len = file_contents->len;
2069 SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value,
2070 NULL, FALSE, pool, pool));
2077 /* Open the filesystem */
2078 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2080 if (opt_state->txn_id)
2082 svn_fs_t *fs = svn_repos_fs(repos);
2085 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
2086 SVN_ERR(svn_fs_change_txn_prop(txn, prop_name, prop_value, pool));
2089 SVN_ERR(svn_repos_fs_change_rev_prop4(
2090 repos, opt_state->start_revision.value.number,
2091 NULL, prop_name, NULL, prop_value,
2092 opt_state->use_pre_revprop_change_hook,
2093 opt_state->use_post_revprop_change_hook,
2096 return SVN_NO_ERROR;
2100 /* This implements `svn_opt_subcommand_t'. */
2101 static svn_error_t *
2102 subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2104 struct svnadmin_opt_state *opt_state = baton;
2105 apr_array_header_t *args;
2106 const char *prop_name, *filename;
2108 /* Expect two more arguments: NAME FILE */
2109 SVN_ERR(parse_args(&args, os, 2, 2, pool));
2110 prop_name = APR_ARRAY_IDX(args, 0, const char *);
2111 filename = APR_ARRAY_IDX(args, 1, const char *);
2112 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
2114 if (opt_state->txn_id)
2116 if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2117 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2118 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2119 _("--revision (-r) and --transaction (-t) "
2120 "are mutually exclusive"));
2122 if (opt_state->use_pre_revprop_change_hook
2123 || opt_state->use_post_revprop_change_hook)
2124 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2125 _("Calling hooks is incompatible with "
2126 "--transaction (-t)"));
2128 else if (opt_state->start_revision.kind != svn_opt_revision_number)
2129 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2130 _("Missing revision"));
2131 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2132 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2133 _("Only one revision allowed"));
2135 return set_revprop(prop_name, filename, opt_state, pool);
2139 /* This implements `svn_opt_subcommand_t'. */
2140 static svn_error_t *
2141 subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2143 struct svnadmin_opt_state *opt_state = baton;
2144 apr_array_header_t *args;
2147 const char *uuid = NULL;
2149 /* Expect zero or one more arguments: [UUID] */
2150 SVN_ERR(parse_args(&args, os, 0, 1, pool));
2151 if (args->nelts == 1)
2152 uuid = APR_ARRAY_IDX(args, 0, const char *);
2154 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2155 fs = svn_repos_fs(repos);
2156 return svn_fs_set_uuid(fs, uuid, pool);
2160 /* This implements `svn_opt_subcommand_t'. */
2161 static svn_error_t *
2162 subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2164 struct svnadmin_opt_state *opt_state = baton;
2165 apr_array_header_t *args;
2166 const char *filename;
2168 /* Expect one more argument: FILE */
2169 SVN_ERR(parse_args(&args, os, 1, 1, pool));
2170 filename = APR_ARRAY_IDX(args, 0, const char *);
2171 SVN_ERR(target_arg_to_dirent(&filename, filename, pool));
2173 if (opt_state->start_revision.kind != svn_opt_revision_number)
2174 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2175 _("Missing revision"));
2176 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2177 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2178 _("Only one revision allowed"));
2180 /* set_revprop() responds only to pre-/post-revprop-change opts. */
2181 if (!opt_state->bypass_hooks)
2183 opt_state->use_pre_revprop_change_hook = TRUE;
2184 opt_state->use_post_revprop_change_hook = TRUE;
2187 return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool);
2191 /* This implements 'svn_opt_subcommand_t'. */
2192 static svn_error_t *
2193 subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2195 struct svnadmin_opt_state *opt_state = baton;
2197 svn_stream_t *feedback_stream = NULL;
2199 /* Expect no more arguments. */
2200 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2202 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2204 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
2205 if (! opt_state->quiet)
2206 feedback_stream = recode_stream_create(stdout, pool);
2208 return svn_error_trace(
2209 svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL,
2210 feedback_stream, check_cancel, NULL, pool));
2214 /* This implements `svn_opt_subcommand_t'. */
2215 static svn_error_t *
2216 subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2218 struct svnadmin_opt_state *opt_state = baton;
2221 svn_revnum_t youngest, lower, upper;
2222 svn_stream_t *feedback_stream = NULL;
2223 struct repos_verify_callback_baton verify_baton = { 0 };
2225 /* Expect no more arguments. */
2226 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2228 if (opt_state->txn_id
2229 && (opt_state->start_revision.kind != svn_opt_revision_unspecified
2230 || opt_state->end_revision.kind != svn_opt_revision_unspecified))
2232 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2233 _("--revision (-r) and --transaction (-t) "
2234 "are mutually exclusive"));
2237 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2238 fs = svn_repos_fs(repos);
2239 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
2242 if (opt_state->txn_id)
2245 svn_fs_root_t *root;
2247 SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool));
2248 SVN_ERR(svn_fs_txn_root(&root, txn, pool));
2249 SVN_ERR(svn_fs_verify_root(root, pool));
2250 return SVN_NO_ERROR;
2256 /* Find the revision numbers at which to start and end. */
2257 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
2258 youngest, repos, pool));
2259 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
2260 youngest, repos, pool));
2262 if (upper == SVN_INVALID_REVNUM)
2267 if (!opt_state->quiet)
2268 feedback_stream = recode_stream_create(stdout, pool);
2270 verify_baton.keep_going = opt_state->keep_going;
2271 verify_baton.error_summary =
2272 apr_array_make(pool, 0, sizeof(struct verification_error *));
2273 verify_baton.result_pool = pool;
2275 SVN_ERR(svn_repos_verify_fs3(repos, lower, upper,
2276 opt_state->check_normalization,
2277 opt_state->metadata_only,
2279 ? repos_notify_handler : NULL,
2281 repos_verify_callback, &verify_baton,
2282 check_cancel, NULL, pool));
2284 /* Show the --keep-going error summary. */
2285 if (opt_state->keep_going && verify_baton.error_summary->nelts > 0)
2288 svn_revnum_t end_revnum;
2289 apr_pool_t *iterpool;
2292 if (feedback_stream == NULL) /* happens when we are in --quiet mode */
2293 feedback_stream = recode_stream_create(stdout, pool);
2296 svn_stream_puts(feedback_stream,
2297 _("\n-----Summary of corrupt revisions-----\n")));
2299 /* The standard column width for the revision number is 6 characters.
2300 If the revision number can potentially be larger (i.e. if end_revnum
2301 is larger than 1000000), we increase the column width as needed. */
2303 end_revnum = APR_ARRAY_IDX(verify_baton.error_summary,
2304 verify_baton.error_summary->nelts - 1,
2305 struct verification_error *)->rev;
2306 while (end_revnum >= 1000000)
2309 end_revnum = end_revnum / 10;
2312 iterpool = svn_pool_create(pool);
2313 for (i = 0; i < verify_baton.error_summary->nelts; i++)
2315 struct verification_error *verr;
2317 const char *rev_str;
2319 svn_pool_clear(iterpool);
2321 verr = APR_ARRAY_IDX(verify_baton.error_summary, i,
2322 struct verification_error *);
2324 if (verr->rev != SVN_INVALID_REVNUM)
2326 rev_str = apr_psprintf(iterpool, "r%ld", verr->rev);
2327 rev_str = apr_psprintf(iterpool, "%*s", rev_maxlength, rev_str);
2328 for (err = svn_error_purge_tracing(verr->err);
2329 err != SVN_NO_ERROR; err = err->child)
2332 const char *message;
2334 message = svn_err_best_message(err, buf, sizeof(buf));
2335 svn_error_clear(svn_stream_printf(feedback_stream, iterpool,
2337 rev_str, err->apr_err,
2343 svn_pool_destroy(iterpool);
2346 if (verify_baton.error_summary->nelts > 0)
2348 return svn_error_createf(SVN_ERR_CL_REPOS_VERIFY_FAILED, NULL,
2349 _("Failed to verify repository '%s'"),
2350 svn_dirent_local_style(
2351 opt_state->repository_path, pool));
2354 return SVN_NO_ERROR;
2357 /* This implements `svn_opt_subcommand_t'. */
2359 subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2361 struct svnadmin_opt_state *opt_state = baton;
2362 svn_stream_t *feedback_stream = NULL;
2363 apr_array_header_t *targets;
2364 const char *new_repos_path;
2366 /* Expect one more argument: NEW_REPOS_PATH */
2367 SVN_ERR(parse_args(&targets, os, 1, 1, pool));
2368 new_repos_path = APR_ARRAY_IDX(targets, 0, const char *);
2369 SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool));
2371 /* Progress feedback goes to STDOUT, unless they asked to suppress it. */
2372 if (! opt_state->quiet)
2373 feedback_stream = recode_stream_create(stdout, pool);
2375 return svn_repos_hotcopy3(opt_state->repository_path, new_repos_path,
2376 opt_state->clean_logs, opt_state->incremental,
2377 !opt_state->quiet ? repos_notify_handler : NULL,
2378 feedback_stream, check_cancel, NULL, pool);
2382 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2384 struct svnadmin_opt_state *opt_state = baton;
2389 svn_revnum_t head_rev;
2391 /* Expect no more arguments. */
2392 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2394 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2395 fs = svn_repos_fs(repos);
2396 SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"),
2397 svn_dirent_local_style(svn_repos_path(repos, pool),
2400 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
2401 SVN_ERR(svn_cmdline_printf(pool, _("UUID: %s\n"), uuid));
2403 SVN_ERR(svn_fs_youngest_rev(&head_rev, fs, pool));
2404 SVN_ERR(svn_cmdline_printf(pool, _("Revisions: %ld\n"), head_rev));
2406 int repos_format, minor;
2407 svn_version_t *repos_version, *fs_version;
2408 SVN_ERR(svn_repos_info_format(&repos_format, &repos_version,
2409 repos, pool, pool));
2410 SVN_ERR(svn_cmdline_printf(pool, _("Repository Format: %d\n"),
2413 SVN_ERR(svn_fs_info_format(&fs_format, &fs_version,
2415 /* fs_format will be printed later. */
2417 SVN_ERR_ASSERT(repos_version->major == SVN_VER_MAJOR);
2418 SVN_ERR_ASSERT(fs_version->major == SVN_VER_MAJOR);
2419 SVN_ERR_ASSERT(repos_version->patch == 0);
2420 SVN_ERR_ASSERT(fs_version->patch == 0);
2422 minor = (repos_version->minor > fs_version->minor)
2423 ? repos_version->minor : fs_version->minor;
2424 SVN_ERR(svn_cmdline_printf(pool, _("Compatible With Version: %d.%d.0\n"),
2425 SVN_VER_MAJOR, minor));
2429 apr_hash_t *capabilities_set;
2430 apr_array_header_t *capabilities;
2433 SVN_ERR(svn_repos_capabilities(&capabilities_set, repos, pool, pool));
2434 capabilities = svn_sort__hash(capabilities_set,
2435 svn_sort_compare_items_lexically,
2438 for (i = 0; i < capabilities->nelts; i++)
2440 svn_sort__item_t *item = &APR_ARRAY_IDX(capabilities, i,
2442 const char *capability = item->key;
2443 SVN_ERR(svn_cmdline_printf(pool, _("Repository Capability: %s\n"),
2449 const svn_fs_info_placeholder_t *info;
2451 SVN_ERR(svn_fs_info(&info, fs, pool, pool));
2452 SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Type: %s\n"),
2454 SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Format: %d\n"),
2456 if (!strcmp(info->fs_type, SVN_FS_TYPE_FSFS))
2458 const svn_fs_fsfs_info_t *fsfs_info = (const void *)info;
2460 if (fsfs_info->shard_size)
2461 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: yes\n")));
2463 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: no\n")));
2465 if (fsfs_info->shard_size)
2466 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shard Size: %d\n"),
2467 fsfs_info->shard_size));
2469 /* Print packing statistics, if enabled on the FS. */
2470 if (fsfs_info->shard_size)
2472 const int shard_size = fsfs_info->shard_size;
2473 const long shards_packed = fsfs_info->min_unpacked_rev / shard_size;
2474 const long shards_full = (head_rev + 1) / shard_size;
2475 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shards Packed: %ld/%ld\n"),
2476 shards_packed, shards_full));
2479 if (fsfs_info->log_addressing)
2480 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: yes\n")));
2482 SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: no\n")));
2484 else if (!strcmp(info->fs_type, SVN_FS_TYPE_FSX))
2486 const svn_fs_fsx_info_t *fsx_info = (const void *)info;
2488 const int shard_size = fsx_info->shard_size;
2489 const long shards_packed = fsx_info->min_unpacked_rev / shard_size;
2490 long shards_full = (head_rev + 1) / shard_size;
2492 SVN_ERR(svn_cmdline_printf(pool, _("FSX Shard Size: %d\n"),
2494 SVN_ERR(svn_cmdline_printf(pool, _("FSX Shards Packed: %ld/%ld\n"),
2495 shards_packed, shards_full));
2500 apr_array_header_t *files;
2503 SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool));
2504 for (i = 0; i < files->nelts; i++)
2505 SVN_ERR(svn_cmdline_printf(pool, _("Configuration File: %s\n"),
2506 svn_dirent_local_style(
2507 APR_ARRAY_IDX(files, i, const char *),
2511 /* 'svn info' prints an extra newline here, to support multiple targets.
2512 We'll do the same. */
2513 SVN_ERR(svn_cmdline_printf(pool, "\n"));
2515 return SVN_NO_ERROR;
2518 /* This implements `svn_opt_subcommand_t'. */
2519 static svn_error_t *
2520 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2522 struct svnadmin_opt_state *opt_state = baton;
2525 svn_fs_access_t *access;
2526 apr_array_header_t *args;
2527 const char *username;
2528 const char *lock_path;
2529 const char *comment_file_name;
2530 svn_stringbuf_t *file_contents;
2532 const char *lock_token = NULL;
2534 /* Expect three more arguments: PATH USERNAME COMMENT-FILE */
2535 SVN_ERR(parse_args(&args, os, 3, 4, pool));
2536 lock_path = APR_ARRAY_IDX(args, 0, const char *);
2537 username = APR_ARRAY_IDX(args, 1, const char *);
2538 comment_file_name = APR_ARRAY_IDX(args, 2, const char *);
2540 /* Expect one more optional argument: TOKEN */
2541 if (args->nelts == 4)
2542 lock_token = APR_ARRAY_IDX(args, 3, const char *);
2544 SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, pool));
2546 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2547 fs = svn_repos_fs(repos);
2549 /* Create an access context describing the user. */
2550 SVN_ERR(svn_fs_create_access(&access, username, pool));
2552 /* Attach the access context to the filesystem. */
2553 SVN_ERR(svn_fs_set_access(fs, access));
2555 SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, pool));
2557 SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, pool, pool));
2559 if (opt_state->bypass_hooks)
2560 SVN_ERR(svn_fs_lock(&lock, fs, lock_path,
2562 file_contents->data, /* comment */
2563 0, /* is_dav_comment */
2564 0, /* no expiration time. */
2568 SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path,
2570 file_contents->data, /* comment */
2571 0, /* is_dav_comment */
2572 0, /* no expiration time. */
2576 if (! opt_state->quiet)
2577 SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"),
2578 lock_path, username));
2580 return SVN_NO_ERROR;
2583 static svn_error_t *
2584 subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2586 struct svnadmin_opt_state *opt_state = baton;
2587 apr_array_header_t *targets;
2589 const char *fs_path;
2591 apr_hash_index_t *hi;
2592 apr_pool_t *iterpool = svn_pool_create(pool);
2594 SVN_ERR(svn_opt__args_to_target_array(&targets, os,
2595 apr_array_make(pool, 0,
2596 sizeof(const char *)),
2598 if (targets->nelts > 1)
2599 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2600 _("Too many arguments given"));
2602 fs_path = APR_ARRAY_IDX(targets, 0, const char *);
2605 SVN_ERR(target_arg_to_fspath(&fs_path, fs_path, pool, pool));
2607 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2609 /* Fetch all locks on or below the root directory. */
2610 SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, svn_depth_infinity,
2613 for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
2615 const char *cr_date, *exp_date = "";
2616 const char *path = apr_hash_this_key(hi);
2617 svn_lock_t *lock = apr_hash_this_val(hi);
2618 int comment_lines = 0;
2620 svn_pool_clear(iterpool);
2622 SVN_ERR(check_cancel(NULL));
2624 cr_date = svn_time_to_human_cstring(lock->creation_date, iterpool);
2626 if (lock->expiration_date)
2627 exp_date = svn_time_to_human_cstring(lock->expiration_date, iterpool);
2630 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2632 SVN_ERR(svn_cmdline_printf(iterpool, _("Path: %s\n"), path));
2633 SVN_ERR(svn_cmdline_printf(iterpool, _("UUID Token: %s\n"), lock->token));
2634 SVN_ERR(svn_cmdline_printf(iterpool, _("Owner: %s\n"), lock->owner));
2635 SVN_ERR(svn_cmdline_printf(iterpool, _("Created: %s\n"), cr_date));
2636 SVN_ERR(svn_cmdline_printf(iterpool, _("Expires: %s\n"), exp_date));
2637 SVN_ERR(svn_cmdline_printf(iterpool,
2638 Q_("Comment (%i line):\n%s\n\n",
2639 "Comment (%i lines):\n%s\n\n",
2642 lock->comment ? lock->comment : ""));
2645 svn_pool_destroy(iterpool);
2647 return SVN_NO_ERROR;
2652 static svn_error_t *
2653 subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2655 struct svnadmin_opt_state *opt_state = baton;
2658 svn_fs_access_t *access;
2660 apr_array_header_t *args;
2662 const char *username;
2663 apr_pool_t *subpool = svn_pool_create(pool);
2665 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2666 fs = svn_repos_fs(repos);
2668 /* svn_fs_unlock() demands that some username be associated with the
2669 filesystem, so just use the UID of the person running 'svnadmin'.*/
2670 username = svn_user_get_name(pool);
2672 username = "administrator";
2674 /* Create an access context describing the current user. */
2675 SVN_ERR(svn_fs_create_access(&access, username, pool));
2677 /* Attach the access context to the filesystem. */
2678 SVN_ERR(svn_fs_set_access(fs, access));
2680 /* Parse out any options. */
2681 SVN_ERR(parse_args(&args, os, -1, -1, pool));
2683 /* Our usage requires at least one FS path. */
2684 if (args->nelts == 0)
2685 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0,
2686 _("No paths to unlock provided"));
2688 /* All the rest of the arguments are paths from which to remove locks. */
2689 for (i = 0; i < args->nelts; i++)
2691 const char *lock_path = APR_ARRAY_IDX(args, i, const char *);
2694 SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, subpool, subpool));
2696 /* Fetch the path's svn_lock_t. */
2697 err = svn_fs_get_lock(&lock, fs, lock_path, subpool);
2702 if (! opt_state->quiet)
2703 SVN_ERR(svn_cmdline_printf(subpool,
2704 _("Path '%s' isn't locked.\n"),
2708 lock = NULL; /* Don't access LOCK after this point. */
2710 /* Now forcibly destroy the lock. */
2711 err = svn_fs_unlock(fs, lock_path,
2712 NULL, 1 /* force */, subpool);
2716 if (! opt_state->quiet)
2717 SVN_ERR(svn_cmdline_printf(subpool,
2718 _("Removed lock on '%s'.\n"),
2724 /* Print the error, but move on to the next lock. */
2725 svn_handle_error2(err, stderr, FALSE /* non-fatal */, "svnadmin: ");
2726 svn_error_clear(err);
2729 svn_pool_clear(subpool);
2732 svn_pool_destroy(subpool);
2733 return SVN_NO_ERROR;
2737 /* This implements `svn_opt_subcommand_t'. */
2738 static svn_error_t *
2739 subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2741 struct svnadmin_opt_state *opt_state = baton;
2744 svn_fs_access_t *access;
2745 apr_array_header_t *args;
2746 const char *username;
2747 const char *lock_path;
2748 const char *lock_token = NULL;
2750 /* Expect three more arguments: PATH USERNAME TOKEN */
2751 SVN_ERR(parse_args(&args, os, 3, 3, pool));
2752 lock_path = APR_ARRAY_IDX(args, 0, const char *);
2753 username = APR_ARRAY_IDX(args, 1, const char *);
2754 lock_token = APR_ARRAY_IDX(args, 2, const char *);
2756 /* Open the repos/FS, and associate an access context containing
2758 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2759 fs = svn_repos_fs(repos);
2760 SVN_ERR(svn_fs_create_access(&access, username, pool));
2761 SVN_ERR(svn_fs_set_access(fs, access));
2763 SVN_ERR(target_arg_to_fspath(&lock_path, lock_path, pool, pool));
2764 if (opt_state->bypass_hooks)
2765 SVN_ERR(svn_fs_unlock(fs, lock_path, lock_token,
2768 SVN_ERR(svn_repos_fs_unlock(repos, lock_path, lock_token,
2771 if (! opt_state->quiet)
2772 SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"),
2773 lock_path, username));
2775 return SVN_NO_ERROR;
2779 /* This implements `svn_opt_subcommand_t'. */
2780 static svn_error_t *
2781 subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2784 struct svnadmin_opt_state *opt_state = baton;
2785 svn_stream_t *feedback_stream = NULL;
2787 /* Expect no more arguments. */
2788 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
2790 SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool));
2792 /* Restore default signal handlers. */
2793 svn_cmdline__disable_cancellation_handler();
2795 err = svn_repos_upgrade2(opt_state->repository_path, TRUE,
2796 repos_notify_handler, feedback_stream, pool);
2799 if (APR_STATUS_IS_EAGAIN(err->apr_err))
2801 svn_error_clear(err);
2803 if (! opt_state->wait)
2804 return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL,
2805 _("Failed to get exclusive repository "
2806 "access; perhaps another process\n"
2807 "such as httpd, svnserve or svn "
2809 SVN_ERR(svn_cmdline_printf(pool,
2810 _("Waiting on repository lock; perhaps"
2811 " another process has it open?\n")));
2812 SVN_ERR(svn_cmdline_fflush(stdout));
2813 SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE,
2814 repos_notify_handler, feedback_stream,
2817 else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE)
2819 return svn_error_quick_wrap(err,
2820 _("Upgrade of this repository's underlying versioned "
2821 "filesystem is not supported; consider "
2822 "dumping and loading the data elsewhere"));
2824 else if (err->apr_err == SVN_ERR_REPOS_UNSUPPORTED_UPGRADE)
2826 return svn_error_quick_wrap(err,
2827 _("Upgrade of this repository is not supported; consider "
2828 "dumping and loading the data elsewhere"));
2833 SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n")));
2834 return SVN_NO_ERROR;
2838 /* This implements `svn_opt_subcommand_t'. */
2839 static svn_error_t *
2840 subcommand_delrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2842 struct svnadmin_opt_state *opt_state = baton;
2843 apr_array_header_t *args;
2844 const char *prop_name;
2846 /* Expect one more argument: NAME */
2847 SVN_ERR(parse_args(&args, os, 1, 1, pool));
2848 prop_name = APR_ARRAY_IDX(args, 0, const char *);
2850 if (opt_state->txn_id)
2852 if (opt_state->start_revision.kind != svn_opt_revision_unspecified
2853 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2854 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2855 _("--revision (-r) and --transaction (-t) "
2856 "are mutually exclusive"));
2858 if (opt_state->use_pre_revprop_change_hook
2859 || opt_state->use_post_revprop_change_hook)
2860 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2861 _("Calling hooks is incompatible with "
2862 "--transaction (-t)"));
2864 else if (opt_state->start_revision.kind != svn_opt_revision_number)
2865 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2866 _("Missing revision"));
2867 else if (opt_state->end_revision.kind != svn_opt_revision_unspecified)
2868 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2869 _("Only one revision allowed"));
2871 return set_revprop(prop_name, NULL, opt_state, pool);
2875 /* Set *REV_SIZE to the total size in bytes of the representation on disk
2876 * of revision REVISION in FS.
2878 * This is implemented only for FSFS repositories, and otherwise returns
2879 * an SVN_ERR_UNSUPPORTED_FEATURE error.
2881 * The size includes revision properties and excludes FSFS indexes.
2883 static svn_error_t *
2884 revision_size(apr_off_t *rev_size,
2886 svn_revnum_t revision,
2887 apr_pool_t *scratch_pool)
2890 svn_fs_fs__ioctl_revision_size_input_t input = {0};
2891 svn_fs_fs__ioctl_revision_size_output_t *output;
2893 input.revision = revision;
2894 err = svn_fs_ioctl(fs, SVN_FS_FS__IOCTL_REVISION_SIZE,
2895 &input, (void **)&output,
2896 check_cancel, NULL, scratch_pool, scratch_pool);
2897 if (err && err->apr_err == SVN_ERR_FS_UNRECOGNIZED_IOCTL_CODE)
2899 return svn_error_quick_wrapf(err,
2900 _("Revision size query is not implemented "
2901 "for the filesystem type found in '%s'"),
2902 svn_fs_path(fs, scratch_pool));
2906 *rev_size = output->rev_size;
2907 return SVN_NO_ERROR;
2910 /* This implements `svn_opt_subcommand_t'. */
2912 subcommand_rev_size(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2914 struct svnadmin_opt_state *opt_state = baton;
2915 svn_revnum_t revision;
2919 if (opt_state->start_revision.kind != svn_opt_revision_number
2920 || opt_state->end_revision.kind != svn_opt_revision_unspecified)
2921 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2922 _("Invalid revision specifier"));
2923 revision = opt_state->start_revision.value.number;
2925 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
2926 SVN_ERR(revision_size(&rev_size, svn_repos_fs(repos), revision, pool));
2928 if (opt_state->quiet)
2929 SVN_ERR(svn_cmdline_printf(pool, "%"APR_OFF_T_FMT"\n", rev_size));
2931 SVN_ERR(svn_cmdline_printf(pool, _("%12"APR_OFF_T_FMT" bytes in revision %ld\n"),
2932 rev_size, revision));
2934 return SVN_NO_ERROR;
2938 build_rep_cache_progress_func(svn_revnum_t revision,
2942 svn_error_clear(svn_cmdline_printf(pool,
2943 _("* Processed revision %ld.\n"),
2947 static svn_error_t *
2948 build_rep_cache(svn_fs_t *fs,
2949 svn_revnum_t start_rev,
2950 svn_revnum_t end_rev,
2951 struct svnadmin_opt_state *opt_state,
2954 svn_fs_fs__ioctl_build_rep_cache_input_t input = {0};
2957 input.start_rev = start_rev;
2958 input.end_rev = end_rev;
2960 if (opt_state->quiet)
2962 input.progress_func = NULL;
2963 input.progress_baton = NULL;
2967 input.progress_func = build_rep_cache_progress_func;
2968 input.progress_baton = NULL;
2971 err = svn_fs_ioctl(fs, SVN_FS_FS__IOCTL_BUILD_REP_CACHE,
2973 check_cancel, NULL, pool, pool);
2974 if (err && err->apr_err == SVN_ERR_FS_UNRECOGNIZED_IOCTL_CODE)
2976 return svn_error_quick_wrapf(err,
2977 _("Building rep-cache is not implemented "
2978 "for the filesystem type found in '%s'"),
2979 svn_fs_path(fs, pool));
2981 else if (err && err->apr_err == SVN_ERR_FS_REP_SHARING_NOT_ALLOWED)
2983 svn_error_clear(err);
2984 SVN_ERR(svn_cmdline_printf(pool,
2985 _("svnadmin: Warning - this repository has rep-sharing disabled."
2986 " Building rep-cache has no effect.\n")));
2987 return SVN_NO_ERROR;
2995 /* This implements `svn_opt_subcommand_t'. */
2996 static svn_error_t *
2997 subcommand_build_repcache(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2999 struct svnadmin_opt_state *opt_state = baton;
3002 svn_revnum_t youngest;
3006 /* Expect no more arguments. */
3007 SVN_ERR(parse_args(NULL, os, 0, 0, pool));
3009 SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool));
3010 fs = svn_repos_fs(repos);
3011 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
3013 SVN_ERR(get_revnum(&lower, &opt_state->start_revision,
3014 youngest, repos, pool));
3015 SVN_ERR(get_revnum(&upper, &opt_state->end_revision,
3016 youngest, repos, pool));
3018 if (SVN_IS_VALID_REVNUM(lower) && SVN_IS_VALID_REVNUM(upper))
3021 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3022 _("First revision cannot be higher than second"));
3024 else if (SVN_IS_VALID_REVNUM(lower))
3033 SVN_ERR(build_rep_cache(fs, lower, upper, opt_state, pool));
3035 return SVN_NO_ERROR;
3042 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
3043 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
3044 * return SVN_NO_ERROR.
3046 static svn_error_t *
3047 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
3050 apr_status_t apr_err;
3052 const svn_opt_subcommand_desc3_t *subcommand = NULL;
3053 struct svnadmin_opt_state opt_state = { 0 };
3056 apr_array_header_t *received_opts;
3058 svn_boolean_t dash_F_arg = FALSE;
3060 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
3062 /* Check library versions */
3063 SVN_ERR(check_lib_versions());
3065 /* Initialize the FS library. */
3066 SVN_ERR(svn_fs_initialize(pool));
3070 SVN_ERR(subcommand_help(NULL, NULL, pool));
3071 *exit_code = EXIT_FAILURE;
3072 return SVN_NO_ERROR;
3075 /* Initialize opt_state. */
3076 opt_state.start_revision.kind = svn_opt_revision_unspecified;
3077 opt_state.end_revision.kind = svn_opt_revision_unspecified;
3078 opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
3080 /* Parse options. */
3081 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
3087 const char *opt_arg;
3088 const char *utf8_opt_arg;
3090 /* Parse the next option. */
3091 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
3092 if (APR_STATUS_IS_EOF(apr_err))
3096 SVN_ERR(subcommand_help(NULL, NULL, pool));
3097 *exit_code = EXIT_FAILURE;
3098 return SVN_NO_ERROR;
3101 /* Stash the option code in an array before parsing it. */
3102 APR_ARRAY_PUSH(received_opts, int) = opt_id;
3107 if (opt_state.start_revision.kind != svn_opt_revision_unspecified)
3109 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3110 _("Multiple revision arguments encountered; "
3111 "try '-r N:M' instead of '-r N -r M'"));
3113 if (svn_opt_parse_revision(&(opt_state.start_revision),
3114 &(opt_state.end_revision),
3115 opt_arg, pool) != 0)
3117 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3119 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3120 _("Syntax error in revision argument '%s'"),
3126 opt_state.txn_id = opt_arg;
3130 opt_state.quiet = TRUE;
3134 opt_state.help = TRUE;
3138 apr_uint64_t sz_val;
3139 SVN_ERR(svn_cstring_atoui64(&sz_val, opt_arg));
3141 opt_state.memory_cache_size = 0x100000 * sz_val;
3145 SVN_ERR(svn_utf_cstring_to_utf8(&(opt_state.file), opt_arg, pool));
3148 case svnadmin__version:
3149 opt_state.version = TRUE;
3151 case svnadmin__incremental:
3152 opt_state.incremental = TRUE;
3154 case svnadmin__deltas:
3155 opt_state.use_deltas = TRUE;
3157 case svnadmin__ignore_uuid:
3158 opt_state.uuid_action = svn_repos_load_uuid_ignore;
3160 case svnadmin__force_uuid:
3161 opt_state.uuid_action = svn_repos_load_uuid_force;
3163 case svnadmin__pre_1_4_compatible:
3164 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
3165 opt_state.compatible_version->major = 1;
3166 opt_state.compatible_version->minor = 3;
3168 case svnadmin__pre_1_5_compatible:
3169 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
3170 opt_state.compatible_version->major = 1;
3171 opt_state.compatible_version->minor = 4;
3173 case svnadmin__pre_1_6_compatible:
3174 opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t));
3175 opt_state.compatible_version->major = 1;
3176 opt_state.compatible_version->minor = 5;
3178 case svnadmin__compatible_version:
3180 svn_version_t latest = { SVN_VER_MAJOR, SVN_VER_MINOR,
3181 SVN_VER_PATCH, NULL };
3182 svn_version_t *compatible_version;
3184 /* Parse the version string which carries our target
3186 SVN_ERR(svn_version__parse_version_string(&compatible_version,
3189 /* We can't create repository with a version older than 1.0.0. */
3190 if (! svn_version__at_least(compatible_version, 1, 0, 0))
3192 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
3193 _("Cannot create pre-1.0-compatible "
3197 /* We can't create repository with a version newer than what
3198 the running version of Subversion supports. */
3199 if (! svn_version__at_least(&latest,
3200 compatible_version->major,
3201 compatible_version->minor,
3202 compatible_version->patch))
3204 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
3205 _("Cannot guarantee compatibility "
3206 "beyond the current running version "
3211 opt_state.compatible_version = compatible_version;
3214 case svnadmin__keep_going:
3215 opt_state.keep_going = TRUE;
3217 case svnadmin__check_normalization:
3218 opt_state.check_normalization = TRUE;
3220 case svnadmin__metadata_only:
3221 opt_state.metadata_only = TRUE;
3223 case svnadmin__fs_type:
3224 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool));
3226 case svnadmin__parent_dir:
3227 SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg,
3229 opt_state.parent_dir
3230 = svn_dirent_internal_style(opt_state.parent_dir, pool);
3232 case svnadmin__use_pre_commit_hook:
3233 opt_state.use_pre_commit_hook = TRUE;
3235 case svnadmin__use_post_commit_hook:
3236 opt_state.use_post_commit_hook = TRUE;
3238 case svnadmin__use_pre_revprop_change_hook:
3239 opt_state.use_pre_revprop_change_hook = TRUE;
3241 case svnadmin__use_post_revprop_change_hook:
3242 opt_state.use_post_revprop_change_hook = TRUE;
3244 case svnadmin__bdb_txn_nosync:
3245 opt_state.bdb_txn_nosync = TRUE;
3247 case svnadmin__bdb_log_keep:
3248 opt_state.bdb_log_keep = TRUE;
3250 case svnadmin__bypass_hooks:
3251 opt_state.bypass_hooks = TRUE;
3253 case svnadmin__bypass_prop_validation:
3254 opt_state.bypass_prop_validation = TRUE;
3256 case svnadmin__ignore_dates:
3257 opt_state.ignore_dates = TRUE;
3259 case svnadmin__clean_logs:
3260 opt_state.clean_logs = TRUE;
3262 case svnadmin__config_dir:
3263 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3264 opt_state.config_dir =
3265 apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool));
3267 case svnadmin__wait:
3268 opt_state.wait = TRUE;
3270 case svnadmin__no_flush_to_disk:
3271 opt_state.no_flush_to_disk = TRUE;
3273 case svnadmin__normalize_props:
3274 opt_state.normalize_props = TRUE;
3276 case svnadmin__exclude:
3277 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3279 if (! opt_state.exclude)
3280 opt_state.exclude = apr_array_make(pool, 1, sizeof(const char *));
3281 APR_ARRAY_PUSH(opt_state.exclude, const char *) = utf8_opt_arg;
3283 case svnadmin__include:
3284 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool));
3286 if (! opt_state.include)
3287 opt_state.include = apr_array_make(pool, 1, sizeof(const char *));
3288 APR_ARRAY_PUSH(opt_state.include, const char *) = utf8_opt_arg;
3290 case svnadmin__glob:
3291 opt_state.glob = TRUE;
3295 SVN_ERR(subcommand_help(NULL, NULL, pool));
3296 *exit_code = EXIT_FAILURE;
3297 return SVN_NO_ERROR;
3299 } /* close `switch' */
3300 } /* close `while' */
3302 /* If the user asked for help, then the rest of the arguments are
3303 the names of subcommands to get help on (if any), or else they're
3304 just typos/mistakes. Whatever the case, the subcommand to
3305 actually run is subcommand_help(). */
3307 subcommand = svn_opt_get_canonical_subcommand3(cmd_table, "help");
3309 /* If we're not running the `help' subcommand, then look for a
3310 subcommand in the first argument. */
3311 if (subcommand == NULL)
3313 if (os->ind >= os->argc)
3315 if (opt_state.version)
3317 /* Use the "help" subcommand to handle the "--version" option. */
3318 static const svn_opt_subcommand_desc3_t pseudo_cmd =
3319 { "--version", subcommand_help, {0}, {""},
3320 {svnadmin__version, /* must accept its own option */
3324 subcommand = &pseudo_cmd;
3328 svn_error_clear(svn_cmdline_fprintf(stderr, pool,
3329 _("subcommand argument required\n")));
3330 SVN_ERR(subcommand_help(NULL, NULL, pool));
3331 *exit_code = EXIT_FAILURE;
3332 return SVN_NO_ERROR;
3337 const char *first_arg;
3339 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
3341 subcommand = svn_opt_get_canonical_subcommand3(cmd_table, first_arg);
3342 if (subcommand == NULL)
3345 svn_cmdline_fprintf(stderr, pool,
3346 _("Unknown subcommand: '%s'\n"),
3348 SVN_ERR(subcommand_help(NULL, NULL, pool));
3349 *exit_code = EXIT_FAILURE;
3350 return SVN_NO_ERROR;
3355 /* Every subcommand except `help' and `freeze' with '-F' require a
3356 second argument -- the repository path. Parse it out here and
3357 store it in opt_state. */
3358 if (!(subcommand->cmd_func == subcommand_help
3359 || (subcommand->cmd_func == subcommand_freeze && dash_F_arg)))
3361 const char *repos_path = NULL;
3363 if (os->ind >= os->argc)
3365 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3366 _("Repository argument required"));
3369 SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool));
3371 if (svn_path_is_url(repos_path))
3373 return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
3374 _("'%s' is a URL when it should be a "
3375 "local path"), repos_path);
3378 opt_state.repository_path = svn_dirent_internal_style(repos_path, pool);
3381 /* Check that the subcommand wasn't passed any inappropriate options. */
3382 for (i = 0; i < received_opts->nelts; i++)
3384 opt_id = APR_ARRAY_IDX(received_opts, i, int);
3386 /* All commands implicitly accept --help, so just skip over this
3387 when we see it. Note that we don't want to include this option
3388 in their "accepted options" list because it would be awfully
3389 redundant to display it in every commands' help text. */
3390 if (opt_id == 'h' || opt_id == '?')
3393 if (! svn_opt_subcommand_takes_option4(subcommand, opt_id, NULL))
3396 const apr_getopt_option_t *badopt =
3397 svn_opt_get_option_from_code3(opt_id, options_table, subcommand,
3399 svn_opt_format_option(&optstr, badopt, FALSE, pool);
3400 if (subcommand->name[0] == '-')
3401 SVN_ERR(subcommand_help(NULL, NULL, pool));
3403 svn_error_clear(svn_cmdline_fprintf(stderr, pool
3404 , _("Subcommand '%s' doesn't accept option '%s'\n"
3405 "Type 'svnadmin help %s' for usage.\n"),
3406 subcommand->name, optstr, subcommand->name));
3407 *exit_code = EXIT_FAILURE;
3408 return SVN_NO_ERROR;
3412 check_cancel = svn_cmdline__setup_cancellation_handler();
3414 /* Configure FSFS caches for maximum efficiency with svnadmin.
3415 * Also, apply the respective command line parameters, if given. */
3417 svn_cache_config_t settings = *svn_cache_config_get();
3419 settings.cache_size = opt_state.memory_cache_size;
3420 settings.single_threaded = TRUE;
3422 svn_cache_config_set(&settings);
3425 /* Run the subcommand. */
3426 err = (*subcommand->cmd_func)(os, &opt_state, pool);
3429 /* For argument-related problems, suggest using the 'help'
3431 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
3432 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
3434 err = svn_error_quick_wrap(err,
3435 _("Try 'svnadmin help' for more info"));
3440 return SVN_NO_ERROR;
3444 main(int argc, const char *argv[])
3447 int exit_code = EXIT_SUCCESS;
3450 /* Initialize the app. */
3451 if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS)
3452 return EXIT_FAILURE;
3454 /* Create our top-level pool. Use a separate mutexless allocator,
3455 * given this application is single threaded.
3457 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
3459 err = sub_main(&exit_code, argc, argv, pool);
3461 /* Flush stdout and report if it fails. It would be flushed on exit anyway
3462 but this makes sure that output is not silently lost if it fails. */
3463 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
3467 exit_code = EXIT_FAILURE;
3468 svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ");
3471 svn_pool_destroy(pool);
3473 svn_cmdline__cancellation_exit();