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