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