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