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