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