1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
//
// This program is made available under the GNU GPL version 2.0 or
// greater. See the accompanying file COPYING for details.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE.
#include <set>
#include <map>
#include <vector>
#include <boost/lexical_cast.hpp>
#include "app_state.hh"
#include "database.hh"
#include "sanity.hh"
#include "cert.hh"
#include "transforms.hh"
#include "ui.hh"
#include "update.hh"
#include "vocab.hh"
#include "revision.hh"
// these functions just encapsulate the (somewhat complex) logic behind
// picking an update target. the actual updating takes place in
// commands.cc, along with most other file-modifying actions.
// the algorithm is:
// - do a depth-first traversal of the current revision's descendent set
// - for each revision, check to see whether it is
// - in the correct branch
// - has acceptable test results
// and add it to the candidate set if so
// - this gives a set containing every descendent that we might want to
// update to.
// - run erase_ancestors on that set, to get just the heads; this is our
// real candidate set.
// the idea is that this should be correct even in the presence of
// discontinuous branches, test results that go from good to bad to good to
// bad to good, etc.
// it may be somewhat inefficient to use erase_ancestors here, but deal with
// that when and if the time comes...
using std::make_pair;
using std::map;
using std::set;
using std::vector;
using boost::lexical_cast;
static void
get_test_results_for_revision(revision_id const & id,
map<rsa_keypair_id, bool> & results,
app_state & app)
{
vector< revision<cert> > certs;
app.db.get_revision_certs(id, testresult_cert_name, certs);
erase_bogus_certs(certs, app);
for (vector< revision<cert> >::const_iterator i = certs.begin();
i != certs.end(); ++i)
{
cert_value cv;
decode_base64(i->inner().value, cv);
try
{
bool test_ok = lexical_cast<bool>(cv());
results.insert(make_pair(i->inner().key, test_ok));
}
catch(boost::bad_lexical_cast &)
{
W(F("failed to decode boolean testresult cert value '%s'") % cv);
}
}
}
static bool
acceptable_descendent(cert_value const & branch,
revision_id const & base,
map<rsa_keypair_id, bool> & base_results,
revision_id const & target,
app_state & app)
{
L(FL("Considering update target %s") % target);
// step 1: check the branch
base64<cert_value> val;
encode_base64(branch, val);
vector< revision<cert> > certs;
app.db.get_revision_certs(target, branch_cert_name, val, certs);
erase_bogus_certs(certs, app);
if (certs.empty())
{
L(FL("%s not in branch %s") % target % branch);
return false;
}
// step 2: check the testresults
map<rsa_keypair_id, bool> target_results;
get_test_results_for_revision(target, target_results, app);
if (app.lua.hook_accept_testresult_change(base_results, target_results))
{
L(FL("%s is acceptable update candidate") % target);
return true;
}
else
{
L(FL("%s has unacceptable test results") % target);
return false;
}
}
static void
calculate_update_set(revision_id const & base,
cert_value const & branch,
app_state & app,
set<revision_id> & candidates)
{
map<rsa_keypair_id, bool> base_results;
get_test_results_for_revision(base, base_results, app);
candidates.clear();
// we possibly insert base into the candidate set as well; returning a set
// containing just it means that we are up to date; returning an empty set
// means that there is no acceptable update.
if (acceptable_descendent(branch, base, base_results, base, app))
candidates.insert(base);
// keep a visited set to avoid repeating work
set<revision_id> visited;
set<revision_id> children;
vector<revision_id> to_traverse;
app.db.get_revision_children(base, children);
copy(children.begin(), children.end(), back_inserter(to_traverse));
while (!to_traverse.empty())
{
revision_id target = to_traverse.back();
to_traverse.pop_back();
// If we've traversed this id before via a different path, skip it.
if (visited.find(target) != visited.end())
continue;
visited.insert(target);
// then, possibly insert this revision as a candidate
if (acceptable_descendent(branch, base, base_results, target, app))
candidates.insert(target);
// and traverse its children as well
app.db.get_revision_children(target, children);
copy(children.begin(), children.end(), back_inserter(to_traverse));
}
erase_ancestors(candidates, app);
}
void pick_update_candidates(revision_id const & base_ident,
app_state & app,
set<revision_id> & candidates)
{
N(app.opts.branch_name() != "",
F("cannot determine branch for update"));
I(!null_id(base_ident));
calculate_update_set(base_ident, cert_value(app.opts.branch_name()),
app, candidates);
}
// Local Variables:
// mode: C++
// fill-column: 76
// c-file-style: "gnu"
// indent-tabs-mode: nil
// End:
// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:
|