# # # add_dir "tests/automate_put_sentinel" # # add_file "tests/automate_put_sentinel/__driver__.lua" # content [66fd77e9bdd5445b33b4597dc56867229de5a314] # # patch "automate.cc" # from [7f9e5ead627f71b092a15b9919bd3f2f721dfb85] # to [640e7137c57fdaaa69167fd8f615e237fea48969] # # patch "cmd_diff_log.cc" # from [197c4f6f8a28fb1d57026c715693a936ccba2c98] # to [c41c4e990bb22fe33a71116db6b62a0e341955de] # # patch "cmd_merging.cc" # from [8ffdc93fea885aef53e7208d9220a60bd17cee03] # to [0c2c658b5cc6c32aebb932f1a3583df2452df692] # # patch "cmd_ws_commit.cc" # from [12e8559a3b9605157fe0252a140edd1ae971a773] # to [c03bbe67ebbdfd04490ea7bd8e386e6ba707f066] # # patch "commands.cc" # from [fd6e94498ce70716326673cf532db2ae63945ef5] # to [f96a673815b3e20fc582898efdd9dc9234e3e870] # # patch "database.cc" # from [d2603c50782ad0ee0f86fa96f58846608a6d9748] # to [79611f6793cf7952e06243ce1f85b76f8e37ccbf] # # patch "database.hh" # from [954a78c55234b282b7c047569cc5cc6ca9014341] # to [4e091b0b2f2cad2244ae34ac12df4267c3d3a14d] # # patch "database_check.cc" # from [011488d738d65307ae76e194cb1da287077cd94f] # to [7c05875eb42c1c4991c547eb276db8dbe6e0733a] # # patch "enumerator.cc" # from [eec6cead8d287bcb00376bb9f2ed8a48fad7f229] # to [1c48c1a7b619a5017ca59ce943db045c6ee15163] # # patch "merge.cc" # from [1a06c3dfc96557c0608d1450917ca071dc4795b2] # to [36f2d212f50b9cf8206578051f7ed3cdbe6785c0] # # patch "merge.hh" # from [42ee1885e4d911e3ff29434b3a417f64c64f9048] # to [13ac205966efa48b65925793ce49013bcdcdb86b] # # patch "netsync.cc" # from [b162d327e4854e204ec02ef5f3fd0e0be2c42596] # to [bd7e698fbbe81885d936fe942cf806ead7bbc03f] # # patch "revision.cc" # from [fbc92e485f3d52c4fdce11cfd0d4fa2d1ab61052] # to [10303ec1420c5127250a6481a607cdbdf40fe611] # # patch "revision.hh" # from [c8ba22a180653cff9231e65e3fbe936abc46e8b7] # to [cfa9104104fca61c27633b2a81ee20f262d26454] # # patch "schema.sql" # from [f9c13c1cc7ea3fa4ba4196b9291582b74a161c14] # to [57dc5fce64780079a92e3df6b89214a9b261cecd] # # patch "schema_migration.cc" # from [16307957747ed7458f56319fefb71049a5c637c9] # to [6ba877284535f007011678f3e46a92bf5f253cbf] # # patch "tests/schema_migration/__driver__.lua" # from [e875c22494147e62d954e6ea67fafebe5cde48c5] # to [528f1b8c8d6866e8d03385539d2283a1e1eba87c] # # patch "vocab.hh" # from [2bd8cac9e74aecc7e0de12a440d08fd9153bfdfa] # to [4676a219108f4c11e4a537ed293776f42d479a7a] # # patch "vocab_terms.hh" # from [bb5a992fb126662ed70bb7f41560f77615164e69] # to [f4201dce520616396108365415df288b41d43b11] # ============================================================ --- tests/automate_put_sentinel/__driver__.lua 66fd77e9bdd5445b33b4597dc56867229de5a314 +++ tests/automate_put_sentinel/__driver__.lua 66fd77e9bdd5445b33b4597dc56867229de5a314 @@ -0,0 +1,56 @@ +mtn_setup() + +sentinel_id = "4c2c1d846fa561601254200918fba1fd71e6795d" +sentinel = "format_version \"1\"\n\nnew_manifest [8c7ed0236ac7b7a36ae7b09c21d2c308303f95a8]\n\nold_revision []\n\nadd_dir \"\"\n\nadd_file \"foo\"\n content [00000000000000000000000000000000deadbeef]\n" + +check(mtn("--debug", "automate", "put_sentinel", sentinel_id, sentinel), 0, true, false) + +check(mtn("automate", "get_revision", sentinel_id), 1, false, true) +canonicalize("stderr") +e = readfile("stderr") +check(e == "mtn: error: missing revision "..sentinel_id.."\n") + +check(mtn("automate", "get_sentinel", sentinel_id), 0, true, false) +canonicalize("stdout") +o = readfile("stdout") +check(o == sentinel) + +check(mtn("automate", "get_manifest_of", sentinel_id), 0, true, false) + +-- add a file for the revision we put on top of the sentinel +check(mtn("automate", "put_file", "contents of foo"), 0, true, false) +canonicalize("stdout") +file_hash = "12b7fe4fb8d865f2215d85c36dd6fc250987b9ec" +result = readfile("stdout") +check(result == file_hash.."\n") + +rev = "format_version \"1\"\n\nnew_manifest [00000000000000000000000000000000deadbeef]\n\nold_revision ["..sentinel_id.."]\n\npatch \"foo\"\n from [00000000000000000000000000000000deadbeef]\n to ["..file_hash.."]\n" + +check(mtn("automate", "put_revision", rev), 0, true, false) +canonicalize("stdout") +rev = "de0289cb69fecad31d732579b6c81e27282fef6a" +result = readfile("stdout") +check(result == rev.."\n") + +check(mtn("automate", "cert", rev, "author", "address@hidden"), 0, true, false) +check(mtn("automate", "cert", rev, "branch", "testbranch"), 0, true, false) +check(mtn("automate", "cert", rev, "changelog", "blah-blah"), 0, true, false) +check(mtn("automate", "cert", rev, "date", "2007-05-28T13:33:33"), 0, true, false) + +check(mtn("co", "-b", "testbranch", "mtnco"), 0, false, false) + +-- check if log informs about missing revisions +check(indir("mtnco", mtn("log", "--no-graph")), 0, true, false) + +-- try to get a diff from log +check(indir("mtnco", mtn("--debug", "log", "--diffs", "--no-graph")), 0, true, true) + +-- try a direct diff +check(indir("mtnco", mtn("diff", "-r", sentinel_id)), 1, false, true) +canonicalize("stderr") +e = readfile("stderr") +check(e == "mtn: error: missing revision '"..sentinel_id.."'\n") + +-- try annotate +-- check(indir("mtnco", mtn("annotate", "foo")), 0, false, false) + ============================================================ --- automate.cc 7f9e5ead627f71b092a15b9919bd3f2f721dfb85 +++ automate.cc 640e7137c57fdaaa69167fd8f615e237fea48969 @@ -95,12 +95,13 @@ CMD_AUTOMATE(ancestors, N_("REV1 [REV2 [ { N(args.size() > 0, F("wrong argument count")); - + set ancestors; vector frontier; for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i) { revision_id rid((*i)()); + E(!app.db.sentinel_exists(rid), F("missing revision %s") % rid); N(app.db.revision_exists(rid), F("No such revision %s") % rid); frontier.push_back(rid); } @@ -125,7 +126,10 @@ CMD_AUTOMATE(ancestors, N_("REV1 [REV2 [ for (set::const_iterator i = ancestors.begin(); i != ancestors.end(); ++i) if (!null_id(*i)) - output << (*i).inner()() << '\n'; + if (!app.db.sentinel_exists(*i)) + output << (*i).inner()() << '\n'; + else + output << "sentinel:" << (*i).inner()() << '\n'; } @@ -151,6 +155,7 @@ CMD_AUTOMATE(descendents, N_("REV1 [REV2 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i) { revision_id rid((*i)()); + E(!app.db.sentinel_exists(rid), F("missing revision %s") % rid); N(app.db.revision_exists(rid), F("No such revision %s") % rid); frontier.push_back(rid); } @@ -172,7 +177,10 @@ CMD_AUTOMATE(descendents, N_("REV1 [REV2 } for (set::const_iterator i = descendents.begin(); i != descendents.end(); ++i) - output << (*i).inner()() << '\n'; + if (!app.db.sentinel_exists(*i)) + output << (*i).inner()() << '\n'; + else + output << "sentinel:" << (*i).inner()() << '\n'; } @@ -198,7 +206,7 @@ CMD_AUTOMATE(erase_ancestors, N_("[REV1 for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i) { revision_id rid((*i)()); - N(app.db.revision_exists(rid), F("No such revision %s") % rid); + N(app.db.revision_or_sentinel_exists(rid), F("No such revision %s") % rid); revs.insert(rid); } erase_ancestors(revs, app); @@ -225,7 +233,7 @@ CMD_AUTOMATE(toposort, N_("[REV1 [REV2 [ for (args_vector::const_iterator i = args.begin(); i != args.end(); ++i) { revision_id rid((*i)()); - N(app.db.revision_exists(rid), F("No such revision %s") % rid); + N(app.db.revision_or_sentinel_exists(rid), F("No such revision %s") % rid); revs.insert(rid); } vector sorted; @@ -268,6 +276,7 @@ CMD_AUTOMATE(ancestry_difference, N_("NE for (++i; i != args.end(); ++i) { revision_id b((*i)()); + E(!app.db.sentinel_exists(b), F("missing revision %s") % b); N(app.db.revision_exists(b), F("No such revision %s") % b); bs.insert(b); } @@ -361,12 +370,16 @@ CMD_AUTOMATE(parents, N_("REV"), F("wrong argument count")); revision_id rid(idx(args, 0)()); + E(!app.db.sentinel_exists(rid), F("missing revision %s") % rid); N(app.db.revision_exists(rid), F("No such revision %s") % rid); set parents; app.db.get_revision_parents(rid, parents); for (set::const_iterator i = parents.begin(); i != parents.end(); ++i) if (!null_id(*i)) + if (app.db.sentinel_exists(*i)) + output << "sentinel:" << (*i).inner()() << '\n'; + else output << (*i).inner()() << '\n'; } @@ -389,12 +402,16 @@ CMD_AUTOMATE(children, N_("REV"), F("wrong argument count")); revision_id rid(idx(args, 0)()); + E(!app.db.sentinel_exists(rid), F("missing revision %s") % rid); N(app.db.revision_exists(rid), F("No such revision %s") % rid); set children; app.db.get_revision_children(rid, children); for (set::const_iterator i = children.begin(); i != children.end(); ++i) if (!null_id(*i)) + if (app.db.sentinel_exists(*i)) + output << "sentinel:" << (*i).inner()() << '\n'; + else output << (*i).inner()() << '\n'; } @@ -882,6 +899,7 @@ CMD_AUTOMATE(get_revision, N_("[REVID]") else { ident = revision_id(idx(args, 0)()); + E(!app.db.sentinel_exists(ident), F("missing revision %s") % ident); N(app.db.revision_exists(ident), F("no revision %s found in database") % ident); app.db.get_revision(ident, dat); @@ -891,6 +909,36 @@ CMD_AUTOMATE(get_revision, N_("[REVID]") output.write(dat.inner()().data(), dat.inner()().size()); } + +// Name: get_sentinel +// Arguments: +// 1: a revision id +// Added in: 2.0 +// Purpose: Prints change information for the gap which starts at +// the specified revision id. +CMD_AUTOMATE(get_sentinel, N_("[REVID]"), + N_("Shows change information for a sentinel"), + "", + options::opts::none) +{ + N(args.size() == 1, + F("wrong argument count")); + + temp_node_id_source nis; + sentinel_data dat; + revision_id ident; + + ident = revision_id(idx(args, 0)()); + N(!app.db.revision_exists(ident), + F("This is not a sentinel, but a real revision") % ident); + N(app.db.sentinel_exists(ident), F("No such sentinel %s") % ident); + app.db.get_sentinel(ident, dat); + + L(FL("dumping sentinel %s") % ident); + output.write(dat.inner()().data(), dat.inner()().size()); +} + + // Name: get_base_revision_id // Arguments: none // Added in: 2.0 @@ -1017,7 +1065,7 @@ CMD_AUTOMATE(get_manifest_of, N_("[REVID else { revision_id rid = revision_id(idx(args, 0)()); - N(app.db.revision_exists(rid), + N(app.db.revision_or_sentinel_exists(rid), F("no revision %s found in database") % rid); app.db.get_roster(rid, new_roster); } @@ -1053,6 +1101,7 @@ CMD_AUTOMATE(packet_for_rdata, N_("REVID revision_id r_id(idx(args, 0)()); revision_data r_data; + E(!app.db.sentinel_exists(r_id), F("missing revision %s") % r_id); N(app.db.revision_exists(r_id), F("no such revision '%s'") % r_id); app.db.get_revision(r_id, r_data); @@ -1083,6 +1132,7 @@ CMD_AUTOMATE(packets_for_certs, N_("REVI revision_id r_id(idx(args, 0)()); vector< revision > certs; + E(!app.db.sentinel_exists(r_id), F("missing revision %s") % r_id); N(app.db.revision_exists(r_id), F("no such revision '%s'") % r_id); app.get_project().get_revision_certs(r_id, certs); @@ -1219,7 +1269,10 @@ CMD_AUTOMATE(common_ancestors, N_("REV1 for (set::const_iterator i = common_ancestors.begin(); i != common_ancestors.end(); ++i) if (!null_id(*i)) - output << (*i).inner()() << '\n'; + if (app.db.sentinel_exists(*i)) + output << "sentinel:" << (*i).inner()() << '\n'; + else + output << (*i).inner()() << '\n'; } // Name: branches @@ -1707,6 +1760,32 @@ CMD_AUTOMATE(put_revision, N_("REVISION- output << id << '\n'; } +// Name: put_sentinel +// Arguments: +// 1: revision id +// 2: sentinel-data +// Added in: 4.1 +// Purpose: +// Store a sentinel into the database. +// Error conditions: +// none +CMD_AUTOMATE(put_sentinel, N_("REV SENTINEL-DATA"), + N_("Stores a sentinel into the database"), + "", + options::opts::none) +{ + N(args.size() == 2, + F("wrong argument count")); + + revision_id id(idx(args, 0)()); + + revision_t rev; + read_sentinel(sentinel_data(idx(args, 1)()), rev); + + E(app.db.put_revision(id, rev), + F("missing prerequisite for sentinel %s") % id); +} + // Name: cert // Arguments: // revision ID ============================================================ --- cmd_diff_log.cc 197c4f6f8a28fb1d57026c715693a936ccba2c98 +++ cmd_diff_log.cc c41c4e990bb22fe33a71116db6b62a0e341955de @@ -392,6 +392,8 @@ prepare_diff(cset & included, revision_id r_old_id; complete(app, idx(app.opts.revision_selectors, 0)(), r_old_id); + E(!app.db.sentinel_exists(r_old_id), + F("missing revision '%s'") % r_old_id); N(app.db.revision_exists(r_old_id), F("no such revision '%s'") % r_old_id); @@ -419,8 +421,12 @@ prepare_diff(cset & included, complete(app, idx(app.opts.revision_selectors, 0)(), r_old_id); complete(app, idx(app.opts.revision_selectors, 1)(), r_new_id); + E(!app.db.sentinel_exists(r_old_id), + F("missing revision '%s'") % r_old_id); N(app.db.revision_exists(r_old_id), F("no such revision '%s'") % r_old_id); + E(!app.db.sentinel_exists(r_old_id), + F("missing revision '%s'") % r_new_id); N(app.db.revision_exists(r_new_id), F("no such revision '%s'") % r_new_id); @@ -773,7 +779,7 @@ CMD(log, "log", "", CMD_REF(informative) } seen.insert(rid); - app.db.get_revision(rid, rev); + app.db.get_revision_or_sentinel(rid, rev); set marked_revs; @@ -852,6 +858,8 @@ CMD(log, "log", "", CMD_REF(informative) ostringstream out; if (app.opts.brief) { + if (!rev.is_sentinel) + { out << rid; log_certs(out, app, rid, author_name); if (app.opts.no_graph) @@ -863,15 +871,24 @@ CMD(log, "log", "", CMD_REF(informative) } log_certs(out, app, rid, branch_name); out << '\n'; + } + else + { + out << "Missing revisions starting from " << rid; + out << '\n'; + } } else { out << string(65, '-') << '\n'; + + if (!rev.is_sentinel) + { out << "Revision: " << rid << '\n'; changes_summary csum; - set ancestors; + set ancestors, sentinel_ancestors; for (edge_map::const_iterator e = rev.edges.begin(); e != rev.edges.end(); ++e) @@ -898,14 +915,25 @@ CMD(log, "log", "", CMD_REF(informative) log_certs(out, app, rid, changelog_name, "ChangeLog: ", true); log_certs(out, app, rid, comment_name, "Comments: ", true); + } + else + { + out << "Missing revisions starting from " << rid; + out << '\n'; + } } - if (app.opts.diffs) + if (app.opts.diffs && !rev.is_sentinel) { for (edge_map::const_iterator e = rev.edges.begin(); e != rev.edges.end(); ++e) - dump_diffs(edge_changes(e), app, true, out, - diff_paths, !mask.empty()); + if (!app.db.sentinel_exists(edge_old_revision(e))) + dump_diffs(edge_changes(e), app, true, out, + diff_paths, !mask.empty()); + else + out << string(60, '=') << '\n' + << "# Unable to diff against sentinel " + << edge_old_revision(e) << '\n'; } if (next > 0) ============================================================ --- cmd_merging.cc 8ffdc93fea885aef53e7208d9220a60bd17cee03 +++ cmd_merging.cc 0c2c658b5cc6c32aebb932f1a3583df2452df692 @@ -527,7 +527,8 @@ CMD(merge_into_dir, "merge_into_dir", "" marking_map left_marking_map, right_marking_map; set left_uncommon_ancestors, - right_uncommon_ancestors; + right_uncommon_ancestors, + sentinels; app.db.get_roster(left_rid, left_roster, left_marking_map); app.db.get_roster(right_rid, right_roster, right_marking_map); @@ -535,6 +536,12 @@ CMD(merge_into_dir, "merge_into_dir", "" left_uncommon_ancestors, right_uncommon_ancestors); + app.db.get_sentinels_of(left_uncommon_ancestors, sentinels); + app.db.get_sentinels_of(right_uncommon_ancestors, sentinels); + + if (sentinels.size() > 0) + print_sentinels_and_abort(sentinels); + { dir_t moved_root = left_roster.root(); split_path sp, dirname; @@ -647,11 +654,20 @@ CMD(merge_into_workspace, "merge_into_wo app.db.get_roster(right_id, right); N(!(left_id == right_id), F("workspace is already at revision %s") % left_id); - set left_uncommon_ancestors, right_uncommon_ancestors; + set left_uncommon_ancestors, + right_uncommon_ancestors, + sentinels; + app.db.get_uncommon_ancestors(left_id, right_id, left_uncommon_ancestors, right_uncommon_ancestors); + app.db.get_sentinels_of(left_uncommon_ancestors, sentinels); + app.db.get_sentinels_of(right_uncommon_ancestors, sentinels); + + if (sentinels.size() > 0) + print_sentinels_and_abort(sentinels); + roster_merge_result merge_result; MM(merge_result); roster_merge(*left.first, *left.second, left_uncommon_ancestors, @@ -743,10 +759,17 @@ CMD(show_conflicts, "show_conflicts", "" marking_map l_marking, r_marking; app.db.get_roster(l_id, l_roster, l_marking); app.db.get_roster(r_id, r_roster, r_marking); - set l_uncommon_ancestors, r_uncommon_ancestors; + set l_uncommon_ancestors, r_uncommon_ancestors, sentinels; app.db.get_uncommon_ancestors(l_id, r_id, l_uncommon_ancestors, r_uncommon_ancestors); + + app.db.get_sentinels_of(l_uncommon_ancestors, sentinels); + app.db.get_sentinels_of(r_uncommon_ancestors, sentinels); + + if (sentinels.size() > 0) + print_sentinels_and_abort(sentinels); + roster_merge_result result; roster_merge(l_roster, l_marking, l_uncommon_ancestors, r_roster, r_marking, r_uncommon_ancestors, @@ -1002,11 +1025,19 @@ CMD(get_roster, "get_roster", "", CMD_RE i++; I(i == parents.end()); - set left_uncommon_ancestors, right_uncommon_ancestors; + set left_uncommon_ancestors, + right_uncommon_ancestors, + sentinels; app.db.get_uncommon_ancestors(left_id, right_id, left_uncommon_ancestors, right_uncommon_ancestors); + app.db.get_sentinels_of(left_uncommon_ancestors, sentinels); + app.db.get_sentinels_of(right_uncommon_ancestors, sentinels); + + if (sentinels.size() > 0) + print_sentinels_and_abort(sentinels); + mark_merge_roster(left_roster, left_markings, left_uncommon_ancestors, right_roster, right_markings, ============================================================ --- cmd_ws_commit.cc 12e8559a3b9605157fe0252a140edd1ae971a773 +++ cmd_ws_commit.cc c03bbe67ebbdfd04490ea7bd8e386e6ba707f066 @@ -289,7 +289,8 @@ CMD(disapprove, "disapprove", "", CMD_RE revision_t rev, rev_inverse; shared_ptr cs_inverse(new cset()); complete(app, idx(args, 0)(), r); - app.db.get_revision(r, rev); + app.db.get_revision_or_sentinel(r, rev); + I(!rev.is_sentinel); N(rev.edges.size() == 1, F("revision %s has %d changesets, cannot invert") % r % rev.edges.size()); ============================================================ --- commands.cc fd6e94498ce70716326673cf532db2ae63945ef5 +++ commands.cc f96a673815b3e20fc582898efdd9dc9234e3e870 @@ -802,7 +802,7 @@ complete(app_state & app, { completion.insert(revision_id(hexenc(id(str)))); if (must_exist) - N(app.db.revision_exists(*completion.begin()), + N(app.db.revision_or_sentinel_exists(*completion.begin()), F("no such revision '%s'") % *completion.begin()); return; } ============================================================ --- database.cc d2603c50782ad0ee0f86fa96f58846608a6d9748 +++ database.cc 79611f6793cf7952e06243ce1f85b76f8e37ccbf @@ -556,6 +556,7 @@ database::info(ostream & out) counts.push_back(count("files")); counts.push_back(count("file_deltas")); counts.push_back(count("revisions")); + counts.push_back(count("sentinels")); counts.push_back(count("revision_ancestry")); counts.push_back(count("revision_certs")); @@ -593,6 +594,7 @@ database::info(ostream & out) bytes.push_back(space("file_deltas", "length(id) + length(base) + length(delta)", total)); bytes.push_back(space("revisions", "length(id) + length(data)", total)); + bytes.push_back(space("sentinels", "length(id) + length(data)", total)); bytes.push_back(space("revision_ancestry", "length(parent) + length(child)", total)); bytes.push_back(space("revision_certs", @@ -628,6 +630,7 @@ database::info(ostream & out) " full files : %s\n" " file deltas : %s\n" " revisions : %s\n" + " sentinels : %s\n" " ancestry edges : %s\n" " certs : %s\n" " logical files : %s\n" @@ -637,6 +640,7 @@ database::info(ostream & out) " full files : %s\n" " file deltas : %s\n" " revisions : %s\n" + " sentinels : %s\n" " cached ancestry : %s\n" " certs : %s\n" " heights : %s\n" @@ -1649,6 +1653,27 @@ database::revision_exists(revision_id co return res.size() == 1; } +bool +database::sentinel_exists(revision_id const & id) +{ + results res; + query q("SELECT id FROM sentinels WHERE id = ?"); + fetch(res, one_col, any_rows, q % text(id.inner()())); + I(res.size() <= 1); + return res.size() == 1; +} + +bool +database::revision_or_sentinel_exists(revision_id const & id) +{ + results res; + query q("SELECT id FROM revisions WHERE id = ? " + "UNION SELECT id FROM sentinels WHERE id = ?"); + fetch(res, one_col, any_rows, q % text(id.inner()()) % text(id.inner()())); + I(res.size() <= 1); + return res.size() == 1; +} + void database::get_file_ids(set & ids) { @@ -1669,6 +1694,16 @@ void } void +database::get_revision_and_sentinel_ids(set & ids) +{ + ids.clear(); + set< hexenc > tmp; + get_ids("revisions", tmp); + get_ids("sentinels", tmp); + add_decoration_to_container(tmp, ids); +} + +void database::get_roster_ids(set & ids) { ids.clear(); @@ -1810,7 +1845,6 @@ database::get_arbitrary_file_delta(file_ del = file_delta(dtmp); } - void database::get_revision_ancestry(rev_ancestry_map & graph) { @@ -1851,21 +1885,41 @@ void } void +database::get_sentinels_of(set const & revs, set & sentinels) +{ + for (set::const_iterator i = revs.begin(); + i != revs.end(); ++i) + if (sentinel_exists(*i) && (sentinels.find(*i) == sentinels.end())) + sentinels.insert(*i); +} + +void database::get_revision_manifest(revision_id const & rid, - manifest_id & mid) + manifest_id & mid) { revision_t rev; - get_revision(rid, rev); + get_revision_or_sentinel(rid, rev); mid = rev.new_manifest; } void -database::get_revision(revision_id const & id, - revision_t & rev) +database::get_revision_or_sentinel(revision_id const & id, + revision_t & rev) { - revision_data d; - get_revision(id, d); - read_revision(d, rev); + if (sentinel_exists(id)) + { + sentinel_data d; + get_sentinel(id, d); + read_sentinel(d, rev); + I(rev.is_sentinel); + } + else + { + revision_data d; + get_revision(id, d); + read_revision(d, rev); + I(!rev.is_sentinel); + } } void @@ -1893,6 +1947,22 @@ void } void +database::get_sentinel(revision_id const & id, + sentinel_data & dat) +{ + I(!null_id(id)); + results res; + fetch(res, one_col, one_row, + query("SELECT data FROM sentinels WHERE id = ?") + % text(id.inner()())); + + gzip gzdata(res[0][0]); + data rdat; + decode_gzip(gzdata,rdat); + dat = sentinel_data(rdat); +} + +void database::get_rev_height(revision_id const & id, rev_height & height) { @@ -1918,7 +1988,7 @@ database::put_rev_height(revision_id con rev_height const & height) { I(!null_id(id)); - I(revision_exists(id)); + I(revision_or_sentinel_exists(id)); I(height.valid()); execute(query("INSERT INTO heights VALUES(?, ?)") @@ -1944,7 +2014,8 @@ database::deltify_revision(revision_id c revision_t rev; MM(rev); MM(rid); - get_revision(rid, rev); + get_revision_or_sentinel(rid, rev); + I(!rev.is_sentinel); // Make sure that all parent revs have their files replaced with deltas // from this rev's files. { @@ -1985,7 +2056,7 @@ database::put_revision(revision_id const I(!null_id(new_id)); - if (revision_exists(new_id)) + if (revision_or_sentinel_exists(new_id)) { L(FL("revision '%s' already exists in db") % new_id); return false; @@ -1999,7 +2070,10 @@ database::put_revision(revision_id const for (edge_map::const_iterator i = rev.edges.begin(); i != rev.edges.end(); ++i) { + bool edge_is_sentinel = sentinel_exists(edge_old_revision(i)); + if (!edge_old_revision(i).inner()().empty() + && !edge_is_sentinel && !revision_exists(edge_old_revision(i))) { W(F("missing prerequisite revision '%s'") % edge_old_revision(i)); @@ -2007,6 +2081,7 @@ database::put_revision(revision_id const return false; } + if (!rev.is_sentinel) for (map::const_iterator a = edge_changes(i).files_added.begin(); a != edge_changes(i).files_added.end(); ++a) @@ -2026,7 +2101,8 @@ database::put_revision(revision_id const I(!delta_entry_src(d).inner()().empty()); I(!delta_entry_dst(d).inner()().empty()); - if (! file_version_exists(delta_entry_src(d))) + if (!edge_is_sentinel + && !file_version_exists(delta_entry_src(d))) { W(F("missing prerequisite file pre-delta '%s'") % delta_entry_src(d)); @@ -2034,7 +2110,8 @@ database::put_revision(revision_id const return false; } - if (! file_version_exists(delta_entry_dst(d))) + if (!rev.is_sentinel && + !file_version_exists(delta_entry_dst(d))) { W(F("missing prerequisite file post-delta '%s'") % delta_entry_dst(d)); @@ -2052,7 +2129,12 @@ database::put_revision(revision_id const write_revision(rev, d); gzip d_packed; encode_gzip(d.inner(), d_packed); - execute(query("INSERT INTO revisions VALUES(?, ?)") + string qstr; + if (rev.is_sentinel) + qstr = "INSERT INTO sentinels VALUES (?, ?)"; + else + qstr = "INSERT INTO revisions VALUES (?, ?)"; + execute(query(qstr) % text(new_id.inner()()) % blob(d_packed())); @@ -2071,7 +2153,8 @@ database::put_revision(revision_id const // Phase 4: rewrite any files that need deltas added - deltify_revision(new_id); + if (!rev.is_sentinel) + deltify_revision(new_id); // Phase 5: determine the revision height @@ -2150,6 +2233,7 @@ database::delete_existing_revs_and_certs void database::delete_existing_revs_and_certs() { + execute(query("DELETE FROM sentinels")); execute(query("DELETE FROM revisions")); execute(query("DELETE FROM revision_ancestry")); execute(query("DELETE FROM revision_certs")); ============================================================ --- database.hh 954a78c55234b282b7c047569cc5cc6ca9014341 +++ database.hh 4e091b0b2f2cad2244ae34ac12df4267c3d3a14d @@ -262,6 +262,8 @@ public: bool file_version_exists(file_id const & ident); bool revision_exists(revision_id const & ident); + bool sentinel_exists(revision_id const & ident); + bool revision_or_sentinel_exists(revision_id const & ident); bool roster_link_exists_for_revision(revision_id const & ident); bool roster_exists_for_revision(revision_id const & ident); @@ -303,11 +305,17 @@ public: void get_revision_manifest(revision_id const & cid, manifest_id & mid); + + void get_sentinels_of(std::set const & revs, + std::set & sentinels); + private: // helper void get_ids(std::string const & table, std::set< hexenc > & ids); public: void get_revision_ids(std::set & ids); + void get_revision_and_sentinel_ids(std::set & ids); + // this is exposed for 'db check': void get_file_ids(std::set & ids); @@ -317,12 +325,15 @@ public: private: void deltify_revision(revision_id const & rid); public: - void get_revision(revision_id const & ident, - revision_t & cs); + void get_revision_or_sentinel(revision_id const & ident, + revision_t & cs); void get_revision(revision_id const & ident, revision_data & dat); + void get_sentinel(revision_id const & ident, + sentinel_data & dat); + bool put_revision(revision_id const & new_id, revision_t const & rev); ============================================================ --- database_check.cc 011488d738d65307ae76e194cb1da287077cd94f +++ database_check.cc 7c05875eb42c1c4991c547eb276db8dbe6e0733a @@ -305,7 +305,7 @@ check_revisions(app_state & app, { set revisions; - app.db.get_revision_ids(revisions); + app.db.get_revision_and_sentinel_ids(revisions); L(FL("checking %d revisions") % revisions.size()); ticker ticks(_("revisions"), "r", revisions.size()/70+1); @@ -314,14 +314,13 @@ check_revisions(app_state & app, i != revisions.end(); ++i) { L(FL("checking revision %s") % *i); - revision_data data; - app.db.get_revision(*i, data); checked_revisions[*i].found = true; revision_t rev; try { - read_revision(data, rev); + app.db.get_revision_or_sentinel(*i, rev); + I(!rev.is_sentinel); } catch (logic_error & e) { ============================================================ --- enumerator.cc eec6cead8d287bcb00376bb9f2ed8a48fad7f229 +++ enumerator.cc 1c48c1a7b619a5017ca59ce943db045c6ee15163 @@ -117,7 +117,7 @@ revision_enumerator::files_for_revision( revision_t rs; MM(rs); - app.db.get_revision(r, rs); + app.db.get_revision_or_sentinel(r, rs); for (edge_map::const_iterator i = rs.edges.begin(); i != rs.edges.end(); ++i) ============================================================ --- merge.cc 1a06c3dfc96557c0608d1450917ca071dc4795b2 +++ merge.cc 36f2d212f50b9cf8206578051f7ed3cdbe6785c0 @@ -18,6 +18,7 @@ #include "safe_map.hh" #include "transforms.hh" #include "app_state.hh" +#include "sanity.hh" using std::make_pair; using std::map; @@ -125,6 +126,17 @@ void } void +print_sentinels_and_abort(set const & sentinels) +{ + P(F("Missing the following revisions:")); + for (set::const_iterator i = sentinels.begin(); + i != sentinels.end(); ++i) + P(F("\t%s") % *i); + + E(false, F("no action taken")); +} + +void interactive_merge_and_store(revision_id const & left_rid, revision_id const & right_rid, revision_id & merged_rid, @@ -132,13 +144,21 @@ interactive_merge_and_store(revision_id { roster_t left_roster, right_roster; marking_map left_marking_map, right_marking_map; - set left_uncommon_ancestors, right_uncommon_ancestors; + set left_uncommon_ancestors, + right_uncommon_ancestors, + sentinels; app.db.get_roster(left_rid, left_roster, left_marking_map); app.db.get_roster(right_rid, right_roster, right_marking_map); app.db.get_uncommon_ancestors(left_rid, right_rid, left_uncommon_ancestors, right_uncommon_ancestors); + app.db.get_sentinels_of(left_uncommon_ancestors, sentinels); + app.db.get_sentinels_of(right_uncommon_ancestors, sentinels); + + if (sentinels.size() > 0) + print_sentinels_and_abort(sentinels); + roster_merge_result result; // { ============================================================ --- merge.hh 42ee1885e4d911e3ff29434b3a417f64c64f9048 +++ merge.hh 13ac205966efa48b65925793ce49013bcdcdb86b @@ -10,6 +10,9 @@ // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. +#include +#include + #include "vocab.hh" class app_state; @@ -30,6 +33,9 @@ resolve_merge_conflicts(roster_t const & content_merge_adaptor & adaptor, app_state & app); +void +print_sentinels_and_abort(std::set const & sentinels); + // traditional resolve-all-conflicts-as-you-go style merging with 3-way merge // for file texts // throws if merge fails ============================================================ --- netsync.cc b162d327e4854e204ec02ef5f3fd0e0be2c42596 +++ netsync.cc bd7e698fbbe81885d936fe942cf806ead7bbc03f @@ -662,7 +662,8 @@ session::note_rev(revision_id const & re revision_t rs; id item; decode_hexenc(rev.inner(), item); - app.db.get_revision(rev, rs); + app.db.get_revision_or_sentinel(rev, rs); + I(!rs.is_sentinel); data tmp; write_revision(rs, tmp); queue_data_cmd(revision_item, item, tmp()); ============================================================ --- revision.cc fbc92e485f3d52c4fdce11cfd0d4fa2d1ab61052 +++ revision.cc 10303ec1420c5127250a6481a607cdbdf40fe611 @@ -124,6 +124,7 @@ revision_t::revision_t(revision_t const made_for = made_for_nobody; if (null_id(other.new_manifest) && other.edges.empty()) return; other.check_sane(); + is_sentinel = other.is_sentinel; new_manifest = other.new_manifest; edges = other.edges; made_for = other.made_for; @@ -133,6 +134,7 @@ revision_t::operator=(revision_t const & revision_t::operator=(revision_t const & other) { other.check_sane(); + is_sentinel = other.is_sentinel; new_manifest = other.new_manifest; edges = other.edges; made_for = other.made_for; @@ -1758,7 +1760,8 @@ regenerate_caches(app_state & app) { revision_t rev; revision_id const & rev_id = *i; - app.db.get_revision(rev_id, rev); + app.db.get_revision_or_sentinel(rev_id, rev); + I(!rev.is_sentinel); app.db.put_roster_for_revision(rev_id, rev); app.db.put_height_for_revision(rev_id, rev); ++done; @@ -1876,6 +1879,30 @@ void } void +parse_sentinel(basic_io::parser & parser, + revision_t & rev) +{ + MM(rev); + rev.is_sentinel = true; + rev.edges.clear(); + rev.made_for = made_for_database; + string tmp; + parser.esym(syms::format_version); + parser.str(tmp); + E(tmp == "1", + F("encountered a sentinel with unknown format, version '%s'\n" + "I only know how to understand the version '1' format\n" + "a newer version of monotone is required to complete this operation") + % tmp); + parser.esym(syms::new_manifest); + parser.hex(tmp); + rev.new_manifest = manifest_id(tmp); + while (parser.symp(syms::old_revision)) + parse_edge(parser, rev.edges); + rev.check_sane(); +} + +void read_revision(data const & dat, revision_t & rev) { @@ -1896,6 +1923,27 @@ read_revision(revision_data const & dat, rev.check_sane(); } +void +read_sentinel(data const & dat, + revision_t & rev) +{ + MM(rev); + basic_io::input_source src(dat(), "sentinel"); + basic_io::tokenizer tok(src); + basic_io::parser pars(tok); + parse_sentinel(pars, rev); + I(src.lookahead == EOF); + rev.check_sane(); +} + +void +read_sentinel(sentinel_data const & dat, + revision_t & rev) +{ + read_sentinel(dat.inner(), rev); + rev.check_sane(); +} + static void write_insane_revision(revision_t const & rev, data & dat) { ============================================================ --- revision.hh c8ba22a180653cff9231e65e3fbe936abc46e8b7 +++ revision.hh cfa9104104fca61c27633b2a81ee20f262d26454 @@ -64,9 +64,10 @@ revision_t // trivial revisions are ones that have no effect -- e.g., commit should // refuse to commit them, saying that there are no changes to commit. bool is_nontrivial() const; - revision_t() : made_for(made_for_nobody) {} + revision_t() : is_sentinel(false), made_for(made_for_nobody) {} revision_t(revision_t const & other); revision_t const & operator=(revision_t const & other); + bool is_sentinel; manifest_id new_manifest; edge_map edges; // workspace::put_work_rev refuses to apply a rev that doesn't have this @@ -112,6 +113,14 @@ void revision_t & rev); void +read_sentinel(data const & dat, + revision_t & rev); + +void +read_sentinel(sentinel_data const & dat, + revision_t & rev); + +void write_revision(revision_t const & rev, data & dat); ============================================================ --- schema.sql f9c13c1cc7ea3fa4ba4196b9291582b74a161c14 +++ schema.sql 57dc5fce64780079a92e3df6b89214a9b261cecd @@ -41,6 +41,12 @@ CREATE TABLE revisions data not null -- compressed, encoded contents of a revision ); +CREATE TABLE sentinels + ( + id primary key, -- revision id of the last missing revision + data not null -- compressed, encoded contents of a sentinel + ); + CREATE TABLE revision_ancestry ( parent not null, -- joins with revisions.id ============================================================ --- schema_migration.cc 16307957747ed7458f56319fefb71049a5c637c9 +++ schema_migration.cc 6ba877284535f007011678f3e46a92bf5f253cbf @@ -624,6 +624,13 @@ migrate_add_ccode(sqlite3 * db, app_stat sql::exec(db, cmd.c_str()); } +char const migrate_add_sentinels[] = + "CREATE TABLE sentinels" + " (" + " id primary key, -- revision id of the last missing revision\n" + " data not null -- compressed, encoded contents of a sentinel\n" + " );" + ; // these must be listed in order so that ones listed earlier override ones // listed later @@ -702,10 +709,13 @@ const migration_event migration_events[] { "fe48b0804e0048b87b4cea51b3ab338ba187bdc2", migrate_add_heights_index, 0, upgrade_none }, + + { "7ca81b45279403419581d7fde31ed888a80bd34e", + migrate_add_sentinels, 0, upgrade_none }, // The last entry in this table should always be the current // schema ID, with 0 for the migrators. - { "7ca81b45279403419581d7fde31ed888a80bd34e", 0, 0, upgrade_none } + { "707e132c66e0c5e56261ecb727fbf58a6a1c19fe", 0, 0, upgrade_none } }; const size_t n_migration_events = (sizeof migration_events / sizeof migration_events[0]); ============================================================ --- tests/schema_migration/__driver__.lua e875c22494147e62d954e6ea67fafebe5cde48c5 +++ tests/schema_migration/__driver__.lua 528f1b8c8d6866e8d03385539d2283a1e1eba87c @@ -118,3 +118,4 @@ check_migrate_from("7ca81b45279403419581 check_migrate_from("48fd5d84f1e5a949ca093e87e5ac558da6e5956d", false) check_migrate_from("fe48b0804e0048b87b4cea51b3ab338ba187bdc2", false) check_migrate_from("7ca81b45279403419581d7fde31ed888a80bd34e", false) +check_migrate_from("707e132c66e0c5e56261ecb727fbf58a6a1c19fe", false) ============================================================ --- vocab.hh 2bd8cac9e74aecc7e0de12a440d08fd9153bfdfa +++ vocab.hh 4676a219108f4c11e4a537ed293776f42d479a7a @@ -95,6 +95,7 @@ typedef revision< data > revision_data typedef epoch< hexenc > epoch_data; typedef revision< data > revision_data; +typedef sentinel< data > sentinel_data; typedef roster< data > roster_data; typedef manifest< data > manifest_data; typedef file< data > file_data; ============================================================ --- vocab_terms.hh bb5a992fb126662ed70bb7f41560f77615164e69 +++ vocab_terms.hh f4201dce520616396108365415df288b41d43b11 @@ -50,6 +50,7 @@ DECORATE(revision); // thing a ATOMIC_NOVERIFY(attr_value); DECORATE(revision); // thing associated with a revision +DECORATE(sentinel); // thing associated with a sentinel DECORATE(roster); // thing associated with a roster DECORATE(manifest); // thing associated with a manifest DECORATE(file); // thing associated with a file @@ -83,6 +84,7 @@ EXTERN template class revision< data >; EXTERN template class base64< gzip >; EXTERN template class revision< data >; +EXTERN template class sentinel< data >; EXTERN template class roster< data >; EXTERN template class manifest< data >; EXTERN template class file< data >;