# # # add_dir "tests/db_check_branch_leaves" # # add_file "tests/db_check_branch_leaves/__driver__.lua" # content [283652fe9b2ccf7d834f74e28aed18efb29ccb59] # # patch "database.cc" # from [026b0f32727c3e31be115a47a4b8de251078151a] # to [25a7d09b691fa29743249d4151dc27bab18c7923] # # patch "database.hh" # from [c7ae2340052b1205f3615f416adb1c58917abfd5] # to [654c356dcdbe48064357eaa66fb6e7c9b5a9d9b5] # # patch "database_check.cc" # from [55d80d4456c96b241ceabd77be9872cfb7285ab6] # to [eb2f10d33d8a4b345982a09ccb29a3d4235c06ab] # # patch "migrate_ancestry.cc" # from [87b81a3b30c9299f5919f8f89b154843a7171989] # to [790cf2e18a4653af131bdb350e9c3ee4e223e294] # # patch "monotone.texi" # from [c3207d84cdb530e1ef24a756ae2ddcc5c3bd0a24] # to [e5901e819adff1d28d8e6476ea33af0b89417047] # ============================================================ --- tests/db_check_branch_leaves/__driver__.lua 283652fe9b2ccf7d834f74e28aed18efb29ccb59 +++ tests/db_check_branch_leaves/__driver__.lua 283652fe9b2ccf7d834f74e28aed18efb29ccb59 @@ -0,0 +1,67 @@ +-- db check checks branch_leaves table + +-- a bug in mtn 0.46 caused extra entries to be left in the +-- branch_leaves table; this shows that 'db check' identifies that, +-- and 'regenerate_caches' fixes it. + +mtn_setup() + +-- create two heads on testbranch, one head on otherbranch +addfile("file1", "rev_A") +commit("testbranch", "rev_A") +base = base_revision() + +writefile("file1", "rev_a") +commit("testbranch", "rev_a") +rev_a = base_revision() + +revert_to(base) + +addfile("file2", "rev_b") +commit("testbranch", "rev_b") +rev_b = base_revision() + +writefile("file2", "rev_c") +commit("otherbranch", "rev_c") +rev_c = base_revision() + +-- db should be ok +check(mtn("db", "check"), 0, false, false) + +-- branch names are stored as binary blobs in the database; +-- these values are from sqlite3 test.db .dump branch_leaves. +testbranch_name = "X'746573746272616E6368'" +otherbranch_name = "X'6F746865726272616E6368'" + +-- add an extra head on 'testbranch' in branch_leaves +check(mtn("db", "execute", "INSERT INTO branch_leaves (branch, revision_id) VALUES (" .. testbranch_name .. ", x'" .. base .. "');"), false, false) + +-- delete 'otherbranch' from branch_leaves +check(mtn("db", "execute", "DELETE FROM branch_leaves WHERE branch=" .. otherbranch_name .. ";"), false, false) + +-- add an extra branch in branch_leaves; it doesn't matter that the +-- name is not actually binary +check(mtn("db", "execute", "INSERT INTO branch_leaves (branch, revision_id) VALUES ('bogusbranch', x'" .. base .. "');"), false, false) + +-- db is not ok +check(mtn("db", "check"), 1, false, true) +check(qgrep("cached branch 'bogusbranch' not used", "stderr")) +check(qgrep("branch 'otherbranch' not cached", "stderr")) +check(qgrep("branch 'testbranch' wrong head count", "stderr")) + +-- fix it +check(mtn("db", "regenerate_caches"), 0, false, false) + +check(mtn("db", "check"), 0, false, false) + +-- double check +check(mtn("automate", "heads", "testbranch"), 0, true, false) +check(readfile("stdout") == rev_a .. "\n" .. rev_b .. "\n") + +check(mtn("automate", "heads", "otherbranch"), 0, true, false) +check(readfile("stdout") == rev_c .. "\n") + +check(mtn("automate", "heads", "bogusbranch"), 0, true, false) +check(readfile("stdout") == "") + +-- end of file ============================================================ --- database.cc 026b0f32727c3e31be115a47a4b8de251078151a +++ database.cc 25a7d09b691fa29743249d4151dc27bab18c7923 @@ -1,3 +1,4 @@ +// Copyright (C) 2010 Stephen Leake // Copyright (C) 2002 Graydon Hoare // // This program is made available under the GNU GPL version 2.0 or @@ -2995,6 +2996,12 @@ database::delete_existing_heights() imp->execute(query("DELETE FROM heights")); } +void +database::delete_existing_branch_leaves() +{ + imp->execute(query("DELETE FROM branch_leaves")); +} + /// Deletes one revision from the local database. /// @see kill_rev_locally void @@ -3039,16 +3046,23 @@ void } void -database::recalc_branch_leaves(cert_value const & name) +database::compute_branch_leaves(cert_value const & branch_name, set & revs) { - imp->execute(query("DELETE FROM branch_leaves WHERE branch = ?") % blob(name())); + imp->execute(query("DELETE FROM branch_leaves WHERE branch = ?") % blob(branch_name())); + get_revisions_with_cert(cert_name("branch"), branch_name, revs); + erase_ancestors(*this, revs); +} + +void +database::recalc_branch_leaves(cert_value const & branch_name) +{ + imp->execute(query("DELETE FROM branch_leaves WHERE branch = ?") % blob(branch_name())); set revs; - get_revisions_with_cert(cert_name("branch"), name, revs); - erase_ancestors(*this, revs); + compute_branch_leaves(branch_name, revs); for (set::const_iterator i = revs.begin(); i != revs.end(); ++i) { imp->execute(query("INSERT INTO branch_leaves (branch, revision_id) " - "VALUES (?, ?)") % blob(name()) % blob((*i).inner()())); + "VALUES (?, ?)") % blob(branch_name()) % blob((*i).inner()())); } } @@ -3608,6 +3622,14 @@ database::record_as_branch_leaf(cert_val } } + // This check is needed for this case: + // + // r1 (branch1) + // | + // r2 (branch2) + // | + // r3 (branch1) + if (!all_parents_were_leaves) { for (set::const_iterator r = current_leaves.begin(); ============================================================ --- database.hh c7ae2340052b1205f3615f416adb1c58917abfd5 +++ database.hh 654c356dcdbe48064357eaa66fb6e7c9b5a9d9b5 @@ -1,3 +1,4 @@ +// Copyright (C) 2010 Stephen Leake // Copyright (C) 2002 Graydon Hoare // // This program is made available under the GNU GPL version 2.0 or @@ -312,9 +313,10 @@ public: outdated_indicator get_branch_leaves(cert_value const & value, std::set & revisions); -private: - void recalc_branch_leaves(cert_value const & value); -public: + // used by check_db, regenerate_caches + void compute_branch_leaves(cert_value const & branch_name, std::set & revs); + void recalc_branch_leaves(cert_value const & branch_name); + void delete_existing_branch_leaves(); // Used through project.cc outdated_indicator get_revision_certs(revision_id const & ident, ============================================================ --- database_check.cc 55d80d4456c96b241ceabd77be9872cfb7285ab6 +++ database_check.cc eb2f10d33d8a4b345982a09ccb29a3d4235c06ab @@ -1,3 +1,4 @@ +// Copyright (C) 2010 Stephen Leake // Copyright (C) 2005 Derek Scherger // // This program is made available under the GNU GPL version 2.0 or @@ -133,6 +134,14 @@ struct checked_height { checked_height(): found(false), unique(false), sensible(true) {} }; +struct checked_branch { + bool used; + bool heads_ok; + bool cached; + + checked_branch(): used(false), heads_ok(false), cached(false) {} +}; + /* * check integrity of the SQLite database */ @@ -597,6 +606,62 @@ static void } static void +check_branch_leaves(database & db, map & checked_branches) +{ + // We don't assume db.get_branches is right, because that uses + // branch_leaves, and we are checking to see if branch_leaves is ok. + + vector all_branch_certs; + set seen_branches; + vector cached_branches; + + db.get_branches (cached_branches); + + L(FL("checking %d branches") % cached_branches.size()); + + db.get_revision_certs(branch_cert_name, all_branch_certs); + + // we assume cached_branches is close enough for the ticker. + ticker ticks(_("branches"), "b", cached_branches.size()); + + for (vector::const_iterator i = all_branch_certs.begin(); i != all_branch_certs.end(); ++i) + { + string const name = i->value(); + + std::pair::iterator, bool> inserted = seen_branches.insert(name); + + if (inserted.second) + { + checked_branches[name].used = true; + + checked_branches[name].cached = + find(cached_branches.begin(), cached_branches.end(), name) != cached_branches.end(); + + set cached_leaves; + set computed_leaves; + + db.get_branch_leaves(i->value, cached_leaves); + db.compute_branch_leaves(i->value, computed_leaves); + + checked_branches[name].heads_ok = cached_leaves == computed_leaves; + ++ticks; + } + } + + for (vector::const_iterator i = cached_branches.begin(); i != cached_branches.end(); ++i) + { + string const name = *i; + + if (seen_branches.find(name) == seen_branches.end()) + { + checked_branches[name].used = false; + checked_branches[name].cached = true; + checked_branches[name].heads_ok = false; + } + } +} + +static void report_files(map const & checked_files, size_t & missing_files, size_t & unreferenced_files) @@ -893,6 +958,32 @@ report_heights(map const & checked_branches, + size_t & extra_branches, + size_t & bad_branches, + size_t & missing_branches) +{ + for (map::const_iterator i = checked_branches.begin(); i != checked_branches.end(); ++i) + { + if (!i->second.used) + { + extra_branches++; + P(F("cached branch '%s' not used") % i->first); + } + else if (!i->second.cached) + { + missing_branches++; + P(F("branch '%s' not cached") % i->first); + } + else if (!i->second.heads_ok) + { + bad_branches ++; + P(F("branch '%s' wrong head count") % i->first); + } + } +} + void check_db(database & db) { @@ -902,6 +993,7 @@ check_db(database & db) map checked_revisions; map checked_keys; map checked_heights; + map checked_branches; size_t missing_files = 0; size_t unreferenced_files = 0; @@ -931,6 +1023,10 @@ check_db(database & db) size_t duplicate_heights = 0; size_t incorrect_heights = 0; + size_t extra_branches = 0; + size_t bad_branches = 0; + size_t missing_branches = 0; + transaction_guard guard(db, false); check_db_integrity_check(db); @@ -945,6 +1041,7 @@ check_db(database & db) check_certs(db, checked_revisions, checked_keys, total_certs); check_heights(db, checked_heights); check_heights_relation(db, checked_heights); + check_branch_leaves(db, checked_branches); report_files(checked_files, missing_files, unreferenced_files); @@ -968,8 +1065,10 @@ check_db(database & db) report_heights(checked_heights, missing_heights, duplicate_heights, incorrect_heights); + report_branches(checked_branches, extra_branches, bad_branches, missing_branches); + // NOTE: any new sorts of problems need to have added: - // -- a message here, that tells the use about them + // -- a message here, that tells the user about them // -- entries in one _or both_ of the sums calculated at the end // -- an entry added to the manual, which describes in detail why the // error occurs and what it means to the user @@ -1024,6 +1123,13 @@ check_db(database & db) if (incorrect_heights > 0) W(F("%d incorrect heights") % incorrect_heights); + if (extra_branches > 0) + W(F("%d branches cached but not used") % extra_branches); + if (bad_branches > 0) + W(F("%d branches with incorrect head count") % bad_branches); + if (missing_branches > 0) + W(F("%d branches missing from branch cache") % missing_branches); + size_t total = missing_files + unreferenced_files + unreferenced_rosters + incomplete_rosters + missing_revisions + incomplete_revisions + @@ -1034,7 +1140,9 @@ check_db(database & db) missing_certs + mismatched_certs + unchecked_sigs + bad_sigs + missing_keys + - missing_heights + duplicate_heights + incorrect_heights; + missing_heights + duplicate_heights + incorrect_heights + + extra_branches + bad_branches + missing_branches; + // unreferenced files and rosters and mismatched certs are not actually // serious errors; odd, but nothing will break. size_t serious = missing_files + @@ -1046,15 +1154,17 @@ check_db(database & db) missing_certs + unchecked_sigs + bad_sigs + missing_keys + - missing_heights + duplicate_heights + incorrect_heights; + missing_heights + duplicate_heights + incorrect_heights+ + extra_branches + bad_branches + missing_branches; - P(F("check complete: %d files; %d rosters; %d revisions; %d keys; %d certs; %d heights") + P(F("check complete: %d files; %d rosters; %d revisions; %d keys; %d certs; %d heights; %d branches") % checked_files.size() % checked_rosters.size() % checked_revisions.size() % checked_keys.size() % total_certs - % checked_heights.size()); + % checked_heights.size() + % checked_branches.size()); P(F("total problems detected: %d (%d serious)") % total % serious); if (serious) { ============================================================ --- migrate_ancestry.cc 87b81a3b30c9299f5919f8f89b154843a7171989 +++ migrate_ancestry.cc 790cf2e18a4653af131bdb350e9c3ee4e223e294 @@ -1,3 +1,4 @@ +// Copyright (C) 2010 Stephen Leake // Copyright (C) 2004 Graydon Hoare // // This program is made available under the GNU GPL version 2.0 or @@ -986,31 +987,58 @@ regenerate_caches(database & db) db.ensure_open_for_cache_reset(); - transaction_guard guard(db); + { + transaction_guard guard(db); - db.delete_existing_rosters(); - db.delete_existing_heights(); + db.delete_existing_rosters(); + db.delete_existing_heights(); - vector sorted_ids; - allrevs_toposorted(db, sorted_ids); + vector sorted_ids; + allrevs_toposorted(db, sorted_ids); - ticker done(_("regenerated"), "r", 5); - done.set_total(sorted_ids.size()); + ticker done(_("regenerated"), "r", 5); + done.set_total(sorted_ids.size()); - for (std::vector::const_iterator i = sorted_ids.begin(); - i != sorted_ids.end(); ++i) - { - revision_t rev; - revision_id const & rev_id = *i; - db.get_revision(rev_id, rev); - db.put_roster_for_revision(rev_id, rev); - db.put_height_for_revision(rev_id, rev); - ++done; - } + for (std::vector::const_iterator i = sorted_ids.begin(); + i != sorted_ids.end(); ++i) + { + revision_t rev; + revision_id const & rev_id = *i; + db.get_revision(rev_id, rev); + db.put_roster_for_revision(rev_id, rev); + db.put_height_for_revision(rev_id, rev); + ++done; + } - guard.commit(); + guard.commit(); + } P(F("finished regenerating cached rosters and heights")); + + P(F("regenerating cached branches")); + { + transaction_guard guard(db); + + db.delete_existing_branch_leaves(); + + vector all_branch_certs; + db.get_revision_certs(branch_cert_name, all_branch_certs); + set seen_branches; + for (vector::const_iterator i = all_branch_certs.begin(); i != all_branch_certs.end(); ++i) + { + string const name = i->value(); + + std::pair::iterator, bool> inserted = seen_branches.insert(name); + + if (inserted.second) + { + db.recalc_branch_leaves (i->value); + } + } + guard.commit(); + } + P(F("finished regenerating cached branches")); + } // Local Variables: ============================================================ --- monotone.texi c3207d84cdb530e1ef24a756ae2ddcc5c3bd0a24 +++ monotone.texi e5901e819adff1d28d8e6476ea33af0b89417047 @@ -6384,6 +6384,12 @@ @section Database using @command{pubkey} on that database to create a public key packet, which can be loaded into your database with @command{read}. address@hidden branches not used or with incorrect head count or missing +in the cache of branch heads. This is a serious problem; it suggests +the presence of a bug in monotone's caching system, but does not +involve data loss; please report this on the monotone mailing +list. You can fix it by running @command{db regenerate_caches}. + @end itemize This command also verifies that the @sc{sha1} hash of every file, manifest,