diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-08-05 16:22:51 +0000 |
commit | cf46733632c7279a9fd0fe6ce26f9185a4ae82a9 (patch) | |
tree | da27775a2161723ef342e91af41a8b51fedef405 /subversion/svnadmin/svnadmin.c | |
parent | bb0ef45f7c46b0ae221b26265ef98a768c33f820 (diff) | |
download | subversion-tarball-master.tar.gz |
subversion-1.9.7HEADsubversion-1.9.7master
Diffstat (limited to 'subversion/svnadmin/svnadmin.c')
-rw-r--r-- | subversion/svnadmin/svnadmin.c | 874 |
1 files changed, 685 insertions, 189 deletions
diff --git a/subversion/svnadmin/svnadmin.c b/subversion/svnadmin/svnadmin.c index cfbcf66..2ee5410 100644 --- a/subversion/svnadmin/svnadmin.c +++ b/subversion/svnadmin/svnadmin.c @@ -39,19 +39,26 @@ #include "svn_cache_config.h" #include "svn_version.h" #include "svn_props.h" +#include "svn_sorts.h" #include "svn_time.h" #include "svn_user.h" #include "svn_xml.h" +#include "private/svn_cmdline_private.h" #include "private/svn_opt_private.h" +#include "private/svn_sorts_private.h" #include "private/svn_subr_private.h" -#include "private/svn_cmdline_private.h" #include "svn_private_config.h" /*** Code. ***/ +/* FSFS format 7's "block-read" feature performs poorly with small caches. + * Enable it only if caches above this threshold have been configured. + * The current threshold is 64MB. */ +#define BLOCK_READ_CACHE_THRESHOLD (0x40 * 0x100000) + /* A flag to see if we've been cancelled by the client or not. */ static volatile sig_atomic_t cancelled = FALSE; @@ -100,7 +107,7 @@ warning_func(void *baton, { if (! err) return; - svn_handle_error2(err, stderr, FALSE, "svnadmin: "); + svn_handle_warning2(stderr, err, "svnadmin: "); } @@ -111,6 +118,10 @@ open_repos(svn_repos_t **repos, const char *path, apr_pool_t *pool) { + /* Enable the "block-read" feature (where it applies)? */ + svn_boolean_t use_block_read + = svn_cache_config_get()->cache_size > BLOCK_READ_CACHE_THRESHOLD; + /* construct FS configuration parameters: enable caches for r/o data */ apr_hash_t *fs_config = apr_hash_make(pool); svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1"); @@ -118,9 +129,11 @@ open_repos(svn_repos_t **repos, svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2"); svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, svn_uuid_generate(pool)); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ, + use_block_read ? "1" : "0"); /* now, open the requested repository */ - SVN_ERR(svn_repos_open2(repos, path, fs_config, pool)); + SVN_ERR(svn_repos_open3(repos, path, fs_config, pool, pool)); svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL); return SVN_NO_ERROR; } @@ -150,11 +163,13 @@ check_lib_versions(void) static svn_opt_subcommand_t subcommand_crashtest, subcommand_create, + subcommand_delrevprop, subcommand_deltify, subcommand_dump, subcommand_freeze, subcommand_help, subcommand_hotcopy, + subcommand_info, subcommand_load, subcommand_list_dblogs, subcommand_list_unused_dblogs, @@ -176,6 +191,7 @@ enum svnadmin__cmdline_options_t { svnadmin__version = SVN_OPT_FIRST_LONGOPT_ID, svnadmin__incremental, + svnadmin__keep_going, svnadmin__deltas, svnadmin__ignore_uuid, svnadmin__force_uuid, @@ -186,6 +202,7 @@ enum svnadmin__cmdline_options_t svnadmin__config_dir, svnadmin__bypass_hooks, svnadmin__bypass_prop_validation, + svnadmin__ignore_dates, svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook, svnadmin__use_pre_revprop_change_hook, @@ -195,7 +212,9 @@ enum svnadmin__cmdline_options_t svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible, svnadmin__pre_1_6_compatible, - svnadmin__compatible_version + svnadmin__compatible_version, + svnadmin__check_normalization, + svnadmin__metadata_only }; /* Option codes and descriptions. @@ -231,8 +250,11 @@ static const apr_getopt_option_t options_table[] = {"bypass-prop-validation", svnadmin__bypass_prop_validation, 0, N_("bypass property validation logic")}, + {"ignore-dates", svnadmin__ignore_dates, 0, + N_("ignore revision datestamps found in the stream")}, + {"quiet", 'q', 0, - N_("no progress (only errors) to stderr")}, + N_("no progress (only errors to stderr)")}, {"ignore-uuid", svnadmin__ignore_uuid, 0, N_("ignore any repos UUID found in the stream")}, @@ -241,7 +263,9 @@ static const apr_getopt_option_t options_table[] = N_("set repos UUID to that found in stream, if any")}, {"fs-type", svnadmin__fs_type, 1, - N_("type of repository: 'fsfs' (default) or 'bdb'")}, + N_("type of repository:\n" + " 'fsfs' (default), 'bdb' or 'fsx'\n" + " CAUTION: FSX is for EXPERIMENTAL use only!")}, {"parent-dir", svnadmin__parent_dir, 1, N_("load at specified directory in repository")}, @@ -284,6 +308,9 @@ static const apr_getopt_option_t options_table[] = {"pre-1.6-compatible", svnadmin__pre_1_6_compatible, 0, N_("deprecated; see --compatible-version")}, + {"keep-going", svnadmin__keep_going, 0, + N_("continue verification after detecting a corruption")}, + {"memory-cache-size", 'M', 1, N_("size of the extra in-memory cache in MB used to\n" " minimize redundant operations. Default: 16.\n" @@ -295,6 +322,17 @@ static const apr_getopt_option_t options_table[] = {"file", 'F', 1, N_("read repository paths from file ARG")}, + {"check-normalization", svnadmin__check_normalization, 0, + N_("report any names within the same directory or\n" + " svn:mergeinfo property value that differ only\n" + " in character representation, but are otherwise\n" + " identical")}, + + {"metadata-only", svnadmin__metadata_only, 0, + N_("verify metadata only (ignored for BDB),\n" + " checking against external corruption in\n" + " Subversion 1.9+ format repositories.\n")}, + {NULL} }; @@ -319,6 +357,19 @@ static const svn_opt_subcommand_desc2_t cmd_table[] = svnadmin__pre_1_6_compatible } }, + {"delrevprop", subcommand_delrevprop, {0}, N_ + ("usage: 1. svnadmin delrevprop REPOS_PATH -r REVISION NAME\n" + " 2. svnadmin delrevprop REPO_PATH -t TXN NAME\n\n" + "1. Delete the property NAME on revision REVISION.\n\n" + "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n" + "trigger the revision property-related hooks (for example, if you want\n" + "an email notification sent from your post-revprop-change hook).\n\n" + "NOTE: Revision properties are not versioned, so this command will\n" + "irreversibly destroy the previous value of the property.\n\n" + "2. Delete the property NAME on transaction TXN.\n"), + {'r', 't', svnadmin__use_pre_revprop_change_hook, + svnadmin__use_post_revprop_change_hook} }, + {"deltify", subcommand_deltify, {0}, N_ ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n" "Run over the requested revision range, performing predecessor delti-\n" @@ -345,6 +396,7 @@ static const svn_opt_subcommand_desc2_t cmd_table[] = ("usage: 1. svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n" " 2. svnadmin freeze -F FILE PROGRAM [ARG...]\n\n" "1. Run PROGRAM passing ARGS while holding a write-lock on REPOS_PATH.\n" + " Allows safe use of third-party backup tools on a live repository.\n" "\n" "2. Like 1 except all repositories listed in FILE are locked. The file\n" " format is repository paths separated by newlines. Repositories are\n" @@ -361,7 +413,12 @@ static const svn_opt_subcommand_desc2_t cmd_table[] = "Make a hot copy of a repository.\n" "If --incremental is passed, data which already exists at the destination\n" "is not copied again. Incremental mode is implemented for FSFS repositories.\n"), - {svnadmin__clean_logs, svnadmin__incremental} }, + {svnadmin__clean_logs, svnadmin__incremental, 'q'} }, + + {"info", subcommand_info, {0}, N_ + ("usage: svnadmin info REPOS_PATH\n\n" + "Print information about the repository at REPOS_PATH.\n"), + {0} }, {"list-dblogs", subcommand_list_dblogs, {0}, N_ ("usage: svnadmin list-dblogs REPOS_PATH\n\n" @@ -384,6 +441,7 @@ static const svn_opt_subcommand_desc2_t cmd_table[] = "If --revision is specified, limit the loaded revisions to only those\n" "in the dump stream whose revision numbers match the specified range.\n"), {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid, + svnadmin__ignore_dates, svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook, svnadmin__parent_dir, svnadmin__bypass_prop_validation, 'M'} }, @@ -409,7 +467,7 @@ static const svn_opt_subcommand_desc2_t cmd_table[] = ("usage: svnadmin pack REPOS_PATH\n\n" "Possibly compact the repository into a more efficient storage model.\n" "This may not apply to all repositories, in which case, exit.\n"), - {'q'} }, + {'q', 'M'} }, {"recover", subcommand_recover, {0}, N_ ("usage: svnadmin recover REPOS_PATH\n\n" @@ -442,14 +500,16 @@ static const svn_opt_subcommand_desc2_t cmd_table[] = {'r', svnadmin__bypass_hooks} }, {"setrevprop", subcommand_setrevprop, {0}, N_ - ("usage: svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n\n" - "Set the property NAME on revision REVISION to the contents of FILE. Use\n" - "--use-pre-revprop-change-hook/--use-post-revprop-change-hook to trigger\n" - "the revision property-related hooks (for example, if you want an email\n" - "notification sent from your post-revprop-change hook).\n\n" + ("usage: 1. svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n" + " 2. svnadmin setrevprop REPOS_PATH -t TXN NAME FILE\n\n" + "1. Set the property NAME on revision REVISION to the contents of FILE.\n\n" + "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook to\n" + "trigger the revision property-related hooks (for example, if you want\n" + "an email notification sent from your post-revprop-change hook).\n\n" "NOTE: Revision properties are not versioned, so this command will\n" - "overwrite the previous value of the property.\n"), - {'r', svnadmin__use_pre_revprop_change_hook, + "overwrite the previous value of the property.\n\n" + "2. Set the property NAME on transaction TXN to the contents of FILE.\n"), + {'r', 't', svnadmin__use_pre_revprop_change_hook, svnadmin__use_post_revprop_change_hook} }, {"setuuid", subcommand_setuuid, {0}, N_ @@ -482,7 +542,8 @@ static const svn_opt_subcommand_desc2_t cmd_table[] = {"verify", subcommand_verify, {0}, N_ ("usage: svnadmin verify REPOS_PATH\n\n" "Verify the data stored in the repository.\n"), - {'t', 'r', 'q', 'M'} }, + {'t', 'r', 'q', svnadmin__keep_going, 'M', + svnadmin__check_normalization, svnadmin__metadata_only} }, { NULL, NULL, {0}, NULL, {0} } }; @@ -493,9 +554,6 @@ struct svnadmin_opt_state { const char *repository_path; const char *fs_type; /* --fs-type */ - svn_boolean_t pre_1_4_compatible; /* --pre-1.4-compatible */ - svn_boolean_t pre_1_5_compatible; /* --pre-1.5-compatible */ - svn_boolean_t pre_1_6_compatible; /* --pre-1.6-compatible */ svn_version_t *compatible_version; /* --compatible-version */ svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */ const char *txn_id; /* -t TXN */ @@ -513,11 +571,15 @@ struct svnadmin_opt_state svn_boolean_t clean_logs; /* --clean-logs */ svn_boolean_t bypass_hooks; /* --bypass-hooks */ svn_boolean_t wait; /* --wait */ + svn_boolean_t keep_going; /* --keep-going */ + svn_boolean_t check_normalization; /* --check-normalization */ + svn_boolean_t metadata_only; /* --metadata-only */ svn_boolean_t bypass_prop_validation; /* --bypass-prop-validation */ + svn_boolean_t ignore_dates; /* --ignore-dates */ enum svn_repos_load_uuid uuid_action; /* --ignore-uuid, --force-uuid */ apr_uint64_t memory_cache_size; /* --memory-cache-size M */ - const char *parent_dir; + const char *parent_dir; /* --parent-dir */ svn_stringbuf_t *filedata; /* --file */ const char *config_dir; /* Overriding Configuration Directory */ @@ -564,7 +626,7 @@ target_arg_to_dirent(const char **dirent, SVN_ERR(svn_utf_cstring_to_utf8(&path, arg, pool)); if (svn_path_is_url(path)) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, - "Path '%s' is not a local path", path); + _("Path '%s' is not a local path"), path); *dirent = svn_dirent_internal_style(path, pool); return SVN_NO_ERROR; } @@ -593,10 +655,10 @@ parse_args(apr_array_header_t **args, if ((min_expected >= 0) && (num_args < min_expected)) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, - "Not enough arguments"); + _("Not enough arguments")); if ((max_expected >= 0) && (num_args > max_expected)) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, - "Too many arguments"); + _("Too many arguments")); if (args) { *args = apr_array_make(pool, num_args, sizeof(const char *)); @@ -611,6 +673,17 @@ parse_args(apr_array_header_t **args, } +/* This implements 'svn_error_malfunction_handler_t. */ +static svn_error_t * +crashtest_malfunction_handler(svn_boolean_t can_return, + const char *file, + int line, + const char *expr) +{ + abort(); + return SVN_NO_ERROR; /* Not reached. */ +} + /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool) @@ -618,7 +691,14 @@ subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool) struct svnadmin_opt_state *opt_state = baton; svn_repos_t *repos; + (void)svn_error_set_malfunction_handler(crashtest_malfunction_handler); SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); + SVN_ERR(svn_cmdline_printf(pool, + _("Successfully opened repository '%s'.\n" + "Will now crash to simulate a crashing " + "server process.\n"), + svn_dirent_local_style(opt_state->repository_path, + pool))); SVN_ERR_MALFUNCTION(); /* merely silence a compiler warning (this will never be executed) */ @@ -662,17 +742,6 @@ subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool) svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, opt_state->fs_type); } - /* Prior to 1.8, we had explicit options to specify compatibility - with a handful of prior Subversion releases. */ - if (opt_state->pre_1_4_compatible) - svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1"); - if (opt_state->pre_1_5_compatible) - svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1"); - if (opt_state->pre_1_6_compatible) - svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1"); - - /* In 1.8, we figured out that we didn't have to keep extending this - madness indefinitely. */ if (opt_state->compatible_version) { if (! svn_version__at_least(opt_state->compatible_version, 1, 4, 0)) @@ -683,18 +752,40 @@ subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool) svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1"); if (! svn_version__at_least(opt_state->compatible_version, 1, 8, 0)) svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1"); + /* In 1.9, we figured out that we didn't have to keep extending this + madness indefinitely. */ + svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION, + apr_psprintf(pool, "%d.%d.%d%s%s", + opt_state->compatible_version->major, + opt_state->compatible_version->minor, + opt_state->compatible_version->patch, + opt_state->compatible_version->tag + ? "-" : "", + opt_state->compatible_version->tag + ? opt_state->compatible_version->tag : "")); } - if (opt_state->compatible_version - && ! svn_version__at_least(opt_state->compatible_version, 1, 1, 0) - /* ### TODO: this NULL check hard-codes knowledge of the library's - default fs-type value */ - && (opt_state->fs_type == NULL - || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS))) + if (opt_state->compatible_version) { - return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, - _("Repositories compatible with 1.0.x must use " - "--fs-type=bdb")); + if (! svn_version__at_least(opt_state->compatible_version, 1, 1, 0) + /* ### TODO: this NULL check hard-codes knowledge of the library's + default fs-type value */ + && (opt_state->fs_type == NULL + || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS))) + { + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Repositories compatible with 1.0.x must " + "use --fs-type=bdb")); + } + + if (! svn_version__at_least(opt_state->compatible_version, 1, 9, 0) + && opt_state->fs_type && !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSX)) + { + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Repositories compatible with 1.8.x or " + "earlier cannot use --fs-type=%s"), + SVN_FS_TYPE_FSX); + } } SVN_ERR(svn_repos_create(&repos, opt_state->repository_path, @@ -756,16 +847,87 @@ subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool) return SVN_NO_ERROR; } +/* Structure for errors encountered during 'svnadmin verify --keep-going'. */ +struct verification_error +{ + svn_revnum_t rev; + svn_error_t *err; +}; + +/* Pool cleanup function to clear an svn_error_t *. */ +static apr_status_t +err_cleanup(void *data) +{ + svn_error_t *err = data; + + svn_error_clear(err); + + return APR_SUCCESS; +} + +struct repos_verify_callback_baton +{ + /* Should we continue after receiving a first verification error? */ + svn_boolean_t keep_going; + + /* List of errors encountered during 'svnadmin verify --keep-going'. */ + apr_array_header_t *error_summary; + + /* Pool for data collected during callback invocations. */ + apr_pool_t *result_pool; +}; + +/* Implementation of svn_repos_verify_callback_t to handle errors coming + from svn_repos_verify_fs3(). */ +static svn_error_t * +repos_verify_callback(void *baton, + svn_revnum_t revision, + svn_error_t *verify_err, + apr_pool_t *scratch_pool) +{ + struct repos_verify_callback_baton *b = baton; + + if (revision == SVN_INVALID_REVNUM) + { + SVN_ERR(svn_cmdline_fputs(_("* Error verifying repository metadata.\n"), + stderr, scratch_pool)); + } + else + { + SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, + _("* Error verifying revision %ld.\n"), + revision)); + } + + if (b->keep_going) + { + struct verification_error *verr; + + svn_handle_error2(verify_err, stderr, FALSE, "svnadmin: "); + + /* Remember the error in B->ERROR_SUMMARY. */ + verr = apr_palloc(b->result_pool, sizeof(*verr)); + verr->rev = revision; + verr->err = svn_error_dup(verify_err); + apr_pool_cleanup_register(b->result_pool, verr->err, err_cleanup, + apr_pool_cleanup_null); + APR_ARRAY_PUSH(b->error_summary, struct verification_error *) = verr; + + return SVN_NO_ERROR; + } + else + return svn_error_trace(svn_error_dup(verify_err)); +} /* Implementation of svn_repos_notify_func_t to wrap the output to a - response stream for svn_repos_dump_fs2() and svn_repos_verify_fs() */ + response stream for svn_repos_dump_fs2(), svn_repos_verify_fs(), + svn_repos_hotcopy3() and others. */ static void repos_notify_handler(void *baton, const svn_repos_notify_t *notify, apr_pool_t *scratch_pool) { svn_stream_t *feedback_stream = baton; - apr_size_t len; switch (notify->action) { @@ -789,7 +951,7 @@ repos_notify_handler(void *baton, case svn_repos_notify_verify_rev_structure: if (notify->revision == SVN_INVALID_REVNUM) - svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, + svn_error_clear(svn_stream_puts(feedback_stream, _("* Verifying repository metadata ...\n"))); else svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, @@ -877,13 +1039,11 @@ repos_notify_handler(void *baton, return; case svn_repos_notify_load_node_done: - svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, - "%s", _(" done.\n"))); + svn_error_clear(svn_stream_puts(feedback_stream, _(" done.\n"))); return; case svn_repos_notify_load_copied_node: - len = 9; - svn_error_clear(svn_stream_write(feedback_stream, "COPIED...", &len)); + svn_error_clear(svn_stream_puts(feedback_stream, "COPIED...")); return; case svn_repos_notify_load_txn_start: @@ -911,7 +1071,7 @@ repos_notify_handler(void *baton, return; case svn_repos_notify_recover_start: - svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, + svn_error_clear(svn_stream_puts(feedback_stream, _("Repository lock acquired.\n" "Please wait; recovering the" " repository may take some time...\n"))); @@ -924,6 +1084,49 @@ repos_notify_handler(void *baton, " repository may take some time...\n"))); return; + case svn_repos_notify_pack_revprops: + { + const char *shardstr = apr_psprintf(scratch_pool, + "%" APR_INT64_T_FMT, + notify->shard); + svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, + _("Packed revision properties in shard %s\n"), + shardstr)); + return; + } + + case svn_repos_notify_cleanup_revprops: + { + const char *shardstr = apr_psprintf(scratch_pool, + "%" APR_INT64_T_FMT, + notify->shard); + svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, + _("Removed non-packed revision properties" + " in shard %s\n"), + shardstr)); + return; + } + + case svn_repos_notify_format_bumped: + svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, + _("Bumped repository format to %ld\n"), + notify->revision)); + return; + + case svn_repos_notify_hotcopy_rev_range: + if (notify->start_revision == notify->end_revision) + { + svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, + _("* Copied revision %ld.\n"), + notify->start_revision)); + } + else + { + svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, + _("* Copied revisions from %ld to %ld.\n"), + notify->start_revision, notify->end_revision)); + } + default: return; } @@ -980,7 +1183,7 @@ subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool) svn_stream_t *stdout_stream; svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM; svn_revnum_t youngest; - svn_stream_t *progress_stream = NULL; + svn_stream_t *feedback_stream = NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); @@ -1014,12 +1217,12 @@ subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool) /* Progress feedback goes to STDERR, unless they asked to suppress it. */ if (! opt_state->quiet) - progress_stream = recode_stream_create(stderr, pool); + feedback_stream = recode_stream_create(stderr, pool); SVN_ERR(svn_repos_dump_fs3(repos, stdout_stream, lower, upper, opt_state->incremental, opt_state->use_deltas, !opt_state->quiet ? repos_notify_handler : NULL, - progress_stream, check_cancel, NULL, pool)); + feedback_stream, check_cancel, NULL, pool)); return SVN_NO_ERROR; } @@ -1079,8 +1282,10 @@ subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool) } else { + const char *utf8; /* All repositories in filedata. */ - paths = svn_cstring_split(opt_state->filedata->data, "\n", FALSE, pool); + SVN_ERR(svn_utf_cstring_to_utf8(&utf8, opt_state->filedata->data, pool)); + paths = svn_cstring_split(utf8, "\r\n", FALSE, pool); } b.command = APR_ARRAY_IDX(args, 0, const char *); @@ -1106,6 +1311,7 @@ subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool) struct svnadmin_opt_state *opt_state = baton; const char *header = _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n" + "Subversion repository administration tool.\n" "Type 'svnadmin help <subcommand>' for help on a specific subcommand.\n" "Type 'svnadmin --version' to see the program version and FS modules.\n" "\n" @@ -1165,7 +1371,8 @@ subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool) struct svnadmin_opt_state *opt_state = baton; svn_repos_t *repos; svn_revnum_t lower = SVN_INVALID_REVNUM, upper = SVN_INVALID_REVNUM; - svn_stream_t *stdin_stream, *stdout_stream = NULL; + svn_stream_t *stdin_stream; + svn_stream_t *feedback_stream = NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); @@ -1199,15 +1406,16 @@ subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool) /* Progress feedback goes to STDOUT, unless they asked to suppress it. */ if (! opt_state->quiet) - stdout_stream = recode_stream_create(stdout, pool); + feedback_stream = recode_stream_create(stdout, pool); - err = svn_repos_load_fs4(repos, stdin_stream, lower, upper, + err = svn_repos_load_fs5(repos, stdin_stream, lower, upper, opt_state->uuid_action, opt_state->parent_dir, opt_state->use_pre_commit_hook, opt_state->use_post_commit_hook, !opt_state->bypass_prop_validation, + opt_state->ignore_dates, opt_state->quiet ? NULL : repos_notify_handler, - stdout_stream, check_cancel, NULL, pool); + feedback_stream, check_cancel, NULL, pool); if (err && err->apr_err == SVN_ERR_BAD_PROPERTY_VALUE) return svn_error_quick_wrap(err, _("Invalid property value found in " @@ -1254,12 +1462,12 @@ subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool) svn_repos_t *repos; svn_error_t *err; struct svnadmin_opt_state *opt_state = baton; - svn_stream_t *stdout_stream; + svn_stream_t *feedback_stream = NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); - SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); + SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool)); /* Restore default signal handlers until after we have acquired the * exclusive lock so that the user interrupt before we actually @@ -1267,7 +1475,7 @@ subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool) setup_cancellation_signals(SIG_DFL); err = svn_repos_recover4(opt_state->repository_path, TRUE, - repos_notify_handler, stdout_stream, + repos_notify_handler, feedback_stream, check_cancel, NULL, pool); if (err) { @@ -1285,7 +1493,7 @@ subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool) " another process has it open?\n"))); SVN_ERR(svn_cmdline_fflush(stdout)); SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE, - repos_notify_handler, stdout_stream, + repos_notify_handler, feedback_stream, check_cancel, NULL, pool)); } @@ -1423,30 +1631,47 @@ subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool) /* A helper for the 'setrevprop' and 'setlog' commands. Expects - OPT_STATE->use_pre_revprop_change_hook and - OPT_STATE->use_post_revprop_change_hook to be set appropriately. */ + OPT_STATE->txn_id, OPT_STATE->use_pre_revprop_change_hook and + OPT_STATE->use_post_revprop_change_hook to be set appropriately. + If FILENAME is NULL, delete property PROP_NAME. */ static svn_error_t * set_revprop(const char *prop_name, const char *filename, struct svnadmin_opt_state *opt_state, apr_pool_t *pool) { svn_repos_t *repos; - svn_string_t *prop_value = svn_string_create_empty(pool); - svn_stringbuf_t *file_contents; + svn_string_t *prop_value; + + if (filename) + { + svn_stringbuf_t *file_contents; - SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool)); + SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool)); - prop_value->data = file_contents->data; - prop_value->len = file_contents->len; + prop_value = svn_string_create_empty(pool); + prop_value->data = file_contents->data; + prop_value->len = file_contents->len; - SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value, - NULL, FALSE, pool, pool)); + SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, prop_value, + NULL, FALSE, pool, pool)); + } + else + { + prop_value = NULL; + } /* Open the filesystem */ SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); - /* If we are bypassing the hooks system, we just hit the filesystem - directly. */ - SVN_ERR(svn_repos_fs_change_rev_prop4( + if (opt_state->txn_id) + { + svn_fs_t *fs = svn_repos_fs(repos); + svn_fs_txn_t *txn; + + SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool)); + SVN_ERR(svn_fs_change_txn_prop(txn, prop_name, prop_value, pool)); + } + else + SVN_ERR(svn_repos_fs_change_rev_prop4( repos, opt_state->start_revision.value.number, NULL, prop_name, NULL, prop_value, opt_state->use_pre_revprop_change_hook, @@ -1471,7 +1696,21 @@ subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool) filename = APR_ARRAY_IDX(args, 1, const char *); SVN_ERR(target_arg_to_dirent(&filename, filename, pool)); - if (opt_state->start_revision.kind != svn_opt_revision_number) + if (opt_state->txn_id) + { + if (opt_state->start_revision.kind != svn_opt_revision_unspecified + || opt_state->end_revision.kind != svn_opt_revision_unspecified) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--revision (-r) and --transaction (-t) " + "are mutually exclusive")); + + if (opt_state->use_pre_revprop_change_hook + || opt_state->use_post_revprop_change_hook) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Calling hooks is incompatible with " + "--transaction (-t)")); + } + else if (opt_state->start_revision.kind != svn_opt_revision_number) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Missing revision")); else if (opt_state->end_revision.kind != svn_opt_revision_unspecified) @@ -1540,7 +1779,7 @@ subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state = baton; svn_repos_t *repos; - svn_stream_t *progress_stream = NULL; + svn_stream_t *feedback_stream = NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); @@ -1549,11 +1788,11 @@ subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool) /* Progress feedback goes to STDOUT, unless they asked to suppress it. */ if (! opt_state->quiet) - progress_stream = recode_stream_create(stdout, pool); + feedback_stream = recode_stream_create(stdout, pool); return svn_error_trace( svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : NULL, - progress_stream, check_cancel, NULL, pool)); + feedback_stream, check_cancel, NULL, pool)); } @@ -1565,7 +1804,8 @@ subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool) svn_repos_t *repos; svn_fs_t *fs; svn_revnum_t youngest, lower, upper; - svn_stream_t *progress_stream = NULL; + svn_stream_t *feedback_stream = NULL; + struct repos_verify_callback_baton verify_baton = { 0 }; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); @@ -1609,13 +1849,93 @@ subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool) upper = lower; } - if (! opt_state->quiet) - progress_stream = recode_stream_create(stderr, pool); + if (!opt_state->quiet) + feedback_stream = recode_stream_create(stdout, pool); + + verify_baton.keep_going = opt_state->keep_going; + verify_baton.error_summary = + apr_array_make(pool, 0, sizeof(struct verification_error *)); + verify_baton.result_pool = pool; + + SVN_ERR(svn_repos_verify_fs3(repos, lower, upper, + opt_state->check_normalization, + opt_state->metadata_only, + !opt_state->quiet + ? repos_notify_handler : NULL, + feedback_stream, + repos_verify_callback, &verify_baton, + check_cancel, NULL, pool)); + + /* Show the --keep-going error summary. */ + if (!opt_state->quiet + && opt_state->keep_going + && verify_baton.error_summary->nelts > 0) + { + int rev_maxlength; + svn_revnum_t end_revnum; + apr_pool_t *iterpool; + int i; + + svn_error_clear( + svn_stream_puts(feedback_stream, + _("\n-----Summary of corrupt revisions-----\n"))); + + /* The standard column width for the revision number is 6 characters. + If the revision number can potentially be larger (i.e. if end_revnum + is larger than 1000000), we increase the column width as needed. */ + rev_maxlength = 6; + end_revnum = APR_ARRAY_IDX(verify_baton.error_summary, + verify_baton.error_summary->nelts - 1, + struct verification_error *)->rev; + while (end_revnum >= 1000000) + { + rev_maxlength++; + end_revnum = end_revnum / 10; + } + + iterpool = svn_pool_create(pool); + for (i = 0; i < verify_baton.error_summary->nelts; i++) + { + struct verification_error *verr; + svn_error_t *err; + const char *rev_str; + + svn_pool_clear(iterpool); + + verr = APR_ARRAY_IDX(verify_baton.error_summary, i, + struct verification_error *); + + if (verr->rev != SVN_INVALID_REVNUM) + { + rev_str = apr_psprintf(iterpool, "r%ld", verr->rev); + rev_str = apr_psprintf(iterpool, "%*s", rev_maxlength, rev_str); + for (err = svn_error_purge_tracing(verr->err); + err != SVN_NO_ERROR; err = err->child) + { + char buf[512]; + const char *message; + + message = svn_err_best_message(err, buf, sizeof(buf)); + svn_error_clear(svn_stream_printf(feedback_stream, iterpool, + "%s: E%06d: %s\n", + rev_str, err->apr_err, + message)); + } + } + } + + svn_pool_destroy(iterpool); + } + + if (verify_baton.error_summary->nelts > 0) + { + return svn_error_createf(SVN_ERR_CL_REPOS_VERIFY_FAILED, NULL, + _("Failed to verify repository '%s'"), + svn_dirent_local_style( + opt_state->repository_path, pool)); + } - return svn_repos_verify_fs2(repos, lower, upper, - !opt_state->quiet - ? repos_notify_handler : NULL, - progress_stream, check_cancel, NULL, pool); + return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ @@ -1623,6 +1943,7 @@ svn_error_t * subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state = baton; + svn_stream_t *feedback_stream = NULL; apr_array_header_t *targets; const char *new_repos_path; @@ -1631,9 +1952,136 @@ subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool) new_repos_path = APR_ARRAY_IDX(targets, 0, const char *); SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool)); - return svn_repos_hotcopy2(opt_state->repository_path, new_repos_path, + /* Progress feedback goes to STDOUT, unless they asked to suppress it. */ + if (! opt_state->quiet) + feedback_stream = recode_stream_create(stdout, pool); + + return svn_repos_hotcopy3(opt_state->repository_path, new_repos_path, opt_state->clean_logs, opt_state->incremental, - check_cancel, NULL, pool); + !opt_state->quiet ? repos_notify_handler : NULL, + feedback_stream, check_cancel, NULL, pool); +} + +svn_error_t * +subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool) +{ + struct svnadmin_opt_state *opt_state = baton; + svn_repos_t *repos; + svn_fs_t *fs; + int fs_format; + const char *uuid; + + /* Expect no more arguments. */ + SVN_ERR(parse_args(NULL, os, 0, 0, pool)); + + SVN_ERR(open_repos(&repos, opt_state->repository_path, pool)); + fs = svn_repos_fs(repos); + SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), + svn_dirent_local_style(svn_repos_path(repos, pool), + pool))); + + SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool)); + SVN_ERR(svn_cmdline_printf(pool, _("UUID: %s\n"), uuid)); + { + int repos_format, minor; + svn_version_t *repos_version, *fs_version; + SVN_ERR(svn_repos_info_format(&repos_format, &repos_version, + repos, pool, pool)); + SVN_ERR(svn_cmdline_printf(pool, _("Repository Format: %d\n"), + repos_format)); + + SVN_ERR(svn_fs_info_format(&fs_format, &fs_version, + fs, pool, pool)); + /* fs_format will be printed later. */ + + SVN_ERR_ASSERT(repos_version->major == SVN_VER_MAJOR); + SVN_ERR_ASSERT(fs_version->major == SVN_VER_MAJOR); + SVN_ERR_ASSERT(repos_version->patch == 0); + SVN_ERR_ASSERT(fs_version->patch == 0); + + minor = (repos_version->minor > fs_version->minor) + ? repos_version->minor : fs_version->minor; + SVN_ERR(svn_cmdline_printf(pool, _("Compatible With Version: %d.%d.0\n"), + SVN_VER_MAJOR, minor)); + } + + { + apr_hash_t *capabilities_set; + apr_array_header_t *capabilities; + int i; + + SVN_ERR(svn_repos_capabilities(&capabilities_set, repos, pool, pool)); + capabilities = svn_sort__hash(capabilities_set, + svn_sort_compare_items_lexically, + pool); + + for (i = 0; i < capabilities->nelts; i++) + { + svn_sort__item_t *item = &APR_ARRAY_IDX(capabilities, i, + svn_sort__item_t); + const char *capability = item->key; + SVN_ERR(svn_cmdline_printf(pool, _("Repository Capability: %s\n"), + capability)); + } + } + + { + const svn_fs_info_placeholder_t *info; + + SVN_ERR(svn_fs_info(&info, fs, pool, pool)); + SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Type: %s\n"), + info->fs_type)); + SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Format: %d\n"), + fs_format)); + if (!strcmp(info->fs_type, SVN_FS_TYPE_FSFS)) + { + const svn_fs_fsfs_info_t *fsfs_info = (const void *)info; + svn_revnum_t youngest; + SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); + + if (fsfs_info->shard_size) + SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: yes\n"))); + else + SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: no\n"))); + + if (fsfs_info->shard_size) + SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shard Size: %d\n"), + fsfs_info->shard_size)); + + /* Print packing statistics, if enabled on the FS. */ + if (fsfs_info->shard_size) + { + const int shard_size = fsfs_info->shard_size; + const long shards_packed = fsfs_info->min_unpacked_rev / shard_size; + const long shards_full = (youngest + 1) / shard_size; + SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shards Packed: %ld/%ld\n"), + shards_packed, shards_full)); + } + + if (fsfs_info->log_addressing) + SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: yes\n"))); + else + SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: no\n"))); + } + } + + { + apr_array_header_t *files; + int i; + + SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool)); + for (i = 0; i < files->nelts; i++) + SVN_ERR(svn_cmdline_printf(pool, _("Configuration File: %s\n"), + svn_dirent_local_style( + APR_ARRAY_IDX(files, i, const char *), + pool))); + } + + /* 'svn info' prints an extra newline here, to support multiple targets. + We'll do the same. */ + SVN_ERR(svn_cmdline_printf(pool, "\n")); + + return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ @@ -1709,6 +2157,7 @@ subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool) const char *fs_path = "/"; apr_hash_t *locks; apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(pool); SVN_ERR(svn_opt__args_to_target_array(&targets, os, apr_array_make(pool, 0, @@ -1729,24 +2178,28 @@ subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool) for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi)) { const char *cr_date, *exp_date = ""; - const char *path = svn__apr_hash_index_key(hi); - svn_lock_t *lock = svn__apr_hash_index_val(hi); + const char *path = apr_hash_this_key(hi); + svn_lock_t *lock = apr_hash_this_val(hi); int comment_lines = 0; - cr_date = svn_time_to_human_cstring(lock->creation_date, pool); + svn_pool_clear(iterpool); + + SVN_ERR(check_cancel(NULL)); + + cr_date = svn_time_to_human_cstring(lock->creation_date, iterpool); if (lock->expiration_date) - exp_date = svn_time_to_human_cstring(lock->expiration_date, pool); + exp_date = svn_time_to_human_cstring(lock->expiration_date, iterpool); if (lock->comment) comment_lines = svn_cstring_count_newlines(lock->comment) + 1; - SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), path)); - SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token)); - SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner)); - SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date)); - SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date)); - SVN_ERR(svn_cmdline_printf(pool, + SVN_ERR(svn_cmdline_printf(iterpool, _("Path: %s\n"), path)); + SVN_ERR(svn_cmdline_printf(iterpool, _("UUID Token: %s\n"), lock->token)); + SVN_ERR(svn_cmdline_printf(iterpool, _("Owner: %s\n"), lock->owner)); + SVN_ERR(svn_cmdline_printf(iterpool, _("Created: %s\n"), cr_date)); + SVN_ERR(svn_cmdline_printf(iterpool, _("Expires: %s\n"), exp_date)); + SVN_ERR(svn_cmdline_printf(iterpool, Q_("Comment (%i line):\n%s\n\n", "Comment (%i lines):\n%s\n\n", comment_lines), @@ -1754,6 +2207,8 @@ subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool) lock->comment ? lock->comment : "")); } + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; } @@ -1888,18 +2343,18 @@ subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool) { svn_error_t *err; struct svnadmin_opt_state *opt_state = baton; - svn_stream_t *stdout_stream; + svn_stream_t *feedback_stream = NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); - SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool)); + SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool)); /* Restore default signal handlers. */ setup_cancellation_signals(SIG_DFL); err = svn_repos_upgrade2(opt_state->repository_path, TRUE, - repos_notify_handler, stdout_stream, pool); + repos_notify_handler, feedback_stream, pool); if (err) { if (APR_STATUS_IS_EAGAIN(err->apr_err)) @@ -1917,7 +2372,7 @@ subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool) " another process has it open?\n"))); SVN_ERR(svn_cmdline_fflush(stdout)); SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE, - repos_notify_handler, stdout_stream, + repos_notify_handler, feedback_stream, pool)); } else if (err->apr_err == SVN_ERR_FS_UNSUPPORTED_UPGRADE) @@ -1941,26 +2396,53 @@ subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool) } +/* This implements `svn_opt_subcommand_t'. */ +static svn_error_t * +subcommand_delrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool) +{ + struct svnadmin_opt_state *opt_state = baton; + apr_array_header_t *args; + const char *prop_name; + + /* Expect one more argument: NAME */ + SVN_ERR(parse_args(&args, os, 1, 1, pool)); + prop_name = APR_ARRAY_IDX(args, 0, const char *); + + if (opt_state->txn_id) + { + if (opt_state->start_revision.kind != svn_opt_revision_unspecified + || opt_state->end_revision.kind != svn_opt_revision_unspecified) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("--revision (-r) and --transaction (-t) " + "are mutually exclusive")); + + if (opt_state->use_pre_revprop_change_hook + || opt_state->use_post_revprop_change_hook) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Calling hooks is incompatible with " + "--transaction (-t)")); + } + else if (opt_state->start_revision.kind != svn_opt_revision_number) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Missing revision")); + else if (opt_state->end_revision.kind != svn_opt_revision_unspecified) + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Only one revision allowed")); + + return set_revprop(prop_name, NULL, opt_state, pool); +} + + /** Main. **/ -/* Report and clear the error ERR, and return EXIT_FAILURE. */ -#define EXIT_ERROR(err) \ - svn_cmdline_handle_exit_error(err, NULL, "svnadmin: ") - -/* A redefinition of the public SVN_INT_ERR macro, that suppresses the - * error message if it is SVN_ERR_IO_PIPE_WRITE_ERROR, amd with the - * program name 'svnadmin' instead of 'svn'. */ -#undef SVN_INT_ERR -#define SVN_INT_ERR(expr) \ - do { \ - svn_error_t *svn_err__temp = (expr); \ - if (svn_err__temp) \ - return EXIT_ERROR(svn_err__temp); \ - } while (0) - -static int -sub_main(int argc, const char *argv[], apr_pool_t *pool) +/* + * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error, + * either return an error to be displayed, or set *EXIT_CODE to non-zero and + * return SVN_NO_ERROR. + */ +static svn_error_t * +sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) { svn_error_t *err; apr_status_t apr_err; @@ -1976,15 +2458,16 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int)); /* Check library versions */ - SVN_INT_ERR(check_lib_versions()); + SVN_ERR(check_lib_versions()); /* Initialize the FS library. */ - SVN_INT_ERR(svn_fs_initialize(pool)); + SVN_ERR(svn_fs_initialize(pool)); if (argc <= 1) { - SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); - return EXIT_FAILURE; + SVN_ERR(subcommand_help(NULL, NULL, pool)); + *exit_code = EXIT_FAILURE; + return SVN_NO_ERROR; } /* Initialize opt_state. */ @@ -1993,7 +2476,7 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) opt_state.memory_cache_size = svn_cache_config_get()->cache_size; /* Parse options. */ - SVN_INT_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); + SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); os->interleave = 1; @@ -2008,8 +2491,9 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) break; else if (apr_err) { - SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); - return EXIT_FAILURE; + SVN_ERR(subcommand_help(NULL, NULL, pool)); + *exit_code = EXIT_FAILURE; + return SVN_NO_ERROR; } /* Stash the option code in an array before parsing it. */ @@ -2020,23 +2504,19 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) { if (opt_state.start_revision.kind != svn_opt_revision_unspecified) { - err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, - _("Multiple revision arguments encountered; " - "try '-r N:M' instead of '-r N -r M'")); - return EXIT_ERROR(err); + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Multiple revision arguments encountered; " + "try '-r N:M' instead of '-r N -r M'")); } if (svn_opt_parse_revision(&(opt_state.start_revision), &(opt_state.end_revision), opt_arg, pool) != 0) { - err = svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, - pool); + SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); - if (! err) - err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Syntax error in revision argument '%s'"), utf8_opt_arg); - return EXIT_ERROR(err); } } break; @@ -2056,8 +2536,8 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) = 0x100000 * apr_strtoi64(opt_arg, NULL, 0); break; case 'F': - SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); - SVN_INT_ERR(svn_stringbuf_from_file2(&(opt_state.filedata), + SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); + SVN_ERR(svn_stringbuf_from_file2(&(opt_state.filedata), utf8_opt_arg, pool)); dash_F_arg = TRUE; case svnadmin__version: @@ -2076,13 +2556,19 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) opt_state.uuid_action = svn_repos_load_uuid_force; break; case svnadmin__pre_1_4_compatible: - opt_state.pre_1_4_compatible = TRUE; + opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t)); + opt_state.compatible_version->major = 1; + opt_state.compatible_version->minor = 3; break; case svnadmin__pre_1_5_compatible: - opt_state.pre_1_5_compatible = TRUE; + opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t)); + opt_state.compatible_version->major = 1; + opt_state.compatible_version->minor = 4; break; case svnadmin__pre_1_6_compatible: - opt_state.pre_1_6_compatible = TRUE; + opt_state.compatible_version = apr_pcalloc(pool, sizeof(svn_version_t)); + opt_state.compatible_version->major = 1; + opt_state.compatible_version->minor = 5; break; case svnadmin__compatible_version: { @@ -2092,16 +2578,15 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) /* Parse the version string which carries our target compatibility. */ - SVN_INT_ERR(svn_version__parse_version_string(&compatible_version, + SVN_ERR(svn_version__parse_version_string(&compatible_version, opt_arg, pool)); /* We can't create repository with a version older than 1.0.0. */ if (! svn_version__at_least(compatible_version, 1, 0, 0)) { - err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, - _("Cannot create pre-1.0-compatible " - "repositories")); - return EXIT_ERROR(err); + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot create pre-1.0-compatible " + "repositories")); } /* We can't create repository with a version newer than what @@ -2111,22 +2596,30 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) compatible_version->minor, compatible_version->patch)) { - err = svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, - _("Cannot guarantee compatibility " - "beyond the current running version " - "(%s)"), - SVN_VER_NUM ); - return EXIT_ERROR(err); + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Cannot guarantee compatibility " + "beyond the current running version " + "(%s)"), + SVN_VER_NUM); } opt_state.compatible_version = compatible_version; } break; + case svnadmin__keep_going: + opt_state.keep_going = TRUE; + break; + case svnadmin__check_normalization: + opt_state.check_normalization = TRUE; + break; + case svnadmin__metadata_only: + opt_state.metadata_only = TRUE; + break; case svnadmin__fs_type: - SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool)); + SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, pool)); break; case svnadmin__parent_dir: - SVN_INT_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg, + SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg, pool)); opt_state.parent_dir = svn_dirent_internal_style(opt_state.parent_dir, pool); @@ -2155,11 +2648,14 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) case svnadmin__bypass_prop_validation: opt_state.bypass_prop_validation = TRUE; break; + case svnadmin__ignore_dates: + opt_state.ignore_dates = TRUE; + break; case svnadmin__clean_logs: opt_state.clean_logs = TRUE; break; case svnadmin__config_dir: - SVN_INT_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); + SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); opt_state.config_dir = apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, pool)); break; @@ -2168,8 +2664,9 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) break; default: { - SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); - return EXIT_FAILURE; + SVN_ERR(subcommand_help(NULL, NULL, pool)); + *exit_code = EXIT_FAILURE; + return SVN_NO_ERROR; } } /* close `switch' */ } /* close `while' */ @@ -2202,8 +2699,9 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) { svn_error_clear(svn_cmdline_fprintf(stderr, pool, _("subcommand argument required\n"))); - SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); - return EXIT_FAILURE; + SVN_ERR(subcommand_help(NULL, NULL, pool)); + *exit_code = EXIT_FAILURE; + return SVN_NO_ERROR; } } else @@ -2213,14 +2711,15 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) if (subcommand == NULL) { const char *first_arg_utf8; - SVN_INT_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, + SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, pool)); svn_error_clear( svn_cmdline_fprintf(stderr, pool, _("Unknown subcommand: '%s'\n"), first_arg_utf8)); - SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); - return EXIT_FAILURE; + SVN_ERR(subcommand_help(NULL, NULL, pool)); + *exit_code = EXIT_FAILURE; + return SVN_NO_ERROR; } } } @@ -2235,23 +2734,17 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) if (os->ind >= os->argc) { - err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, - _("Repository argument required")); - return EXIT_ERROR(err); + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("Repository argument required")); } - if ((err = svn_utf_cstring_to_utf8(&repos_path, - os->argv[os->ind++], pool))) - { - return EXIT_ERROR(err); - } + SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], pool)); if (svn_path_is_url(repos_path)) { - err = svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, - _("'%s' is a URL when it should be a " - "local path"), repos_path); - return EXIT_ERROR(err); + return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("'%s' is a URL when it should be a " + "local path"), repos_path); } opt_state.repository_path = svn_dirent_internal_style(repos_path, pool); @@ -2277,13 +2770,14 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) pool); svn_opt_format_option(&optstr, badopt, FALSE, pool); if (subcommand->name[0] == '-') - SVN_INT_ERR(subcommand_help(NULL, NULL, pool)); + SVN_ERR(subcommand_help(NULL, NULL, pool)); else svn_error_clear(svn_cmdline_fprintf(stderr, pool , _("Subcommand '%s' doesn't accept option '%s'\n" "Type 'svnadmin help %s' for usage.\n"), subcommand->name, optstr, subcommand->name)); - return EXIT_FAILURE; + *exit_code = EXIT_FAILURE; + return SVN_NO_ERROR; } } @@ -2325,26 +2819,18 @@ sub_main(int argc, const char *argv[], apr_pool_t *pool) err = svn_error_quick_wrap(err, _("Try 'svnadmin help' for more info")); } - return EXIT_ERROR(err); - } - else - { - /* Ensure that everything is written to stdout, so the user will - see any print errors. */ - err = svn_cmdline_fflush(stdout); - if (err) - { - return EXIT_ERROR(err); - } - return EXIT_SUCCESS; + return err; } + + return SVN_NO_ERROR; } int main(int argc, const char *argv[]) { apr_pool_t *pool; - int exit_code; + int exit_code = EXIT_SUCCESS; + svn_error_t *err; /* Initialize the app. */ if (svn_cmdline_init("svnadmin", stderr) != EXIT_SUCCESS) @@ -2355,7 +2841,17 @@ main(int argc, const char *argv[]) */ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); - exit_code = sub_main(argc, argv, pool); + err = sub_main(&exit_code, argc, argv, pool); + + /* Flush stdout and report if it fails. It would be flushed on exit anyway + but this makes sure that output is not silently lost if it fails. */ + err = svn_error_compose_create(err, svn_cmdline_fflush(stdout)); + + if (err) + { + exit_code = EXIT_FAILURE; + svn_cmdline_handle_exit_error(err, NULL, "svnadmin: "); + } svn_pool_destroy(pool); return exit_code; |