##// END OF EJS Templates
hbisect: add functions to return a label for a cset bisection status...
"Yann E. MORIN" -
r15154:aa2e908c default
parent child Browse files
Show More
@@ -1,222 +1,251 b''
1 1 # changelog bisection for mercurial
2 2 #
3 3 # Copyright 2007 Matt Mackall
4 4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
5 5 #
6 6 # Inspired by git bisect, extension skeleton taken from mq.py.
7 7 #
8 8 # This software may be used and distributed according to the terms of the
9 9 # GNU General Public License version 2 or any later version.
10 10
11 11 import os, error
12 12 from i18n import _
13 13 from node import short, hex
14 14 import util
15 15
16 16 def bisect(changelog, state):
17 17 """find the next node (if any) for testing during a bisect search.
18 18 returns a (nodes, number, good) tuple.
19 19
20 20 'nodes' is the final result of the bisect if 'number' is 0.
21 21 Otherwise 'number' indicates the remaining possible candidates for
22 22 the search and 'nodes' contains the next bisect target.
23 23 'good' is True if bisect is searching for a first good changeset, False
24 24 if searching for a first bad one.
25 25 """
26 26
27 27 clparents = changelog.parentrevs
28 28 skip = set([changelog.rev(n) for n in state['skip']])
29 29
30 30 def buildancestors(bad, good):
31 31 # only the earliest bad revision matters
32 32 badrev = min([changelog.rev(n) for n in bad])
33 33 goodrevs = [changelog.rev(n) for n in good]
34 34 goodrev = min(goodrevs)
35 35 # build visit array
36 36 ancestors = [None] * (len(changelog) + 1) # an extra for [-1]
37 37
38 38 # set nodes descended from goodrevs
39 39 for rev in goodrevs:
40 40 ancestors[rev] = []
41 41 for rev in xrange(goodrev + 1, len(changelog)):
42 42 for prev in clparents(rev):
43 43 if ancestors[prev] == []:
44 44 ancestors[rev] = []
45 45
46 46 # clear good revs from array
47 47 for rev in goodrevs:
48 48 ancestors[rev] = None
49 49 for rev in xrange(len(changelog), goodrev, -1):
50 50 if ancestors[rev] is None:
51 51 for prev in clparents(rev):
52 52 ancestors[prev] = None
53 53
54 54 if ancestors[badrev] is None:
55 55 return badrev, None
56 56 return badrev, ancestors
57 57
58 58 good = False
59 59 badrev, ancestors = buildancestors(state['bad'], state['good'])
60 60 if not ancestors: # looking for bad to good transition?
61 61 good = True
62 62 badrev, ancestors = buildancestors(state['good'], state['bad'])
63 63 bad = changelog.node(badrev)
64 64 if not ancestors: # now we're confused
65 65 if len(state['bad']) == 1 and len(state['good']) == 1:
66 66 raise util.Abort(_("starting revisions are not directly related"))
67 67 raise util.Abort(_("inconsistent state, %s:%s is good and bad")
68 68 % (badrev, short(bad)))
69 69
70 70 # build children dict
71 71 children = {}
72 72 visit = [badrev]
73 73 candidates = []
74 74 while visit:
75 75 rev = visit.pop(0)
76 76 if ancestors[rev] == []:
77 77 candidates.append(rev)
78 78 for prev in clparents(rev):
79 79 if prev != -1:
80 80 if prev in children:
81 81 children[prev].append(rev)
82 82 else:
83 83 children[prev] = [rev]
84 84 visit.append(prev)
85 85
86 86 candidates.sort()
87 87 # have we narrowed it down to one entry?
88 88 # or have all other possible candidates besides 'bad' have been skipped?
89 89 tot = len(candidates)
90 90 unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
91 91 if tot == 1 or not unskipped:
92 92 return ([changelog.node(rev) for rev in candidates], 0, good)
93 93 perfect = tot // 2
94 94
95 95 # find the best node to test
96 96 best_rev = None
97 97 best_len = -1
98 98 poison = set()
99 99 for rev in candidates:
100 100 if rev in poison:
101 101 # poison children
102 102 poison.update(children.get(rev, []))
103 103 continue
104 104
105 105 a = ancestors[rev] or [rev]
106 106 ancestors[rev] = None
107 107
108 108 x = len(a) # number of ancestors
109 109 y = tot - x # number of non-ancestors
110 110 value = min(x, y) # how good is this test?
111 111 if value > best_len and rev not in skip:
112 112 best_len = value
113 113 best_rev = rev
114 114 if value == perfect: # found a perfect candidate? quit early
115 115 break
116 116
117 117 if y < perfect and rev not in skip: # all downhill from here?
118 118 # poison children
119 119 poison.update(children.get(rev, []))
120 120 continue
121 121
122 122 for c in children.get(rev, []):
123 123 if ancestors[c]:
124 124 ancestors[c] = list(set(ancestors[c] + a))
125 125 else:
126 126 ancestors[c] = a + [c]
127 127
128 128 assert best_rev is not None
129 129 best_node = changelog.node(best_rev)
130 130
131 131 return ([best_node], tot, good)
132 132
133 133
134 134 def load_state(repo):
135 135 state = {'good': [], 'bad': [], 'skip': []}
136 136 if os.path.exists(repo.join("bisect.state")):
137 137 for l in repo.opener("bisect.state"):
138 138 kind, node = l[:-1].split()
139 139 node = repo.lookup(node)
140 140 if kind not in state:
141 141 raise util.Abort(_("unknown bisect kind %s") % kind)
142 142 state[kind].append(node)
143 143 return state
144 144
145 145
146 146 def save_state(repo, state):
147 147 f = repo.opener("bisect.state", "w", atomictemp=True)
148 148 wlock = repo.wlock()
149 149 try:
150 150 for kind in state:
151 151 for node in state[kind]:
152 152 f.write("%s %s\n" % (kind, hex(node)))
153 153 f.close()
154 154 finally:
155 155 wlock.release()
156 156
157 157 def get(repo, status):
158 158 """
159 159 Return a list of revision(s) that match the given status:
160 160
161 161 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
162 162 - ``goods``, ``bads`` : csets topologicaly good/bad
163 163 - ``range`` : csets taking part in the bisection
164 164 - ``pruned`` : csets that are goods, bads or skipped
165 165 - ``untested`` : csets whose fate is yet unknown
166 166 - ``ignored`` : csets ignored due to DAG topology
167 167 """
168 168 state = load_state(repo)
169 169 if status in ('good', 'bad', 'skip'):
170 170 return [repo.changelog.rev(n) for n in state[status]]
171 171 else:
172 172 # In the floowing sets, we do *not* call 'bisect()' with more
173 173 # than one level of recusrsion, because that can be very, very
174 174 # time consuming. Instead, we always develop the expression as
175 175 # much as possible.
176 176
177 177 # 'range' is all csets that make the bisection:
178 178 # - have a good ancestor and a bad descendant, or conversely
179 179 # that's because the bisection can go either way
180 180 range = '( bisect(bad)::bisect(good) | bisect(good)::bisect(bad) )'
181 181
182 182 _t = [c.rev() for c in repo.set('bisect(good)::bisect(bad)')]
183 183 # The sets of topologically good or bad csets
184 184 if len(_t) == 0:
185 185 # Goods are topologically after bads
186 186 goods = 'bisect(good)::' # Pruned good csets
187 187 bads = '::bisect(bad)' # Pruned bad csets
188 188 else:
189 189 # Goods are topologically before bads
190 190 goods = '::bisect(good)' # Pruned good csets
191 191 bads = 'bisect(bad)::' # Pruned bad csets
192 192
193 193 # 'pruned' is all csets whose fate is already known: good, bad, skip
194 194 skips = 'bisect(skip)' # Pruned skipped csets
195 195 pruned = '( (%s) | (%s) | (%s) )' % (goods, bads, skips)
196 196
197 197 # 'untested' is all cset that are- in 'range', but not in 'pruned'
198 198 untested = '( (%s) - (%s) )' % (range, pruned)
199 199
200 200 # 'ignored' is all csets that were not used during the bisection
201 201 # due to DAG topology, but may however have had an impact.
202 202 # Eg., a branch merged between bads and goods, but whose branch-
203 203 # point is out-side of the range.
204 204 iba = '::bisect(bad) - ::bisect(good)' # Ignored bads' ancestors
205 205 iga = '::bisect(good) - ::bisect(bad)' # Ignored goods' ancestors
206 206 ignored = '( ( (%s) | (%s) ) - (%s) )' % (iba, iga, range)
207 207
208 208 if status == 'range':
209 209 return [c.rev() for c in repo.set(range)]
210 210 elif status == 'pruned':
211 211 return [c.rev() for c in repo.set(pruned)]
212 212 elif status == 'untested':
213 213 return [c.rev() for c in repo.set(untested)]
214 214 elif status == 'ignored':
215 215 return [c.rev() for c in repo.set(ignored)]
216 216 elif status == "goods":
217 217 return [c.rev() for c in repo.set(goods)]
218 218 elif status == "bads":
219 219 return [c.rev() for c in repo.set(bads)]
220 220
221 221 else:
222 222 raise error.ParseError(_('invalid bisect state'))
223
224 def label(repo, node, short=False):
225 rev = repo.changelog.rev(node)
226
227 # Try explicit sets
228 if rev in get(repo, 'good'):
229 return _('good')
230 if rev in get(repo, 'bad'):
231 return _('bad')
232 if rev in get(repo, 'skip'):
233 return _('skipped')
234 if rev in get(repo, 'untested'):
235 return _('untested')
236 if rev in get(repo, 'ignored'):
237 return _('ignored')
238
239 # Try implicit sets
240 if rev in get(repo, 'goods'):
241 return _('good (implicit)')
242 if rev in get(repo, 'bads'):
243 return _('bad (implicit)')
244
245 return None
246
247 def shortlabel(label):
248 if label:
249 return label[0].upper()
250
251 return None
General Comments 0
You need to be logged in to leave comments. Login now