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