Introduce 'svnadmin build-repcache' command. Implement the 'svnadmin build-repcache' CLI and add an ioctl API for building the representation cache. The implementation iterates over revisions in the specified range and recursively processes the changed nodes, starting from the corresponding revision roots. For each changed node, it ensures that its data and property representations exist in the rep-cache. The nodes are processed in the same order as when committing a transaction (see write_final_rev() function in libsvn_fs_fs/transaction.c), so that the rep-cache.db files are fully consistent. If a repository does not support the rep-sharing or rep-sharing is disabled, then do nothing. * subversion/svnadmin/svnadmin.c (cmd_table): Add and document the 'build-repcache' command. (subcommand_build_repcache, build_rep_cache, build_rep_cache_progress_func): New. * subversion/include/private/svn_fs_fs_private.h (svn_fs_fs__ioctl_build_rep_cache_input_t, SVN_FS_FS__IOCTL_BUILD_REP_CACHE): New. * subversion/libsvn_fs_fs/fs.c (fs_ioctl): Handle SVN_FS_FS__IOCTL_BUILD_REP_CACHE. * subversion/libsvn_fs_fs/fs_fs.h * subversion/libsvn_fs_fs/fs_fs.c (): Include 'low_level.h'. (svn_fs_fs__build_rep_cache, reindex_node, ensure_representation_sha1): New. Iterate over revisions and recursively process the changed nodes. For each changed node, ensure that its data and property representations exist in the rep-cache. * subversion/tests/cmdline/svnadmin_tests.py (build_repcache): New test. (test_list): Add the new test. * subversion/tests/libsvn_fs_fs/fs-fs-private-test.c (): Include 'libsvn_fs_fs/rep-cache.h' and 'libsvn_fs/fs-loader.h'. (build_rep_cache): New test. (test_funcs): Add the new test. * tools/client-side/bash_completion (_svnadmin): Add the 'build-repcache' command. Patch by: Denis Kovalchuk Index: subversion/include/private/svn_fs_fs_private.h =================================================================== --- subversion/include/private/svn_fs_fs_private.h (revision 1875261) +++ subversion/include/private/svn_fs_fs_private.h (working copy) @@ -353,6 +353,16 @@ /* See svn_fs_fs__revision_size(). */ SVN_FS_DECLARE_IOCTL_CODE(SVN_FS_FS__IOCTL_REVISION_SIZE, SVN_FS_TYPE_FSFS, 1003); +typedef struct svn_fs_fs__ioctl_build_rep_cache_input_t +{ + svn_revnum_t start_rev; + svn_revnum_t end_rev; + svn_fs_progress_notify_func_t progress_func; + void *progress_baton; +} svn_fs_fs__ioctl_build_rep_cache_input_t; + +SVN_FS_DECLARE_IOCTL_CODE(SVN_FS_FS__IOCTL_BUILD_REP_CACHE, SVN_FS_TYPE_FSFS, 1004); + #ifdef __cplusplus } #endif /* __cplusplus */ Index: subversion/libsvn_fs_fs/fs.c =================================================================== --- subversion/libsvn_fs_fs/fs.c (revision 1875261) +++ subversion/libsvn_fs_fs/fs.c (working copy) @@ -312,6 +312,22 @@ *output_p = output; return SVN_NO_ERROR; } + else if (ctlcode.code == SVN_FS_FS__IOCTL_BUILD_REP_CACHE.code) + { + svn_fs_fs__ioctl_build_rep_cache_input_t *input = input_void; + + SVN_ERR(svn_fs_fs__build_rep_cache(fs, + input->start_rev, + input->end_rev, + input->progress_func, + input->progress_baton, + cancel_func, + cancel_baton, + scratch_pool)); + + *output_p = NULL; + return SVN_NO_ERROR; + } } return svn_error_create(SVN_ERR_FS_UNRECOGNIZED_IOCTL_CODE, NULL, NULL); Index: subversion/libsvn_fs_fs/fs_fs.c =================================================================== --- subversion/libsvn_fs_fs/fs_fs.c (revision 1875261) +++ subversion/libsvn_fs_fs/fs_fs.c (working copy) @@ -37,6 +37,7 @@ #include "cached_data.h" #include "id.h" #include "index.h" +#include "low_level.h" #include "rep-cache.h" #include "revprops.h" #include "transaction.h" @@ -2343,3 +2344,159 @@ result_pool); return SVN_NO_ERROR; } + +static svn_error_t * +ensure_representation_sha1(svn_fs_t *fs, + representation_t *rep, + apr_pool_t *pool) +{ + if (!rep->has_sha1) + { + svn_stream_t *contents; + svn_checksum_t *checksum; + + SVN_ERR(svn_fs_fs__get_contents(&contents, fs, rep, FALSE, pool)); + SVN_ERR(svn_stream_contents_checksum(&checksum, contents, + svn_checksum_sha1, pool, pool)); + + memcpy(rep->sha1_digest, checksum->digest, APR_SHA1_DIGESTSIZE); + rep->has_sha1 = TRUE; + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +reindex_node(svn_fs_t *fs, + const svn_fs_id_t *id, + svn_revnum_t rev, + svn_fs_fs__revision_file_t *rev_file, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + node_revision_t *noderev; + apr_off_t offset; + + if (svn_fs_fs__id_rev(id) != rev) + { + return SVN_NO_ERROR; + } + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, rev, NULL, + svn_fs_fs__id_item(id), pool)); + + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool)); + SVN_ERR(svn_fs_fs__read_noderev(&noderev, rev_file->stream, + pool, pool)); + + /* First reindex sub-directory to match write_final_rev() behavior. */ + if (noderev->kind == svn_node_dir) + { + apr_array_header_t *entries; + + SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool, pool)); + + if (entries->nelts > 0) + { + int i; + apr_pool_t *iterpool; + + iterpool = svn_pool_create(pool); + for (i = 0; i < entries->nelts; i++) + { + const svn_fs_dirent_t *dirent; + + svn_pool_clear(iterpool); + + dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *); + + SVN_ERR(reindex_node(fs, dirent->id, rev, rev_file, + cancel_func, cancel_baton, iterpool)); + } + svn_pool_destroy(iterpool); + } + } + + if (noderev->data_rep && noderev->data_rep->revision == rev && + noderev->kind == svn_node_file) + { + SVN_ERR(ensure_representation_sha1(fs, noderev->data_rep, pool)); + SVN_ERR(svn_fs_fs__set_rep_reference(fs, noderev->data_rep, pool)); + } + + if (noderev->prop_rep && noderev->prop_rep->revision == rev) + { + SVN_ERR(ensure_representation_sha1(fs, noderev->prop_rep, pool)); + SVN_ERR(svn_fs_fs__set_rep_reference(fs, noderev->prop_rep, pool)); + } + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__build_rep_cache(svn_fs_t *fs, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_fs_progress_notify_func_t progress_func, + void *progress_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool) +{ + fs_fs_data_t *ffd = fs->fsap_data; + apr_pool_t *iterpool; + svn_revnum_t rev; + + if (!ffd->rep_sharing_allowed) + { + return SVN_NO_ERROR; + } + + if (start_rev == SVN_INVALID_REVNUM) + start_rev = 1; + + if (end_rev == SVN_INVALID_REVNUM) + SVN_ERR(svn_fs_fs__youngest_rev(&end_rev, fs, pool)); + + /* Do nothing for empty FS. */ + if (start_rev > end_rev) + { + return SVN_NO_ERROR; + } + + if (!ffd->rep_cache_db) + SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool)); + + iterpool = svn_pool_create(pool); + /* Do not build rep-cache for revision zero to match + * svn_fs_fs__create() behavior. */ + for (rev = start_rev; rev <= end_rev; rev++) + { + svn_fs_id_t *root_id; + svn_fs_fs__revision_file_t *file; + svn_error_t *err; + + svn_pool_clear(iterpool); + + if (progress_func) + progress_func(rev, progress_baton, iterpool); + + SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&file, fs, rev, + iterpool, iterpool)); + SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, rev, iterpool, iterpool)); + + SVN_ERR(svn_sqlite__begin_transaction(ffd->rep_cache_db)); + err = reindex_node(fs, root_id, rev, file, cancel_func, cancel_baton, iterpool); + SVN_ERR(svn_sqlite__finish_transaction(ffd->rep_cache_db, err)); + + SVN_ERR(svn_fs_fs__close_revision_file(file)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} Index: subversion/libsvn_fs_fs/fs_fs.h =================================================================== --- subversion/libsvn_fs_fs/fs_fs.h (revision 1875261) +++ subversion/libsvn_fs_fs/fs_fs.h (working copy) @@ -356,4 +356,23 @@ svn_revnum_t revision, apr_pool_t *scratch_pool); +/* Add missing entries to the rep-cache on the filesystem FS. Process data + * in revisions START_REV through END_REV inclusive. If START_REV is + * SVN_INVALID_REVNUM, start at revision 1; if END_REV is SVN_INVALID_REVNUM, + * end at the head revision. If the rep-cache does not exist, then create it. + * + * Indicate progress via the optional PROGRESS_FUNC callback using + * PROGRESS_BATON. The optional CANCEL_FUNC will periodically be called with + * CANCEL_BATON to allow cancellation. Use POOL for temporary allocations. + */ +svn_error_t * +svn_fs_fs__build_rep_cache(svn_fs_t *fs, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + svn_fs_progress_notify_func_t progress_func, + void *progress_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *pool); + #endif Index: subversion/svnadmin/svnadmin.c =================================================================== --- subversion/svnadmin/svnadmin.c (revision 1875261) +++ subversion/svnadmin/svnadmin.c (working copy) @@ -97,6 +97,7 @@ /** Subcommands. **/ static svn_opt_subcommand_t + subcommand_build_repcache, subcommand_crashtest, subcommand_create, subcommand_delrevprop, @@ -306,6 +307,16 @@ */ static const svn_opt_subcommand_desc3_t cmd_table[] = { + {"build-repcache", subcommand_build_repcache, {0}, {N_( + "usage: svnadmin build-repcache REPOS_PATH [-r LOWER[:UPPER]]\n" + "\n") N_( + "Add missing entries to the representation cache for the repository\n" + "at REPOS_PATH. Process data in revisions LOWER through UPPER.\n" + "If no revision arguments are given, process all revisions. If only\n" + "LOWER revision argument is given, process only that single revision.\n" + )}, + {'r', 'q', 'M'} }, + {"crashtest", subcommand_crashtest, {0}, {N_( "usage: svnadmin crashtest REPOS_PATH\n" "\n"), N_( @@ -2923,6 +2934,98 @@ return SVN_NO_ERROR; } +static void +build_rep_cache_progress_func(svn_revnum_t revision, + void *baton, + apr_pool_t *pool) +{ + svn_error_clear(svn_cmdline_printf(pool, + _("* Processed revision %ld.\n"), + revision)); +} + +static svn_error_t * +build_rep_cache(svn_fs_t *fs, + svn_revnum_t start_rev, + svn_revnum_t end_rev, + struct svnadmin_opt_state *opt_state, + apr_pool_t *pool) +{ + svn_fs_fs__ioctl_build_rep_cache_input_t input = {0}; + svn_error_t *err; + + input.start_rev = start_rev; + input.end_rev = end_rev; + + if (opt_state->quiet) + { + input.progress_func = NULL; + input.progress_baton = NULL; + } + else + { + input.progress_func = build_rep_cache_progress_func; + input.progress_baton = NULL; + } + + err = svn_fs_ioctl(fs, SVN_FS_FS__IOCTL_BUILD_REP_CACHE, + &input, NULL, + check_cancel, NULL, pool, pool); + if (err && err->apr_err == SVN_ERR_FS_UNRECOGNIZED_IOCTL_CODE) + { + return svn_error_quick_wrapf(err, + _("Building rep-cache is not implemented " + "for the filesystem type found in '%s'"), + svn_fs_path(fs, pool)); + } + SVN_ERR(err); + + return SVN_NO_ERROR; +} + +/* This implements `svn_opt_subcommand_t'. */ +static svn_error_t * +subcommand_build_repcache(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; + svn_revnum_t youngest; + svn_revnum_t lower; + svn_revnum_t upper; + + /* Expect no more arguments. */ + SVN_ERR(parse_args(NULL, os, 0, 0, pool)); + + SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, pool)); + fs = svn_repos_fs(repos); + SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); + + SVN_ERR(get_revnum(&lower, &opt_state->start_revision, + youngest, repos, pool)); + SVN_ERR(get_revnum(&upper, &opt_state->end_revision, + youngest, repos, pool)); + + if (SVN_IS_VALID_REVNUM(lower) && SVN_IS_VALID_REVNUM(upper)) + { + if (lower > upper) + return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, + _("First revision cannot be higher than second")); + } + else if (SVN_IS_VALID_REVNUM(lower)) + { + upper = lower; + } + else + { + upper = youngest; + } + + SVN_ERR(build_rep_cache(fs, lower, upper, opt_state, pool)); + + return SVN_NO_ERROR; +} + /** Main. **/ Index: subversion/tests/cmdline/svnadmin_tests.py =================================================================== --- subversion/tests/cmdline/svnadmin_tests.py (revision 1875261) +++ subversion/tests/cmdline/svnadmin_tests.py (working copy) @@ -4036,7 +4036,28 @@ if output != ['\n', '\n']: raise svntest.Failure("Unexpected property value %s" % output) +@SkipUnless(svntest.main.is_fs_type_fsfs) +@SkipUnless(svntest.main.fs_has_rep_sharing) +def build_repcache(sbox): + "svnadmin build-repcache" + sbox.build(create_wc = False) + + # Remember and remove the existing rep-cache. + rep_cache = read_rep_cache(sbox.repo_dir) + rep_cache_path = os.path.join(sbox.repo_dir, 'db', 'rep-cache.db') + os.remove(rep_cache_path) + + # Build a new rep-cache and compare with the original one. + expected_output = ["* Processed revision 1.\n"] + svntest.actions.run_and_verify_svnadmin(expected_output, [], + "build-repcache", sbox.repo_dir) + + new_rep_cache = read_rep_cache(sbox.repo_dir) + if new_rep_cache != rep_cache: + raise svntest.Failure + + ######################################################################## # Run the tests @@ -4116,6 +4137,7 @@ recover_prunes_rep_cache_when_disabled, dump_include_copied_directory, load_normalize_node_props, + build_repcache, ] if __name__ == '__main__': Index: subversion/tests/libsvn_fs_fs/fs-fs-private-test.c =================================================================== --- subversion/tests/libsvn_fs_fs/fs-fs-private-test.c (revision 1875261) +++ subversion/tests/libsvn_fs_fs/fs-fs-private-test.c (working copy) @@ -35,6 +35,8 @@ #include "private/svn_subr_private.h" #include "../../libsvn_fs_fs/index.h" +#include "../../libsvn_fs_fs/rep-cache.h" +#include "../../libsvn_fs/fs-loader.h" #include "../svn_test_fs.h" @@ -440,7 +442,64 @@ #undef REPO_NAME +/* ------------------------------------------------------------------------ */ +static svn_error_t * +build_rep_cache(const svn_test_opts_t *opts, apr_pool_t *pool) +{ + svn_fs_t *fs; + fs_fs_data_t *ffd; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + svn_revnum_t rev; + svn_boolean_t exists; + const char *fs_path; + svn_fs_fs__ioctl_build_rep_cache_input_t input = {0}; + + /* Bail (with success) on known-untestable scenarios */ + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "this will test FSFS repositories only"); + + if (opts->server_minor_version && (opts->server_minor_version < 6)) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "pre-1.6 SVN doesn't support FSFS rep-sharing"); + + /* Create a filesystem and explicitly disable rep-sharing. */ + fs_path = "test-repo-build-rep-cache-test"; + SVN_ERR(svn_test__create_fs2(&fs, fs_path, opts, NULL, pool)); + ffd = fs->fsap_data; + ffd->rep_sharing_allowed = FALSE; + + /* Add the Greek tree. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev)); + + /* Make sure the rep-cache does not exist. */ + SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool)); + SVN_TEST_ASSERT(!exists); + + /* Build and verify the rep-cache. */ + ffd->rep_sharing_allowed = TRUE; + + input.start_rev = rev; + input.end_rev = rev; + SVN_ERR(svn_fs_ioctl(fs, SVN_FS_FS__IOCTL_BUILD_REP_CACHE, + &input, NULL, NULL, NULL, pool, pool)); + + SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool)); + SVN_TEST_ASSERT(exists); + + SVN_ERR(svn_fs_verify(fs_path, NULL, 0, SVN_INVALID_REVNUM, + NULL, NULL, NULL, NULL, pool)); + + return SVN_NO_ERROR; +} + + /* The test table. */ @@ -455,6 +514,8 @@ "dump the P2L index"), SVN_TEST_OPTS_PASS(load_index, "load the P2L index"), + SVN_TEST_OPTS_PASS(build_rep_cache, + "build the representation cache"), SVN_TEST_NULL }; Index: tools/client-side/bash_completion =================================================================== --- tools/client-side/bash_completion (revision 1875261) +++ tools/client-side/bash_completion (working copy) @@ -1137,7 +1137,7 @@ cur=${COMP_WORDS[COMP_CWORD]} # Possible expansions, without pure-prefix abbreviations such as "h". - cmds='crashtest create delrevprop deltify dump dump-revprops freeze \ + cmds='build-repcache crashtest create delrevprop deltify dump dump-revprops freeze \ help hotcopy info list-dblogs list-unused-dblogs \ load load-revprops lock lslocks lstxns pack recover rev-size rmlocks \ rmtxns setlog setrevprop setuuid unlock upgrade verify --version' @@ -1163,6 +1163,9 @@ cmdOpts= case ${COMP_WORDS[1]} in + build-repcache) + cmdOpts="-r --revision -q --quiet -M --memory-cache-size" + ;; create) cmdOpts="--bdb-txn-nosync --bdb-log-keep --config-dir \ --fs-type --compatible-version"