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