##// END OF EJS Templates
hbisect: add two new revset descriptions: 'goods' and 'bads'...
"Yann E. MORIN" -
r15153:fa0a464e default
parent child Browse files
Show More
@@ -1,213 +1,222 b''
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, error
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 goodrevs
39 39 for rev in goodrevs:
40 40 ancestors[rev] = []
41 41 for rev in xrange(goodrev + 1, len(changelog)):
42 42 for prev in clparents(rev):
43 43 if ancestors[prev] == []:
44 44 ancestors[rev] = []
45 45
46 46 # clear good revs from array
47 47 for rev in goodrevs:
48 48 ancestors[rev] = None
49 49 for rev in xrange(len(changelog), goodrev, -1):
50 50 if ancestors[rev] is None:
51 51 for prev in clparents(rev):
52 52 ancestors[prev] = None
53 53
54 54 if ancestors[badrev] is None:
55 55 return badrev, None
56 56 return badrev, ancestors
57 57
58 58 good = False
59 59 badrev, ancestors = buildancestors(state['bad'], state['good'])
60 60 if not ancestors: # looking for bad to good transition?
61 61 good = True
62 62 badrev, ancestors = buildancestors(state['good'], state['bad'])
63 63 bad = changelog.node(badrev)
64 64 if not ancestors: # now we're confused
65 65 if len(state['bad']) == 1 and len(state['good']) == 1:
66 66 raise util.Abort(_("starting revisions are not directly related"))
67 67 raise util.Abort(_("inconsistent state, %s:%s is good and bad")
68 68 % (badrev, short(bad)))
69 69
70 70 # build children dict
71 71 children = {}
72 72 visit = [badrev]
73 73 candidates = []
74 74 while visit:
75 75 rev = visit.pop(0)
76 76 if ancestors[rev] == []:
77 77 candidates.append(rev)
78 78 for prev in clparents(rev):
79 79 if prev != -1:
80 80 if prev in children:
81 81 children[prev].append(rev)
82 82 else:
83 83 children[prev] = [rev]
84 84 visit.append(prev)
85 85
86 86 candidates.sort()
87 87 # have we narrowed it down to one entry?
88 88 # or have all other possible candidates besides 'bad' have been skipped?
89 89 tot = len(candidates)
90 90 unskipped = [c for c in candidates if (c not in skip) and (c != badrev)]
91 91 if tot == 1 or not unskipped:
92 92 return ([changelog.node(rev) for rev in candidates], 0, good)
93 93 perfect = tot // 2
94 94
95 95 # find the best node to test
96 96 best_rev = None
97 97 best_len = -1
98 98 poison = set()
99 99 for rev in candidates:
100 100 if rev in poison:
101 101 # poison children
102 102 poison.update(children.get(rev, []))
103 103 continue
104 104
105 105 a = ancestors[rev] or [rev]
106 106 ancestors[rev] = None
107 107
108 108 x = len(a) # number of ancestors
109 109 y = tot - x # number of non-ancestors
110 110 value = min(x, y) # how good is this test?
111 111 if value > best_len and rev not in skip:
112 112 best_len = value
113 113 best_rev = rev
114 114 if value == perfect: # found a perfect candidate? quit early
115 115 break
116 116
117 117 if y < perfect and rev not in skip: # all downhill from here?
118 118 # poison children
119 119 poison.update(children.get(rev, []))
120 120 continue
121 121
122 122 for c in children.get(rev, []):
123 123 if ancestors[c]:
124 124 ancestors[c] = list(set(ancestors[c] + a))
125 125 else:
126 126 ancestors[c] = a + [c]
127 127
128 128 assert best_rev is not None
129 129 best_node = changelog.node(best_rev)
130 130
131 131 return ([best_node], tot, good)
132 132
133 133
134 134 def load_state(repo):
135 135 state = {'good': [], 'bad': [], 'skip': []}
136 136 if os.path.exists(repo.join("bisect.state")):
137 137 for l in repo.opener("bisect.state"):
138 138 kind, node = l[:-1].split()
139 139 node = repo.lookup(node)
140 140 if kind not in state:
141 141 raise util.Abort(_("unknown bisect kind %s") % kind)
142 142 state[kind].append(node)
143 143 return state
144 144
145 145
146 146 def save_state(repo, state):
147 147 f = repo.opener("bisect.state", "w", atomictemp=True)
148 148 wlock = repo.wlock()
149 149 try:
150 150 for kind in state:
151 151 for node in state[kind]:
152 152 f.write("%s %s\n" % (kind, hex(node)))
153 153 f.close()
154 154 finally:
155 155 wlock.release()
156 156
157 157 def get(repo, status):
158 158 """
159 159 Return a list of revision(s) that match the given status:
160 160
161 - ``good``, ``bad``, ``skip``: as the names imply
162 - ``range`` : all csets taking part in the bisection
163 - ``pruned`` : csets that are good, bad or skipped
161 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
162 - ``goods``, ``bads`` : csets topologicaly good/bad
163 - ``range`` : csets taking part in the bisection
164 - ``pruned`` : csets that are goods, bads or skipped
164 165 - ``untested`` : csets whose fate is yet unknown
165 166 - ``ignored`` : csets ignored due to DAG topology
166 167 """
167 168 state = load_state(repo)
168 169 if status in ('good', 'bad', 'skip'):
169 170 return [repo.changelog.rev(n) for n in state[status]]
170 171 else:
171 172 # In the floowing sets, we do *not* call 'bisect()' with more
172 173 # than one level of recusrsion, because that can be very, very
173 174 # time consuming. Instead, we always develop the expression as
174 175 # much as possible.
175 176
176 177 # 'range' is all csets that make the bisection:
177 178 # - have a good ancestor and a bad descendant, or conversely
178 179 # that's because the bisection can go either way
179 180 range = '( bisect(bad)::bisect(good) | bisect(good)::bisect(bad) )'
180 181
181 # 'pruned' is all csets whose fate is already known:
182 # - a good ancestor and a good ascendant, or
183 # - a bad ancestor and a bad descendant, or
184 # - skipped
185 # But in case of irrelevant goods/bads, we also need to
186 # include them.
187 pg = 'bisect(good)::bisect(good)' # Pruned goods
188 pb = 'bisect(bad)::bisect(bad)' # Pruned bads
189 ps = 'bisect(skip)' # Pruned skipped
190 pruned = '( (%s) | (%s) | (%s) )' % (pg, pb, ps)
182 _t = [c.rev() for c in repo.set('bisect(good)::bisect(bad)')]
183 # The sets of topologically good or bad csets
184 if len(_t) == 0:
185 # Goods are topologically after bads
186 goods = 'bisect(good)::' # Pruned good csets
187 bads = '::bisect(bad)' # Pruned bad csets
188 else:
189 # Goods are topologically before bads
190 goods = '::bisect(good)' # Pruned good csets
191 bads = 'bisect(bad)::' # Pruned bad csets
192
193 # 'pruned' is all csets whose fate is already known: good, bad, skip
194 skips = 'bisect(skip)' # Pruned skipped csets
195 pruned = '( (%s) | (%s) | (%s) )' % (goods, bads, skips)
191 196
192 197 # 'untested' is all cset that are- in 'range', but not in 'pruned'
193 198 untested = '( (%s) - (%s) )' % (range, pruned)
194 199
195 200 # 'ignored' is all csets that were not used during the bisection
196 201 # due to DAG topology, but may however have had an impact.
197 202 # Eg., a branch merged between bads and goods, but whose branch-
198 203 # point is out-side of the range.
199 204 iba = '::bisect(bad) - ::bisect(good)' # Ignored bads' ancestors
200 205 iga = '::bisect(good) - ::bisect(bad)' # Ignored goods' ancestors
201 206 ignored = '( ( (%s) | (%s) ) - (%s) )' % (iba, iga, range)
202 207
203 208 if status == 'range':
204 209 return [c.rev() for c in repo.set(range)]
205 210 elif status == 'pruned':
206 211 return [c.rev() for c in repo.set(pruned)]
207 212 elif status == 'untested':
208 213 return [c.rev() for c in repo.set(untested)]
209 214 elif status == 'ignored':
210 215 return [c.rev() for c in repo.set(ignored)]
216 elif status == "goods":
217 return [c.rev() for c in repo.set(goods)]
218 elif status == "bads":
219 return [c.rev() for c in repo.set(bads)]
211 220
212 221 else:
213 222 raise error.ParseError(_('invalid bisect state'))
@@ -1,1119 +1,1120 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re
9 9 import parser, util, error, discovery, hbisect, node
10 10 import bookmarks as bookmarksmod
11 11 import match as matchmod
12 12 from i18n import _
13 13
14 14 elements = {
15 15 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
16 16 "~": (18, None, ("ancestor", 18)),
17 17 "^": (18, None, ("parent", 18), ("parentpost", 18)),
18 18 "-": (5, ("negate", 19), ("minus", 5)),
19 19 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
20 20 ("dagrangepost", 17)),
21 21 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
22 22 ("dagrangepost", 17)),
23 23 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
24 24 "not": (10, ("not", 10)),
25 25 "!": (10, ("not", 10)),
26 26 "and": (5, None, ("and", 5)),
27 27 "&": (5, None, ("and", 5)),
28 28 "or": (4, None, ("or", 4)),
29 29 "|": (4, None, ("or", 4)),
30 30 "+": (4, None, ("or", 4)),
31 31 ",": (2, None, ("list", 2)),
32 32 ")": (0, None, None),
33 33 "symbol": (0, ("symbol",), None),
34 34 "string": (0, ("string",), None),
35 35 "end": (0, None, None),
36 36 }
37 37
38 38 keywords = set(['and', 'or', 'not'])
39 39
40 40 def tokenize(program):
41 41 pos, l = 0, len(program)
42 42 while pos < l:
43 43 c = program[pos]
44 44 if c.isspace(): # skip inter-token whitespace
45 45 pass
46 46 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
47 47 yield ('::', None, pos)
48 48 pos += 1 # skip ahead
49 49 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
50 50 yield ('..', None, pos)
51 51 pos += 1 # skip ahead
52 52 elif c in "():,-|&+!~^": # handle simple operators
53 53 yield (c, None, pos)
54 54 elif (c in '"\'' or c == 'r' and
55 55 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
56 56 if c == 'r':
57 57 pos += 1
58 58 c = program[pos]
59 59 decode = lambda x: x
60 60 else:
61 61 decode = lambda x: x.decode('string-escape')
62 62 pos += 1
63 63 s = pos
64 64 while pos < l: # find closing quote
65 65 d = program[pos]
66 66 if d == '\\': # skip over escaped characters
67 67 pos += 2
68 68 continue
69 69 if d == c:
70 70 yield ('string', decode(program[s:pos]), s)
71 71 break
72 72 pos += 1
73 73 else:
74 74 raise error.ParseError(_("unterminated string"), s)
75 75 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
76 76 s = pos
77 77 pos += 1
78 78 while pos < l: # find end of symbol
79 79 d = program[pos]
80 80 if not (d.isalnum() or d in "._" or ord(d) > 127):
81 81 break
82 82 if d == '.' and program[pos - 1] == '.': # special case for ..
83 83 pos -= 1
84 84 break
85 85 pos += 1
86 86 sym = program[s:pos]
87 87 if sym in keywords: # operator keywords
88 88 yield (sym, None, s)
89 89 else:
90 90 yield ('symbol', sym, s)
91 91 pos -= 1
92 92 else:
93 93 raise error.ParseError(_("syntax error"), pos)
94 94 pos += 1
95 95 yield ('end', None, pos)
96 96
97 97 # helpers
98 98
99 99 def getstring(x, err):
100 100 if x and (x[0] == 'string' or x[0] == 'symbol'):
101 101 return x[1]
102 102 raise error.ParseError(err)
103 103
104 104 def getlist(x):
105 105 if not x:
106 106 return []
107 107 if x[0] == 'list':
108 108 return getlist(x[1]) + [x[2]]
109 109 return [x]
110 110
111 111 def getargs(x, min, max, err):
112 112 l = getlist(x)
113 113 if len(l) < min or len(l) > max:
114 114 raise error.ParseError(err)
115 115 return l
116 116
117 117 def getset(repo, subset, x):
118 118 if not x:
119 119 raise error.ParseError(_("missing argument"))
120 120 return methods[x[0]](repo, subset, *x[1:])
121 121
122 122 # operator methods
123 123
124 124 def stringset(repo, subset, x):
125 125 x = repo[x].rev()
126 126 if x == -1 and len(subset) == len(repo):
127 127 return [-1]
128 128 if len(subset) == len(repo) or x in subset:
129 129 return [x]
130 130 return []
131 131
132 132 def symbolset(repo, subset, x):
133 133 if x in symbols:
134 134 raise error.ParseError(_("can't use %s here") % x)
135 135 return stringset(repo, subset, x)
136 136
137 137 def rangeset(repo, subset, x, y):
138 138 m = getset(repo, subset, x)
139 139 if not m:
140 140 m = getset(repo, range(len(repo)), x)
141 141
142 142 n = getset(repo, subset, y)
143 143 if not n:
144 144 n = getset(repo, range(len(repo)), y)
145 145
146 146 if not m or not n:
147 147 return []
148 148 m, n = m[0], n[-1]
149 149
150 150 if m < n:
151 151 r = range(m, n + 1)
152 152 else:
153 153 r = range(m, n - 1, -1)
154 154 s = set(subset)
155 155 return [x for x in r if x in s]
156 156
157 157 def andset(repo, subset, x, y):
158 158 return getset(repo, getset(repo, subset, x), y)
159 159
160 160 def orset(repo, subset, x, y):
161 161 xl = getset(repo, subset, x)
162 162 s = set(xl)
163 163 yl = getset(repo, [r for r in subset if r not in s], y)
164 164 return xl + yl
165 165
166 166 def notset(repo, subset, x):
167 167 s = set(getset(repo, subset, x))
168 168 return [r for r in subset if r not in s]
169 169
170 170 def listset(repo, subset, a, b):
171 171 raise error.ParseError(_("can't use a list in this context"))
172 172
173 173 def func(repo, subset, a, b):
174 174 if a[0] == 'symbol' and a[1] in symbols:
175 175 return symbols[a[1]](repo, subset, b)
176 176 raise error.ParseError(_("not a function: %s") % a[1])
177 177
178 178 # functions
179 179
180 180 def adds(repo, subset, x):
181 181 """``adds(pattern)``
182 182 Changesets that add a file matching pattern.
183 183 """
184 184 # i18n: "adds" is a keyword
185 185 pat = getstring(x, _("adds requires a pattern"))
186 186 return checkstatus(repo, subset, pat, 1)
187 187
188 188 def ancestor(repo, subset, x):
189 189 """``ancestor(single, single)``
190 190 Greatest common ancestor of the two changesets.
191 191 """
192 192 # i18n: "ancestor" is a keyword
193 193 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
194 194 r = range(len(repo))
195 195 a = getset(repo, r, l[0])
196 196 b = getset(repo, r, l[1])
197 197 if len(a) != 1 or len(b) != 1:
198 198 # i18n: "ancestor" is a keyword
199 199 raise error.ParseError(_("ancestor arguments must be single revisions"))
200 200 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
201 201
202 202 return [r for r in an if r in subset]
203 203
204 204 def ancestors(repo, subset, x):
205 205 """``ancestors(set)``
206 206 Changesets that are ancestors of a changeset in set.
207 207 """
208 208 args = getset(repo, range(len(repo)), x)
209 209 if not args:
210 210 return []
211 211 s = set(repo.changelog.ancestors(*args)) | set(args)
212 212 return [r for r in subset if r in s]
213 213
214 214 def ancestorspec(repo, subset, x, n):
215 215 """``set~n``
216 216 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
217 217 """
218 218 try:
219 219 n = int(n[1])
220 220 except (TypeError, ValueError):
221 221 raise error.ParseError(_("~ expects a number"))
222 222 ps = set()
223 223 cl = repo.changelog
224 224 for r in getset(repo, subset, x):
225 225 for i in range(n):
226 226 r = cl.parentrevs(r)[0]
227 227 ps.add(r)
228 228 return [r for r in subset if r in ps]
229 229
230 230 def author(repo, subset, x):
231 231 """``author(string)``
232 232 Alias for ``user(string)``.
233 233 """
234 234 # i18n: "author" is a keyword
235 235 n = getstring(x, _("author requires a string")).lower()
236 236 return [r for r in subset if n in repo[r].user().lower()]
237 237
238 238 def bisect(repo, subset, x):
239 239 """``bisect(string)``
240 Changesets marked in the specified bisect status (``good``, ``bad``,
241 ``skip``), or any of the meta-status:
240 Changesets marked in the specified bisect status:
242 241
243 - ``range`` : all csets taking part in the bisection
244 - ``pruned`` : csets that are good, bad or skipped
242 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
243 - ``goods``, ``bads`` : csets topologicaly good/bad
244 - ``range`` : csets taking part in the bisection
245 - ``pruned`` : csets that are goods, bads or skipped
245 246 - ``untested`` : csets whose fate is yet unknown
246 247 - ``ignored`` : csets ignored due to DAG topology
247 248 """
248 249 status = getstring(x, _("bisect requires a string")).lower()
249 250 return [r for r in subset if r in hbisect.get(repo, status)]
250 251
251 252 # Backward-compatibility
252 253 # - no help entry so that we do not advertise it any more
253 254 def bisected(repo, subset, x):
254 255 return bisect(repo, subset, x)
255 256
256 257 def bookmark(repo, subset, x):
257 258 """``bookmark([name])``
258 259 The named bookmark or all bookmarks.
259 260 """
260 261 # i18n: "bookmark" is a keyword
261 262 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
262 263 if args:
263 264 bm = getstring(args[0],
264 265 # i18n: "bookmark" is a keyword
265 266 _('the argument to bookmark must be a string'))
266 267 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
267 268 if not bmrev:
268 269 raise util.Abort(_("bookmark '%s' does not exist") % bm)
269 270 bmrev = repo[bmrev].rev()
270 271 return [r for r in subset if r == bmrev]
271 272 bms = set([repo[r].rev()
272 273 for r in bookmarksmod.listbookmarks(repo).values()])
273 274 return [r for r in subset if r in bms]
274 275
275 276 def branch(repo, subset, x):
276 277 """``branch(string or set)``
277 278 All changesets belonging to the given branch or the branches of the given
278 279 changesets.
279 280 """
280 281 try:
281 282 b = getstring(x, '')
282 283 if b in repo.branchmap():
283 284 return [r for r in subset if repo[r].branch() == b]
284 285 except error.ParseError:
285 286 # not a string, but another revspec, e.g. tip()
286 287 pass
287 288
288 289 s = getset(repo, range(len(repo)), x)
289 290 b = set()
290 291 for r in s:
291 292 b.add(repo[r].branch())
292 293 s = set(s)
293 294 return [r for r in subset if r in s or repo[r].branch() in b]
294 295
295 296 def checkstatus(repo, subset, pat, field):
296 297 m = matchmod.match(repo.root, repo.getcwd(), [pat])
297 298 s = []
298 299 fast = (m.files() == [pat])
299 300 for r in subset:
300 301 c = repo[r]
301 302 if fast:
302 303 if pat not in c.files():
303 304 continue
304 305 else:
305 306 for f in c.files():
306 307 if m(f):
307 308 break
308 309 else:
309 310 continue
310 311 files = repo.status(c.p1().node(), c.node())[field]
311 312 if fast:
312 313 if pat in files:
313 314 s.append(r)
314 315 else:
315 316 for f in files:
316 317 if m(f):
317 318 s.append(r)
318 319 break
319 320 return s
320 321
321 322 def children(repo, subset, x):
322 323 """``children(set)``
323 324 Child changesets of changesets in set.
324 325 """
325 326 cs = set()
326 327 cl = repo.changelog
327 328 s = set(getset(repo, range(len(repo)), x))
328 329 for r in xrange(0, len(repo)):
329 330 for p in cl.parentrevs(r):
330 331 if p in s:
331 332 cs.add(r)
332 333 return [r for r in subset if r in cs]
333 334
334 335 def closed(repo, subset, x):
335 336 """``closed()``
336 337 Changeset is closed.
337 338 """
338 339 # i18n: "closed" is a keyword
339 340 getargs(x, 0, 0, _("closed takes no arguments"))
340 341 return [r for r in subset if repo[r].extra().get('close')]
341 342
342 343 def contains(repo, subset, x):
343 344 """``contains(pattern)``
344 345 Revision contains a file matching pattern. See :hg:`help patterns`
345 346 for information about file patterns.
346 347 """
347 348 # i18n: "contains" is a keyword
348 349 pat = getstring(x, _("contains requires a pattern"))
349 350 m = matchmod.match(repo.root, repo.getcwd(), [pat])
350 351 s = []
351 352 if m.files() == [pat]:
352 353 for r in subset:
353 354 if pat in repo[r]:
354 355 s.append(r)
355 356 else:
356 357 for r in subset:
357 358 for f in repo[r].manifest():
358 359 if m(f):
359 360 s.append(r)
360 361 break
361 362 return s
362 363
363 364 def date(repo, subset, x):
364 365 """``date(interval)``
365 366 Changesets within the interval, see :hg:`help dates`.
366 367 """
367 368 # i18n: "date" is a keyword
368 369 ds = getstring(x, _("date requires a string"))
369 370 dm = util.matchdate(ds)
370 371 return [r for r in subset if dm(repo[r].date()[0])]
371 372
372 373 def desc(repo, subset, x):
373 374 """``desc(string)``
374 375 Search commit message for string. The match is case-insensitive.
375 376 """
376 377 # i18n: "desc" is a keyword
377 378 ds = getstring(x, _("desc requires a string")).lower()
378 379 l = []
379 380 for r in subset:
380 381 c = repo[r]
381 382 if ds in c.description().lower():
382 383 l.append(r)
383 384 return l
384 385
385 386 def descendants(repo, subset, x):
386 387 """``descendants(set)``
387 388 Changesets which are descendants of changesets in set.
388 389 """
389 390 args = getset(repo, range(len(repo)), x)
390 391 if not args:
391 392 return []
392 393 s = set(repo.changelog.descendants(*args)) | set(args)
393 394 return [r for r in subset if r in s]
394 395
395 396 def filelog(repo, subset, x):
396 397 """``filelog(pattern)``
397 398 Changesets connected to the specified filelog.
398 399 """
399 400
400 401 pat = getstring(x, _("filelog requires a pattern"))
401 402 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
402 403 s = set()
403 404
404 405 if not m.anypats():
405 406 for f in m.files():
406 407 fl = repo.file(f)
407 408 for fr in fl:
408 409 s.add(fl.linkrev(fr))
409 410 else:
410 411 for f in repo[None]:
411 412 if m(f):
412 413 fl = repo.file(f)
413 414 for fr in fl:
414 415 s.add(fl.linkrev(fr))
415 416
416 417 return [r for r in subset if r in s]
417 418
418 419 def first(repo, subset, x):
419 420 """``first(set, [n])``
420 421 An alias for limit().
421 422 """
422 423 return limit(repo, subset, x)
423 424
424 425 def follow(repo, subset, x):
425 426 """``follow([file])``
426 427 An alias for ``::.`` (ancestors of the working copy's first parent).
427 428 If a filename is specified, the history of the given file is followed,
428 429 including copies.
429 430 """
430 431 # i18n: "follow" is a keyword
431 432 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
432 433 p = repo['.'].rev()
433 434 if l:
434 435 x = getstring(l[0], _("follow expected a filename"))
435 436 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
436 437 else:
437 438 s = set(repo.changelog.ancestors(p))
438 439
439 440 s |= set([p])
440 441 return [r for r in subset if r in s]
441 442
442 443 def followfile(repo, subset, x):
443 444 """``follow()``
444 445 An alias for ``::.`` (ancestors of the working copy's first parent).
445 446 """
446 447 # i18n: "follow" is a keyword
447 448 getargs(x, 0, 0, _("follow takes no arguments"))
448 449 p = repo['.'].rev()
449 450 s = set(repo.changelog.ancestors(p)) | set([p])
450 451 return [r for r in subset if r in s]
451 452
452 453 def getall(repo, subset, x):
453 454 """``all()``
454 455 All changesets, the same as ``0:tip``.
455 456 """
456 457 # i18n: "all" is a keyword
457 458 getargs(x, 0, 0, _("all takes no arguments"))
458 459 return subset
459 460
460 461 def grep(repo, subset, x):
461 462 """``grep(regex)``
462 463 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
463 464 to ensure special escape characters are handled correctly. Unlike
464 465 ``keyword(string)``, the match is case-sensitive.
465 466 """
466 467 try:
467 468 # i18n: "grep" is a keyword
468 469 gr = re.compile(getstring(x, _("grep requires a string")))
469 470 except re.error, e:
470 471 raise error.ParseError(_('invalid match pattern: %s') % e)
471 472 l = []
472 473 for r in subset:
473 474 c = repo[r]
474 475 for e in c.files() + [c.user(), c.description()]:
475 476 if gr.search(e):
476 477 l.append(r)
477 478 break
478 479 return l
479 480
480 481 def hasfile(repo, subset, x):
481 482 """``file(pattern)``
482 483 Changesets affecting files matched by pattern.
483 484 """
484 485 # i18n: "file" is a keyword
485 486 pat = getstring(x, _("file requires a pattern"))
486 487 m = matchmod.match(repo.root, repo.getcwd(), [pat])
487 488 s = []
488 489 for r in subset:
489 490 for f in repo[r].files():
490 491 if m(f):
491 492 s.append(r)
492 493 break
493 494 return s
494 495
495 496 def head(repo, subset, x):
496 497 """``head()``
497 498 Changeset is a named branch head.
498 499 """
499 500 # i18n: "head" is a keyword
500 501 getargs(x, 0, 0, _("head takes no arguments"))
501 502 hs = set()
502 503 for b, ls in repo.branchmap().iteritems():
503 504 hs.update(repo[h].rev() for h in ls)
504 505 return [r for r in subset if r in hs]
505 506
506 507 def heads(repo, subset, x):
507 508 """``heads(set)``
508 509 Members of set with no children in set.
509 510 """
510 511 s = getset(repo, subset, x)
511 512 ps = set(parents(repo, subset, x))
512 513 return [r for r in s if r not in ps]
513 514
514 515 def keyword(repo, subset, x):
515 516 """``keyword(string)``
516 517 Search commit message, user name, and names of changed files for
517 518 string. The match is case-insensitive.
518 519 """
519 520 # i18n: "keyword" is a keyword
520 521 kw = getstring(x, _("keyword requires a string")).lower()
521 522 l = []
522 523 for r in subset:
523 524 c = repo[r]
524 525 t = " ".join(c.files() + [c.user(), c.description()])
525 526 if kw in t.lower():
526 527 l.append(r)
527 528 return l
528 529
529 530 def limit(repo, subset, x):
530 531 """``limit(set, [n])``
531 532 First n members of set, defaulting to 1.
532 533 """
533 534 # i18n: "limit" is a keyword
534 535 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
535 536 try:
536 537 lim = 1
537 538 if len(l) == 2:
538 539 # i18n: "limit" is a keyword
539 540 lim = int(getstring(l[1], _("limit requires a number")))
540 541 except (TypeError, ValueError):
541 542 # i18n: "limit" is a keyword
542 543 raise error.ParseError(_("limit expects a number"))
543 544 ss = set(subset)
544 545 os = getset(repo, range(len(repo)), l[0])[:lim]
545 546 return [r for r in os if r in ss]
546 547
547 548 def last(repo, subset, x):
548 549 """``last(set, [n])``
549 550 Last n members of set, defaulting to 1.
550 551 """
551 552 # i18n: "last" is a keyword
552 553 l = getargs(x, 1, 2, _("last requires one or two arguments"))
553 554 try:
554 555 lim = 1
555 556 if len(l) == 2:
556 557 # i18n: "last" is a keyword
557 558 lim = int(getstring(l[1], _("last requires a number")))
558 559 except (TypeError, ValueError):
559 560 # i18n: "last" is a keyword
560 561 raise error.ParseError(_("last expects a number"))
561 562 ss = set(subset)
562 563 os = getset(repo, range(len(repo)), l[0])[-lim:]
563 564 return [r for r in os if r in ss]
564 565
565 566 def maxrev(repo, subset, x):
566 567 """``max(set)``
567 568 Changeset with highest revision number in set.
568 569 """
569 570 os = getset(repo, range(len(repo)), x)
570 571 if os:
571 572 m = max(os)
572 573 if m in subset:
573 574 return [m]
574 575 return []
575 576
576 577 def merge(repo, subset, x):
577 578 """``merge()``
578 579 Changeset is a merge changeset.
579 580 """
580 581 # i18n: "merge" is a keyword
581 582 getargs(x, 0, 0, _("merge takes no arguments"))
582 583 cl = repo.changelog
583 584 return [r for r in subset if cl.parentrevs(r)[1] != -1]
584 585
585 586 def minrev(repo, subset, x):
586 587 """``min(set)``
587 588 Changeset with lowest revision number in set.
588 589 """
589 590 os = getset(repo, range(len(repo)), x)
590 591 if os:
591 592 m = min(os)
592 593 if m in subset:
593 594 return [m]
594 595 return []
595 596
596 597 def modifies(repo, subset, x):
597 598 """``modifies(pattern)``
598 599 Changesets modifying files matched by pattern.
599 600 """
600 601 # i18n: "modifies" is a keyword
601 602 pat = getstring(x, _("modifies requires a pattern"))
602 603 return checkstatus(repo, subset, pat, 0)
603 604
604 605 def node(repo, subset, x):
605 606 """``id(string)``
606 607 Revision non-ambiguously specified by the given hex string prefix.
607 608 """
608 609 # i18n: "id" is a keyword
609 610 l = getargs(x, 1, 1, _("id requires one argument"))
610 611 # i18n: "id" is a keyword
611 612 n = getstring(l[0], _("id requires a string"))
612 613 if len(n) == 40:
613 614 rn = repo[n].rev()
614 615 else:
615 616 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
616 617 return [r for r in subset if r == rn]
617 618
618 619 def outgoing(repo, subset, x):
619 620 """``outgoing([path])``
620 621 Changesets not found in the specified destination repository, or the
621 622 default push location.
622 623 """
623 624 import hg # avoid start-up nasties
624 625 # i18n: "outgoing" is a keyword
625 626 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
626 627 # i18n: "outgoing" is a keyword
627 628 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
628 629 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
629 630 dest, branches = hg.parseurl(dest)
630 631 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
631 632 if revs:
632 633 revs = [repo.lookup(rev) for rev in revs]
633 634 other = hg.peer(repo, {}, dest)
634 635 repo.ui.pushbuffer()
635 636 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
636 637 repo.ui.popbuffer()
637 638 cl = repo.changelog
638 639 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
639 640 return [r for r in subset if r in o]
640 641
641 642 def p1(repo, subset, x):
642 643 """``p1([set])``
643 644 First parent of changesets in set, or the working directory.
644 645 """
645 646 if x is None:
646 647 p = repo[x].p1().rev()
647 648 return [r for r in subset if r == p]
648 649
649 650 ps = set()
650 651 cl = repo.changelog
651 652 for r in getset(repo, range(len(repo)), x):
652 653 ps.add(cl.parentrevs(r)[0])
653 654 return [r for r in subset if r in ps]
654 655
655 656 def p2(repo, subset, x):
656 657 """``p2([set])``
657 658 Second parent of changesets in set, or the working directory.
658 659 """
659 660 if x is None:
660 661 ps = repo[x].parents()
661 662 try:
662 663 p = ps[1].rev()
663 664 return [r for r in subset if r == p]
664 665 except IndexError:
665 666 return []
666 667
667 668 ps = set()
668 669 cl = repo.changelog
669 670 for r in getset(repo, range(len(repo)), x):
670 671 ps.add(cl.parentrevs(r)[1])
671 672 return [r for r in subset if r in ps]
672 673
673 674 def parents(repo, subset, x):
674 675 """``parents([set])``
675 676 The set of all parents for all changesets in set, or the working directory.
676 677 """
677 678 if x is None:
678 679 ps = tuple(p.rev() for p in repo[x].parents())
679 680 return [r for r in subset if r in ps]
680 681
681 682 ps = set()
682 683 cl = repo.changelog
683 684 for r in getset(repo, range(len(repo)), x):
684 685 ps.update(cl.parentrevs(r))
685 686 return [r for r in subset if r in ps]
686 687
687 688 def parentspec(repo, subset, x, n):
688 689 """``set^0``
689 690 The set.
690 691 ``set^1`` (or ``set^``), ``set^2``
691 692 First or second parent, respectively, of all changesets in set.
692 693 """
693 694 try:
694 695 n = int(n[1])
695 696 if n not in (0, 1, 2):
696 697 raise ValueError
697 698 except (TypeError, ValueError):
698 699 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
699 700 ps = set()
700 701 cl = repo.changelog
701 702 for r in getset(repo, subset, x):
702 703 if n == 0:
703 704 ps.add(r)
704 705 elif n == 1:
705 706 ps.add(cl.parentrevs(r)[0])
706 707 elif n == 2:
707 708 parents = cl.parentrevs(r)
708 709 if len(parents) > 1:
709 710 ps.add(parents[1])
710 711 return [r for r in subset if r in ps]
711 712
712 713 def present(repo, subset, x):
713 714 """``present(set)``
714 715 An empty set, if any revision in set isn't found; otherwise,
715 716 all revisions in set.
716 717 """
717 718 try:
718 719 return getset(repo, subset, x)
719 720 except error.RepoLookupError:
720 721 return []
721 722
722 723 def removes(repo, subset, x):
723 724 """``removes(pattern)``
724 725 Changesets which remove files matching pattern.
725 726 """
726 727 # i18n: "removes" is a keyword
727 728 pat = getstring(x, _("removes requires a pattern"))
728 729 return checkstatus(repo, subset, pat, 2)
729 730
730 731 def rev(repo, subset, x):
731 732 """``rev(number)``
732 733 Revision with the given numeric identifier.
733 734 """
734 735 # i18n: "rev" is a keyword
735 736 l = getargs(x, 1, 1, _("rev requires one argument"))
736 737 try:
737 738 # i18n: "rev" is a keyword
738 739 l = int(getstring(l[0], _("rev requires a number")))
739 740 except (TypeError, ValueError):
740 741 # i18n: "rev" is a keyword
741 742 raise error.ParseError(_("rev expects a number"))
742 743 return [r for r in subset if r == l]
743 744
744 745 def reverse(repo, subset, x):
745 746 """``reverse(set)``
746 747 Reverse order of set.
747 748 """
748 749 l = getset(repo, subset, x)
749 750 l.reverse()
750 751 return l
751 752
752 753 def roots(repo, subset, x):
753 754 """``roots(set)``
754 755 Changesets with no parent changeset in set.
755 756 """
756 757 s = getset(repo, subset, x)
757 758 cs = set(children(repo, subset, x))
758 759 return [r for r in s if r not in cs]
759 760
760 761 def sort(repo, subset, x):
761 762 """``sort(set[, [-]key...])``
762 763 Sort set by keys. The default sort order is ascending, specify a key
763 764 as ``-key`` to sort in descending order.
764 765
765 766 The keys can be:
766 767
767 768 - ``rev`` for the revision number,
768 769 - ``branch`` for the branch name,
769 770 - ``desc`` for the commit message (description),
770 771 - ``user`` for user name (``author`` can be used as an alias),
771 772 - ``date`` for the commit date
772 773 """
773 774 # i18n: "sort" is a keyword
774 775 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
775 776 keys = "rev"
776 777 if len(l) == 2:
777 778 keys = getstring(l[1], _("sort spec must be a string"))
778 779
779 780 s = l[0]
780 781 keys = keys.split()
781 782 l = []
782 783 def invert(s):
783 784 return "".join(chr(255 - ord(c)) for c in s)
784 785 for r in getset(repo, subset, s):
785 786 c = repo[r]
786 787 e = []
787 788 for k in keys:
788 789 if k == 'rev':
789 790 e.append(r)
790 791 elif k == '-rev':
791 792 e.append(-r)
792 793 elif k == 'branch':
793 794 e.append(c.branch())
794 795 elif k == '-branch':
795 796 e.append(invert(c.branch()))
796 797 elif k == 'desc':
797 798 e.append(c.description())
798 799 elif k == '-desc':
799 800 e.append(invert(c.description()))
800 801 elif k in 'user author':
801 802 e.append(c.user())
802 803 elif k in '-user -author':
803 804 e.append(invert(c.user()))
804 805 elif k == 'date':
805 806 e.append(c.date()[0])
806 807 elif k == '-date':
807 808 e.append(-c.date()[0])
808 809 else:
809 810 raise error.ParseError(_("unknown sort key %r") % k)
810 811 e.append(r)
811 812 l.append(e)
812 813 l.sort()
813 814 return [e[-1] for e in l]
814 815
815 816 def tag(repo, subset, x):
816 817 """``tag([name])``
817 818 The specified tag by name, or all tagged revisions if no name is given.
818 819 """
819 820 # i18n: "tag" is a keyword
820 821 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
821 822 cl = repo.changelog
822 823 if args:
823 824 tn = getstring(args[0],
824 825 # i18n: "tag" is a keyword
825 826 _('the argument to tag must be a string'))
826 827 if not repo.tags().get(tn, None):
827 828 raise util.Abort(_("tag '%s' does not exist") % tn)
828 829 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
829 830 else:
830 831 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
831 832 return [r for r in subset if r in s]
832 833
833 834 def tagged(repo, subset, x):
834 835 return tag(repo, subset, x)
835 836
836 837 def user(repo, subset, x):
837 838 """``user(string)``
838 839 User name contains string. The match is case-insensitive.
839 840 """
840 841 return author(repo, subset, x)
841 842
842 843 symbols = {
843 844 "adds": adds,
844 845 "all": getall,
845 846 "ancestor": ancestor,
846 847 "ancestors": ancestors,
847 848 "author": author,
848 849 "bisect": bisect,
849 850 "bisected": bisected,
850 851 "bookmark": bookmark,
851 852 "branch": branch,
852 853 "children": children,
853 854 "closed": closed,
854 855 "contains": contains,
855 856 "date": date,
856 857 "desc": desc,
857 858 "descendants": descendants,
858 859 "file": hasfile,
859 860 "filelog": filelog,
860 861 "first": first,
861 862 "follow": follow,
862 863 "grep": grep,
863 864 "head": head,
864 865 "heads": heads,
865 866 "id": node,
866 867 "keyword": keyword,
867 868 "last": last,
868 869 "limit": limit,
869 870 "max": maxrev,
870 871 "merge": merge,
871 872 "min": minrev,
872 873 "modifies": modifies,
873 874 "outgoing": outgoing,
874 875 "p1": p1,
875 876 "p2": p2,
876 877 "parents": parents,
877 878 "present": present,
878 879 "removes": removes,
879 880 "rev": rev,
880 881 "reverse": reverse,
881 882 "roots": roots,
882 883 "sort": sort,
883 884 "tag": tag,
884 885 "tagged": tagged,
885 886 "user": user,
886 887 }
887 888
888 889 methods = {
889 890 "range": rangeset,
890 891 "string": stringset,
891 892 "symbol": symbolset,
892 893 "and": andset,
893 894 "or": orset,
894 895 "not": notset,
895 896 "list": listset,
896 897 "func": func,
897 898 "ancestor": ancestorspec,
898 899 "parent": parentspec,
899 900 "parentpost": p1,
900 901 }
901 902
902 903 def optimize(x, small):
903 904 if x is None:
904 905 return 0, x
905 906
906 907 smallbonus = 1
907 908 if small:
908 909 smallbonus = .5
909 910
910 911 op = x[0]
911 912 if op == 'minus':
912 913 return optimize(('and', x[1], ('not', x[2])), small)
913 914 elif op == 'dagrange':
914 915 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
915 916 ('func', ('symbol', 'ancestors'), x[2])), small)
916 917 elif op == 'dagrangepre':
917 918 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
918 919 elif op == 'dagrangepost':
919 920 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
920 921 elif op == 'rangepre':
921 922 return optimize(('range', ('string', '0'), x[1]), small)
922 923 elif op == 'rangepost':
923 924 return optimize(('range', x[1], ('string', 'tip')), small)
924 925 elif op == 'negate':
925 926 return optimize(('string',
926 927 '-' + getstring(x[1], _("can't negate that"))), small)
927 928 elif op in 'string symbol negate':
928 929 return smallbonus, x # single revisions are small
929 930 elif op == 'and' or op == 'dagrange':
930 931 wa, ta = optimize(x[1], True)
931 932 wb, tb = optimize(x[2], True)
932 933 w = min(wa, wb)
933 934 if wa > wb:
934 935 return w, (op, tb, ta)
935 936 return w, (op, ta, tb)
936 937 elif op == 'or':
937 938 wa, ta = optimize(x[1], False)
938 939 wb, tb = optimize(x[2], False)
939 940 if wb < wa:
940 941 wb, wa = wa, wb
941 942 return max(wa, wb), (op, ta, tb)
942 943 elif op == 'not':
943 944 o = optimize(x[1], not small)
944 945 return o[0], (op, o[1])
945 946 elif op == 'parentpost':
946 947 o = optimize(x[1], small)
947 948 return o[0], (op, o[1])
948 949 elif op == 'group':
949 950 return optimize(x[1], small)
950 951 elif op in 'range list parent ancestorspec':
951 952 if op == 'parent':
952 953 # x^:y means (x^) : y, not x ^ (:y)
953 954 post = ('parentpost', x[1])
954 955 if x[2][0] == 'dagrangepre':
955 956 return optimize(('dagrange', post, x[2][1]), small)
956 957 elif x[2][0] == 'rangepre':
957 958 return optimize(('range', post, x[2][1]), small)
958 959
959 960 wa, ta = optimize(x[1], small)
960 961 wb, tb = optimize(x[2], small)
961 962 return wa + wb, (op, ta, tb)
962 963 elif op == 'func':
963 964 f = getstring(x[1], _("not a symbol"))
964 965 wa, ta = optimize(x[2], small)
965 966 if f in ("author branch closed date desc file grep keyword "
966 967 "outgoing user"):
967 968 w = 10 # slow
968 969 elif f in "modifies adds removes":
969 970 w = 30 # slower
970 971 elif f == "contains":
971 972 w = 100 # very slow
972 973 elif f == "ancestor":
973 974 w = 1 * smallbonus
974 975 elif f in "reverse limit first":
975 976 w = 0
976 977 elif f in "sort":
977 978 w = 10 # assume most sorts look at changelog
978 979 else:
979 980 w = 1
980 981 return w + wa, (op, x[1], ta)
981 982 return 1, x
982 983
983 984 class revsetalias(object):
984 985 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
985 986 args = None
986 987
987 988 def __init__(self, name, value):
988 989 '''Aliases like:
989 990
990 991 h = heads(default)
991 992 b($1) = ancestors($1) - ancestors(default)
992 993 '''
993 994 if isinstance(name, tuple): # parameter substitution
994 995 self.tree = name
995 996 self.replacement = value
996 997 else: # alias definition
997 998 m = self.funcre.search(name)
998 999 if m:
999 1000 self.tree = ('func', ('symbol', m.group(1)))
1000 1001 self.args = [x.strip() for x in m.group(2).split(',')]
1001 1002 for arg in self.args:
1002 1003 value = value.replace(arg, repr(arg))
1003 1004 else:
1004 1005 self.tree = ('symbol', name)
1005 1006
1006 1007 self.replacement, pos = parse(value)
1007 1008 if pos != len(value):
1008 1009 raise error.ParseError(_('invalid token'), pos)
1009 1010
1010 1011 def process(self, tree):
1011 1012 if isinstance(tree, tuple):
1012 1013 if self.args is None:
1013 1014 if tree == self.tree:
1014 1015 return self.replacement
1015 1016 elif tree[:2] == self.tree:
1016 1017 l = getlist(tree[2])
1017 1018 if len(l) != len(self.args):
1018 1019 raise error.ParseError(
1019 1020 _('invalid number of arguments: %s') % len(l))
1020 1021 result = self.replacement
1021 1022 for a, v in zip(self.args, l):
1022 1023 valalias = revsetalias(('string', a), v)
1023 1024 result = valalias.process(result)
1024 1025 return result
1025 1026 return tuple(map(self.process, tree))
1026 1027 return tree
1027 1028
1028 1029 def findaliases(ui, tree):
1029 1030 for k, v in ui.configitems('revsetalias'):
1030 1031 alias = revsetalias(k, v)
1031 1032 tree = alias.process(tree)
1032 1033 return tree
1033 1034
1034 1035 parse = parser.parser(tokenize, elements).parse
1035 1036
1036 1037 def match(ui, spec):
1037 1038 if not spec:
1038 1039 raise error.ParseError(_("empty query"))
1039 1040 tree, pos = parse(spec)
1040 1041 if (pos != len(spec)):
1041 1042 raise error.ParseError(_("invalid token"), pos)
1042 1043 if ui:
1043 1044 tree = findaliases(ui, tree)
1044 1045 weight, tree = optimize(tree, True)
1045 1046 def mfunc(repo, subset):
1046 1047 return getset(repo, subset, tree)
1047 1048 return mfunc
1048 1049
1049 1050 def formatspec(expr, *args):
1050 1051 '''
1051 1052 This is a convenience function for using revsets internally, and
1052 1053 escapes arguments appropriately. Aliases are intentionally ignored
1053 1054 so that intended expression behavior isn't accidentally subverted.
1054 1055
1055 1056 Supported arguments:
1056 1057
1057 1058 %d = int(arg), no quoting
1058 1059 %s = string(arg), escaped and single-quoted
1059 1060 %b = arg.branch(), escaped and single-quoted
1060 1061 %n = hex(arg), single-quoted
1061 1062 %% = a literal '%'
1062 1063
1063 1064 Prefixing the type with 'l' specifies a list of that type.
1064 1065
1065 1066 >>> formatspec('%d:: and not %d::', 10, 20)
1066 1067 '10:: and not 20::'
1067 1068 >>> formatspec('keyword(%s)', 'foo\\xe9')
1068 1069 "keyword('foo\\\\xe9')"
1069 1070 >>> b = lambda: 'default'
1070 1071 >>> b.branch = b
1071 1072 >>> formatspec('branch(%b)', b)
1072 1073 "branch('default')"
1073 1074 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1074 1075 "root(('a' or 'b' or 'c' or 'd'))"
1075 1076 '''
1076 1077
1077 1078 def quote(s):
1078 1079 return repr(str(s))
1079 1080
1080 1081 def argtype(c, arg):
1081 1082 if c == 'd':
1082 1083 return str(int(arg))
1083 1084 elif c == 's':
1084 1085 return quote(arg)
1085 1086 elif c == 'n':
1086 1087 return quote(node.hex(arg))
1087 1088 elif c == 'b':
1088 1089 return quote(arg.branch())
1089 1090
1090 1091 ret = ''
1091 1092 pos = 0
1092 1093 arg = 0
1093 1094 while pos < len(expr):
1094 1095 c = expr[pos]
1095 1096 if c == '%':
1096 1097 pos += 1
1097 1098 d = expr[pos]
1098 1099 if d == '%':
1099 1100 ret += d
1100 1101 elif d in 'dsnb':
1101 1102 ret += argtype(d, args[arg])
1102 1103 arg += 1
1103 1104 elif d == 'l':
1104 1105 # a list of some type
1105 1106 pos += 1
1106 1107 d = expr[pos]
1107 1108 lv = ' or '.join(argtype(d, e) for e in args[arg])
1108 1109 ret += '(%s)' % lv
1109 1110 arg += 1
1110 1111 else:
1111 1112 raise util.Abort('unexpected revspec format character %s' % d)
1112 1113 else:
1113 1114 ret += c
1114 1115 pos += 1
1115 1116
1116 1117 return ret
1117 1118
1118 1119 # tell hggettext to extract docstrings from these functions:
1119 1120 i18nfunctions = symbols.values()
@@ -1,706 +1,795 b''
1 1 # The tests in test-bisect are done on a linear history. Here the
2 2 # following repository history is used for testing:
3 3 #
4 4 # 17
5 5 # |
6 6 # 18 16
7 7 # \ /
8 8 # 15
9 9 # / \
10 10 # / \
11 11 # 10 13
12 12 # / \ |
13 13 # / \ | 14
14 14 # 7 6 9 12 /
15 15 # \ / \ | |/
16 16 # 4 \ | 11
17 17 # \ \ | /
18 18 # 3 5 | /
19 19 # \ / |/
20 20 # 2 8
21 21 # \ /
22 22 # 1
23 23 # |
24 24 # 0
25 25
26 26 init
27 27
28 28 $ hg init
29 29
30 30 committing changes
31 31
32 32 $ echo > a
33 33 $ echo '0' >> a
34 34 $ hg add a
35 35 $ hg ci -m "0" -d "0 0"
36 36 $ echo '1' >> a
37 37 $ hg ci -m "1" -d "1 0"
38 38 $ echo '2' >> a
39 39 $ hg ci -m "2" -d "2 0"
40 40 $ echo '3' >> a
41 41 $ hg ci -m "3" -d "3 0"
42 42 $ echo '4' >> a
43 43 $ hg ci -m "4" -d "4 0"
44 44
45 45 create branch
46 46
47 47 $ hg up -r 2
48 48 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
49 49 $ echo '5' >> b
50 50 $ hg add b
51 51 $ hg ci -m "5" -d "5 0"
52 52 created new head
53 53
54 54 merge
55 55
56 56 $ hg merge
57 57 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
58 58 (branch merge, don't forget to commit)
59 59 $ hg ci -m "merge 4,5" -d "6 0"
60 60
61 61 create branch
62 62
63 63 $ hg up -r 4
64 64 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
65 65 $ echo '7' > c
66 66 $ hg add c
67 67 $ hg ci -m "7" -d "7 0"
68 68 created new head
69 69
70 70 create branch
71 71
72 72 $ hg up -r 1
73 73 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
74 74 $ echo '8' > d
75 75 $ hg add d
76 76 $ hg ci -m "8" -d "8 0"
77 77 created new head
78 78 $ echo '9' >> d
79 79 $ hg ci -m "9" -d "9 0"
80 80
81 81 merge
82 82
83 83 $ hg merge -r 6
84 84 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 85 (branch merge, don't forget to commit)
86 86 $ hg ci -m "merge 6,9" -d "10 0"
87 87
88 88 create branch
89 89
90 90 $ hg up -r 8
91 91 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
92 92 $ echo '11' > e
93 93 $ hg add e
94 94 $ hg ci -m "11" -d "11 0"
95 95 created new head
96 96 $ echo '12' >> e
97 97 $ hg ci -m "12" -d "12 0"
98 98 $ echo '13' >> e
99 99 $ hg ci -m "13" -d "13 0"
100 100
101 101 create branch
102 102
103 103 $ hg up -r 11
104 104 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
105 105 $ echo '14' > f
106 106 $ hg add f
107 107 $ hg ci -m "14" -d "14 0"
108 108 created new head
109 109
110 110 merge
111 111
112 112 $ hg up -r 13 -C
113 113 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
114 114 $ hg merge -r 10
115 115 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
116 116 (branch merge, don't forget to commit)
117 117 $ hg ci -m "merge 10,13" -d "15 0"
118 118 $ echo '16' >> e
119 119 $ hg ci -m "16" -d "16 0"
120 120 $ echo '17' >> e
121 121 $ hg ci -m "17" -d "17 0"
122 122
123 123 create branch
124 124
125 125 $ hg up -r 15
126 126 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
127 127 $ echo '18' >> e
128 128 $ hg ci -m "18" -d "18 0"
129 129 created new head
130 130
131 131 log
132 132
133 133 $ hg log
134 134 changeset: 18:d42e18c7bc9b
135 135 tag: tip
136 136 parent: 15:857b178a7cf3
137 137 user: test
138 138 date: Thu Jan 01 00:00:18 1970 +0000
139 139 summary: 18
140 140
141 141 changeset: 17:228c06deef46
142 142 user: test
143 143 date: Thu Jan 01 00:00:17 1970 +0000
144 144 summary: 17
145 145
146 146 changeset: 16:609d82a7ebae
147 147 user: test
148 148 date: Thu Jan 01 00:00:16 1970 +0000
149 149 summary: 16
150 150
151 151 changeset: 15:857b178a7cf3
152 152 parent: 13:b0a32c86eb31
153 153 parent: 10:429fcd26f52d
154 154 user: test
155 155 date: Thu Jan 01 00:00:15 1970 +0000
156 156 summary: merge 10,13
157 157
158 158 changeset: 14:faa450606157
159 159 parent: 11:82ca6f06eccd
160 160 user: test
161 161 date: Thu Jan 01 00:00:14 1970 +0000
162 162 summary: 14
163 163
164 164 changeset: 13:b0a32c86eb31
165 165 user: test
166 166 date: Thu Jan 01 00:00:13 1970 +0000
167 167 summary: 13
168 168
169 169 changeset: 12:9f259202bbe7
170 170 user: test
171 171 date: Thu Jan 01 00:00:12 1970 +0000
172 172 summary: 12
173 173
174 174 changeset: 11:82ca6f06eccd
175 175 parent: 8:dab8161ac8fc
176 176 user: test
177 177 date: Thu Jan 01 00:00:11 1970 +0000
178 178 summary: 11
179 179
180 180 changeset: 10:429fcd26f52d
181 181 parent: 9:3c77083deb4a
182 182 parent: 6:a214d5d3811a
183 183 user: test
184 184 date: Thu Jan 01 00:00:10 1970 +0000
185 185 summary: merge 6,9
186 186
187 187 changeset: 9:3c77083deb4a
188 188 user: test
189 189 date: Thu Jan 01 00:00:09 1970 +0000
190 190 summary: 9
191 191
192 192 changeset: 8:dab8161ac8fc
193 193 parent: 1:4ca5088da217
194 194 user: test
195 195 date: Thu Jan 01 00:00:08 1970 +0000
196 196 summary: 8
197 197
198 198 changeset: 7:50c76098bbf2
199 199 parent: 4:5c668c22234f
200 200 user: test
201 201 date: Thu Jan 01 00:00:07 1970 +0000
202 202 summary: 7
203 203
204 204 changeset: 6:a214d5d3811a
205 205 parent: 5:385a529b6670
206 206 parent: 4:5c668c22234f
207 207 user: test
208 208 date: Thu Jan 01 00:00:06 1970 +0000
209 209 summary: merge 4,5
210 210
211 211 changeset: 5:385a529b6670
212 212 parent: 2:051e12f87bf1
213 213 user: test
214 214 date: Thu Jan 01 00:00:05 1970 +0000
215 215 summary: 5
216 216
217 217 changeset: 4:5c668c22234f
218 218 user: test
219 219 date: Thu Jan 01 00:00:04 1970 +0000
220 220 summary: 4
221 221
222 222 changeset: 3:0950834f0a9c
223 223 user: test
224 224 date: Thu Jan 01 00:00:03 1970 +0000
225 225 summary: 3
226 226
227 227 changeset: 2:051e12f87bf1
228 228 user: test
229 229 date: Thu Jan 01 00:00:02 1970 +0000
230 230 summary: 2
231 231
232 232 changeset: 1:4ca5088da217
233 233 user: test
234 234 date: Thu Jan 01 00:00:01 1970 +0000
235 235 summary: 1
236 236
237 237 changeset: 0:33b1f9bc8bc5
238 238 user: test
239 239 date: Thu Jan 01 00:00:00 1970 +0000
240 240 summary: 0
241 241
242 242
243 243 hg up -C
244 244
245 245 $ hg up -C
246 246 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
247 247
248 248 complex bisect test 1 # first bad rev is 9
249 249
250 250 $ hg bisect -r
251 251 $ hg bisect -g 0
252 252 $ hg bisect -b 17 # -> update to rev 6
253 253 Testing changeset 6:a214d5d3811a (15 changesets remaining, ~3 tests)
254 254 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
255 255 $ hg log -q -r 'bisect(pruned)'
256 256 0:33b1f9bc8bc5
257 257 17:228c06deef46
258 258 $ hg log -q -r 'bisect(untested)'
259 259 1:4ca5088da217
260 260 2:051e12f87bf1
261 261 3:0950834f0a9c
262 262 4:5c668c22234f
263 263 5:385a529b6670
264 264 6:a214d5d3811a
265 265 8:dab8161ac8fc
266 266 9:3c77083deb4a
267 267 10:429fcd26f52d
268 268 11:82ca6f06eccd
269 269 12:9f259202bbe7
270 270 13:b0a32c86eb31
271 271 15:857b178a7cf3
272 272 16:609d82a7ebae
273 273 $ hg log -q -r 'bisect(ignored)'
274 274 $ hg bisect -g # -> update to rev 13
275 275 Testing changeset 13:b0a32c86eb31 (9 changesets remaining, ~3 tests)
276 276 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
277 277 $ hg bisect -s # -> update to rev 10
278 278 Testing changeset 10:429fcd26f52d (9 changesets remaining, ~3 tests)
279 279 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
280 280 $ hg bisect -b # -> update to rev 8
281 281 Testing changeset 8:dab8161ac8fc (3 changesets remaining, ~1 tests)
282 282 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
283 283 $ hg bisect -g # -> update to rev 9
284 284 Testing changeset 9:3c77083deb4a (2 changesets remaining, ~1 tests)
285 285 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
286 286 $ hg bisect -b
287 287 The first bad revision is:
288 288 changeset: 9:3c77083deb4a
289 289 user: test
290 290 date: Thu Jan 01 00:00:09 1970 +0000
291 291 summary: 9
292 292
293 293 $ hg log -q -r 'bisect(range)'
294 294 0:33b1f9bc8bc5
295 295 1:4ca5088da217
296 296 2:051e12f87bf1
297 297 3:0950834f0a9c
298 298 4:5c668c22234f
299 299 5:385a529b6670
300 300 6:a214d5d3811a
301 301 8:dab8161ac8fc
302 302 9:3c77083deb4a
303 303 10:429fcd26f52d
304 304 11:82ca6f06eccd
305 305 12:9f259202bbe7
306 306 13:b0a32c86eb31
307 307 15:857b178a7cf3
308 308 16:609d82a7ebae
309 309 17:228c06deef46
310 310 $ hg log -q -r 'bisect(pruned)'
311 311 0:33b1f9bc8bc5
312 312 1:4ca5088da217
313 313 2:051e12f87bf1
314 314 3:0950834f0a9c
315 315 4:5c668c22234f
316 316 5:385a529b6670
317 317 6:a214d5d3811a
318 318 8:dab8161ac8fc
319 319 9:3c77083deb4a
320 320 10:429fcd26f52d
321 321 13:b0a32c86eb31
322 322 15:857b178a7cf3
323 323 16:609d82a7ebae
324 324 17:228c06deef46
325 18:d42e18c7bc9b
325 326 $ hg log -q -r 'bisect(untested)'
326 327 11:82ca6f06eccd
327 328 12:9f259202bbe7
329 $ hg log -q -r 'bisect(goods)'
330 0:33b1f9bc8bc5
331 1:4ca5088da217
332 2:051e12f87bf1
333 3:0950834f0a9c
334 4:5c668c22234f
335 5:385a529b6670
336 6:a214d5d3811a
337 8:dab8161ac8fc
338 $ hg log -q -r 'bisect(bads)'
339 9:3c77083deb4a
340 10:429fcd26f52d
341 15:857b178a7cf3
342 16:609d82a7ebae
343 17:228c06deef46
344 18:d42e18c7bc9b
328 345
329 346 complex bisect test 2 # first good rev is 13
330 347
331 348 $ hg bisect -r
332 349 $ hg bisect -g 18
333 350 $ hg bisect -b 1 # -> update to rev 6
334 351 Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests)
335 352 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
336 353 $ hg bisect -s # -> update to rev 10
337 354 Testing changeset 10:429fcd26f52d (13 changesets remaining, ~3 tests)
338 355 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
339 356 $ hg log -q -r 'bisect(pruned)'
357 0:33b1f9bc8bc5
340 358 1:4ca5088da217
341 359 6:a214d5d3811a
342 360 18:d42e18c7bc9b
343 361 $ hg bisect -b # -> update to rev 12
344 362 Testing changeset 12:9f259202bbe7 (5 changesets remaining, ~2 tests)
345 363 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
346 364 $ hg log -q -r 'bisect(pruned)'
365 0:33b1f9bc8bc5
347 366 1:4ca5088da217
348 367 2:051e12f87bf1
349 368 3:0950834f0a9c
350 369 4:5c668c22234f
351 370 5:385a529b6670
352 371 6:a214d5d3811a
353 372 8:dab8161ac8fc
354 373 9:3c77083deb4a
355 374 10:429fcd26f52d
356 375 18:d42e18c7bc9b
357 376 $ hg log -q -r 'bisect(untested)'
358 377 11:82ca6f06eccd
359 378 12:9f259202bbe7
360 379 13:b0a32c86eb31
361 380 15:857b178a7cf3
362 381 $ hg bisect -b # -> update to rev 13
363 382 Testing changeset 13:b0a32c86eb31 (3 changesets remaining, ~1 tests)
364 383 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
365 384 $ hg bisect -g
366 385 The first good revision is:
367 386 changeset: 13:b0a32c86eb31
368 387 user: test
369 388 date: Thu Jan 01 00:00:13 1970 +0000
370 389 summary: 13
371 390
372 391 $ hg log -q -r 'bisect(range)'
373 392 1:4ca5088da217
374 393 2:051e12f87bf1
375 394 3:0950834f0a9c
376 395 4:5c668c22234f
377 396 5:385a529b6670
378 397 6:a214d5d3811a
379 398 8:dab8161ac8fc
380 399 9:3c77083deb4a
381 400 10:429fcd26f52d
382 401 11:82ca6f06eccd
383 402 12:9f259202bbe7
384 403 13:b0a32c86eb31
385 404 15:857b178a7cf3
386 405 18:d42e18c7bc9b
387 406
388 407 complex bisect test 3
389 408
390 409 first bad rev is 15
391 410 10,9,13 are skipped an might be the first bad revisions as well
392 411
393 412 $ hg bisect -r
394 413 $ hg bisect -g 1
395 414 $ hg bisect -b 16 # -> update to rev 6
396 415 Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests)
397 416 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
398 417 $ hg log -q -r 'bisect(pruned)'
418 0:33b1f9bc8bc5
399 419 1:4ca5088da217
400 420 16:609d82a7ebae
421 17:228c06deef46
401 422 $ hg bisect -g # -> update to rev 13
402 423 Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests)
403 424 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
404 425 $ hg bisect -s # -> update to rev 10
405 426 Testing changeset 10:429fcd26f52d (8 changesets remaining, ~3 tests)
406 427 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
407 428 $ hg bisect -s # -> update to rev 12
408 429 Testing changeset 12:9f259202bbe7 (8 changesets remaining, ~3 tests)
409 430 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
410 431 $ hg log -q -r 'bisect(pruned)'
432 0:33b1f9bc8bc5
411 433 1:4ca5088da217
412 434 2:051e12f87bf1
413 435 3:0950834f0a9c
414 436 4:5c668c22234f
415 437 5:385a529b6670
416 438 6:a214d5d3811a
417 439 10:429fcd26f52d
418 440 13:b0a32c86eb31
419 441 16:609d82a7ebae
442 17:228c06deef46
420 443 $ hg bisect -g # -> update to rev 9
421 444 Testing changeset 9:3c77083deb4a (5 changesets remaining, ~2 tests)
422 445 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
423 446 $ hg bisect -s # -> update to rev 15
424 447 Testing changeset 15:857b178a7cf3 (5 changesets remaining, ~2 tests)
425 448 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
426 449 $ hg log -q -r 'bisect(ignored)'
427 450 $ hg bisect -b
428 451 Due to skipped revisions, the first bad revision could be any of:
429 452 changeset: 9:3c77083deb4a
430 453 user: test
431 454 date: Thu Jan 01 00:00:09 1970 +0000
432 455 summary: 9
433 456
434 457 changeset: 10:429fcd26f52d
435 458 parent: 9:3c77083deb4a
436 459 parent: 6:a214d5d3811a
437 460 user: test
438 461 date: Thu Jan 01 00:00:10 1970 +0000
439 462 summary: merge 6,9
440 463
441 464 changeset: 13:b0a32c86eb31
442 465 user: test
443 466 date: Thu Jan 01 00:00:13 1970 +0000
444 467 summary: 13
445 468
446 469 changeset: 15:857b178a7cf3
447 470 parent: 13:b0a32c86eb31
448 471 parent: 10:429fcd26f52d
449 472 user: test
450 473 date: Thu Jan 01 00:00:15 1970 +0000
451 474 summary: merge 10,13
452 475
453 476 $ hg log -q -r 'bisect(range)'
454 477 1:4ca5088da217
455 478 2:051e12f87bf1
456 479 3:0950834f0a9c
457 480 4:5c668c22234f
458 481 5:385a529b6670
459 482 6:a214d5d3811a
460 483 8:dab8161ac8fc
461 484 9:3c77083deb4a
462 485 10:429fcd26f52d
463 486 11:82ca6f06eccd
464 487 12:9f259202bbe7
465 488 13:b0a32c86eb31
466 489 15:857b178a7cf3
467 490 16:609d82a7ebae
468 491 $ hg log -q -r 'bisect(ignored)'
469 492
470 493 complex bisect test 4
471 494
472 495 first good revision is 17
473 496 15,16 are skipped an might be the first good revisions as well
474 497
475 498 $ hg bisect -r
476 499 $ hg bisect -g 17
477 500 $ hg bisect -b 8 # -> update to rev 10
478 501 Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests)
479 502 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
480 503 $ hg bisect -b # -> update to rev 13
481 504 Testing changeset 10:429fcd26f52d (5 changesets remaining, ~2 tests)
482 505 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
483 506 $ hg bisect -b # -> update to rev 15
484 507 Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests)
485 508 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
486 509 $ hg log -q -r 'bisect(pruned)'
510 0:33b1f9bc8bc5
511 1:4ca5088da217
512 2:051e12f87bf1
513 3:0950834f0a9c
514 4:5c668c22234f
515 5:385a529b6670
516 6:a214d5d3811a
487 517 8:dab8161ac8fc
488 518 9:3c77083deb4a
489 519 10:429fcd26f52d
490 520 11:82ca6f06eccd
491 521 12:9f259202bbe7
492 522 13:b0a32c86eb31
493 523 17:228c06deef46
494 524 $ hg bisect -s # -> update to rev 16
495 525 Testing changeset 16:609d82a7ebae (3 changesets remaining, ~1 tests)
496 526 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
497 527 $ hg log -q -r 'bisect(pruned)'
528 0:33b1f9bc8bc5
529 1:4ca5088da217
530 2:051e12f87bf1
531 3:0950834f0a9c
532 4:5c668c22234f
533 5:385a529b6670
534 6:a214d5d3811a
498 535 8:dab8161ac8fc
499 536 9:3c77083deb4a
500 537 10:429fcd26f52d
501 538 11:82ca6f06eccd
502 539 12:9f259202bbe7
503 540 13:b0a32c86eb31
504 541 15:857b178a7cf3
505 542 17:228c06deef46
506 543 $ hg bisect -s
507 544 Due to skipped revisions, the first good revision could be any of:
508 545 changeset: 15:857b178a7cf3
509 546 parent: 13:b0a32c86eb31
510 547 parent: 10:429fcd26f52d
511 548 user: test
512 549 date: Thu Jan 01 00:00:15 1970 +0000
513 550 summary: merge 10,13
514 551
515 552 changeset: 16:609d82a7ebae
516 553 user: test
517 554 date: Thu Jan 01 00:00:16 1970 +0000
518 555 summary: 16
519 556
520 557 changeset: 17:228c06deef46
521 558 user: test
522 559 date: Thu Jan 01 00:00:17 1970 +0000
523 560 summary: 17
524 561
525 562 $ hg log -q -r 'bisect(range)'
526 563 8:dab8161ac8fc
527 564 9:3c77083deb4a
528 565 10:429fcd26f52d
529 566 11:82ca6f06eccd
530 567 12:9f259202bbe7
531 568 13:b0a32c86eb31
532 569 15:857b178a7cf3
533 570 16:609d82a7ebae
534 571 17:228c06deef46
535 572 $ hg log -q -r 'bisect(pruned)'
573 0:33b1f9bc8bc5
574 1:4ca5088da217
575 2:051e12f87bf1
576 3:0950834f0a9c
577 4:5c668c22234f
578 5:385a529b6670
579 6:a214d5d3811a
536 580 8:dab8161ac8fc
537 581 9:3c77083deb4a
538 582 10:429fcd26f52d
539 583 11:82ca6f06eccd
540 584 12:9f259202bbe7
541 585 13:b0a32c86eb31
542 586 15:857b178a7cf3
543 587 16:609d82a7ebae
544 588 17:228c06deef46
545 589
546 590 test unrelated revs:
547 591
548 592 $ hg bisect --reset
549 593 $ hg bisect -b 7
550 594 $ hg bisect -g 14
551 595 abort: starting revisions are not directly related
552 596 [255]
553 597 $ hg log -q -r 'bisect(range)'
554 598 $ hg log -q -r 'bisect(pruned)'
599 0:33b1f9bc8bc5
600 1:4ca5088da217
601 2:051e12f87bf1
602 3:0950834f0a9c
603 4:5c668c22234f
555 604 7:50c76098bbf2
556 605 14:faa450606157
557 606 $ hg bisect --reset
558 607
559 608 end at merge: 17 bad, 11 good (but 9 is first bad)
560 609
561 610 $ hg bisect -r
562 611 $ hg bisect -b 17
563 612 $ hg bisect -g 11
564 613 Testing changeset 13:b0a32c86eb31 (5 changesets remaining, ~2 tests)
565 614 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
566 615 $ hg log -q -r 'bisect(ignored)'
567 616 2:051e12f87bf1
568 617 3:0950834f0a9c
569 618 4:5c668c22234f
570 619 5:385a529b6670
571 620 6:a214d5d3811a
572 621 9:3c77083deb4a
573 622 10:429fcd26f52d
574 623 $ hg bisect -g
575 624 Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests)
576 625 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
577 626 $ hg bisect -b
578 627 The first bad revision is:
579 628 changeset: 15:857b178a7cf3
580 629 parent: 13:b0a32c86eb31
581 630 parent: 10:429fcd26f52d
582 631 user: test
583 632 date: Thu Jan 01 00:00:15 1970 +0000
584 633 summary: merge 10,13
585 634
586 635 Not all ancestors of this changeset have been checked.
587 636 Use bisect --extend to continue the bisection from
588 637 the common ancestor, dab8161ac8fc.
589 638 $ hg log -q -r 'bisect(range)'
590 639 11:82ca6f06eccd
591 640 12:9f259202bbe7
592 641 13:b0a32c86eb31
593 642 15:857b178a7cf3
594 643 16:609d82a7ebae
595 644 17:228c06deef46
596 645 $ hg log -q -r 'bisect(pruned)'
646 0:33b1f9bc8bc5
647 1:4ca5088da217
648 8:dab8161ac8fc
597 649 11:82ca6f06eccd
598 650 12:9f259202bbe7
599 651 13:b0a32c86eb31
600 652 15:857b178a7cf3
601 653 16:609d82a7ebae
602 654 17:228c06deef46
655 18:d42e18c7bc9b
603 656 $ hg log -q -r 'bisect(untested)'
604 657 $ hg log -q -r 'bisect(ignored)'
605 658 2:051e12f87bf1
606 659 3:0950834f0a9c
607 660 4:5c668c22234f
608 661 5:385a529b6670
609 662 6:a214d5d3811a
610 663 9:3c77083deb4a
611 664 10:429fcd26f52d
612 665 $ hg bisect --extend
613 666 Extending search to changeset 8:dab8161ac8fc
614 667 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
615 668 $ hg log -q -r 'bisect(untested)'
616 669 $ hg log -q -r 'bisect(ignored)'
617 670 2:051e12f87bf1
618 671 3:0950834f0a9c
619 672 4:5c668c22234f
620 673 5:385a529b6670
621 674 6:a214d5d3811a
622 675 9:3c77083deb4a
623 676 10:429fcd26f52d
624 677 $ hg bisect -g # dab8161ac8fc
625 678 Testing changeset 9:3c77083deb4a (3 changesets remaining, ~1 tests)
626 679 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
627 680 $ hg log -q -r 'bisect(untested)'
628 681 9:3c77083deb4a
629 682 10:429fcd26f52d
630 683 $ hg log -q -r 'bisect(ignored)'
631 684 2:051e12f87bf1
632 685 3:0950834f0a9c
633 686 4:5c668c22234f
634 687 5:385a529b6670
635 688 6:a214d5d3811a
689 $ hg log -q -r 'bisect(goods)'
690 0:33b1f9bc8bc5
691 1:4ca5088da217
692 8:dab8161ac8fc
693 11:82ca6f06eccd
694 12:9f259202bbe7
695 13:b0a32c86eb31
696 $ hg log -q -r 'bisect(bads)'
697 15:857b178a7cf3
698 16:609d82a7ebae
699 17:228c06deef46
700 18:d42e18c7bc9b
636 701 $ hg bisect -b
637 702 The first bad revision is:
638 703 changeset: 9:3c77083deb4a
639 704 user: test
640 705 date: Thu Jan 01 00:00:09 1970 +0000
641 706 summary: 9
642 707
643 708 $ hg log -q -r 'bisect(range)'
644 709 8:dab8161ac8fc
645 710 9:3c77083deb4a
646 711 10:429fcd26f52d
647 712 11:82ca6f06eccd
648 713 12:9f259202bbe7
649 714 13:b0a32c86eb31
650 715 15:857b178a7cf3
651 716 16:609d82a7ebae
652 717 17:228c06deef46
653 718 $ hg log -q -r 'bisect(pruned)'
719 0:33b1f9bc8bc5
720 1:4ca5088da217
654 721 8:dab8161ac8fc
655 722 9:3c77083deb4a
656 723 10:429fcd26f52d
657 724 11:82ca6f06eccd
658 725 12:9f259202bbe7
659 726 13:b0a32c86eb31
660 727 15:857b178a7cf3
661 728 16:609d82a7ebae
662 729 17:228c06deef46
730 18:d42e18c7bc9b
663 731 $ hg log -q -r 'bisect(untested)'
664 732 $ hg log -q -r 'bisect(ignored)'
665 733 2:051e12f87bf1
666 734 3:0950834f0a9c
667 735 4:5c668c22234f
668 736 5:385a529b6670
669 737 6:a214d5d3811a
738 $ hg log -q -r 'bisect(goods)'
739 0:33b1f9bc8bc5
740 1:4ca5088da217
741 8:dab8161ac8fc
742 11:82ca6f06eccd
743 12:9f259202bbe7
744 13:b0a32c86eb31
745 $ hg log -q -r 'bisect(bads)'
746 9:3c77083deb4a
747 10:429fcd26f52d
748 15:857b178a7cf3
749 16:609d82a7ebae
750 17:228c06deef46
751 18:d42e18c7bc9b
670 752
671 753 user adds irrelevant but consistent information (here: -g 2) to bisect state
672 754
673 755 $ hg bisect -r
674 756 $ hg bisect -b 13
675 757 $ hg bisect -g 8
676 758 Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests)
677 759 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
678 760 $ hg log -q -r 'bisect(untested)'
679 761 11:82ca6f06eccd
680 762 12:9f259202bbe7
681 763 $ hg bisect -g 2
682 764 Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests)
683 765 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
684 766 $ hg log -q -r 'bisect(untested)'
685 767 11:82ca6f06eccd
686 768 12:9f259202bbe7
687 769 $ hg bisect -b
688 770 The first bad revision is:
689 771 changeset: 11:82ca6f06eccd
690 772 parent: 8:dab8161ac8fc
691 773 user: test
692 774 date: Thu Jan 01 00:00:11 1970 +0000
693 775 summary: 11
694 776
695 777 $ hg log -q -r 'bisect(range)'
696 778 8:dab8161ac8fc
697 779 11:82ca6f06eccd
698 780 12:9f259202bbe7
699 781 13:b0a32c86eb31
700 782 $ hg log -q -r 'bisect(pruned)'
783 0:33b1f9bc8bc5
784 1:4ca5088da217
701 785 2:051e12f87bf1
702 786 8:dab8161ac8fc
703 787 11:82ca6f06eccd
704 788 12:9f259202bbe7
705 789 13:b0a32c86eb31
790 14:faa450606157
791 15:857b178a7cf3
792 16:609d82a7ebae
793 17:228c06deef46
794 18:d42e18c7bc9b
706 795 $ hg log -q -r 'bisect(untested)'
General Comments 0
You need to be logged in to leave comments. Login now