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