##// END OF EJS Templates
Added copyright information to hbisect.py
Thomas Arendsen Hein -
r1861:65949d1c default
parent child Browse files
Show More
@@ -1,287 +1,290
1 1 # bisect extension for mercurial
2 2 #
3 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 # Inspired by git bisect, extension skeleton taken from mq.py.
5 #
3 6 # This software may be used and distributed according to the terms
4 7 # of the GNU General Public License, incorporated herein by reference.
5 8
6 9 from mercurial.demandload import demandload
7 10 demandload(globals(), "os sys sets mercurial:hg,util")
8 11
9 12 versionstr = "0.0.3"
10 13
11 14 def lookup_rev(ui, repo, rev=None):
12 15 """returns rev or the checked-out revision if rev is None"""
13 16 if not rev is None:
14 17 return repo.lookup(rev)
15 18 parents = [p for p in repo.dirstate.parents() if p != hg.nullid]
16 19 if len(parents) != 1:
17 20 ui.warn("unexpected number of parents\n")
18 21 ui.warn("please commit or revert\n")
19 22 sys.exit(1)
20 23 return parents.pop()
21 24
22 25 def check_clean(ui, repo):
23 26 modified, added, removed, deleted, unknown = repo.changes()
24 27 if modified or added or removed:
25 28 ui.warn("Repository is not clean, please commit or revert\n")
26 29 sys.exit(1)
27 30
28 31 class bisect(object):
29 32 """dichotomic search in the DAG of changesets"""
30 33 def __init__(self, ui, repo):
31 34 self.repo = repo
32 35 self.path = repo.join("bisect")
33 36 self.opener = util.opener(self.path)
34 37 self.ui = ui
35 38 self.goodrevs = []
36 39 self.badrev = None
37 40 self.good_dirty = 0
38 41 self.bad_dirty = 0
39 42 self.good_path = "good"
40 43 self.bad_path = "bad"
41 44
42 45 if os.path.exists(os.path.join(self.path, self.good_path)):
43 46 self.goodrevs = self.opener(self.good_path).read().splitlines()
44 47 self.goodrevs = [hg.bin(x) for x in self.goodrevs]
45 48 if os.path.exists(os.path.join(self.path, self.bad_path)):
46 49 r = self.opener(self.bad_path).read().splitlines()
47 50 if r:
48 51 self.badrev = hg.bin(r.pop(0))
49 52
50 53 def __del__(self):
51 54 if not os.path.isdir(self.path):
52 55 return
53 56 f = self.opener(self.good_path, "w")
54 57 f.write("\n".join([hg.hex(r) for r in self.goodrevs]))
55 58 if len(self.goodrevs) > 0:
56 59 f.write("\n")
57 60 f = self.opener(self.bad_path, "w")
58 61 if self.badrev:
59 62 f.write(hg.hex(self.badrev) + "\n")
60 63
61 64 def init(self):
62 65 """start a new bisection"""
63 66 if os.path.isdir(self.path):
64 67 self.ui.warn("bisect directory already exists\n")
65 68 return 1
66 69 os.mkdir(self.path)
67 70 check_clean(self.ui, self.repo)
68 71 return 0
69 72
70 73 def reset(self):
71 74 """finish a bisection"""
72 75 if os.path.isdir(self.path):
73 76 sl = [os.path.join(self.path, p)
74 77 for p in [self.bad_path, self.good_path]]
75 78 for s in sl:
76 79 if os.path.exists(s):
77 80 os.unlink(s)
78 81 os.rmdir(self.path)
79 82 # Not sure about this
80 83 #self.ui.write("Going back to tip\n")
81 84 #self.repo.update(self.repo.changelog.tip())
82 85 return 1
83 86
84 87 def num_ancestors(self, head=None, stop=None):
85 88 """
86 89 returns a dict with the mapping:
87 90 node -> number of ancestors (self included)
88 91 for all nodes who are ancestor of head and
89 92 not in stop.
90 93 """
91 94 if head is None:
92 95 head = self.badrev
93 96 return self.__ancestors_and_nb_ancestors(head, stop)[1]
94 97
95 98 def ancestors(self, head=None, stop=None):
96 99 """
97 100 returns the set of the ancestors of head (self included)
98 101 who are not in stop.
99 102 """
100 103 if head is None:
101 104 head = self.badrev
102 105 return self.__ancestors_and_nb_ancestors(head, stop)[0]
103 106
104 107 def __ancestors_and_nb_ancestors(self, head, stop=None):
105 108 """
106 109 if stop is None then ancestors of goodrevs are used as
107 110 lower limit.
108 111
109 112 returns (anc, n_child) where anc is the set of the ancestors of head
110 113 and n_child is a dictionary with the following mapping:
111 114 node -> number of ancestors (self included)
112 115 """
113 116 cl = self.repo.changelog
114 117 if not stop:
115 118 stop = sets.Set([])
116 119 for i in xrange(len(self.goodrevs)-1, -1, -1):
117 120 g = self.goodrevs[i]
118 121 if g in stop:
119 122 continue
120 123 stop.update(cl.reachable(g))
121 124 def num_children(a):
122 125 """
123 126 returns a dictionnary with the following mapping
124 127 node -> [number of children, empty set]
125 128 """
126 129 d = {a: [0, sets.Set([])]}
127 130 for i in xrange(cl.rev(a)+1):
128 131 n = cl.node(i)
129 132 if not d.has_key(n):
130 133 d[n] = [0, sets.Set([])]
131 134 parents = [p for p in cl.parents(n) if p != hg.nullid]
132 135 for p in parents:
133 136 d[p][0] += 1
134 137 return d
135 138
136 139 if head in stop:
137 140 self.ui.warn("Unconsistent state, %s is good and bad\n"
138 141 % hg.hex(head))
139 142 sys.exit(1)
140 143 n_child = num_children(head)
141 144 for i in xrange(cl.rev(head)+1):
142 145 n = cl.node(i)
143 146 parents = [p for p in cl.parents(n) if p != hg.nullid]
144 147 for p in parents:
145 148 n_child[p][0] -= 1
146 149 if not n in stop:
147 150 n_child[n][1].union_update(n_child[p][1])
148 151 if n_child[p][0] == 0:
149 152 n_child[p] = len(n_child[p][1])
150 153 if not n in stop:
151 154 n_child[n][1].add(n)
152 155 if n_child[n][0] == 0:
153 156 if n == head:
154 157 anc = n_child[n][1]
155 158 n_child[n] = len(n_child[n][1])
156 159 return anc, n_child
157 160
158 161 def next(self):
159 162 if not self.badrev:
160 163 self.ui.warn("You should give at least one bad\n")
161 164 sys.exit(1)
162 165 if not self.goodrevs:
163 166 self.ui.warn("No good revision given\n")
164 167 self.ui.warn("Assuming the first revision is good\n")
165 168 ancestors, num_ancestors = self.__ancestors_and_nb_ancestors(
166 169 self.badrev)
167 170 tot = len(ancestors)
168 171 if tot == 1:
169 172 if ancestors.pop() != self.badrev:
170 173 self.ui.warn("Could not find the first bad revision\n")
171 174 sys.exit(1)
172 175 self.ui.write(
173 176 "The first bad revision is : %s\n" % hg.hex(self.badrev))
174 177 sys.exit(0)
175 178 self.ui.write("%d revisions left\n" % tot)
176 179 best_rev = None
177 180 best_len = -1
178 181 for n in ancestors:
179 182 l = num_ancestors[n]
180 183 l = min(l, tot - l)
181 184 if l > best_len:
182 185 best_len = l
183 186 best_rev = n
184 187 return best_rev
185 188
186 189 def autonext(self):
187 190 """find and update to the next revision to test"""
188 191 check_clean(self.ui, self.repo)
189 192 rev = self.next()
190 193 self.ui.write("Now testing %s\n" % hg.hex(rev))
191 194 return self.repo.update(rev, force=True)
192 195
193 196 def good(self, rev):
194 197 self.goodrevs.append(rev)
195 198
196 199 def autogood(self, rev=None):
197 200 """mark revision as good and update to the next revision to test"""
198 201 check_clean(self.ui, self.repo)
199 202 rev = lookup_rev(self.ui, self.repo, rev)
200 203 self.good(rev)
201 204 if self.badrev:
202 205 self.autonext()
203 206
204 207 def bad(self, rev):
205 208 self.badrev = rev
206 209
207 210 def autobad(self, rev=None):
208 211 """mark revision as bad and update to the next revision to test"""
209 212 check_clean(self.ui, self.repo)
210 213 rev = lookup_rev(self.ui, self.repo, rev)
211 214 self.bad(rev)
212 215 if self.goodrevs:
213 216 self.autonext()
214 217
215 218 # should we put it in the class ?
216 219 def test(ui, repo, rev):
217 220 """test the bisection code"""
218 221 b = bisect(ui, repo)
219 222 rev = repo.lookup(rev)
220 223 ui.write("testing with rev %s\n" % hg.hex(rev))
221 224 anc = b.ancestors()
222 225 while len(anc) > 1:
223 226 if not rev in anc:
224 227 ui.warn("failure while bisecting\n")
225 228 sys.exit(1)
226 229 ui.write("it worked :)\n")
227 230 new_rev = b.next()
228 231 ui.write("choosing if good or bad\n")
229 232 if rev in b.ancestors(head=new_rev):
230 233 b.bad(new_rev)
231 234 ui.write("it is bad\n")
232 235 else:
233 236 b.good(new_rev)
234 237 ui.write("it is good\n")
235 238 anc = b.ancestors()
236 239 repo.update(new_rev, force=True)
237 240 for v in anc:
238 241 if v != rev:
239 242 ui.warn("fail to found cset! :(\n")
240 243 return 1
241 244 ui.write("Found bad cset: %s\n" % hg.hex(b.badrev))
242 245 ui.write("Everything is ok :)\n")
243 246 return 0
244 247
245 248 def bisect_run(ui, repo, cmd=None, *args):
246 249 """bisect extension: dichotomic search in the DAG of changesets
247 250 for subcommands see "hg bisect help\"
248 251 """
249 252 def help_(cmd=None, *args):
250 253 """show help for a given bisect subcommand or all subcommands"""
251 254 cmdtable = bisectcmdtable
252 255 if cmd:
253 256 doc = cmdtable[cmd][0].__doc__
254 257 synopsis = cmdtable[cmd][2]
255 258 ui.write(synopsis + "\n")
256 259 ui.write("\n" + doc + "\n")
257 260 return
258 261 ui.write("list of subcommands for the bisect extension\n\n")
259 262 cmds = cmdtable.keys()
260 263 cmds.sort()
261 264 m = max([len(c) for c in cmds])
262 265 for cmd in cmds:
263 266 doc = cmdtable[cmd][0].__doc__.splitlines(0)[0].rstrip()
264 267 ui.write(" %-*s %s\n" % (m, cmd, doc))
265 268
266 269 b = bisect(ui, repo)
267 270 bisectcmdtable = {
268 271 "init": (b.init, 0, "hg bisect init"),
269 272 "bad": (b.autobad, 1, "hg bisect bad [<rev>]"),
270 273 "good": (b.autogood, 1, "hg bisect good [<rev>]"),
271 274 "next": (b.autonext, 0, "hg bisect next"),
272 275 "reset": (b.reset, 0, "hg bisect reset"),
273 276 "help": (help_, 1, "hg bisect help [<subcommand>]"),
274 277 }
275 278
276 279 if not bisectcmdtable.has_key(cmd):
277 280 ui.warn("bisect: Unknown sub-command\n")
278 281 return help_()
279 282 if len(args) > bisectcmdtable[cmd][1]:
280 283 ui.warn("bisect: Too many arguments\n")
281 284 return help_()
282 285 return bisectcmdtable[cmd][0](*args)
283 286
284 287 cmdtable = {
285 288 "bisect": (bisect_run, [], "hg bisect [help|init|reset|next|good|bad]"),
286 289 #"bisect-test": (test, [], "hg bisect-test rev"),
287 290 }
General Comments 0
You need to be logged in to leave comments. Login now