# # # patch "cmd_ws_commit.cc" # from [a9581fa74b19c546d6408f2460b6bce8dab7eff2] # to [3fcaf3975fa321f3c19c87432fc83b3d2fd7d652] # # patch "tests/bisect/__driver__.lua" # from [c89ddcd054d658ca6a5e838ab832ca649a4463ca] # to [b4a1510356c9aec93b7fe1b7cd4bdc1e82bcc623] # # patch "work.cc" # from [8b0f95dc56325eff7e25a1e9241e21ffbceb54ba] # to [2a82871555bb409e2b94593071c525cd60a921fb] # # patch "work.hh" # from [aca3f1a3e8668e0c9a2a679abb224567131fd620] # to [bda8c5daaad8dd6bff2afcd99836e7f09154ca6c] # ============================================================ --- cmd_ws_commit.cc a9581fa74b19c546d6408f2460b6bce8dab7eff2 +++ cmd_ws_commit.cc 3fcaf3975fa321f3c19c87432fc83b3d2fd7d652 @@ -1482,10 +1482,57 @@ CMD(reset, "reset", "", CMD_REF(bisect), N_("All current search information is discarded"), options::opts::none) { + database db(app); workspace work(app); + project_t project(db); + + vector bisect; + work.get_bisect_info(bisect); + + E(!bisect.empty(), origin::user, F("no bisection in progress")); + + parent_map parents; + work.get_parent_rosters(db, parents); + E(parents.size() == 1, origin::user, + F("this command can only be used in a single-parent workspace")); + + revision_id current_id = parent_id(*parents.begin()); + + temp_node_id_source nis; + roster_t current_roster; + work.get_current_roster_shape(db, nis, current_roster); + work.update_current_roster_from_filesystem(current_roster); + + E(parent_roster(parents.begin()) == current_roster, origin::user, + F("'%s' can only be used in a workspace with no pending changes") % + join_words(execid)()); + + bisect::entry start = *bisect.begin(); + I(start.first == bisect::start); + + revision_id starting_id = start.second; + P(F("reset back to %s") % describe_revision(project, starting_id)); + + roster_t starting_roster; + db.get_roster(starting_id, starting_roster); + + cset update; + make_cset(current_roster, starting_roster, update); + + content_merge_checkout_adaptor adaptor(db); + work.perform_content_update(current_roster, starting_roster, update, adaptor); + + revision_t starting_rev; + cset empty; + make_revision_for_workspace(starting_id, empty, starting_rev); + + work.put_work_rev(starting_rev); + work.maybe_update_inodeprints(db); + + // note that the various bisect commands didn't change the workspace + // branch so this should not need to reset it. + work.remove_bisect_info(); - P(F("bisect reset")); - // FIXME: update back to the initial starting rev } CMD(bisect_status, "status", "", CMD_REF(bisect), "", @@ -1502,6 +1549,9 @@ CMD(bisect_status, "status", "", CMD_REF { switch (i->first) { + case bisect::start: + std::cerr << " start " << i->second << std::endl; + break; case bisect::good: std::cerr << " good " << i->second << std::endl; break; @@ -1588,69 +1638,68 @@ bisect_update(app_state & app, commands: project_t project(db); revision_loader loader(db); - revision_id source_id; - - E(app.opts.revision_selectors.size() < 2, origin::user, - F("bisect allows only one revision selector")); - parent_map parents; work.get_parent_rosters(db, parents); E(parents.size() == 1, origin::user, F("this command can only be used in a single-parent workspace")); + revision_id current_id = parent_id(*parents.begin()); + temp_node_id_source nis; - roster_t source_roster; - work.get_current_roster_shape(db, nis, source_roster); - work.update_current_roster_from_filesystem(source_roster); + roster_t current_roster; + work.get_current_roster_shape(db, nis, current_roster); + work.update_current_roster_from_filesystem(current_roster); - E(parent_roster(parents.begin()) == source_roster, origin::user, + E(parent_roster(parents.begin()) == current_roster, origin::user, F("'%s' can only be used in a workspace with no pending changes") % join_words(execid)()); - if (app.opts.revision_selectors.size() == 0) - { - source_id = parent_id(*parents.begin()); - } - else if (app.opts.revision_selectors.size() == 1) - { - set rids; - MM(rids); - complete(app.opts, app.lua, project, - (*app.opts.revision_selectors.begin())(), rids); + set marked_ids; - diagnose_ambiguous_expansion(project, - (*app.opts.revision_selectors.begin())(), - rids); - - I(rids.size() == 1); - source_id = *rids.begin(); - } + // mark the current or specified revisions as good, bad or skipped + if (app.opts.revision_selectors.empty()) + marked_ids.insert(current_id); else - I(false); + for (args_vector::const_iterator i = app.opts.revision_selectors.begin(); + i != app.opts.revision_selectors.end(); i++) + { + set rids; + MM(rids); + MM(*i); + complete(app.opts, app.lua, project, (*i)(), rids); + marked_ids.insert(rids.begin(), rids.end()); + } - // FIXME: this should possibly be delayed until it's known whether the new - // addition is sensible or not. i.e. ignore nonsense revs and don't change - // the bisect info - vector bisect; work.get_bisect_info(bisect); - bisect.push_back(make_pair(type, source_id)); + + if (bisect.empty()) + { + bisect.push_back(make_pair(bisect::start, current_id)); + P(F("bisection started at revision %s") % describe_revision(project, current_id)); + } + + // push back all marked revs with the appropriate type + for (set::const_iterator i = marked_ids.begin(); + i != marked_ids.end(); ++i) + bisect.push_back(make_pair(type, *i)); + work.put_bisect_info(bisect); - set first_good, good, first_bad, bad, skipped; + set good, bad, skipped; for (vector::const_iterator i = bisect.begin(); i != bisect.end(); ++i) { switch (i->first) { + case bisect::start: + // ignored the for the purposes of bisection + // used only by reset after bisection is complete + break; case bisect::good: - if (first_good.empty()) - first_good.insert(i->second); good.insert(i->second); break; case bisect::bad: - if (first_bad.empty()) - first_bad.insert(i->second); bad.insert(i->second); break; case bisect::skipped: @@ -1661,56 +1710,77 @@ bisect_update(app_state & app, commands: if (good.empty() && !bad.empty()) { - P(F("%d bad revisions selected; specify good revisions to start search") - % bad.size()); + P(F("bisecting revisions; %d good; %d bad; %d skipped; specify good revisions to start search") + % good.size() % bad.size() % skipped.size()); return; } else if (!good.empty() && bad.empty()) { - P(F("%d good revisions selected; specify bad revisions to start search") - % good.size()); + P(F("bisecting revisions; %d good; %d bad; %d skipped; specify bad revisions to start search") + % good.size() % bad.size() % skipped.size()); return; } I(!good.empty()); I(!bad.empty()); - // the initial set of revisions to be searched are those from the first - // good revision to the first bad revision. this clamps the search set - // between these two revisions. + // the initial set of revisions to be searched is the intersection between + // the good revisions and their descendants and the bad revisions and + // their ancestors. this clamps the search set between these two sets of + // revisions. - // NOTE: it also presupposes that the search is looking for a good->bad + // NOTE: this also presupposes that the search is looking for a good->bad // transition rather than a bad->good transition. - loader.load_descendants(first_good); - loader.load_ancestors(first_bad); + set good_descendants(good), bad_ancestors(bad); + loader.load_descendants(good_descendants); + loader.load_ancestors(bad_ancestors); set search; - set_intersection(first_bad.begin(), first_bad.end(), - first_good.begin(), first_good.end(), + set_intersection(good_descendants.begin(), good_descendants.end(), + bad_ancestors.begin(), bad_ancestors.end(), inserter(search, search.end())); - // good revisions and their ancestors are removed from the search set. - // bad revisions and their descendants are removed from the search set. - // skipped revisions are removed from the search set. + // the searchable set of revisions excludes those explicitly skipped - loader.load_ancestors(good); - loader.load_descendants(bad); + set searchable; + set_difference(search.begin(), search.end(), + skipped.begin(), skipped.end(), + inserter(searchable, searchable.begin())); + // partition the searchable set into three subsets + // - known good revisions + // - remaining revisions + // - known bad revisions + + set good_ancestors(good), bad_descendants(bad); + loader.load_ancestors(good_ancestors); + loader.load_descendants(bad_descendants); + + set known_good; + set_intersection(searchable.begin(), searchable.end(), + good_ancestors.begin(), good_ancestors.end(), + inserter(known_good, known_good.end())); + + set known_bad; + set_intersection(searchable.begin(), searchable.end(), + bad_descendants.begin(), bad_descendants.end(), + inserter(known_bad, known_bad.end())); + + // remove known good and known bad revisions from the searchable set + set removed; - removed.insert(good.begin(), good.end()); - removed.insert(bad.begin(), bad.end()); - removed.insert(skipped.begin(), skipped.end()); + set_union(known_good.begin(), known_good.end(), + known_bad.begin(), known_bad.end(), + inserter(removed, removed.begin())); set remaining; - set_difference(search.begin(), search.end(), + set_difference(searchable.begin(), searchable.end(), removed.begin(), removed.end(), inserter(remaining, remaining.end())); - P(FP("bisecting %d revision; %d good; %d bad; %d skipped; %d remaining", - "bisecting %d revisions; %d good; %d bad; %d skipped; %d remaining", - search.size()) - % search.size() % good.size() % bad.size() % skipped.size() + P(F("bisecting %d revisions; %d good; %d bad; %d skipped; %d remaining") + % search.size() % known_good.size() % known_bad.size() % skipped.size() % remaining.size()); // the bad revision that is the ancestor of all other bad revisions must @@ -1726,12 +1796,13 @@ bisect_update(app_state & app, commands: remaining.insert(bad0); // remove the current revision from the remaining set so it cannot be - // chosen as the next update target - remaining.erase(source_id); + // chosen as the next update target. this may remove the top bad revision + // and end the search. + remaining.erase(current_id); if (remaining.empty() && type == bisect::bad) { - P(F("ended at revision %s") % describe_revision(project, source_id)); + P(F("bisection finished at revision %s") % describe_revision(project, current_id)); return; } @@ -1744,25 +1815,25 @@ bisect_update(app_state & app, commands: vector candidates; toposort(db, remaining, candidates); - revision_id target_id = candidates[candidates.size()/2]; - P(F("updating to %s") % describe_revision(project, target_id)); + revision_id selected_id = candidates[candidates.size()/2]; + P(F("updating to %s") % describe_revision(project, selected_id)); - roster_t target_roster; - db.get_roster(target_id, target_roster); + roster_t selected_roster; + db.get_roster(selected_id, selected_roster); cset update; - make_cset(source_roster, target_roster, update); + make_cset(current_roster, selected_roster, update); content_merge_checkout_adaptor adaptor(db); - work.perform_content_update(source_roster, target_roster, update, adaptor); + work.perform_content_update(current_roster, selected_roster, update, adaptor); - revision_t workrev; + revision_t selected_rev; cset empty; - make_revision_for_workspace(target_id, empty, workrev); + make_revision_for_workspace(selected_id, empty, selected_rev); - // FIXME: work.put_update_id(source_id); // probably not wanted + // FIXME: work.put_update_id(current_id); // probably not wanted - work.put_work_rev(workrev); + work.put_work_rev(selected_rev); work.maybe_update_inodeprints(db); // FIXME: what about the workspace branch option? ============================================================ --- tests/bisect/__driver__.lua c89ddcd054d658ca6a5e838ab832ca649a4463ca +++ tests/bisect/__driver__.lua b4a1510356c9aec93b7fe1b7cd4bdc1e82bcc623 @@ -81,7 +81,9 @@ check(exists("head3")) -- test 1: addition of head3 is considered to be the error check(exists("head3")) +check(not exists("_MTN/bisect")) check(mtn("bisect", "bad"), 0, false, false) +check(exists("_MTN/bisect")) check(mtn("bisect", "good", "--revision", head1), 0, false, false) while rev ~= base_revision() do @@ -117,11 +119,14 @@ check(mtn("bisect", "reset"), 0, false, -- test 2: addition of left2 is considered to be the error check(mtn("bisect", "reset"), 0, false, false) -check(mtn("update"), 0, false, false) +check(not exists("_MTN/bisect")) +check(base_revision() == tail4) + rev = base_revision() check(exists("left2")) check(mtn("bisect", "good", "--revision", head1), 0, false, false) +check(exists("_MTN/bisect")) check(mtn("bisect", "bad"), 0, false, false) while rev ~= base_revision() do @@ -139,11 +144,14 @@ check(mtn("bisect", "reset"), 0, false, -- test 3: addition of right3 is considered to be the error check(mtn("bisect", "reset"), 0, false, false) -check(mtn("update"), 0, false, false) +check(not exists("_MTN/bisect")) +check(base_revision() == tail4) + rev = base_revision() check(exists("right3")) check(mtn("bisect", "good", "--revision", head1), 0, false, false) +check(exists("_MTN/bisect")) check(mtn("bisect", "bad"), 0, false, false) while rev ~= base_revision() do @@ -161,11 +169,14 @@ check(mtn("bisect", "reset"), 0, false, -- test 4: addition of tail1 is considered to be the error check(mtn("bisect", "reset"), 0, false, false) -check(mtn("update"), 0, false, false) +check(not exists("_MTN/bisect")) +check(base_revision() == tail4) + rev = base_revision() check(exists("tail1")) check(mtn("bisect", "good", "--revision", head1), 0, false, false) +check(exists("_MTN/bisect")) check(mtn("bisect", "bad"), 0, false, false) while rev ~= base_revision() do @@ -178,3 +189,30 @@ check(base_revision() == tail1) end check(base_revision() == tail1) + + +-- test 5: addition of left3 is considered to be the error +-- specify multiple good/bad/skipped revisions when starting search + +check(mtn("bisect", "reset"), 0, false, false) +check(base_revision() == tail4) +check(not exists("_MTN/bisect")) + +rev = base_revision() + +check(exists("tail1")) +check(mtn("bisect", "good", "--revision", head4, "--revision", right4), 0, false, false) +check(exists("_MTN/bisect")) +check(mtn("bisect", "skip", "--revision", tail1, "--revision", head4), 0, false, false) +check(mtn("bisect", "bad", "--revision", tail4, "--revision", left4), 0, false, false) + +while rev ~= base_revision() do + rev = base_revision() + if exists("left3") then + check(mtn("bisect", "bad"), 0, false, false) + else + check(mtn("bisect", "good"), 0, false, false) + end +end + +check(base_revision() == left3) ============================================================ --- work.cc 8b0f95dc56325eff7e25a1e9241e21ffbceb54ba +++ work.cc 2a82871555bb409e2b94593071c525cd60a921fb @@ -610,6 +610,7 @@ namespace syms namespace syms { + symbol const start("start"); symbol const good("good"); symbol const bad("bad"); symbol const skipped("skipped"); @@ -636,10 +637,16 @@ workspace::get_bisect_info(vectorfirst) { + case bisect::start: + st.push_binary_pair(syms::start, i->second.inner()); + break; + case bisect::good: st.push_binary_pair(syms::good, i->second.inner()); break; ============================================================ --- work.hh aca3f1a3e8668e0c9a2a679abb224567131fd620 +++ work.hh bda8c5daaad8dd6bff2afcd99836e7f09154ca6c @@ -83,8 +83,8 @@ namespace bisect namespace bisect { - enum type { good, bad, skipped }; - typedef std::pair entry; + enum type { start, good, bad, skipped }; + typedef std::pair entry; }; struct workspace