Author: julianfoad Date: Fri Aug 24 10:48:37 2018 New Revision: 1838813 URL: http://svn.apache.org/viewvc?rev=1838813&view=rev Log: Let 'svnadmin recover' prune the rep-cache even if it is disabled.
Part of issue #4077: "FSFS recover should prune unborn revisions from rep-cache.db". Also add tests for both cases (enabled, disabled). Pruning the rep-cache even if disabled was included as r1213716 in the original fix for issue #4077, first released in Subversion 1.7.3, but was reverted in r1367674 (issue #4214, recovery should not create rep-cache.db) and so omitted from Subversion 1.8 and later series of releases. * subversion/libsvn_fs_fs/recovery.c (recover_body): Prune the rep-cache no matter whether it's in use. * subversion/tests/cmdline/svnadmin_tests.py (read_rep_cache, check_recover_prunes_rep_cache): New functions. (recover_prunes_rep_cache_when_enabled, recover_prunes_rep_cache_when_disabled): New tests. (test_list): Run them. Modified: subversion/trunk/subversion/libsvn_fs_fs/recovery.c subversion/trunk/subversion/tests/cmdline/svnadmin_tests.py Modified: subversion/trunk/subversion/libsvn_fs_fs/recovery.c URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/recovery.c?rev=1838813&r1=1838812&r2=1838813&view=diff ============================================================================== --- subversion/trunk/subversion/libsvn_fs_fs/recovery.c (original) +++ subversion/trunk/subversion/libsvn_fs_fs/recovery.c Fri Aug 24 10:48:37 2018 @@ -471,9 +471,15 @@ recover_body(void *baton, apr_pool_t *po } /* Prune younger-than-(newfound-youngest) revisions from the rep - cache if sharing is enabled taking care not to create the cache - if it does not exist. */ - if (ffd->rep_sharing_allowed) + cache, taking care not to create the cache if it does not exist. + + We do this whenever rep-cache.db exists, whether it's currently enabled + or not, to prevent a data loss that could result from having revisions + created after this 'recover' operation referring to rep-cache.db rows + that were created before the recover and that point to revisions younger- + than-(newfound-youngest). + */ + if (ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT) { svn_boolean_t rep_cache_exists; Modified: subversion/trunk/subversion/tests/cmdline/svnadmin_tests.py URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/svnadmin_tests.py?rev=1838813&r1=1838812&r2=1838813&view=diff ============================================================================== --- subversion/trunk/subversion/tests/cmdline/svnadmin_tests.py (original) +++ subversion/trunk/subversion/tests/cmdline/svnadmin_tests.py Fri Aug 24 10:48:37 2018 @@ -53,6 +53,23 @@ Wimp = svntest.testcase.Wimp_deco SkipDumpLoadCrossCheck = svntest.testcase.SkipDumpLoadCrossCheck_deco Item = svntest.wc.StateItem +def read_rep_cache(repo_dir): + """Return the rep-cache contents as a dict {hash: (rev, index, ...)}. + """ + db_path = os.path.join(repo_dir, 'db', 'rep-cache.db') + db1 = svntest.sqlite3.connect(db_path) + schema1 = db1.execute("pragma user_version").fetchone()[0] + # Can't test newer rep-cache schemas with an old built-in SQLite. + if schema1 >= 2 and svntest.sqlite3.sqlite_version_info < (3, 8, 2): + raise svntest.Failure("Can't read rep-cache schema %d using old " + "Python-SQLite version %s < (3,8,2)" % + (schema1, + svntest.sqlite3.sqlite_version_info)) + + content = { row[0]: row[1:] for row in + db1.execute("select * from rep_cache") } + return content + def check_hotcopy_bdb(src, dst): "Verify that the SRC BDB repository has been correctly copied to DST." ### TODO: This function should be extended to verify all hotcopied files, @@ -3843,6 +3860,57 @@ def dump_no_canonicalize_svndate(sbox): dump_lines = svntest.actions.run_and_verify_dump(sbox.repo_dir) assert propval + '\n' in dump_lines +def check_recover_prunes_rep_cache(sbox, enable_rep_sharing): + """Check 'recover' prunes the rep-cache while enable-rep-sharing is + true/false. + """ + # Remember the initial rep cache content. + rep_cache_r1 = read_rep_cache(sbox.repo_dir) + #print '\n'.join([h + ": " + repr(ref) for h, ref in rep_cache_r1.items()]) + + # Commit one new rep and check the rep-cache is extended. + sbox.simple_append('iota', 'New line.\n') + sbox.simple_commit() + rep_cache_r2 = read_rep_cache(sbox.repo_dir) + assert len(rep_cache_r2) == len(rep_cache_r1) + 1 + + # To test 'recover' while rep-sharing is disabled, disable it now. + if not enable_rep_sharing: + fsfs_conf = svntest.main.get_fsfs_conf_file_path(sbox.repo_dir) + svntest.main.file_append(fsfs_conf, + "[rep-sharing]\n" + "enable-rep-sharing = false\n") + + # Break r2 in such a way that 'recover' will discard it + head_rev_path = fsfs_file(sbox.repo_dir, 'revs', '2') + os.remove(head_rev_path) + current_path = os.path.join(sbox.repo_dir, 'db', 'current') + svntest.main.file_write(current_path, '1\n') + + # Recover back to r1. + svntest.actions.run_and_verify_svnadmin(None, [], + "recover", sbox.repo_dir) + + # Check the rep-cache is pruned. + rep_cache_recovered = read_rep_cache(sbox.repo_dir) + assert rep_cache_recovered == rep_cache_r1 + +@Issue(4077) +@SkipUnless(svntest.main.is_fs_type_fsfs) +def recover_prunes_rep_cache_when_enabled(sbox): + "recover prunes rep cache when enabled" + sbox.build() + + check_recover_prunes_rep_cache(sbox, enable_rep_sharing=True) + +@Issue(4077) +@SkipUnless(svntest.main.is_fs_type_fsfs) +def recover_prunes_rep_cache_when_disabled(sbox): + "recover prunes rep cache when disabled" + sbox.build() + + check_recover_prunes_rep_cache(sbox, enable_rep_sharing=False) + ######################################################################## # Run the tests @@ -3918,6 +3986,8 @@ test_list = [ None, dump_invalid_filtering_option, load_issue4725, dump_no_canonicalize_svndate, + recover_prunes_rep_cache_when_enabled, + recover_prunes_rep_cache_when_disabled, ] if __name__ == '__main__':