##// END OF EJS Templates
bisect: faster merging
Matt Mackall -
r5774:c850a864 default
parent child Browse files
Show More
@@ -1,194 +1,192 b''
1 1 # bisect extension for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 4 # Inspired by git bisect, extension skeleton taken from mq.py.
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from mercurial.i18n import _
10 10 from mercurial import hg, util, cmdutil
11 11 import os
12 12
13 13 def _bisect(changelog, state):
14 14 clparents = changelog.parentrevs
15 15 # only the earliest bad revision matters
16 16 badrev = min([changelog.rev(n) for n in state['bad']])
17 17 bad = changelog.node(badrev)
18 18 skip = dict.fromkeys([changelog.rev(n) for n in state['skip']])
19 19
20 20 # build ancestors array
21 21 ancestors = [[]] * (changelog.count() + 1) # an extra for [-1]
22 22
23 23 # clear good revs from array
24 24 for node in state['good']:
25 25 ancestors[changelog.rev(node)] = None
26 26 for rev in xrange(changelog.count(), -1, -1):
27 27 if ancestors[rev] is None:
28 28 for prev in clparents(rev):
29 29 ancestors[prev] = None
30 30
31 31 if ancestors[badrev] is None:
32 32 raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
33 33 % (badrev, hg.short(bad)))
34 34
35 35 # build children dict
36 36 children = {}
37 37 visit = [badrev]
38 38 candidates = []
39 39 while visit:
40 40 rev = visit.pop(0)
41 41 if ancestors[rev] == []:
42 42 candidates.append(rev)
43 43 for prev in clparents(rev):
44 44 if prev != -1:
45 45 if prev in children:
46 46 children[prev].append(rev)
47 47 else:
48 48 children[prev] = [rev]
49 49 visit.append(prev)
50 50
51 51 candidates.sort()
52 52 # have we narrowed it down to one entry?
53 53 tot = len(candidates)
54 54 if tot == 1:
55 55 return (bad, 0)
56 56 perfect = tot / 2
57 57
58 58 # find the best node to test
59 59 best_rev = None
60 60 best_len = -1
61 61 poison = {}
62 62 for rev in candidates:
63 63 if rev in poison:
64 64 for c in children.get(rev, []):
65 65 poison[c] = True # poison children
66 66 continue
67 67
68 68 a = ancestors[rev] or [rev]
69 69 ancestors[rev] = None
70 70
71 71 x = len(a) # number of ancestors
72 72 y = tot - x # number of non-ancestors
73 73 value = min(x, y) # how good is this test?
74 74 if value > best_len and rev not in skip:
75 75 best_len = value
76 76 best_rev = rev
77 77 if value == perfect: # found a perfect candidate? quit early
78 78 break
79 79
80 80 if y < perfect: # all downhill from here?
81 81 for c in children.get(rev, []):
82 82 poison[c] = True # poison children
83 83 continue
84 84
85 85 for c in children.get(rev, []):
86 86 if ancestors[c]:
87 s = dict.fromkeys(ancestors[c])
88 s.update(dict.fromkeys(a))
89 ancestors[c] = s.keys()
87 ancestors[c] = dict.fromkeys(ancestors[c] + a).keys()
90 88 else:
91 89 ancestors[c] = a + [c]
92 90
93 91 assert best_rev is not None
94 92 best_node = changelog.node(best_rev)
95 93
96 94 return (best_node, tot)
97 95
98 96 def bisect(ui, repo, rev=None, extra=None,
99 97 reset=None, good=None, bad=None, skip=None, noupdate=None):
100 98 """Subdivision search of changesets
101 99
102 100 This extension helps to find changesets which introduce problems.
103 101 To use, mark the earliest changeset you know exhibits the problem
104 102 as bad, then mark the latest changeset which is free from the problem
105 103 as good. Bisect will update your working directory to a revision for
106 104 testing. Once you have performed tests, mark the working directory
107 105 as bad or good and bisect will either update to another candidate
108 106 changeset or announce that it has found the bad revision.
109 107
110 108 Note: bisect expects bad revisions to be descendants of good revisions.
111 109 If you are looking for the point at which a problem was fixed, then make
112 110 the problem-free state "bad" and the problematic state "good."
113 111
114 112 """
115 113 # backward compatibility
116 114 if rev in "good bad reset init".split():
117 115 ui.warn(_("(use of 'hg bisect <cmd>' is deprecated)\n"))
118 116 cmd, rev, extra = rev, extra, None
119 117 if cmd == "good":
120 118 good = True
121 119 elif cmd == "bad":
122 120 bad = True
123 121 else:
124 122 reset = True
125 123 elif extra or good + bad + skip + reset > 1:
126 124 raise util.Abort("Incompatible arguments")
127 125
128 126 if reset:
129 127 p = repo.join("bisect.state")
130 128 if os.path.exists(p):
131 129 os.unlink(p)
132 130 return
133 131
134 132 # load state
135 133 state = {'good': [], 'bad': [], 'skip': []}
136 134 if os.path.exists(repo.join("bisect.state")):
137 135 for l in repo.opener("bisect.state"):
138 136 kind, node = l[:-1].split()
139 137 node = repo.lookup(node)
140 138 if kind not in state:
141 139 raise util.Abort(_("unknown bisect kind %s") % kind)
142 140 state[kind].append(node)
143 141
144 142 # update state
145 143 node = repo.lookup(rev or '.')
146 144 if good:
147 145 state['good'].append(node)
148 146 elif bad:
149 147 state['bad'].append(node)
150 148 elif skip:
151 149 state['skip'].append(node)
152 150
153 151 # save state
154 152 f = repo.opener("bisect.state", "w", atomictemp=True)
155 153 wlock = repo.wlock()
156 154 try:
157 155 for kind in state:
158 156 for node in state[kind]:
159 157 f.write("%s %s\n" % (kind, hg.hex(node)))
160 158 f.rename()
161 159 finally:
162 160 del wlock
163 161
164 162 if not state['good'] or not state['bad']:
165 163 return
166 164
167 165 # actually bisect
168 166 node, changesets = _bisect(repo.changelog, state)
169 167 if changesets == 0:
170 168 ui.write(_("The first bad revision is:\n"))
171 169 displayer = cmdutil.show_changeset(ui, repo, {})
172 170 displayer.show(changenode=node)
173 171 elif node is not None:
174 172 # compute the approximate number of remaining tests
175 173 tests, size = 0, 2
176 174 while size <= changesets:
177 175 tests, size = tests + 1, size * 2
178 176 rev = repo.changelog.rev(node)
179 177 ui.write(_("Testing changeset %s:%s "
180 178 "(%s changesets remaining, ~%s tests)\n")
181 179 % (rev, hg.short(node), changesets, tests))
182 180 if not noupdate:
183 181 cmdutil.bail_if_changed(repo)
184 182 return hg.clean(repo, node)
185 183
186 184 cmdtable = {
187 185 "bisect": (bisect,
188 186 [('r', 'reset', False, _('reset bisect state')),
189 187 ('g', 'good', False, _('mark changeset good')),
190 188 ('b', 'bad', False, _('mark changeset bad')),
191 189 ('s', 'skip', False, _('skip testing changeset')),
192 190 ('U', 'noupdate', False, _('do not update to target'))],
193 191 _("hg bisect [-gbsr] [REV]"))
194 192 }
General Comments 0
You need to be logged in to leave comments. Login now