##// END OF EJS Templates
revsets: use '&' instead of '.filter' in head...
Pierre-Yves David -
r25634:1ddefcfc default
parent child Browse files
Show More
@@ -1,3629 +1,3628 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 import re
9 9 import parser, util, error, hbisect, phases
10 10 import node
11 11 import heapq
12 12 import match as matchmod
13 13 from i18n import _
14 14 import encoding
15 15 import obsolete as obsmod
16 16 import pathutil
17 17 import repoview
18 18
19 19 def _revancestors(repo, revs, followfirst):
20 20 """Like revlog.ancestors(), but supports followfirst."""
21 21 if followfirst:
22 22 cut = 1
23 23 else:
24 24 cut = None
25 25 cl = repo.changelog
26 26
27 27 def iterate():
28 28 revs.sort(reverse=True)
29 29 irevs = iter(revs)
30 30 h = []
31 31
32 32 inputrev = next(irevs, None)
33 33 if inputrev is not None:
34 34 heapq.heappush(h, -inputrev)
35 35
36 36 seen = set()
37 37 while h:
38 38 current = -heapq.heappop(h)
39 39 if current == inputrev:
40 40 inputrev = next(irevs, None)
41 41 if inputrev is not None:
42 42 heapq.heappush(h, -inputrev)
43 43 if current not in seen:
44 44 seen.add(current)
45 45 yield current
46 46 for parent in cl.parentrevs(current)[:cut]:
47 47 if parent != node.nullrev:
48 48 heapq.heappush(h, -parent)
49 49
50 50 return generatorset(iterate(), iterasc=False)
51 51
52 52 def _revdescendants(repo, revs, followfirst):
53 53 """Like revlog.descendants() but supports followfirst."""
54 54 if followfirst:
55 55 cut = 1
56 56 else:
57 57 cut = None
58 58
59 59 def iterate():
60 60 cl = repo.changelog
61 61 # XXX this should be 'parentset.min()' assuming 'parentset' is a
62 62 # smartset (and if it is not, it should.)
63 63 first = min(revs)
64 64 nullrev = node.nullrev
65 65 if first == nullrev:
66 66 # Are there nodes with a null first parent and a non-null
67 67 # second one? Maybe. Do we care? Probably not.
68 68 for i in cl:
69 69 yield i
70 70 else:
71 71 seen = set(revs)
72 72 for i in cl.revs(first + 1):
73 73 for x in cl.parentrevs(i)[:cut]:
74 74 if x != nullrev and x in seen:
75 75 seen.add(i)
76 76 yield i
77 77 break
78 78
79 79 return generatorset(iterate(), iterasc=True)
80 80
81 81 def _revsbetween(repo, roots, heads):
82 82 """Return all paths between roots and heads, inclusive of both endpoint
83 83 sets."""
84 84 if not roots:
85 85 return baseset()
86 86 parentrevs = repo.changelog.parentrevs
87 87 visit = list(heads)
88 88 reachable = set()
89 89 seen = {}
90 90 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
91 91 # (and if it is not, it should.)
92 92 minroot = min(roots)
93 93 roots = set(roots)
94 94 # prefetch all the things! (because python is slow)
95 95 reached = reachable.add
96 96 dovisit = visit.append
97 97 nextvisit = visit.pop
98 98 # open-code the post-order traversal due to the tiny size of
99 99 # sys.getrecursionlimit()
100 100 while visit:
101 101 rev = nextvisit()
102 102 if rev in roots:
103 103 reached(rev)
104 104 parents = parentrevs(rev)
105 105 seen[rev] = parents
106 106 for parent in parents:
107 107 if parent >= minroot and parent not in seen:
108 108 dovisit(parent)
109 109 if not reachable:
110 110 return baseset()
111 111 for rev in sorted(seen):
112 112 for parent in seen[rev]:
113 113 if parent in reachable:
114 114 reached(rev)
115 115 return baseset(sorted(reachable))
116 116
117 117 elements = {
118 118 "(": (21, ("group", 1, ")"), ("func", 1, ")")),
119 119 "##": (20, None, ("_concat", 20)),
120 120 "~": (18, None, ("ancestor", 18)),
121 121 "^": (18, None, ("parent", 18), ("parentpost", 18)),
122 122 "-": (5, ("negate", 19), ("minus", 5)),
123 123 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
124 124 ("dagrangepost", 17)),
125 125 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
126 126 ("dagrangepost", 17)),
127 127 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
128 128 "not": (10, ("not", 10)),
129 129 "!": (10, ("not", 10)),
130 130 "and": (5, None, ("and", 5)),
131 131 "&": (5, None, ("and", 5)),
132 132 "%": (5, None, ("only", 5), ("onlypost", 5)),
133 133 "or": (4, None, ("or", 4)),
134 134 "|": (4, None, ("or", 4)),
135 135 "+": (4, None, ("or", 4)),
136 136 ",": (2, None, ("list", 2)),
137 137 ")": (0, None, None),
138 138 "symbol": (0, ("symbol",), None),
139 139 "string": (0, ("string",), None),
140 140 "end": (0, None, None),
141 141 }
142 142
143 143 keywords = set(['and', 'or', 'not'])
144 144
145 145 # default set of valid characters for the initial letter of symbols
146 146 _syminitletters = set(c for c in [chr(i) for i in xrange(256)]
147 147 if c.isalnum() or c in '._@' or ord(c) > 127)
148 148
149 149 # default set of valid characters for non-initial letters of symbols
150 150 _symletters = set(c for c in [chr(i) for i in xrange(256)]
151 151 if c.isalnum() or c in '-._/@' or ord(c) > 127)
152 152
153 153 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
154 154 '''
155 155 Parse a revset statement into a stream of tokens
156 156
157 157 ``syminitletters`` is the set of valid characters for the initial
158 158 letter of symbols.
159 159
160 160 By default, character ``c`` is recognized as valid for initial
161 161 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
162 162
163 163 ``symletters`` is the set of valid characters for non-initial
164 164 letters of symbols.
165 165
166 166 By default, character ``c`` is recognized as valid for non-initial
167 167 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
168 168
169 169 Check that @ is a valid unquoted token character (issue3686):
170 170 >>> list(tokenize("@::"))
171 171 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
172 172
173 173 '''
174 174 if syminitletters is None:
175 175 syminitletters = _syminitletters
176 176 if symletters is None:
177 177 symletters = _symletters
178 178
179 179 pos, l = 0, len(program)
180 180 while pos < l:
181 181 c = program[pos]
182 182 if c.isspace(): # skip inter-token whitespace
183 183 pass
184 184 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
185 185 yield ('::', None, pos)
186 186 pos += 1 # skip ahead
187 187 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
188 188 yield ('..', None, pos)
189 189 pos += 1 # skip ahead
190 190 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
191 191 yield ('##', None, pos)
192 192 pos += 1 # skip ahead
193 193 elif c in "():,-|&+!~^%": # handle simple operators
194 194 yield (c, None, pos)
195 195 elif (c in '"\'' or c == 'r' and
196 196 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
197 197 if c == 'r':
198 198 pos += 1
199 199 c = program[pos]
200 200 decode = lambda x: x
201 201 else:
202 202 decode = lambda x: x.decode('string-escape')
203 203 pos += 1
204 204 s = pos
205 205 while pos < l: # find closing quote
206 206 d = program[pos]
207 207 if d == '\\': # skip over escaped characters
208 208 pos += 2
209 209 continue
210 210 if d == c:
211 211 yield ('string', decode(program[s:pos]), s)
212 212 break
213 213 pos += 1
214 214 else:
215 215 raise error.ParseError(_("unterminated string"), s)
216 216 # gather up a symbol/keyword
217 217 elif c in syminitletters:
218 218 s = pos
219 219 pos += 1
220 220 while pos < l: # find end of symbol
221 221 d = program[pos]
222 222 if d not in symletters:
223 223 break
224 224 if d == '.' and program[pos - 1] == '.': # special case for ..
225 225 pos -= 1
226 226 break
227 227 pos += 1
228 228 sym = program[s:pos]
229 229 if sym in keywords: # operator keywords
230 230 yield (sym, None, s)
231 231 elif '-' in sym:
232 232 # some jerk gave us foo-bar-baz, try to check if it's a symbol
233 233 if lookup and lookup(sym):
234 234 # looks like a real symbol
235 235 yield ('symbol', sym, s)
236 236 else:
237 237 # looks like an expression
238 238 parts = sym.split('-')
239 239 for p in parts[:-1]:
240 240 if p: # possible consecutive -
241 241 yield ('symbol', p, s)
242 242 s += len(p)
243 243 yield ('-', None, pos)
244 244 s += 1
245 245 if parts[-1]: # possible trailing -
246 246 yield ('symbol', parts[-1], s)
247 247 else:
248 248 yield ('symbol', sym, s)
249 249 pos -= 1
250 250 else:
251 251 raise error.ParseError(_("syntax error in revset '%s'") %
252 252 program, pos)
253 253 pos += 1
254 254 yield ('end', None, pos)
255 255
256 256 def parseerrordetail(inst):
257 257 """Compose error message from specified ParseError object
258 258 """
259 259 if len(inst.args) > 1:
260 260 return _('at %s: %s') % (inst.args[1], inst.args[0])
261 261 else:
262 262 return inst.args[0]
263 263
264 264 # helpers
265 265
266 266 def getstring(x, err):
267 267 if x and (x[0] == 'string' or x[0] == 'symbol'):
268 268 return x[1]
269 269 raise error.ParseError(err)
270 270
271 271 def getlist(x):
272 272 if not x:
273 273 return []
274 274 if x[0] == 'list':
275 275 return getlist(x[1]) + [x[2]]
276 276 return [x]
277 277
278 278 def getargs(x, min, max, err):
279 279 l = getlist(x)
280 280 if len(l) < min or (max >= 0 and len(l) > max):
281 281 raise error.ParseError(err)
282 282 return l
283 283
284 284 def isvalidsymbol(tree):
285 285 """Examine whether specified ``tree`` is valid ``symbol`` or not
286 286 """
287 287 return tree[0] == 'symbol' and len(tree) > 1
288 288
289 289 def getsymbol(tree):
290 290 """Get symbol name from valid ``symbol`` in ``tree``
291 291
292 292 This assumes that ``tree`` is already examined by ``isvalidsymbol``.
293 293 """
294 294 return tree[1]
295 295
296 296 def isvalidfunc(tree):
297 297 """Examine whether specified ``tree`` is valid ``func`` or not
298 298 """
299 299 return tree[0] == 'func' and len(tree) > 1 and isvalidsymbol(tree[1])
300 300
301 301 def getfuncname(tree):
302 302 """Get function name from valid ``func`` in ``tree``
303 303
304 304 This assumes that ``tree`` is already examined by ``isvalidfunc``.
305 305 """
306 306 return getsymbol(tree[1])
307 307
308 308 def getfuncargs(tree):
309 309 """Get list of function arguments from valid ``func`` in ``tree``
310 310
311 311 This assumes that ``tree`` is already examined by ``isvalidfunc``.
312 312 """
313 313 if len(tree) > 2:
314 314 return getlist(tree[2])
315 315 else:
316 316 return []
317 317
318 318 def getset(repo, subset, x):
319 319 if not x:
320 320 raise error.ParseError(_("missing argument"))
321 321 s = methods[x[0]](repo, subset, *x[1:])
322 322 if util.safehasattr(s, 'isascending'):
323 323 return s
324 324 if (repo.ui.configbool('devel', 'all-warnings')
325 325 or repo.ui.configbool('devel', 'old-revset')):
326 326 # else case should not happen, because all non-func are internal,
327 327 # ignoring for now.
328 328 if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
329 329 repo.ui.develwarn('revset "%s" use list instead of smartset, '
330 330 '(upgrade your code)' % x[1][1])
331 331 return baseset(s)
332 332
333 333 def _getrevsource(repo, r):
334 334 extra = repo[r].extra()
335 335 for label in ('source', 'transplant_source', 'rebase_source'):
336 336 if label in extra:
337 337 try:
338 338 return repo[extra[label]].rev()
339 339 except error.RepoLookupError:
340 340 pass
341 341 return None
342 342
343 343 # operator methods
344 344
345 345 def stringset(repo, subset, x):
346 346 x = repo[x].rev()
347 347 if (x in subset
348 348 or x == node.nullrev and isinstance(subset, fullreposet)):
349 349 return baseset([x])
350 350 return baseset()
351 351
352 352 def rangeset(repo, subset, x, y):
353 353 m = getset(repo, fullreposet(repo), x)
354 354 n = getset(repo, fullreposet(repo), y)
355 355
356 356 if not m or not n:
357 357 return baseset()
358 358 m, n = m.first(), n.last()
359 359
360 360 if m < n:
361 361 r = spanset(repo, m, n + 1)
362 362 else:
363 363 r = spanset(repo, m, n - 1)
364 364 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
365 365 # necessary to ensure we preserve the order in subset.
366 366 #
367 367 # This has performance implication, carrying the sorting over when possible
368 368 # would be more efficient.
369 369 return r & subset
370 370
371 371 def dagrange(repo, subset, x, y):
372 372 r = fullreposet(repo)
373 373 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
374 374 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
375 375 # necessary to ensure we preserve the order in subset.
376 376 return xs & subset
377 377
378 378 def andset(repo, subset, x, y):
379 379 return getset(repo, getset(repo, subset, x), y)
380 380
381 381 def orset(repo, subset, *xs):
382 382 rs = [getset(repo, subset, x) for x in xs]
383 383 return _combinesets(rs)
384 384
385 385 def notset(repo, subset, x):
386 386 return subset - getset(repo, subset, x)
387 387
388 388 def listset(repo, subset, a, b):
389 389 raise error.ParseError(_("can't use a list in this context"))
390 390
391 391 def func(repo, subset, a, b):
392 392 if a[0] == 'symbol' and a[1] in symbols:
393 393 return symbols[a[1]](repo, subset, b)
394 394
395 395 keep = lambda fn: getattr(fn, '__doc__', None) is not None
396 396
397 397 syms = [s for (s, fn) in symbols.items() if keep(fn)]
398 398 raise error.UnknownIdentifier(a[1], syms)
399 399
400 400 # functions
401 401
402 402 def adds(repo, subset, x):
403 403 """``adds(pattern)``
404 404 Changesets that add a file matching pattern.
405 405
406 406 The pattern without explicit kind like ``glob:`` is expected to be
407 407 relative to the current directory and match against a file or a
408 408 directory.
409 409 """
410 410 # i18n: "adds" is a keyword
411 411 pat = getstring(x, _("adds requires a pattern"))
412 412 return checkstatus(repo, subset, pat, 1)
413 413
414 414 def ancestor(repo, subset, x):
415 415 """``ancestor(*changeset)``
416 416 A greatest common ancestor of the changesets.
417 417
418 418 Accepts 0 or more changesets.
419 419 Will return empty list when passed no args.
420 420 Greatest common ancestor of a single changeset is that changeset.
421 421 """
422 422 # i18n: "ancestor" is a keyword
423 423 l = getlist(x)
424 424 rl = fullreposet(repo)
425 425 anc = None
426 426
427 427 # (getset(repo, rl, i) for i in l) generates a list of lists
428 428 for revs in (getset(repo, rl, i) for i in l):
429 429 for r in revs:
430 430 if anc is None:
431 431 anc = repo[r]
432 432 else:
433 433 anc = anc.ancestor(repo[r])
434 434
435 435 if anc is not None and anc.rev() in subset:
436 436 return baseset([anc.rev()])
437 437 return baseset()
438 438
439 439 def _ancestors(repo, subset, x, followfirst=False):
440 440 heads = getset(repo, fullreposet(repo), x)
441 441 if not heads:
442 442 return baseset()
443 443 s = _revancestors(repo, heads, followfirst)
444 444 return subset & s
445 445
446 446 def ancestors(repo, subset, x):
447 447 """``ancestors(set)``
448 448 Changesets that are ancestors of a changeset in set.
449 449 """
450 450 return _ancestors(repo, subset, x)
451 451
452 452 def _firstancestors(repo, subset, x):
453 453 # ``_firstancestors(set)``
454 454 # Like ``ancestors(set)`` but follows only the first parents.
455 455 return _ancestors(repo, subset, x, followfirst=True)
456 456
457 457 def ancestorspec(repo, subset, x, n):
458 458 """``set~n``
459 459 Changesets that are the Nth ancestor (first parents only) of a changeset
460 460 in set.
461 461 """
462 462 try:
463 463 n = int(n[1])
464 464 except (TypeError, ValueError):
465 465 raise error.ParseError(_("~ expects a number"))
466 466 ps = set()
467 467 cl = repo.changelog
468 468 for r in getset(repo, fullreposet(repo), x):
469 469 for i in range(n):
470 470 r = cl.parentrevs(r)[0]
471 471 ps.add(r)
472 472 return subset & ps
473 473
474 474 def author(repo, subset, x):
475 475 """``author(string)``
476 476 Alias for ``user(string)``.
477 477 """
478 478 # i18n: "author" is a keyword
479 479 n = encoding.lower(getstring(x, _("author requires a string")))
480 480 kind, pattern, matcher = _substringmatcher(n)
481 481 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
482 482
483 483 def bisect(repo, subset, x):
484 484 """``bisect(string)``
485 485 Changesets marked in the specified bisect status:
486 486
487 487 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
488 488 - ``goods``, ``bads`` : csets topologically good/bad
489 489 - ``range`` : csets taking part in the bisection
490 490 - ``pruned`` : csets that are goods, bads or skipped
491 491 - ``untested`` : csets whose fate is yet unknown
492 492 - ``ignored`` : csets ignored due to DAG topology
493 493 - ``current`` : the cset currently being bisected
494 494 """
495 495 # i18n: "bisect" is a keyword
496 496 status = getstring(x, _("bisect requires a string")).lower()
497 497 state = set(hbisect.get(repo, status))
498 498 return subset & state
499 499
500 500 # Backward-compatibility
501 501 # - no help entry so that we do not advertise it any more
502 502 def bisected(repo, subset, x):
503 503 return bisect(repo, subset, x)
504 504
505 505 def bookmark(repo, subset, x):
506 506 """``bookmark([name])``
507 507 The named bookmark or all bookmarks.
508 508
509 509 If `name` starts with `re:`, the remainder of the name is treated as
510 510 a regular expression. To match a bookmark that actually starts with `re:`,
511 511 use the prefix `literal:`.
512 512 """
513 513 # i18n: "bookmark" is a keyword
514 514 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
515 515 if args:
516 516 bm = getstring(args[0],
517 517 # i18n: "bookmark" is a keyword
518 518 _('the argument to bookmark must be a string'))
519 519 kind, pattern, matcher = _stringmatcher(bm)
520 520 bms = set()
521 521 if kind == 'literal':
522 522 bmrev = repo._bookmarks.get(pattern, None)
523 523 if not bmrev:
524 524 raise error.RepoLookupError(_("bookmark '%s' does not exist")
525 525 % bm)
526 526 bms.add(repo[bmrev].rev())
527 527 else:
528 528 matchrevs = set()
529 529 for name, bmrev in repo._bookmarks.iteritems():
530 530 if matcher(name):
531 531 matchrevs.add(bmrev)
532 532 if not matchrevs:
533 533 raise error.RepoLookupError(_("no bookmarks exist"
534 534 " that match '%s'") % pattern)
535 535 for bmrev in matchrevs:
536 536 bms.add(repo[bmrev].rev())
537 537 else:
538 538 bms = set([repo[r].rev()
539 539 for r in repo._bookmarks.values()])
540 540 bms -= set([node.nullrev])
541 541 return subset & bms
542 542
543 543 def branch(repo, subset, x):
544 544 """``branch(string or set)``
545 545 All changesets belonging to the given branch or the branches of the given
546 546 changesets.
547 547
548 548 If `string` starts with `re:`, the remainder of the name is treated as
549 549 a regular expression. To match a branch that actually starts with `re:`,
550 550 use the prefix `literal:`.
551 551 """
552 552 getbi = repo.revbranchcache().branchinfo
553 553
554 554 try:
555 555 b = getstring(x, '')
556 556 except error.ParseError:
557 557 # not a string, but another revspec, e.g. tip()
558 558 pass
559 559 else:
560 560 kind, pattern, matcher = _stringmatcher(b)
561 561 if kind == 'literal':
562 562 # note: falls through to the revspec case if no branch with
563 563 # this name exists
564 564 if pattern in repo.branchmap():
565 565 return subset.filter(lambda r: matcher(getbi(r)[0]))
566 566 else:
567 567 return subset.filter(lambda r: matcher(getbi(r)[0]))
568 568
569 569 s = getset(repo, fullreposet(repo), x)
570 570 b = set()
571 571 for r in s:
572 572 b.add(getbi(r)[0])
573 573 c = s.__contains__
574 574 return subset.filter(lambda r: c(r) or getbi(r)[0] in b)
575 575
576 576 def bumped(repo, subset, x):
577 577 """``bumped()``
578 578 Mutable changesets marked as successors of public changesets.
579 579
580 580 Only non-public and non-obsolete changesets can be `bumped`.
581 581 """
582 582 # i18n: "bumped" is a keyword
583 583 getargs(x, 0, 0, _("bumped takes no arguments"))
584 584 bumped = obsmod.getrevs(repo, 'bumped')
585 585 return subset & bumped
586 586
587 587 def bundle(repo, subset, x):
588 588 """``bundle()``
589 589 Changesets in the bundle.
590 590
591 591 Bundle must be specified by the -R option."""
592 592
593 593 try:
594 594 bundlerevs = repo.changelog.bundlerevs
595 595 except AttributeError:
596 596 raise util.Abort(_("no bundle provided - specify with -R"))
597 597 return subset & bundlerevs
598 598
599 599 def checkstatus(repo, subset, pat, field):
600 600 hasset = matchmod.patkind(pat) == 'set'
601 601
602 602 mcache = [None]
603 603 def matches(x):
604 604 c = repo[x]
605 605 if not mcache[0] or hasset:
606 606 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
607 607 m = mcache[0]
608 608 fname = None
609 609 if not m.anypats() and len(m.files()) == 1:
610 610 fname = m.files()[0]
611 611 if fname is not None:
612 612 if fname not in c.files():
613 613 return False
614 614 else:
615 615 for f in c.files():
616 616 if m(f):
617 617 break
618 618 else:
619 619 return False
620 620 files = repo.status(c.p1().node(), c.node())[field]
621 621 if fname is not None:
622 622 if fname in files:
623 623 return True
624 624 else:
625 625 for f in files:
626 626 if m(f):
627 627 return True
628 628
629 629 return subset.filter(matches)
630 630
631 631 def _children(repo, narrow, parentset):
632 632 if not parentset:
633 633 return baseset()
634 634 cs = set()
635 635 pr = repo.changelog.parentrevs
636 636 minrev = parentset.min()
637 637 for r in narrow:
638 638 if r <= minrev:
639 639 continue
640 640 for p in pr(r):
641 641 if p in parentset:
642 642 cs.add(r)
643 643 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
644 644 # This does not break because of other fullreposet misbehavior.
645 645 return baseset(cs)
646 646
647 647 def children(repo, subset, x):
648 648 """``children(set)``
649 649 Child changesets of changesets in set.
650 650 """
651 651 s = getset(repo, fullreposet(repo), x)
652 652 cs = _children(repo, subset, s)
653 653 return subset & cs
654 654
655 655 def closed(repo, subset, x):
656 656 """``closed()``
657 657 Changeset is closed.
658 658 """
659 659 # i18n: "closed" is a keyword
660 660 getargs(x, 0, 0, _("closed takes no arguments"))
661 661 return subset.filter(lambda r: repo[r].closesbranch())
662 662
663 663 def contains(repo, subset, x):
664 664 """``contains(pattern)``
665 665 The revision's manifest contains a file matching pattern (but might not
666 666 modify it). See :hg:`help patterns` for information about file patterns.
667 667
668 668 The pattern without explicit kind like ``glob:`` is expected to be
669 669 relative to the current directory and match against a file exactly
670 670 for efficiency.
671 671 """
672 672 # i18n: "contains" is a keyword
673 673 pat = getstring(x, _("contains requires a pattern"))
674 674
675 675 def matches(x):
676 676 if not matchmod.patkind(pat):
677 677 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
678 678 if pats in repo[x]:
679 679 return True
680 680 else:
681 681 c = repo[x]
682 682 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
683 683 for f in c.manifest():
684 684 if m(f):
685 685 return True
686 686 return False
687 687
688 688 return subset.filter(matches)
689 689
690 690 def converted(repo, subset, x):
691 691 """``converted([id])``
692 692 Changesets converted from the given identifier in the old repository if
693 693 present, or all converted changesets if no identifier is specified.
694 694 """
695 695
696 696 # There is exactly no chance of resolving the revision, so do a simple
697 697 # string compare and hope for the best
698 698
699 699 rev = None
700 700 # i18n: "converted" is a keyword
701 701 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
702 702 if l:
703 703 # i18n: "converted" is a keyword
704 704 rev = getstring(l[0], _('converted requires a revision'))
705 705
706 706 def _matchvalue(r):
707 707 source = repo[r].extra().get('convert_revision', None)
708 708 return source is not None and (rev is None or source.startswith(rev))
709 709
710 710 return subset.filter(lambda r: _matchvalue(r))
711 711
712 712 def date(repo, subset, x):
713 713 """``date(interval)``
714 714 Changesets within the interval, see :hg:`help dates`.
715 715 """
716 716 # i18n: "date" is a keyword
717 717 ds = getstring(x, _("date requires a string"))
718 718 dm = util.matchdate(ds)
719 719 return subset.filter(lambda x: dm(repo[x].date()[0]))
720 720
721 721 def desc(repo, subset, x):
722 722 """``desc(string)``
723 723 Search commit message for string. The match is case-insensitive.
724 724 """
725 725 # i18n: "desc" is a keyword
726 726 ds = encoding.lower(getstring(x, _("desc requires a string")))
727 727
728 728 def matches(x):
729 729 c = repo[x]
730 730 return ds in encoding.lower(c.description())
731 731
732 732 return subset.filter(matches)
733 733
734 734 def _descendants(repo, subset, x, followfirst=False):
735 735 roots = getset(repo, fullreposet(repo), x)
736 736 if not roots:
737 737 return baseset()
738 738 s = _revdescendants(repo, roots, followfirst)
739 739
740 740 # Both sets need to be ascending in order to lazily return the union
741 741 # in the correct order.
742 742 base = subset & roots
743 743 desc = subset & s
744 744 result = base + desc
745 745 if subset.isascending():
746 746 result.sort()
747 747 elif subset.isdescending():
748 748 result.sort(reverse=True)
749 749 else:
750 750 result = subset & result
751 751 return result
752 752
753 753 def descendants(repo, subset, x):
754 754 """``descendants(set)``
755 755 Changesets which are descendants of changesets in set.
756 756 """
757 757 return _descendants(repo, subset, x)
758 758
759 759 def _firstdescendants(repo, subset, x):
760 760 # ``_firstdescendants(set)``
761 761 # Like ``descendants(set)`` but follows only the first parents.
762 762 return _descendants(repo, subset, x, followfirst=True)
763 763
764 764 def destination(repo, subset, x):
765 765 """``destination([set])``
766 766 Changesets that were created by a graft, transplant or rebase operation,
767 767 with the given revisions specified as the source. Omitting the optional set
768 768 is the same as passing all().
769 769 """
770 770 if x is not None:
771 771 sources = getset(repo, fullreposet(repo), x)
772 772 else:
773 773 sources = fullreposet(repo)
774 774
775 775 dests = set()
776 776
777 777 # subset contains all of the possible destinations that can be returned, so
778 778 # iterate over them and see if their source(s) were provided in the arg set.
779 779 # Even if the immediate src of r is not in the arg set, src's source (or
780 780 # further back) may be. Scanning back further than the immediate src allows
781 781 # transitive transplants and rebases to yield the same results as transitive
782 782 # grafts.
783 783 for r in subset:
784 784 src = _getrevsource(repo, r)
785 785 lineage = None
786 786
787 787 while src is not None:
788 788 if lineage is None:
789 789 lineage = list()
790 790
791 791 lineage.append(r)
792 792
793 793 # The visited lineage is a match if the current source is in the arg
794 794 # set. Since every candidate dest is visited by way of iterating
795 795 # subset, any dests further back in the lineage will be tested by a
796 796 # different iteration over subset. Likewise, if the src was already
797 797 # selected, the current lineage can be selected without going back
798 798 # further.
799 799 if src in sources or src in dests:
800 800 dests.update(lineage)
801 801 break
802 802
803 803 r = src
804 804 src = _getrevsource(repo, r)
805 805
806 806 return subset.filter(dests.__contains__)
807 807
808 808 def divergent(repo, subset, x):
809 809 """``divergent()``
810 810 Final successors of changesets with an alternative set of final successors.
811 811 """
812 812 # i18n: "divergent" is a keyword
813 813 getargs(x, 0, 0, _("divergent takes no arguments"))
814 814 divergent = obsmod.getrevs(repo, 'divergent')
815 815 return subset & divergent
816 816
817 817 def extinct(repo, subset, x):
818 818 """``extinct()``
819 819 Obsolete changesets with obsolete descendants only.
820 820 """
821 821 # i18n: "extinct" is a keyword
822 822 getargs(x, 0, 0, _("extinct takes no arguments"))
823 823 extincts = obsmod.getrevs(repo, 'extinct')
824 824 return subset & extincts
825 825
826 826 def extra(repo, subset, x):
827 827 """``extra(label, [value])``
828 828 Changesets with the given label in the extra metadata, with the given
829 829 optional value.
830 830
831 831 If `value` starts with `re:`, the remainder of the value is treated as
832 832 a regular expression. To match a value that actually starts with `re:`,
833 833 use the prefix `literal:`.
834 834 """
835 835
836 836 # i18n: "extra" is a keyword
837 837 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
838 838 # i18n: "extra" is a keyword
839 839 label = getstring(l[0], _('first argument to extra must be a string'))
840 840 value = None
841 841
842 842 if len(l) > 1:
843 843 # i18n: "extra" is a keyword
844 844 value = getstring(l[1], _('second argument to extra must be a string'))
845 845 kind, value, matcher = _stringmatcher(value)
846 846
847 847 def _matchvalue(r):
848 848 extra = repo[r].extra()
849 849 return label in extra and (value is None or matcher(extra[label]))
850 850
851 851 return subset.filter(lambda r: _matchvalue(r))
852 852
853 853 def filelog(repo, subset, x):
854 854 """``filelog(pattern)``
855 855 Changesets connected to the specified filelog.
856 856
857 857 For performance reasons, visits only revisions mentioned in the file-level
858 858 filelog, rather than filtering through all changesets (much faster, but
859 859 doesn't include deletes or duplicate changes). For a slower, more accurate
860 860 result, use ``file()``.
861 861
862 862 The pattern without explicit kind like ``glob:`` is expected to be
863 863 relative to the current directory and match against a file exactly
864 864 for efficiency.
865 865
866 866 If some linkrev points to revisions filtered by the current repoview, we'll
867 867 work around it to return a non-filtered value.
868 868 """
869 869
870 870 # i18n: "filelog" is a keyword
871 871 pat = getstring(x, _("filelog requires a pattern"))
872 872 s = set()
873 873 cl = repo.changelog
874 874
875 875 if not matchmod.patkind(pat):
876 876 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
877 877 files = [f]
878 878 else:
879 879 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
880 880 files = (f for f in repo[None] if m(f))
881 881
882 882 for f in files:
883 883 backrevref = {} # final value for: filerev -> changerev
884 884 lowestchild = {} # lowest known filerev child of a filerev
885 885 delayed = [] # filerev with filtered linkrev, for post-processing
886 886 lowesthead = None # cache for manifest content of all head revisions
887 887 fl = repo.file(f)
888 888 for fr in list(fl):
889 889 rev = fl.linkrev(fr)
890 890 if rev not in cl:
891 891 # changerev pointed in linkrev is filtered
892 892 # record it for post processing.
893 893 delayed.append((fr, rev))
894 894 continue
895 895 for p in fl.parentrevs(fr):
896 896 if 0 <= p and p not in lowestchild:
897 897 lowestchild[p] = fr
898 898 backrevref[fr] = rev
899 899 s.add(rev)
900 900
901 901 # Post-processing of all filerevs we skipped because they were
902 902 # filtered. If such filerevs have known and unfiltered children, this
903 903 # means they have an unfiltered appearance out there. We'll use linkrev
904 904 # adjustment to find one of these appearances. The lowest known child
905 905 # will be used as a starting point because it is the best upper-bound we
906 906 # have.
907 907 #
908 908 # This approach will fail when an unfiltered but linkrev-shadowed
909 909 # appearance exists in a head changeset without unfiltered filerev
910 910 # children anywhere.
911 911 while delayed:
912 912 # must be a descending iteration. To slowly fill lowest child
913 913 # information that is of potential use by the next item.
914 914 fr, rev = delayed.pop()
915 915 lkr = rev
916 916
917 917 child = lowestchild.get(fr)
918 918
919 919 if child is None:
920 920 # search for existence of this file revision in a head revision.
921 921 # There are three possibilities:
922 922 # - the revision exists in a head and we can find an
923 923 # introduction from there,
924 924 # - the revision does not exist in a head because it has been
925 925 # changed since its introduction: we would have found a child
926 926 # and be in the other 'else' clause,
927 927 # - all versions of the revision are hidden.
928 928 if lowesthead is None:
929 929 lowesthead = {}
930 930 for h in repo.heads():
931 931 fnode = repo[h].manifest().get(f)
932 932 if fnode is not None:
933 933 lowesthead[fl.rev(fnode)] = h
934 934 headrev = lowesthead.get(fr)
935 935 if headrev is None:
936 936 # content is nowhere unfiltered
937 937 continue
938 938 rev = repo[headrev][f].introrev()
939 939 else:
940 940 # the lowest known child is a good upper bound
941 941 childcrev = backrevref[child]
942 942 # XXX this does not guarantee returning the lowest
943 943 # introduction of this revision, but this gives a
944 944 # result which is a good start and will fit in most
945 945 # cases. We probably need to fix the multiple
946 946 # introductions case properly (report each
947 947 # introduction, even for identical file revisions)
948 948 # once and for all at some point anyway.
949 949 for p in repo[childcrev][f].parents():
950 950 if p.filerev() == fr:
951 951 rev = p.rev()
952 952 break
953 953 if rev == lkr: # no shadowed entry found
954 954 # XXX This should never happen unless some manifest points
955 955 # to biggish file revisions (like a revision that uses a
956 956 # parent that never appears in the manifest ancestors)
957 957 continue
958 958
959 959 # Fill the data for the next iteration.
960 960 for p in fl.parentrevs(fr):
961 961 if 0 <= p and p not in lowestchild:
962 962 lowestchild[p] = fr
963 963 backrevref[fr] = rev
964 964 s.add(rev)
965 965
966 966 return subset & s
967 967
968 968 def first(repo, subset, x):
969 969 """``first(set, [n])``
970 970 An alias for limit().
971 971 """
972 972 return limit(repo, subset, x)
973 973
974 974 def _follow(repo, subset, x, name, followfirst=False):
975 975 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
976 976 c = repo['.']
977 977 if l:
978 978 x = getstring(l[0], _("%s expected a filename") % name)
979 979 if x in c:
980 980 cx = c[x]
981 981 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
982 982 # include the revision responsible for the most recent version
983 983 s.add(cx.introrev())
984 984 else:
985 985 return baseset()
986 986 else:
987 987 s = _revancestors(repo, baseset([c.rev()]), followfirst)
988 988
989 989 return subset & s
990 990
991 991 def follow(repo, subset, x):
992 992 """``follow([file])``
993 993 An alias for ``::.`` (ancestors of the working directory's first parent).
994 994 If a filename is specified, the history of the given file is followed,
995 995 including copies.
996 996 """
997 997 return _follow(repo, subset, x, 'follow')
998 998
999 999 def _followfirst(repo, subset, x):
1000 1000 # ``followfirst([file])``
1001 1001 # Like ``follow([file])`` but follows only the first parent of
1002 1002 # every revision or file revision.
1003 1003 return _follow(repo, subset, x, '_followfirst', followfirst=True)
1004 1004
1005 1005 def getall(repo, subset, x):
1006 1006 """``all()``
1007 1007 All changesets, the same as ``0:tip``.
1008 1008 """
1009 1009 # i18n: "all" is a keyword
1010 1010 getargs(x, 0, 0, _("all takes no arguments"))
1011 1011 return subset & spanset(repo) # drop "null" if any
1012 1012
1013 1013 def grep(repo, subset, x):
1014 1014 """``grep(regex)``
1015 1015 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1016 1016 to ensure special escape characters are handled correctly. Unlike
1017 1017 ``keyword(string)``, the match is case-sensitive.
1018 1018 """
1019 1019 try:
1020 1020 # i18n: "grep" is a keyword
1021 1021 gr = re.compile(getstring(x, _("grep requires a string")))
1022 1022 except re.error, e:
1023 1023 raise error.ParseError(_('invalid match pattern: %s') % e)
1024 1024
1025 1025 def matches(x):
1026 1026 c = repo[x]
1027 1027 for e in c.files() + [c.user(), c.description()]:
1028 1028 if gr.search(e):
1029 1029 return True
1030 1030 return False
1031 1031
1032 1032 return subset.filter(matches)
1033 1033
1034 1034 def _matchfiles(repo, subset, x):
1035 1035 # _matchfiles takes a revset list of prefixed arguments:
1036 1036 #
1037 1037 # [p:foo, i:bar, x:baz]
1038 1038 #
1039 1039 # builds a match object from them and filters subset. Allowed
1040 1040 # prefixes are 'p:' for regular patterns, 'i:' for include
1041 1041 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1042 1042 # a revision identifier, or the empty string to reference the
1043 1043 # working directory, from which the match object is
1044 1044 # initialized. Use 'd:' to set the default matching mode, default
1045 1045 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1046 1046
1047 1047 # i18n: "_matchfiles" is a keyword
1048 1048 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
1049 1049 pats, inc, exc = [], [], []
1050 1050 rev, default = None, None
1051 1051 for arg in l:
1052 1052 # i18n: "_matchfiles" is a keyword
1053 1053 s = getstring(arg, _("_matchfiles requires string arguments"))
1054 1054 prefix, value = s[:2], s[2:]
1055 1055 if prefix == 'p:':
1056 1056 pats.append(value)
1057 1057 elif prefix == 'i:':
1058 1058 inc.append(value)
1059 1059 elif prefix == 'x:':
1060 1060 exc.append(value)
1061 1061 elif prefix == 'r:':
1062 1062 if rev is not None:
1063 1063 # i18n: "_matchfiles" is a keyword
1064 1064 raise error.ParseError(_('_matchfiles expected at most one '
1065 1065 'revision'))
1066 1066 if value != '': # empty means working directory; leave rev as None
1067 1067 rev = value
1068 1068 elif prefix == 'd:':
1069 1069 if default is not None:
1070 1070 # i18n: "_matchfiles" is a keyword
1071 1071 raise error.ParseError(_('_matchfiles expected at most one '
1072 1072 'default mode'))
1073 1073 default = value
1074 1074 else:
1075 1075 # i18n: "_matchfiles" is a keyword
1076 1076 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
1077 1077 if not default:
1078 1078 default = 'glob'
1079 1079
1080 1080 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1081 1081 exclude=exc, ctx=repo[rev], default=default)
1082 1082
1083 1083 def matches(x):
1084 1084 for f in repo[x].files():
1085 1085 if m(f):
1086 1086 return True
1087 1087 return False
1088 1088
1089 1089 return subset.filter(matches)
1090 1090
1091 1091 def hasfile(repo, subset, x):
1092 1092 """``file(pattern)``
1093 1093 Changesets affecting files matched by pattern.
1094 1094
1095 1095 For a faster but less accurate result, consider using ``filelog()``
1096 1096 instead.
1097 1097
1098 1098 This predicate uses ``glob:`` as the default kind of pattern.
1099 1099 """
1100 1100 # i18n: "file" is a keyword
1101 1101 pat = getstring(x, _("file requires a pattern"))
1102 1102 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1103 1103
1104 1104 def head(repo, subset, x):
1105 1105 """``head()``
1106 1106 Changeset is a named branch head.
1107 1107 """
1108 1108 # i18n: "head" is a keyword
1109 1109 getargs(x, 0, 0, _("head takes no arguments"))
1110 1110 hs = set()
1111 1111 cl = repo.changelog
1112 1112 for b, ls in repo.branchmap().iteritems():
1113 1113 hs.update(cl.rev(h) for h in ls)
1114 1114 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
1115 1115 # This does not break because of other fullreposet misbehavior.
1116 # XXX We should not be using '.filter' here, but combines subset with '&'
1117 1116 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1118 1117 # necessary to ensure we preserve the order in subset.
1119 return baseset(hs).filter(subset.__contains__)
1118 return baseset(hs) & subset
1120 1119
1121 1120 def heads(repo, subset, x):
1122 1121 """``heads(set)``
1123 1122 Members of set with no children in set.
1124 1123 """
1125 1124 s = getset(repo, subset, x)
1126 1125 ps = parents(repo, subset, x)
1127 1126 return s - ps
1128 1127
1129 1128 def hidden(repo, subset, x):
1130 1129 """``hidden()``
1131 1130 Hidden changesets.
1132 1131 """
1133 1132 # i18n: "hidden" is a keyword
1134 1133 getargs(x, 0, 0, _("hidden takes no arguments"))
1135 1134 hiddenrevs = repoview.filterrevs(repo, 'visible')
1136 1135 return subset & hiddenrevs
1137 1136
1138 1137 def keyword(repo, subset, x):
1139 1138 """``keyword(string)``
1140 1139 Search commit message, user name, and names of changed files for
1141 1140 string. The match is case-insensitive.
1142 1141 """
1143 1142 # i18n: "keyword" is a keyword
1144 1143 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1145 1144
1146 1145 def matches(r):
1147 1146 c = repo[r]
1148 1147 return any(kw in encoding.lower(t)
1149 1148 for t in c.files() + [c.user(), c.description()])
1150 1149
1151 1150 return subset.filter(matches)
1152 1151
1153 1152 def limit(repo, subset, x):
1154 1153 """``limit(set, [n])``
1155 1154 First n members of set, defaulting to 1.
1156 1155 """
1157 1156 # i18n: "limit" is a keyword
1158 1157 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
1159 1158 try:
1160 1159 lim = 1
1161 1160 if len(l) == 2:
1162 1161 # i18n: "limit" is a keyword
1163 1162 lim = int(getstring(l[1], _("limit requires a number")))
1164 1163 except (TypeError, ValueError):
1165 1164 # i18n: "limit" is a keyword
1166 1165 raise error.ParseError(_("limit expects a number"))
1167 1166 ss = subset
1168 1167 os = getset(repo, fullreposet(repo), l[0])
1169 1168 result = []
1170 1169 it = iter(os)
1171 1170 for x in xrange(lim):
1172 1171 y = next(it, None)
1173 1172 if y is None:
1174 1173 break
1175 1174 elif y in ss:
1176 1175 result.append(y)
1177 1176 return baseset(result)
1178 1177
1179 1178 def last(repo, subset, x):
1180 1179 """``last(set, [n])``
1181 1180 Last n members of set, defaulting to 1.
1182 1181 """
1183 1182 # i18n: "last" is a keyword
1184 1183 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1185 1184 try:
1186 1185 lim = 1
1187 1186 if len(l) == 2:
1188 1187 # i18n: "last" is a keyword
1189 1188 lim = int(getstring(l[1], _("last requires a number")))
1190 1189 except (TypeError, ValueError):
1191 1190 # i18n: "last" is a keyword
1192 1191 raise error.ParseError(_("last expects a number"))
1193 1192 ss = subset
1194 1193 os = getset(repo, fullreposet(repo), l[0])
1195 1194 os.reverse()
1196 1195 result = []
1197 1196 it = iter(os)
1198 1197 for x in xrange(lim):
1199 1198 y = next(it, None)
1200 1199 if y is None:
1201 1200 break
1202 1201 elif y in ss:
1203 1202 result.append(y)
1204 1203 return baseset(result)
1205 1204
1206 1205 def maxrev(repo, subset, x):
1207 1206 """``max(set)``
1208 1207 Changeset with highest revision number in set.
1209 1208 """
1210 1209 os = getset(repo, fullreposet(repo), x)
1211 1210 if os:
1212 1211 m = os.max()
1213 1212 if m in subset:
1214 1213 return baseset([m])
1215 1214 return baseset()
1216 1215
1217 1216 def merge(repo, subset, x):
1218 1217 """``merge()``
1219 1218 Changeset is a merge changeset.
1220 1219 """
1221 1220 # i18n: "merge" is a keyword
1222 1221 getargs(x, 0, 0, _("merge takes no arguments"))
1223 1222 cl = repo.changelog
1224 1223 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1225 1224
1226 1225 def branchpoint(repo, subset, x):
1227 1226 """``branchpoint()``
1228 1227 Changesets with more than one child.
1229 1228 """
1230 1229 # i18n: "branchpoint" is a keyword
1231 1230 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1232 1231 cl = repo.changelog
1233 1232 if not subset:
1234 1233 return baseset()
1235 1234 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1236 1235 # (and if it is not, it should.)
1237 1236 baserev = min(subset)
1238 1237 parentscount = [0]*(len(repo) - baserev)
1239 1238 for r in cl.revs(start=baserev + 1):
1240 1239 for p in cl.parentrevs(r):
1241 1240 if p >= baserev:
1242 1241 parentscount[p - baserev] += 1
1243 1242 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1244 1243
1245 1244 def minrev(repo, subset, x):
1246 1245 """``min(set)``
1247 1246 Changeset with lowest revision number in set.
1248 1247 """
1249 1248 os = getset(repo, fullreposet(repo), x)
1250 1249 if os:
1251 1250 m = os.min()
1252 1251 if m in subset:
1253 1252 return baseset([m])
1254 1253 return baseset()
1255 1254
1256 1255 def modifies(repo, subset, x):
1257 1256 """``modifies(pattern)``
1258 1257 Changesets modifying files matched by pattern.
1259 1258
1260 1259 The pattern without explicit kind like ``glob:`` is expected to be
1261 1260 relative to the current directory and match against a file or a
1262 1261 directory.
1263 1262 """
1264 1263 # i18n: "modifies" is a keyword
1265 1264 pat = getstring(x, _("modifies requires a pattern"))
1266 1265 return checkstatus(repo, subset, pat, 0)
1267 1266
1268 1267 def named(repo, subset, x):
1269 1268 """``named(namespace)``
1270 1269 The changesets in a given namespace.
1271 1270
1272 1271 If `namespace` starts with `re:`, the remainder of the string is treated as
1273 1272 a regular expression. To match a namespace that actually starts with `re:`,
1274 1273 use the prefix `literal:`.
1275 1274 """
1276 1275 # i18n: "named" is a keyword
1277 1276 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1278 1277
1279 1278 ns = getstring(args[0],
1280 1279 # i18n: "named" is a keyword
1281 1280 _('the argument to named must be a string'))
1282 1281 kind, pattern, matcher = _stringmatcher(ns)
1283 1282 namespaces = set()
1284 1283 if kind == 'literal':
1285 1284 if pattern not in repo.names:
1286 1285 raise error.RepoLookupError(_("namespace '%s' does not exist")
1287 1286 % ns)
1288 1287 namespaces.add(repo.names[pattern])
1289 1288 else:
1290 1289 for name, ns in repo.names.iteritems():
1291 1290 if matcher(name):
1292 1291 namespaces.add(ns)
1293 1292 if not namespaces:
1294 1293 raise error.RepoLookupError(_("no namespace exists"
1295 1294 " that match '%s'") % pattern)
1296 1295
1297 1296 names = set()
1298 1297 for ns in namespaces:
1299 1298 for name in ns.listnames(repo):
1300 1299 if name not in ns.deprecated:
1301 1300 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1302 1301
1303 1302 names -= set([node.nullrev])
1304 1303 return subset & names
1305 1304
1306 1305 def node_(repo, subset, x):
1307 1306 """``id(string)``
1308 1307 Revision non-ambiguously specified by the given hex string prefix.
1309 1308 """
1310 1309 # i18n: "id" is a keyword
1311 1310 l = getargs(x, 1, 1, _("id requires one argument"))
1312 1311 # i18n: "id" is a keyword
1313 1312 n = getstring(l[0], _("id requires a string"))
1314 1313 if len(n) == 40:
1315 1314 try:
1316 1315 rn = repo.changelog.rev(node.bin(n))
1317 1316 except (LookupError, TypeError):
1318 1317 rn = None
1319 1318 else:
1320 1319 rn = None
1321 1320 pm = repo.changelog._partialmatch(n)
1322 1321 if pm is not None:
1323 1322 rn = repo.changelog.rev(pm)
1324 1323
1325 1324 if rn is None:
1326 1325 return baseset()
1327 1326 result = baseset([rn])
1328 1327 return result & subset
1329 1328
1330 1329 def obsolete(repo, subset, x):
1331 1330 """``obsolete()``
1332 1331 Mutable changeset with a newer version."""
1333 1332 # i18n: "obsolete" is a keyword
1334 1333 getargs(x, 0, 0, _("obsolete takes no arguments"))
1335 1334 obsoletes = obsmod.getrevs(repo, 'obsolete')
1336 1335 return subset & obsoletes
1337 1336
1338 1337 def only(repo, subset, x):
1339 1338 """``only(set, [set])``
1340 1339 Changesets that are ancestors of the first set that are not ancestors
1341 1340 of any other head in the repo. If a second set is specified, the result
1342 1341 is ancestors of the first set that are not ancestors of the second set
1343 1342 (i.e. ::<set1> - ::<set2>).
1344 1343 """
1345 1344 cl = repo.changelog
1346 1345 # i18n: "only" is a keyword
1347 1346 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1348 1347 include = getset(repo, fullreposet(repo), args[0])
1349 1348 if len(args) == 1:
1350 1349 if not include:
1351 1350 return baseset()
1352 1351
1353 1352 descendants = set(_revdescendants(repo, include, False))
1354 1353 exclude = [rev for rev in cl.headrevs()
1355 1354 if not rev in descendants and not rev in include]
1356 1355 else:
1357 1356 exclude = getset(repo, fullreposet(repo), args[1])
1358 1357
1359 1358 results = set(cl.findmissingrevs(common=exclude, heads=include))
1360 1359 # XXX we should turn this into a baseset instead of a set, smartset may do
1361 1360 # some optimisations from the fact this is a baseset.
1362 1361 return subset & results
1363 1362
1364 1363 def origin(repo, subset, x):
1365 1364 """``origin([set])``
1366 1365 Changesets that were specified as a source for the grafts, transplants or
1367 1366 rebases that created the given revisions. Omitting the optional set is the
1368 1367 same as passing all(). If a changeset created by these operations is itself
1369 1368 specified as a source for one of these operations, only the source changeset
1370 1369 for the first operation is selected.
1371 1370 """
1372 1371 if x is not None:
1373 1372 dests = getset(repo, fullreposet(repo), x)
1374 1373 else:
1375 1374 dests = fullreposet(repo)
1376 1375
1377 1376 def _firstsrc(rev):
1378 1377 src = _getrevsource(repo, rev)
1379 1378 if src is None:
1380 1379 return None
1381 1380
1382 1381 while True:
1383 1382 prev = _getrevsource(repo, src)
1384 1383
1385 1384 if prev is None:
1386 1385 return src
1387 1386 src = prev
1388 1387
1389 1388 o = set([_firstsrc(r) for r in dests])
1390 1389 o -= set([None])
1391 1390 # XXX we should turn this into a baseset instead of a set, smartset may do
1392 1391 # some optimisations from the fact this is a baseset.
1393 1392 return subset & o
1394 1393
1395 1394 def outgoing(repo, subset, x):
1396 1395 """``outgoing([path])``
1397 1396 Changesets not found in the specified destination repository, or the
1398 1397 default push location.
1399 1398 """
1400 1399 # Avoid cycles.
1401 1400 import discovery
1402 1401 import hg
1403 1402 # i18n: "outgoing" is a keyword
1404 1403 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1405 1404 # i18n: "outgoing" is a keyword
1406 1405 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1407 1406 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1408 1407 dest, branches = hg.parseurl(dest)
1409 1408 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1410 1409 if revs:
1411 1410 revs = [repo.lookup(rev) for rev in revs]
1412 1411 other = hg.peer(repo, {}, dest)
1413 1412 repo.ui.pushbuffer()
1414 1413 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1415 1414 repo.ui.popbuffer()
1416 1415 cl = repo.changelog
1417 1416 o = set([cl.rev(r) for r in outgoing.missing])
1418 1417 return subset & o
1419 1418
1420 1419 def p1(repo, subset, x):
1421 1420 """``p1([set])``
1422 1421 First parent of changesets in set, or the working directory.
1423 1422 """
1424 1423 if x is None:
1425 1424 p = repo[x].p1().rev()
1426 1425 if p >= 0:
1427 1426 return subset & baseset([p])
1428 1427 return baseset()
1429 1428
1430 1429 ps = set()
1431 1430 cl = repo.changelog
1432 1431 for r in getset(repo, fullreposet(repo), x):
1433 1432 ps.add(cl.parentrevs(r)[0])
1434 1433 ps -= set([node.nullrev])
1435 1434 # XXX we should turn this into a baseset instead of a set, smartset may do
1436 1435 # some optimisations from the fact this is a baseset.
1437 1436 return subset & ps
1438 1437
1439 1438 def p2(repo, subset, x):
1440 1439 """``p2([set])``
1441 1440 Second parent of changesets in set, or the working directory.
1442 1441 """
1443 1442 if x is None:
1444 1443 ps = repo[x].parents()
1445 1444 try:
1446 1445 p = ps[1].rev()
1447 1446 if p >= 0:
1448 1447 return subset & baseset([p])
1449 1448 return baseset()
1450 1449 except IndexError:
1451 1450 return baseset()
1452 1451
1453 1452 ps = set()
1454 1453 cl = repo.changelog
1455 1454 for r in getset(repo, fullreposet(repo), x):
1456 1455 ps.add(cl.parentrevs(r)[1])
1457 1456 ps -= set([node.nullrev])
1458 1457 # XXX we should turn this into a baseset instead of a set, smartset may do
1459 1458 # some optimisations from the fact this is a baseset.
1460 1459 return subset & ps
1461 1460
1462 1461 def parents(repo, subset, x):
1463 1462 """``parents([set])``
1464 1463 The set of all parents for all changesets in set, or the working directory.
1465 1464 """
1466 1465 if x is None:
1467 1466 ps = set(p.rev() for p in repo[x].parents())
1468 1467 else:
1469 1468 ps = set()
1470 1469 cl = repo.changelog
1471 1470 for r in getset(repo, fullreposet(repo), x):
1472 1471 ps.update(cl.parentrevs(r))
1473 1472 ps -= set([node.nullrev])
1474 1473 return subset & ps
1475 1474
1476 1475 def _phase(repo, subset, target):
1477 1476 """helper to select all rev in phase <target>"""
1478 1477 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1479 1478 if repo._phasecache._phasesets:
1480 1479 s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
1481 1480 s = baseset(s)
1482 1481 s.sort() # set are non ordered, so we enforce ascending
1483 1482 return subset & s
1484 1483 else:
1485 1484 phase = repo._phasecache.phase
1486 1485 condition = lambda r: phase(repo, r) == target
1487 1486 return subset.filter(condition, cache=False)
1488 1487
1489 1488 def draft(repo, subset, x):
1490 1489 """``draft()``
1491 1490 Changeset in draft phase."""
1492 1491 # i18n: "draft" is a keyword
1493 1492 getargs(x, 0, 0, _("draft takes no arguments"))
1494 1493 target = phases.draft
1495 1494 return _phase(repo, subset, target)
1496 1495
1497 1496 def secret(repo, subset, x):
1498 1497 """``secret()``
1499 1498 Changeset in secret phase."""
1500 1499 # i18n: "secret" is a keyword
1501 1500 getargs(x, 0, 0, _("secret takes no arguments"))
1502 1501 target = phases.secret
1503 1502 return _phase(repo, subset, target)
1504 1503
1505 1504 def parentspec(repo, subset, x, n):
1506 1505 """``set^0``
1507 1506 The set.
1508 1507 ``set^1`` (or ``set^``), ``set^2``
1509 1508 First or second parent, respectively, of all changesets in set.
1510 1509 """
1511 1510 try:
1512 1511 n = int(n[1])
1513 1512 if n not in (0, 1, 2):
1514 1513 raise ValueError
1515 1514 except (TypeError, ValueError):
1516 1515 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1517 1516 ps = set()
1518 1517 cl = repo.changelog
1519 1518 for r in getset(repo, fullreposet(repo), x):
1520 1519 if n == 0:
1521 1520 ps.add(r)
1522 1521 elif n == 1:
1523 1522 ps.add(cl.parentrevs(r)[0])
1524 1523 elif n == 2:
1525 1524 parents = cl.parentrevs(r)
1526 1525 if len(parents) > 1:
1527 1526 ps.add(parents[1])
1528 1527 return subset & ps
1529 1528
1530 1529 def present(repo, subset, x):
1531 1530 """``present(set)``
1532 1531 An empty set, if any revision in set isn't found; otherwise,
1533 1532 all revisions in set.
1534 1533
1535 1534 If any of specified revisions is not present in the local repository,
1536 1535 the query is normally aborted. But this predicate allows the query
1537 1536 to continue even in such cases.
1538 1537 """
1539 1538 try:
1540 1539 return getset(repo, subset, x)
1541 1540 except error.RepoLookupError:
1542 1541 return baseset()
1543 1542
1544 1543 # for internal use
1545 1544 def _notpublic(repo, subset, x):
1546 1545 getargs(x, 0, 0, "_notpublic takes no arguments")
1547 1546 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1548 1547 if repo._phasecache._phasesets:
1549 1548 s = set()
1550 1549 for u in repo._phasecache._phasesets[1:]:
1551 1550 s.update(u)
1552 1551 s = baseset(s - repo.changelog.filteredrevs)
1553 1552 s.sort()
1554 1553 return subset & s
1555 1554 else:
1556 1555 phase = repo._phasecache.phase
1557 1556 target = phases.public
1558 1557 condition = lambda r: phase(repo, r) != target
1559 1558 return subset.filter(condition, cache=False)
1560 1559
1561 1560 def public(repo, subset, x):
1562 1561 """``public()``
1563 1562 Changeset in public phase."""
1564 1563 # i18n: "public" is a keyword
1565 1564 getargs(x, 0, 0, _("public takes no arguments"))
1566 1565 phase = repo._phasecache.phase
1567 1566 target = phases.public
1568 1567 condition = lambda r: phase(repo, r) == target
1569 1568 return subset.filter(condition, cache=False)
1570 1569
1571 1570 def remote(repo, subset, x):
1572 1571 """``remote([id [,path]])``
1573 1572 Local revision that corresponds to the given identifier in a
1574 1573 remote repository, if present. Here, the '.' identifier is a
1575 1574 synonym for the current local branch.
1576 1575 """
1577 1576
1578 1577 import hg # avoid start-up nasties
1579 1578 # i18n: "remote" is a keyword
1580 1579 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1581 1580
1582 1581 q = '.'
1583 1582 if len(l) > 0:
1584 1583 # i18n: "remote" is a keyword
1585 1584 q = getstring(l[0], _("remote requires a string id"))
1586 1585 if q == '.':
1587 1586 q = repo['.'].branch()
1588 1587
1589 1588 dest = ''
1590 1589 if len(l) > 1:
1591 1590 # i18n: "remote" is a keyword
1592 1591 dest = getstring(l[1], _("remote requires a repository path"))
1593 1592 dest = repo.ui.expandpath(dest or 'default')
1594 1593 dest, branches = hg.parseurl(dest)
1595 1594 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1596 1595 if revs:
1597 1596 revs = [repo.lookup(rev) for rev in revs]
1598 1597 other = hg.peer(repo, {}, dest)
1599 1598 n = other.lookup(q)
1600 1599 if n in repo:
1601 1600 r = repo[n].rev()
1602 1601 if r in subset:
1603 1602 return baseset([r])
1604 1603 return baseset()
1605 1604
1606 1605 def removes(repo, subset, x):
1607 1606 """``removes(pattern)``
1608 1607 Changesets which remove files matching pattern.
1609 1608
1610 1609 The pattern without explicit kind like ``glob:`` is expected to be
1611 1610 relative to the current directory and match against a file or a
1612 1611 directory.
1613 1612 """
1614 1613 # i18n: "removes" is a keyword
1615 1614 pat = getstring(x, _("removes requires a pattern"))
1616 1615 return checkstatus(repo, subset, pat, 2)
1617 1616
1618 1617 def rev(repo, subset, x):
1619 1618 """``rev(number)``
1620 1619 Revision with the given numeric identifier.
1621 1620 """
1622 1621 # i18n: "rev" is a keyword
1623 1622 l = getargs(x, 1, 1, _("rev requires one argument"))
1624 1623 try:
1625 1624 # i18n: "rev" is a keyword
1626 1625 l = int(getstring(l[0], _("rev requires a number")))
1627 1626 except (TypeError, ValueError):
1628 1627 # i18n: "rev" is a keyword
1629 1628 raise error.ParseError(_("rev expects a number"))
1630 1629 if l not in repo.changelog and l != node.nullrev:
1631 1630 return baseset()
1632 1631 return subset & baseset([l])
1633 1632
1634 1633 def matching(repo, subset, x):
1635 1634 """``matching(revision [, field])``
1636 1635 Changesets in which a given set of fields match the set of fields in the
1637 1636 selected revision or set.
1638 1637
1639 1638 To match more than one field pass the list of fields to match separated
1640 1639 by spaces (e.g. ``author description``).
1641 1640
1642 1641 Valid fields are most regular revision fields and some special fields.
1643 1642
1644 1643 Regular revision fields are ``description``, ``author``, ``branch``,
1645 1644 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1646 1645 and ``diff``.
1647 1646 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1648 1647 contents of the revision. Two revisions matching their ``diff`` will
1649 1648 also match their ``files``.
1650 1649
1651 1650 Special fields are ``summary`` and ``metadata``:
1652 1651 ``summary`` matches the first line of the description.
1653 1652 ``metadata`` is equivalent to matching ``description user date``
1654 1653 (i.e. it matches the main metadata fields).
1655 1654
1656 1655 ``metadata`` is the default field which is used when no fields are
1657 1656 specified. You can match more than one field at a time.
1658 1657 """
1659 1658 # i18n: "matching" is a keyword
1660 1659 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1661 1660
1662 1661 revs = getset(repo, fullreposet(repo), l[0])
1663 1662
1664 1663 fieldlist = ['metadata']
1665 1664 if len(l) > 1:
1666 1665 fieldlist = getstring(l[1],
1667 1666 # i18n: "matching" is a keyword
1668 1667 _("matching requires a string "
1669 1668 "as its second argument")).split()
1670 1669
1671 1670 # Make sure that there are no repeated fields,
1672 1671 # expand the 'special' 'metadata' field type
1673 1672 # and check the 'files' whenever we check the 'diff'
1674 1673 fields = []
1675 1674 for field in fieldlist:
1676 1675 if field == 'metadata':
1677 1676 fields += ['user', 'description', 'date']
1678 1677 elif field == 'diff':
1679 1678 # a revision matching the diff must also match the files
1680 1679 # since matching the diff is very costly, make sure to
1681 1680 # also match the files first
1682 1681 fields += ['files', 'diff']
1683 1682 else:
1684 1683 if field == 'author':
1685 1684 field = 'user'
1686 1685 fields.append(field)
1687 1686 fields = set(fields)
1688 1687 if 'summary' in fields and 'description' in fields:
1689 1688 # If a revision matches its description it also matches its summary
1690 1689 fields.discard('summary')
1691 1690
1692 1691 # We may want to match more than one field
1693 1692 # Not all fields take the same amount of time to be matched
1694 1693 # Sort the selected fields in order of increasing matching cost
1695 1694 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1696 1695 'files', 'description', 'substate', 'diff']
1697 1696 def fieldkeyfunc(f):
1698 1697 try:
1699 1698 return fieldorder.index(f)
1700 1699 except ValueError:
1701 1700 # assume an unknown field is very costly
1702 1701 return len(fieldorder)
1703 1702 fields = list(fields)
1704 1703 fields.sort(key=fieldkeyfunc)
1705 1704
1706 1705 # Each field will be matched with its own "getfield" function
1707 1706 # which will be added to the getfieldfuncs array of functions
1708 1707 getfieldfuncs = []
1709 1708 _funcs = {
1710 1709 'user': lambda r: repo[r].user(),
1711 1710 'branch': lambda r: repo[r].branch(),
1712 1711 'date': lambda r: repo[r].date(),
1713 1712 'description': lambda r: repo[r].description(),
1714 1713 'files': lambda r: repo[r].files(),
1715 1714 'parents': lambda r: repo[r].parents(),
1716 1715 'phase': lambda r: repo[r].phase(),
1717 1716 'substate': lambda r: repo[r].substate,
1718 1717 'summary': lambda r: repo[r].description().splitlines()[0],
1719 1718 'diff': lambda r: list(repo[r].diff(git=True),)
1720 1719 }
1721 1720 for info in fields:
1722 1721 getfield = _funcs.get(info, None)
1723 1722 if getfield is None:
1724 1723 raise error.ParseError(
1725 1724 # i18n: "matching" is a keyword
1726 1725 _("unexpected field name passed to matching: %s") % info)
1727 1726 getfieldfuncs.append(getfield)
1728 1727 # convert the getfield array of functions into a "getinfo" function
1729 1728 # which returns an array of field values (or a single value if there
1730 1729 # is only one field to match)
1731 1730 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1732 1731
1733 1732 def matches(x):
1734 1733 for rev in revs:
1735 1734 target = getinfo(rev)
1736 1735 match = True
1737 1736 for n, f in enumerate(getfieldfuncs):
1738 1737 if target[n] != f(x):
1739 1738 match = False
1740 1739 if match:
1741 1740 return True
1742 1741 return False
1743 1742
1744 1743 return subset.filter(matches)
1745 1744
1746 1745 def reverse(repo, subset, x):
1747 1746 """``reverse(set)``
1748 1747 Reverse order of set.
1749 1748 """
1750 1749 l = getset(repo, subset, x)
1751 1750 l.reverse()
1752 1751 return l
1753 1752
1754 1753 def roots(repo, subset, x):
1755 1754 """``roots(set)``
1756 1755 Changesets in set with no parent changeset in set.
1757 1756 """
1758 1757 s = getset(repo, fullreposet(repo), x)
1759 1758 subset = subset & s# baseset([r for r in s if r in subset])
1760 1759 cs = _children(repo, subset, s)
1761 1760 return subset - cs
1762 1761
1763 1762 def sort(repo, subset, x):
1764 1763 """``sort(set[, [-]key...])``
1765 1764 Sort set by keys. The default sort order is ascending, specify a key
1766 1765 as ``-key`` to sort in descending order.
1767 1766
1768 1767 The keys can be:
1769 1768
1770 1769 - ``rev`` for the revision number,
1771 1770 - ``branch`` for the branch name,
1772 1771 - ``desc`` for the commit message (description),
1773 1772 - ``user`` for user name (``author`` can be used as an alias),
1774 1773 - ``date`` for the commit date
1775 1774 """
1776 1775 # i18n: "sort" is a keyword
1777 1776 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1778 1777 keys = "rev"
1779 1778 if len(l) == 2:
1780 1779 # i18n: "sort" is a keyword
1781 1780 keys = getstring(l[1], _("sort spec must be a string"))
1782 1781
1783 1782 s = l[0]
1784 1783 keys = keys.split()
1785 1784 l = []
1786 1785 def invert(s):
1787 1786 return "".join(chr(255 - ord(c)) for c in s)
1788 1787 revs = getset(repo, subset, s)
1789 1788 if keys == ["rev"]:
1790 1789 revs.sort()
1791 1790 return revs
1792 1791 elif keys == ["-rev"]:
1793 1792 revs.sort(reverse=True)
1794 1793 return revs
1795 1794 for r in revs:
1796 1795 c = repo[r]
1797 1796 e = []
1798 1797 for k in keys:
1799 1798 if k == 'rev':
1800 1799 e.append(r)
1801 1800 elif k == '-rev':
1802 1801 e.append(-r)
1803 1802 elif k == 'branch':
1804 1803 e.append(c.branch())
1805 1804 elif k == '-branch':
1806 1805 e.append(invert(c.branch()))
1807 1806 elif k == 'desc':
1808 1807 e.append(c.description())
1809 1808 elif k == '-desc':
1810 1809 e.append(invert(c.description()))
1811 1810 elif k in 'user author':
1812 1811 e.append(c.user())
1813 1812 elif k in '-user -author':
1814 1813 e.append(invert(c.user()))
1815 1814 elif k == 'date':
1816 1815 e.append(c.date()[0])
1817 1816 elif k == '-date':
1818 1817 e.append(-c.date()[0])
1819 1818 else:
1820 1819 raise error.ParseError(_("unknown sort key %r") % k)
1821 1820 e.append(r)
1822 1821 l.append(e)
1823 1822 l.sort()
1824 1823 return baseset([e[-1] for e in l])
1825 1824
1826 1825 def subrepo(repo, subset, x):
1827 1826 """``subrepo([pattern])``
1828 1827 Changesets that add, modify or remove the given subrepo. If no subrepo
1829 1828 pattern is named, any subrepo changes are returned.
1830 1829 """
1831 1830 # i18n: "subrepo" is a keyword
1832 1831 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1833 1832 if len(args) != 0:
1834 1833 pat = getstring(args[0], _("subrepo requires a pattern"))
1835 1834
1836 1835 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1837 1836
1838 1837 def submatches(names):
1839 1838 k, p, m = _stringmatcher(pat)
1840 1839 for name in names:
1841 1840 if m(name):
1842 1841 yield name
1843 1842
1844 1843 def matches(x):
1845 1844 c = repo[x]
1846 1845 s = repo.status(c.p1().node(), c.node(), match=m)
1847 1846
1848 1847 if len(args) == 0:
1849 1848 return s.added or s.modified or s.removed
1850 1849
1851 1850 if s.added:
1852 1851 return any(submatches(c.substate.keys()))
1853 1852
1854 1853 if s.modified:
1855 1854 subs = set(c.p1().substate.keys())
1856 1855 subs.update(c.substate.keys())
1857 1856
1858 1857 for path in submatches(subs):
1859 1858 if c.p1().substate.get(path) != c.substate.get(path):
1860 1859 return True
1861 1860
1862 1861 if s.removed:
1863 1862 return any(submatches(c.p1().substate.keys()))
1864 1863
1865 1864 return False
1866 1865
1867 1866 return subset.filter(matches)
1868 1867
1869 1868 def _stringmatcher(pattern):
1870 1869 """
1871 1870 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1872 1871 returns the matcher name, pattern, and matcher function.
1873 1872 missing or unknown prefixes are treated as literal matches.
1874 1873
1875 1874 helper for tests:
1876 1875 >>> def test(pattern, *tests):
1877 1876 ... kind, pattern, matcher = _stringmatcher(pattern)
1878 1877 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1879 1878
1880 1879 exact matching (no prefix):
1881 1880 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1882 1881 ('literal', 'abcdefg', [False, False, True])
1883 1882
1884 1883 regex matching ('re:' prefix)
1885 1884 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1886 1885 ('re', 'a.+b', [False, False, True])
1887 1886
1888 1887 force exact matches ('literal:' prefix)
1889 1888 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1890 1889 ('literal', 're:foobar', [False, True])
1891 1890
1892 1891 unknown prefixes are ignored and treated as literals
1893 1892 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1894 1893 ('literal', 'foo:bar', [False, False, True])
1895 1894 """
1896 1895 if pattern.startswith('re:'):
1897 1896 pattern = pattern[3:]
1898 1897 try:
1899 1898 regex = re.compile(pattern)
1900 1899 except re.error, e:
1901 1900 raise error.ParseError(_('invalid regular expression: %s')
1902 1901 % e)
1903 1902 return 're', pattern, regex.search
1904 1903 elif pattern.startswith('literal:'):
1905 1904 pattern = pattern[8:]
1906 1905 return 'literal', pattern, pattern.__eq__
1907 1906
1908 1907 def _substringmatcher(pattern):
1909 1908 kind, pattern, matcher = _stringmatcher(pattern)
1910 1909 if kind == 'literal':
1911 1910 matcher = lambda s: pattern in s
1912 1911 return kind, pattern, matcher
1913 1912
1914 1913 def tag(repo, subset, x):
1915 1914 """``tag([name])``
1916 1915 The specified tag by name, or all tagged revisions if no name is given.
1917 1916
1918 1917 If `name` starts with `re:`, the remainder of the name is treated as
1919 1918 a regular expression. To match a tag that actually starts with `re:`,
1920 1919 use the prefix `literal:`.
1921 1920 """
1922 1921 # i18n: "tag" is a keyword
1923 1922 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1924 1923 cl = repo.changelog
1925 1924 if args:
1926 1925 pattern = getstring(args[0],
1927 1926 # i18n: "tag" is a keyword
1928 1927 _('the argument to tag must be a string'))
1929 1928 kind, pattern, matcher = _stringmatcher(pattern)
1930 1929 if kind == 'literal':
1931 1930 # avoid resolving all tags
1932 1931 tn = repo._tagscache.tags.get(pattern, None)
1933 1932 if tn is None:
1934 1933 raise error.RepoLookupError(_("tag '%s' does not exist")
1935 1934 % pattern)
1936 1935 s = set([repo[tn].rev()])
1937 1936 else:
1938 1937 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1939 1938 else:
1940 1939 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1941 1940 return subset & s
1942 1941
1943 1942 def tagged(repo, subset, x):
1944 1943 return tag(repo, subset, x)
1945 1944
1946 1945 def unstable(repo, subset, x):
1947 1946 """``unstable()``
1948 1947 Non-obsolete changesets with obsolete ancestors.
1949 1948 """
1950 1949 # i18n: "unstable" is a keyword
1951 1950 getargs(x, 0, 0, _("unstable takes no arguments"))
1952 1951 unstables = obsmod.getrevs(repo, 'unstable')
1953 1952 return subset & unstables
1954 1953
1955 1954
1956 1955 def user(repo, subset, x):
1957 1956 """``user(string)``
1958 1957 User name contains string. The match is case-insensitive.
1959 1958
1960 1959 If `string` starts with `re:`, the remainder of the string is treated as
1961 1960 a regular expression. To match a user that actually contains `re:`, use
1962 1961 the prefix `literal:`.
1963 1962 """
1964 1963 return author(repo, subset, x)
1965 1964
1966 1965 # experimental
1967 1966 def wdir(repo, subset, x):
1968 1967 # i18n: "wdir" is a keyword
1969 1968 getargs(x, 0, 0, _("wdir takes no arguments"))
1970 1969 if None in subset or isinstance(subset, fullreposet):
1971 1970 return baseset([None])
1972 1971 return baseset()
1973 1972
1974 1973 # for internal use
1975 1974 def _list(repo, subset, x):
1976 1975 s = getstring(x, "internal error")
1977 1976 if not s:
1978 1977 return baseset()
1979 1978 # remove duplicates here. it's difficult for caller to deduplicate sets
1980 1979 # because different symbols can point to the same rev.
1981 1980 cl = repo.changelog
1982 1981 ls = []
1983 1982 seen = set()
1984 1983 for t in s.split('\0'):
1985 1984 try:
1986 1985 # fast path for integer revision
1987 1986 r = int(t)
1988 1987 if str(r) != t or r not in cl:
1989 1988 raise ValueError
1990 1989 except ValueError:
1991 1990 r = repo[t].rev()
1992 1991 if r in seen:
1993 1992 continue
1994 1993 if (r in subset
1995 1994 or r == node.nullrev and isinstance(subset, fullreposet)):
1996 1995 ls.append(r)
1997 1996 seen.add(r)
1998 1997 return baseset(ls)
1999 1998
2000 1999 # for internal use
2001 2000 def _intlist(repo, subset, x):
2002 2001 s = getstring(x, "internal error")
2003 2002 if not s:
2004 2003 return baseset()
2005 2004 ls = [int(r) for r in s.split('\0')]
2006 2005 s = subset
2007 2006 return baseset([r for r in ls if r in s])
2008 2007
2009 2008 # for internal use
2010 2009 def _hexlist(repo, subset, x):
2011 2010 s = getstring(x, "internal error")
2012 2011 if not s:
2013 2012 return baseset()
2014 2013 cl = repo.changelog
2015 2014 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2016 2015 s = subset
2017 2016 return baseset([r for r in ls if r in s])
2018 2017
2019 2018 symbols = {
2020 2019 "adds": adds,
2021 2020 "all": getall,
2022 2021 "ancestor": ancestor,
2023 2022 "ancestors": ancestors,
2024 2023 "_firstancestors": _firstancestors,
2025 2024 "author": author,
2026 2025 "bisect": bisect,
2027 2026 "bisected": bisected,
2028 2027 "bookmark": bookmark,
2029 2028 "branch": branch,
2030 2029 "branchpoint": branchpoint,
2031 2030 "bumped": bumped,
2032 2031 "bundle": bundle,
2033 2032 "children": children,
2034 2033 "closed": closed,
2035 2034 "contains": contains,
2036 2035 "converted": converted,
2037 2036 "date": date,
2038 2037 "desc": desc,
2039 2038 "descendants": descendants,
2040 2039 "_firstdescendants": _firstdescendants,
2041 2040 "destination": destination,
2042 2041 "divergent": divergent,
2043 2042 "draft": draft,
2044 2043 "extinct": extinct,
2045 2044 "extra": extra,
2046 2045 "file": hasfile,
2047 2046 "filelog": filelog,
2048 2047 "first": first,
2049 2048 "follow": follow,
2050 2049 "_followfirst": _followfirst,
2051 2050 "grep": grep,
2052 2051 "head": head,
2053 2052 "heads": heads,
2054 2053 "hidden": hidden,
2055 2054 "id": node_,
2056 2055 "keyword": keyword,
2057 2056 "last": last,
2058 2057 "limit": limit,
2059 2058 "_matchfiles": _matchfiles,
2060 2059 "max": maxrev,
2061 2060 "merge": merge,
2062 2061 "min": minrev,
2063 2062 "modifies": modifies,
2064 2063 "named": named,
2065 2064 "obsolete": obsolete,
2066 2065 "only": only,
2067 2066 "origin": origin,
2068 2067 "outgoing": outgoing,
2069 2068 "p1": p1,
2070 2069 "p2": p2,
2071 2070 "parents": parents,
2072 2071 "present": present,
2073 2072 "public": public,
2074 2073 "_notpublic": _notpublic,
2075 2074 "remote": remote,
2076 2075 "removes": removes,
2077 2076 "rev": rev,
2078 2077 "reverse": reverse,
2079 2078 "roots": roots,
2080 2079 "sort": sort,
2081 2080 "secret": secret,
2082 2081 "subrepo": subrepo,
2083 2082 "matching": matching,
2084 2083 "tag": tag,
2085 2084 "tagged": tagged,
2086 2085 "user": user,
2087 2086 "unstable": unstable,
2088 2087 "wdir": wdir,
2089 2088 "_list": _list,
2090 2089 "_intlist": _intlist,
2091 2090 "_hexlist": _hexlist,
2092 2091 }
2093 2092
2094 2093 # symbols which can't be used for a DoS attack for any given input
2095 2094 # (e.g. those which accept regexes as plain strings shouldn't be included)
2096 2095 # functions that just return a lot of changesets (like all) don't count here
2097 2096 safesymbols = set([
2098 2097 "adds",
2099 2098 "all",
2100 2099 "ancestor",
2101 2100 "ancestors",
2102 2101 "_firstancestors",
2103 2102 "author",
2104 2103 "bisect",
2105 2104 "bisected",
2106 2105 "bookmark",
2107 2106 "branch",
2108 2107 "branchpoint",
2109 2108 "bumped",
2110 2109 "bundle",
2111 2110 "children",
2112 2111 "closed",
2113 2112 "converted",
2114 2113 "date",
2115 2114 "desc",
2116 2115 "descendants",
2117 2116 "_firstdescendants",
2118 2117 "destination",
2119 2118 "divergent",
2120 2119 "draft",
2121 2120 "extinct",
2122 2121 "extra",
2123 2122 "file",
2124 2123 "filelog",
2125 2124 "first",
2126 2125 "follow",
2127 2126 "_followfirst",
2128 2127 "head",
2129 2128 "heads",
2130 2129 "hidden",
2131 2130 "id",
2132 2131 "keyword",
2133 2132 "last",
2134 2133 "limit",
2135 2134 "_matchfiles",
2136 2135 "max",
2137 2136 "merge",
2138 2137 "min",
2139 2138 "modifies",
2140 2139 "obsolete",
2141 2140 "only",
2142 2141 "origin",
2143 2142 "outgoing",
2144 2143 "p1",
2145 2144 "p2",
2146 2145 "parents",
2147 2146 "present",
2148 2147 "public",
2149 2148 "_notpublic",
2150 2149 "remote",
2151 2150 "removes",
2152 2151 "rev",
2153 2152 "reverse",
2154 2153 "roots",
2155 2154 "sort",
2156 2155 "secret",
2157 2156 "matching",
2158 2157 "tag",
2159 2158 "tagged",
2160 2159 "user",
2161 2160 "unstable",
2162 2161 "wdir",
2163 2162 "_list",
2164 2163 "_intlist",
2165 2164 "_hexlist",
2166 2165 ])
2167 2166
2168 2167 methods = {
2169 2168 "range": rangeset,
2170 2169 "dagrange": dagrange,
2171 2170 "string": stringset,
2172 2171 "symbol": stringset,
2173 2172 "and": andset,
2174 2173 "or": orset,
2175 2174 "not": notset,
2176 2175 "list": listset,
2177 2176 "func": func,
2178 2177 "ancestor": ancestorspec,
2179 2178 "parent": parentspec,
2180 2179 "parentpost": p1,
2181 2180 }
2182 2181
2183 2182 def optimize(x, small):
2184 2183 if x is None:
2185 2184 return 0, x
2186 2185
2187 2186 smallbonus = 1
2188 2187 if small:
2189 2188 smallbonus = .5
2190 2189
2191 2190 op = x[0]
2192 2191 if op == 'minus':
2193 2192 return optimize(('and', x[1], ('not', x[2])), small)
2194 2193 elif op == 'only':
2195 2194 return optimize(('func', ('symbol', 'only'),
2196 2195 ('list', x[1], x[2])), small)
2197 2196 elif op == 'onlypost':
2198 2197 return optimize(('func', ('symbol', 'only'), x[1]), small)
2199 2198 elif op == 'dagrangepre':
2200 2199 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2201 2200 elif op == 'dagrangepost':
2202 2201 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2203 2202 elif op == 'rangepre':
2204 2203 return optimize(('range', ('string', '0'), x[1]), small)
2205 2204 elif op == 'rangepost':
2206 2205 return optimize(('range', x[1], ('string', 'tip')), small)
2207 2206 elif op == 'negate':
2208 2207 return optimize(('string',
2209 2208 '-' + getstring(x[1], _("can't negate that"))), small)
2210 2209 elif op in 'string symbol negate':
2211 2210 return smallbonus, x # single revisions are small
2212 2211 elif op == 'and':
2213 2212 wa, ta = optimize(x[1], True)
2214 2213 wb, tb = optimize(x[2], True)
2215 2214
2216 2215 # (::x and not ::y)/(not ::y and ::x) have a fast path
2217 2216 def isonly(revs, bases):
2218 2217 return (
2219 2218 revs[0] == 'func'
2220 2219 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2221 2220 and bases[0] == 'not'
2222 2221 and bases[1][0] == 'func'
2223 2222 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2224 2223
2225 2224 w = min(wa, wb)
2226 2225 if isonly(ta, tb):
2227 2226 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2228 2227 if isonly(tb, ta):
2229 2228 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2230 2229
2231 2230 if wa > wb:
2232 2231 return w, (op, tb, ta)
2233 2232 return w, (op, ta, tb)
2234 2233 elif op == 'or':
2235 2234 # fast path for machine-generated expression, that is likely to have
2236 2235 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
2237 2236 ws, ts, ss = [], [], []
2238 2237 def flushss():
2239 2238 if not ss:
2240 2239 return
2241 2240 if len(ss) == 1:
2242 2241 w, t = ss[0]
2243 2242 else:
2244 2243 s = '\0'.join(t[1] for w, t in ss)
2245 2244 y = ('func', ('symbol', '_list'), ('string', s))
2246 2245 w, t = optimize(y, False)
2247 2246 ws.append(w)
2248 2247 ts.append(t)
2249 2248 del ss[:]
2250 2249 for y in x[1:]:
2251 2250 w, t = optimize(y, False)
2252 2251 if t[0] == 'string' or t[0] == 'symbol':
2253 2252 ss.append((w, t))
2254 2253 continue
2255 2254 flushss()
2256 2255 ws.append(w)
2257 2256 ts.append(t)
2258 2257 flushss()
2259 2258 if len(ts) == 1:
2260 2259 return ws[0], ts[0] # 'or' operation is fully optimized out
2261 2260 # we can't reorder trees by weight because it would change the order.
2262 2261 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2263 2262 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
2264 2263 return max(ws), (op,) + tuple(ts)
2265 2264 elif op == 'not':
2266 2265 # Optimize not public() to _notpublic() because we have a fast version
2267 2266 if x[1] == ('func', ('symbol', 'public'), None):
2268 2267 newsym = ('func', ('symbol', '_notpublic'), None)
2269 2268 o = optimize(newsym, not small)
2270 2269 return o[0], o[1]
2271 2270 else:
2272 2271 o = optimize(x[1], not small)
2273 2272 return o[0], (op, o[1])
2274 2273 elif op == 'parentpost':
2275 2274 o = optimize(x[1], small)
2276 2275 return o[0], (op, o[1])
2277 2276 elif op == 'group':
2278 2277 return optimize(x[1], small)
2279 2278 elif op in 'dagrange range list parent ancestorspec':
2280 2279 if op == 'parent':
2281 2280 # x^:y means (x^) : y, not x ^ (:y)
2282 2281 post = ('parentpost', x[1])
2283 2282 if x[2][0] == 'dagrangepre':
2284 2283 return optimize(('dagrange', post, x[2][1]), small)
2285 2284 elif x[2][0] == 'rangepre':
2286 2285 return optimize(('range', post, x[2][1]), small)
2287 2286
2288 2287 wa, ta = optimize(x[1], small)
2289 2288 wb, tb = optimize(x[2], small)
2290 2289 return wa + wb, (op, ta, tb)
2291 2290 elif op == 'func':
2292 2291 f = getstring(x[1], _("not a symbol"))
2293 2292 wa, ta = optimize(x[2], small)
2294 2293 if f in ("author branch closed date desc file grep keyword "
2295 2294 "outgoing user"):
2296 2295 w = 10 # slow
2297 2296 elif f in "modifies adds removes":
2298 2297 w = 30 # slower
2299 2298 elif f == "contains":
2300 2299 w = 100 # very slow
2301 2300 elif f == "ancestor":
2302 2301 w = 1 * smallbonus
2303 2302 elif f in "reverse limit first _intlist":
2304 2303 w = 0
2305 2304 elif f in "sort":
2306 2305 w = 10 # assume most sorts look at changelog
2307 2306 else:
2308 2307 w = 1
2309 2308 return w + wa, (op, x[1], ta)
2310 2309 return 1, x
2311 2310
2312 2311 _aliasarg = ('func', ('symbol', '_aliasarg'))
2313 2312 def _getaliasarg(tree):
2314 2313 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2315 2314 return X, None otherwise.
2316 2315 """
2317 2316 if (len(tree) == 3 and tree[:2] == _aliasarg
2318 2317 and tree[2][0] == 'string'):
2319 2318 return tree[2][1]
2320 2319 return None
2321 2320
2322 2321 def _checkaliasarg(tree, known=None):
2323 2322 """Check tree contains no _aliasarg construct or only ones which
2324 2323 value is in known. Used to avoid alias placeholders injection.
2325 2324 """
2326 2325 if isinstance(tree, tuple):
2327 2326 arg = _getaliasarg(tree)
2328 2327 if arg is not None and (not known or arg not in known):
2329 2328 raise error.UnknownIdentifier('_aliasarg', [])
2330 2329 for t in tree:
2331 2330 _checkaliasarg(t, known)
2332 2331
2333 2332 # the set of valid characters for the initial letter of symbols in
2334 2333 # alias declarations and definitions
2335 2334 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2336 2335 if c.isalnum() or c in '._@$' or ord(c) > 127)
2337 2336
2338 2337 def _tokenizealias(program, lookup=None):
2339 2338 """Parse alias declaration/definition into a stream of tokens
2340 2339
2341 2340 This allows symbol names to use also ``$`` as an initial letter
2342 2341 (for backward compatibility), and callers of this function should
2343 2342 examine whether ``$`` is used also for unexpected symbols or not.
2344 2343 """
2345 2344 return tokenize(program, lookup=lookup,
2346 2345 syminitletters=_aliassyminitletters)
2347 2346
2348 2347 def _parsealiasdecl(decl):
2349 2348 """Parse alias declaration ``decl``
2350 2349
2351 2350 This returns ``(name, tree, args, errorstr)`` tuple:
2352 2351
2353 2352 - ``name``: of declared alias (may be ``decl`` itself at error)
2354 2353 - ``tree``: parse result (or ``None`` at error)
2355 2354 - ``args``: list of alias argument names (or None for symbol declaration)
2356 2355 - ``errorstr``: detail about detected error (or None)
2357 2356
2358 2357 >>> _parsealiasdecl('foo')
2359 2358 ('foo', ('symbol', 'foo'), None, None)
2360 2359 >>> _parsealiasdecl('$foo')
2361 2360 ('$foo', None, None, "'$' not for alias arguments")
2362 2361 >>> _parsealiasdecl('foo::bar')
2363 2362 ('foo::bar', None, None, 'invalid format')
2364 2363 >>> _parsealiasdecl('foo bar')
2365 2364 ('foo bar', None, None, 'at 4: invalid token')
2366 2365 >>> _parsealiasdecl('foo()')
2367 2366 ('foo', ('func', ('symbol', 'foo')), [], None)
2368 2367 >>> _parsealiasdecl('$foo()')
2369 2368 ('$foo()', None, None, "'$' not for alias arguments")
2370 2369 >>> _parsealiasdecl('foo($1, $2)')
2371 2370 ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
2372 2371 >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
2373 2372 ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
2374 2373 >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
2375 2374 ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
2376 2375 >>> _parsealiasdecl('foo(bar($1, $2))')
2377 2376 ('foo(bar($1, $2))', None, None, 'invalid argument list')
2378 2377 >>> _parsealiasdecl('foo("string")')
2379 2378 ('foo("string")', None, None, 'invalid argument list')
2380 2379 >>> _parsealiasdecl('foo($1, $2')
2381 2380 ('foo($1, $2', None, None, 'at 10: unexpected token: end')
2382 2381 >>> _parsealiasdecl('foo("string')
2383 2382 ('foo("string', None, None, 'at 5: unterminated string')
2384 2383 >>> _parsealiasdecl('foo($1, $2, $1)')
2385 2384 ('foo', None, None, 'argument names collide with each other')
2386 2385 """
2387 2386 p = parser.parser(_tokenizealias, elements)
2388 2387 try:
2389 2388 tree, pos = p.parse(decl)
2390 2389 if (pos != len(decl)):
2391 2390 raise error.ParseError(_('invalid token'), pos)
2392 2391
2393 2392 if isvalidsymbol(tree):
2394 2393 # "name = ...." style
2395 2394 name = getsymbol(tree)
2396 2395 if name.startswith('$'):
2397 2396 return (decl, None, None, _("'$' not for alias arguments"))
2398 2397 return (name, ('symbol', name), None, None)
2399 2398
2400 2399 if isvalidfunc(tree):
2401 2400 # "name(arg, ....) = ...." style
2402 2401 name = getfuncname(tree)
2403 2402 if name.startswith('$'):
2404 2403 return (decl, None, None, _("'$' not for alias arguments"))
2405 2404 args = []
2406 2405 for arg in getfuncargs(tree):
2407 2406 if not isvalidsymbol(arg):
2408 2407 return (decl, None, None, _("invalid argument list"))
2409 2408 args.append(getsymbol(arg))
2410 2409 if len(args) != len(set(args)):
2411 2410 return (name, None, None,
2412 2411 _("argument names collide with each other"))
2413 2412 return (name, ('func', ('symbol', name)), args, None)
2414 2413
2415 2414 return (decl, None, None, _("invalid format"))
2416 2415 except error.ParseError, inst:
2417 2416 return (decl, None, None, parseerrordetail(inst))
2418 2417
2419 2418 def _parsealiasdefn(defn, args):
2420 2419 """Parse alias definition ``defn``
2421 2420
2422 2421 This function also replaces alias argument references in the
2423 2422 specified definition by ``_aliasarg(ARGNAME)``.
2424 2423
2425 2424 ``args`` is a list of alias argument names, or None if the alias
2426 2425 is declared as a symbol.
2427 2426
2428 2427 This returns "tree" as parsing result.
2429 2428
2430 2429 >>> args = ['$1', '$2', 'foo']
2431 2430 >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
2432 2431 (or
2433 2432 (func
2434 2433 ('symbol', '_aliasarg')
2435 2434 ('string', '$1'))
2436 2435 (func
2437 2436 ('symbol', '_aliasarg')
2438 2437 ('string', 'foo')))
2439 2438 >>> try:
2440 2439 ... _parsealiasdefn('$1 or $bar', args)
2441 2440 ... except error.ParseError, inst:
2442 2441 ... print parseerrordetail(inst)
2443 2442 at 6: '$' not for alias arguments
2444 2443 >>> args = ['$1', '$10', 'foo']
2445 2444 >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
2446 2445 (or
2447 2446 (func
2448 2447 ('symbol', '_aliasarg')
2449 2448 ('string', '$10'))
2450 2449 ('symbol', 'foobar'))
2451 2450 >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
2452 2451 (or
2453 2452 ('string', '$1')
2454 2453 ('string', 'foo'))
2455 2454 """
2456 2455 def tokenizedefn(program, lookup=None):
2457 2456 if args:
2458 2457 argset = set(args)
2459 2458 else:
2460 2459 argset = set()
2461 2460
2462 2461 for t, value, pos in _tokenizealias(program, lookup=lookup):
2463 2462 if t == 'symbol':
2464 2463 if value in argset:
2465 2464 # emulate tokenization of "_aliasarg('ARGNAME')":
2466 2465 # "_aliasarg()" is an unknown symbol only used separate
2467 2466 # alias argument placeholders from regular strings.
2468 2467 yield ('symbol', '_aliasarg', pos)
2469 2468 yield ('(', None, pos)
2470 2469 yield ('string', value, pos)
2471 2470 yield (')', None, pos)
2472 2471 continue
2473 2472 elif value.startswith('$'):
2474 2473 raise error.ParseError(_("'$' not for alias arguments"),
2475 2474 pos)
2476 2475 yield (t, value, pos)
2477 2476
2478 2477 p = parser.parser(tokenizedefn, elements)
2479 2478 tree, pos = p.parse(defn)
2480 2479 if pos != len(defn):
2481 2480 raise error.ParseError(_('invalid token'), pos)
2482 2481 return parser.simplifyinfixops(tree, ('or',))
2483 2482
2484 2483 class revsetalias(object):
2485 2484 # whether own `error` information is already shown or not.
2486 2485 # this avoids showing same warning multiple times at each `findaliases`.
2487 2486 warned = False
2488 2487
2489 2488 def __init__(self, name, value):
2490 2489 '''Aliases like:
2491 2490
2492 2491 h = heads(default)
2493 2492 b($1) = ancestors($1) - ancestors(default)
2494 2493 '''
2495 2494 self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
2496 2495 if self.error:
2497 2496 self.error = _('failed to parse the declaration of revset alias'
2498 2497 ' "%s": %s') % (self.name, self.error)
2499 2498 return
2500 2499
2501 2500 try:
2502 2501 self.replacement = _parsealiasdefn(value, self.args)
2503 2502 # Check for placeholder injection
2504 2503 _checkaliasarg(self.replacement, self.args)
2505 2504 except error.ParseError, inst:
2506 2505 self.error = _('failed to parse the definition of revset alias'
2507 2506 ' "%s": %s') % (self.name, parseerrordetail(inst))
2508 2507
2509 2508 def _getalias(aliases, tree):
2510 2509 """If tree looks like an unexpanded alias, return it. Return None
2511 2510 otherwise.
2512 2511 """
2513 2512 if isinstance(tree, tuple) and tree:
2514 2513 if tree[0] == 'symbol' and len(tree) == 2:
2515 2514 name = tree[1]
2516 2515 alias = aliases.get(name)
2517 2516 if alias and alias.args is None and alias.tree == tree:
2518 2517 return alias
2519 2518 if tree[0] == 'func' and len(tree) > 1:
2520 2519 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2521 2520 name = tree[1][1]
2522 2521 alias = aliases.get(name)
2523 2522 if alias and alias.args is not None and alias.tree == tree[:2]:
2524 2523 return alias
2525 2524 return None
2526 2525
2527 2526 def _expandargs(tree, args):
2528 2527 """Replace _aliasarg instances with the substitution value of the
2529 2528 same name in args, recursively.
2530 2529 """
2531 2530 if not tree or not isinstance(tree, tuple):
2532 2531 return tree
2533 2532 arg = _getaliasarg(tree)
2534 2533 if arg is not None:
2535 2534 return args[arg]
2536 2535 return tuple(_expandargs(t, args) for t in tree)
2537 2536
2538 2537 def _expandaliases(aliases, tree, expanding, cache):
2539 2538 """Expand aliases in tree, recursively.
2540 2539
2541 2540 'aliases' is a dictionary mapping user defined aliases to
2542 2541 revsetalias objects.
2543 2542 """
2544 2543 if not isinstance(tree, tuple):
2545 2544 # Do not expand raw strings
2546 2545 return tree
2547 2546 alias = _getalias(aliases, tree)
2548 2547 if alias is not None:
2549 2548 if alias.error:
2550 2549 raise util.Abort(alias.error)
2551 2550 if alias in expanding:
2552 2551 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2553 2552 'detected') % alias.name)
2554 2553 expanding.append(alias)
2555 2554 if alias.name not in cache:
2556 2555 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2557 2556 expanding, cache)
2558 2557 result = cache[alias.name]
2559 2558 expanding.pop()
2560 2559 if alias.args is not None:
2561 2560 l = getlist(tree[2])
2562 2561 if len(l) != len(alias.args):
2563 2562 raise error.ParseError(
2564 2563 _('invalid number of arguments: %s') % len(l))
2565 2564 l = [_expandaliases(aliases, a, [], cache) for a in l]
2566 2565 result = _expandargs(result, dict(zip(alias.args, l)))
2567 2566 else:
2568 2567 result = tuple(_expandaliases(aliases, t, expanding, cache)
2569 2568 for t in tree)
2570 2569 return result
2571 2570
2572 2571 def findaliases(ui, tree, showwarning=None):
2573 2572 _checkaliasarg(tree)
2574 2573 aliases = {}
2575 2574 for k, v in ui.configitems('revsetalias'):
2576 2575 alias = revsetalias(k, v)
2577 2576 aliases[alias.name] = alias
2578 2577 tree = _expandaliases(aliases, tree, [], {})
2579 2578 if showwarning:
2580 2579 # warn about problematic (but not referred) aliases
2581 2580 for name, alias in sorted(aliases.iteritems()):
2582 2581 if alias.error and not alias.warned:
2583 2582 showwarning(_('warning: %s\n') % (alias.error))
2584 2583 alias.warned = True
2585 2584 return tree
2586 2585
2587 2586 def foldconcat(tree):
2588 2587 """Fold elements to be concatenated by `##`
2589 2588 """
2590 2589 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2591 2590 return tree
2592 2591 if tree[0] == '_concat':
2593 2592 pending = [tree]
2594 2593 l = []
2595 2594 while pending:
2596 2595 e = pending.pop()
2597 2596 if e[0] == '_concat':
2598 2597 pending.extend(reversed(e[1:]))
2599 2598 elif e[0] in ('string', 'symbol'):
2600 2599 l.append(e[1])
2601 2600 else:
2602 2601 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2603 2602 raise error.ParseError(msg)
2604 2603 return ('string', ''.join(l))
2605 2604 else:
2606 2605 return tuple(foldconcat(t) for t in tree)
2607 2606
2608 2607 def parse(spec, lookup=None):
2609 2608 p = parser.parser(tokenize, elements)
2610 2609 tree, pos = p.parse(spec, lookup=lookup)
2611 2610 if pos != len(spec):
2612 2611 raise error.ParseError(_("invalid token"), pos)
2613 2612 return parser.simplifyinfixops(tree, ('or',))
2614 2613
2615 2614 def posttreebuilthook(tree, repo):
2616 2615 # hook for extensions to execute code on the optimized tree
2617 2616 pass
2618 2617
2619 2618 def match(ui, spec, repo=None):
2620 2619 if not spec:
2621 2620 raise error.ParseError(_("empty query"))
2622 2621 lookup = None
2623 2622 if repo:
2624 2623 lookup = repo.__contains__
2625 2624 tree = parse(spec, lookup)
2626 2625 if ui:
2627 2626 tree = findaliases(ui, tree, showwarning=ui.warn)
2628 2627 tree = foldconcat(tree)
2629 2628 weight, tree = optimize(tree, True)
2630 2629 posttreebuilthook(tree, repo)
2631 2630 def mfunc(repo, subset=None):
2632 2631 if subset is None:
2633 2632 subset = fullreposet(repo)
2634 2633 if util.safehasattr(subset, 'isascending'):
2635 2634 result = getset(repo, subset, tree)
2636 2635 else:
2637 2636 result = getset(repo, baseset(subset), tree)
2638 2637 return result
2639 2638 return mfunc
2640 2639
2641 2640 def formatspec(expr, *args):
2642 2641 '''
2643 2642 This is a convenience function for using revsets internally, and
2644 2643 escapes arguments appropriately. Aliases are intentionally ignored
2645 2644 so that intended expression behavior isn't accidentally subverted.
2646 2645
2647 2646 Supported arguments:
2648 2647
2649 2648 %r = revset expression, parenthesized
2650 2649 %d = int(arg), no quoting
2651 2650 %s = string(arg), escaped and single-quoted
2652 2651 %b = arg.branch(), escaped and single-quoted
2653 2652 %n = hex(arg), single-quoted
2654 2653 %% = a literal '%'
2655 2654
2656 2655 Prefixing the type with 'l' specifies a parenthesized list of that type.
2657 2656
2658 2657 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2659 2658 '(10 or 11):: and ((this()) or (that()))'
2660 2659 >>> formatspec('%d:: and not %d::', 10, 20)
2661 2660 '10:: and not 20::'
2662 2661 >>> formatspec('%ld or %ld', [], [1])
2663 2662 "_list('') or 1"
2664 2663 >>> formatspec('keyword(%s)', 'foo\\xe9')
2665 2664 "keyword('foo\\\\xe9')"
2666 2665 >>> b = lambda: 'default'
2667 2666 >>> b.branch = b
2668 2667 >>> formatspec('branch(%b)', b)
2669 2668 "branch('default')"
2670 2669 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2671 2670 "root(_list('a\\x00b\\x00c\\x00d'))"
2672 2671 '''
2673 2672
2674 2673 def quote(s):
2675 2674 return repr(str(s))
2676 2675
2677 2676 def argtype(c, arg):
2678 2677 if c == 'd':
2679 2678 return str(int(arg))
2680 2679 elif c == 's':
2681 2680 return quote(arg)
2682 2681 elif c == 'r':
2683 2682 parse(arg) # make sure syntax errors are confined
2684 2683 return '(%s)' % arg
2685 2684 elif c == 'n':
2686 2685 return quote(node.hex(arg))
2687 2686 elif c == 'b':
2688 2687 return quote(arg.branch())
2689 2688
2690 2689 def listexp(s, t):
2691 2690 l = len(s)
2692 2691 if l == 0:
2693 2692 return "_list('')"
2694 2693 elif l == 1:
2695 2694 return argtype(t, s[0])
2696 2695 elif t == 'd':
2697 2696 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2698 2697 elif t == 's':
2699 2698 return "_list('%s')" % "\0".join(s)
2700 2699 elif t == 'n':
2701 2700 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2702 2701 elif t == 'b':
2703 2702 return "_list('%s')" % "\0".join(a.branch() for a in s)
2704 2703
2705 2704 m = l // 2
2706 2705 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2707 2706
2708 2707 ret = ''
2709 2708 pos = 0
2710 2709 arg = 0
2711 2710 while pos < len(expr):
2712 2711 c = expr[pos]
2713 2712 if c == '%':
2714 2713 pos += 1
2715 2714 d = expr[pos]
2716 2715 if d == '%':
2717 2716 ret += d
2718 2717 elif d in 'dsnbr':
2719 2718 ret += argtype(d, args[arg])
2720 2719 arg += 1
2721 2720 elif d == 'l':
2722 2721 # a list of some type
2723 2722 pos += 1
2724 2723 d = expr[pos]
2725 2724 ret += listexp(list(args[arg]), d)
2726 2725 arg += 1
2727 2726 else:
2728 2727 raise util.Abort('unexpected revspec format character %s' % d)
2729 2728 else:
2730 2729 ret += c
2731 2730 pos += 1
2732 2731
2733 2732 return ret
2734 2733
2735 2734 def prettyformat(tree):
2736 2735 return parser.prettyformat(tree, ('string', 'symbol'))
2737 2736
2738 2737 def depth(tree):
2739 2738 if isinstance(tree, tuple):
2740 2739 return max(map(depth, tree)) + 1
2741 2740 else:
2742 2741 return 0
2743 2742
2744 2743 def funcsused(tree):
2745 2744 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2746 2745 return set()
2747 2746 else:
2748 2747 funcs = set()
2749 2748 for s in tree[1:]:
2750 2749 funcs |= funcsused(s)
2751 2750 if tree[0] == 'func':
2752 2751 funcs.add(tree[1][1])
2753 2752 return funcs
2754 2753
2755 2754 class abstractsmartset(object):
2756 2755
2757 2756 def __nonzero__(self):
2758 2757 """True if the smartset is not empty"""
2759 2758 raise NotImplementedError()
2760 2759
2761 2760 def __contains__(self, rev):
2762 2761 """provide fast membership testing"""
2763 2762 raise NotImplementedError()
2764 2763
2765 2764 def __iter__(self):
2766 2765 """iterate the set in the order it is supposed to be iterated"""
2767 2766 raise NotImplementedError()
2768 2767
2769 2768 # Attributes containing a function to perform a fast iteration in a given
2770 2769 # direction. A smartset can have none, one, or both defined.
2771 2770 #
2772 2771 # Default value is None instead of a function returning None to avoid
2773 2772 # initializing an iterator just for testing if a fast method exists.
2774 2773 fastasc = None
2775 2774 fastdesc = None
2776 2775
2777 2776 def isascending(self):
2778 2777 """True if the set will iterate in ascending order"""
2779 2778 raise NotImplementedError()
2780 2779
2781 2780 def isdescending(self):
2782 2781 """True if the set will iterate in descending order"""
2783 2782 raise NotImplementedError()
2784 2783
2785 2784 def min(self):
2786 2785 """return the minimum element in the set"""
2787 2786 if self.fastasc is not None:
2788 2787 for r in self.fastasc():
2789 2788 return r
2790 2789 raise ValueError('arg is an empty sequence')
2791 2790 return min(self)
2792 2791
2793 2792 def max(self):
2794 2793 """return the maximum element in the set"""
2795 2794 if self.fastdesc is not None:
2796 2795 for r in self.fastdesc():
2797 2796 return r
2798 2797 raise ValueError('arg is an empty sequence')
2799 2798 return max(self)
2800 2799
2801 2800 def first(self):
2802 2801 """return the first element in the set (user iteration perspective)
2803 2802
2804 2803 Return None if the set is empty"""
2805 2804 raise NotImplementedError()
2806 2805
2807 2806 def last(self):
2808 2807 """return the last element in the set (user iteration perspective)
2809 2808
2810 2809 Return None if the set is empty"""
2811 2810 raise NotImplementedError()
2812 2811
2813 2812 def __len__(self):
2814 2813 """return the length of the smartsets
2815 2814
2816 2815 This can be expensive on smartset that could be lazy otherwise."""
2817 2816 raise NotImplementedError()
2818 2817
2819 2818 def reverse(self):
2820 2819 """reverse the expected iteration order"""
2821 2820 raise NotImplementedError()
2822 2821
2823 2822 def sort(self, reverse=True):
2824 2823 """get the set to iterate in an ascending or descending order"""
2825 2824 raise NotImplementedError()
2826 2825
2827 2826 def __and__(self, other):
2828 2827 """Returns a new object with the intersection of the two collections.
2829 2828
2830 2829 This is part of the mandatory API for smartset."""
2831 2830 if isinstance(other, fullreposet):
2832 2831 return self
2833 2832 return self.filter(other.__contains__, cache=False)
2834 2833
2835 2834 def __add__(self, other):
2836 2835 """Returns a new object with the union of the two collections.
2837 2836
2838 2837 This is part of the mandatory API for smartset."""
2839 2838 return addset(self, other)
2840 2839
2841 2840 def __sub__(self, other):
2842 2841 """Returns a new object with the substraction of the two collections.
2843 2842
2844 2843 This is part of the mandatory API for smartset."""
2845 2844 c = other.__contains__
2846 2845 return self.filter(lambda r: not c(r), cache=False)
2847 2846
2848 2847 def filter(self, condition, cache=True):
2849 2848 """Returns this smartset filtered by condition as a new smartset.
2850 2849
2851 2850 `condition` is a callable which takes a revision number and returns a
2852 2851 boolean.
2853 2852
2854 2853 This is part of the mandatory API for smartset."""
2855 2854 # builtin cannot be cached. but do not needs to
2856 2855 if cache and util.safehasattr(condition, 'func_code'):
2857 2856 condition = util.cachefunc(condition)
2858 2857 return filteredset(self, condition)
2859 2858
2860 2859 class baseset(abstractsmartset):
2861 2860 """Basic data structure that represents a revset and contains the basic
2862 2861 operation that it should be able to perform.
2863 2862
2864 2863 Every method in this class should be implemented by any smartset class.
2865 2864 """
2866 2865 def __init__(self, data=()):
2867 2866 if not isinstance(data, list):
2868 2867 data = list(data)
2869 2868 self._list = data
2870 2869 self._ascending = None
2871 2870
2872 2871 @util.propertycache
2873 2872 def _set(self):
2874 2873 return set(self._list)
2875 2874
2876 2875 @util.propertycache
2877 2876 def _asclist(self):
2878 2877 asclist = self._list[:]
2879 2878 asclist.sort()
2880 2879 return asclist
2881 2880
2882 2881 def __iter__(self):
2883 2882 if self._ascending is None:
2884 2883 return iter(self._list)
2885 2884 elif self._ascending:
2886 2885 return iter(self._asclist)
2887 2886 else:
2888 2887 return reversed(self._asclist)
2889 2888
2890 2889 def fastasc(self):
2891 2890 return iter(self._asclist)
2892 2891
2893 2892 def fastdesc(self):
2894 2893 return reversed(self._asclist)
2895 2894
2896 2895 @util.propertycache
2897 2896 def __contains__(self):
2898 2897 return self._set.__contains__
2899 2898
2900 2899 def __nonzero__(self):
2901 2900 return bool(self._list)
2902 2901
2903 2902 def sort(self, reverse=False):
2904 2903 self._ascending = not bool(reverse)
2905 2904
2906 2905 def reverse(self):
2907 2906 if self._ascending is None:
2908 2907 self._list.reverse()
2909 2908 else:
2910 2909 self._ascending = not self._ascending
2911 2910
2912 2911 def __len__(self):
2913 2912 return len(self._list)
2914 2913
2915 2914 def isascending(self):
2916 2915 """Returns True if the collection is ascending order, False if not.
2917 2916
2918 2917 This is part of the mandatory API for smartset."""
2919 2918 if len(self) <= 1:
2920 2919 return True
2921 2920 return self._ascending is not None and self._ascending
2922 2921
2923 2922 def isdescending(self):
2924 2923 """Returns True if the collection is descending order, False if not.
2925 2924
2926 2925 This is part of the mandatory API for smartset."""
2927 2926 if len(self) <= 1:
2928 2927 return True
2929 2928 return self._ascending is not None and not self._ascending
2930 2929
2931 2930 def first(self):
2932 2931 if self:
2933 2932 if self._ascending is None:
2934 2933 return self._list[0]
2935 2934 elif self._ascending:
2936 2935 return self._asclist[0]
2937 2936 else:
2938 2937 return self._asclist[-1]
2939 2938 return None
2940 2939
2941 2940 def last(self):
2942 2941 if self:
2943 2942 if self._ascending is None:
2944 2943 return self._list[-1]
2945 2944 elif self._ascending:
2946 2945 return self._asclist[-1]
2947 2946 else:
2948 2947 return self._asclist[0]
2949 2948 return None
2950 2949
2951 2950 def __repr__(self):
2952 2951 d = {None: '', False: '-', True: '+'}[self._ascending]
2953 2952 return '<%s%s %r>' % (type(self).__name__, d, self._list)
2954 2953
2955 2954 class filteredset(abstractsmartset):
2956 2955 """Duck type for baseset class which iterates lazily over the revisions in
2957 2956 the subset and contains a function which tests for membership in the
2958 2957 revset
2959 2958 """
2960 2959 def __init__(self, subset, condition=lambda x: True):
2961 2960 """
2962 2961 condition: a function that decide whether a revision in the subset
2963 2962 belongs to the revset or not.
2964 2963 """
2965 2964 self._subset = subset
2966 2965 self._condition = condition
2967 2966 self._cache = {}
2968 2967
2969 2968 def __contains__(self, x):
2970 2969 c = self._cache
2971 2970 if x not in c:
2972 2971 v = c[x] = x in self._subset and self._condition(x)
2973 2972 return v
2974 2973 return c[x]
2975 2974
2976 2975 def __iter__(self):
2977 2976 return self._iterfilter(self._subset)
2978 2977
2979 2978 def _iterfilter(self, it):
2980 2979 cond = self._condition
2981 2980 for x in it:
2982 2981 if cond(x):
2983 2982 yield x
2984 2983
2985 2984 @property
2986 2985 def fastasc(self):
2987 2986 it = self._subset.fastasc
2988 2987 if it is None:
2989 2988 return None
2990 2989 return lambda: self._iterfilter(it())
2991 2990
2992 2991 @property
2993 2992 def fastdesc(self):
2994 2993 it = self._subset.fastdesc
2995 2994 if it is None:
2996 2995 return None
2997 2996 return lambda: self._iterfilter(it())
2998 2997
2999 2998 def __nonzero__(self):
3000 2999 for r in self:
3001 3000 return True
3002 3001 return False
3003 3002
3004 3003 def __len__(self):
3005 3004 # Basic implementation to be changed in future patches.
3006 3005 l = baseset([r for r in self])
3007 3006 return len(l)
3008 3007
3009 3008 def sort(self, reverse=False):
3010 3009 self._subset.sort(reverse=reverse)
3011 3010
3012 3011 def reverse(self):
3013 3012 self._subset.reverse()
3014 3013
3015 3014 def isascending(self):
3016 3015 return self._subset.isascending()
3017 3016
3018 3017 def isdescending(self):
3019 3018 return self._subset.isdescending()
3020 3019
3021 3020 def first(self):
3022 3021 for x in self:
3023 3022 return x
3024 3023 return None
3025 3024
3026 3025 def last(self):
3027 3026 it = None
3028 3027 if self._subset.isascending:
3029 3028 it = self.fastdesc
3030 3029 elif self._subset.isdescending:
3031 3030 it = self.fastdesc
3032 3031 if it is None:
3033 3032 # slowly consume everything. This needs improvement
3034 3033 it = lambda: reversed(list(self))
3035 3034 for x in it():
3036 3035 return x
3037 3036 return None
3038 3037
3039 3038 def __repr__(self):
3040 3039 return '<%s %r>' % (type(self).__name__, self._subset)
3041 3040
3042 3041 # this function will be removed, or merged to addset or orset, when
3043 3042 # - scmutil.revrange() can be rewritten to not combine calculated smartsets
3044 3043 # - or addset can handle more than two sets without balanced tree
3045 3044 def _combinesets(subsets):
3046 3045 """Create balanced tree of addsets representing union of given sets"""
3047 3046 if not subsets:
3048 3047 return baseset()
3049 3048 if len(subsets) == 1:
3050 3049 return subsets[0]
3051 3050 p = len(subsets) // 2
3052 3051 xs = _combinesets(subsets[:p])
3053 3052 ys = _combinesets(subsets[p:])
3054 3053 return addset(xs, ys)
3055 3054
3056 3055 def _iterordered(ascending, iter1, iter2):
3057 3056 """produce an ordered iteration from two iterators with the same order
3058 3057
3059 3058 The ascending is used to indicated the iteration direction.
3060 3059 """
3061 3060 choice = max
3062 3061 if ascending:
3063 3062 choice = min
3064 3063
3065 3064 val1 = None
3066 3065 val2 = None
3067 3066 try:
3068 3067 # Consume both iterators in an ordered way until one is empty
3069 3068 while True:
3070 3069 if val1 is None:
3071 3070 val1 = iter1.next()
3072 3071 if val2 is None:
3073 3072 val2 = iter2.next()
3074 3073 next = choice(val1, val2)
3075 3074 yield next
3076 3075 if val1 == next:
3077 3076 val1 = None
3078 3077 if val2 == next:
3079 3078 val2 = None
3080 3079 except StopIteration:
3081 3080 # Flush any remaining values and consume the other one
3082 3081 it = iter2
3083 3082 if val1 is not None:
3084 3083 yield val1
3085 3084 it = iter1
3086 3085 elif val2 is not None:
3087 3086 # might have been equality and both are empty
3088 3087 yield val2
3089 3088 for val in it:
3090 3089 yield val
3091 3090
3092 3091 class addset(abstractsmartset):
3093 3092 """Represent the addition of two sets
3094 3093
3095 3094 Wrapper structure for lazily adding two structures without losing much
3096 3095 performance on the __contains__ method
3097 3096
3098 3097 If the ascending attribute is set, that means the two structures are
3099 3098 ordered in either an ascending or descending way. Therefore, we can add
3100 3099 them maintaining the order by iterating over both at the same time
3101 3100
3102 3101 >>> xs = baseset([0, 3, 2])
3103 3102 >>> ys = baseset([5, 2, 4])
3104 3103
3105 3104 >>> rs = addset(xs, ys)
3106 3105 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3107 3106 (True, True, False, True, 0, 4)
3108 3107 >>> rs = addset(xs, baseset([]))
3109 3108 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3110 3109 (True, True, False, 0, 2)
3111 3110 >>> rs = addset(baseset([]), baseset([]))
3112 3111 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3113 3112 (False, False, None, None)
3114 3113
3115 3114 iterate unsorted:
3116 3115 >>> rs = addset(xs, ys)
3117 3116 >>> [x for x in rs] # without _genlist
3118 3117 [0, 3, 2, 5, 4]
3119 3118 >>> assert not rs._genlist
3120 3119 >>> len(rs)
3121 3120 5
3122 3121 >>> [x for x in rs] # with _genlist
3123 3122 [0, 3, 2, 5, 4]
3124 3123 >>> assert rs._genlist
3125 3124
3126 3125 iterate ascending:
3127 3126 >>> rs = addset(xs, ys, ascending=True)
3128 3127 >>> [x for x in rs], [x for x in rs.fastasc()] # without _asclist
3129 3128 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3130 3129 >>> assert not rs._asclist
3131 3130 >>> len(rs)
3132 3131 5
3133 3132 >>> [x for x in rs], [x for x in rs.fastasc()]
3134 3133 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3135 3134 >>> assert rs._asclist
3136 3135
3137 3136 iterate descending:
3138 3137 >>> rs = addset(xs, ys, ascending=False)
3139 3138 >>> [x for x in rs], [x for x in rs.fastdesc()] # without _asclist
3140 3139 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3141 3140 >>> assert not rs._asclist
3142 3141 >>> len(rs)
3143 3142 5
3144 3143 >>> [x for x in rs], [x for x in rs.fastdesc()]
3145 3144 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3146 3145 >>> assert rs._asclist
3147 3146
3148 3147 iterate ascending without fastasc:
3149 3148 >>> rs = addset(xs, generatorset(ys), ascending=True)
3150 3149 >>> assert rs.fastasc is None
3151 3150 >>> [x for x in rs]
3152 3151 [0, 2, 3, 4, 5]
3153 3152
3154 3153 iterate descending without fastdesc:
3155 3154 >>> rs = addset(generatorset(xs), ys, ascending=False)
3156 3155 >>> assert rs.fastdesc is None
3157 3156 >>> [x for x in rs]
3158 3157 [5, 4, 3, 2, 0]
3159 3158 """
3160 3159 def __init__(self, revs1, revs2, ascending=None):
3161 3160 self._r1 = revs1
3162 3161 self._r2 = revs2
3163 3162 self._iter = None
3164 3163 self._ascending = ascending
3165 3164 self._genlist = None
3166 3165 self._asclist = None
3167 3166
3168 3167 def __len__(self):
3169 3168 return len(self._list)
3170 3169
3171 3170 def __nonzero__(self):
3172 3171 return bool(self._r1) or bool(self._r2)
3173 3172
3174 3173 @util.propertycache
3175 3174 def _list(self):
3176 3175 if not self._genlist:
3177 3176 self._genlist = baseset(iter(self))
3178 3177 return self._genlist
3179 3178
3180 3179 def __iter__(self):
3181 3180 """Iterate over both collections without repeating elements
3182 3181
3183 3182 If the ascending attribute is not set, iterate over the first one and
3184 3183 then over the second one checking for membership on the first one so we
3185 3184 dont yield any duplicates.
3186 3185
3187 3186 If the ascending attribute is set, iterate over both collections at the
3188 3187 same time, yielding only one value at a time in the given order.
3189 3188 """
3190 3189 if self._ascending is None:
3191 3190 if self._genlist:
3192 3191 return iter(self._genlist)
3193 3192 def arbitraryordergen():
3194 3193 for r in self._r1:
3195 3194 yield r
3196 3195 inr1 = self._r1.__contains__
3197 3196 for r in self._r2:
3198 3197 if not inr1(r):
3199 3198 yield r
3200 3199 return arbitraryordergen()
3201 3200 # try to use our own fast iterator if it exists
3202 3201 self._trysetasclist()
3203 3202 if self._ascending:
3204 3203 attr = 'fastasc'
3205 3204 else:
3206 3205 attr = 'fastdesc'
3207 3206 it = getattr(self, attr)
3208 3207 if it is not None:
3209 3208 return it()
3210 3209 # maybe half of the component supports fast
3211 3210 # get iterator for _r1
3212 3211 iter1 = getattr(self._r1, attr)
3213 3212 if iter1 is None:
3214 3213 # let's avoid side effect (not sure it matters)
3215 3214 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3216 3215 else:
3217 3216 iter1 = iter1()
3218 3217 # get iterator for _r2
3219 3218 iter2 = getattr(self._r2, attr)
3220 3219 if iter2 is None:
3221 3220 # let's avoid side effect (not sure it matters)
3222 3221 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3223 3222 else:
3224 3223 iter2 = iter2()
3225 3224 return _iterordered(self._ascending, iter1, iter2)
3226 3225
3227 3226 def _trysetasclist(self):
3228 3227 """populate the _asclist attribute if possible and necessary"""
3229 3228 if self._genlist is not None and self._asclist is None:
3230 3229 self._asclist = sorted(self._genlist)
3231 3230
3232 3231 @property
3233 3232 def fastasc(self):
3234 3233 self._trysetasclist()
3235 3234 if self._asclist is not None:
3236 3235 return self._asclist.__iter__
3237 3236 iter1 = self._r1.fastasc
3238 3237 iter2 = self._r2.fastasc
3239 3238 if None in (iter1, iter2):
3240 3239 return None
3241 3240 return lambda: _iterordered(True, iter1(), iter2())
3242 3241
3243 3242 @property
3244 3243 def fastdesc(self):
3245 3244 self._trysetasclist()
3246 3245 if self._asclist is not None:
3247 3246 return self._asclist.__reversed__
3248 3247 iter1 = self._r1.fastdesc
3249 3248 iter2 = self._r2.fastdesc
3250 3249 if None in (iter1, iter2):
3251 3250 return None
3252 3251 return lambda: _iterordered(False, iter1(), iter2())
3253 3252
3254 3253 def __contains__(self, x):
3255 3254 return x in self._r1 or x in self._r2
3256 3255
3257 3256 def sort(self, reverse=False):
3258 3257 """Sort the added set
3259 3258
3260 3259 For this we use the cached list with all the generated values and if we
3261 3260 know they are ascending or descending we can sort them in a smart way.
3262 3261 """
3263 3262 self._ascending = not reverse
3264 3263
3265 3264 def isascending(self):
3266 3265 return self._ascending is not None and self._ascending
3267 3266
3268 3267 def isdescending(self):
3269 3268 return self._ascending is not None and not self._ascending
3270 3269
3271 3270 def reverse(self):
3272 3271 if self._ascending is None:
3273 3272 self._list.reverse()
3274 3273 else:
3275 3274 self._ascending = not self._ascending
3276 3275
3277 3276 def first(self):
3278 3277 for x in self:
3279 3278 return x
3280 3279 return None
3281 3280
3282 3281 def last(self):
3283 3282 self.reverse()
3284 3283 val = self.first()
3285 3284 self.reverse()
3286 3285 return val
3287 3286
3288 3287 def __repr__(self):
3289 3288 d = {None: '', False: '-', True: '+'}[self._ascending]
3290 3289 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3291 3290
3292 3291 class generatorset(abstractsmartset):
3293 3292 """Wrap a generator for lazy iteration
3294 3293
3295 3294 Wrapper structure for generators that provides lazy membership and can
3296 3295 be iterated more than once.
3297 3296 When asked for membership it generates values until either it finds the
3298 3297 requested one or has gone through all the elements in the generator
3299 3298 """
3300 3299 def __init__(self, gen, iterasc=None):
3301 3300 """
3302 3301 gen: a generator producing the values for the generatorset.
3303 3302 """
3304 3303 self._gen = gen
3305 3304 self._asclist = None
3306 3305 self._cache = {}
3307 3306 self._genlist = []
3308 3307 self._finished = False
3309 3308 self._ascending = True
3310 3309 if iterasc is not None:
3311 3310 if iterasc:
3312 3311 self.fastasc = self._iterator
3313 3312 self.__contains__ = self._asccontains
3314 3313 else:
3315 3314 self.fastdesc = self._iterator
3316 3315 self.__contains__ = self._desccontains
3317 3316
3318 3317 def __nonzero__(self):
3319 3318 # Do not use 'for r in self' because it will enforce the iteration
3320 3319 # order (default ascending), possibly unrolling a whole descending
3321 3320 # iterator.
3322 3321 if self._genlist:
3323 3322 return True
3324 3323 for r in self._consumegen():
3325 3324 return True
3326 3325 return False
3327 3326
3328 3327 def __contains__(self, x):
3329 3328 if x in self._cache:
3330 3329 return self._cache[x]
3331 3330
3332 3331 # Use new values only, as existing values would be cached.
3333 3332 for l in self._consumegen():
3334 3333 if l == x:
3335 3334 return True
3336 3335
3337 3336 self._cache[x] = False
3338 3337 return False
3339 3338
3340 3339 def _asccontains(self, x):
3341 3340 """version of contains optimised for ascending generator"""
3342 3341 if x in self._cache:
3343 3342 return self._cache[x]
3344 3343
3345 3344 # Use new values only, as existing values would be cached.
3346 3345 for l in self._consumegen():
3347 3346 if l == x:
3348 3347 return True
3349 3348 if l > x:
3350 3349 break
3351 3350
3352 3351 self._cache[x] = False
3353 3352 return False
3354 3353
3355 3354 def _desccontains(self, x):
3356 3355 """version of contains optimised for descending generator"""
3357 3356 if x in self._cache:
3358 3357 return self._cache[x]
3359 3358
3360 3359 # Use new values only, as existing values would be cached.
3361 3360 for l in self._consumegen():
3362 3361 if l == x:
3363 3362 return True
3364 3363 if l < x:
3365 3364 break
3366 3365
3367 3366 self._cache[x] = False
3368 3367 return False
3369 3368
3370 3369 def __iter__(self):
3371 3370 if self._ascending:
3372 3371 it = self.fastasc
3373 3372 else:
3374 3373 it = self.fastdesc
3375 3374 if it is not None:
3376 3375 return it()
3377 3376 # we need to consume the iterator
3378 3377 for x in self._consumegen():
3379 3378 pass
3380 3379 # recall the same code
3381 3380 return iter(self)
3382 3381
3383 3382 def _iterator(self):
3384 3383 if self._finished:
3385 3384 return iter(self._genlist)
3386 3385
3387 3386 # We have to use this complex iteration strategy to allow multiple
3388 3387 # iterations at the same time. We need to be able to catch revision
3389 3388 # removed from _consumegen and added to genlist in another instance.
3390 3389 #
3391 3390 # Getting rid of it would provide an about 15% speed up on this
3392 3391 # iteration.
3393 3392 genlist = self._genlist
3394 3393 nextrev = self._consumegen().next
3395 3394 _len = len # cache global lookup
3396 3395 def gen():
3397 3396 i = 0
3398 3397 while True:
3399 3398 if i < _len(genlist):
3400 3399 yield genlist[i]
3401 3400 else:
3402 3401 yield nextrev()
3403 3402 i += 1
3404 3403 return gen()
3405 3404
3406 3405 def _consumegen(self):
3407 3406 cache = self._cache
3408 3407 genlist = self._genlist.append
3409 3408 for item in self._gen:
3410 3409 cache[item] = True
3411 3410 genlist(item)
3412 3411 yield item
3413 3412 if not self._finished:
3414 3413 self._finished = True
3415 3414 asc = self._genlist[:]
3416 3415 asc.sort()
3417 3416 self._asclist = asc
3418 3417 self.fastasc = asc.__iter__
3419 3418 self.fastdesc = asc.__reversed__
3420 3419
3421 3420 def __len__(self):
3422 3421 for x in self._consumegen():
3423 3422 pass
3424 3423 return len(self._genlist)
3425 3424
3426 3425 def sort(self, reverse=False):
3427 3426 self._ascending = not reverse
3428 3427
3429 3428 def reverse(self):
3430 3429 self._ascending = not self._ascending
3431 3430
3432 3431 def isascending(self):
3433 3432 return self._ascending
3434 3433
3435 3434 def isdescending(self):
3436 3435 return not self._ascending
3437 3436
3438 3437 def first(self):
3439 3438 if self._ascending:
3440 3439 it = self.fastasc
3441 3440 else:
3442 3441 it = self.fastdesc
3443 3442 if it is None:
3444 3443 # we need to consume all and try again
3445 3444 for x in self._consumegen():
3446 3445 pass
3447 3446 return self.first()
3448 3447 return next(it(), None)
3449 3448
3450 3449 def last(self):
3451 3450 if self._ascending:
3452 3451 it = self.fastdesc
3453 3452 else:
3454 3453 it = self.fastasc
3455 3454 if it is None:
3456 3455 # we need to consume all and try again
3457 3456 for x in self._consumegen():
3458 3457 pass
3459 3458 return self.first()
3460 3459 return next(it(), None)
3461 3460
3462 3461 def __repr__(self):
3463 3462 d = {False: '-', True: '+'}[self._ascending]
3464 3463 return '<%s%s>' % (type(self).__name__, d)
3465 3464
3466 3465 class spanset(abstractsmartset):
3467 3466 """Duck type for baseset class which represents a range of revisions and
3468 3467 can work lazily and without having all the range in memory
3469 3468
3470 3469 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3471 3470 notable points:
3472 3471 - when x < y it will be automatically descending,
3473 3472 - revision filtered with this repoview will be skipped.
3474 3473
3475 3474 """
3476 3475 def __init__(self, repo, start=0, end=None):
3477 3476 """
3478 3477 start: first revision included the set
3479 3478 (default to 0)
3480 3479 end: first revision excluded (last+1)
3481 3480 (default to len(repo)
3482 3481
3483 3482 Spanset will be descending if `end` < `start`.
3484 3483 """
3485 3484 if end is None:
3486 3485 end = len(repo)
3487 3486 self._ascending = start <= end
3488 3487 if not self._ascending:
3489 3488 start, end = end + 1, start +1
3490 3489 self._start = start
3491 3490 self._end = end
3492 3491 self._hiddenrevs = repo.changelog.filteredrevs
3493 3492
3494 3493 def sort(self, reverse=False):
3495 3494 self._ascending = not reverse
3496 3495
3497 3496 def reverse(self):
3498 3497 self._ascending = not self._ascending
3499 3498
3500 3499 def _iterfilter(self, iterrange):
3501 3500 s = self._hiddenrevs
3502 3501 for r in iterrange:
3503 3502 if r not in s:
3504 3503 yield r
3505 3504
3506 3505 def __iter__(self):
3507 3506 if self._ascending:
3508 3507 return self.fastasc()
3509 3508 else:
3510 3509 return self.fastdesc()
3511 3510
3512 3511 def fastasc(self):
3513 3512 iterrange = xrange(self._start, self._end)
3514 3513 if self._hiddenrevs:
3515 3514 return self._iterfilter(iterrange)
3516 3515 return iter(iterrange)
3517 3516
3518 3517 def fastdesc(self):
3519 3518 iterrange = xrange(self._end - 1, self._start - 1, -1)
3520 3519 if self._hiddenrevs:
3521 3520 return self._iterfilter(iterrange)
3522 3521 return iter(iterrange)
3523 3522
3524 3523 def __contains__(self, rev):
3525 3524 hidden = self._hiddenrevs
3526 3525 return ((self._start <= rev < self._end)
3527 3526 and not (hidden and rev in hidden))
3528 3527
3529 3528 def __nonzero__(self):
3530 3529 for r in self:
3531 3530 return True
3532 3531 return False
3533 3532
3534 3533 def __len__(self):
3535 3534 if not self._hiddenrevs:
3536 3535 return abs(self._end - self._start)
3537 3536 else:
3538 3537 count = 0
3539 3538 start = self._start
3540 3539 end = self._end
3541 3540 for rev in self._hiddenrevs:
3542 3541 if (end < rev <= start) or (start <= rev < end):
3543 3542 count += 1
3544 3543 return abs(self._end - self._start) - count
3545 3544
3546 3545 def isascending(self):
3547 3546 return self._ascending
3548 3547
3549 3548 def isdescending(self):
3550 3549 return not self._ascending
3551 3550
3552 3551 def first(self):
3553 3552 if self._ascending:
3554 3553 it = self.fastasc
3555 3554 else:
3556 3555 it = self.fastdesc
3557 3556 for x in it():
3558 3557 return x
3559 3558 return None
3560 3559
3561 3560 def last(self):
3562 3561 if self._ascending:
3563 3562 it = self.fastdesc
3564 3563 else:
3565 3564 it = self.fastasc
3566 3565 for x in it():
3567 3566 return x
3568 3567 return None
3569 3568
3570 3569 def __repr__(self):
3571 3570 d = {False: '-', True: '+'}[self._ascending]
3572 3571 return '<%s%s %d:%d>' % (type(self).__name__, d,
3573 3572 self._start, self._end - 1)
3574 3573
3575 3574 class fullreposet(spanset):
3576 3575 """a set containing all revisions in the repo
3577 3576
3578 3577 This class exists to host special optimization and magic to handle virtual
3579 3578 revisions such as "null".
3580 3579 """
3581 3580
3582 3581 def __init__(self, repo):
3583 3582 super(fullreposet, self).__init__(repo)
3584 3583
3585 3584 def __and__(self, other):
3586 3585 """As self contains the whole repo, all of the other set should also be
3587 3586 in self. Therefore `self & other = other`.
3588 3587
3589 3588 This boldly assumes the other contains valid revs only.
3590 3589 """
3591 3590 # other not a smartset, make is so
3592 3591 if not util.safehasattr(other, 'isascending'):
3593 3592 # filter out hidden revision
3594 3593 # (this boldly assumes all smartset are pure)
3595 3594 #
3596 3595 # `other` was used with "&", let's assume this is a set like
3597 3596 # object.
3598 3597 other = baseset(other - self._hiddenrevs)
3599 3598
3600 3599 # XXX As fullreposet is also used as bootstrap, this is wrong.
3601 3600 #
3602 3601 # With a giveme312() revset returning [3,1,2], this makes
3603 3602 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3604 3603 # We cannot just drop it because other usage still need to sort it:
3605 3604 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3606 3605 #
3607 3606 # There is also some faulty revset implementations that rely on it
3608 3607 # (eg: children as of its state in e8075329c5fb)
3609 3608 #
3610 3609 # When we fix the two points above we can move this into the if clause
3611 3610 other.sort(reverse=self.isdescending())
3612 3611 return other
3613 3612
3614 3613 def prettyformatset(revs):
3615 3614 lines = []
3616 3615 rs = repr(revs)
3617 3616 p = 0
3618 3617 while p < len(rs):
3619 3618 q = rs.find('<', p + 1)
3620 3619 if q < 0:
3621 3620 q = len(rs)
3622 3621 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3623 3622 assert l >= 0
3624 3623 lines.append((l, rs[p:q].rstrip()))
3625 3624 p = q
3626 3625 return '\n'.join(' ' * l + s for l, s in lines)
3627 3626
3628 3627 # tell hggettext to extract docstrings from these functions:
3629 3628 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now