##// END OF EJS Templates
bisect: better message for unrelated starting revisions
Matt Mackall -
r12005:c6b1be67 stable
parent child Browse files
Show More
@@ -1,153 +1,155
1 # changelog bisection for mercurial
1 # changelog bisection for mercurial
2 #
2 #
3 # Copyright 2007 Matt Mackall
3 # Copyright 2007 Matt Mackall
4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
4 # Copyright 2005, 2006 Benoit Boissinot <benoit.boissinot@ens-lyon.org>
5 #
5 #
6 # Inspired by git bisect, extension skeleton taken from mq.py.
6 # Inspired by git bisect, extension skeleton taken from mq.py.
7 #
7 #
8 # This software may be used and distributed according to the terms of the
8 # This software may be used and distributed according to the terms of the
9 # GNU General Public License version 2 or any later version.
9 # GNU General Public License version 2 or any later version.
10
10
11 import os
11 import os
12 from i18n import _
12 from i18n import _
13 from node import short, hex
13 from node import short, hex
14 import util
14 import util
15
15
16 def bisect(changelog, state):
16 def bisect(changelog, state):
17 """find the next node (if any) for testing during a bisect search.
17 """find the next node (if any) for testing during a bisect search.
18 returns a (nodes, number, good) tuple.
18 returns a (nodes, number, good) tuple.
19
19
20 'nodes' is the final result of the bisect if 'number' is 0.
20 'nodes' is the final result of the bisect if 'number' is 0.
21 Otherwise 'number' indicates the remaining possible candidates for
21 Otherwise 'number' indicates the remaining possible candidates for
22 the search and 'nodes' contains the next bisect target.
22 the search and 'nodes' contains the next bisect target.
23 'good' is True if bisect is searching for a first good changeset, False
23 'good' is True if bisect is searching for a first good changeset, False
24 if searching for a first bad one.
24 if searching for a first bad one.
25 """
25 """
26
26
27 clparents = changelog.parentrevs
27 clparents = changelog.parentrevs
28 skip = set([changelog.rev(n) for n in state['skip']])
28 skip = set([changelog.rev(n) for n in state['skip']])
29
29
30 def buildancestors(bad, good):
30 def buildancestors(bad, good):
31 # only the earliest bad revision matters
31 # only the earliest bad revision matters
32 badrev = min([changelog.rev(n) for n in bad])
32 badrev = min([changelog.rev(n) for n in bad])
33 goodrevs = [changelog.rev(n) for n in good]
33 goodrevs = [changelog.rev(n) for n in good]
34 goodrev = min(goodrevs)
34 goodrev = min(goodrevs)
35 # build visit array
35 # build visit array
36 ancestors = [None] * (len(changelog) + 1) # an extra for [-1]
36 ancestors = [None] * (len(changelog) + 1) # an extra for [-1]
37
37
38 # set nodes descended from goodrev
38 # set nodes descended from goodrev
39 ancestors[goodrev] = []
39 ancestors[goodrev] = []
40 for rev in xrange(goodrev + 1, len(changelog)):
40 for rev in xrange(goodrev + 1, len(changelog)):
41 for prev in clparents(rev):
41 for prev in clparents(rev):
42 if ancestors[prev] == []:
42 if ancestors[prev] == []:
43 ancestors[rev] = []
43 ancestors[rev] = []
44
44
45 # clear good revs from array
45 # clear good revs from array
46 for node in goodrevs:
46 for node in goodrevs:
47 ancestors[node] = None
47 ancestors[node] = None
48 for rev in xrange(len(changelog), -1, -1):
48 for rev in xrange(len(changelog), -1, -1):
49 if ancestors[rev] is None:
49 if ancestors[rev] is None:
50 for prev in clparents(rev):
50 for prev in clparents(rev):
51 ancestors[prev] = None
51 ancestors[prev] = None
52
52
53 if ancestors[badrev] is None:
53 if ancestors[badrev] is None:
54 return badrev, None
54 return badrev, None
55 return badrev, ancestors
55 return badrev, ancestors
56
56
57 good = 0
57 good = 0
58 badrev, ancestors = buildancestors(state['bad'], state['good'])
58 badrev, ancestors = buildancestors(state['bad'], state['good'])
59 if not ancestors: # looking for bad to good transition?
59 if not ancestors: # looking for bad to good transition?
60 good = 1
60 good = 1
61 badrev, ancestors = buildancestors(state['good'], state['bad'])
61 badrev, ancestors = buildancestors(state['good'], state['bad'])
62 bad = changelog.node(badrev)
62 bad = changelog.node(badrev)
63 if not ancestors: # now we're confused
63 if not ancestors: # now we're confused
64 if len(state['bad']) == 1 and len(state['good']) == 1:
65 raise util.Abort(_("starting revisions are not directly related"))
64 raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
66 raise util.Abort(_("Inconsistent state, %s:%s is good and bad")
65 % (badrev, short(bad)))
67 % (badrev, short(bad)))
66
68
67 # build children dict
69 # build children dict
68 children = {}
70 children = {}
69 visit = [badrev]
71 visit = [badrev]
70 candidates = []
72 candidates = []
71 while visit:
73 while visit:
72 rev = visit.pop(0)
74 rev = visit.pop(0)
73 if ancestors[rev] == []:
75 if ancestors[rev] == []:
74 candidates.append(rev)
76 candidates.append(rev)
75 for prev in clparents(rev):
77 for prev in clparents(rev):
76 if prev != -1:
78 if prev != -1:
77 if prev in children:
79 if prev in children:
78 children[prev].append(rev)
80 children[prev].append(rev)
79 else:
81 else:
80 children[prev] = [rev]
82 children[prev] = [rev]
81 visit.append(prev)
83 visit.append(prev)
82
84
83 candidates.sort()
85 candidates.sort()
84 # have we narrowed it down to one entry?
86 # have we narrowed it down to one entry?
85 # or have all other possible candidates besides 'bad' have been skipped?
87 # or have all other possible candidates besides 'bad' have been skipped?
86 tot = len(candidates)
88 tot = len(candidates)
87 unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
89 unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
88 if tot == 1 or not unskipped:
90 if tot == 1 or not unskipped:
89 return ([changelog.node(rev) for rev in candidates], 0, good)
91 return ([changelog.node(rev) for rev in candidates], 0, good)
90 perfect = tot // 2
92 perfect = tot // 2
91
93
92 # find the best node to test
94 # find the best node to test
93 best_rev = None
95 best_rev = None
94 best_len = -1
96 best_len = -1
95 poison = set()
97 poison = set()
96 for rev in candidates:
98 for rev in candidates:
97 if rev in poison:
99 if rev in poison:
98 # poison children
100 # poison children
99 poison.update(children.get(rev, []))
101 poison.update(children.get(rev, []))
100 continue
102 continue
101
103
102 a = ancestors[rev] or [rev]
104 a = ancestors[rev] or [rev]
103 ancestors[rev] = None
105 ancestors[rev] = None
104
106
105 x = len(a) # number of ancestors
107 x = len(a) # number of ancestors
106 y = tot - x # number of non-ancestors
108 y = tot - x # number of non-ancestors
107 value = min(x, y) # how good is this test?
109 value = min(x, y) # how good is this test?
108 if value > best_len and rev not in skip:
110 if value > best_len and rev not in skip:
109 best_len = value
111 best_len = value
110 best_rev = rev
112 best_rev = rev
111 if value == perfect: # found a perfect candidate? quit early
113 if value == perfect: # found a perfect candidate? quit early
112 break
114 break
113
115
114 if y < perfect and rev not in skip: # all downhill from here?
116 if y < perfect and rev not in skip: # all downhill from here?
115 # poison children
117 # poison children
116 poison.update(children.get(rev, []))
118 poison.update(children.get(rev, []))
117 continue
119 continue
118
120
119 for c in children.get(rev, []):
121 for c in children.get(rev, []):
120 if ancestors[c]:
122 if ancestors[c]:
121 ancestors[c] = list(set(ancestors[c] + a))
123 ancestors[c] = list(set(ancestors[c] + a))
122 else:
124 else:
123 ancestors[c] = a + [c]
125 ancestors[c] = a + [c]
124
126
125 assert best_rev is not None
127 assert best_rev is not None
126 best_node = changelog.node(best_rev)
128 best_node = changelog.node(best_rev)
127
129
128 return ([best_node], tot, good)
130 return ([best_node], tot, good)
129
131
130
132
131 def load_state(repo):
133 def load_state(repo):
132 state = {'good': [], 'bad': [], 'skip': []}
134 state = {'good': [], 'bad': [], 'skip': []}
133 if os.path.exists(repo.join("bisect.state")):
135 if os.path.exists(repo.join("bisect.state")):
134 for l in repo.opener("bisect.state"):
136 for l in repo.opener("bisect.state"):
135 kind, node = l[:-1].split()
137 kind, node = l[:-1].split()
136 node = repo.lookup(node)
138 node = repo.lookup(node)
137 if kind not in state:
139 if kind not in state:
138 raise util.Abort(_("unknown bisect kind %s") % kind)
140 raise util.Abort(_("unknown bisect kind %s") % kind)
139 state[kind].append(node)
141 state[kind].append(node)
140 return state
142 return state
141
143
142
144
143 def save_state(repo, state):
145 def save_state(repo, state):
144 f = repo.opener("bisect.state", "w", atomictemp=True)
146 f = repo.opener("bisect.state", "w", atomictemp=True)
145 wlock = repo.wlock()
147 wlock = repo.wlock()
146 try:
148 try:
147 for kind in state:
149 for kind in state:
148 for node in state[kind]:
150 for node in state[kind]:
149 f.write("%s %s\n" % (kind, hex(node)))
151 f.write("%s %s\n" % (kind, hex(node)))
150 f.rename()
152 f.rename()
151 finally:
153 finally:
152 wlock.release()
154 wlock.release()
153
155
General Comments 0
You need to be logged in to leave comments. Login now