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