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