##// END OF EJS Templates
revset: stop supporting predicate that returns plain list (API)...
Yuya Nishihara -
r31809:35b8bb1e default
parent child Browse files
Show More
@@ -1,2285 +1,2276 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 from __future__ import absolute_import
9 9
10 10 import heapq
11 11 import re
12 12
13 13 from .i18n import _
14 14 from . import (
15 15 destutil,
16 16 encoding,
17 17 error,
18 18 hbisect,
19 19 match as matchmod,
20 20 node,
21 21 obsolete as obsmod,
22 22 pathutil,
23 23 phases,
24 24 registrar,
25 25 repoview,
26 26 revsetlang,
27 27 smartset,
28 28 util,
29 29 )
30 30
31 31 # helpers for processing parsed tree
32 32 getsymbol = revsetlang.getsymbol
33 33 getstring = revsetlang.getstring
34 34 getinteger = revsetlang.getinteger
35 35 getlist = revsetlang.getlist
36 36 getrange = revsetlang.getrange
37 37 getargs = revsetlang.getargs
38 38 getargsdict = revsetlang.getargsdict
39 39
40 40 # constants used as an argument of match() and matchany()
41 41 anyorder = revsetlang.anyorder
42 42 defineorder = revsetlang.defineorder
43 43 followorder = revsetlang.followorder
44 44
45 45 baseset = smartset.baseset
46 46 generatorset = smartset.generatorset
47 47 spanset = smartset.spanset
48 48 fullreposet = smartset.fullreposet
49 49
50 50 def _revancestors(repo, revs, followfirst):
51 51 """Like revlog.ancestors(), but supports followfirst."""
52 52 if followfirst:
53 53 cut = 1
54 54 else:
55 55 cut = None
56 56 cl = repo.changelog
57 57
58 58 def iterate():
59 59 revs.sort(reverse=True)
60 60 irevs = iter(revs)
61 61 h = []
62 62
63 63 inputrev = next(irevs, None)
64 64 if inputrev is not None:
65 65 heapq.heappush(h, -inputrev)
66 66
67 67 seen = set()
68 68 while h:
69 69 current = -heapq.heappop(h)
70 70 if current == inputrev:
71 71 inputrev = next(irevs, None)
72 72 if inputrev is not None:
73 73 heapq.heappush(h, -inputrev)
74 74 if current not in seen:
75 75 seen.add(current)
76 76 yield current
77 77 for parent in cl.parentrevs(current)[:cut]:
78 78 if parent != node.nullrev:
79 79 heapq.heappush(h, -parent)
80 80
81 81 return generatorset(iterate(), iterasc=False)
82 82
83 83 def _revdescendants(repo, revs, followfirst):
84 84 """Like revlog.descendants() but supports followfirst."""
85 85 if followfirst:
86 86 cut = 1
87 87 else:
88 88 cut = None
89 89
90 90 def iterate():
91 91 cl = repo.changelog
92 92 # XXX this should be 'parentset.min()' assuming 'parentset' is a
93 93 # smartset (and if it is not, it should.)
94 94 first = min(revs)
95 95 nullrev = node.nullrev
96 96 if first == nullrev:
97 97 # Are there nodes with a null first parent and a non-null
98 98 # second one? Maybe. Do we care? Probably not.
99 99 for i in cl:
100 100 yield i
101 101 else:
102 102 seen = set(revs)
103 103 for i in cl.revs(first + 1):
104 104 for x in cl.parentrevs(i)[:cut]:
105 105 if x != nullrev and x in seen:
106 106 seen.add(i)
107 107 yield i
108 108 break
109 109
110 110 return generatorset(iterate(), iterasc=True)
111 111
112 112 def _reachablerootspure(repo, minroot, roots, heads, includepath):
113 113 """return (heads(::<roots> and ::<heads>))
114 114
115 115 If includepath is True, return (<roots>::<heads>)."""
116 116 if not roots:
117 117 return []
118 118 parentrevs = repo.changelog.parentrevs
119 119 roots = set(roots)
120 120 visit = list(heads)
121 121 reachable = set()
122 122 seen = {}
123 123 # prefetch all the things! (because python is slow)
124 124 reached = reachable.add
125 125 dovisit = visit.append
126 126 nextvisit = visit.pop
127 127 # open-code the post-order traversal due to the tiny size of
128 128 # sys.getrecursionlimit()
129 129 while visit:
130 130 rev = nextvisit()
131 131 if rev in roots:
132 132 reached(rev)
133 133 if not includepath:
134 134 continue
135 135 parents = parentrevs(rev)
136 136 seen[rev] = parents
137 137 for parent in parents:
138 138 if parent >= minroot and parent not in seen:
139 139 dovisit(parent)
140 140 if not reachable:
141 141 return baseset()
142 142 if not includepath:
143 143 return reachable
144 144 for rev in sorted(seen):
145 145 for parent in seen[rev]:
146 146 if parent in reachable:
147 147 reached(rev)
148 148 return reachable
149 149
150 150 def reachableroots(repo, roots, heads, includepath=False):
151 151 """return (heads(::<roots> and ::<heads>))
152 152
153 153 If includepath is True, return (<roots>::<heads>)."""
154 154 if not roots:
155 155 return baseset()
156 156 minroot = roots.min()
157 157 roots = list(roots)
158 158 heads = list(heads)
159 159 try:
160 160 revs = repo.changelog.reachableroots(minroot, heads, roots, includepath)
161 161 except AttributeError:
162 162 revs = _reachablerootspure(repo, minroot, roots, heads, includepath)
163 163 revs = baseset(revs)
164 164 revs.sort()
165 165 return revs
166 166
167 167 # helpers
168 168
169 169 def getset(repo, subset, x):
170 170 if not x:
171 171 raise error.ParseError(_("missing argument"))
172 s = methods[x[0]](repo, subset, *x[1:])
173 if util.safehasattr(s, 'isascending'):
174 return s
175 # else case should not happen, because all non-func are internal,
176 # ignoring for now.
177 if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
178 repo.ui.deprecwarn('revset "%s" uses list instead of smartset'
179 % x[1][1],
180 '3.9')
181 return baseset(s)
172 return methods[x[0]](repo, subset, *x[1:])
182 173
183 174 def _getrevsource(repo, r):
184 175 extra = repo[r].extra()
185 176 for label in ('source', 'transplant_source', 'rebase_source'):
186 177 if label in extra:
187 178 try:
188 179 return repo[extra[label]].rev()
189 180 except error.RepoLookupError:
190 181 pass
191 182 return None
192 183
193 184 # operator methods
194 185
195 186 def stringset(repo, subset, x):
196 187 x = repo[x].rev()
197 188 if (x in subset
198 189 or x == node.nullrev and isinstance(subset, fullreposet)):
199 190 return baseset([x])
200 191 return baseset()
201 192
202 193 def rangeset(repo, subset, x, y, order):
203 194 m = getset(repo, fullreposet(repo), x)
204 195 n = getset(repo, fullreposet(repo), y)
205 196
206 197 if not m or not n:
207 198 return baseset()
208 199 return _makerangeset(repo, subset, m.first(), n.last(), order)
209 200
210 201 def rangeall(repo, subset, x, order):
211 202 assert x is None
212 203 return _makerangeset(repo, subset, 0, len(repo) - 1, order)
213 204
214 205 def rangepre(repo, subset, y, order):
215 206 # ':y' can't be rewritten to '0:y' since '0' may be hidden
216 207 n = getset(repo, fullreposet(repo), y)
217 208 if not n:
218 209 return baseset()
219 210 return _makerangeset(repo, subset, 0, n.last(), order)
220 211
221 212 def rangepost(repo, subset, x, order):
222 213 m = getset(repo, fullreposet(repo), x)
223 214 if not m:
224 215 return baseset()
225 216 return _makerangeset(repo, subset, m.first(), len(repo) - 1, order)
226 217
227 218 def _makerangeset(repo, subset, m, n, order):
228 219 if m == n:
229 220 r = baseset([m])
230 221 elif n == node.wdirrev:
231 222 r = spanset(repo, m, len(repo)) + baseset([n])
232 223 elif m == node.wdirrev:
233 224 r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1)
234 225 elif m < n:
235 226 r = spanset(repo, m, n + 1)
236 227 else:
237 228 r = spanset(repo, m, n - 1)
238 229
239 230 if order == defineorder:
240 231 return r & subset
241 232 else:
242 233 # carrying the sorting over when possible would be more efficient
243 234 return subset & r
244 235
245 236 def dagrange(repo, subset, x, y, order):
246 237 r = fullreposet(repo)
247 238 xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
248 239 includepath=True)
249 240 return subset & xs
250 241
251 242 def andset(repo, subset, x, y, order):
252 243 return getset(repo, getset(repo, subset, x), y)
253 244
254 245 def differenceset(repo, subset, x, y, order):
255 246 return getset(repo, subset, x) - getset(repo, subset, y)
256 247
257 248 def _orsetlist(repo, subset, xs):
258 249 assert xs
259 250 if len(xs) == 1:
260 251 return getset(repo, subset, xs[0])
261 252 p = len(xs) // 2
262 253 a = _orsetlist(repo, subset, xs[:p])
263 254 b = _orsetlist(repo, subset, xs[p:])
264 255 return a + b
265 256
266 257 def orset(repo, subset, x, order):
267 258 xs = getlist(x)
268 259 if order == followorder:
269 260 # slow path to take the subset order
270 261 return subset & _orsetlist(repo, fullreposet(repo), xs)
271 262 else:
272 263 return _orsetlist(repo, subset, xs)
273 264
274 265 def notset(repo, subset, x, order):
275 266 return subset - getset(repo, subset, x)
276 267
277 268 def listset(repo, subset, *xs):
278 269 raise error.ParseError(_("can't use a list in this context"),
279 270 hint=_('see hg help "revsets.x or y"'))
280 271
281 272 def keyvaluepair(repo, subset, k, v):
282 273 raise error.ParseError(_("can't use a key-value pair in this context"))
283 274
284 275 def func(repo, subset, a, b, order):
285 276 f = getsymbol(a)
286 277 if f in symbols:
287 278 func = symbols[f]
288 279 if getattr(func, '_takeorder', False):
289 280 return func(repo, subset, b, order)
290 281 return func(repo, subset, b)
291 282
292 283 keep = lambda fn: getattr(fn, '__doc__', None) is not None
293 284
294 285 syms = [s for (s, fn) in symbols.items() if keep(fn)]
295 286 raise error.UnknownIdentifier(f, syms)
296 287
297 288 # functions
298 289
299 290 # symbols are callables like:
300 291 # fn(repo, subset, x)
301 292 # with:
302 293 # repo - current repository instance
303 294 # subset - of revisions to be examined
304 295 # x - argument in tree form
305 296 symbols = {}
306 297
307 298 # symbols which can't be used for a DoS attack for any given input
308 299 # (e.g. those which accept regexes as plain strings shouldn't be included)
309 300 # functions that just return a lot of changesets (like all) don't count here
310 301 safesymbols = set()
311 302
312 303 predicate = registrar.revsetpredicate()
313 304
314 305 @predicate('_destupdate')
315 306 def _destupdate(repo, subset, x):
316 307 # experimental revset for update destination
317 308 args = getargsdict(x, 'limit', 'clean')
318 309 return subset & baseset([destutil.destupdate(repo, **args)[0]])
319 310
320 311 @predicate('_destmerge')
321 312 def _destmerge(repo, subset, x):
322 313 # experimental revset for merge destination
323 314 sourceset = None
324 315 if x is not None:
325 316 sourceset = getset(repo, fullreposet(repo), x)
326 317 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
327 318
328 319 @predicate('adds(pattern)', safe=True)
329 320 def adds(repo, subset, x):
330 321 """Changesets that add a file matching pattern.
331 322
332 323 The pattern without explicit kind like ``glob:`` is expected to be
333 324 relative to the current directory and match against a file or a
334 325 directory.
335 326 """
336 327 # i18n: "adds" is a keyword
337 328 pat = getstring(x, _("adds requires a pattern"))
338 329 return checkstatus(repo, subset, pat, 1)
339 330
340 331 @predicate('ancestor(*changeset)', safe=True)
341 332 def ancestor(repo, subset, x):
342 333 """A greatest common ancestor of the changesets.
343 334
344 335 Accepts 0 or more changesets.
345 336 Will return empty list when passed no args.
346 337 Greatest common ancestor of a single changeset is that changeset.
347 338 """
348 339 # i18n: "ancestor" is a keyword
349 340 l = getlist(x)
350 341 rl = fullreposet(repo)
351 342 anc = None
352 343
353 344 # (getset(repo, rl, i) for i in l) generates a list of lists
354 345 for revs in (getset(repo, rl, i) for i in l):
355 346 for r in revs:
356 347 if anc is None:
357 348 anc = repo[r]
358 349 else:
359 350 anc = anc.ancestor(repo[r])
360 351
361 352 if anc is not None and anc.rev() in subset:
362 353 return baseset([anc.rev()])
363 354 return baseset()
364 355
365 356 def _ancestors(repo, subset, x, followfirst=False):
366 357 heads = getset(repo, fullreposet(repo), x)
367 358 if not heads:
368 359 return baseset()
369 360 s = _revancestors(repo, heads, followfirst)
370 361 return subset & s
371 362
372 363 @predicate('ancestors(set)', safe=True)
373 364 def ancestors(repo, subset, x):
374 365 """Changesets that are ancestors of a changeset in set.
375 366 """
376 367 return _ancestors(repo, subset, x)
377 368
378 369 @predicate('_firstancestors', safe=True)
379 370 def _firstancestors(repo, subset, x):
380 371 # ``_firstancestors(set)``
381 372 # Like ``ancestors(set)`` but follows only the first parents.
382 373 return _ancestors(repo, subset, x, followfirst=True)
383 374
384 375 def ancestorspec(repo, subset, x, n, order):
385 376 """``set~n``
386 377 Changesets that are the Nth ancestor (first parents only) of a changeset
387 378 in set.
388 379 """
389 380 n = getinteger(n, _("~ expects a number"))
390 381 ps = set()
391 382 cl = repo.changelog
392 383 for r in getset(repo, fullreposet(repo), x):
393 384 for i in range(n):
394 385 r = cl.parentrevs(r)[0]
395 386 ps.add(r)
396 387 return subset & ps
397 388
398 389 @predicate('author(string)', safe=True)
399 390 def author(repo, subset, x):
400 391 """Alias for ``user(string)``.
401 392 """
402 393 # i18n: "author" is a keyword
403 394 n = getstring(x, _("author requires a string"))
404 395 kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
405 396 return subset.filter(lambda x: matcher(repo[x].user()),
406 397 condrepr=('<user %r>', n))
407 398
408 399 @predicate('bisect(string)', safe=True)
409 400 def bisect(repo, subset, x):
410 401 """Changesets marked in the specified bisect status:
411 402
412 403 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
413 404 - ``goods``, ``bads`` : csets topologically good/bad
414 405 - ``range`` : csets taking part in the bisection
415 406 - ``pruned`` : csets that are goods, bads or skipped
416 407 - ``untested`` : csets whose fate is yet unknown
417 408 - ``ignored`` : csets ignored due to DAG topology
418 409 - ``current`` : the cset currently being bisected
419 410 """
420 411 # i18n: "bisect" is a keyword
421 412 status = getstring(x, _("bisect requires a string")).lower()
422 413 state = set(hbisect.get(repo, status))
423 414 return subset & state
424 415
425 416 # Backward-compatibility
426 417 # - no help entry so that we do not advertise it any more
427 418 @predicate('bisected', safe=True)
428 419 def bisected(repo, subset, x):
429 420 return bisect(repo, subset, x)
430 421
431 422 @predicate('bookmark([name])', safe=True)
432 423 def bookmark(repo, subset, x):
433 424 """The named bookmark or all bookmarks.
434 425
435 426 Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
436 427 """
437 428 # i18n: "bookmark" is a keyword
438 429 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
439 430 if args:
440 431 bm = getstring(args[0],
441 432 # i18n: "bookmark" is a keyword
442 433 _('the argument to bookmark must be a string'))
443 434 kind, pattern, matcher = util.stringmatcher(bm)
444 435 bms = set()
445 436 if kind == 'literal':
446 437 bmrev = repo._bookmarks.get(pattern, None)
447 438 if not bmrev:
448 439 raise error.RepoLookupError(_("bookmark '%s' does not exist")
449 440 % pattern)
450 441 bms.add(repo[bmrev].rev())
451 442 else:
452 443 matchrevs = set()
453 444 for name, bmrev in repo._bookmarks.iteritems():
454 445 if matcher(name):
455 446 matchrevs.add(bmrev)
456 447 if not matchrevs:
457 448 raise error.RepoLookupError(_("no bookmarks exist"
458 449 " that match '%s'") % pattern)
459 450 for bmrev in matchrevs:
460 451 bms.add(repo[bmrev].rev())
461 452 else:
462 453 bms = set([repo[r].rev()
463 454 for r in repo._bookmarks.values()])
464 455 bms -= set([node.nullrev])
465 456 return subset & bms
466 457
467 458 @predicate('branch(string or set)', safe=True)
468 459 def branch(repo, subset, x):
469 460 """
470 461 All changesets belonging to the given branch or the branches of the given
471 462 changesets.
472 463
473 464 Pattern matching is supported for `string`. See
474 465 :hg:`help revisions.patterns`.
475 466 """
476 467 getbi = repo.revbranchcache().branchinfo
477 468
478 469 try:
479 470 b = getstring(x, '')
480 471 except error.ParseError:
481 472 # not a string, but another revspec, e.g. tip()
482 473 pass
483 474 else:
484 475 kind, pattern, matcher = util.stringmatcher(b)
485 476 if kind == 'literal':
486 477 # note: falls through to the revspec case if no branch with
487 478 # this name exists and pattern kind is not specified explicitly
488 479 if pattern in repo.branchmap():
489 480 return subset.filter(lambda r: matcher(getbi(r)[0]),
490 481 condrepr=('<branch %r>', b))
491 482 if b.startswith('literal:'):
492 483 raise error.RepoLookupError(_("branch '%s' does not exist")
493 484 % pattern)
494 485 else:
495 486 return subset.filter(lambda r: matcher(getbi(r)[0]),
496 487 condrepr=('<branch %r>', b))
497 488
498 489 s = getset(repo, fullreposet(repo), x)
499 490 b = set()
500 491 for r in s:
501 492 b.add(getbi(r)[0])
502 493 c = s.__contains__
503 494 return subset.filter(lambda r: c(r) or getbi(r)[0] in b,
504 495 condrepr=lambda: '<branch %r>' % sorted(b))
505 496
506 497 @predicate('bumped()', safe=True)
507 498 def bumped(repo, subset, x):
508 499 """Mutable changesets marked as successors of public changesets.
509 500
510 501 Only non-public and non-obsolete changesets can be `bumped`.
511 502 """
512 503 # i18n: "bumped" is a keyword
513 504 getargs(x, 0, 0, _("bumped takes no arguments"))
514 505 bumped = obsmod.getrevs(repo, 'bumped')
515 506 return subset & bumped
516 507
517 508 @predicate('bundle()', safe=True)
518 509 def bundle(repo, subset, x):
519 510 """Changesets in the bundle.
520 511
521 512 Bundle must be specified by the -R option."""
522 513
523 514 try:
524 515 bundlerevs = repo.changelog.bundlerevs
525 516 except AttributeError:
526 517 raise error.Abort(_("no bundle provided - specify with -R"))
527 518 return subset & bundlerevs
528 519
529 520 def checkstatus(repo, subset, pat, field):
530 521 hasset = matchmod.patkind(pat) == 'set'
531 522
532 523 mcache = [None]
533 524 def matches(x):
534 525 c = repo[x]
535 526 if not mcache[0] or hasset:
536 527 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
537 528 m = mcache[0]
538 529 fname = None
539 530 if not m.anypats() and len(m.files()) == 1:
540 531 fname = m.files()[0]
541 532 if fname is not None:
542 533 if fname not in c.files():
543 534 return False
544 535 else:
545 536 for f in c.files():
546 537 if m(f):
547 538 break
548 539 else:
549 540 return False
550 541 files = repo.status(c.p1().node(), c.node())[field]
551 542 if fname is not None:
552 543 if fname in files:
553 544 return True
554 545 else:
555 546 for f in files:
556 547 if m(f):
557 548 return True
558 549
559 550 return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
560 551
561 552 def _children(repo, subset, parentset):
562 553 if not parentset:
563 554 return baseset()
564 555 cs = set()
565 556 pr = repo.changelog.parentrevs
566 557 minrev = parentset.min()
567 558 nullrev = node.nullrev
568 559 for r in subset:
569 560 if r <= minrev:
570 561 continue
571 562 p1, p2 = pr(r)
572 563 if p1 in parentset:
573 564 cs.add(r)
574 565 if p2 != nullrev and p2 in parentset:
575 566 cs.add(r)
576 567 return baseset(cs)
577 568
578 569 @predicate('children(set)', safe=True)
579 570 def children(repo, subset, x):
580 571 """Child changesets of changesets in set.
581 572 """
582 573 s = getset(repo, fullreposet(repo), x)
583 574 cs = _children(repo, subset, s)
584 575 return subset & cs
585 576
586 577 @predicate('closed()', safe=True)
587 578 def closed(repo, subset, x):
588 579 """Changeset is closed.
589 580 """
590 581 # i18n: "closed" is a keyword
591 582 getargs(x, 0, 0, _("closed takes no arguments"))
592 583 return subset.filter(lambda r: repo[r].closesbranch(),
593 584 condrepr='<branch closed>')
594 585
595 586 @predicate('contains(pattern)')
596 587 def contains(repo, subset, x):
597 588 """The revision's manifest contains a file matching pattern (but might not
598 589 modify it). See :hg:`help patterns` for information about file patterns.
599 590
600 591 The pattern without explicit kind like ``glob:`` is expected to be
601 592 relative to the current directory and match against a file exactly
602 593 for efficiency.
603 594 """
604 595 # i18n: "contains" is a keyword
605 596 pat = getstring(x, _("contains requires a pattern"))
606 597
607 598 def matches(x):
608 599 if not matchmod.patkind(pat):
609 600 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
610 601 if pats in repo[x]:
611 602 return True
612 603 else:
613 604 c = repo[x]
614 605 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
615 606 for f in c.manifest():
616 607 if m(f):
617 608 return True
618 609 return False
619 610
620 611 return subset.filter(matches, condrepr=('<contains %r>', pat))
621 612
622 613 @predicate('converted([id])', safe=True)
623 614 def converted(repo, subset, x):
624 615 """Changesets converted from the given identifier in the old repository if
625 616 present, or all converted changesets if no identifier is specified.
626 617 """
627 618
628 619 # There is exactly no chance of resolving the revision, so do a simple
629 620 # string compare and hope for the best
630 621
631 622 rev = None
632 623 # i18n: "converted" is a keyword
633 624 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
634 625 if l:
635 626 # i18n: "converted" is a keyword
636 627 rev = getstring(l[0], _('converted requires a revision'))
637 628
638 629 def _matchvalue(r):
639 630 source = repo[r].extra().get('convert_revision', None)
640 631 return source is not None and (rev is None or source.startswith(rev))
641 632
642 633 return subset.filter(lambda r: _matchvalue(r),
643 634 condrepr=('<converted %r>', rev))
644 635
645 636 @predicate('date(interval)', safe=True)
646 637 def date(repo, subset, x):
647 638 """Changesets within the interval, see :hg:`help dates`.
648 639 """
649 640 # i18n: "date" is a keyword
650 641 ds = getstring(x, _("date requires a string"))
651 642 dm = util.matchdate(ds)
652 643 return subset.filter(lambda x: dm(repo[x].date()[0]),
653 644 condrepr=('<date %r>', ds))
654 645
655 646 @predicate('desc(string)', safe=True)
656 647 def desc(repo, subset, x):
657 648 """Search commit message for string. The match is case-insensitive.
658 649
659 650 Pattern matching is supported for `string`. See
660 651 :hg:`help revisions.patterns`.
661 652 """
662 653 # i18n: "desc" is a keyword
663 654 ds = getstring(x, _("desc requires a string"))
664 655
665 656 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
666 657
667 658 return subset.filter(lambda r: matcher(repo[r].description()),
668 659 condrepr=('<desc %r>', ds))
669 660
670 661 def _descendants(repo, subset, x, followfirst=False):
671 662 roots = getset(repo, fullreposet(repo), x)
672 663 if not roots:
673 664 return baseset()
674 665 s = _revdescendants(repo, roots, followfirst)
675 666
676 667 # Both sets need to be ascending in order to lazily return the union
677 668 # in the correct order.
678 669 base = subset & roots
679 670 desc = subset & s
680 671 result = base + desc
681 672 if subset.isascending():
682 673 result.sort()
683 674 elif subset.isdescending():
684 675 result.sort(reverse=True)
685 676 else:
686 677 result = subset & result
687 678 return result
688 679
689 680 @predicate('descendants(set)', safe=True)
690 681 def descendants(repo, subset, x):
691 682 """Changesets which are descendants of changesets in set.
692 683 """
693 684 return _descendants(repo, subset, x)
694 685
695 686 @predicate('_firstdescendants', safe=True)
696 687 def _firstdescendants(repo, subset, x):
697 688 # ``_firstdescendants(set)``
698 689 # Like ``descendants(set)`` but follows only the first parents.
699 690 return _descendants(repo, subset, x, followfirst=True)
700 691
701 692 @predicate('destination([set])', safe=True)
702 693 def destination(repo, subset, x):
703 694 """Changesets that were created by a graft, transplant or rebase operation,
704 695 with the given revisions specified as the source. Omitting the optional set
705 696 is the same as passing all().
706 697 """
707 698 if x is not None:
708 699 sources = getset(repo, fullreposet(repo), x)
709 700 else:
710 701 sources = fullreposet(repo)
711 702
712 703 dests = set()
713 704
714 705 # subset contains all of the possible destinations that can be returned, so
715 706 # iterate over them and see if their source(s) were provided in the arg set.
716 707 # Even if the immediate src of r is not in the arg set, src's source (or
717 708 # further back) may be. Scanning back further than the immediate src allows
718 709 # transitive transplants and rebases to yield the same results as transitive
719 710 # grafts.
720 711 for r in subset:
721 712 src = _getrevsource(repo, r)
722 713 lineage = None
723 714
724 715 while src is not None:
725 716 if lineage is None:
726 717 lineage = list()
727 718
728 719 lineage.append(r)
729 720
730 721 # The visited lineage is a match if the current source is in the arg
731 722 # set. Since every candidate dest is visited by way of iterating
732 723 # subset, any dests further back in the lineage will be tested by a
733 724 # different iteration over subset. Likewise, if the src was already
734 725 # selected, the current lineage can be selected without going back
735 726 # further.
736 727 if src in sources or src in dests:
737 728 dests.update(lineage)
738 729 break
739 730
740 731 r = src
741 732 src = _getrevsource(repo, r)
742 733
743 734 return subset.filter(dests.__contains__,
744 735 condrepr=lambda: '<destination %r>' % sorted(dests))
745 736
746 737 @predicate('divergent()', safe=True)
747 738 def divergent(repo, subset, x):
748 739 """
749 740 Final successors of changesets with an alternative set of final successors.
750 741 """
751 742 # i18n: "divergent" is a keyword
752 743 getargs(x, 0, 0, _("divergent takes no arguments"))
753 744 divergent = obsmod.getrevs(repo, 'divergent')
754 745 return subset & divergent
755 746
756 747 @predicate('extinct()', safe=True)
757 748 def extinct(repo, subset, x):
758 749 """Obsolete changesets with obsolete descendants only.
759 750 """
760 751 # i18n: "extinct" is a keyword
761 752 getargs(x, 0, 0, _("extinct takes no arguments"))
762 753 extincts = obsmod.getrevs(repo, 'extinct')
763 754 return subset & extincts
764 755
765 756 @predicate('extra(label, [value])', safe=True)
766 757 def extra(repo, subset, x):
767 758 """Changesets with the given label in the extra metadata, with the given
768 759 optional value.
769 760
770 761 Pattern matching is supported for `value`. See
771 762 :hg:`help revisions.patterns`.
772 763 """
773 764 args = getargsdict(x, 'extra', 'label value')
774 765 if 'label' not in args:
775 766 # i18n: "extra" is a keyword
776 767 raise error.ParseError(_('extra takes at least 1 argument'))
777 768 # i18n: "extra" is a keyword
778 769 label = getstring(args['label'], _('first argument to extra must be '
779 770 'a string'))
780 771 value = None
781 772
782 773 if 'value' in args:
783 774 # i18n: "extra" is a keyword
784 775 value = getstring(args['value'], _('second argument to extra must be '
785 776 'a string'))
786 777 kind, value, matcher = util.stringmatcher(value)
787 778
788 779 def _matchvalue(r):
789 780 extra = repo[r].extra()
790 781 return label in extra and (value is None or matcher(extra[label]))
791 782
792 783 return subset.filter(lambda r: _matchvalue(r),
793 784 condrepr=('<extra[%r] %r>', label, value))
794 785
795 786 @predicate('filelog(pattern)', safe=True)
796 787 def filelog(repo, subset, x):
797 788 """Changesets connected to the specified filelog.
798 789
799 790 For performance reasons, visits only revisions mentioned in the file-level
800 791 filelog, rather than filtering through all changesets (much faster, but
801 792 doesn't include deletes or duplicate changes). For a slower, more accurate
802 793 result, use ``file()``.
803 794
804 795 The pattern without explicit kind like ``glob:`` is expected to be
805 796 relative to the current directory and match against a file exactly
806 797 for efficiency.
807 798
808 799 If some linkrev points to revisions filtered by the current repoview, we'll
809 800 work around it to return a non-filtered value.
810 801 """
811 802
812 803 # i18n: "filelog" is a keyword
813 804 pat = getstring(x, _("filelog requires a pattern"))
814 805 s = set()
815 806 cl = repo.changelog
816 807
817 808 if not matchmod.patkind(pat):
818 809 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
819 810 files = [f]
820 811 else:
821 812 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
822 813 files = (f for f in repo[None] if m(f))
823 814
824 815 for f in files:
825 816 fl = repo.file(f)
826 817 known = {}
827 818 scanpos = 0
828 819 for fr in list(fl):
829 820 fn = fl.node(fr)
830 821 if fn in known:
831 822 s.add(known[fn])
832 823 continue
833 824
834 825 lr = fl.linkrev(fr)
835 826 if lr in cl:
836 827 s.add(lr)
837 828 elif scanpos is not None:
838 829 # lowest matching changeset is filtered, scan further
839 830 # ahead in changelog
840 831 start = max(lr, scanpos) + 1
841 832 scanpos = None
842 833 for r in cl.revs(start):
843 834 # minimize parsing of non-matching entries
844 835 if f in cl.revision(r) and f in cl.readfiles(r):
845 836 try:
846 837 # try to use manifest delta fastpath
847 838 n = repo[r].filenode(f)
848 839 if n not in known:
849 840 if n == fn:
850 841 s.add(r)
851 842 scanpos = r
852 843 break
853 844 else:
854 845 known[n] = r
855 846 except error.ManifestLookupError:
856 847 # deletion in changelog
857 848 continue
858 849
859 850 return subset & s
860 851
861 852 @predicate('first(set, [n])', safe=True)
862 853 def first(repo, subset, x):
863 854 """An alias for limit().
864 855 """
865 856 return limit(repo, subset, x)
866 857
867 858 def _follow(repo, subset, x, name, followfirst=False):
868 859 l = getargs(x, 0, 2, _("%s takes no arguments or a pattern "
869 860 "and an optional revset") % name)
870 861 c = repo['.']
871 862 if l:
872 863 x = getstring(l[0], _("%s expected a pattern") % name)
873 864 rev = None
874 865 if len(l) >= 2:
875 866 revs = getset(repo, fullreposet(repo), l[1])
876 867 if len(revs) != 1:
877 868 raise error.RepoLookupError(
878 869 _("%s expected one starting revision") % name)
879 870 rev = revs.last()
880 871 c = repo[rev]
881 872 matcher = matchmod.match(repo.root, repo.getcwd(), [x],
882 873 ctx=repo[rev], default='path')
883 874
884 875 files = c.manifest().walk(matcher)
885 876
886 877 s = set()
887 878 for fname in files:
888 879 fctx = c[fname]
889 880 s = s.union(set(c.rev() for c in fctx.ancestors(followfirst)))
890 881 # include the revision responsible for the most recent version
891 882 s.add(fctx.introrev())
892 883 else:
893 884 s = _revancestors(repo, baseset([c.rev()]), followfirst)
894 885
895 886 return subset & s
896 887
897 888 @predicate('follow([pattern[, startrev]])', safe=True)
898 889 def follow(repo, subset, x):
899 890 """
900 891 An alias for ``::.`` (ancestors of the working directory's first parent).
901 892 If pattern is specified, the histories of files matching given
902 893 pattern in the revision given by startrev are followed, including copies.
903 894 """
904 895 return _follow(repo, subset, x, 'follow')
905 896
906 897 @predicate('_followfirst', safe=True)
907 898 def _followfirst(repo, subset, x):
908 899 # ``followfirst([pattern[, startrev]])``
909 900 # Like ``follow([pattern[, startrev]])`` but follows only the first parent
910 901 # of every revisions or files revisions.
911 902 return _follow(repo, subset, x, '_followfirst', followfirst=True)
912 903
913 904 @predicate('followlines(file, fromline:toline[, startrev=.])', safe=True)
914 905 def followlines(repo, subset, x):
915 906 """Changesets modifying `file` in line range ('fromline', 'toline').
916 907
917 908 Line range corresponds to 'file' content at 'startrev' and should hence be
918 909 consistent with file size. If startrev is not specified, working directory's
919 910 parent is used.
920 911 """
921 912 from . import context # avoid circular import issues
922 913
923 914 args = getargsdict(x, 'followlines', 'file *lines startrev')
924 915 if len(args['lines']) != 1:
925 916 raise error.ParseError(_("followlines requires a line range"))
926 917
927 918 rev = '.'
928 919 if 'startrev' in args:
929 920 revs = getset(repo, fullreposet(repo), args['startrev'])
930 921 if len(revs) != 1:
931 922 raise error.ParseError(
932 923 _("followlines expects exactly one revision"))
933 924 rev = revs.last()
934 925
935 926 pat = getstring(args['file'], _("followlines requires a pattern"))
936 927 if not matchmod.patkind(pat):
937 928 fname = pathutil.canonpath(repo.root, repo.getcwd(), pat)
938 929 else:
939 930 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[rev])
940 931 files = [f for f in repo[rev] if m(f)]
941 932 if len(files) != 1:
942 933 raise error.ParseError(_("followlines expects exactly one file"))
943 934 fname = files[0]
944 935
945 936 lr = getrange(args['lines'][0], _("followlines expects a line range"))
946 937 fromline, toline = [getinteger(a, _("line range bounds must be integers"))
947 938 for a in lr]
948 939 fromline, toline = util.processlinerange(fromline, toline)
949 940
950 941 fctx = repo[rev].filectx(fname)
951 942 revs = (c.rev() for c, _linerange
952 943 in context.blockancestors(fctx, fromline, toline))
953 944 return subset & generatorset(revs, iterasc=False)
954 945
955 946 @predicate('all()', safe=True)
956 947 def getall(repo, subset, x):
957 948 """All changesets, the same as ``0:tip``.
958 949 """
959 950 # i18n: "all" is a keyword
960 951 getargs(x, 0, 0, _("all takes no arguments"))
961 952 return subset & spanset(repo) # drop "null" if any
962 953
963 954 @predicate('grep(regex)')
964 955 def grep(repo, subset, x):
965 956 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
966 957 to ensure special escape characters are handled correctly. Unlike
967 958 ``keyword(string)``, the match is case-sensitive.
968 959 """
969 960 try:
970 961 # i18n: "grep" is a keyword
971 962 gr = re.compile(getstring(x, _("grep requires a string")))
972 963 except re.error as e:
973 964 raise error.ParseError(_('invalid match pattern: %s') % e)
974 965
975 966 def matches(x):
976 967 c = repo[x]
977 968 for e in c.files() + [c.user(), c.description()]:
978 969 if gr.search(e):
979 970 return True
980 971 return False
981 972
982 973 return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))
983 974
984 975 @predicate('_matchfiles', safe=True)
985 976 def _matchfiles(repo, subset, x):
986 977 # _matchfiles takes a revset list of prefixed arguments:
987 978 #
988 979 # [p:foo, i:bar, x:baz]
989 980 #
990 981 # builds a match object from them and filters subset. Allowed
991 982 # prefixes are 'p:' for regular patterns, 'i:' for include
992 983 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
993 984 # a revision identifier, or the empty string to reference the
994 985 # working directory, from which the match object is
995 986 # initialized. Use 'd:' to set the default matching mode, default
996 987 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
997 988
998 989 l = getargs(x, 1, -1, "_matchfiles requires at least one argument")
999 990 pats, inc, exc = [], [], []
1000 991 rev, default = None, None
1001 992 for arg in l:
1002 993 s = getstring(arg, "_matchfiles requires string arguments")
1003 994 prefix, value = s[:2], s[2:]
1004 995 if prefix == 'p:':
1005 996 pats.append(value)
1006 997 elif prefix == 'i:':
1007 998 inc.append(value)
1008 999 elif prefix == 'x:':
1009 1000 exc.append(value)
1010 1001 elif prefix == 'r:':
1011 1002 if rev is not None:
1012 1003 raise error.ParseError('_matchfiles expected at most one '
1013 1004 'revision')
1014 1005 if value != '': # empty means working directory; leave rev as None
1015 1006 rev = value
1016 1007 elif prefix == 'd:':
1017 1008 if default is not None:
1018 1009 raise error.ParseError('_matchfiles expected at most one '
1019 1010 'default mode')
1020 1011 default = value
1021 1012 else:
1022 1013 raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)
1023 1014 if not default:
1024 1015 default = 'glob'
1025 1016
1026 1017 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1027 1018 exclude=exc, ctx=repo[rev], default=default)
1028 1019
1029 1020 # This directly read the changelog data as creating changectx for all
1030 1021 # revisions is quite expensive.
1031 1022 getfiles = repo.changelog.readfiles
1032 1023 wdirrev = node.wdirrev
1033 1024 def matches(x):
1034 1025 if x == wdirrev:
1035 1026 files = repo[x].files()
1036 1027 else:
1037 1028 files = getfiles(x)
1038 1029 for f in files:
1039 1030 if m(f):
1040 1031 return True
1041 1032 return False
1042 1033
1043 1034 return subset.filter(matches,
1044 1035 condrepr=('<matchfiles patterns=%r, include=%r '
1045 1036 'exclude=%r, default=%r, rev=%r>',
1046 1037 pats, inc, exc, default, rev))
1047 1038
1048 1039 @predicate('file(pattern)', safe=True)
1049 1040 def hasfile(repo, subset, x):
1050 1041 """Changesets affecting files matched by pattern.
1051 1042
1052 1043 For a faster but less accurate result, consider using ``filelog()``
1053 1044 instead.
1054 1045
1055 1046 This predicate uses ``glob:`` as the default kind of pattern.
1056 1047 """
1057 1048 # i18n: "file" is a keyword
1058 1049 pat = getstring(x, _("file requires a pattern"))
1059 1050 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1060 1051
1061 1052 @predicate('head()', safe=True)
1062 1053 def head(repo, subset, x):
1063 1054 """Changeset is a named branch head.
1064 1055 """
1065 1056 # i18n: "head" is a keyword
1066 1057 getargs(x, 0, 0, _("head takes no arguments"))
1067 1058 hs = set()
1068 1059 cl = repo.changelog
1069 1060 for ls in repo.branchmap().itervalues():
1070 1061 hs.update(cl.rev(h) for h in ls)
1071 1062 return subset & baseset(hs)
1072 1063
1073 1064 @predicate('heads(set)', safe=True)
1074 1065 def heads(repo, subset, x):
1075 1066 """Members of set with no children in set.
1076 1067 """
1077 1068 s = getset(repo, subset, x)
1078 1069 ps = parents(repo, subset, x)
1079 1070 return s - ps
1080 1071
1081 1072 @predicate('hidden()', safe=True)
1082 1073 def hidden(repo, subset, x):
1083 1074 """Hidden changesets.
1084 1075 """
1085 1076 # i18n: "hidden" is a keyword
1086 1077 getargs(x, 0, 0, _("hidden takes no arguments"))
1087 1078 hiddenrevs = repoview.filterrevs(repo, 'visible')
1088 1079 return subset & hiddenrevs
1089 1080
1090 1081 @predicate('keyword(string)', safe=True)
1091 1082 def keyword(repo, subset, x):
1092 1083 """Search commit message, user name, and names of changed files for
1093 1084 string. The match is case-insensitive.
1094 1085
1095 1086 For a regular expression or case sensitive search of these fields, use
1096 1087 ``grep(regex)``.
1097 1088 """
1098 1089 # i18n: "keyword" is a keyword
1099 1090 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1100 1091
1101 1092 def matches(r):
1102 1093 c = repo[r]
1103 1094 return any(kw in encoding.lower(t)
1104 1095 for t in c.files() + [c.user(), c.description()])
1105 1096
1106 1097 return subset.filter(matches, condrepr=('<keyword %r>', kw))
1107 1098
1108 1099 @predicate('limit(set[, n[, offset]])', safe=True)
1109 1100 def limit(repo, subset, x):
1110 1101 """First n members of set, defaulting to 1, starting from offset.
1111 1102 """
1112 1103 args = getargsdict(x, 'limit', 'set n offset')
1113 1104 if 'set' not in args:
1114 1105 # i18n: "limit" is a keyword
1115 1106 raise error.ParseError(_("limit requires one to three arguments"))
1116 1107 # i18n: "limit" is a keyword
1117 1108 lim = getinteger(args.get('n'), _("limit expects a number"), default=1)
1118 1109 # i18n: "limit" is a keyword
1119 1110 ofs = getinteger(args.get('offset'), _("limit expects a number"), default=0)
1120 1111 if ofs < 0:
1121 1112 raise error.ParseError(_("negative offset"))
1122 1113 os = getset(repo, fullreposet(repo), args['set'])
1123 1114 result = []
1124 1115 it = iter(os)
1125 1116 for x in xrange(ofs):
1126 1117 y = next(it, None)
1127 1118 if y is None:
1128 1119 break
1129 1120 for x in xrange(lim):
1130 1121 y = next(it, None)
1131 1122 if y is None:
1132 1123 break
1133 1124 elif y in subset:
1134 1125 result.append(y)
1135 1126 return baseset(result, datarepr=('<limit n=%d, offset=%d, %r, %r>',
1136 1127 lim, ofs, subset, os))
1137 1128
1138 1129 @predicate('last(set, [n])', safe=True)
1139 1130 def last(repo, subset, x):
1140 1131 """Last n members of set, defaulting to 1.
1141 1132 """
1142 1133 # i18n: "last" is a keyword
1143 1134 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1144 1135 lim = 1
1145 1136 if len(l) == 2:
1146 1137 # i18n: "last" is a keyword
1147 1138 lim = getinteger(l[1], _("last expects a number"))
1148 1139 os = getset(repo, fullreposet(repo), l[0])
1149 1140 os.reverse()
1150 1141 result = []
1151 1142 it = iter(os)
1152 1143 for x in xrange(lim):
1153 1144 y = next(it, None)
1154 1145 if y is None:
1155 1146 break
1156 1147 elif y in subset:
1157 1148 result.append(y)
1158 1149 return baseset(result, datarepr=('<last n=%d, %r, %r>', lim, subset, os))
1159 1150
1160 1151 @predicate('max(set)', safe=True)
1161 1152 def maxrev(repo, subset, x):
1162 1153 """Changeset with highest revision number in set.
1163 1154 """
1164 1155 os = getset(repo, fullreposet(repo), x)
1165 1156 try:
1166 1157 m = os.max()
1167 1158 if m in subset:
1168 1159 return baseset([m], datarepr=('<max %r, %r>', subset, os))
1169 1160 except ValueError:
1170 1161 # os.max() throws a ValueError when the collection is empty.
1171 1162 # Same as python's max().
1172 1163 pass
1173 1164 return baseset(datarepr=('<max %r, %r>', subset, os))
1174 1165
1175 1166 @predicate('merge()', safe=True)
1176 1167 def merge(repo, subset, x):
1177 1168 """Changeset is a merge changeset.
1178 1169 """
1179 1170 # i18n: "merge" is a keyword
1180 1171 getargs(x, 0, 0, _("merge takes no arguments"))
1181 1172 cl = repo.changelog
1182 1173 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1,
1183 1174 condrepr='<merge>')
1184 1175
1185 1176 @predicate('branchpoint()', safe=True)
1186 1177 def branchpoint(repo, subset, x):
1187 1178 """Changesets with more than one child.
1188 1179 """
1189 1180 # i18n: "branchpoint" is a keyword
1190 1181 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1191 1182 cl = repo.changelog
1192 1183 if not subset:
1193 1184 return baseset()
1194 1185 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1195 1186 # (and if it is not, it should.)
1196 1187 baserev = min(subset)
1197 1188 parentscount = [0]*(len(repo) - baserev)
1198 1189 for r in cl.revs(start=baserev + 1):
1199 1190 for p in cl.parentrevs(r):
1200 1191 if p >= baserev:
1201 1192 parentscount[p - baserev] += 1
1202 1193 return subset.filter(lambda r: parentscount[r - baserev] > 1,
1203 1194 condrepr='<branchpoint>')
1204 1195
1205 1196 @predicate('min(set)', safe=True)
1206 1197 def minrev(repo, subset, x):
1207 1198 """Changeset with lowest revision number in set.
1208 1199 """
1209 1200 os = getset(repo, fullreposet(repo), x)
1210 1201 try:
1211 1202 m = os.min()
1212 1203 if m in subset:
1213 1204 return baseset([m], datarepr=('<min %r, %r>', subset, os))
1214 1205 except ValueError:
1215 1206 # os.min() throws a ValueError when the collection is empty.
1216 1207 # Same as python's min().
1217 1208 pass
1218 1209 return baseset(datarepr=('<min %r, %r>', subset, os))
1219 1210
1220 1211 @predicate('modifies(pattern)', safe=True)
1221 1212 def modifies(repo, subset, x):
1222 1213 """Changesets modifying files matched by pattern.
1223 1214
1224 1215 The pattern without explicit kind like ``glob:`` is expected to be
1225 1216 relative to the current directory and match against a file or a
1226 1217 directory.
1227 1218 """
1228 1219 # i18n: "modifies" is a keyword
1229 1220 pat = getstring(x, _("modifies requires a pattern"))
1230 1221 return checkstatus(repo, subset, pat, 0)
1231 1222
1232 1223 @predicate('named(namespace)')
1233 1224 def named(repo, subset, x):
1234 1225 """The changesets in a given namespace.
1235 1226
1236 1227 Pattern matching is supported for `namespace`. See
1237 1228 :hg:`help revisions.patterns`.
1238 1229 """
1239 1230 # i18n: "named" is a keyword
1240 1231 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1241 1232
1242 1233 ns = getstring(args[0],
1243 1234 # i18n: "named" is a keyword
1244 1235 _('the argument to named must be a string'))
1245 1236 kind, pattern, matcher = util.stringmatcher(ns)
1246 1237 namespaces = set()
1247 1238 if kind == 'literal':
1248 1239 if pattern not in repo.names:
1249 1240 raise error.RepoLookupError(_("namespace '%s' does not exist")
1250 1241 % ns)
1251 1242 namespaces.add(repo.names[pattern])
1252 1243 else:
1253 1244 for name, ns in repo.names.iteritems():
1254 1245 if matcher(name):
1255 1246 namespaces.add(ns)
1256 1247 if not namespaces:
1257 1248 raise error.RepoLookupError(_("no namespace exists"
1258 1249 " that match '%s'") % pattern)
1259 1250
1260 1251 names = set()
1261 1252 for ns in namespaces:
1262 1253 for name in ns.listnames(repo):
1263 1254 if name not in ns.deprecated:
1264 1255 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1265 1256
1266 1257 names -= set([node.nullrev])
1267 1258 return subset & names
1268 1259
1269 1260 @predicate('id(string)', safe=True)
1270 1261 def node_(repo, subset, x):
1271 1262 """Revision non-ambiguously specified by the given hex string prefix.
1272 1263 """
1273 1264 # i18n: "id" is a keyword
1274 1265 l = getargs(x, 1, 1, _("id requires one argument"))
1275 1266 # i18n: "id" is a keyword
1276 1267 n = getstring(l[0], _("id requires a string"))
1277 1268 if len(n) == 40:
1278 1269 try:
1279 1270 rn = repo.changelog.rev(node.bin(n))
1280 1271 except (LookupError, TypeError):
1281 1272 rn = None
1282 1273 else:
1283 1274 rn = None
1284 1275 pm = repo.changelog._partialmatch(n)
1285 1276 if pm is not None:
1286 1277 rn = repo.changelog.rev(pm)
1287 1278
1288 1279 if rn is None:
1289 1280 return baseset()
1290 1281 result = baseset([rn])
1291 1282 return result & subset
1292 1283
1293 1284 @predicate('obsolete()', safe=True)
1294 1285 def obsolete(repo, subset, x):
1295 1286 """Mutable changeset with a newer version."""
1296 1287 # i18n: "obsolete" is a keyword
1297 1288 getargs(x, 0, 0, _("obsolete takes no arguments"))
1298 1289 obsoletes = obsmod.getrevs(repo, 'obsolete')
1299 1290 return subset & obsoletes
1300 1291
1301 1292 @predicate('only(set, [set])', safe=True)
1302 1293 def only(repo, subset, x):
1303 1294 """Changesets that are ancestors of the first set that are not ancestors
1304 1295 of any other head in the repo. If a second set is specified, the result
1305 1296 is ancestors of the first set that are not ancestors of the second set
1306 1297 (i.e. ::<set1> - ::<set2>).
1307 1298 """
1308 1299 cl = repo.changelog
1309 1300 # i18n: "only" is a keyword
1310 1301 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1311 1302 include = getset(repo, fullreposet(repo), args[0])
1312 1303 if len(args) == 1:
1313 1304 if not include:
1314 1305 return baseset()
1315 1306
1316 1307 descendants = set(_revdescendants(repo, include, False))
1317 1308 exclude = [rev for rev in cl.headrevs()
1318 1309 if not rev in descendants and not rev in include]
1319 1310 else:
1320 1311 exclude = getset(repo, fullreposet(repo), args[1])
1321 1312
1322 1313 results = set(cl.findmissingrevs(common=exclude, heads=include))
1323 1314 # XXX we should turn this into a baseset instead of a set, smartset may do
1324 1315 # some optimizations from the fact this is a baseset.
1325 1316 return subset & results
1326 1317
1327 1318 @predicate('origin([set])', safe=True)
1328 1319 def origin(repo, subset, x):
1329 1320 """
1330 1321 Changesets that were specified as a source for the grafts, transplants or
1331 1322 rebases that created the given revisions. Omitting the optional set is the
1332 1323 same as passing all(). If a changeset created by these operations is itself
1333 1324 specified as a source for one of these operations, only the source changeset
1334 1325 for the first operation is selected.
1335 1326 """
1336 1327 if x is not None:
1337 1328 dests = getset(repo, fullreposet(repo), x)
1338 1329 else:
1339 1330 dests = fullreposet(repo)
1340 1331
1341 1332 def _firstsrc(rev):
1342 1333 src = _getrevsource(repo, rev)
1343 1334 if src is None:
1344 1335 return None
1345 1336
1346 1337 while True:
1347 1338 prev = _getrevsource(repo, src)
1348 1339
1349 1340 if prev is None:
1350 1341 return src
1351 1342 src = prev
1352 1343
1353 1344 o = set([_firstsrc(r) for r in dests])
1354 1345 o -= set([None])
1355 1346 # XXX we should turn this into a baseset instead of a set, smartset may do
1356 1347 # some optimizations from the fact this is a baseset.
1357 1348 return subset & o
1358 1349
1359 1350 @predicate('outgoing([path])', safe=False)
1360 1351 def outgoing(repo, subset, x):
1361 1352 """Changesets not found in the specified destination repository, or the
1362 1353 default push location.
1363 1354 """
1364 1355 # Avoid cycles.
1365 1356 from . import (
1366 1357 discovery,
1367 1358 hg,
1368 1359 )
1369 1360 # i18n: "outgoing" is a keyword
1370 1361 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1371 1362 # i18n: "outgoing" is a keyword
1372 1363 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1373 1364 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1374 1365 dest, branches = hg.parseurl(dest)
1375 1366 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1376 1367 if revs:
1377 1368 revs = [repo.lookup(rev) for rev in revs]
1378 1369 other = hg.peer(repo, {}, dest)
1379 1370 repo.ui.pushbuffer()
1380 1371 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1381 1372 repo.ui.popbuffer()
1382 1373 cl = repo.changelog
1383 1374 o = set([cl.rev(r) for r in outgoing.missing])
1384 1375 return subset & o
1385 1376
1386 1377 @predicate('p1([set])', safe=True)
1387 1378 def p1(repo, subset, x):
1388 1379 """First parent of changesets in set, or the working directory.
1389 1380 """
1390 1381 if x is None:
1391 1382 p = repo[x].p1().rev()
1392 1383 if p >= 0:
1393 1384 return subset & baseset([p])
1394 1385 return baseset()
1395 1386
1396 1387 ps = set()
1397 1388 cl = repo.changelog
1398 1389 for r in getset(repo, fullreposet(repo), x):
1399 1390 ps.add(cl.parentrevs(r)[0])
1400 1391 ps -= set([node.nullrev])
1401 1392 # XXX we should turn this into a baseset instead of a set, smartset may do
1402 1393 # some optimizations from the fact this is a baseset.
1403 1394 return subset & ps
1404 1395
1405 1396 @predicate('p2([set])', safe=True)
1406 1397 def p2(repo, subset, x):
1407 1398 """Second parent of changesets in set, or the working directory.
1408 1399 """
1409 1400 if x is None:
1410 1401 ps = repo[x].parents()
1411 1402 try:
1412 1403 p = ps[1].rev()
1413 1404 if p >= 0:
1414 1405 return subset & baseset([p])
1415 1406 return baseset()
1416 1407 except IndexError:
1417 1408 return baseset()
1418 1409
1419 1410 ps = set()
1420 1411 cl = repo.changelog
1421 1412 for r in getset(repo, fullreposet(repo), x):
1422 1413 ps.add(cl.parentrevs(r)[1])
1423 1414 ps -= set([node.nullrev])
1424 1415 # XXX we should turn this into a baseset instead of a set, smartset may do
1425 1416 # some optimizations from the fact this is a baseset.
1426 1417 return subset & ps
1427 1418
1428 1419 def parentpost(repo, subset, x, order):
1429 1420 return p1(repo, subset, x)
1430 1421
1431 1422 @predicate('parents([set])', safe=True)
1432 1423 def parents(repo, subset, x):
1433 1424 """
1434 1425 The set of all parents for all changesets in set, or the working directory.
1435 1426 """
1436 1427 if x is None:
1437 1428 ps = set(p.rev() for p in repo[x].parents())
1438 1429 else:
1439 1430 ps = set()
1440 1431 cl = repo.changelog
1441 1432 up = ps.update
1442 1433 parentrevs = cl.parentrevs
1443 1434 for r in getset(repo, fullreposet(repo), x):
1444 1435 if r == node.wdirrev:
1445 1436 up(p.rev() for p in repo[r].parents())
1446 1437 else:
1447 1438 up(parentrevs(r))
1448 1439 ps -= set([node.nullrev])
1449 1440 return subset & ps
1450 1441
1451 1442 def _phase(repo, subset, *targets):
1452 1443 """helper to select all rev in <targets> phases"""
1453 1444 s = repo._phasecache.getrevset(repo, targets)
1454 1445 return subset & s
1455 1446
1456 1447 @predicate('draft()', safe=True)
1457 1448 def draft(repo, subset, x):
1458 1449 """Changeset in draft phase."""
1459 1450 # i18n: "draft" is a keyword
1460 1451 getargs(x, 0, 0, _("draft takes no arguments"))
1461 1452 target = phases.draft
1462 1453 return _phase(repo, subset, target)
1463 1454
1464 1455 @predicate('secret()', safe=True)
1465 1456 def secret(repo, subset, x):
1466 1457 """Changeset in secret phase."""
1467 1458 # i18n: "secret" is a keyword
1468 1459 getargs(x, 0, 0, _("secret takes no arguments"))
1469 1460 target = phases.secret
1470 1461 return _phase(repo, subset, target)
1471 1462
1472 1463 def parentspec(repo, subset, x, n, order):
1473 1464 """``set^0``
1474 1465 The set.
1475 1466 ``set^1`` (or ``set^``), ``set^2``
1476 1467 First or second parent, respectively, of all changesets in set.
1477 1468 """
1478 1469 try:
1479 1470 n = int(n[1])
1480 1471 if n not in (0, 1, 2):
1481 1472 raise ValueError
1482 1473 except (TypeError, ValueError):
1483 1474 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1484 1475 ps = set()
1485 1476 cl = repo.changelog
1486 1477 for r in getset(repo, fullreposet(repo), x):
1487 1478 if n == 0:
1488 1479 ps.add(r)
1489 1480 elif n == 1:
1490 1481 ps.add(cl.parentrevs(r)[0])
1491 1482 elif n == 2:
1492 1483 parents = cl.parentrevs(r)
1493 1484 if parents[1] != node.nullrev:
1494 1485 ps.add(parents[1])
1495 1486 return subset & ps
1496 1487
1497 1488 @predicate('present(set)', safe=True)
1498 1489 def present(repo, subset, x):
1499 1490 """An empty set, if any revision in set isn't found; otherwise,
1500 1491 all revisions in set.
1501 1492
1502 1493 If any of specified revisions is not present in the local repository,
1503 1494 the query is normally aborted. But this predicate allows the query
1504 1495 to continue even in such cases.
1505 1496 """
1506 1497 try:
1507 1498 return getset(repo, subset, x)
1508 1499 except error.RepoLookupError:
1509 1500 return baseset()
1510 1501
1511 1502 # for internal use
1512 1503 @predicate('_notpublic', safe=True)
1513 1504 def _notpublic(repo, subset, x):
1514 1505 getargs(x, 0, 0, "_notpublic takes no arguments")
1515 1506 return _phase(repo, subset, phases.draft, phases.secret)
1516 1507
1517 1508 @predicate('public()', safe=True)
1518 1509 def public(repo, subset, x):
1519 1510 """Changeset in public phase."""
1520 1511 # i18n: "public" is a keyword
1521 1512 getargs(x, 0, 0, _("public takes no arguments"))
1522 1513 phase = repo._phasecache.phase
1523 1514 target = phases.public
1524 1515 condition = lambda r: phase(repo, r) == target
1525 1516 return subset.filter(condition, condrepr=('<phase %r>', target),
1526 1517 cache=False)
1527 1518
1528 1519 @predicate('remote([id [,path]])', safe=False)
1529 1520 def remote(repo, subset, x):
1530 1521 """Local revision that corresponds to the given identifier in a
1531 1522 remote repository, if present. Here, the '.' identifier is a
1532 1523 synonym for the current local branch.
1533 1524 """
1534 1525
1535 1526 from . import hg # avoid start-up nasties
1536 1527 # i18n: "remote" is a keyword
1537 1528 l = getargs(x, 0, 2, _("remote takes zero, one, or two arguments"))
1538 1529
1539 1530 q = '.'
1540 1531 if len(l) > 0:
1541 1532 # i18n: "remote" is a keyword
1542 1533 q = getstring(l[0], _("remote requires a string id"))
1543 1534 if q == '.':
1544 1535 q = repo['.'].branch()
1545 1536
1546 1537 dest = ''
1547 1538 if len(l) > 1:
1548 1539 # i18n: "remote" is a keyword
1549 1540 dest = getstring(l[1], _("remote requires a repository path"))
1550 1541 dest = repo.ui.expandpath(dest or 'default')
1551 1542 dest, branches = hg.parseurl(dest)
1552 1543 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1553 1544 if revs:
1554 1545 revs = [repo.lookup(rev) for rev in revs]
1555 1546 other = hg.peer(repo, {}, dest)
1556 1547 n = other.lookup(q)
1557 1548 if n in repo:
1558 1549 r = repo[n].rev()
1559 1550 if r in subset:
1560 1551 return baseset([r])
1561 1552 return baseset()
1562 1553
1563 1554 @predicate('removes(pattern)', safe=True)
1564 1555 def removes(repo, subset, x):
1565 1556 """Changesets which remove files matching pattern.
1566 1557
1567 1558 The pattern without explicit kind like ``glob:`` is expected to be
1568 1559 relative to the current directory and match against a file or a
1569 1560 directory.
1570 1561 """
1571 1562 # i18n: "removes" is a keyword
1572 1563 pat = getstring(x, _("removes requires a pattern"))
1573 1564 return checkstatus(repo, subset, pat, 2)
1574 1565
1575 1566 @predicate('rev(number)', safe=True)
1576 1567 def rev(repo, subset, x):
1577 1568 """Revision with the given numeric identifier.
1578 1569 """
1579 1570 # i18n: "rev" is a keyword
1580 1571 l = getargs(x, 1, 1, _("rev requires one argument"))
1581 1572 try:
1582 1573 # i18n: "rev" is a keyword
1583 1574 l = int(getstring(l[0], _("rev requires a number")))
1584 1575 except (TypeError, ValueError):
1585 1576 # i18n: "rev" is a keyword
1586 1577 raise error.ParseError(_("rev expects a number"))
1587 1578 if l not in repo.changelog and l != node.nullrev:
1588 1579 return baseset()
1589 1580 return subset & baseset([l])
1590 1581
1591 1582 @predicate('matching(revision [, field])', safe=True)
1592 1583 def matching(repo, subset, x):
1593 1584 """Changesets in which a given set of fields match the set of fields in the
1594 1585 selected revision or set.
1595 1586
1596 1587 To match more than one field pass the list of fields to match separated
1597 1588 by spaces (e.g. ``author description``).
1598 1589
1599 1590 Valid fields are most regular revision fields and some special fields.
1600 1591
1601 1592 Regular revision fields are ``description``, ``author``, ``branch``,
1602 1593 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1603 1594 and ``diff``.
1604 1595 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1605 1596 contents of the revision. Two revisions matching their ``diff`` will
1606 1597 also match their ``files``.
1607 1598
1608 1599 Special fields are ``summary`` and ``metadata``:
1609 1600 ``summary`` matches the first line of the description.
1610 1601 ``metadata`` is equivalent to matching ``description user date``
1611 1602 (i.e. it matches the main metadata fields).
1612 1603
1613 1604 ``metadata`` is the default field which is used when no fields are
1614 1605 specified. You can match more than one field at a time.
1615 1606 """
1616 1607 # i18n: "matching" is a keyword
1617 1608 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1618 1609
1619 1610 revs = getset(repo, fullreposet(repo), l[0])
1620 1611
1621 1612 fieldlist = ['metadata']
1622 1613 if len(l) > 1:
1623 1614 fieldlist = getstring(l[1],
1624 1615 # i18n: "matching" is a keyword
1625 1616 _("matching requires a string "
1626 1617 "as its second argument")).split()
1627 1618
1628 1619 # Make sure that there are no repeated fields,
1629 1620 # expand the 'special' 'metadata' field type
1630 1621 # and check the 'files' whenever we check the 'diff'
1631 1622 fields = []
1632 1623 for field in fieldlist:
1633 1624 if field == 'metadata':
1634 1625 fields += ['user', 'description', 'date']
1635 1626 elif field == 'diff':
1636 1627 # a revision matching the diff must also match the files
1637 1628 # since matching the diff is very costly, make sure to
1638 1629 # also match the files first
1639 1630 fields += ['files', 'diff']
1640 1631 else:
1641 1632 if field == 'author':
1642 1633 field = 'user'
1643 1634 fields.append(field)
1644 1635 fields = set(fields)
1645 1636 if 'summary' in fields and 'description' in fields:
1646 1637 # If a revision matches its description it also matches its summary
1647 1638 fields.discard('summary')
1648 1639
1649 1640 # We may want to match more than one field
1650 1641 # Not all fields take the same amount of time to be matched
1651 1642 # Sort the selected fields in order of increasing matching cost
1652 1643 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1653 1644 'files', 'description', 'substate', 'diff']
1654 1645 def fieldkeyfunc(f):
1655 1646 try:
1656 1647 return fieldorder.index(f)
1657 1648 except ValueError:
1658 1649 # assume an unknown field is very costly
1659 1650 return len(fieldorder)
1660 1651 fields = list(fields)
1661 1652 fields.sort(key=fieldkeyfunc)
1662 1653
1663 1654 # Each field will be matched with its own "getfield" function
1664 1655 # which will be added to the getfieldfuncs array of functions
1665 1656 getfieldfuncs = []
1666 1657 _funcs = {
1667 1658 'user': lambda r: repo[r].user(),
1668 1659 'branch': lambda r: repo[r].branch(),
1669 1660 'date': lambda r: repo[r].date(),
1670 1661 'description': lambda r: repo[r].description(),
1671 1662 'files': lambda r: repo[r].files(),
1672 1663 'parents': lambda r: repo[r].parents(),
1673 1664 'phase': lambda r: repo[r].phase(),
1674 1665 'substate': lambda r: repo[r].substate,
1675 1666 'summary': lambda r: repo[r].description().splitlines()[0],
1676 1667 'diff': lambda r: list(repo[r].diff(git=True),)
1677 1668 }
1678 1669 for info in fields:
1679 1670 getfield = _funcs.get(info, None)
1680 1671 if getfield is None:
1681 1672 raise error.ParseError(
1682 1673 # i18n: "matching" is a keyword
1683 1674 _("unexpected field name passed to matching: %s") % info)
1684 1675 getfieldfuncs.append(getfield)
1685 1676 # convert the getfield array of functions into a "getinfo" function
1686 1677 # which returns an array of field values (or a single value if there
1687 1678 # is only one field to match)
1688 1679 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1689 1680
1690 1681 def matches(x):
1691 1682 for rev in revs:
1692 1683 target = getinfo(rev)
1693 1684 match = True
1694 1685 for n, f in enumerate(getfieldfuncs):
1695 1686 if target[n] != f(x):
1696 1687 match = False
1697 1688 if match:
1698 1689 return True
1699 1690 return False
1700 1691
1701 1692 return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
1702 1693
1703 1694 @predicate('reverse(set)', safe=True, takeorder=True)
1704 1695 def reverse(repo, subset, x, order):
1705 1696 """Reverse order of set.
1706 1697 """
1707 1698 l = getset(repo, subset, x)
1708 1699 if order == defineorder:
1709 1700 l.reverse()
1710 1701 return l
1711 1702
1712 1703 @predicate('roots(set)', safe=True)
1713 1704 def roots(repo, subset, x):
1714 1705 """Changesets in set with no parent changeset in set.
1715 1706 """
1716 1707 s = getset(repo, fullreposet(repo), x)
1717 1708 parents = repo.changelog.parentrevs
1718 1709 def filter(r):
1719 1710 for p in parents(r):
1720 1711 if 0 <= p and p in s:
1721 1712 return False
1722 1713 return True
1723 1714 return subset & s.filter(filter, condrepr='<roots>')
1724 1715
1725 1716 _sortkeyfuncs = {
1726 1717 'rev': lambda c: c.rev(),
1727 1718 'branch': lambda c: c.branch(),
1728 1719 'desc': lambda c: c.description(),
1729 1720 'user': lambda c: c.user(),
1730 1721 'author': lambda c: c.user(),
1731 1722 'date': lambda c: c.date()[0],
1732 1723 }
1733 1724
1734 1725 def _getsortargs(x):
1735 1726 """Parse sort options into (set, [(key, reverse)], opts)"""
1736 1727 args = getargsdict(x, 'sort', 'set keys topo.firstbranch')
1737 1728 if 'set' not in args:
1738 1729 # i18n: "sort" is a keyword
1739 1730 raise error.ParseError(_('sort requires one or two arguments'))
1740 1731 keys = "rev"
1741 1732 if 'keys' in args:
1742 1733 # i18n: "sort" is a keyword
1743 1734 keys = getstring(args['keys'], _("sort spec must be a string"))
1744 1735
1745 1736 keyflags = []
1746 1737 for k in keys.split():
1747 1738 fk = k
1748 1739 reverse = (k[0] == '-')
1749 1740 if reverse:
1750 1741 k = k[1:]
1751 1742 if k not in _sortkeyfuncs and k != 'topo':
1752 1743 raise error.ParseError(_("unknown sort key %r") % fk)
1753 1744 keyflags.append((k, reverse))
1754 1745
1755 1746 if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):
1756 1747 # i18n: "topo" is a keyword
1757 1748 raise error.ParseError(_('topo sort order cannot be combined '
1758 1749 'with other sort keys'))
1759 1750
1760 1751 opts = {}
1761 1752 if 'topo.firstbranch' in args:
1762 1753 if any(k == 'topo' for k, reverse in keyflags):
1763 1754 opts['topo.firstbranch'] = args['topo.firstbranch']
1764 1755 else:
1765 1756 # i18n: "topo" and "topo.firstbranch" are keywords
1766 1757 raise error.ParseError(_('topo.firstbranch can only be used '
1767 1758 'when using the topo sort key'))
1768 1759
1769 1760 return args['set'], keyflags, opts
1770 1761
1771 1762 @predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True)
1772 1763 def sort(repo, subset, x, order):
1773 1764 """Sort set by keys. The default sort order is ascending, specify a key
1774 1765 as ``-key`` to sort in descending order.
1775 1766
1776 1767 The keys can be:
1777 1768
1778 1769 - ``rev`` for the revision number,
1779 1770 - ``branch`` for the branch name,
1780 1771 - ``desc`` for the commit message (description),
1781 1772 - ``user`` for user name (``author`` can be used as an alias),
1782 1773 - ``date`` for the commit date
1783 1774 - ``topo`` for a reverse topographical sort
1784 1775
1785 1776 The ``topo`` sort order cannot be combined with other sort keys. This sort
1786 1777 takes one optional argument, ``topo.firstbranch``, which takes a revset that
1787 1778 specifies what topographical branches to prioritize in the sort.
1788 1779
1789 1780 """
1790 1781 s, keyflags, opts = _getsortargs(x)
1791 1782 revs = getset(repo, subset, s)
1792 1783
1793 1784 if not keyflags or order != defineorder:
1794 1785 return revs
1795 1786 if len(keyflags) == 1 and keyflags[0][0] == "rev":
1796 1787 revs.sort(reverse=keyflags[0][1])
1797 1788 return revs
1798 1789 elif keyflags[0][0] == "topo":
1799 1790 firstbranch = ()
1800 1791 if 'topo.firstbranch' in opts:
1801 1792 firstbranch = getset(repo, subset, opts['topo.firstbranch'])
1802 1793 revs = baseset(_toposort(revs, repo.changelog.parentrevs, firstbranch),
1803 1794 istopo=True)
1804 1795 if keyflags[0][1]:
1805 1796 revs.reverse()
1806 1797 return revs
1807 1798
1808 1799 # sort() is guaranteed to be stable
1809 1800 ctxs = [repo[r] for r in revs]
1810 1801 for k, reverse in reversed(keyflags):
1811 1802 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
1812 1803 return baseset([c.rev() for c in ctxs])
1813 1804
1814 1805 def _toposort(revs, parentsfunc, firstbranch=()):
1815 1806 """Yield revisions from heads to roots one (topo) branch at a time.
1816 1807
1817 1808 This function aims to be used by a graph generator that wishes to minimize
1818 1809 the number of parallel branches and their interleaving.
1819 1810
1820 1811 Example iteration order (numbers show the "true" order in a changelog):
1821 1812
1822 1813 o 4
1823 1814 |
1824 1815 o 1
1825 1816 |
1826 1817 | o 3
1827 1818 | |
1828 1819 | o 2
1829 1820 |/
1830 1821 o 0
1831 1822
1832 1823 Note that the ancestors of merges are understood by the current
1833 1824 algorithm to be on the same branch. This means no reordering will
1834 1825 occur behind a merge.
1835 1826 """
1836 1827
1837 1828 ### Quick summary of the algorithm
1838 1829 #
1839 1830 # This function is based around a "retention" principle. We keep revisions
1840 1831 # in memory until we are ready to emit a whole branch that immediately
1841 1832 # "merges" into an existing one. This reduces the number of parallel
1842 1833 # branches with interleaved revisions.
1843 1834 #
1844 1835 # During iteration revs are split into two groups:
1845 1836 # A) revision already emitted
1846 1837 # B) revision in "retention". They are stored as different subgroups.
1847 1838 #
1848 1839 # for each REV, we do the following logic:
1849 1840 #
1850 1841 # 1) if REV is a parent of (A), we will emit it. If there is a
1851 1842 # retention group ((B) above) that is blocked on REV being
1852 1843 # available, we emit all the revisions out of that retention
1853 1844 # group first.
1854 1845 #
1855 1846 # 2) else, we'll search for a subgroup in (B) awaiting for REV to be
1856 1847 # available, if such subgroup exist, we add REV to it and the subgroup is
1857 1848 # now awaiting for REV.parents() to be available.
1858 1849 #
1859 1850 # 3) finally if no such group existed in (B), we create a new subgroup.
1860 1851 #
1861 1852 #
1862 1853 # To bootstrap the algorithm, we emit the tipmost revision (which
1863 1854 # puts it in group (A) from above).
1864 1855
1865 1856 revs.sort(reverse=True)
1866 1857
1867 1858 # Set of parents of revision that have been emitted. They can be considered
1868 1859 # unblocked as the graph generator is already aware of them so there is no
1869 1860 # need to delay the revisions that reference them.
1870 1861 #
1871 1862 # If someone wants to prioritize a branch over the others, pre-filling this
1872 1863 # set will force all other branches to wait until this branch is ready to be
1873 1864 # emitted.
1874 1865 unblocked = set(firstbranch)
1875 1866
1876 1867 # list of groups waiting to be displayed, each group is defined by:
1877 1868 #
1878 1869 # (revs: lists of revs waiting to be displayed,
1879 1870 # blocked: set of that cannot be displayed before those in 'revs')
1880 1871 #
1881 1872 # The second value ('blocked') correspond to parents of any revision in the
1882 1873 # group ('revs') that is not itself contained in the group. The main idea
1883 1874 # of this algorithm is to delay as much as possible the emission of any
1884 1875 # revision. This means waiting for the moment we are about to display
1885 1876 # these parents to display the revs in a group.
1886 1877 #
1887 1878 # This first implementation is smart until it encounters a merge: it will
1888 1879 # emit revs as soon as any parent is about to be emitted and can grow an
1889 1880 # arbitrary number of revs in 'blocked'. In practice this mean we properly
1890 1881 # retains new branches but gives up on any special ordering for ancestors
1891 1882 # of merges. The implementation can be improved to handle this better.
1892 1883 #
1893 1884 # The first subgroup is special. It corresponds to all the revision that
1894 1885 # were already emitted. The 'revs' lists is expected to be empty and the
1895 1886 # 'blocked' set contains the parents revisions of already emitted revision.
1896 1887 #
1897 1888 # You could pre-seed the <parents> set of groups[0] to a specific
1898 1889 # changesets to select what the first emitted branch should be.
1899 1890 groups = [([], unblocked)]
1900 1891 pendingheap = []
1901 1892 pendingset = set()
1902 1893
1903 1894 heapq.heapify(pendingheap)
1904 1895 heappop = heapq.heappop
1905 1896 heappush = heapq.heappush
1906 1897 for currentrev in revs:
1907 1898 # Heap works with smallest element, we want highest so we invert
1908 1899 if currentrev not in pendingset:
1909 1900 heappush(pendingheap, -currentrev)
1910 1901 pendingset.add(currentrev)
1911 1902 # iterates on pending rev until after the current rev have been
1912 1903 # processed.
1913 1904 rev = None
1914 1905 while rev != currentrev:
1915 1906 rev = -heappop(pendingheap)
1916 1907 pendingset.remove(rev)
1917 1908
1918 1909 # Seek for a subgroup blocked, waiting for the current revision.
1919 1910 matching = [i for i, g in enumerate(groups) if rev in g[1]]
1920 1911
1921 1912 if matching:
1922 1913 # The main idea is to gather together all sets that are blocked
1923 1914 # on the same revision.
1924 1915 #
1925 1916 # Groups are merged when a common blocking ancestor is
1926 1917 # observed. For example, given two groups:
1927 1918 #
1928 1919 # revs [5, 4] waiting for 1
1929 1920 # revs [3, 2] waiting for 1
1930 1921 #
1931 1922 # These two groups will be merged when we process
1932 1923 # 1. In theory, we could have merged the groups when
1933 1924 # we added 2 to the group it is now in (we could have
1934 1925 # noticed the groups were both blocked on 1 then), but
1935 1926 # the way it works now makes the algorithm simpler.
1936 1927 #
1937 1928 # We also always keep the oldest subgroup first. We can
1938 1929 # probably improve the behavior by having the longest set
1939 1930 # first. That way, graph algorithms could minimise the length
1940 1931 # of parallel lines their drawing. This is currently not done.
1941 1932 targetidx = matching.pop(0)
1942 1933 trevs, tparents = groups[targetidx]
1943 1934 for i in matching:
1944 1935 gr = groups[i]
1945 1936 trevs.extend(gr[0])
1946 1937 tparents |= gr[1]
1947 1938 # delete all merged subgroups (except the one we kept)
1948 1939 # (starting from the last subgroup for performance and
1949 1940 # sanity reasons)
1950 1941 for i in reversed(matching):
1951 1942 del groups[i]
1952 1943 else:
1953 1944 # This is a new head. We create a new subgroup for it.
1954 1945 targetidx = len(groups)
1955 1946 groups.append(([], set([rev])))
1956 1947
1957 1948 gr = groups[targetidx]
1958 1949
1959 1950 # We now add the current nodes to this subgroups. This is done
1960 1951 # after the subgroup merging because all elements from a subgroup
1961 1952 # that relied on this rev must precede it.
1962 1953 #
1963 1954 # we also update the <parents> set to include the parents of the
1964 1955 # new nodes.
1965 1956 if rev == currentrev: # only display stuff in rev
1966 1957 gr[0].append(rev)
1967 1958 gr[1].remove(rev)
1968 1959 parents = [p for p in parentsfunc(rev) if p > node.nullrev]
1969 1960 gr[1].update(parents)
1970 1961 for p in parents:
1971 1962 if p not in pendingset:
1972 1963 pendingset.add(p)
1973 1964 heappush(pendingheap, -p)
1974 1965
1975 1966 # Look for a subgroup to display
1976 1967 #
1977 1968 # When unblocked is empty (if clause), we were not waiting for any
1978 1969 # revisions during the first iteration (if no priority was given) or
1979 1970 # if we emitted a whole disconnected set of the graph (reached a
1980 1971 # root). In that case we arbitrarily take the oldest known
1981 1972 # subgroup. The heuristic could probably be better.
1982 1973 #
1983 1974 # Otherwise (elif clause) if the subgroup is blocked on
1984 1975 # a revision we just emitted, we can safely emit it as
1985 1976 # well.
1986 1977 if not unblocked:
1987 1978 if len(groups) > 1: # display other subset
1988 1979 targetidx = 1
1989 1980 gr = groups[1]
1990 1981 elif not gr[1] & unblocked:
1991 1982 gr = None
1992 1983
1993 1984 if gr is not None:
1994 1985 # update the set of awaited revisions with the one from the
1995 1986 # subgroup
1996 1987 unblocked |= gr[1]
1997 1988 # output all revisions in the subgroup
1998 1989 for r in gr[0]:
1999 1990 yield r
2000 1991 # delete the subgroup that you just output
2001 1992 # unless it is groups[0] in which case you just empty it.
2002 1993 if targetidx:
2003 1994 del groups[targetidx]
2004 1995 else:
2005 1996 gr[0][:] = []
2006 1997 # Check if we have some subgroup waiting for revisions we are not going to
2007 1998 # iterate over
2008 1999 for g in groups:
2009 2000 for r in g[0]:
2010 2001 yield r
2011 2002
2012 2003 @predicate('subrepo([pattern])')
2013 2004 def subrepo(repo, subset, x):
2014 2005 """Changesets that add, modify or remove the given subrepo. If no subrepo
2015 2006 pattern is named, any subrepo changes are returned.
2016 2007 """
2017 2008 # i18n: "subrepo" is a keyword
2018 2009 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
2019 2010 pat = None
2020 2011 if len(args) != 0:
2021 2012 pat = getstring(args[0], _("subrepo requires a pattern"))
2022 2013
2023 2014 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
2024 2015
2025 2016 def submatches(names):
2026 2017 k, p, m = util.stringmatcher(pat)
2027 2018 for name in names:
2028 2019 if m(name):
2029 2020 yield name
2030 2021
2031 2022 def matches(x):
2032 2023 c = repo[x]
2033 2024 s = repo.status(c.p1().node(), c.node(), match=m)
2034 2025
2035 2026 if pat is None:
2036 2027 return s.added or s.modified or s.removed
2037 2028
2038 2029 if s.added:
2039 2030 return any(submatches(c.substate.keys()))
2040 2031
2041 2032 if s.modified:
2042 2033 subs = set(c.p1().substate.keys())
2043 2034 subs.update(c.substate.keys())
2044 2035
2045 2036 for path in submatches(subs):
2046 2037 if c.p1().substate.get(path) != c.substate.get(path):
2047 2038 return True
2048 2039
2049 2040 if s.removed:
2050 2041 return any(submatches(c.p1().substate.keys()))
2051 2042
2052 2043 return False
2053 2044
2054 2045 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
2055 2046
2056 2047 def _substringmatcher(pattern, casesensitive=True):
2057 2048 kind, pattern, matcher = util.stringmatcher(pattern,
2058 2049 casesensitive=casesensitive)
2059 2050 if kind == 'literal':
2060 2051 if not casesensitive:
2061 2052 pattern = encoding.lower(pattern)
2062 2053 matcher = lambda s: pattern in encoding.lower(s)
2063 2054 else:
2064 2055 matcher = lambda s: pattern in s
2065 2056 return kind, pattern, matcher
2066 2057
2067 2058 @predicate('tag([name])', safe=True)
2068 2059 def tag(repo, subset, x):
2069 2060 """The specified tag by name, or all tagged revisions if no name is given.
2070 2061
2071 2062 Pattern matching is supported for `name`. See
2072 2063 :hg:`help revisions.patterns`.
2073 2064 """
2074 2065 # i18n: "tag" is a keyword
2075 2066 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
2076 2067 cl = repo.changelog
2077 2068 if args:
2078 2069 pattern = getstring(args[0],
2079 2070 # i18n: "tag" is a keyword
2080 2071 _('the argument to tag must be a string'))
2081 2072 kind, pattern, matcher = util.stringmatcher(pattern)
2082 2073 if kind == 'literal':
2083 2074 # avoid resolving all tags
2084 2075 tn = repo._tagscache.tags.get(pattern, None)
2085 2076 if tn is None:
2086 2077 raise error.RepoLookupError(_("tag '%s' does not exist")
2087 2078 % pattern)
2088 2079 s = set([repo[tn].rev()])
2089 2080 else:
2090 2081 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
2091 2082 else:
2092 2083 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
2093 2084 return subset & s
2094 2085
2095 2086 @predicate('tagged', safe=True)
2096 2087 def tagged(repo, subset, x):
2097 2088 return tag(repo, subset, x)
2098 2089
2099 2090 @predicate('unstable()', safe=True)
2100 2091 def unstable(repo, subset, x):
2101 2092 """Non-obsolete changesets with obsolete ancestors.
2102 2093 """
2103 2094 # i18n: "unstable" is a keyword
2104 2095 getargs(x, 0, 0, _("unstable takes no arguments"))
2105 2096 unstables = obsmod.getrevs(repo, 'unstable')
2106 2097 return subset & unstables
2107 2098
2108 2099
2109 2100 @predicate('user(string)', safe=True)
2110 2101 def user(repo, subset, x):
2111 2102 """User name contains string. The match is case-insensitive.
2112 2103
2113 2104 Pattern matching is supported for `string`. See
2114 2105 :hg:`help revisions.patterns`.
2115 2106 """
2116 2107 return author(repo, subset, x)
2117 2108
2118 2109 @predicate('wdir', safe=True)
2119 2110 def wdir(repo, subset, x):
2120 2111 """Working directory. (EXPERIMENTAL)"""
2121 2112 # i18n: "wdir" is a keyword
2122 2113 getargs(x, 0, 0, _("wdir takes no arguments"))
2123 2114 if node.wdirrev in subset or isinstance(subset, fullreposet):
2124 2115 return baseset([node.wdirrev])
2125 2116 return baseset()
2126 2117
2127 2118 def _orderedlist(repo, subset, x):
2128 2119 s = getstring(x, "internal error")
2129 2120 if not s:
2130 2121 return baseset()
2131 2122 # remove duplicates here. it's difficult for caller to deduplicate sets
2132 2123 # because different symbols can point to the same rev.
2133 2124 cl = repo.changelog
2134 2125 ls = []
2135 2126 seen = set()
2136 2127 for t in s.split('\0'):
2137 2128 try:
2138 2129 # fast path for integer revision
2139 2130 r = int(t)
2140 2131 if str(r) != t or r not in cl:
2141 2132 raise ValueError
2142 2133 revs = [r]
2143 2134 except ValueError:
2144 2135 revs = stringset(repo, subset, t)
2145 2136
2146 2137 for r in revs:
2147 2138 if r in seen:
2148 2139 continue
2149 2140 if (r in subset
2150 2141 or r == node.nullrev and isinstance(subset, fullreposet)):
2151 2142 ls.append(r)
2152 2143 seen.add(r)
2153 2144 return baseset(ls)
2154 2145
2155 2146 # for internal use
2156 2147 @predicate('_list', safe=True, takeorder=True)
2157 2148 def _list(repo, subset, x, order):
2158 2149 if order == followorder:
2159 2150 # slow path to take the subset order
2160 2151 return subset & _orderedlist(repo, fullreposet(repo), x)
2161 2152 else:
2162 2153 return _orderedlist(repo, subset, x)
2163 2154
2164 2155 def _orderedintlist(repo, subset, x):
2165 2156 s = getstring(x, "internal error")
2166 2157 if not s:
2167 2158 return baseset()
2168 2159 ls = [int(r) for r in s.split('\0')]
2169 2160 s = subset
2170 2161 return baseset([r for r in ls if r in s])
2171 2162
2172 2163 # for internal use
2173 2164 @predicate('_intlist', safe=True, takeorder=True)
2174 2165 def _intlist(repo, subset, x, order):
2175 2166 if order == followorder:
2176 2167 # slow path to take the subset order
2177 2168 return subset & _orderedintlist(repo, fullreposet(repo), x)
2178 2169 else:
2179 2170 return _orderedintlist(repo, subset, x)
2180 2171
2181 2172 def _orderedhexlist(repo, subset, x):
2182 2173 s = getstring(x, "internal error")
2183 2174 if not s:
2184 2175 return baseset()
2185 2176 cl = repo.changelog
2186 2177 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2187 2178 s = subset
2188 2179 return baseset([r for r in ls if r in s])
2189 2180
2190 2181 # for internal use
2191 2182 @predicate('_hexlist', safe=True, takeorder=True)
2192 2183 def _hexlist(repo, subset, x, order):
2193 2184 if order == followorder:
2194 2185 # slow path to take the subset order
2195 2186 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2196 2187 else:
2197 2188 return _orderedhexlist(repo, subset, x)
2198 2189
2199 2190 methods = {
2200 2191 "range": rangeset,
2201 2192 "rangeall": rangeall,
2202 2193 "rangepre": rangepre,
2203 2194 "rangepost": rangepost,
2204 2195 "dagrange": dagrange,
2205 2196 "string": stringset,
2206 2197 "symbol": stringset,
2207 2198 "and": andset,
2208 2199 "or": orset,
2209 2200 "not": notset,
2210 2201 "difference": differenceset,
2211 2202 "list": listset,
2212 2203 "keyvalue": keyvaluepair,
2213 2204 "func": func,
2214 2205 "ancestor": ancestorspec,
2215 2206 "parent": parentspec,
2216 2207 "parentpost": parentpost,
2217 2208 }
2218 2209
2219 2210 def posttreebuilthook(tree, repo):
2220 2211 # hook for extensions to execute code on the optimized tree
2221 2212 pass
2222 2213
2223 2214 def match(ui, spec, repo=None, order=defineorder):
2224 2215 """Create a matcher for a single revision spec
2225 2216
2226 2217 If order=followorder, a matcher takes the ordering specified by the input
2227 2218 set.
2228 2219 """
2229 2220 return matchany(ui, [spec], repo=repo, order=order)
2230 2221
2231 2222 def matchany(ui, specs, repo=None, order=defineorder):
2232 2223 """Create a matcher that will include any revisions matching one of the
2233 2224 given specs
2234 2225
2235 2226 If order=followorder, a matcher takes the ordering specified by the input
2236 2227 set.
2237 2228 """
2238 2229 if not specs:
2239 2230 def mfunc(repo, subset=None):
2240 2231 return baseset()
2241 2232 return mfunc
2242 2233 if not all(specs):
2243 2234 raise error.ParseError(_("empty query"))
2244 2235 lookup = None
2245 2236 if repo:
2246 2237 lookup = repo.__contains__
2247 2238 if len(specs) == 1:
2248 2239 tree = revsetlang.parse(specs[0], lookup)
2249 2240 else:
2250 2241 tree = ('or',
2251 2242 ('list',) + tuple(revsetlang.parse(s, lookup) for s in specs))
2252 2243
2253 2244 if ui:
2254 2245 tree = revsetlang.expandaliases(ui, tree)
2255 2246 tree = revsetlang.foldconcat(tree)
2256 2247 tree = revsetlang.analyze(tree, order)
2257 2248 tree = revsetlang.optimize(tree)
2258 2249 posttreebuilthook(tree, repo)
2259 2250 return makematcher(tree)
2260 2251
2261 2252 def makematcher(tree):
2262 2253 """Create a matcher from an evaluatable tree"""
2263 2254 def mfunc(repo, subset=None):
2264 2255 if subset is None:
2265 2256 subset = fullreposet(repo)
2266 2257 if util.safehasattr(subset, 'isascending'):
2267 2258 result = getset(repo, subset, tree)
2268 2259 else:
2269 2260 result = getset(repo, baseset(subset), tree)
2270 2261 return result
2271 2262 return mfunc
2272 2263
2273 2264 def loadpredicate(ui, extname, registrarobj):
2274 2265 """Load revset predicates from specified registrarobj
2275 2266 """
2276 2267 for name, func in registrarobj._table.iteritems():
2277 2268 symbols[name] = func
2278 2269 if func._safe:
2279 2270 safesymbols.add(name)
2280 2271
2281 2272 # load built-in predicates explicitly to setup safesymbols
2282 2273 loadpredicate(None, None, predicate)
2283 2274
2284 2275 # tell hggettext to extract docstrings from these functions:
2285 2276 i18nfunctions = symbols.values()
@@ -1,178 +1,166 b''
1 1
2 2 $ cat << EOF > buggylocking.py
3 3 > """A small extension that tests our developer warnings
4 4 > """
5 5 >
6 > from mercurial import cmdutil, repair, revset
6 > from mercurial import cmdutil, repair
7 7 >
8 8 > cmdtable = {}
9 9 > command = cmdutil.command(cmdtable)
10 10 >
11 11 > @command('buggylocking', [], '')
12 12 > def buggylocking(ui, repo):
13 13 > lo = repo.lock()
14 14 > wl = repo.wlock()
15 15 > wl.release()
16 16 > lo.release()
17 17 >
18 18 > @command('buggytransaction', [], '')
19 19 > def buggylocking(ui, repo):
20 20 > tr = repo.transaction('buggy')
21 21 > # make sure we rollback the transaction as we don't want to rely on the__del__
22 22 > tr.release()
23 23 >
24 24 > @command('properlocking', [], '')
25 25 > def properlocking(ui, repo):
26 26 > """check that reentrance is fine"""
27 27 > wl = repo.wlock()
28 28 > lo = repo.lock()
29 29 > tr = repo.transaction('proper')
30 30 > tr2 = repo.transaction('proper')
31 31 > lo2 = repo.lock()
32 32 > wl2 = repo.wlock()
33 33 > wl2.release()
34 34 > lo2.release()
35 35 > tr2.close()
36 36 > tr.close()
37 37 > lo.release()
38 38 > wl.release()
39 39 >
40 40 > @command('nowaitlocking', [], '')
41 41 > def nowaitlocking(ui, repo):
42 42 > lo = repo.lock()
43 43 > wl = repo.wlock(wait=False)
44 44 > wl.release()
45 45 > lo.release()
46 46 >
47 47 > @command('stripintr', [], '')
48 48 > def stripintr(ui, repo):
49 49 > lo = repo.lock()
50 50 > tr = repo.transaction('foobar')
51 51 > try:
52 52 > repair.strip(repo.ui, repo, [repo['.'].node()])
53 53 > finally:
54 54 > lo.release()
55 55 > @command('oldanddeprecated', [], '')
56 56 > def oldanddeprecated(ui, repo):
57 57 > """test deprecation warning API"""
58 58 > def foobar(ui):
59 59 > ui.deprecwarn('foorbar is deprecated, go shopping', '42.1337')
60 60 > foobar(ui)
61 >
62 > def oldstylerevset(repo, subset, x):
63 > return list(subset)
64 >
65 > revset.symbols['oldstyle'] = oldstylerevset
66 61 > EOF
67 62
68 63 $ cat << EOF >> $HGRCPATH
69 64 > [extensions]
70 65 > buggylocking=$TESTTMP/buggylocking.py
71 66 > mock=$TESTDIR/mockblackbox.py
72 67 > blackbox=
73 68 > [devel]
74 69 > all-warnings=1
75 70 > EOF
76 71
77 72 $ hg init lock-checker
78 73 $ cd lock-checker
79 74 $ hg buggylocking
80 75 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
81 76 $ cat << EOF >> $HGRCPATH
82 77 > [devel]
83 78 > all=0
84 79 > check-locks=1
85 80 > EOF
86 81 $ hg buggylocking
87 82 devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
88 83 $ hg buggylocking --traceback
89 84 devel-warn: "wlock" acquired after "lock" at:
90 85 */hg:* in * (glob)
91 86 */mercurial/dispatch.py:* in run (glob)
92 87 */mercurial/dispatch.py:* in dispatch (glob)
93 88 */mercurial/dispatch.py:* in _runcatch (glob)
94 89 */mercurial/dispatch.py:* in callcatch (glob)
95 90 */mercurial/scmutil.py* in callcatch (glob)
96 91 */mercurial/dispatch.py:* in _runcatchfunc (glob)
97 92 */mercurial/dispatch.py:* in _dispatch (glob)
98 93 */mercurial/dispatch.py:* in runcommand (glob)
99 94 */mercurial/dispatch.py:* in _runcommand (glob)
100 95 */mercurial/dispatch.py:* in <lambda> (glob)
101 96 */mercurial/util.py:* in check (glob)
102 97 $TESTTMP/buggylocking.py:* in buggylocking (glob)
103 98 $ hg properlocking
104 99 $ hg nowaitlocking
105 100
106 101 $ echo a > a
107 102 $ hg add a
108 103 $ hg commit -m a
109 104 $ hg stripintr 2>&1 | egrep -v '^(\*\*| )'
110 105 saved backup bundle to $TESTTMP/lock-checker/.hg/strip-backup/*-backup.hg (glob)
111 106 Traceback (most recent call last):
112 107 mercurial.error.ProgrammingError: cannot strip from inside a transaction
113 108
114 $ hg log -r "oldstyle()" -T '{rev}\n'
115 devel-warn: revset "oldstyle" uses list instead of smartset
116 (compatibility will be dropped after Mercurial-3.9, update your code.) at: *mercurial/revset.py:* (mfunc) (glob)
117 0
118 109 $ hg oldanddeprecated
119 110 devel-warn: foorbar is deprecated, go shopping
120 111 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
121 112
122 113 $ hg oldanddeprecated --traceback
123 114 devel-warn: foorbar is deprecated, go shopping
124 115 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
125 116 */hg:* in <module> (glob)
126 117 */mercurial/dispatch.py:* in run (glob)
127 118 */mercurial/dispatch.py:* in dispatch (glob)
128 119 */mercurial/dispatch.py:* in _runcatch (glob)
129 120 */mercurial/dispatch.py:* in callcatch (glob)
130 121 */mercurial/scmutil.py* in callcatch (glob)
131 122 */mercurial/dispatch.py:* in _runcatchfunc (glob)
132 123 */mercurial/dispatch.py:* in _dispatch (glob)
133 124 */mercurial/dispatch.py:* in runcommand (glob)
134 125 */mercurial/dispatch.py:* in _runcommand (glob)
135 126 */mercurial/dispatch.py:* in <lambda> (glob)
136 127 */mercurial/util.py:* in check (glob)
137 128 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
138 $ hg blackbox -l 9
139 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: revset "oldstyle" uses list instead of smartset
140 (compatibility will be dropped after Mercurial-3.9, update your code.) at: *mercurial/revset.py:* (mfunc) (glob)
141 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> log -r *oldstyle()* -T *{rev}\n* exited 0 after * seconds (glob)
129 $ hg blackbox -l 7
142 130 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
143 131 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
144 132 (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
145 133 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
146 134 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
147 135 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
148 136 (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
149 137 */hg:* in <module> (glob)
150 138 */mercurial/dispatch.py:* in run (glob)
151 139 */mercurial/dispatch.py:* in dispatch (glob)
152 140 */mercurial/dispatch.py:* in _runcatch (glob)
153 141 */mercurial/dispatch.py:* in callcatch (glob)
154 142 */mercurial/scmutil.py* in callcatch (glob)
155 143 */mercurial/dispatch.py:* in _runcatchfunc (glob)
156 144 */mercurial/dispatch.py:* in _dispatch (glob)
157 145 */mercurial/dispatch.py:* in runcommand (glob)
158 146 */mercurial/dispatch.py:* in _runcommand (glob)
159 147 */mercurial/dispatch.py:* in <lambda> (glob)
160 148 */mercurial/util.py:* in check (glob)
161 149 $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
162 150 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
163 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 9
151 1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
164 152
165 153 Test programming error failure:
166 154
167 155 $ hg buggytransaction 2>&1 | egrep -v '^ '
168 156 ** Unknown exception encountered with possibly-broken third-party extension buggylocking
169 157 ** which supports versions unknown of Mercurial.
170 158 ** Please disable buggylocking and try your action again.
171 159 ** If that fixes the bug please report it to the extension author.
172 160 ** Python * (glob)
173 161 ** Mercurial Distributed SCM (*) (glob)
174 162 ** Extensions loaded: * (glob)
175 163 Traceback (most recent call last):
176 164 mercurial.error.ProgrammingError: transaction requires locking
177 165
178 166 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now