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