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