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