##// END OF EJS Templates
revset.bisect: add new 'untested' set to the bisect keyword...
"Yann E. MORIN" -
r15138:883d2823 default
parent child Browse files
Show More
@@ -1,199 +1,203 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 161 - ``good``, ``bad``, ``skip``: as the names imply
162 162 - ``range`` : all csets taking part in the bisection
163 163 - ``pruned`` : good|bad|skip, excluding out-of-range csets
164 - ``untested`` : csets whose fate is yet unknown
164 165 """
165 166 state = load_state(repo)
166 167 if status in ('good', 'bad', 'skip'):
167 168 return [repo.changelog.rev(n) for n in state[status]]
168 169 else:
169 170 # Build sets of good, bad, and skipped csets
170 171 goods = set(repo.changelog.rev(n) for n in state['good'])
171 172 bads = set(repo.changelog.rev(n) for n in state['bad'])
172 173 skips = set(repo.changelog.rev(n) for n in state['skip'])
173 174
174 175 # Build kinship of good csets
175 176 ga = goods.copy() # Goods' Ancestors
176 177 gd = goods.copy() # Goods' Descendants
177 178 for g in goods:
178 179 ga |= set(repo.changelog.ancestors(g))
179 180 gd |= set(repo.changelog.descendants(g))
180 181
181 182 # Build kinship of bad csets
182 183 ba = bads.copy() # Bads' Ancestors
183 184 bd = bads.copy() # Bads' Descendants
184 185 for b in bads:
185 186 ba |= set(repo.changelog.ancestors(b))
186 187 bd |= set(repo.changelog.descendants(b))
187 188
188 189 # Build the range of the bisection
189 190 range = set(c for c in ba if c in gd)
190 191 range |= set(c for c in ga if c in bd)
191 192
192 193 if status == 'range':
193 194 return [c for c in range]
194 195 elif status == 'pruned':
195 196 # We do not want skipped csets that are out-of-range
196 197 return [c for c in range if c in (goods | bads | skips)]
198 elif status == 'untested':
199 # Return the csets in range that are not pruned
200 return [c for c in range if not c in (goods | bads | skips)]
197 201
198 202 else:
199 203 raise error.ParseError(_('invalid bisect state'))
@@ -1,1105 +1,1106 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 240 Changesets marked in the specified bisect status (``good``, ``bad``,
241 241 ``skip``), or any of the meta-status:
242 242
243 243 - ``range`` : all csets taking part in the bisection
244 244 - ``pruned`` : good|bad|skip, excluding out-of-range csets
245 - ``untested`` : csets whose fate is yet unknown
245 246 """
246 247 status = getstring(x, _("bisect requires a string")).lower()
247 248 return [r for r in subset if r in hbisect.get(repo, status)]
248 249
249 250 # Backward-compatibility
250 251 # - no help entry so that we do not advertise it any more
251 252 def bisected(repo, subset, x):
252 253 return bisect(repo, subset, x)
253 254
254 255 def bookmark(repo, subset, x):
255 256 """``bookmark([name])``
256 257 The named bookmark or all bookmarks.
257 258 """
258 259 # i18n: "bookmark" is a keyword
259 260 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
260 261 if args:
261 262 bm = getstring(args[0],
262 263 # i18n: "bookmark" is a keyword
263 264 _('the argument to bookmark must be a string'))
264 265 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
265 266 if not bmrev:
266 267 raise util.Abort(_("bookmark '%s' does not exist") % bm)
267 268 bmrev = repo[bmrev].rev()
268 269 return [r for r in subset if r == bmrev]
269 270 bms = set([repo[r].rev()
270 271 for r in bookmarksmod.listbookmarks(repo).values()])
271 272 return [r for r in subset if r in bms]
272 273
273 274 def branch(repo, subset, x):
274 275 """``branch(string or set)``
275 276 All changesets belonging to the given branch or the branches of the given
276 277 changesets.
277 278 """
278 279 try:
279 280 b = getstring(x, '')
280 281 if b in repo.branchmap():
281 282 return [r for r in subset if repo[r].branch() == b]
282 283 except error.ParseError:
283 284 # not a string, but another revspec, e.g. tip()
284 285 pass
285 286
286 287 s = getset(repo, range(len(repo)), x)
287 288 b = set()
288 289 for r in s:
289 290 b.add(repo[r].branch())
290 291 s = set(s)
291 292 return [r for r in subset if r in s or repo[r].branch() in b]
292 293
293 294 def checkstatus(repo, subset, pat, field):
294 295 m = matchmod.match(repo.root, repo.getcwd(), [pat])
295 296 s = []
296 297 fast = (m.files() == [pat])
297 298 for r in subset:
298 299 c = repo[r]
299 300 if fast:
300 301 if pat not in c.files():
301 302 continue
302 303 else:
303 304 for f in c.files():
304 305 if m(f):
305 306 break
306 307 else:
307 308 continue
308 309 files = repo.status(c.p1().node(), c.node())[field]
309 310 if fast:
310 311 if pat in files:
311 312 s.append(r)
312 313 else:
313 314 for f in files:
314 315 if m(f):
315 316 s.append(r)
316 317 break
317 318 return s
318 319
319 320 def children(repo, subset, x):
320 321 """``children(set)``
321 322 Child changesets of changesets in set.
322 323 """
323 324 cs = set()
324 325 cl = repo.changelog
325 326 s = set(getset(repo, range(len(repo)), x))
326 327 for r in xrange(0, len(repo)):
327 328 for p in cl.parentrevs(r):
328 329 if p in s:
329 330 cs.add(r)
330 331 return [r for r in subset if r in cs]
331 332
332 333 def closed(repo, subset, x):
333 334 """``closed()``
334 335 Changeset is closed.
335 336 """
336 337 # i18n: "closed" is a keyword
337 338 getargs(x, 0, 0, _("closed takes no arguments"))
338 339 return [r for r in subset if repo[r].extra().get('close')]
339 340
340 341 def contains(repo, subset, x):
341 342 """``contains(pattern)``
342 343 Revision contains a file matching pattern. See :hg:`help patterns`
343 344 for information about file patterns.
344 345 """
345 346 # i18n: "contains" is a keyword
346 347 pat = getstring(x, _("contains requires a pattern"))
347 348 m = matchmod.match(repo.root, repo.getcwd(), [pat])
348 349 s = []
349 350 if m.files() == [pat]:
350 351 for r in subset:
351 352 if pat in repo[r]:
352 353 s.append(r)
353 354 else:
354 355 for r in subset:
355 356 for f in repo[r].manifest():
356 357 if m(f):
357 358 s.append(r)
358 359 break
359 360 return s
360 361
361 362 def date(repo, subset, x):
362 363 """``date(interval)``
363 364 Changesets within the interval, see :hg:`help dates`.
364 365 """
365 366 # i18n: "date" is a keyword
366 367 ds = getstring(x, _("date requires a string"))
367 368 dm = util.matchdate(ds)
368 369 return [r for r in subset if dm(repo[r].date()[0])]
369 370
370 371 def desc(repo, subset, x):
371 372 """``desc(string)``
372 373 Search commit message for string. The match is case-insensitive.
373 374 """
374 375 # i18n: "desc" is a keyword
375 376 ds = getstring(x, _("desc requires a string")).lower()
376 377 l = []
377 378 for r in subset:
378 379 c = repo[r]
379 380 if ds in c.description().lower():
380 381 l.append(r)
381 382 return l
382 383
383 384 def descendants(repo, subset, x):
384 385 """``descendants(set)``
385 386 Changesets which are descendants of changesets in set.
386 387 """
387 388 args = getset(repo, range(len(repo)), x)
388 389 if not args:
389 390 return []
390 391 s = set(repo.changelog.descendants(*args)) | set(args)
391 392 return [r for r in subset if r in s]
392 393
393 394 def filelog(repo, subset, x):
394 395 """``filelog(pattern)``
395 396 Changesets connected to the specified filelog.
396 397 """
397 398
398 399 pat = getstring(x, _("filelog requires a pattern"))
399 400 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath')
400 401 s = set()
401 402
402 403 if not m.anypats():
403 404 for f in m.files():
404 405 fl = repo.file(f)
405 406 for fr in fl:
406 407 s.add(fl.linkrev(fr))
407 408 else:
408 409 for f in repo[None]:
409 410 if m(f):
410 411 fl = repo.file(f)
411 412 for fr in fl:
412 413 s.add(fl.linkrev(fr))
413 414
414 415 return [r for r in subset if r in s]
415 416
416 417 def first(repo, subset, x):
417 418 """``first(set, [n])``
418 419 An alias for limit().
419 420 """
420 421 return limit(repo, subset, x)
421 422
422 423 def follow(repo, subset, x):
423 424 """``follow([file])``
424 425 An alias for ``::.`` (ancestors of the working copy's first parent).
425 426 If a filename is specified, the history of the given file is followed,
426 427 including copies.
427 428 """
428 429 # i18n: "follow" is a keyword
429 430 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
430 431 p = repo['.'].rev()
431 432 if l:
432 433 x = getstring(l[0], _("follow expected a filename"))
433 434 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
434 435 else:
435 436 s = set(repo.changelog.ancestors(p))
436 437
437 438 s |= set([p])
438 439 return [r for r in subset if r in s]
439 440
440 441 def followfile(repo, subset, x):
441 442 """``follow()``
442 443 An alias for ``::.`` (ancestors of the working copy's first parent).
443 444 """
444 445 # i18n: "follow" is a keyword
445 446 getargs(x, 0, 0, _("follow takes no arguments"))
446 447 p = repo['.'].rev()
447 448 s = set(repo.changelog.ancestors(p)) | set([p])
448 449 return [r for r in subset if r in s]
449 450
450 451 def getall(repo, subset, x):
451 452 """``all()``
452 453 All changesets, the same as ``0:tip``.
453 454 """
454 455 # i18n: "all" is a keyword
455 456 getargs(x, 0, 0, _("all takes no arguments"))
456 457 return subset
457 458
458 459 def grep(repo, subset, x):
459 460 """``grep(regex)``
460 461 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
461 462 to ensure special escape characters are handled correctly. Unlike
462 463 ``keyword(string)``, the match is case-sensitive.
463 464 """
464 465 try:
465 466 # i18n: "grep" is a keyword
466 467 gr = re.compile(getstring(x, _("grep requires a string")))
467 468 except re.error, e:
468 469 raise error.ParseError(_('invalid match pattern: %s') % e)
469 470 l = []
470 471 for r in subset:
471 472 c = repo[r]
472 473 for e in c.files() + [c.user(), c.description()]:
473 474 if gr.search(e):
474 475 l.append(r)
475 476 break
476 477 return l
477 478
478 479 def hasfile(repo, subset, x):
479 480 """``file(pattern)``
480 481 Changesets affecting files matched by pattern.
481 482 """
482 483 # i18n: "file" is a keyword
483 484 pat = getstring(x, _("file requires a pattern"))
484 485 m = matchmod.match(repo.root, repo.getcwd(), [pat])
485 486 s = []
486 487 for r in subset:
487 488 for f in repo[r].files():
488 489 if m(f):
489 490 s.append(r)
490 491 break
491 492 return s
492 493
493 494 def head(repo, subset, x):
494 495 """``head()``
495 496 Changeset is a named branch head.
496 497 """
497 498 # i18n: "head" is a keyword
498 499 getargs(x, 0, 0, _("head takes no arguments"))
499 500 hs = set()
500 501 for b, ls in repo.branchmap().iteritems():
501 502 hs.update(repo[h].rev() for h in ls)
502 503 return [r for r in subset if r in hs]
503 504
504 505 def heads(repo, subset, x):
505 506 """``heads(set)``
506 507 Members of set with no children in set.
507 508 """
508 509 s = getset(repo, subset, x)
509 510 ps = set(parents(repo, subset, x))
510 511 return [r for r in s if r not in ps]
511 512
512 513 def keyword(repo, subset, x):
513 514 """``keyword(string)``
514 515 Search commit message, user name, and names of changed files for
515 516 string. The match is case-insensitive.
516 517 """
517 518 # i18n: "keyword" is a keyword
518 519 kw = getstring(x, _("keyword requires a string")).lower()
519 520 l = []
520 521 for r in subset:
521 522 c = repo[r]
522 523 t = " ".join(c.files() + [c.user(), c.description()])
523 524 if kw in t.lower():
524 525 l.append(r)
525 526 return l
526 527
527 528 def limit(repo, subset, x):
528 529 """``limit(set, [n])``
529 530 First n members of set, defaulting to 1.
530 531 """
531 532 # i18n: "limit" is a keyword
532 533 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
533 534 try:
534 535 lim = 1
535 536 if len(l) == 2:
536 537 # i18n: "limit" is a keyword
537 538 lim = int(getstring(l[1], _("limit requires a number")))
538 539 except (TypeError, ValueError):
539 540 # i18n: "limit" is a keyword
540 541 raise error.ParseError(_("limit expects a number"))
541 542 ss = set(subset)
542 543 os = getset(repo, range(len(repo)), l[0])[:lim]
543 544 return [r for r in os if r in ss]
544 545
545 546 def last(repo, subset, x):
546 547 """``last(set, [n])``
547 548 Last n members of set, defaulting to 1.
548 549 """
549 550 # i18n: "last" is a keyword
550 551 l = getargs(x, 1, 2, _("last requires one or two arguments"))
551 552 try:
552 553 lim = 1
553 554 if len(l) == 2:
554 555 # i18n: "last" is a keyword
555 556 lim = int(getstring(l[1], _("last requires a number")))
556 557 except (TypeError, ValueError):
557 558 # i18n: "last" is a keyword
558 559 raise error.ParseError(_("last expects a number"))
559 560 ss = set(subset)
560 561 os = getset(repo, range(len(repo)), l[0])[-lim:]
561 562 return [r for r in os if r in ss]
562 563
563 564 def maxrev(repo, subset, x):
564 565 """``max(set)``
565 566 Changeset with highest revision number in set.
566 567 """
567 568 os = getset(repo, range(len(repo)), x)
568 569 if os:
569 570 m = max(os)
570 571 if m in subset:
571 572 return [m]
572 573 return []
573 574
574 575 def merge(repo, subset, x):
575 576 """``merge()``
576 577 Changeset is a merge changeset.
577 578 """
578 579 # i18n: "merge" is a keyword
579 580 getargs(x, 0, 0, _("merge takes no arguments"))
580 581 cl = repo.changelog
581 582 return [r for r in subset if cl.parentrevs(r)[1] != -1]
582 583
583 584 def minrev(repo, subset, x):
584 585 """``min(set)``
585 586 Changeset with lowest revision number in set.
586 587 """
587 588 os = getset(repo, range(len(repo)), x)
588 589 if os:
589 590 m = min(os)
590 591 if m in subset:
591 592 return [m]
592 593 return []
593 594
594 595 def modifies(repo, subset, x):
595 596 """``modifies(pattern)``
596 597 Changesets modifying files matched by pattern.
597 598 """
598 599 # i18n: "modifies" is a keyword
599 600 pat = getstring(x, _("modifies requires a pattern"))
600 601 return checkstatus(repo, subset, pat, 0)
601 602
602 603 def node(repo, subset, x):
603 604 """``id(string)``
604 605 Revision non-ambiguously specified by the given hex string prefix.
605 606 """
606 607 # i18n: "id" is a keyword
607 608 l = getargs(x, 1, 1, _("id requires one argument"))
608 609 # i18n: "id" is a keyword
609 610 n = getstring(l[0], _("id requires a string"))
610 611 if len(n) == 40:
611 612 rn = repo[n].rev()
612 613 else:
613 614 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
614 615 return [r for r in subset if r == rn]
615 616
616 617 def outgoing(repo, subset, x):
617 618 """``outgoing([path])``
618 619 Changesets not found in the specified destination repository, or the
619 620 default push location.
620 621 """
621 622 import hg # avoid start-up nasties
622 623 # i18n: "outgoing" is a keyword
623 624 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
624 625 # i18n: "outgoing" is a keyword
625 626 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
626 627 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
627 628 dest, branches = hg.parseurl(dest)
628 629 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
629 630 if revs:
630 631 revs = [repo.lookup(rev) for rev in revs]
631 632 other = hg.peer(repo, {}, dest)
632 633 repo.ui.pushbuffer()
633 634 common, outheads = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
634 635 repo.ui.popbuffer()
635 636 cl = repo.changelog
636 637 o = set([cl.rev(r) for r in repo.changelog.findmissing(common, outheads)])
637 638 return [r for r in subset if r in o]
638 639
639 640 def p1(repo, subset, x):
640 641 """``p1([set])``
641 642 First parent of changesets in set, or the working directory.
642 643 """
643 644 if x is None:
644 645 p = repo[x].p1().rev()
645 646 return [r for r in subset if r == p]
646 647
647 648 ps = set()
648 649 cl = repo.changelog
649 650 for r in getset(repo, range(len(repo)), x):
650 651 ps.add(cl.parentrevs(r)[0])
651 652 return [r for r in subset if r in ps]
652 653
653 654 def p2(repo, subset, x):
654 655 """``p2([set])``
655 656 Second parent of changesets in set, or the working directory.
656 657 """
657 658 if x is None:
658 659 ps = repo[x].parents()
659 660 try:
660 661 p = ps[1].rev()
661 662 return [r for r in subset if r == p]
662 663 except IndexError:
663 664 return []
664 665
665 666 ps = set()
666 667 cl = repo.changelog
667 668 for r in getset(repo, range(len(repo)), x):
668 669 ps.add(cl.parentrevs(r)[1])
669 670 return [r for r in subset if r in ps]
670 671
671 672 def parents(repo, subset, x):
672 673 """``parents([set])``
673 674 The set of all parents for all changesets in set, or the working directory.
674 675 """
675 676 if x is None:
676 677 ps = tuple(p.rev() for p in repo[x].parents())
677 678 return [r for r in subset if r in ps]
678 679
679 680 ps = set()
680 681 cl = repo.changelog
681 682 for r in getset(repo, range(len(repo)), x):
682 683 ps.update(cl.parentrevs(r))
683 684 return [r for r in subset if r in ps]
684 685
685 686 def parentspec(repo, subset, x, n):
686 687 """``set^0``
687 688 The set.
688 689 ``set^1`` (or ``set^``), ``set^2``
689 690 First or second parent, respectively, of all changesets in set.
690 691 """
691 692 try:
692 693 n = int(n[1])
693 694 if n not in (0, 1, 2):
694 695 raise ValueError
695 696 except (TypeError, ValueError):
696 697 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
697 698 ps = set()
698 699 cl = repo.changelog
699 700 for r in getset(repo, subset, x):
700 701 if n == 0:
701 702 ps.add(r)
702 703 elif n == 1:
703 704 ps.add(cl.parentrevs(r)[0])
704 705 elif n == 2:
705 706 parents = cl.parentrevs(r)
706 707 if len(parents) > 1:
707 708 ps.add(parents[1])
708 709 return [r for r in subset if r in ps]
709 710
710 711 def present(repo, subset, x):
711 712 """``present(set)``
712 713 An empty set, if any revision in set isn't found; otherwise,
713 714 all revisions in set.
714 715 """
715 716 try:
716 717 return getset(repo, subset, x)
717 718 except error.RepoLookupError:
718 719 return []
719 720
720 721 def removes(repo, subset, x):
721 722 """``removes(pattern)``
722 723 Changesets which remove files matching pattern.
723 724 """
724 725 # i18n: "removes" is a keyword
725 726 pat = getstring(x, _("removes requires a pattern"))
726 727 return checkstatus(repo, subset, pat, 2)
727 728
728 729 def rev(repo, subset, x):
729 730 """``rev(number)``
730 731 Revision with the given numeric identifier.
731 732 """
732 733 # i18n: "rev" is a keyword
733 734 l = getargs(x, 1, 1, _("rev requires one argument"))
734 735 try:
735 736 # i18n: "rev" is a keyword
736 737 l = int(getstring(l[0], _("rev requires a number")))
737 738 except (TypeError, ValueError):
738 739 # i18n: "rev" is a keyword
739 740 raise error.ParseError(_("rev expects a number"))
740 741 return [r for r in subset if r == l]
741 742
742 743 def reverse(repo, subset, x):
743 744 """``reverse(set)``
744 745 Reverse order of set.
745 746 """
746 747 l = getset(repo, subset, x)
747 748 l.reverse()
748 749 return l
749 750
750 751 def roots(repo, subset, x):
751 752 """``roots(set)``
752 753 Changesets with no parent changeset in set.
753 754 """
754 755 s = getset(repo, subset, x)
755 756 cs = set(children(repo, subset, x))
756 757 return [r for r in s if r not in cs]
757 758
758 759 def sort(repo, subset, x):
759 760 """``sort(set[, [-]key...])``
760 761 Sort set by keys. The default sort order is ascending, specify a key
761 762 as ``-key`` to sort in descending order.
762 763
763 764 The keys can be:
764 765
765 766 - ``rev`` for the revision number,
766 767 - ``branch`` for the branch name,
767 768 - ``desc`` for the commit message (description),
768 769 - ``user`` for user name (``author`` can be used as an alias),
769 770 - ``date`` for the commit date
770 771 """
771 772 # i18n: "sort" is a keyword
772 773 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
773 774 keys = "rev"
774 775 if len(l) == 2:
775 776 keys = getstring(l[1], _("sort spec must be a string"))
776 777
777 778 s = l[0]
778 779 keys = keys.split()
779 780 l = []
780 781 def invert(s):
781 782 return "".join(chr(255 - ord(c)) for c in s)
782 783 for r in getset(repo, subset, s):
783 784 c = repo[r]
784 785 e = []
785 786 for k in keys:
786 787 if k == 'rev':
787 788 e.append(r)
788 789 elif k == '-rev':
789 790 e.append(-r)
790 791 elif k == 'branch':
791 792 e.append(c.branch())
792 793 elif k == '-branch':
793 794 e.append(invert(c.branch()))
794 795 elif k == 'desc':
795 796 e.append(c.description())
796 797 elif k == '-desc':
797 798 e.append(invert(c.description()))
798 799 elif k in 'user author':
799 800 e.append(c.user())
800 801 elif k in '-user -author':
801 802 e.append(invert(c.user()))
802 803 elif k == 'date':
803 804 e.append(c.date()[0])
804 805 elif k == '-date':
805 806 e.append(-c.date()[0])
806 807 else:
807 808 raise error.ParseError(_("unknown sort key %r") % k)
808 809 e.append(r)
809 810 l.append(e)
810 811 l.sort()
811 812 return [e[-1] for e in l]
812 813
813 814 def tag(repo, subset, x):
814 815 """``tag([name])``
815 816 The specified tag by name, or all tagged revisions if no name is given.
816 817 """
817 818 # i18n: "tag" is a keyword
818 819 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
819 820 cl = repo.changelog
820 821 if args:
821 822 tn = getstring(args[0],
822 823 # i18n: "tag" is a keyword
823 824 _('the argument to tag must be a string'))
824 825 if not repo.tags().get(tn, None):
825 826 raise util.Abort(_("tag '%s' does not exist") % tn)
826 827 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
827 828 else:
828 829 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
829 830 return [r for r in subset if r in s]
830 831
831 832 def tagged(repo, subset, x):
832 833 return tag(repo, subset, x)
833 834
834 835 def user(repo, subset, x):
835 836 """``user(string)``
836 837 User name contains string. The match is case-insensitive.
837 838 """
838 839 return author(repo, subset, x)
839 840
840 841 symbols = {
841 842 "adds": adds,
842 843 "all": getall,
843 844 "ancestor": ancestor,
844 845 "ancestors": ancestors,
845 846 "author": author,
846 847 "bisect": bisect,
847 848 "bisected": bisected,
848 849 "bookmark": bookmark,
849 850 "branch": branch,
850 851 "children": children,
851 852 "closed": closed,
852 853 "contains": contains,
853 854 "date": date,
854 855 "desc": desc,
855 856 "descendants": descendants,
856 857 "file": hasfile,
857 858 "filelog": filelog,
858 859 "first": first,
859 860 "follow": follow,
860 861 "grep": grep,
861 862 "head": head,
862 863 "heads": heads,
863 864 "id": node,
864 865 "keyword": keyword,
865 866 "last": last,
866 867 "limit": limit,
867 868 "max": maxrev,
868 869 "merge": merge,
869 870 "min": minrev,
870 871 "modifies": modifies,
871 872 "outgoing": outgoing,
872 873 "p1": p1,
873 874 "p2": p2,
874 875 "parents": parents,
875 876 "present": present,
876 877 "removes": removes,
877 878 "rev": rev,
878 879 "reverse": reverse,
879 880 "roots": roots,
880 881 "sort": sort,
881 882 "tag": tag,
882 883 "tagged": tagged,
883 884 "user": user,
884 885 }
885 886
886 887 methods = {
887 888 "range": rangeset,
888 889 "string": stringset,
889 890 "symbol": symbolset,
890 891 "and": andset,
891 892 "or": orset,
892 893 "not": notset,
893 894 "list": listset,
894 895 "func": func,
895 896 "ancestor": ancestorspec,
896 897 "parent": parentspec,
897 898 "parentpost": p1,
898 899 }
899 900
900 901 def optimize(x, small):
901 902 if x is None:
902 903 return 0, x
903 904
904 905 smallbonus = 1
905 906 if small:
906 907 smallbonus = .5
907 908
908 909 op = x[0]
909 910 if op == 'minus':
910 911 return optimize(('and', x[1], ('not', x[2])), small)
911 912 elif op == 'dagrange':
912 913 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
913 914 ('func', ('symbol', 'ancestors'), x[2])), small)
914 915 elif op == 'dagrangepre':
915 916 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
916 917 elif op == 'dagrangepost':
917 918 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
918 919 elif op == 'rangepre':
919 920 return optimize(('range', ('string', '0'), x[1]), small)
920 921 elif op == 'rangepost':
921 922 return optimize(('range', x[1], ('string', 'tip')), small)
922 923 elif op == 'negate':
923 924 return optimize(('string',
924 925 '-' + getstring(x[1], _("can't negate that"))), small)
925 926 elif op in 'string symbol negate':
926 927 return smallbonus, x # single revisions are small
927 928 elif op == 'and' or op == 'dagrange':
928 929 wa, ta = optimize(x[1], True)
929 930 wb, tb = optimize(x[2], True)
930 931 w = min(wa, wb)
931 932 if wa > wb:
932 933 return w, (op, tb, ta)
933 934 return w, (op, ta, tb)
934 935 elif op == 'or':
935 936 wa, ta = optimize(x[1], False)
936 937 wb, tb = optimize(x[2], False)
937 938 if wb < wa:
938 939 wb, wa = wa, wb
939 940 return max(wa, wb), (op, ta, tb)
940 941 elif op == 'not':
941 942 o = optimize(x[1], not small)
942 943 return o[0], (op, o[1])
943 944 elif op == 'parentpost':
944 945 o = optimize(x[1], small)
945 946 return o[0], (op, o[1])
946 947 elif op == 'group':
947 948 return optimize(x[1], small)
948 949 elif op in 'range list parent ancestorspec':
949 950 if op == 'parent':
950 951 # x^:y means (x^) : y, not x ^ (:y)
951 952 post = ('parentpost', x[1])
952 953 if x[2][0] == 'dagrangepre':
953 954 return optimize(('dagrange', post, x[2][1]), small)
954 955 elif x[2][0] == 'rangepre':
955 956 return optimize(('range', post, x[2][1]), small)
956 957
957 958 wa, ta = optimize(x[1], small)
958 959 wb, tb = optimize(x[2], small)
959 960 return wa + wb, (op, ta, tb)
960 961 elif op == 'func':
961 962 f = getstring(x[1], _("not a symbol"))
962 963 wa, ta = optimize(x[2], small)
963 964 if f in ("author branch closed date desc file grep keyword "
964 965 "outgoing user"):
965 966 w = 10 # slow
966 967 elif f in "modifies adds removes":
967 968 w = 30 # slower
968 969 elif f == "contains":
969 970 w = 100 # very slow
970 971 elif f == "ancestor":
971 972 w = 1 * smallbonus
972 973 elif f in "reverse limit first":
973 974 w = 0
974 975 elif f in "sort":
975 976 w = 10 # assume most sorts look at changelog
976 977 else:
977 978 w = 1
978 979 return w + wa, (op, x[1], ta)
979 980 return 1, x
980 981
981 982 class revsetalias(object):
982 983 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
983 984 args = None
984 985
985 986 def __init__(self, name, value):
986 987 '''Aliases like:
987 988
988 989 h = heads(default)
989 990 b($1) = ancestors($1) - ancestors(default)
990 991 '''
991 992 if isinstance(name, tuple): # parameter substitution
992 993 self.tree = name
993 994 self.replacement = value
994 995 else: # alias definition
995 996 m = self.funcre.search(name)
996 997 if m:
997 998 self.tree = ('func', ('symbol', m.group(1)))
998 999 self.args = [x.strip() for x in m.group(2).split(',')]
999 1000 for arg in self.args:
1000 1001 value = value.replace(arg, repr(arg))
1001 1002 else:
1002 1003 self.tree = ('symbol', name)
1003 1004
1004 1005 self.replacement, pos = parse(value)
1005 1006 if pos != len(value):
1006 1007 raise error.ParseError(_('invalid token'), pos)
1007 1008
1008 1009 def process(self, tree):
1009 1010 if isinstance(tree, tuple):
1010 1011 if self.args is None:
1011 1012 if tree == self.tree:
1012 1013 return self.replacement
1013 1014 elif tree[:2] == self.tree:
1014 1015 l = getlist(tree[2])
1015 1016 if len(l) != len(self.args):
1016 1017 raise error.ParseError(
1017 1018 _('invalid number of arguments: %s') % len(l))
1018 1019 result = self.replacement
1019 1020 for a, v in zip(self.args, l):
1020 1021 valalias = revsetalias(('string', a), v)
1021 1022 result = valalias.process(result)
1022 1023 return result
1023 1024 return tuple(map(self.process, tree))
1024 1025 return tree
1025 1026
1026 1027 def findaliases(ui, tree):
1027 1028 for k, v in ui.configitems('revsetalias'):
1028 1029 alias = revsetalias(k, v)
1029 1030 tree = alias.process(tree)
1030 1031 return tree
1031 1032
1032 1033 parse = parser.parser(tokenize, elements).parse
1033 1034
1034 1035 def match(ui, spec):
1035 1036 if not spec:
1036 1037 raise error.ParseError(_("empty query"))
1037 1038 tree, pos = parse(spec)
1038 1039 if (pos != len(spec)):
1039 1040 raise error.ParseError(_("invalid token"), pos)
1040 1041 if ui:
1041 1042 tree = findaliases(ui, tree)
1042 1043 weight, tree = optimize(tree, True)
1043 1044 def mfunc(repo, subset):
1044 1045 return getset(repo, subset, tree)
1045 1046 return mfunc
1046 1047
1047 1048 def formatspec(expr, *args):
1048 1049 '''
1049 1050 This is a convenience function for using revsets internally, and
1050 1051 escapes arguments appropriately. Aliases are intentionally ignored
1051 1052 so that intended expression behavior isn't accidentally subverted.
1052 1053
1053 1054 Supported arguments:
1054 1055
1055 1056 %d = int(arg), no quoting
1056 1057 %s = string(arg), escaped and single-quoted
1057 1058 %b = arg.branch(), escaped and single-quoted
1058 1059 %n = hex(arg), single-quoted
1059 1060 %% = a literal '%'
1060 1061
1061 1062 >>> formatspec('%d:: and not %d::', 10, 20)
1062 1063 '10:: and not 20::'
1063 1064 >>> formatspec('keyword(%s)', 'foo\\xe9')
1064 1065 "keyword('foo\\\\xe9')"
1065 1066 >>> b = lambda: 'default'
1066 1067 >>> b.branch = b
1067 1068 >>> formatspec('branch(%b)', b)
1068 1069 "branch('default')"
1069 1070 '''
1070 1071
1071 1072 def quote(s):
1072 1073 return repr(str(s))
1073 1074
1074 1075 ret = ''
1075 1076 pos = 0
1076 1077 arg = 0
1077 1078 while pos < len(expr):
1078 1079 c = expr[pos]
1079 1080 if c == '%':
1080 1081 pos += 1
1081 1082 d = expr[pos]
1082 1083 if d == '%':
1083 1084 ret += d
1084 1085 elif d == 'd':
1085 1086 ret += str(int(args[arg]))
1086 1087 arg += 1
1087 1088 elif d == 's':
1088 1089 ret += quote(args[arg])
1089 1090 arg += 1
1090 1091 elif d == 'n':
1091 1092 ret += quote(node.hex(args[arg]))
1092 1093 arg += 1
1093 1094 elif d == 'b':
1094 1095 ret += quote(args[arg].branch())
1095 1096 arg += 1
1096 1097 else:
1097 1098 raise util.Abort('unexpected revspec format character %s' % d)
1098 1099 else:
1099 1100 ret += c
1100 1101 pos += 1
1101 1102
1102 1103 return ret
1103 1104
1104 1105 # tell hggettext to extract docstrings from these functions:
1105 1106 i18nfunctions = symbols.values()
@@ -1,580 +1,634 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 $ hg log -q -r 'bisect(untested)'
256 1:4ca5088da217
257 2:051e12f87bf1
258 3:0950834f0a9c
259 4:5c668c22234f
260 5:385a529b6670
261 6:a214d5d3811a
262 8:dab8161ac8fc
263 9:3c77083deb4a
264 10:429fcd26f52d
265 11:82ca6f06eccd
266 12:9f259202bbe7
267 13:b0a32c86eb31
268 15:857b178a7cf3
269 16:609d82a7ebae
255 270 $ hg bisect -g # -> update to rev 13
256 271 Testing changeset 13:b0a32c86eb31 (9 changesets remaining, ~3 tests)
257 272 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
258 273 $ hg bisect -s # -> update to rev 10
259 274 Testing changeset 10:429fcd26f52d (9 changesets remaining, ~3 tests)
260 275 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
261 276 $ hg bisect -b # -> update to rev 8
262 277 Testing changeset 8:dab8161ac8fc (3 changesets remaining, ~1 tests)
263 278 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
264 279 $ hg bisect -g # -> update to rev 9
265 280 Testing changeset 9:3c77083deb4a (2 changesets remaining, ~1 tests)
266 281 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
267 282 $ hg bisect -b
268 283 The first bad revision is:
269 284 changeset: 9:3c77083deb4a
270 285 user: test
271 286 date: Thu Jan 01 00:00:09 1970 +0000
272 287 summary: 9
273 288
274 289 $ hg log -q -r 'bisect(range)'
275 290 0:33b1f9bc8bc5
276 291 1:4ca5088da217
277 292 2:051e12f87bf1
278 293 3:0950834f0a9c
279 294 4:5c668c22234f
280 295 5:385a529b6670
281 296 6:a214d5d3811a
282 297 8:dab8161ac8fc
283 298 9:3c77083deb4a
284 299 10:429fcd26f52d
285 300 11:82ca6f06eccd
286 301 12:9f259202bbe7
287 302 13:b0a32c86eb31
288 303 15:857b178a7cf3
289 304 16:609d82a7ebae
290 305 17:228c06deef46
291 306 $ hg log -q -r 'bisect(pruned)'
292 307 0:33b1f9bc8bc5
293 308 6:a214d5d3811a
294 309 8:dab8161ac8fc
295 310 9:3c77083deb4a
296 311 10:429fcd26f52d
297 312 13:b0a32c86eb31
298 313 17:228c06deef46
314 $ hg log -q -r 'bisect(untested)'
315 1:4ca5088da217
316 2:051e12f87bf1
317 3:0950834f0a9c
318 4:5c668c22234f
319 5:385a529b6670
320 11:82ca6f06eccd
321 12:9f259202bbe7
322 15:857b178a7cf3
323 16:609d82a7ebae
299 324
300 325 complex bisect test 2 # first good rev is 13
301 326
302 327 $ hg bisect -r
303 328 $ hg bisect -g 18
304 329 $ hg bisect -b 1 # -> update to rev 6
305 330 Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests)
306 331 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
307 332 $ hg bisect -s # -> update to rev 10
308 333 Testing changeset 10:429fcd26f52d (13 changesets remaining, ~3 tests)
309 334 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
310 335 $ hg bisect -b # -> update to rev 12
311 336 Testing changeset 12:9f259202bbe7 (5 changesets remaining, ~2 tests)
312 337 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
338 $ hg log -q -r 'bisect(untested)'
339 2:051e12f87bf1
340 3:0950834f0a9c
341 4:5c668c22234f
342 5:385a529b6670
343 8:dab8161ac8fc
344 9:3c77083deb4a
345 11:82ca6f06eccd
346 12:9f259202bbe7
347 13:b0a32c86eb31
348 15:857b178a7cf3
313 349 $ hg bisect -b # -> update to rev 13
314 350 Testing changeset 13:b0a32c86eb31 (3 changesets remaining, ~1 tests)
315 351 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
316 352 $ hg bisect -g
317 353 The first good revision is:
318 354 changeset: 13:b0a32c86eb31
319 355 user: test
320 356 date: Thu Jan 01 00:00:13 1970 +0000
321 357 summary: 13
322 358
323 359 $ hg log -q -r 'bisect(range)'
324 360 1:4ca5088da217
325 361 2:051e12f87bf1
326 362 3:0950834f0a9c
327 363 4:5c668c22234f
328 364 5:385a529b6670
329 365 6:a214d5d3811a
330 366 8:dab8161ac8fc
331 367 9:3c77083deb4a
332 368 10:429fcd26f52d
333 369 11:82ca6f06eccd
334 370 12:9f259202bbe7
335 371 13:b0a32c86eb31
336 372 15:857b178a7cf3
337 373 18:d42e18c7bc9b
338 374 $ hg log -q -r 'bisect(pruned)'
339 375 1:4ca5088da217
340 376 6:a214d5d3811a
341 377 10:429fcd26f52d
342 378 12:9f259202bbe7
343 379 13:b0a32c86eb31
344 380 18:d42e18c7bc9b
345 381
346 382 complex bisect test 3
347 383
348 384 first bad rev is 15
349 385 10,9,13 are skipped an might be the first bad revisions as well
350 386
351 387 $ hg bisect -r
352 388 $ hg bisect -g 1
353 389 $ hg bisect -b 16 # -> update to rev 6
354 390 Testing changeset 6:a214d5d3811a (13 changesets remaining, ~3 tests)
355 391 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
356 392 $ hg bisect -g # -> update to rev 13
357 393 Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests)
358 394 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
359 395 $ hg bisect -s # -> update to rev 10
360 396 Testing changeset 10:429fcd26f52d (8 changesets remaining, ~3 tests)
361 397 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
362 398 $ hg bisect -s # -> update to rev 12
363 399 Testing changeset 12:9f259202bbe7 (8 changesets remaining, ~3 tests)
364 400 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
365 401 $ hg bisect -g # -> update to rev 9
366 402 Testing changeset 9:3c77083deb4a (5 changesets remaining, ~2 tests)
367 403 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
368 404 $ hg bisect -s # -> update to rev 15
369 405 Testing changeset 15:857b178a7cf3 (5 changesets remaining, ~2 tests)
370 406 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
371 407 $ hg bisect -b
372 408 Due to skipped revisions, the first bad revision could be any of:
373 409 changeset: 9:3c77083deb4a
374 410 user: test
375 411 date: Thu Jan 01 00:00:09 1970 +0000
376 412 summary: 9
377 413
378 414 changeset: 10:429fcd26f52d
379 415 parent: 9:3c77083deb4a
380 416 parent: 6:a214d5d3811a
381 417 user: test
382 418 date: Thu Jan 01 00:00:10 1970 +0000
383 419 summary: merge 6,9
384 420
385 421 changeset: 13:b0a32c86eb31
386 422 user: test
387 423 date: Thu Jan 01 00:00:13 1970 +0000
388 424 summary: 13
389 425
390 426 changeset: 15:857b178a7cf3
391 427 parent: 13:b0a32c86eb31
392 428 parent: 10:429fcd26f52d
393 429 user: test
394 430 date: Thu Jan 01 00:00:15 1970 +0000
395 431 summary: merge 10,13
396 432
397 433 $ hg log -q -r 'bisect(range)'
398 434 1:4ca5088da217
399 435 2:051e12f87bf1
400 436 3:0950834f0a9c
401 437 4:5c668c22234f
402 438 5:385a529b6670
403 439 6:a214d5d3811a
404 440 8:dab8161ac8fc
405 441 9:3c77083deb4a
406 442 10:429fcd26f52d
407 443 11:82ca6f06eccd
408 444 12:9f259202bbe7
409 445 13:b0a32c86eb31
410 446 15:857b178a7cf3
411 447 16:609d82a7ebae
412 448 $ hg log -q -r 'bisect(pruned)'
413 449 1:4ca5088da217
414 450 6:a214d5d3811a
415 451 9:3c77083deb4a
416 452 10:429fcd26f52d
417 453 12:9f259202bbe7
418 454 13:b0a32c86eb31
419 455 15:857b178a7cf3
420 456 16:609d82a7ebae
421 457
422 458 complex bisect test 4
423 459
424 460 first good revision is 17
425 461 15,16 are skipped an might be the first good revisions as well
426 462
427 463 $ hg bisect -r
428 464 $ hg bisect -g 17
429 465 $ hg bisect -b 8 # -> update to rev 10
430 466 Testing changeset 13:b0a32c86eb31 (8 changesets remaining, ~3 tests)
431 467 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
432 468 $ hg bisect -b # -> update to rev 13
433 469 Testing changeset 10:429fcd26f52d (5 changesets remaining, ~2 tests)
434 470 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
435 471 $ hg bisect -b # -> update to rev 15
436 472 Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests)
437 473 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
438 474 $ hg bisect -s # -> update to rev 16
439 475 Testing changeset 16:609d82a7ebae (3 changesets remaining, ~1 tests)
440 476 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
441 477 $ hg bisect -s
442 478 Due to skipped revisions, the first good revision could be any of:
443 479 changeset: 15:857b178a7cf3
444 480 parent: 13:b0a32c86eb31
445 481 parent: 10:429fcd26f52d
446 482 user: test
447 483 date: Thu Jan 01 00:00:15 1970 +0000
448 484 summary: merge 10,13
449 485
450 486 changeset: 16:609d82a7ebae
451 487 user: test
452 488 date: Thu Jan 01 00:00:16 1970 +0000
453 489 summary: 16
454 490
455 491 changeset: 17:228c06deef46
456 492 user: test
457 493 date: Thu Jan 01 00:00:17 1970 +0000
458 494 summary: 17
459 495
460 496 $ hg log -q -r 'bisect(range)'
461 497 8:dab8161ac8fc
462 498 9:3c77083deb4a
463 499 10:429fcd26f52d
464 500 11:82ca6f06eccd
465 501 12:9f259202bbe7
466 502 13:b0a32c86eb31
467 503 15:857b178a7cf3
468 504 16:609d82a7ebae
469 505 17:228c06deef46
470 506 $ hg log -q -r 'bisect(pruned)'
471 507 8:dab8161ac8fc
472 508 10:429fcd26f52d
473 509 13:b0a32c86eb31
474 510 15:857b178a7cf3
475 511 16:609d82a7ebae
476 512 17:228c06deef46
477 513
478 514 test unrelated revs:
479 515
480 516 $ hg bisect --reset
481 517 $ hg bisect -b 7
482 518 $ hg bisect -g 14
483 519 abort: starting revisions are not directly related
484 520 [255]
485 521 $ hg log -q -r 'bisect(range)'
486 522 $ hg log -q -r 'bisect(pruned)'
487 523 $ hg bisect --reset
488 524
489 525 end at merge: 17 bad, 11 good (but 9 is first bad)
490 526
491 527 $ hg bisect -r
492 528 $ hg bisect -b 17
493 529 $ hg bisect -g 11
494 530 Testing changeset 13:b0a32c86eb31 (5 changesets remaining, ~2 tests)
495 531 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
496 532 $ hg bisect -g
497 533 Testing changeset 15:857b178a7cf3 (3 changesets remaining, ~1 tests)
498 534 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
499 535 $ hg bisect -b
500 536 The first bad revision is:
501 537 changeset: 15:857b178a7cf3
502 538 parent: 13:b0a32c86eb31
503 539 parent: 10:429fcd26f52d
504 540 user: test
505 541 date: Thu Jan 01 00:00:15 1970 +0000
506 542 summary: merge 10,13
507 543
508 544 Not all ancestors of this changeset have been checked.
509 545 Use bisect --extend to continue the bisection from
510 546 the common ancestor, dab8161ac8fc.
511 547 $ hg log -q -r 'bisect(range)'
512 548 11:82ca6f06eccd
513 549 12:9f259202bbe7
514 550 13:b0a32c86eb31
515 551 15:857b178a7cf3
516 552 16:609d82a7ebae
517 553 17:228c06deef46
518 554 $ hg log -q -r 'bisect(pruned)'
519 555 11:82ca6f06eccd
520 556 13:b0a32c86eb31
521 557 15:857b178a7cf3
522 558 17:228c06deef46
559 $ hg log -q -r 'bisect(untested)'
560 12:9f259202bbe7
561 16:609d82a7ebae
523 562 $ hg bisect --extend
524 563 Extending search to changeset 8:dab8161ac8fc
525 564 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
565 $ hg log -q -r 'bisect(untested)'
566 12:9f259202bbe7
567 16:609d82a7ebae
526 568 $ hg bisect -g # dab8161ac8fc
527 569 Testing changeset 9:3c77083deb4a (3 changesets remaining, ~1 tests)
528 570 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
529 571 $ hg bisect -b
530 572 The first bad revision is:
531 573 changeset: 9:3c77083deb4a
532 574 user: test
533 575 date: Thu Jan 01 00:00:09 1970 +0000
534 576 summary: 9
535 577
536 578 $ hg log -q -r 'bisect(range)'
537 579 8:dab8161ac8fc
538 580 9:3c77083deb4a
539 581 10:429fcd26f52d
540 582 11:82ca6f06eccd
541 583 12:9f259202bbe7
542 584 13:b0a32c86eb31
543 585 15:857b178a7cf3
544 586 16:609d82a7ebae
545 587 17:228c06deef46
546 588 $ hg log -q -r 'bisect(pruned)'
547 589 8:dab8161ac8fc
548 590 9:3c77083deb4a
549 591 11:82ca6f06eccd
550 592 13:b0a32c86eb31
551 593 15:857b178a7cf3
552 594 17:228c06deef46
595 $ hg log -q -r 'bisect(untested)'
596 10:429fcd26f52d
597 12:9f259202bbe7
598 16:609d82a7ebae
553 599
554 600 user adds irrelevant but consistent information (here: -g 2) to bisect state
555 601
556 602 $ hg bisect -r
557 603 $ hg bisect -b 13
558 604 $ hg bisect -g 8
559 605 Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests)
560 606 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
607 $ hg log -q -r 'bisect(untested)'
608 11:82ca6f06eccd
609 12:9f259202bbe7
561 610 $ hg bisect -g 2
562 611 Testing changeset 11:82ca6f06eccd (3 changesets remaining, ~1 tests)
563 612 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
613 $ hg log -q -r 'bisect(untested)'
614 11:82ca6f06eccd
615 12:9f259202bbe7
564 616 $ hg bisect -b
565 617 The first bad revision is:
566 618 changeset: 11:82ca6f06eccd
567 619 parent: 8:dab8161ac8fc
568 620 user: test
569 621 date: Thu Jan 01 00:00:11 1970 +0000
570 622 summary: 11
571 623
572 624 $ hg log -q -r 'bisect(range)'
573 625 8:dab8161ac8fc
574 626 11:82ca6f06eccd
575 627 12:9f259202bbe7
576 628 13:b0a32c86eb31
577 629 $ hg log -q -r 'bisect(pruned)'
578 630 8:dab8161ac8fc
579 631 11:82ca6f06eccd
580 632 13:b0a32c86eb31
633 $ hg log -q -r 'bisect(untested)'
634 12:9f259202bbe7
General Comments 0
You need to be logged in to leave comments. Login now