##// END OF EJS Templates
revset: use {force,}bytestr to fix some %r formatting issues...
Augie Fackler -
r36598:04e50037 default
parent child Browse files
Show More
@@ -1,2215 +1,2217
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 raise error.ParseError(_('invalid match pattern: %s') % e)
1014 raise error.ParseError(
1015 _('invalid match pattern: %s') % util.forcebytestr(e))
1015 1016
1016 1017 def matches(x):
1017 1018 c = repo[x]
1018 1019 for e in c.files() + [c.user(), c.description()]:
1019 1020 if gr.search(e):
1020 1021 return True
1021 1022 return False
1022 1023
1023 1024 return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))
1024 1025
1025 1026 @predicate('_matchfiles', safe=True)
1026 1027 def _matchfiles(repo, subset, x):
1027 1028 # _matchfiles takes a revset list of prefixed arguments:
1028 1029 #
1029 1030 # [p:foo, i:bar, x:baz]
1030 1031 #
1031 1032 # builds a match object from them and filters subset. Allowed
1032 1033 # prefixes are 'p:' for regular patterns, 'i:' for include
1033 1034 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1034 1035 # a revision identifier, or the empty string to reference the
1035 1036 # working directory, from which the match object is
1036 1037 # initialized. Use 'd:' to set the default matching mode, default
1037 1038 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1038 1039
1039 1040 l = getargs(x, 1, -1, "_matchfiles requires at least one argument")
1040 1041 pats, inc, exc = [], [], []
1041 1042 rev, default = None, None
1042 1043 for arg in l:
1043 1044 s = getstring(arg, "_matchfiles requires string arguments")
1044 1045 prefix, value = s[:2], s[2:]
1045 1046 if prefix == 'p:':
1046 1047 pats.append(value)
1047 1048 elif prefix == 'i:':
1048 1049 inc.append(value)
1049 1050 elif prefix == 'x:':
1050 1051 exc.append(value)
1051 1052 elif prefix == 'r:':
1052 1053 if rev is not None:
1053 1054 raise error.ParseError('_matchfiles expected at most one '
1054 1055 'revision')
1055 1056 if value == '': # empty means working directory
1056 1057 rev = node.wdirrev
1057 1058 else:
1058 1059 rev = value
1059 1060 elif prefix == 'd:':
1060 1061 if default is not None:
1061 1062 raise error.ParseError('_matchfiles expected at most one '
1062 1063 'default mode')
1063 1064 default = value
1064 1065 else:
1065 1066 raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)
1066 1067 if not default:
1067 1068 default = 'glob'
1068 1069 hasset = any(matchmod.patkind(p) == 'set' for p in pats + inc + exc)
1069 1070
1070 1071 mcache = [None]
1071 1072
1072 1073 # This directly read the changelog data as creating changectx for all
1073 1074 # revisions is quite expensive.
1074 1075 getfiles = repo.changelog.readfiles
1075 1076 wdirrev = node.wdirrev
1076 1077 def matches(x):
1077 1078 if x == wdirrev:
1078 1079 files = repo[x].files()
1079 1080 else:
1080 1081 files = getfiles(x)
1081 1082
1082 1083 if not mcache[0] or (hasset and rev is None):
1083 1084 r = x if rev is None else rev
1084 1085 mcache[0] = matchmod.match(repo.root, repo.getcwd(), pats,
1085 1086 include=inc, exclude=exc, ctx=repo[r],
1086 1087 default=default)
1087 1088 m = mcache[0]
1088 1089
1089 1090 for f in files:
1090 1091 if m(f):
1091 1092 return True
1092 1093 return False
1093 1094
1094 1095 return subset.filter(matches,
1095 1096 condrepr=('<matchfiles patterns=%r, include=%r '
1096 1097 'exclude=%r, default=%r, rev=%r>',
1097 1098 pats, inc, exc, default, rev))
1098 1099
1099 1100 @predicate('file(pattern)', safe=True, weight=10)
1100 1101 def hasfile(repo, subset, x):
1101 1102 """Changesets affecting files matched by pattern.
1102 1103
1103 1104 For a faster but less accurate result, consider using ``filelog()``
1104 1105 instead.
1105 1106
1106 1107 This predicate uses ``glob:`` as the default kind of pattern.
1107 1108 """
1108 1109 # i18n: "file" is a keyword
1109 1110 pat = getstring(x, _("file requires a pattern"))
1110 1111 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1111 1112
1112 1113 @predicate('head()', safe=True)
1113 1114 def head(repo, subset, x):
1114 1115 """Changeset is a named branch head.
1115 1116 """
1116 1117 # i18n: "head" is a keyword
1117 1118 getargs(x, 0, 0, _("head takes no arguments"))
1118 1119 hs = set()
1119 1120 cl = repo.changelog
1120 1121 for ls in repo.branchmap().itervalues():
1121 1122 hs.update(cl.rev(h) for h in ls)
1122 1123 return subset & baseset(hs)
1123 1124
1124 1125 @predicate('heads(set)', safe=True)
1125 1126 def heads(repo, subset, x):
1126 1127 """Members of set with no children in set.
1127 1128 """
1128 1129 s = getset(repo, subset, x)
1129 1130 ps = parents(repo, subset, x)
1130 1131 return s - ps
1131 1132
1132 1133 @predicate('hidden()', safe=True)
1133 1134 def hidden(repo, subset, x):
1134 1135 """Hidden changesets.
1135 1136 """
1136 1137 # i18n: "hidden" is a keyword
1137 1138 getargs(x, 0, 0, _("hidden takes no arguments"))
1138 1139 hiddenrevs = repoview.filterrevs(repo, 'visible')
1139 1140 return subset & hiddenrevs
1140 1141
1141 1142 @predicate('keyword(string)', safe=True, weight=10)
1142 1143 def keyword(repo, subset, x):
1143 1144 """Search commit message, user name, and names of changed files for
1144 1145 string. The match is case-insensitive.
1145 1146
1146 1147 For a regular expression or case sensitive search of these fields, use
1147 1148 ``grep(regex)``.
1148 1149 """
1149 1150 # i18n: "keyword" is a keyword
1150 1151 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1151 1152
1152 1153 def matches(r):
1153 1154 c = repo[r]
1154 1155 return any(kw in encoding.lower(t)
1155 1156 for t in c.files() + [c.user(), c.description()])
1156 1157
1157 1158 return subset.filter(matches, condrepr=('<keyword %r>', kw))
1158 1159
1159 1160 @predicate('limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1160 1161 def limit(repo, subset, x, order):
1161 1162 """First n members of set, defaulting to 1, starting from offset.
1162 1163 """
1163 1164 args = getargsdict(x, 'limit', 'set n offset')
1164 1165 if 'set' not in args:
1165 1166 # i18n: "limit" is a keyword
1166 1167 raise error.ParseError(_("limit requires one to three arguments"))
1167 1168 # i18n: "limit" is a keyword
1168 1169 lim = getinteger(args.get('n'), _("limit expects a number"), default=1)
1169 1170 if lim < 0:
1170 1171 raise error.ParseError(_("negative number to select"))
1171 1172 # i18n: "limit" is a keyword
1172 1173 ofs = getinteger(args.get('offset'), _("limit expects a number"), default=0)
1173 1174 if ofs < 0:
1174 1175 raise error.ParseError(_("negative offset"))
1175 1176 os = getset(repo, fullreposet(repo), args['set'])
1176 1177 ls = os.slice(ofs, ofs + lim)
1177 1178 if order == followorder and lim > 1:
1178 1179 return subset & ls
1179 1180 return ls & subset
1180 1181
1181 1182 @predicate('last(set, [n])', safe=True, takeorder=True)
1182 1183 def last(repo, subset, x, order):
1183 1184 """Last n members of set, defaulting to 1.
1184 1185 """
1185 1186 # i18n: "last" is a keyword
1186 1187 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1187 1188 lim = 1
1188 1189 if len(l) == 2:
1189 1190 # i18n: "last" is a keyword
1190 1191 lim = getinteger(l[1], _("last expects a number"))
1191 1192 if lim < 0:
1192 1193 raise error.ParseError(_("negative number to select"))
1193 1194 os = getset(repo, fullreposet(repo), l[0])
1194 1195 os.reverse()
1195 1196 ls = os.slice(0, lim)
1196 1197 if order == followorder and lim > 1:
1197 1198 return subset & ls
1198 1199 ls.reverse()
1199 1200 return ls & subset
1200 1201
1201 1202 @predicate('max(set)', safe=True)
1202 1203 def maxrev(repo, subset, x):
1203 1204 """Changeset with highest revision number in set.
1204 1205 """
1205 1206 os = getset(repo, fullreposet(repo), x)
1206 1207 try:
1207 1208 m = os.max()
1208 1209 if m in subset:
1209 1210 return baseset([m], datarepr=('<max %r, %r>', subset, os))
1210 1211 except ValueError:
1211 1212 # os.max() throws a ValueError when the collection is empty.
1212 1213 # Same as python's max().
1213 1214 pass
1214 1215 return baseset(datarepr=('<max %r, %r>', subset, os))
1215 1216
1216 1217 @predicate('merge()', safe=True)
1217 1218 def merge(repo, subset, x):
1218 1219 """Changeset is a merge changeset.
1219 1220 """
1220 1221 # i18n: "merge" is a keyword
1221 1222 getargs(x, 0, 0, _("merge takes no arguments"))
1222 1223 cl = repo.changelog
1223 1224 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1,
1224 1225 condrepr='<merge>')
1225 1226
1226 1227 @predicate('branchpoint()', safe=True)
1227 1228 def branchpoint(repo, subset, x):
1228 1229 """Changesets with more than one child.
1229 1230 """
1230 1231 # i18n: "branchpoint" is a keyword
1231 1232 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1232 1233 cl = repo.changelog
1233 1234 if not subset:
1234 1235 return baseset()
1235 1236 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1236 1237 # (and if it is not, it should.)
1237 1238 baserev = min(subset)
1238 1239 parentscount = [0]*(len(repo) - baserev)
1239 1240 for r in cl.revs(start=baserev + 1):
1240 1241 for p in cl.parentrevs(r):
1241 1242 if p >= baserev:
1242 1243 parentscount[p - baserev] += 1
1243 1244 return subset.filter(lambda r: parentscount[r - baserev] > 1,
1244 1245 condrepr='<branchpoint>')
1245 1246
1246 1247 @predicate('min(set)', safe=True)
1247 1248 def minrev(repo, subset, x):
1248 1249 """Changeset with lowest revision number in set.
1249 1250 """
1250 1251 os = getset(repo, fullreposet(repo), x)
1251 1252 try:
1252 1253 m = os.min()
1253 1254 if m in subset:
1254 1255 return baseset([m], datarepr=('<min %r, %r>', subset, os))
1255 1256 except ValueError:
1256 1257 # os.min() throws a ValueError when the collection is empty.
1257 1258 # Same as python's min().
1258 1259 pass
1259 1260 return baseset(datarepr=('<min %r, %r>', subset, os))
1260 1261
1261 1262 @predicate('modifies(pattern)', safe=True, weight=30)
1262 1263 def modifies(repo, subset, x):
1263 1264 """Changesets modifying files matched by pattern.
1264 1265
1265 1266 The pattern without explicit kind like ``glob:`` is expected to be
1266 1267 relative to the current directory and match against a file or a
1267 1268 directory.
1268 1269 """
1269 1270 # i18n: "modifies" is a keyword
1270 1271 pat = getstring(x, _("modifies requires a pattern"))
1271 1272 return checkstatus(repo, subset, pat, 0)
1272 1273
1273 1274 @predicate('named(namespace)')
1274 1275 def named(repo, subset, x):
1275 1276 """The changesets in a given namespace.
1276 1277
1277 1278 Pattern matching is supported for `namespace`. See
1278 1279 :hg:`help revisions.patterns`.
1279 1280 """
1280 1281 # i18n: "named" is a keyword
1281 1282 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1282 1283
1283 1284 ns = getstring(args[0],
1284 1285 # i18n: "named" is a keyword
1285 1286 _('the argument to named must be a string'))
1286 1287 kind, pattern, matcher = util.stringmatcher(ns)
1287 1288 namespaces = set()
1288 1289 if kind == 'literal':
1289 1290 if pattern not in repo.names:
1290 1291 raise error.RepoLookupError(_("namespace '%s' does not exist")
1291 1292 % ns)
1292 1293 namespaces.add(repo.names[pattern])
1293 1294 else:
1294 1295 for name, ns in repo.names.iteritems():
1295 1296 if matcher(name):
1296 1297 namespaces.add(ns)
1297 1298 if not namespaces:
1298 1299 raise error.RepoLookupError(_("no namespace exists"
1299 1300 " that match '%s'") % pattern)
1300 1301
1301 1302 names = set()
1302 1303 for ns in namespaces:
1303 1304 for name in ns.listnames(repo):
1304 1305 if name not in ns.deprecated:
1305 1306 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1306 1307
1307 1308 names -= {node.nullrev}
1308 1309 return subset & names
1309 1310
1310 1311 @predicate('id(string)', safe=True)
1311 1312 def node_(repo, subset, x):
1312 1313 """Revision non-ambiguously specified by the given hex string prefix.
1313 1314 """
1314 1315 # i18n: "id" is a keyword
1315 1316 l = getargs(x, 1, 1, _("id requires one argument"))
1316 1317 # i18n: "id" is a keyword
1317 1318 n = getstring(l[0], _("id requires a string"))
1318 1319 if len(n) == 40:
1319 1320 try:
1320 1321 rn = repo.changelog.rev(node.bin(n))
1321 1322 except error.WdirUnsupported:
1322 1323 rn = node.wdirrev
1323 1324 except (LookupError, TypeError):
1324 1325 rn = None
1325 1326 else:
1326 1327 rn = None
1327 1328 try:
1328 1329 pm = repo.changelog._partialmatch(n)
1329 1330 if pm is not None:
1330 1331 rn = repo.changelog.rev(pm)
1331 1332 except error.WdirUnsupported:
1332 1333 rn = node.wdirrev
1333 1334
1334 1335 if rn is None:
1335 1336 return baseset()
1336 1337 result = baseset([rn])
1337 1338 return result & subset
1338 1339
1339 1340 @predicate('obsolete()', safe=True)
1340 1341 def obsolete(repo, subset, x):
1341 1342 """Mutable changeset with a newer version."""
1342 1343 # i18n: "obsolete" is a keyword
1343 1344 getargs(x, 0, 0, _("obsolete takes no arguments"))
1344 1345 obsoletes = obsmod.getrevs(repo, 'obsolete')
1345 1346 return subset & obsoletes
1346 1347
1347 1348 @predicate('only(set, [set])', safe=True)
1348 1349 def only(repo, subset, x):
1349 1350 """Changesets that are ancestors of the first set that are not ancestors
1350 1351 of any other head in the repo. If a second set is specified, the result
1351 1352 is ancestors of the first set that are not ancestors of the second set
1352 1353 (i.e. ::<set1> - ::<set2>).
1353 1354 """
1354 1355 cl = repo.changelog
1355 1356 # i18n: "only" is a keyword
1356 1357 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1357 1358 include = getset(repo, fullreposet(repo), args[0])
1358 1359 if len(args) == 1:
1359 1360 if not include:
1360 1361 return baseset()
1361 1362
1362 1363 descendants = set(dagop.revdescendants(repo, include, False))
1363 1364 exclude = [rev for rev in cl.headrevs()
1364 1365 if not rev in descendants and not rev in include]
1365 1366 else:
1366 1367 exclude = getset(repo, fullreposet(repo), args[1])
1367 1368
1368 1369 results = set(cl.findmissingrevs(common=exclude, heads=include))
1369 1370 # XXX we should turn this into a baseset instead of a set, smartset may do
1370 1371 # some optimizations from the fact this is a baseset.
1371 1372 return subset & results
1372 1373
1373 1374 @predicate('origin([set])', safe=True)
1374 1375 def origin(repo, subset, x):
1375 1376 """
1376 1377 Changesets that were specified as a source for the grafts, transplants or
1377 1378 rebases that created the given revisions. Omitting the optional set is the
1378 1379 same as passing all(). If a changeset created by these operations is itself
1379 1380 specified as a source for one of these operations, only the source changeset
1380 1381 for the first operation is selected.
1381 1382 """
1382 1383 if x is not None:
1383 1384 dests = getset(repo, fullreposet(repo), x)
1384 1385 else:
1385 1386 dests = fullreposet(repo)
1386 1387
1387 1388 def _firstsrc(rev):
1388 1389 src = _getrevsource(repo, rev)
1389 1390 if src is None:
1390 1391 return None
1391 1392
1392 1393 while True:
1393 1394 prev = _getrevsource(repo, src)
1394 1395
1395 1396 if prev is None:
1396 1397 return src
1397 1398 src = prev
1398 1399
1399 1400 o = {_firstsrc(r) for r in dests}
1400 1401 o -= {None}
1401 1402 # XXX we should turn this into a baseset instead of a set, smartset may do
1402 1403 # some optimizations from the fact this is a baseset.
1403 1404 return subset & o
1404 1405
1405 1406 @predicate('outgoing([path])', safe=False, weight=10)
1406 1407 def outgoing(repo, subset, x):
1407 1408 """Changesets not found in the specified destination repository, or the
1408 1409 default push location.
1409 1410 """
1410 1411 # Avoid cycles.
1411 1412 from . import (
1412 1413 discovery,
1413 1414 hg,
1414 1415 )
1415 1416 # i18n: "outgoing" is a keyword
1416 1417 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1417 1418 # i18n: "outgoing" is a keyword
1418 1419 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1419 1420 if not dest:
1420 1421 # ui.paths.getpath() explicitly tests for None, not just a boolean
1421 1422 dest = None
1422 1423 path = repo.ui.paths.getpath(dest, default=('default-push', 'default'))
1423 1424 if not path:
1424 1425 raise error.Abort(_('default repository not configured!'),
1425 1426 hint=_("see 'hg help config.paths'"))
1426 1427 dest = path.pushloc or path.loc
1427 1428 branches = path.branch, []
1428 1429
1429 1430 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1430 1431 if revs:
1431 1432 revs = [repo.lookup(rev) for rev in revs]
1432 1433 other = hg.peer(repo, {}, dest)
1433 1434 repo.ui.pushbuffer()
1434 1435 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1435 1436 repo.ui.popbuffer()
1436 1437 cl = repo.changelog
1437 1438 o = {cl.rev(r) for r in outgoing.missing}
1438 1439 return subset & o
1439 1440
1440 1441 @predicate('p1([set])', safe=True)
1441 1442 def p1(repo, subset, x):
1442 1443 """First parent of changesets in set, or the working directory.
1443 1444 """
1444 1445 if x is None:
1445 1446 p = repo[x].p1().rev()
1446 1447 if p >= 0:
1447 1448 return subset & baseset([p])
1448 1449 return baseset()
1449 1450
1450 1451 ps = set()
1451 1452 cl = repo.changelog
1452 1453 for r in getset(repo, fullreposet(repo), x):
1453 1454 try:
1454 1455 ps.add(cl.parentrevs(r)[0])
1455 1456 except error.WdirUnsupported:
1456 1457 ps.add(repo[r].parents()[0].rev())
1457 1458 ps -= {node.nullrev}
1458 1459 # XXX we should turn this into a baseset instead of a set, smartset may do
1459 1460 # some optimizations from the fact this is a baseset.
1460 1461 return subset & ps
1461 1462
1462 1463 @predicate('p2([set])', safe=True)
1463 1464 def p2(repo, subset, x):
1464 1465 """Second parent of changesets in set, or the working directory.
1465 1466 """
1466 1467 if x is None:
1467 1468 ps = repo[x].parents()
1468 1469 try:
1469 1470 p = ps[1].rev()
1470 1471 if p >= 0:
1471 1472 return subset & baseset([p])
1472 1473 return baseset()
1473 1474 except IndexError:
1474 1475 return baseset()
1475 1476
1476 1477 ps = set()
1477 1478 cl = repo.changelog
1478 1479 for r in getset(repo, fullreposet(repo), x):
1479 1480 try:
1480 1481 ps.add(cl.parentrevs(r)[1])
1481 1482 except error.WdirUnsupported:
1482 1483 parents = repo[r].parents()
1483 1484 if len(parents) == 2:
1484 1485 ps.add(parents[1])
1485 1486 ps -= {node.nullrev}
1486 1487 # XXX we should turn this into a baseset instead of a set, smartset may do
1487 1488 # some optimizations from the fact this is a baseset.
1488 1489 return subset & ps
1489 1490
1490 1491 def parentpost(repo, subset, x, order):
1491 1492 return p1(repo, subset, x)
1492 1493
1493 1494 @predicate('parents([set])', safe=True)
1494 1495 def parents(repo, subset, x):
1495 1496 """
1496 1497 The set of all parents for all changesets in set, or the working directory.
1497 1498 """
1498 1499 if x is None:
1499 1500 ps = set(p.rev() for p in repo[x].parents())
1500 1501 else:
1501 1502 ps = set()
1502 1503 cl = repo.changelog
1503 1504 up = ps.update
1504 1505 parentrevs = cl.parentrevs
1505 1506 for r in getset(repo, fullreposet(repo), x):
1506 1507 try:
1507 1508 up(parentrevs(r))
1508 1509 except error.WdirUnsupported:
1509 1510 up(p.rev() for p in repo[r].parents())
1510 1511 ps -= {node.nullrev}
1511 1512 return subset & ps
1512 1513
1513 1514 def _phase(repo, subset, *targets):
1514 1515 """helper to select all rev in <targets> phases"""
1515 1516 return repo._phasecache.getrevset(repo, targets, subset)
1516 1517
1517 1518 @predicate('draft()', safe=True)
1518 1519 def draft(repo, subset, x):
1519 1520 """Changeset in draft phase."""
1520 1521 # i18n: "draft" is a keyword
1521 1522 getargs(x, 0, 0, _("draft takes no arguments"))
1522 1523 target = phases.draft
1523 1524 return _phase(repo, subset, target)
1524 1525
1525 1526 @predicate('secret()', safe=True)
1526 1527 def secret(repo, subset, x):
1527 1528 """Changeset in secret phase."""
1528 1529 # i18n: "secret" is a keyword
1529 1530 getargs(x, 0, 0, _("secret takes no arguments"))
1530 1531 target = phases.secret
1531 1532 return _phase(repo, subset, target)
1532 1533
1533 1534 def parentspec(repo, subset, x, n, order):
1534 1535 """``set^0``
1535 1536 The set.
1536 1537 ``set^1`` (or ``set^``), ``set^2``
1537 1538 First or second parent, respectively, of all changesets in set.
1538 1539 """
1539 1540 try:
1540 1541 n = int(n[1])
1541 1542 if n not in (0, 1, 2):
1542 1543 raise ValueError
1543 1544 except (TypeError, ValueError):
1544 1545 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1545 1546 ps = set()
1546 1547 cl = repo.changelog
1547 1548 for r in getset(repo, fullreposet(repo), x):
1548 1549 if n == 0:
1549 1550 ps.add(r)
1550 1551 elif n == 1:
1551 1552 try:
1552 1553 ps.add(cl.parentrevs(r)[0])
1553 1554 except error.WdirUnsupported:
1554 1555 ps.add(repo[r].parents()[0].rev())
1555 1556 else:
1556 1557 try:
1557 1558 parents = cl.parentrevs(r)
1558 1559 if parents[1] != node.nullrev:
1559 1560 ps.add(parents[1])
1560 1561 except error.WdirUnsupported:
1561 1562 parents = repo[r].parents()
1562 1563 if len(parents) == 2:
1563 1564 ps.add(parents[1].rev())
1564 1565 return subset & ps
1565 1566
1566 1567 @predicate('present(set)', safe=True, takeorder=True)
1567 1568 def present(repo, subset, x, order):
1568 1569 """An empty set, if any revision in set isn't found; otherwise,
1569 1570 all revisions in set.
1570 1571
1571 1572 If any of specified revisions is not present in the local repository,
1572 1573 the query is normally aborted. But this predicate allows the query
1573 1574 to continue even in such cases.
1574 1575 """
1575 1576 try:
1576 1577 return getset(repo, subset, x, order)
1577 1578 except error.RepoLookupError:
1578 1579 return baseset()
1579 1580
1580 1581 # for internal use
1581 1582 @predicate('_notpublic', safe=True)
1582 1583 def _notpublic(repo, subset, x):
1583 1584 getargs(x, 0, 0, "_notpublic takes no arguments")
1584 1585 return _phase(repo, subset, phases.draft, phases.secret)
1585 1586
1586 1587 # for internal use
1587 1588 @predicate('_phaseandancestors(phasename, set)', safe=True)
1588 1589 def _phaseandancestors(repo, subset, x):
1589 1590 # equivalent to (phasename() & ancestors(set)) but more efficient
1590 1591 # phasename could be one of 'draft', 'secret', or '_notpublic'
1591 1592 args = getargs(x, 2, 2, "_phaseandancestors requires two arguments")
1592 1593 phasename = getsymbol(args[0])
1593 1594 s = getset(repo, fullreposet(repo), args[1])
1594 1595
1595 1596 draft = phases.draft
1596 1597 secret = phases.secret
1597 1598 phasenamemap = {
1598 1599 '_notpublic': draft,
1599 1600 'draft': draft, # follow secret's ancestors
1600 1601 'secret': secret,
1601 1602 }
1602 1603 if phasename not in phasenamemap:
1603 1604 raise error.ParseError('%r is not a valid phasename' % phasename)
1604 1605
1605 1606 minimalphase = phasenamemap[phasename]
1606 1607 getphase = repo._phasecache.phase
1607 1608
1608 1609 def cutfunc(rev):
1609 1610 return getphase(repo, rev) < minimalphase
1610 1611
1611 1612 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
1612 1613
1613 1614 if phasename == 'draft': # need to remove secret changesets
1614 1615 revs = revs.filter(lambda r: getphase(repo, r) == draft)
1615 1616 return subset & revs
1616 1617
1617 1618 @predicate('public()', safe=True)
1618 1619 def public(repo, subset, x):
1619 1620 """Changeset in public phase."""
1620 1621 # i18n: "public" is a keyword
1621 1622 getargs(x, 0, 0, _("public takes no arguments"))
1622 1623 return _phase(repo, subset, phases.public)
1623 1624
1624 1625 @predicate('remote([id [,path]])', safe=False)
1625 1626 def remote(repo, subset, x):
1626 1627 """Local revision that corresponds to the given identifier in a
1627 1628 remote repository, if present. Here, the '.' identifier is a
1628 1629 synonym for the current local branch.
1629 1630 """
1630 1631
1631 1632 from . import hg # avoid start-up nasties
1632 1633 # i18n: "remote" is a keyword
1633 1634 l = getargs(x, 0, 2, _("remote takes zero, one, or two arguments"))
1634 1635
1635 1636 q = '.'
1636 1637 if len(l) > 0:
1637 1638 # i18n: "remote" is a keyword
1638 1639 q = getstring(l[0], _("remote requires a string id"))
1639 1640 if q == '.':
1640 1641 q = repo['.'].branch()
1641 1642
1642 1643 dest = ''
1643 1644 if len(l) > 1:
1644 1645 # i18n: "remote" is a keyword
1645 1646 dest = getstring(l[1], _("remote requires a repository path"))
1646 1647 dest = repo.ui.expandpath(dest or 'default')
1647 1648 dest, branches = hg.parseurl(dest)
1648 1649 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1649 1650 if revs:
1650 1651 revs = [repo.lookup(rev) for rev in revs]
1651 1652 other = hg.peer(repo, {}, dest)
1652 1653 n = other.lookup(q)
1653 1654 if n in repo:
1654 1655 r = repo[n].rev()
1655 1656 if r in subset:
1656 1657 return baseset([r])
1657 1658 return baseset()
1658 1659
1659 1660 @predicate('removes(pattern)', safe=True, weight=30)
1660 1661 def removes(repo, subset, x):
1661 1662 """Changesets which remove files matching pattern.
1662 1663
1663 1664 The pattern without explicit kind like ``glob:`` is expected to be
1664 1665 relative to the current directory and match against a file or a
1665 1666 directory.
1666 1667 """
1667 1668 # i18n: "removes" is a keyword
1668 1669 pat = getstring(x, _("removes requires a pattern"))
1669 1670 return checkstatus(repo, subset, pat, 2)
1670 1671
1671 1672 @predicate('rev(number)', safe=True)
1672 1673 def rev(repo, subset, x):
1673 1674 """Revision with the given numeric identifier.
1674 1675 """
1675 1676 # i18n: "rev" is a keyword
1676 1677 l = getargs(x, 1, 1, _("rev requires one argument"))
1677 1678 try:
1678 1679 # i18n: "rev" is a keyword
1679 1680 l = int(getstring(l[0], _("rev requires a number")))
1680 1681 except (TypeError, ValueError):
1681 1682 # i18n: "rev" is a keyword
1682 1683 raise error.ParseError(_("rev expects a number"))
1683 1684 if l not in repo.changelog and l not in (node.nullrev, node.wdirrev):
1684 1685 return baseset()
1685 1686 return subset & baseset([l])
1686 1687
1687 1688 @predicate('matching(revision [, field])', safe=True)
1688 1689 def matching(repo, subset, x):
1689 1690 """Changesets in which a given set of fields match the set of fields in the
1690 1691 selected revision or set.
1691 1692
1692 1693 To match more than one field pass the list of fields to match separated
1693 1694 by spaces (e.g. ``author description``).
1694 1695
1695 1696 Valid fields are most regular revision fields and some special fields.
1696 1697
1697 1698 Regular revision fields are ``description``, ``author``, ``branch``,
1698 1699 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1699 1700 and ``diff``.
1700 1701 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1701 1702 contents of the revision. Two revisions matching their ``diff`` will
1702 1703 also match their ``files``.
1703 1704
1704 1705 Special fields are ``summary`` and ``metadata``:
1705 1706 ``summary`` matches the first line of the description.
1706 1707 ``metadata`` is equivalent to matching ``description user date``
1707 1708 (i.e. it matches the main metadata fields).
1708 1709
1709 1710 ``metadata`` is the default field which is used when no fields are
1710 1711 specified. You can match more than one field at a time.
1711 1712 """
1712 1713 # i18n: "matching" is a keyword
1713 1714 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1714 1715
1715 1716 revs = getset(repo, fullreposet(repo), l[0])
1716 1717
1717 1718 fieldlist = ['metadata']
1718 1719 if len(l) > 1:
1719 1720 fieldlist = getstring(l[1],
1720 1721 # i18n: "matching" is a keyword
1721 1722 _("matching requires a string "
1722 1723 "as its second argument")).split()
1723 1724
1724 1725 # Make sure that there are no repeated fields,
1725 1726 # expand the 'special' 'metadata' field type
1726 1727 # and check the 'files' whenever we check the 'diff'
1727 1728 fields = []
1728 1729 for field in fieldlist:
1729 1730 if field == 'metadata':
1730 1731 fields += ['user', 'description', 'date']
1731 1732 elif field == 'diff':
1732 1733 # a revision matching the diff must also match the files
1733 1734 # since matching the diff is very costly, make sure to
1734 1735 # also match the files first
1735 1736 fields += ['files', 'diff']
1736 1737 else:
1737 1738 if field == 'author':
1738 1739 field = 'user'
1739 1740 fields.append(field)
1740 1741 fields = set(fields)
1741 1742 if 'summary' in fields and 'description' in fields:
1742 1743 # If a revision matches its description it also matches its summary
1743 1744 fields.discard('summary')
1744 1745
1745 1746 # We may want to match more than one field
1746 1747 # Not all fields take the same amount of time to be matched
1747 1748 # Sort the selected fields in order of increasing matching cost
1748 1749 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1749 1750 'files', 'description', 'substate', 'diff']
1750 1751 def fieldkeyfunc(f):
1751 1752 try:
1752 1753 return fieldorder.index(f)
1753 1754 except ValueError:
1754 1755 # assume an unknown field is very costly
1755 1756 return len(fieldorder)
1756 1757 fields = list(fields)
1757 1758 fields.sort(key=fieldkeyfunc)
1758 1759
1759 1760 # Each field will be matched with its own "getfield" function
1760 1761 # which will be added to the getfieldfuncs array of functions
1761 1762 getfieldfuncs = []
1762 1763 _funcs = {
1763 1764 'user': lambda r: repo[r].user(),
1764 1765 'branch': lambda r: repo[r].branch(),
1765 1766 'date': lambda r: repo[r].date(),
1766 1767 'description': lambda r: repo[r].description(),
1767 1768 'files': lambda r: repo[r].files(),
1768 1769 'parents': lambda r: repo[r].parents(),
1769 1770 'phase': lambda r: repo[r].phase(),
1770 1771 'substate': lambda r: repo[r].substate,
1771 1772 'summary': lambda r: repo[r].description().splitlines()[0],
1772 1773 'diff': lambda r: list(repo[r].diff(git=True),)
1773 1774 }
1774 1775 for info in fields:
1775 1776 getfield = _funcs.get(info, None)
1776 1777 if getfield is None:
1777 1778 raise error.ParseError(
1778 1779 # i18n: "matching" is a keyword
1779 1780 _("unexpected field name passed to matching: %s") % info)
1780 1781 getfieldfuncs.append(getfield)
1781 1782 # convert the getfield array of functions into a "getinfo" function
1782 1783 # which returns an array of field values (or a single value if there
1783 1784 # is only one field to match)
1784 1785 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1785 1786
1786 1787 def matches(x):
1787 1788 for rev in revs:
1788 1789 target = getinfo(rev)
1789 1790 match = True
1790 1791 for n, f in enumerate(getfieldfuncs):
1791 1792 if target[n] != f(x):
1792 1793 match = False
1793 1794 if match:
1794 1795 return True
1795 1796 return False
1796 1797
1797 1798 return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
1798 1799
1799 1800 @predicate('reverse(set)', safe=True, takeorder=True, weight=0)
1800 1801 def reverse(repo, subset, x, order):
1801 1802 """Reverse order of set.
1802 1803 """
1803 1804 l = getset(repo, subset, x, order)
1804 1805 if order == defineorder:
1805 1806 l.reverse()
1806 1807 return l
1807 1808
1808 1809 @predicate('roots(set)', safe=True)
1809 1810 def roots(repo, subset, x):
1810 1811 """Changesets in set with no parent changeset in set.
1811 1812 """
1812 1813 s = getset(repo, fullreposet(repo), x)
1813 1814 parents = repo.changelog.parentrevs
1814 1815 def filter(r):
1815 1816 for p in parents(r):
1816 1817 if 0 <= p and p in s:
1817 1818 return False
1818 1819 return True
1819 1820 return subset & s.filter(filter, condrepr='<roots>')
1820 1821
1821 1822 _sortkeyfuncs = {
1822 1823 'rev': lambda c: c.rev(),
1823 1824 'branch': lambda c: c.branch(),
1824 1825 'desc': lambda c: c.description(),
1825 1826 'user': lambda c: c.user(),
1826 1827 'author': lambda c: c.user(),
1827 1828 'date': lambda c: c.date()[0],
1828 1829 }
1829 1830
1830 1831 def _getsortargs(x):
1831 1832 """Parse sort options into (set, [(key, reverse)], opts)"""
1832 1833 args = getargsdict(x, 'sort', 'set keys topo.firstbranch')
1833 1834 if 'set' not in args:
1834 1835 # i18n: "sort" is a keyword
1835 1836 raise error.ParseError(_('sort requires one or two arguments'))
1836 1837 keys = "rev"
1837 1838 if 'keys' in args:
1838 1839 # i18n: "sort" is a keyword
1839 1840 keys = getstring(args['keys'], _("sort spec must be a string"))
1840 1841
1841 1842 keyflags = []
1842 1843 for k in keys.split():
1843 1844 fk = k
1844 1845 reverse = (k.startswith('-'))
1845 1846 if reverse:
1846 1847 k = k[1:]
1847 1848 if k not in _sortkeyfuncs and k != 'topo':
1848 raise error.ParseError(_("unknown sort key %r") % fk)
1849 raise error.ParseError(
1850 _("unknown sort key %r") % pycompat.bytestr(fk))
1849 1851 keyflags.append((k, reverse))
1850 1852
1851 1853 if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):
1852 1854 # i18n: "topo" is a keyword
1853 1855 raise error.ParseError(_('topo sort order cannot be combined '
1854 1856 'with other sort keys'))
1855 1857
1856 1858 opts = {}
1857 1859 if 'topo.firstbranch' in args:
1858 1860 if any(k == 'topo' for k, reverse in keyflags):
1859 1861 opts['topo.firstbranch'] = args['topo.firstbranch']
1860 1862 else:
1861 1863 # i18n: "topo" and "topo.firstbranch" are keywords
1862 1864 raise error.ParseError(_('topo.firstbranch can only be used '
1863 1865 'when using the topo sort key'))
1864 1866
1865 1867 return args['set'], keyflags, opts
1866 1868
1867 1869 @predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True,
1868 1870 weight=10)
1869 1871 def sort(repo, subset, x, order):
1870 1872 """Sort set by keys. The default sort order is ascending, specify a key
1871 1873 as ``-key`` to sort in descending order.
1872 1874
1873 1875 The keys can be:
1874 1876
1875 1877 - ``rev`` for the revision number,
1876 1878 - ``branch`` for the branch name,
1877 1879 - ``desc`` for the commit message (description),
1878 1880 - ``user`` for user name (``author`` can be used as an alias),
1879 1881 - ``date`` for the commit date
1880 1882 - ``topo`` for a reverse topographical sort
1881 1883
1882 1884 The ``topo`` sort order cannot be combined with other sort keys. This sort
1883 1885 takes one optional argument, ``topo.firstbranch``, which takes a revset that
1884 1886 specifies what topographical branches to prioritize in the sort.
1885 1887
1886 1888 """
1887 1889 s, keyflags, opts = _getsortargs(x)
1888 1890 revs = getset(repo, subset, s, order)
1889 1891
1890 1892 if not keyflags or order != defineorder:
1891 1893 return revs
1892 1894 if len(keyflags) == 1 and keyflags[0][0] == "rev":
1893 1895 revs.sort(reverse=keyflags[0][1])
1894 1896 return revs
1895 1897 elif keyflags[0][0] == "topo":
1896 1898 firstbranch = ()
1897 1899 if 'topo.firstbranch' in opts:
1898 1900 firstbranch = getset(repo, subset, opts['topo.firstbranch'])
1899 1901 revs = baseset(dagop.toposort(revs, repo.changelog.parentrevs,
1900 1902 firstbranch),
1901 1903 istopo=True)
1902 1904 if keyflags[0][1]:
1903 1905 revs.reverse()
1904 1906 return revs
1905 1907
1906 1908 # sort() is guaranteed to be stable
1907 1909 ctxs = [repo[r] for r in revs]
1908 1910 for k, reverse in reversed(keyflags):
1909 1911 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
1910 1912 return baseset([c.rev() for c in ctxs])
1911 1913
1912 1914 @predicate('subrepo([pattern])')
1913 1915 def subrepo(repo, subset, x):
1914 1916 """Changesets that add, modify or remove the given subrepo. If no subrepo
1915 1917 pattern is named, any subrepo changes are returned.
1916 1918 """
1917 1919 # i18n: "subrepo" is a keyword
1918 1920 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1919 1921 pat = None
1920 1922 if len(args) != 0:
1921 1923 pat = getstring(args[0], _("subrepo requires a pattern"))
1922 1924
1923 1925 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1924 1926
1925 1927 def submatches(names):
1926 1928 k, p, m = util.stringmatcher(pat)
1927 1929 for name in names:
1928 1930 if m(name):
1929 1931 yield name
1930 1932
1931 1933 def matches(x):
1932 1934 c = repo[x]
1933 1935 s = repo.status(c.p1().node(), c.node(), match=m)
1934 1936
1935 1937 if pat is None:
1936 1938 return s.added or s.modified or s.removed
1937 1939
1938 1940 if s.added:
1939 1941 return any(submatches(c.substate.keys()))
1940 1942
1941 1943 if s.modified:
1942 1944 subs = set(c.p1().substate.keys())
1943 1945 subs.update(c.substate.keys())
1944 1946
1945 1947 for path in submatches(subs):
1946 1948 if c.p1().substate.get(path) != c.substate.get(path):
1947 1949 return True
1948 1950
1949 1951 if s.removed:
1950 1952 return any(submatches(c.p1().substate.keys()))
1951 1953
1952 1954 return False
1953 1955
1954 1956 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
1955 1957
1956 1958 def _mapbynodefunc(repo, s, f):
1957 1959 """(repo, smartset, [node] -> [node]) -> smartset
1958 1960
1959 1961 Helper method to map a smartset to another smartset given a function only
1960 1962 talking about nodes. Handles converting between rev numbers and nodes, and
1961 1963 filtering.
1962 1964 """
1963 1965 cl = repo.unfiltered().changelog
1964 1966 torev = cl.rev
1965 1967 tonode = cl.node
1966 1968 nodemap = cl.nodemap
1967 1969 result = set(torev(n) for n in f(tonode(r) for r in s) if n in nodemap)
1968 1970 return smartset.baseset(result - repo.changelog.filteredrevs)
1969 1971
1970 1972 @predicate('successors(set)', safe=True)
1971 1973 def successors(repo, subset, x):
1972 1974 """All successors for set, including the given set themselves"""
1973 1975 s = getset(repo, fullreposet(repo), x)
1974 1976 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
1975 1977 d = _mapbynodefunc(repo, s, f)
1976 1978 return subset & d
1977 1979
1978 1980 def _substringmatcher(pattern, casesensitive=True):
1979 1981 kind, pattern, matcher = util.stringmatcher(pattern,
1980 1982 casesensitive=casesensitive)
1981 1983 if kind == 'literal':
1982 1984 if not casesensitive:
1983 1985 pattern = encoding.lower(pattern)
1984 1986 matcher = lambda s: pattern in encoding.lower(s)
1985 1987 else:
1986 1988 matcher = lambda s: pattern in s
1987 1989 return kind, pattern, matcher
1988 1990
1989 1991 @predicate('tag([name])', safe=True)
1990 1992 def tag(repo, subset, x):
1991 1993 """The specified tag by name, or all tagged revisions if no name is given.
1992 1994
1993 1995 Pattern matching is supported for `name`. See
1994 1996 :hg:`help revisions.patterns`.
1995 1997 """
1996 1998 # i18n: "tag" is a keyword
1997 1999 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1998 2000 cl = repo.changelog
1999 2001 if args:
2000 2002 pattern = getstring(args[0],
2001 2003 # i18n: "tag" is a keyword
2002 2004 _('the argument to tag must be a string'))
2003 2005 kind, pattern, matcher = util.stringmatcher(pattern)
2004 2006 if kind == 'literal':
2005 2007 # avoid resolving all tags
2006 2008 tn = repo._tagscache.tags.get(pattern, None)
2007 2009 if tn is None:
2008 2010 raise error.RepoLookupError(_("tag '%s' does not exist")
2009 2011 % pattern)
2010 2012 s = {repo[tn].rev()}
2011 2013 else:
2012 2014 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2013 2015 else:
2014 2016 s = {cl.rev(n) for t, n in repo.tagslist() if t != 'tip'}
2015 2017 return subset & s
2016 2018
2017 2019 @predicate('tagged', safe=True)
2018 2020 def tagged(repo, subset, x):
2019 2021 return tag(repo, subset, x)
2020 2022
2021 2023 @predicate('orphan()', safe=True)
2022 2024 def orphan(repo, subset, x):
2023 2025 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
2024 2026 """
2025 2027 # i18n: "orphan" is a keyword
2026 2028 getargs(x, 0, 0, _("orphan takes no arguments"))
2027 2029 orphan = obsmod.getrevs(repo, 'orphan')
2028 2030 return subset & orphan
2029 2031
2030 2032
2031 2033 @predicate('user(string)', safe=True, weight=10)
2032 2034 def user(repo, subset, x):
2033 2035 """User name contains string. The match is case-insensitive.
2034 2036
2035 2037 Pattern matching is supported for `string`. See
2036 2038 :hg:`help revisions.patterns`.
2037 2039 """
2038 2040 return author(repo, subset, x)
2039 2041
2040 2042 @predicate('wdir()', safe=True, weight=0)
2041 2043 def wdir(repo, subset, x):
2042 2044 """Working directory. (EXPERIMENTAL)"""
2043 2045 # i18n: "wdir" is a keyword
2044 2046 getargs(x, 0, 0, _("wdir takes no arguments"))
2045 2047 if node.wdirrev in subset or isinstance(subset, fullreposet):
2046 2048 return baseset([node.wdirrev])
2047 2049 return baseset()
2048 2050
2049 2051 def _orderedlist(repo, subset, x):
2050 2052 s = getstring(x, "internal error")
2051 2053 if not s:
2052 2054 return baseset()
2053 2055 # remove duplicates here. it's difficult for caller to deduplicate sets
2054 2056 # because different symbols can point to the same rev.
2055 2057 cl = repo.changelog
2056 2058 ls = []
2057 2059 seen = set()
2058 2060 for t in s.split('\0'):
2059 2061 try:
2060 2062 # fast path for integer revision
2061 2063 r = int(t)
2062 2064 if str(r) != t or r not in cl:
2063 2065 raise ValueError
2064 2066 revs = [r]
2065 2067 except ValueError:
2066 2068 revs = stringset(repo, subset, t, defineorder)
2067 2069
2068 2070 for r in revs:
2069 2071 if r in seen:
2070 2072 continue
2071 2073 if (r in subset
2072 2074 or r == node.nullrev and isinstance(subset, fullreposet)):
2073 2075 ls.append(r)
2074 2076 seen.add(r)
2075 2077 return baseset(ls)
2076 2078
2077 2079 # for internal use
2078 2080 @predicate('_list', safe=True, takeorder=True)
2079 2081 def _list(repo, subset, x, order):
2080 2082 if order == followorder:
2081 2083 # slow path to take the subset order
2082 2084 return subset & _orderedlist(repo, fullreposet(repo), x)
2083 2085 else:
2084 2086 return _orderedlist(repo, subset, x)
2085 2087
2086 2088 def _orderedintlist(repo, subset, x):
2087 2089 s = getstring(x, "internal error")
2088 2090 if not s:
2089 2091 return baseset()
2090 2092 ls = [int(r) for r in s.split('\0')]
2091 2093 s = subset
2092 2094 return baseset([r for r in ls if r in s])
2093 2095
2094 2096 # for internal use
2095 2097 @predicate('_intlist', safe=True, takeorder=True, weight=0)
2096 2098 def _intlist(repo, subset, x, order):
2097 2099 if order == followorder:
2098 2100 # slow path to take the subset order
2099 2101 return subset & _orderedintlist(repo, fullreposet(repo), x)
2100 2102 else:
2101 2103 return _orderedintlist(repo, subset, x)
2102 2104
2103 2105 def _orderedhexlist(repo, subset, x):
2104 2106 s = getstring(x, "internal error")
2105 2107 if not s:
2106 2108 return baseset()
2107 2109 cl = repo.changelog
2108 2110 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2109 2111 s = subset
2110 2112 return baseset([r for r in ls if r in s])
2111 2113
2112 2114 # for internal use
2113 2115 @predicate('_hexlist', safe=True, takeorder=True)
2114 2116 def _hexlist(repo, subset, x, order):
2115 2117 if order == followorder:
2116 2118 # slow path to take the subset order
2117 2119 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2118 2120 else:
2119 2121 return _orderedhexlist(repo, subset, x)
2120 2122
2121 2123 methods = {
2122 2124 "range": rangeset,
2123 2125 "rangeall": rangeall,
2124 2126 "rangepre": rangepre,
2125 2127 "rangepost": rangepost,
2126 2128 "dagrange": dagrange,
2127 2129 "string": stringset,
2128 2130 "symbol": stringset,
2129 2131 "and": andset,
2130 2132 "andsmally": andsmallyset,
2131 2133 "or": orset,
2132 2134 "not": notset,
2133 2135 "difference": differenceset,
2134 2136 "relation": relationset,
2135 2137 "relsubscript": relsubscriptset,
2136 2138 "subscript": subscriptset,
2137 2139 "list": listset,
2138 2140 "keyvalue": keyvaluepair,
2139 2141 "func": func,
2140 2142 "ancestor": ancestorspec,
2141 2143 "parent": parentspec,
2142 2144 "parentpost": parentpost,
2143 2145 }
2144 2146
2145 2147 def posttreebuilthook(tree, repo):
2146 2148 # hook for extensions to execute code on the optimized tree
2147 2149 pass
2148 2150
2149 2151 def match(ui, spec, repo=None):
2150 2152 """Create a matcher for a single revision spec"""
2151 2153 return matchany(ui, [spec], repo=repo)
2152 2154
2153 2155 def matchany(ui, specs, repo=None, localalias=None):
2154 2156 """Create a matcher that will include any revisions matching one of the
2155 2157 given specs
2156 2158
2157 2159 If localalias is not None, it is a dict {name: definitionstring}. It takes
2158 2160 precedence over [revsetalias] config section.
2159 2161 """
2160 2162 if not specs:
2161 2163 def mfunc(repo, subset=None):
2162 2164 return baseset()
2163 2165 return mfunc
2164 2166 if not all(specs):
2165 2167 raise error.ParseError(_("empty query"))
2166 2168 lookup = None
2167 2169 if repo:
2168 2170 lookup = repo.__contains__
2169 2171 if len(specs) == 1:
2170 2172 tree = revsetlang.parse(specs[0], lookup)
2171 2173 else:
2172 2174 tree = ('or',
2173 2175 ('list',) + tuple(revsetlang.parse(s, lookup) for s in specs))
2174 2176
2175 2177 aliases = []
2176 2178 warn = None
2177 2179 if ui:
2178 2180 aliases.extend(ui.configitems('revsetalias'))
2179 2181 warn = ui.warn
2180 2182 if localalias:
2181 2183 aliases.extend(localalias.items())
2182 2184 if aliases:
2183 2185 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2184 2186 tree = revsetlang.foldconcat(tree)
2185 2187 tree = revsetlang.analyze(tree)
2186 2188 tree = revsetlang.optimize(tree)
2187 2189 posttreebuilthook(tree, repo)
2188 2190 return makematcher(tree)
2189 2191
2190 2192 def makematcher(tree):
2191 2193 """Create a matcher from an evaluatable tree"""
2192 2194 def mfunc(repo, subset=None, order=None):
2193 2195 if order is None:
2194 2196 if subset is None:
2195 2197 order = defineorder # 'x'
2196 2198 else:
2197 2199 order = followorder # 'subset & x'
2198 2200 if subset is None:
2199 2201 subset = fullreposet(repo)
2200 2202 return getset(repo, subset, tree, order)
2201 2203 return mfunc
2202 2204
2203 2205 def loadpredicate(ui, extname, registrarobj):
2204 2206 """Load revset predicates from specified registrarobj
2205 2207 """
2206 2208 for name, func in registrarobj._table.iteritems():
2207 2209 symbols[name] = func
2208 2210 if func._safe:
2209 2211 safesymbols.add(name)
2210 2212
2211 2213 # load built-in predicates explicitly to setup safesymbols
2212 2214 loadpredicate(None, None, predicate)
2213 2215
2214 2216 # tell hggettext to extract docstrings from these functions:
2215 2217 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now