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