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