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