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