Index: subversion/libsvn_fs_fs/hotcopy.c =================================================================== --- subversion/libsvn_fs_fs/hotcopy.c (revision 1564943) +++ subversion/libsvn_fs_fs/hotcopy.c (working copy) @@ -382,12 +382,6 @@ hotcopy_update_current(svn_revnum_t *dst_youngest, /* If necessary, get new current next_node and next_copy IDs. */ if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT) { - /* Make sure NEW_YOUNGEST is a valid revision in DST_FS. - Because a crash in hotcopy requires at least a full recovery run, - it is safe to temporarily reset the max IDs to 0. */ - SVN_ERR(svn_fs_fs__write_current(dst_fs, new_youngest, next_node_id, - next_copy_id, scratch_pool)); - SVN_ERR(svn_fs_fs__find_max_ids(dst_fs, new_youngest, &next_node_id, &next_copy_id, scratch_pool)); Index: subversion/libsvn_fs_fs/recovery.c =================================================================== --- subversion/libsvn_fs_fs/recovery.c (revision 1564943) +++ subversion/libsvn_fs_fs/recovery.c (working copy) @@ -275,6 +275,41 @@ recover_find_max_ids(svn_fs_t *fs, return SVN_NO_ERROR; } +/* Part of the recovery procedure. Given an open non-packed revision file + REV_FILE for REV, locate the trailer that specifies the offset to the root + node-id and store this offset in *ROOT_OFFSET. Do temporary allocations in + POOL. */ +static svn_error_t * +recover_get_root_offset(apr_off_t *root_offset, + svn_revnum_t rev, + svn_fs_fs__revision_file_t *rev_file, + apr_pool_t *pool) +{ + svn_stringbuf_t *trailer = svn_stringbuf_create_ensure(64, pool); + apr_off_t offset = 0; + + SVN_ERR_ASSERT(!rev_file->is_packed); + + /* We will assume that the last line containing the two offsets (to the root + node-id and to the changed path information) will never be longer than 64 + characters. */ + SVN_ERR(svn_io_file_seek(rev_file->file, APR_END, &offset, pool)); + + trailer->len = trailer->blocksize-1; + offset = offset - trailer->len; + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, pool)); + + /* Read in this last block, from which we will identify the last line. */ + SVN_ERR(svn_io_file_read(rev_file->file, trailer->data, + &trailer->len, pool)); + trailer->data[trailer->len] = 0; + + /* Parse the last line. */ + SVN_ERR(svn_fs_fs__parse_revision_trailer(root_offset, NULL, trailer, rev)); + + return SVN_NO_ERROR; +} + svn_error_t * svn_fs_fs__find_max_ids(svn_fs_t *fs, svn_revnum_t youngest, @@ -285,16 +320,12 @@ svn_fs_fs__find_max_ids(svn_fs_t *fs, fs_fs_data_t *ffd = fs->fsap_data; apr_off_t root_offset; svn_fs_fs__revision_file_t *rev_file; - svn_fs_id_t *root_id; /* call this function for old repo formats only */ SVN_ERR_ASSERT(ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT); - SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, youngest, pool)); SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, youngest, pool)); - SVN_ERR(svn_fs_fs__item_offset(&root_offset, fs, rev_file, youngest, NULL, - svn_fs_fs__id_item(root_id), pool)); - + SVN_ERR(recover_get_root_offset(&root_offset, youngest, rev_file, pool)); SVN_ERR(recover_find_max_ids(fs, youngest, rev_file, root_offset, max_node_id, max_copy_id, pool)); SVN_ERR(svn_fs_fs__close_revision_file(rev_file)); Index: subversion/tests/cmdline/svnadmin_tests.py =================================================================== --- subversion/tests/cmdline/svnadmin_tests.py (revision 1564943) +++ subversion/tests/cmdline/svnadmin_tests.py (working copy) @@ -828,10 +828,14 @@ _0.0.t1-1 add false false /A/B/E/bravo #---------------------------------------------------------------------- -@SkipUnless(svntest.main.is_fs_type_fsfs) -def recover_fsfs(sbox): - "recover a repository (FSFS only)" - sbox.build() +# Helper for two test functions. +def corrupt_and_recover_db_current(sbox, minor_version=None): + """Build up a MINOR_VERSION sanbox and test different recovery scenarios + with missing, out-of-date or even corrupt db/current files. Recovery should + behave the same way with all values of MINOR_VERSION, hence this helper + containing the common code that allows us to check it.""" + + sbox.build(minor_version=minor_version) current_path = os.path.join(sbox.repo_dir, 'db', 'current') # Commit up to r3, so we can test various recovery scenarios. @@ -906,6 +910,24 @@ _0.0.t1-1 add false false /A/B/E/bravo "Contents of db/current is unexpected.", 'db/current', expected_current_contents, actual_current_contents) + +@SkipUnless(svntest.main.is_fs_type_fsfs) +def fsfs_recover_db_current(sbox): + "fsfs recover db/current" + corrupt_and_recover_db_current(sbox) + + +@SkipUnless(svntest.main.is_fs_type_fsfs) +def fsfs_recover_old_db_current(sbox): + "fsfs recover db/current --compatible-version=1.3" + + # Around trunk@1564943, 'svnadmin recover' wrongly errored out + # for the --compatible-version=1.3 repositories with missing or + # invalid db/current file: + # svnadmin: E160006: No such revision 1 + + corrupt_and_recover_db_current(sbox, minor_version=3) + #---------------------------------------------------------------------- @Issue(2983) def load_with_parent_dir(sbox): @@ -2291,7 +2313,8 @@ test_list = [ None, setrevprop, verify_windows_paths_in_repos, verify_incremental_fsfs, - recover_fsfs, + fsfs_recover_db_current, + fsfs_recover_old_db_current, load_with_parent_dir, set_uuid, reflect_dropped_renumbered_revs,