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