##// END OF EJS Templates
hbisect: confine loop to the relevant interval...
Alexander Krauss -
r14893:01e00916 default
parent child Browse files
Show More
@@ -1,155 +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 for rev in xrange(len(changelog), -1, -1):
48 for rev in xrange(len(changelog), goodrev, -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 = False
58 58 badrev, ancestors = buildancestors(state['bad'], state['good'])
59 59 if not ancestors: # looking for bad to good transition?
60 60 good = True
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 64 if len(state['bad']) == 1 and len(state['good']) == 1:
65 65 raise util.Abort(_("starting revisions are not directly related"))
66 66 raise util.Abort(_("inconsistent state, %s:%s is good and bad")
67 67 % (badrev, short(bad)))
68 68
69 69 # build children dict
70 70 children = {}
71 71 visit = [badrev]
72 72 candidates = []
73 73 while visit:
74 74 rev = visit.pop(0)
75 75 if ancestors[rev] == []:
76 76 candidates.append(rev)
77 77 for prev in clparents(rev):
78 78 if prev != -1:
79 79 if prev in children:
80 80 children[prev].append(rev)
81 81 else:
82 82 children[prev] = [rev]
83 83 visit.append(prev)
84 84
85 85 candidates.sort()
86 86 # have we narrowed it down to one entry?
87 87 # or have all other possible candidates besides 'bad' have been skipped?
88 88 tot = len(candidates)
89 89 unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
90 90 if tot == 1 or not unskipped:
91 91 return ([changelog.node(rev) for rev in candidates], 0, good)
92 92 perfect = tot // 2
93 93
94 94 # find the best node to test
95 95 best_rev = None
96 96 best_len = -1
97 97 poison = set()
98 98 for rev in candidates:
99 99 if rev in poison:
100 100 # poison children
101 101 poison.update(children.get(rev, []))
102 102 continue
103 103
104 104 a = ancestors[rev] or [rev]
105 105 ancestors[rev] = None
106 106
107 107 x = len(a) # number of ancestors
108 108 y = tot - x # number of non-ancestors
109 109 value = min(x, y) # how good is this test?
110 110 if value > best_len and rev not in skip:
111 111 best_len = value
112 112 best_rev = rev
113 113 if value == perfect: # found a perfect candidate? quit early
114 114 break
115 115
116 116 if y < perfect and rev not in skip: # all downhill from here?
117 117 # poison children
118 118 poison.update(children.get(rev, []))
119 119 continue
120 120
121 121 for c in children.get(rev, []):
122 122 if ancestors[c]:
123 123 ancestors[c] = list(set(ancestors[c] + a))
124 124 else:
125 125 ancestors[c] = a + [c]
126 126
127 127 assert best_rev is not None
128 128 best_node = changelog.node(best_rev)
129 129
130 130 return ([best_node], tot, good)
131 131
132 132
133 133 def load_state(repo):
134 134 state = {'good': [], 'bad': [], 'skip': []}
135 135 if os.path.exists(repo.join("bisect.state")):
136 136 for l in repo.opener("bisect.state"):
137 137 kind, node = l[:-1].split()
138 138 node = repo.lookup(node)
139 139 if kind not in state:
140 140 raise util.Abort(_("unknown bisect kind %s") % kind)
141 141 state[kind].append(node)
142 142 return state
143 143
144 144
145 145 def save_state(repo, state):
146 146 f = repo.opener("bisect.state", "w", atomictemp=True)
147 147 wlock = repo.wlock()
148 148 try:
149 149 for kind in state:
150 150 for node in state[kind]:
151 151 f.write("%s %s\n" % (kind, hex(node)))
152 152 f.rename()
153 153 finally:
154 154 wlock.release()
155 155
General Comments 0
You need to be logged in to leave comments. Login now