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