File: update.cc

package info (click to toggle)
monotone 0.18-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 16,440 kB
  • ctags: 13,394
  • sloc: sh: 130,618; ansic: 70,657; cpp: 51,980; perl: 421; makefile: 359; python: 184; lisp: 132; sql: 83
file content (165 lines) | stat: -rw-r--r-- 5,149 bytes parent folder | download
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
// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com>
// copyright (C) 2004 Nathaniel Smith <njs@pobox.com>
// all rights reserved.
// licensed to the public under the terms of the GNU GPL (>= 2)
// see the file COPYING for details

#include <set>
#include <map>
#include <vector>
#include <boost/lexical_cast.hpp>

#include "app_state.hh"
#include "database.hh"
#include "manifest.hh"
#include "sanity.hh"
#include "cert.hh"
#include "transforms.hh"
#include "ui.hh"
#include "update.hh"
#include "vocab.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 boost::lexical_cast;

using namespace std;

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 & e)
        {
          W(F("failed to decode boolean testresult cert value '%s'\n") % 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(F("Considering update target %s\n") % 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(F("%s not in branch %s\n") % 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(F("%s is acceptable update candidate\n") % target);
      return true;
    }
  else
    {
      L(F("%s has unacceptable test results\n") % 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 insert 'base' into the candidate set, because the way we signal 'no
  // update necessary' is to return a set containing just it.
  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.branch_name() != "",
    F("cannot determine branch for update"));
  I(!null_id(base_ident));

  calculate_update_set(base_ident, cert_value(app.branch_name()),
                       app, candidates);
}