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