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