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