# # patch "commands.cc" # from [4ee40cd2380f8cfd4de3f97ae5ce8bdeafe43701] # to [874b7c6e26322e3dcfc8d8dc5c53e3f3f2c0bbe1] # # patch "database.cc" # from [bdc5880f4379402c007c62531f33104da5e87d09] # to [c8045a26672bb50696bcd9d12748f37399a52768] # # patch "database.hh" # from [0103ae7924020613ab0eef601d6ca4930df8d9d4] # to [ec17217f4b392f21635c1bb15487d7b1c0146856] # # patch "database_check.cc" # from [ef6fa8b0b5acf5297d64f4a16e8646f68dec40da] # to [7850476d369646786127fab5036b287d71e91a9c] # # patch "schema.sql" # from [ed4848cf3241d4de48ab8630736d04e04a269abd] # to [f44144278a4158695818d8f7e1901ac6f89e39bb] # # patch "schema_migration.cc" # from [fc00e2d80ad173e9ead4121c80a6bbefd453e5db] # to [0e70ff287887b7f412e6f36b6280670c049fc12e] # # patch "tests/t_merge_add_del.at" # from [1f411da2f286911a000fc6ea9d4efa5fb7ba68c4] # to [6e28c3a84a4c148144295fd22ea1648dcc8a6bb0] # ======================================================================== --- commands.cc 4ee40cd2380f8cfd4de3f97ae5ce8bdeafe43701 +++ commands.cc 874b7c6e26322e3dcfc8d8dc5c53e3f3f2c0bbe1 @@ -3441,12 +3441,38 @@ set nodes; + set frontier; + + revision_id first_rid; + if (app.revision_selectors.size() == 0) + { + get_revision_id(first_rid); + frontier.insert(first_rid); + } + else + { + for (std::vector::const_iterator i = app.revision_selectors.begin(); + i != app.revision_selectors.end(); i++) + { + revision_id rid; + complete(app, (*i)(), rid); + frontier.insert(rid); + if (i == app.revision_selectors.begin()) + first_rid = rid; + } + } + if (args.size() > 0) { // User wants to trace only specific files roster_t old_roster, new_roster; revision_set rev; - get_unrestricted_working_revision_and_rosters(app, rev, old_roster, new_roster); + + if (app.revision_selectors.size() == 0) + get_unrestricted_working_revision_and_rosters(app, rev, old_roster, new_roster); + else + app.db.get_roster(first_rid, new_roster); + for (size_t i = 0; i < args.size(); ++i) { file_path fp = file_path_external(idx(args, i)); @@ -3458,24 +3484,7 @@ } } - set frontier; - if (app.revision_selectors.size() == 0) - { - revision_id rid; - get_revision_id(rid); - frontier.insert(rid); - } - else - { - for (std::vector::const_iterator i = app.revision_selectors.begin(); - i != app.revision_selectors.end(); i++) { - revision_id rid; - complete(app, (*i)(), rid); - frontier.insert(rid); - } - } - cert_name author_name(author_cert_name); cert_name date_name(date_cert_name); cert_name branch_name(branch_cert_name); ======================================================================== --- database.cc bdc5880f4379402c007c62531f33104da5e87d09 +++ database.cc c8045a26672bb50696bcd9d12748f37399a52768 @@ -75,7 +75,7 @@ // non-alphabetic ordering of tables in sql source files. we could create // a temporary db, write our intended schema into it, and read it back, // but this seems like it would be too rude. possibly revisit this issue. - schema("9598aecfa8fbd6bb00acf8dc6e42b46d7e2a46a2"), + schema("1db80c7cee8fa966913db1a463ed50bf1b0e5b0e"), __sql(NULL), transaction_level(0) {} @@ -1128,6 +1128,85 @@ } void +database::remove_version(hexenc const & target_id, + string const & data_table, + string const & delta_table) +{ + // We have a one of two cases (for multiple 'older' nodes): + // + // 1. pre: older <- target <- newer + // post: older <- newer + // + // 2. pre: older <- target (a root) + // post: older (a root) + // + // In case 1 we want to build new deltas bypassing the target we're + // removing. In case 2 we just promote the older object to a root. + + transaction_guard guard(*this); + + I(exists(target_id, data_table) + || delta_exists(target_id, delta_table)); + + map, data> older; + + { + results res; + string query = "SELECT id FROM " + delta_table + " WHERE base = ?"; + fetch(res, one_col, any_rows, + query.c_str(), target_id().c_str()); + for (size_t i = 0; i < res.size(); ++i) + { + hexenc old_id(res[i][0]); + data old_data; + get_version(old_id, old_data, data_table, delta_table); + older.insert(make_pair(old_id, old_data)); + } + } + + if (delta_exists(target_id, delta_table)) + { + if (!older.empty()) + { + // Case 1: need to re-deltify all the older values against a newer + // member of the delta chain. Doesn't really matter which newer + // element (we have no good heuristic for guessing a good one + // anyways). + hexenc newer_id; + data newer_data; + results res; + string query = "SELECT base FROM " + delta_table + " WHERE id = ?"; + fetch(res, one_col, any_rows, + query.c_str(), target_id().c_str()); + I(res.size() > 0); + newer_id = hexenc(res[0][0]); + get_version(newer_id, newer_data, data_table, delta_table); + for (map, data>::const_iterator i = older.begin(); + i != older.end(); ++i) + { + delta bypass_delta; + diff(newer_data, i->second, bypass_delta); + put_delta(i->first, newer_id, bypass_delta, delta_table); + } + } + string query = "DELETE from " + delta_table + " WHERE id = ?"; + execute(query.c_str(), target_id().c_str()); + } + else + { + // Case 2: just plop the older values down as new storage roots. + I(exists(target_id, data_table)); + for (map, data>::const_iterator i = older.begin(); + i != older.end(); ++i) + put(i->first, i->second, data_table); + string query = "DELETE from " + data_table + " WHERE id = ?"; + execute(query.c_str(), target_id().c_str()); + } + + guard.commit(); +} + +void database::put_reverse_version(hexenc const & new_id, hexenc const & old_id, delta const & reverse_del, @@ -1163,10 +1242,10 @@ } bool -database::manifest_version_exists(manifest_id const & id) +database::roster_version_exists(hexenc const & id) { - return delta_exists(id.inner(), "manifest_deltas") - || exists(id.inner(), "manifests"); + return delta_exists(id(), "roster_deltas") + || exists(id(), "rosters"); } bool @@ -1176,35 +1255,48 @@ } bool +database::roster_link_exists_for_revision(revision_id const & rev_id) +{ + results res; + string query = ("SELECT roster_id FROM revision_roster WHERE rev_id = ? "); + fetch(res, one_col, any_rows, query.c_str(), + rev_id.inner()().c_str()); + I((res.size() == 1) || (res.size() == 0)); + return res.size() == 1; +} + +bool database::roster_exists_for_revision(revision_id const & rev_id) { results res; - string query = ("SELECT id FROM rosters WHERE rev_id = ? " - "UNION " - "SELECT id FROM roster_deltas WHERE rev_id = ? "); + string query = ("SELECT roster_id FROM revision_roster WHERE rev_id = ? "); fetch(res, one_col, any_rows, query.c_str(), - rev_id.inner()().c_str(), rev_id.inner()().c_str()); + rev_id.inner()().c_str()); I((res.size() == 1) || (res.size() == 0)); - return res.size() == 1; + return (res.size() == 1) && roster_version_exists(hexenc(res[0][0])); } void -database::get_file_ids(set & ids) +database::get_roster_links(std::map > & links) { - ids.clear(); - set< hexenc > tmp; - get_ids("files", tmp); - get_ids("file_deltas", tmp); - ids.insert(tmp.begin(), tmp.end()); + links.clear(); + results res; + string query = ("SELECT rev_id, roster_id FROM revision_roster"); + fetch(res, 2, any_rows, query.c_str()); + for (size_t i = 0; i < res.size(); ++i) + { + links.insert(make_pair(revision_id(res[i][0]), + hexenc(res[i][1]))); + } } void -database::get_manifest_ids(set & ids) +database::get_file_ids(set & ids) { ids.clear(); set< hexenc > tmp; - get_ids("manifests", tmp); - get_ids("manifest_deltas", tmp); + get_ids("files", tmp); + get_ids("file_deltas", tmp); ids.insert(tmp.begin(), tmp.end()); } @@ -1254,7 +1346,6 @@ read_manifest_map(mdat, mm); } - void database::put_file(file_id const & id, file_data const & dat) @@ -1282,32 +1373,6 @@ void -database::put_manifest(manifest_id const & id, - manifest_data const & dat) -{ - put(id.inner(), dat.inner(), "manifests"); -} - -void -database::put_manifest_version(manifest_id const & old_id, - manifest_id const & new_id, - manifest_delta const & del) -{ - put_version(old_id.inner(), new_id.inner(), del.inner(), - "manifests", "manifest_deltas"); -} - -void -database::put_manifest_reverse_version(manifest_id const & new_id, - manifest_id const & old_id, - manifest_delta const & del) -{ - put_reverse_version(new_id.inner(), old_id.inner(), del.inner(), - "manifests", "manifest_deltas"); -} - - -void database::get_revision_ancestry(std::multimap & graph) { results res; @@ -1396,8 +1461,8 @@ transaction_guard guard(*this); revision_set rev; get_revision(rid, rev); - // make sure that all parent revs have their manifests and files - // replaced with deltas from this rev's manifest and files + // Make sure that all parent revs have their files replaced with deltas + // from this rev's files. { for (edge_map::const_iterator i = rev.edges.begin(); i != rev.edges.end(); ++i) @@ -1534,38 +1599,43 @@ void database::delete_existing_rev_and_certs(revision_id const & rid) { + transaction_guard guard (*this); - //check that the revision exists and doesn't have any children + // Check that the revision exists and doesn't have any children. I(revision_exists(rid)); set children; get_revision_children(rid, children); I(!children.size()); + - // perform the actual SQL transactions to kill rev rid here L(F("Killing revision %s locally\n") % rid); + + // Kill the certs, ancestry, and rev itself. execute("DELETE from revision_certs WHERE id = ?",rid.inner()().c_str()); execute("DELETE from revision_ancestry WHERE child = ?", rid.inner()().c_str()); execute("DELETE from revisions WHERE id = ?",rid.inner()().c_str()); - - // rosters + + // Find the associated roster and count the number of links to it hexenc roster_id; + size_t link_count = 0; get_roster_id_for_revision(rid, roster_id); - results res; - fetch(res, 2, any_rows, - "SELECT id, rev_id FROM roster_deltas WHERE base = ?", - roster_id().c_str()); - for (size_t i = 0; i < res.size(); ++i) - { - data dat; - get_roster(res[i][0], dat); - base64 > dat_packed; - pack(dat, dat_packed); - execute("INSERT INTO rosters VALUES(?, ?, ?)", - res[i][0].c_str(), res[i][1].c_str(), dat_packed().c_str()); - } - execute("DELETE from rosters WHERE rev_id = ?",rid.inner()().c_str()); - execute("DELETE from roster_deltas WHERE rev_id = ? or base = ?", - rid.inner()().c_str(), rid.inner()().c_str()); + { + results res; + string query = ("SELECT rev_id, roster_id FROM revision_roster " + "WHERE roster_id = ?"); + fetch(res, 2, any_rows, query.c_str(), roster_id().c_str()); + I(res.size() > 0); + link_count = res.size(); + } + + // Delete our link. + execute("DELETE from revision_roster WHERE rev_id = ?", rid.inner()().c_str()); + + // If that was the last link to the roster, kill the roster too. + if (link_count == 1) + remove_version(roster_id, "rosters", "roster_deltas"); + + guard.commit(); } /// Deletes all certs referring to a particular branch. @@ -2583,12 +2653,8 @@ } results res; - string query = ("SELECT id FROM rosters WHERE rev_id = ? " - "UNION " - "SELECT id FROM roster_deltas WHERE rev_id = ? "); - + string query = ("SELECT roster_id FROM revision_roster WHERE rev_id = ? "); fetch(res, one_col, one_row, query.c_str(), - rev_id.inner()().c_str(), rev_id.inner()().c_str()); roster_id = hexenc(res[0][0]); } @@ -2639,12 +2705,10 @@ { data old_data, new_data; delta reverse_delta; - hexenc ident; - base64 > new_data_packed; + hexenc old_id, new_id; write_roster_and_marking(roster, marks, new_data); - calculate_ident(new_data, ident); - pack(new_data, new_data_packed); + calculate_ident(new_data, new_id); // First: find the "old" revision; if there are multiple old // revisions, we just pick the first. It probably doesn't matter for @@ -2653,52 +2717,44 @@ string data_table = "rosters"; string delta_table = "roster_deltas"; + transaction_guard guard(*this); + + execute("INSERT into revision_roster VALUES (?, ?)", + rev_id.inner()().c_str(), + new_id().c_str()); + + if (exists(new_id, data_table) + || delta_exists(new_id, delta_table)) + { + guard.commit(); + return; + } + + // Else we have a new roster the database hasn't seen yet; our task is to + // add it, and deltify all the incoming edges (if they aren't already). + + put(new_id, new_data, data_table); + std::set parents; get_revision_parents(rev_id, parents); - transaction_guard guard(*this); + // Now do what deltify would do if we bothered (we have the + // roster written now, so might as well do it here). for (std::set::const_iterator i = parents.begin(); i != parents.end(); ++i) { if (null_id(*i)) - continue; - - // There's a parent revision; we are going to do delta - // compression on the roster by using the roster associated with - // the parent rev. Keep in mind that we're composing a *reverse* - // delta here, which goes from new->old. So the row we insert - // will have (id=old_id, rev_id=old_rev, base=new_id, - // delta=new->old) - + continue; revision_id old_rev = *i; - hexenc old_ident; - get_roster_id_for_revision(old_rev, old_ident); - get_version(old_ident, old_data, data_table, delta_table); - diff(new_data, old_data, reverse_delta); - base64 > del_packed; - pack(reverse_delta, del_packed); - - if (exists(old_ident, data_table)) + get_roster_id_for_revision(old_rev, old_id); + if (exists(new_id, data_table)) { - // Descendent of a head version replaces the head, therefore - // old head, if it exists in entirety, must be disposed of. - drop(old_ident, data_table); + get_version(old_id, old_data, data_table, delta_table); + diff(new_data, old_data, reverse_delta); + drop(old_id, data_table); + put_delta(old_id, new_id, reverse_delta, delta_table); } - - // nb: roster_deltas schema is (id, rev_id, base, delta) - string query = "INSERT INTO " + delta_table + " VALUES(?, ?, ?, ?)"; - execute(query.c_str(), - old_ident().c_str(), old_rev.inner()().c_str(), - ident().c_str(), del_packed().c_str()); - } - - // nb: rosters schema is (id, rev_id, data) - string query = "INSERT INTO " + data_table + " VALUES(?, ?, ?)"; - execute(query.c_str(), - ident().c_str(), rev_id.inner()().c_str(), - new_data_packed().c_str()); - guard.commit(); } ======================================================================== --- database.hh 0103ae7924020613ab0eef601d6ca4930df8d9d4 +++ database.hh ec17217f4b392f21635c1bb15487d7b1c0146856 @@ -148,6 +148,9 @@ delta const & del, std::string const & data_table, std::string const & delta_table); + void remove_version(hexenc const & target_id, + std::string const & data_table, + std::string const & delta_table); void put_reverse_version(hexenc const & new_id, hexenc const & old_id, delta const & reverse_del, @@ -197,10 +200,6 @@ hexenc const & new_id, delta const & del, database & db); - friend void rcs_put_raw_manifest_edge(hexenc const & old_id, - hexenc const & new_id, - delta const & del, - database & db); void put_roster(revision_id const & rev_id, roster_t & roster, @@ -227,12 +226,13 @@ bool database_specified(); bool file_version_exists(file_id const & id); - bool manifest_version_exists(manifest_id const & id); + bool roster_version_exists(hexenc const & id); bool revision_exists(revision_id const & id); + bool roster_link_exists_for_revision(revision_id const & id); bool roster_exists_for_revision(revision_id const & id); + void get_roster_links(std::map > & links); void get_file_ids(std::set & ids); - void get_manifest_ids(std::set & ids); void get_revision_ids(std::set & ids); void get_roster_ids(std::set< hexenc > & ids) ; @@ -263,26 +263,9 @@ void get_manifest_version(manifest_id const & id, manifest_data & dat); - // get a constructed manifest void get_manifest(manifest_id const & id, manifest_map & mm); - // put manifest w/o predecessor into db - void put_manifest(manifest_id const & new_id, - manifest_data const & dat); - - // store new version and update old version to be a delta - void put_manifest_version(manifest_id const & old_id, - manifest_id const & new_id, - manifest_delta const & del); - - // load in a "direct" new -> old reverse edge (used during - // netsync and CVS load-in) - void put_manifest_reverse_version(manifest_id const & old_id, - manifest_id const & new_id, - manifest_delta const & del); - - void get_revision_ancestry(std::multimap & graph); void get_revision_parents(revision_id const & id, ======================================================================== --- database_check.cc ef6fa8b0b5acf5297d64f4a16e8646f68dec40da +++ database_check.cc 7850476d369646786127fab5036b287d71e91a9c @@ -23,7 +23,7 @@ // | | // keys revisions // | -// manifests +// rosters // | // files // @@ -88,6 +88,7 @@ size_t ancestry_child_refs; // number of references to this revision by ancestry child size_t marking_refs; // number of references to this revision by roster markings + bool found_roster_link; // the revision->roster link for this revision exists bool found_roster; // the roster for this revision exists bool manifest_mismatch; // manifest doesn't match the roster for this revision bool incomplete_roster; // the roster for this revision is missing files @@ -273,6 +274,37 @@ } } +static void +check_roster_links(app_state & app, + std::map & checked_revisions, + std::map, checked_roster> & checked_rosters, + size_t & unreferenced_roster_links, + size_t & missing_rosters) +{ + unreferenced_roster_links = 0; + + std::map > links; + app.db.get_roster_links(links); + + for (std::map >::const_iterator i = links.begin(); + i != links.end(); ++i) + { + revision_id rev(i->first); + hexenc ros(i->second); + + std::map::const_iterator j + = checked_revisions.find(rev); + if (j == checked_revisions.end() || (!j->second.found)) + ++unreferenced_roster_links; + + std::map, checked_roster>::const_iterator k + = checked_rosters.find(ros); + if (k == checked_rosters.end() || (!k->second.found)) + ++missing_rosters; + } +} + + static void check_revisions(app_state & app, std::map & checked_revisions, @@ -316,17 +348,21 @@ checked_revisions[*i].normalized = true; // roster checks - if (app.db.roster_exists_for_revision(*i)) + if (app.db.roster_link_exists_for_revision(*i)) { hexenc roster_id; - checked_revisions[*i].found_roster = true; + checked_revisions[*i].found_roster_link = true; app.db.get_roster_id_for_revision(*i, roster_id); - I(checked_rosters[roster_id].found); - checked_rosters[roster_id].revision_refs++; - if (!(rev.new_manifest == checked_rosters[roster_id].man_id)) - checked_revisions[*i].manifest_mismatch = true; - if (checked_rosters[roster_id].missing_files > 0) - checked_revisions[*i].incomplete_roster = true; + if (app.db.roster_exists_for_revision(*i)) + { + checked_revisions[*i].found_roster = true; + I(checked_rosters[roster_id].found); + checked_rosters[roster_id].revision_refs++; + if (!(rev.new_manifest == checked_rosters[roster_id].man_id)) + checked_revisions[*i].manifest_mismatch = true; + if (checked_rosters[roster_id].missing_files > 0) + checked_revisions[*i].incomplete_roster = true; + } } if (found_manifests.find(rev.new_manifest) == found_manifests.end()) @@ -621,6 +657,12 @@ % i->first % revision.missing_revisions); } + if (!revision.found_roster_link) + { + incomplete_revisions++; + P(F("revision %s incomplete (missing roster link)\n") % i->first); + } + if (!revision.found_roster) { incomplete_revisions++; @@ -793,6 +835,7 @@ size_t incomplete_rosters = 0; size_t non_parseable_rosters = 0; size_t non_normalized_rosters = 0; + size_t unreferenced_roster_links = 0; size_t missing_revisions = 0; size_t incomplete_revisions = 0; @@ -816,6 +859,9 @@ found_manifests, checked_files); check_revisions(app, checked_revisions, checked_rosters, found_manifests); check_rosters_marking(app, checked_rosters, checked_revisions); + check_roster_links(app, checked_revisions, checked_rosters, + unreferenced_roster_links, + missing_rosters); check_ancestry(app, checked_revisions); check_sane(app, checked_revisions); check_keys(app, checked_keys); @@ -824,11 +870,11 @@ report_files(checked_files, missing_files, unreferenced_files); report_rosters(checked_rosters, - unreferenced_rosters, - incomplete_rosters, - non_parseable_rosters, - non_normalized_rosters); - + unreferenced_rosters, + incomplete_rosters, + non_parseable_rosters, + non_normalized_rosters); + report_revisions(checked_revisions, missing_revisions, incomplete_revisions, mismatched_parents, mismatched_children, @@ -873,6 +919,14 @@ if (non_normalized_revisions > 0) W(F("%d revisions not in normalized form\n") % non_normalized_revisions); + + if (unreferenced_roster_links > 0) + W(F("%d unreferenced roster links\n") % unreferenced_roster_links); + + if (missing_rosters > 0) + W(F("%d missing rosters\n") % missing_rosters); + + if (missing_keys > 0) W(F("%d missing keys\n") % missing_keys); @@ -892,13 +946,14 @@ non_parseable_revisions + non_normalized_revisions + mismatched_parents + mismatched_children + bad_history + + unreferenced_roster_links + missing_rosters + missing_certs + mismatched_certs + unchecked_sigs + bad_sigs + missing_keys; // unreferenced files and rosters and mismatched certs are not actually // serious errors; odd, but nothing will break. size_t serious = missing_files + - incomplete_rosters + + incomplete_rosters + missing_rosters + non_parseable_rosters + non_normalized_rosters + missing_revisions + incomplete_revisions + non_parseable_revisions + non_normalized_revisions + ======================================================================== --- schema.sql ed4848cf3241d4de48ab8630736d04e04a269abd +++ schema.sql f44144278a4158695818d8f7e1901ac6f89e39bb @@ -63,20 +63,23 @@ CREATE TABLE rosters ( id primary key, -- strong hash of the roster - rev_id not null unique, -- strong hash of associated revision data not null -- compressed, encoded contents of the roster ); CREATE TABLE roster_deltas ( id not null, -- strong hash of the roster - rev_id not null, -- strong hash of associated revision base not null, -- joins with either rosters.id or roster_deltas.id delta not null, -- rdiff to construct current from base - unique(id, base), - unique(rev_id, base) + unique(id, base) ); +CREATE TABLE revision_roster + ( + rev_id primary key, -- joins with revisions.id + roster_id not null -- joins with either rosters.id or roster_deltas.id + ); + CREATE TABLE next_roster_node_number ( node primary key -- only one entry in this table, ever ======================================================================== --- schema_migration.cc fc00e2d80ad173e9ead4121c80a6bbefd453e5db +++ schema_migration.cc 0e70ff287887b7f412e6f36b6280670c049fc12e @@ -853,7 +853,6 @@ "CREATE TABLE rosters\n" "(\n" "id primary key, -- strong hash of the roster\n" - "rev_id not null unique, -- strong hash of associated revision\n" "data not null -- compressed, encoded contents of the roster\n" ");", NULL, NULL, errmsg); @@ -864,21 +863,29 @@ "CREATE TABLE roster_deltas\n" "(\n" "id not null, -- strong hash of the roster\n" - "rev_id not null, -- strong hash of associated revision\n" "base not null, -- joins with either rosters.id or roster_deltas.id\n" "delta not null, -- rdiff to construct current from base\n" - "unique(id, base),\n" - "unique(rev_id, base)\n" + "unique(id, base)\n" ");", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; res = sqlite3_exec(sql, + "CREATE TABLE revision_roster\n" + "(\n" + "rev_id primary key, -- joins with revisions.id\n" + "roster_id not null -- joins with either rosters.id or roster_deltas.id\n" + ");", + NULL, NULL, errmsg); + if (res != SQLITE_OK) + return false; + + res = sqlite3_exec(sql, "CREATE TABLE next_roster_node_number\n" "(\n" "node primary key -- only one entry in this table, ever\n" - ");", + ");", NULL, NULL, errmsg); if (res != SQLITE_OK) return false; @@ -921,5 +928,5 @@ // also add a new migration test for the new schema version. See // tests/t_migrate_schema.at for details. - m.migrate(sql, "9598aecfa8fbd6bb00acf8dc6e42b46d7e2a46a2"); + m.migrate(sql, "1db80c7cee8fa966913db1a463ed50bf1b0e5b0e"); } ======================================================================== --- tests/t_merge_add_del.at 1f411da2f286911a000fc6ea9d4efa5fb7ba68c4 +++ tests/t_merge_add_del.at 6e28c3a84a4c148144295fd22ea1648dcc8a6bb0 @@ -1,10 +1,7 @@ # -*- Autoconf -*- -AT_SETUP([(imp) merging with ]) +AT_SETUP([merging with ]) -# This test is a bug report. -AT_XFAIL_IF(true) - MONOTONE_SETUP # We want a graph which looks like: