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