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