##// END OF EJS Templates
revset: improve _descendants performance...
Durham Goode -
r20894:04e1596d default
parent child Browse files
Show More
@@ -1,15 +1,16 b''
1 1 all()
2 2 draft()
3 3 ::tip
4 4 draft() and ::tip
5 5 0::tip
6 6 roots(0::tip)
7 7 author(lmoscovicz)
8 8 author(mpm)
9 9 author(lmoscovicz) or author(mpm)
10 10 tip:0
11 11 max(tip:0)
12 12 min(0:tip)
13 13 0::
14 14 min(0::)
15 roots((tip~100::) - (tip~100::tip))
15 16 ::p1(p1(tip))::
@@ -1,2852 +1,2862 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, discovery, hbisect, phases
10 10 import node
11 11 import heapq
12 12 import match as matchmod
13 13 import ancestor as ancestormod
14 14 from i18n import _
15 15 import encoding
16 16 import obsolete as obsmod
17 17 import pathutil
18 18 import repoview
19 19
20 20 def _revancestors(repo, revs, followfirst):
21 21 """Like revlog.ancestors(), but supports followfirst."""
22 22 cut = followfirst and 1 or None
23 23 cl = repo.changelog
24 24
25 25 def iterate():
26 26 revqueue, revsnode = None, None
27 27 h = []
28 28
29 29 revs.descending()
30 30 revqueue = util.deque(revs)
31 31 if revqueue:
32 32 revsnode = revqueue.popleft()
33 33 heapq.heappush(h, -revsnode)
34 34
35 35 seen = set([node.nullrev])
36 36 while h:
37 37 current = -heapq.heappop(h)
38 38 if current not in seen:
39 39 if revsnode and current == revsnode:
40 40 if revqueue:
41 41 revsnode = revqueue.popleft()
42 42 heapq.heappush(h, -revsnode)
43 43 seen.add(current)
44 44 yield current
45 45 for parent in cl.parentrevs(current)[:cut]:
46 46 if parent != node.nullrev:
47 47 heapq.heappush(h, -parent)
48 48
49 49 return _descgeneratorset(iterate())
50 50
51 51 def _revdescendants(repo, revs, followfirst):
52 52 """Like revlog.descendants() but supports followfirst."""
53 53 cut = followfirst and 1 or None
54 54
55 55 def iterate():
56 56 cl = repo.changelog
57 57 first = min(revs)
58 58 nullrev = node.nullrev
59 59 if first == nullrev:
60 60 # Are there nodes with a null first parent and a non-null
61 61 # second one? Maybe. Do we care? Probably not.
62 62 for i in cl:
63 63 yield i
64 64 else:
65 65 seen = set(revs)
66 66 for i in cl.revs(first + 1):
67 67 for x in cl.parentrevs(i)[:cut]:
68 68 if x != nullrev and x in seen:
69 69 seen.add(i)
70 70 yield i
71 71 break
72 72
73 73 return _ascgeneratorset(iterate())
74 74
75 75 def _revsbetween(repo, roots, heads):
76 76 """Return all paths between roots and heads, inclusive of both endpoint
77 77 sets."""
78 78 if not roots:
79 79 return baseset([])
80 80 parentrevs = repo.changelog.parentrevs
81 81 visit = baseset(heads)
82 82 reachable = set()
83 83 seen = {}
84 84 minroot = min(roots)
85 85 roots = set(roots)
86 86 # open-code the post-order traversal due to the tiny size of
87 87 # sys.getrecursionlimit()
88 88 while visit:
89 89 rev = visit.pop()
90 90 if rev in roots:
91 91 reachable.add(rev)
92 92 parents = parentrevs(rev)
93 93 seen[rev] = parents
94 94 for parent in parents:
95 95 if parent >= minroot and parent not in seen:
96 96 visit.append(parent)
97 97 if not reachable:
98 98 return baseset([])
99 99 for rev in sorted(seen):
100 100 for parent in seen[rev]:
101 101 if parent in reachable:
102 102 reachable.add(rev)
103 103 return baseset(sorted(reachable))
104 104
105 105 elements = {
106 106 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
107 107 "~": (18, None, ("ancestor", 18)),
108 108 "^": (18, None, ("parent", 18), ("parentpost", 18)),
109 109 "-": (5, ("negate", 19), ("minus", 5)),
110 110 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
111 111 ("dagrangepost", 17)),
112 112 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
113 113 ("dagrangepost", 17)),
114 114 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
115 115 "not": (10, ("not", 10)),
116 116 "!": (10, ("not", 10)),
117 117 "and": (5, None, ("and", 5)),
118 118 "&": (5, None, ("and", 5)),
119 119 "or": (4, None, ("or", 4)),
120 120 "|": (4, None, ("or", 4)),
121 121 "+": (4, None, ("or", 4)),
122 122 ",": (2, None, ("list", 2)),
123 123 ")": (0, None, None),
124 124 "symbol": (0, ("symbol",), None),
125 125 "string": (0, ("string",), None),
126 126 "end": (0, None, None),
127 127 }
128 128
129 129 keywords = set(['and', 'or', 'not'])
130 130
131 131 def tokenize(program, lookup=None):
132 132 '''
133 133 Parse a revset statement into a stream of tokens
134 134
135 135 Check that @ is a valid unquoted token character (issue3686):
136 136 >>> list(tokenize("@::"))
137 137 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
138 138
139 139 '''
140 140
141 141 pos, l = 0, len(program)
142 142 while pos < l:
143 143 c = program[pos]
144 144 if c.isspace(): # skip inter-token whitespace
145 145 pass
146 146 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
147 147 yield ('::', None, pos)
148 148 pos += 1 # skip ahead
149 149 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
150 150 yield ('..', None, pos)
151 151 pos += 1 # skip ahead
152 152 elif c in "():,-|&+!~^": # handle simple operators
153 153 yield (c, None, pos)
154 154 elif (c in '"\'' or c == 'r' and
155 155 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
156 156 if c == 'r':
157 157 pos += 1
158 158 c = program[pos]
159 159 decode = lambda x: x
160 160 else:
161 161 decode = lambda x: x.decode('string-escape')
162 162 pos += 1
163 163 s = pos
164 164 while pos < l: # find closing quote
165 165 d = program[pos]
166 166 if d == '\\': # skip over escaped characters
167 167 pos += 2
168 168 continue
169 169 if d == c:
170 170 yield ('string', decode(program[s:pos]), s)
171 171 break
172 172 pos += 1
173 173 else:
174 174 raise error.ParseError(_("unterminated string"), s)
175 175 # gather up a symbol/keyword
176 176 elif c.isalnum() or c in '._@' or ord(c) > 127:
177 177 s = pos
178 178 pos += 1
179 179 while pos < l: # find end of symbol
180 180 d = program[pos]
181 181 if not (d.isalnum() or d in "-._/@" or ord(d) > 127):
182 182 break
183 183 if d == '.' and program[pos - 1] == '.': # special case for ..
184 184 pos -= 1
185 185 break
186 186 pos += 1
187 187 sym = program[s:pos]
188 188 if sym in keywords: # operator keywords
189 189 yield (sym, None, s)
190 190 elif '-' in sym:
191 191 # some jerk gave us foo-bar-baz, try to check if it's a symbol
192 192 if lookup and lookup(sym):
193 193 # looks like a real symbol
194 194 yield ('symbol', sym, s)
195 195 else:
196 196 # looks like an expression
197 197 parts = sym.split('-')
198 198 for p in parts[:-1]:
199 199 if p: # possible consecutive -
200 200 yield ('symbol', p, s)
201 201 s += len(p)
202 202 yield ('-', None, pos)
203 203 s += 1
204 204 if parts[-1]: # possible trailing -
205 205 yield ('symbol', parts[-1], s)
206 206 else:
207 207 yield ('symbol', sym, s)
208 208 pos -= 1
209 209 else:
210 210 raise error.ParseError(_("syntax error"), pos)
211 211 pos += 1
212 212 yield ('end', None, pos)
213 213
214 214 # helpers
215 215
216 216 def getstring(x, err):
217 217 if x and (x[0] == 'string' or x[0] == 'symbol'):
218 218 return x[1]
219 219 raise error.ParseError(err)
220 220
221 221 def getlist(x):
222 222 if not x:
223 223 return []
224 224 if x[0] == 'list':
225 225 return getlist(x[1]) + [x[2]]
226 226 return [x]
227 227
228 228 def getargs(x, min, max, err):
229 229 l = getlist(x)
230 230 if len(l) < min or (max >= 0 and len(l) > max):
231 231 raise error.ParseError(err)
232 232 return l
233 233
234 234 def getset(repo, subset, x):
235 235 if not x:
236 236 raise error.ParseError(_("missing argument"))
237 237 s = methods[x[0]](repo, subset, *x[1:])
238 238 if util.safehasattr(s, 'set'):
239 239 return s
240 240 return baseset(s)
241 241
242 242 def _getrevsource(repo, r):
243 243 extra = repo[r].extra()
244 244 for label in ('source', 'transplant_source', 'rebase_source'):
245 245 if label in extra:
246 246 try:
247 247 return repo[extra[label]].rev()
248 248 except error.RepoLookupError:
249 249 pass
250 250 return None
251 251
252 252 # operator methods
253 253
254 254 def stringset(repo, subset, x):
255 255 x = repo[x].rev()
256 256 if x == -1 and len(subset) == len(repo):
257 257 return baseset([-1])
258 258 if len(subset) == len(repo) or x in subset:
259 259 return baseset([x])
260 260 return baseset([])
261 261
262 262 def symbolset(repo, subset, x):
263 263 if x in symbols:
264 264 raise error.ParseError(_("can't use %s here") % x)
265 265 return stringset(repo, subset, x)
266 266
267 267 def rangeset(repo, subset, x, y):
268 268 cl = baseset(repo.changelog)
269 269 m = getset(repo, cl, x)
270 270 n = getset(repo, cl, y)
271 271
272 272 if not m or not n:
273 273 return baseset([])
274 274 m, n = m[0], n[-1]
275 275
276 276 if m < n:
277 277 r = spanset(repo, m, n + 1)
278 278 else:
279 279 r = spanset(repo, m, n - 1)
280 280 return r & subset
281 281
282 282 def dagrange(repo, subset, x, y):
283 283 r = spanset(repo)
284 284 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
285 285 s = subset.set()
286 286 return xs.filter(lambda r: r in s)
287 287
288 288 def andset(repo, subset, x, y):
289 289 return getset(repo, getset(repo, subset, x), y)
290 290
291 291 def orset(repo, subset, x, y):
292 292 xl = getset(repo, subset, x)
293 293 yl = getset(repo, subset - xl, y)
294 294 return xl + yl
295 295
296 296 def notset(repo, subset, x):
297 297 return subset - getset(repo, subset, x)
298 298
299 299 def listset(repo, subset, a, b):
300 300 raise error.ParseError(_("can't use a list in this context"))
301 301
302 302 def func(repo, subset, a, b):
303 303 if a[0] == 'symbol' and a[1] in symbols:
304 304 return symbols[a[1]](repo, subset, b)
305 305 raise error.ParseError(_("not a function: %s") % a[1])
306 306
307 307 # functions
308 308
309 309 def adds(repo, subset, x):
310 310 """``adds(pattern)``
311 311 Changesets that add a file matching pattern.
312 312
313 313 The pattern without explicit kind like ``glob:`` is expected to be
314 314 relative to the current directory and match against a file or a
315 315 directory.
316 316 """
317 317 # i18n: "adds" is a keyword
318 318 pat = getstring(x, _("adds requires a pattern"))
319 319 return checkstatus(repo, subset, pat, 1)
320 320
321 321 def ancestor(repo, subset, x):
322 322 """``ancestor(*changeset)``
323 323 Greatest common ancestor of the changesets.
324 324
325 325 Accepts 0 or more changesets.
326 326 Will return empty list when passed no args.
327 327 Greatest common ancestor of a single changeset is that changeset.
328 328 """
329 329 # i18n: "ancestor" is a keyword
330 330 l = getlist(x)
331 331 rl = spanset(repo)
332 332 anc = None
333 333
334 334 # (getset(repo, rl, i) for i in l) generates a list of lists
335 335 rev = repo.changelog.rev
336 336 ancestor = repo.changelog.ancestor
337 337 node = repo.changelog.node
338 338 for revs in (getset(repo, rl, i) for i in l):
339 339 for r in revs:
340 340 if anc is None:
341 341 anc = r
342 342 else:
343 343 anc = rev(ancestor(node(anc), node(r)))
344 344
345 345 if anc is not None and anc in subset:
346 346 return baseset([anc])
347 347 return baseset([])
348 348
349 349 def _ancestors(repo, subset, x, followfirst=False):
350 350 args = getset(repo, spanset(repo), x)
351 351 if not args:
352 352 return baseset([])
353 353 s = _revancestors(repo, args, followfirst)
354 354 return subset.filter(lambda r: r in s)
355 355
356 356 def ancestors(repo, subset, x):
357 357 """``ancestors(set)``
358 358 Changesets that are ancestors of a changeset in set.
359 359 """
360 360 return _ancestors(repo, subset, x)
361 361
362 362 def _firstancestors(repo, subset, x):
363 363 # ``_firstancestors(set)``
364 364 # Like ``ancestors(set)`` but follows only the first parents.
365 365 return _ancestors(repo, subset, x, followfirst=True)
366 366
367 367 def ancestorspec(repo, subset, x, n):
368 368 """``set~n``
369 369 Changesets that are the Nth ancestor (first parents only) of a changeset
370 370 in set.
371 371 """
372 372 try:
373 373 n = int(n[1])
374 374 except (TypeError, ValueError):
375 375 raise error.ParseError(_("~ expects a number"))
376 376 ps = set()
377 377 cl = repo.changelog
378 378 for r in getset(repo, baseset(cl), x):
379 379 for i in range(n):
380 380 r = cl.parentrevs(r)[0]
381 381 ps.add(r)
382 382 return subset.filter(lambda r: r in ps)
383 383
384 384 def author(repo, subset, x):
385 385 """``author(string)``
386 386 Alias for ``user(string)``.
387 387 """
388 388 # i18n: "author" is a keyword
389 389 n = encoding.lower(getstring(x, _("author requires a string")))
390 390 kind, pattern, matcher = _substringmatcher(n)
391 391 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
392 392
393 393 def only(repo, subset, x):
394 394 """``only(set, [set])``
395 395 Changesets that are ancestors of the first set that are not ancestors
396 396 of any other head in the repo. If a second set is specified, the result
397 397 is ancestors of the first set that are not ancestors of the second set
398 398 (i.e. ::<set1> - ::<set2>).
399 399 """
400 400 cl = repo.changelog
401 401 args = getargs(x, 1, 2, _('only takes one or two arguments'))
402 402 include = getset(repo, spanset(repo), args[0]).set()
403 403 if len(args) == 1:
404 404 descendants = set(_revdescendants(repo, include, False))
405 405 exclude = [rev for rev in cl.headrevs()
406 406 if not rev in descendants and not rev in include]
407 407 else:
408 408 exclude = getset(repo, spanset(repo), args[1])
409 409
410 410 results = set(ancestormod.missingancestors(include, exclude, cl.parentrevs))
411 411 return lazyset(subset, lambda x: x in results)
412 412
413 413 def bisect(repo, subset, x):
414 414 """``bisect(string)``
415 415 Changesets marked in the specified bisect status:
416 416
417 417 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
418 418 - ``goods``, ``bads`` : csets topologically good/bad
419 419 - ``range`` : csets taking part in the bisection
420 420 - ``pruned`` : csets that are goods, bads or skipped
421 421 - ``untested`` : csets whose fate is yet unknown
422 422 - ``ignored`` : csets ignored due to DAG topology
423 423 - ``current`` : the cset currently being bisected
424 424 """
425 425 # i18n: "bisect" is a keyword
426 426 status = getstring(x, _("bisect requires a string")).lower()
427 427 state = set(hbisect.get(repo, status))
428 428 return subset.filter(lambda r: r in state)
429 429
430 430 # Backward-compatibility
431 431 # - no help entry so that we do not advertise it any more
432 432 def bisected(repo, subset, x):
433 433 return bisect(repo, subset, x)
434 434
435 435 def bookmark(repo, subset, x):
436 436 """``bookmark([name])``
437 437 The named bookmark or all bookmarks.
438 438
439 439 If `name` starts with `re:`, the remainder of the name is treated as
440 440 a regular expression. To match a bookmark that actually starts with `re:`,
441 441 use the prefix `literal:`.
442 442 """
443 443 # i18n: "bookmark" is a keyword
444 444 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
445 445 if args:
446 446 bm = getstring(args[0],
447 447 # i18n: "bookmark" is a keyword
448 448 _('the argument to bookmark must be a string'))
449 449 kind, pattern, matcher = _stringmatcher(bm)
450 450 if kind == 'literal':
451 451 bmrev = repo._bookmarks.get(bm, None)
452 452 if not bmrev:
453 453 raise util.Abort(_("bookmark '%s' does not exist") % bm)
454 454 bmrev = repo[bmrev].rev()
455 455 return subset.filter(lambda r: r == bmrev)
456 456 else:
457 457 matchrevs = set()
458 458 for name, bmrev in repo._bookmarks.iteritems():
459 459 if matcher(name):
460 460 matchrevs.add(bmrev)
461 461 if not matchrevs:
462 462 raise util.Abort(_("no bookmarks exist that match '%s'")
463 463 % pattern)
464 464 bmrevs = set()
465 465 for bmrev in matchrevs:
466 466 bmrevs.add(repo[bmrev].rev())
467 467 return subset & bmrevs
468 468
469 469 bms = set([repo[r].rev()
470 470 for r in repo._bookmarks.values()])
471 471 return subset.filter(lambda r: r in bms)
472 472
473 473 def branch(repo, subset, x):
474 474 """``branch(string or set)``
475 475 All changesets belonging to the given branch or the branches of the given
476 476 changesets.
477 477
478 478 If `string` starts with `re:`, the remainder of the name is treated as
479 479 a regular expression. To match a branch that actually starts with `re:`,
480 480 use the prefix `literal:`.
481 481 """
482 482 try:
483 483 b = getstring(x, '')
484 484 except error.ParseError:
485 485 # not a string, but another revspec, e.g. tip()
486 486 pass
487 487 else:
488 488 kind, pattern, matcher = _stringmatcher(b)
489 489 if kind == 'literal':
490 490 # note: falls through to the revspec case if no branch with
491 491 # this name exists
492 492 if pattern in repo.branchmap():
493 493 return subset.filter(lambda r: matcher(repo[r].branch()))
494 494 else:
495 495 return subset.filter(lambda r: matcher(repo[r].branch()))
496 496
497 497 s = getset(repo, spanset(repo), x)
498 498 b = set()
499 499 for r in s:
500 500 b.add(repo[r].branch())
501 501 s = s.set()
502 502 return subset.filter(lambda r: r in s or repo[r].branch() in b)
503 503
504 504 def bumped(repo, subset, x):
505 505 """``bumped()``
506 506 Mutable changesets marked as successors of public changesets.
507 507
508 508 Only non-public and non-obsolete changesets can be `bumped`.
509 509 """
510 510 # i18n: "bumped" is a keyword
511 511 getargs(x, 0, 0, _("bumped takes no arguments"))
512 512 bumped = obsmod.getrevs(repo, 'bumped')
513 513 return subset & bumped
514 514
515 515 def bundle(repo, subset, x):
516 516 """``bundle()``
517 517 Changesets in the bundle.
518 518
519 519 Bundle must be specified by the -R option."""
520 520
521 521 try:
522 522 bundlerevs = repo.changelog.bundlerevs
523 523 except AttributeError:
524 524 raise util.Abort(_("no bundle provided - specify with -R"))
525 525 return subset & bundlerevs
526 526
527 527 def checkstatus(repo, subset, pat, field):
528 528 hasset = matchmod.patkind(pat) == 'set'
529 529
530 530 def matches(x):
531 531 m = None
532 532 fname = None
533 533 c = repo[x]
534 534 if not m or hasset:
535 535 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
536 536 if not m.anypats() and len(m.files()) == 1:
537 537 fname = m.files()[0]
538 538 if fname is not None:
539 539 if fname not in c.files():
540 540 return False
541 541 else:
542 542 for f in c.files():
543 543 if m(f):
544 544 break
545 545 else:
546 546 return False
547 547 files = repo.status(c.p1().node(), c.node())[field]
548 548 if fname is not None:
549 549 if fname in files:
550 550 return True
551 551 else:
552 552 for f in files:
553 553 if m(f):
554 554 return True
555 555
556 556 return subset.filter(matches)
557 557
558 558 def _children(repo, narrow, parentset):
559 559 cs = set()
560 560 if not parentset:
561 561 return baseset(cs)
562 562 pr = repo.changelog.parentrevs
563 563 minrev = min(parentset)
564 564 for r in narrow:
565 565 if r <= minrev:
566 566 continue
567 567 for p in pr(r):
568 568 if p in parentset:
569 569 cs.add(r)
570 570 return baseset(cs)
571 571
572 572 def children(repo, subset, x):
573 573 """``children(set)``
574 574 Child changesets of changesets in set.
575 575 """
576 576 s = getset(repo, baseset(repo), x).set()
577 577 cs = _children(repo, subset, s)
578 578 return subset & cs
579 579
580 580 def closed(repo, subset, x):
581 581 """``closed()``
582 582 Changeset is closed.
583 583 """
584 584 # i18n: "closed" is a keyword
585 585 getargs(x, 0, 0, _("closed takes no arguments"))
586 586 return subset.filter(lambda r: repo[r].closesbranch())
587 587
588 588 def contains(repo, subset, x):
589 589 """``contains(pattern)``
590 590 Revision contains a file matching pattern. See :hg:`help patterns`
591 591 for information about file patterns.
592 592
593 593 The pattern without explicit kind like ``glob:`` is expected to be
594 594 relative to the current directory and match against a file exactly
595 595 for efficiency.
596 596 """
597 597 # i18n: "contains" is a keyword
598 598 pat = getstring(x, _("contains requires a pattern"))
599 599
600 600 def matches(x):
601 601 if not matchmod.patkind(pat):
602 602 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
603 603 if pats in repo[x]:
604 604 return True
605 605 else:
606 606 c = repo[x]
607 607 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
608 608 for f in c.manifest():
609 609 if m(f):
610 610 return True
611 611 return False
612 612
613 613 return subset.filter(matches)
614 614
615 615 def converted(repo, subset, x):
616 616 """``converted([id])``
617 617 Changesets converted from the given identifier in the old repository if
618 618 present, or all converted changesets if no identifier is specified.
619 619 """
620 620
621 621 # There is exactly no chance of resolving the revision, so do a simple
622 622 # string compare and hope for the best
623 623
624 624 rev = None
625 625 # i18n: "converted" is a keyword
626 626 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
627 627 if l:
628 628 # i18n: "converted" is a keyword
629 629 rev = getstring(l[0], _('converted requires a revision'))
630 630
631 631 def _matchvalue(r):
632 632 source = repo[r].extra().get('convert_revision', None)
633 633 return source is not None and (rev is None or source.startswith(rev))
634 634
635 635 return subset.filter(lambda r: _matchvalue(r))
636 636
637 637 def date(repo, subset, x):
638 638 """``date(interval)``
639 639 Changesets within the interval, see :hg:`help dates`.
640 640 """
641 641 # i18n: "date" is a keyword
642 642 ds = getstring(x, _("date requires a string"))
643 643 dm = util.matchdate(ds)
644 644 return subset.filter(lambda x: dm(repo[x].date()[0]))
645 645
646 646 def desc(repo, subset, x):
647 647 """``desc(string)``
648 648 Search commit message for string. The match is case-insensitive.
649 649 """
650 650 # i18n: "desc" is a keyword
651 651 ds = encoding.lower(getstring(x, _("desc requires a string")))
652 652
653 653 def matches(x):
654 654 c = repo[x]
655 655 return ds in encoding.lower(c.description())
656 656
657 657 return subset.filter(matches)
658 658
659 659 def _descendants(repo, subset, x, followfirst=False):
660 660 args = getset(repo, spanset(repo), x)
661 661 if not args:
662 662 return baseset([])
663 663 s = _revdescendants(repo, args, followfirst)
664 a = set(args)
665 return subset.filter(lambda r: r in s or r in a)
664
665 # Both sets need to be ascending in order to lazily return the union
666 # in the correct order.
667 args.ascending()
668
669 subsetset = subset.set()
670 result = (orderedlazyset(s, subsetset.__contains__, ascending=True) +
671 orderedlazyset(args, subsetset.__contains__, ascending=True))
672
673 # Wrap result in a lazyset since it's an _addset, which doesn't implement
674 # all the necessary functions to be consumed by callers.
675 return orderedlazyset(result, lambda r: True, ascending=True)
666 676
667 677 def descendants(repo, subset, x):
668 678 """``descendants(set)``
669 679 Changesets which are descendants of changesets in set.
670 680 """
671 681 return _descendants(repo, subset, x)
672 682
673 683 def _firstdescendants(repo, subset, x):
674 684 # ``_firstdescendants(set)``
675 685 # Like ``descendants(set)`` but follows only the first parents.
676 686 return _descendants(repo, subset, x, followfirst=True)
677 687
678 688 def destination(repo, subset, x):
679 689 """``destination([set])``
680 690 Changesets that were created by a graft, transplant or rebase operation,
681 691 with the given revisions specified as the source. Omitting the optional set
682 692 is the same as passing all().
683 693 """
684 694 if x is not None:
685 695 args = getset(repo, spanset(repo), x).set()
686 696 else:
687 697 args = getall(repo, spanset(repo), x).set()
688 698
689 699 dests = set()
690 700
691 701 # subset contains all of the possible destinations that can be returned, so
692 702 # iterate over them and see if their source(s) were provided in the args.
693 703 # Even if the immediate src of r is not in the args, src's source (or
694 704 # further back) may be. Scanning back further than the immediate src allows
695 705 # transitive transplants and rebases to yield the same results as transitive
696 706 # grafts.
697 707 for r in subset:
698 708 src = _getrevsource(repo, r)
699 709 lineage = None
700 710
701 711 while src is not None:
702 712 if lineage is None:
703 713 lineage = list()
704 714
705 715 lineage.append(r)
706 716
707 717 # The visited lineage is a match if the current source is in the arg
708 718 # set. Since every candidate dest is visited by way of iterating
709 719 # subset, any dests further back in the lineage will be tested by a
710 720 # different iteration over subset. Likewise, if the src was already
711 721 # selected, the current lineage can be selected without going back
712 722 # further.
713 723 if src in args or src in dests:
714 724 dests.update(lineage)
715 725 break
716 726
717 727 r = src
718 728 src = _getrevsource(repo, r)
719 729
720 730 return subset.filter(lambda r: r in dests)
721 731
722 732 def divergent(repo, subset, x):
723 733 """``divergent()``
724 734 Final successors of changesets with an alternative set of final successors.
725 735 """
726 736 # i18n: "divergent" is a keyword
727 737 getargs(x, 0, 0, _("divergent takes no arguments"))
728 738 divergent = obsmod.getrevs(repo, 'divergent')
729 739 return subset.filter(lambda r: r in divergent)
730 740
731 741 def draft(repo, subset, x):
732 742 """``draft()``
733 743 Changeset in draft phase."""
734 744 # i18n: "draft" is a keyword
735 745 getargs(x, 0, 0, _("draft takes no arguments"))
736 746 pc = repo._phasecache
737 747 return subset.filter(lambda r: pc.phase(repo, r) == phases.draft)
738 748
739 749 def extinct(repo, subset, x):
740 750 """``extinct()``
741 751 Obsolete changesets with obsolete descendants only.
742 752 """
743 753 # i18n: "extinct" is a keyword
744 754 getargs(x, 0, 0, _("extinct takes no arguments"))
745 755 extincts = obsmod.getrevs(repo, 'extinct')
746 756 return subset & extincts
747 757
748 758 def extra(repo, subset, x):
749 759 """``extra(label, [value])``
750 760 Changesets with the given label in the extra metadata, with the given
751 761 optional value.
752 762
753 763 If `value` starts with `re:`, the remainder of the value is treated as
754 764 a regular expression. To match a value that actually starts with `re:`,
755 765 use the prefix `literal:`.
756 766 """
757 767
758 768 # i18n: "extra" is a keyword
759 769 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
760 770 # i18n: "extra" is a keyword
761 771 label = getstring(l[0], _('first argument to extra must be a string'))
762 772 value = None
763 773
764 774 if len(l) > 1:
765 775 # i18n: "extra" is a keyword
766 776 value = getstring(l[1], _('second argument to extra must be a string'))
767 777 kind, value, matcher = _stringmatcher(value)
768 778
769 779 def _matchvalue(r):
770 780 extra = repo[r].extra()
771 781 return label in extra and (value is None or matcher(extra[label]))
772 782
773 783 return subset.filter(lambda r: _matchvalue(r))
774 784
775 785 def filelog(repo, subset, x):
776 786 """``filelog(pattern)``
777 787 Changesets connected to the specified filelog.
778 788
779 789 For performance reasons, ``filelog()`` does not show every changeset
780 790 that affects the requested file(s). See :hg:`help log` for details. For
781 791 a slower, more accurate result, use ``file()``.
782 792
783 793 The pattern without explicit kind like ``glob:`` is expected to be
784 794 relative to the current directory and match against a file exactly
785 795 for efficiency.
786 796 """
787 797
788 798 # i18n: "filelog" is a keyword
789 799 pat = getstring(x, _("filelog requires a pattern"))
790 800 s = set()
791 801
792 802 if not matchmod.patkind(pat):
793 803 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
794 804 fl = repo.file(f)
795 805 for fr in fl:
796 806 s.add(fl.linkrev(fr))
797 807 else:
798 808 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
799 809 for f in repo[None]:
800 810 if m(f):
801 811 fl = repo.file(f)
802 812 for fr in fl:
803 813 s.add(fl.linkrev(fr))
804 814
805 815 return subset.filter(lambda r: r in s)
806 816
807 817 def first(repo, subset, x):
808 818 """``first(set, [n])``
809 819 An alias for limit().
810 820 """
811 821 return limit(repo, subset, x)
812 822
813 823 def _follow(repo, subset, x, name, followfirst=False):
814 824 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
815 825 c = repo['.']
816 826 if l:
817 827 x = getstring(l[0], _("%s expected a filename") % name)
818 828 if x in c:
819 829 cx = c[x]
820 830 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
821 831 # include the revision responsible for the most recent version
822 832 s.add(cx.linkrev())
823 833 else:
824 834 return baseset([])
825 835 else:
826 836 s = _revancestors(repo, baseset([c.rev()]), followfirst)
827 837
828 838 return subset.filter(lambda r: r in s)
829 839
830 840 def follow(repo, subset, x):
831 841 """``follow([file])``
832 842 An alias for ``::.`` (ancestors of the working copy's first parent).
833 843 If a filename is specified, the history of the given file is followed,
834 844 including copies.
835 845 """
836 846 return _follow(repo, subset, x, 'follow')
837 847
838 848 def _followfirst(repo, subset, x):
839 849 # ``followfirst([file])``
840 850 # Like ``follow([file])`` but follows only the first parent of
841 851 # every revision or file revision.
842 852 return _follow(repo, subset, x, '_followfirst', followfirst=True)
843 853
844 854 def getall(repo, subset, x):
845 855 """``all()``
846 856 All changesets, the same as ``0:tip``.
847 857 """
848 858 # i18n: "all" is a keyword
849 859 getargs(x, 0, 0, _("all takes no arguments"))
850 860 return subset
851 861
852 862 def grep(repo, subset, x):
853 863 """``grep(regex)``
854 864 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
855 865 to ensure special escape characters are handled correctly. Unlike
856 866 ``keyword(string)``, the match is case-sensitive.
857 867 """
858 868 try:
859 869 # i18n: "grep" is a keyword
860 870 gr = re.compile(getstring(x, _("grep requires a string")))
861 871 except re.error, e:
862 872 raise error.ParseError(_('invalid match pattern: %s') % e)
863 873
864 874 def matches(x):
865 875 c = repo[x]
866 876 for e in c.files() + [c.user(), c.description()]:
867 877 if gr.search(e):
868 878 return True
869 879 return False
870 880
871 881 return subset.filter(matches)
872 882
873 883 def _matchfiles(repo, subset, x):
874 884 # _matchfiles takes a revset list of prefixed arguments:
875 885 #
876 886 # [p:foo, i:bar, x:baz]
877 887 #
878 888 # builds a match object from them and filters subset. Allowed
879 889 # prefixes are 'p:' for regular patterns, 'i:' for include
880 890 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
881 891 # a revision identifier, or the empty string to reference the
882 892 # working directory, from which the match object is
883 893 # initialized. Use 'd:' to set the default matching mode, default
884 894 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
885 895
886 896 # i18n: "_matchfiles" is a keyword
887 897 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
888 898 pats, inc, exc = [], [], []
889 899 hasset = False
890 900 rev, default = None, None
891 901 for arg in l:
892 902 # i18n: "_matchfiles" is a keyword
893 903 s = getstring(arg, _("_matchfiles requires string arguments"))
894 904 prefix, value = s[:2], s[2:]
895 905 if prefix == 'p:':
896 906 pats.append(value)
897 907 elif prefix == 'i:':
898 908 inc.append(value)
899 909 elif prefix == 'x:':
900 910 exc.append(value)
901 911 elif prefix == 'r:':
902 912 if rev is not None:
903 913 # i18n: "_matchfiles" is a keyword
904 914 raise error.ParseError(_('_matchfiles expected at most one '
905 915 'revision'))
906 916 rev = value
907 917 elif prefix == 'd:':
908 918 if default is not None:
909 919 # i18n: "_matchfiles" is a keyword
910 920 raise error.ParseError(_('_matchfiles expected at most one '
911 921 'default mode'))
912 922 default = value
913 923 else:
914 924 # i18n: "_matchfiles" is a keyword
915 925 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
916 926 if not hasset and matchmod.patkind(value) == 'set':
917 927 hasset = True
918 928 if not default:
919 929 default = 'glob'
920 930
921 931 def matches(x):
922 932 m = None
923 933 c = repo[x]
924 934 if not m or (hasset and rev is None):
925 935 ctx = c
926 936 if rev is not None:
927 937 ctx = repo[rev or None]
928 938 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
929 939 exclude=exc, ctx=ctx, default=default)
930 940 for f in c.files():
931 941 if m(f):
932 942 return True
933 943 return False
934 944
935 945 return subset.filter(matches)
936 946
937 947 def hasfile(repo, subset, x):
938 948 """``file(pattern)``
939 949 Changesets affecting files matched by pattern.
940 950
941 951 For a faster but less accurate result, consider using ``filelog()``
942 952 instead.
943 953
944 954 This predicate uses ``glob:`` as the default kind of pattern.
945 955 """
946 956 # i18n: "file" is a keyword
947 957 pat = getstring(x, _("file requires a pattern"))
948 958 return _matchfiles(repo, subset, ('string', 'p:' + pat))
949 959
950 960 def head(repo, subset, x):
951 961 """``head()``
952 962 Changeset is a named branch head.
953 963 """
954 964 # i18n: "head" is a keyword
955 965 getargs(x, 0, 0, _("head takes no arguments"))
956 966 hs = set()
957 967 for b, ls in repo.branchmap().iteritems():
958 968 hs.update(repo[h].rev() for h in ls)
959 969 return baseset(hs).filter(subset.__contains__)
960 970
961 971 def heads(repo, subset, x):
962 972 """``heads(set)``
963 973 Members of set with no children in set.
964 974 """
965 975 s = getset(repo, subset, x)
966 976 ps = parents(repo, subset, x)
967 977 return s - ps
968 978
969 979 def hidden(repo, subset, x):
970 980 """``hidden()``
971 981 Hidden changesets.
972 982 """
973 983 # i18n: "hidden" is a keyword
974 984 getargs(x, 0, 0, _("hidden takes no arguments"))
975 985 hiddenrevs = repoview.filterrevs(repo, 'visible')
976 986 return subset & hiddenrevs
977 987
978 988 def keyword(repo, subset, x):
979 989 """``keyword(string)``
980 990 Search commit message, user name, and names of changed files for
981 991 string. The match is case-insensitive.
982 992 """
983 993 # i18n: "keyword" is a keyword
984 994 kw = encoding.lower(getstring(x, _("keyword requires a string")))
985 995
986 996 def matches(r):
987 997 c = repo[r]
988 998 return util.any(kw in encoding.lower(t) for t in c.files() + [c.user(),
989 999 c.description()])
990 1000
991 1001 return subset.filter(matches)
992 1002
993 1003 def limit(repo, subset, x):
994 1004 """``limit(set, [n])``
995 1005 First n members of set, defaulting to 1.
996 1006 """
997 1007 # i18n: "limit" is a keyword
998 1008 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
999 1009 try:
1000 1010 lim = 1
1001 1011 if len(l) == 2:
1002 1012 # i18n: "limit" is a keyword
1003 1013 lim = int(getstring(l[1], _("limit requires a number")))
1004 1014 except (TypeError, ValueError):
1005 1015 # i18n: "limit" is a keyword
1006 1016 raise error.ParseError(_("limit expects a number"))
1007 1017 ss = subset.set()
1008 1018 os = getset(repo, spanset(repo), l[0])
1009 1019 bs = baseset([])
1010 1020 it = iter(os)
1011 1021 for x in xrange(lim):
1012 1022 try:
1013 1023 y = it.next()
1014 1024 if y in ss:
1015 1025 bs.append(y)
1016 1026 except (StopIteration):
1017 1027 break
1018 1028 return bs
1019 1029
1020 1030 def last(repo, subset, x):
1021 1031 """``last(set, [n])``
1022 1032 Last n members of set, defaulting to 1.
1023 1033 """
1024 1034 # i18n: "last" is a keyword
1025 1035 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1026 1036 try:
1027 1037 lim = 1
1028 1038 if len(l) == 2:
1029 1039 # i18n: "last" is a keyword
1030 1040 lim = int(getstring(l[1], _("last requires a number")))
1031 1041 except (TypeError, ValueError):
1032 1042 # i18n: "last" is a keyword
1033 1043 raise error.ParseError(_("last expects a number"))
1034 1044 ss = subset.set()
1035 1045 os = getset(repo, spanset(repo), l[0])
1036 1046 os.reverse()
1037 1047 bs = baseset([])
1038 1048 it = iter(os)
1039 1049 for x in xrange(lim):
1040 1050 try:
1041 1051 y = it.next()
1042 1052 if y in ss:
1043 1053 bs.append(y)
1044 1054 except (StopIteration):
1045 1055 break
1046 1056 return bs
1047 1057
1048 1058 def maxrev(repo, subset, x):
1049 1059 """``max(set)``
1050 1060 Changeset with highest revision number in set.
1051 1061 """
1052 1062 os = getset(repo, spanset(repo), x)
1053 1063 if os:
1054 1064 m = os.max()
1055 1065 if m in subset:
1056 1066 return baseset([m])
1057 1067 return baseset([])
1058 1068
1059 1069 def merge(repo, subset, x):
1060 1070 """``merge()``
1061 1071 Changeset is a merge changeset.
1062 1072 """
1063 1073 # i18n: "merge" is a keyword
1064 1074 getargs(x, 0, 0, _("merge takes no arguments"))
1065 1075 cl = repo.changelog
1066 1076 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1067 1077
1068 1078 def branchpoint(repo, subset, x):
1069 1079 """``branchpoint()``
1070 1080 Changesets with more than one child.
1071 1081 """
1072 1082 # i18n: "branchpoint" is a keyword
1073 1083 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1074 1084 cl = repo.changelog
1075 1085 if not subset:
1076 1086 return baseset([])
1077 1087 baserev = min(subset)
1078 1088 parentscount = [0]*(len(repo) - baserev)
1079 1089 for r in cl.revs(start=baserev + 1):
1080 1090 for p in cl.parentrevs(r):
1081 1091 if p >= baserev:
1082 1092 parentscount[p - baserev] += 1
1083 1093 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1084 1094
1085 1095 def minrev(repo, subset, x):
1086 1096 """``min(set)``
1087 1097 Changeset with lowest revision number in set.
1088 1098 """
1089 1099 os = getset(repo, spanset(repo), x)
1090 1100 if os:
1091 1101 m = os.min()
1092 1102 if m in subset:
1093 1103 return baseset([m])
1094 1104 return baseset([])
1095 1105
1096 1106 def _missingancestors(repo, subset, x):
1097 1107 # i18n: "_missingancestors" is a keyword
1098 1108 revs, bases = getargs(x, 2, 2,
1099 1109 _("_missingancestors requires two arguments"))
1100 1110 rs = baseset(repo)
1101 1111 revs = getset(repo, rs, revs)
1102 1112 bases = getset(repo, rs, bases)
1103 1113 missing = set(repo.changelog.findmissingrevs(bases, revs))
1104 1114 return baseset([r for r in subset if r in missing])
1105 1115
1106 1116 def modifies(repo, subset, x):
1107 1117 """``modifies(pattern)``
1108 1118 Changesets modifying files matched by pattern.
1109 1119
1110 1120 The pattern without explicit kind like ``glob:`` is expected to be
1111 1121 relative to the current directory and match against a file or a
1112 1122 directory.
1113 1123 """
1114 1124 # i18n: "modifies" is a keyword
1115 1125 pat = getstring(x, _("modifies requires a pattern"))
1116 1126 return checkstatus(repo, subset, pat, 0)
1117 1127
1118 1128 def node_(repo, subset, x):
1119 1129 """``id(string)``
1120 1130 Revision non-ambiguously specified by the given hex string prefix.
1121 1131 """
1122 1132 # i18n: "id" is a keyword
1123 1133 l = getargs(x, 1, 1, _("id requires one argument"))
1124 1134 # i18n: "id" is a keyword
1125 1135 n = getstring(l[0], _("id requires a string"))
1126 1136 if len(n) == 40:
1127 1137 rn = repo[n].rev()
1128 1138 else:
1129 1139 rn = None
1130 1140 pm = repo.changelog._partialmatch(n)
1131 1141 if pm is not None:
1132 1142 rn = repo.changelog.rev(pm)
1133 1143
1134 1144 return subset.filter(lambda r: r == rn)
1135 1145
1136 1146 def obsolete(repo, subset, x):
1137 1147 """``obsolete()``
1138 1148 Mutable changeset with a newer version."""
1139 1149 # i18n: "obsolete" is a keyword
1140 1150 getargs(x, 0, 0, _("obsolete takes no arguments"))
1141 1151 obsoletes = obsmod.getrevs(repo, 'obsolete')
1142 1152 return subset & obsoletes
1143 1153
1144 1154 def origin(repo, subset, x):
1145 1155 """``origin([set])``
1146 1156 Changesets that were specified as a source for the grafts, transplants or
1147 1157 rebases that created the given revisions. Omitting the optional set is the
1148 1158 same as passing all(). If a changeset created by these operations is itself
1149 1159 specified as a source for one of these operations, only the source changeset
1150 1160 for the first operation is selected.
1151 1161 """
1152 1162 if x is not None:
1153 1163 args = getset(repo, spanset(repo), x).set()
1154 1164 else:
1155 1165 args = getall(repo, spanset(repo), x).set()
1156 1166
1157 1167 def _firstsrc(rev):
1158 1168 src = _getrevsource(repo, rev)
1159 1169 if src is None:
1160 1170 return None
1161 1171
1162 1172 while True:
1163 1173 prev = _getrevsource(repo, src)
1164 1174
1165 1175 if prev is None:
1166 1176 return src
1167 1177 src = prev
1168 1178
1169 1179 o = set([_firstsrc(r) for r in args])
1170 1180 return subset.filter(lambda r: r in o)
1171 1181
1172 1182 def outgoing(repo, subset, x):
1173 1183 """``outgoing([path])``
1174 1184 Changesets not found in the specified destination repository, or the
1175 1185 default push location.
1176 1186 """
1177 1187 import hg # avoid start-up nasties
1178 1188 # i18n: "outgoing" is a keyword
1179 1189 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1180 1190 # i18n: "outgoing" is a keyword
1181 1191 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1182 1192 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1183 1193 dest, branches = hg.parseurl(dest)
1184 1194 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1185 1195 if revs:
1186 1196 revs = [repo.lookup(rev) for rev in revs]
1187 1197 other = hg.peer(repo, {}, dest)
1188 1198 repo.ui.pushbuffer()
1189 1199 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1190 1200 repo.ui.popbuffer()
1191 1201 cl = repo.changelog
1192 1202 o = set([cl.rev(r) for r in outgoing.missing])
1193 1203 return subset.filter(lambda r: r in o)
1194 1204
1195 1205 def p1(repo, subset, x):
1196 1206 """``p1([set])``
1197 1207 First parent of changesets in set, or the working directory.
1198 1208 """
1199 1209 if x is None:
1200 1210 p = repo[x].p1().rev()
1201 1211 return subset.filter(lambda r: r == p)
1202 1212
1203 1213 ps = set()
1204 1214 cl = repo.changelog
1205 1215 for r in getset(repo, spanset(repo), x):
1206 1216 ps.add(cl.parentrevs(r)[0])
1207 1217 return subset & ps
1208 1218
1209 1219 def p2(repo, subset, x):
1210 1220 """``p2([set])``
1211 1221 Second parent of changesets in set, or the working directory.
1212 1222 """
1213 1223 if x is None:
1214 1224 ps = repo[x].parents()
1215 1225 try:
1216 1226 p = ps[1].rev()
1217 1227 return subset.filter(lambda r: r == p)
1218 1228 except IndexError:
1219 1229 return baseset([])
1220 1230
1221 1231 ps = set()
1222 1232 cl = repo.changelog
1223 1233 for r in getset(repo, spanset(repo), x):
1224 1234 ps.add(cl.parentrevs(r)[1])
1225 1235 return subset & ps
1226 1236
1227 1237 def parents(repo, subset, x):
1228 1238 """``parents([set])``
1229 1239 The set of all parents for all changesets in set, or the working directory.
1230 1240 """
1231 1241 if x is None:
1232 1242 ps = tuple(p.rev() for p in repo[x].parents())
1233 1243 return subset & ps
1234 1244
1235 1245 ps = set()
1236 1246 cl = repo.changelog
1237 1247 for r in getset(repo, spanset(repo), x):
1238 1248 ps.update(cl.parentrevs(r))
1239 1249 return subset & ps
1240 1250
1241 1251 def parentspec(repo, subset, x, n):
1242 1252 """``set^0``
1243 1253 The set.
1244 1254 ``set^1`` (or ``set^``), ``set^2``
1245 1255 First or second parent, respectively, of all changesets in set.
1246 1256 """
1247 1257 try:
1248 1258 n = int(n[1])
1249 1259 if n not in (0, 1, 2):
1250 1260 raise ValueError
1251 1261 except (TypeError, ValueError):
1252 1262 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1253 1263 ps = set()
1254 1264 cl = repo.changelog
1255 1265 for r in getset(repo, baseset(cl), x):
1256 1266 if n == 0:
1257 1267 ps.add(r)
1258 1268 elif n == 1:
1259 1269 ps.add(cl.parentrevs(r)[0])
1260 1270 elif n == 2:
1261 1271 parents = cl.parentrevs(r)
1262 1272 if len(parents) > 1:
1263 1273 ps.add(parents[1])
1264 1274 return subset & ps
1265 1275
1266 1276 def present(repo, subset, x):
1267 1277 """``present(set)``
1268 1278 An empty set, if any revision in set isn't found; otherwise,
1269 1279 all revisions in set.
1270 1280
1271 1281 If any of specified revisions is not present in the local repository,
1272 1282 the query is normally aborted. But this predicate allows the query
1273 1283 to continue even in such cases.
1274 1284 """
1275 1285 try:
1276 1286 return getset(repo, subset, x)
1277 1287 except error.RepoLookupError:
1278 1288 return baseset([])
1279 1289
1280 1290 def public(repo, subset, x):
1281 1291 """``public()``
1282 1292 Changeset in public phase."""
1283 1293 # i18n: "public" is a keyword
1284 1294 getargs(x, 0, 0, _("public takes no arguments"))
1285 1295 pc = repo._phasecache
1286 1296 return subset.filter(lambda r: pc.phase(repo, r) == phases.public)
1287 1297
1288 1298 def remote(repo, subset, x):
1289 1299 """``remote([id [,path]])``
1290 1300 Local revision that corresponds to the given identifier in a
1291 1301 remote repository, if present. Here, the '.' identifier is a
1292 1302 synonym for the current local branch.
1293 1303 """
1294 1304
1295 1305 import hg # avoid start-up nasties
1296 1306 # i18n: "remote" is a keyword
1297 1307 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1298 1308
1299 1309 q = '.'
1300 1310 if len(l) > 0:
1301 1311 # i18n: "remote" is a keyword
1302 1312 q = getstring(l[0], _("remote requires a string id"))
1303 1313 if q == '.':
1304 1314 q = repo['.'].branch()
1305 1315
1306 1316 dest = ''
1307 1317 if len(l) > 1:
1308 1318 # i18n: "remote" is a keyword
1309 1319 dest = getstring(l[1], _("remote requires a repository path"))
1310 1320 dest = repo.ui.expandpath(dest or 'default')
1311 1321 dest, branches = hg.parseurl(dest)
1312 1322 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1313 1323 if revs:
1314 1324 revs = [repo.lookup(rev) for rev in revs]
1315 1325 other = hg.peer(repo, {}, dest)
1316 1326 n = other.lookup(q)
1317 1327 if n in repo:
1318 1328 r = repo[n].rev()
1319 1329 if r in subset:
1320 1330 return baseset([r])
1321 1331 return baseset([])
1322 1332
1323 1333 def removes(repo, subset, x):
1324 1334 """``removes(pattern)``
1325 1335 Changesets which remove files matching pattern.
1326 1336
1327 1337 The pattern without explicit kind like ``glob:`` is expected to be
1328 1338 relative to the current directory and match against a file or a
1329 1339 directory.
1330 1340 """
1331 1341 # i18n: "removes" is a keyword
1332 1342 pat = getstring(x, _("removes requires a pattern"))
1333 1343 return checkstatus(repo, subset, pat, 2)
1334 1344
1335 1345 def rev(repo, subset, x):
1336 1346 """``rev(number)``
1337 1347 Revision with the given numeric identifier.
1338 1348 """
1339 1349 # i18n: "rev" is a keyword
1340 1350 l = getargs(x, 1, 1, _("rev requires one argument"))
1341 1351 try:
1342 1352 # i18n: "rev" is a keyword
1343 1353 l = int(getstring(l[0], _("rev requires a number")))
1344 1354 except (TypeError, ValueError):
1345 1355 # i18n: "rev" is a keyword
1346 1356 raise error.ParseError(_("rev expects a number"))
1347 1357 return subset.filter(lambda r: r == l)
1348 1358
1349 1359 def matching(repo, subset, x):
1350 1360 """``matching(revision [, field])``
1351 1361 Changesets in which a given set of fields match the set of fields in the
1352 1362 selected revision or set.
1353 1363
1354 1364 To match more than one field pass the list of fields to match separated
1355 1365 by spaces (e.g. ``author description``).
1356 1366
1357 1367 Valid fields are most regular revision fields and some special fields.
1358 1368
1359 1369 Regular revision fields are ``description``, ``author``, ``branch``,
1360 1370 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1361 1371 and ``diff``.
1362 1372 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1363 1373 contents of the revision. Two revisions matching their ``diff`` will
1364 1374 also match their ``files``.
1365 1375
1366 1376 Special fields are ``summary`` and ``metadata``:
1367 1377 ``summary`` matches the first line of the description.
1368 1378 ``metadata`` is equivalent to matching ``description user date``
1369 1379 (i.e. it matches the main metadata fields).
1370 1380
1371 1381 ``metadata`` is the default field which is used when no fields are
1372 1382 specified. You can match more than one field at a time.
1373 1383 """
1374 1384 # i18n: "matching" is a keyword
1375 1385 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1376 1386
1377 1387 revs = getset(repo, baseset(repo.changelog), l[0])
1378 1388
1379 1389 fieldlist = ['metadata']
1380 1390 if len(l) > 1:
1381 1391 fieldlist = getstring(l[1],
1382 1392 # i18n: "matching" is a keyword
1383 1393 _("matching requires a string "
1384 1394 "as its second argument")).split()
1385 1395
1386 1396 # Make sure that there are no repeated fields,
1387 1397 # expand the 'special' 'metadata' field type
1388 1398 # and check the 'files' whenever we check the 'diff'
1389 1399 fields = []
1390 1400 for field in fieldlist:
1391 1401 if field == 'metadata':
1392 1402 fields += ['user', 'description', 'date']
1393 1403 elif field == 'diff':
1394 1404 # a revision matching the diff must also match the files
1395 1405 # since matching the diff is very costly, make sure to
1396 1406 # also match the files first
1397 1407 fields += ['files', 'diff']
1398 1408 else:
1399 1409 if field == 'author':
1400 1410 field = 'user'
1401 1411 fields.append(field)
1402 1412 fields = set(fields)
1403 1413 if 'summary' in fields and 'description' in fields:
1404 1414 # If a revision matches its description it also matches its summary
1405 1415 fields.discard('summary')
1406 1416
1407 1417 # We may want to match more than one field
1408 1418 # Not all fields take the same amount of time to be matched
1409 1419 # Sort the selected fields in order of increasing matching cost
1410 1420 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1411 1421 'files', 'description', 'substate', 'diff']
1412 1422 def fieldkeyfunc(f):
1413 1423 try:
1414 1424 return fieldorder.index(f)
1415 1425 except ValueError:
1416 1426 # assume an unknown field is very costly
1417 1427 return len(fieldorder)
1418 1428 fields = list(fields)
1419 1429 fields.sort(key=fieldkeyfunc)
1420 1430
1421 1431 # Each field will be matched with its own "getfield" function
1422 1432 # which will be added to the getfieldfuncs array of functions
1423 1433 getfieldfuncs = []
1424 1434 _funcs = {
1425 1435 'user': lambda r: repo[r].user(),
1426 1436 'branch': lambda r: repo[r].branch(),
1427 1437 'date': lambda r: repo[r].date(),
1428 1438 'description': lambda r: repo[r].description(),
1429 1439 'files': lambda r: repo[r].files(),
1430 1440 'parents': lambda r: repo[r].parents(),
1431 1441 'phase': lambda r: repo[r].phase(),
1432 1442 'substate': lambda r: repo[r].substate,
1433 1443 'summary': lambda r: repo[r].description().splitlines()[0],
1434 1444 'diff': lambda r: list(repo[r].diff(git=True),)
1435 1445 }
1436 1446 for info in fields:
1437 1447 getfield = _funcs.get(info, None)
1438 1448 if getfield is None:
1439 1449 raise error.ParseError(
1440 1450 # i18n: "matching" is a keyword
1441 1451 _("unexpected field name passed to matching: %s") % info)
1442 1452 getfieldfuncs.append(getfield)
1443 1453 # convert the getfield array of functions into a "getinfo" function
1444 1454 # which returns an array of field values (or a single value if there
1445 1455 # is only one field to match)
1446 1456 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1447 1457
1448 1458 def matches(x):
1449 1459 for rev in revs:
1450 1460 target = getinfo(rev)
1451 1461 match = True
1452 1462 for n, f in enumerate(getfieldfuncs):
1453 1463 if target[n] != f(x):
1454 1464 match = False
1455 1465 if match:
1456 1466 return True
1457 1467 return False
1458 1468
1459 1469 return subset.filter(matches)
1460 1470
1461 1471 def reverse(repo, subset, x):
1462 1472 """``reverse(set)``
1463 1473 Reverse order of set.
1464 1474 """
1465 1475 l = getset(repo, subset, x)
1466 1476 l.reverse()
1467 1477 return l
1468 1478
1469 1479 def roots(repo, subset, x):
1470 1480 """``roots(set)``
1471 1481 Changesets in set with no parent changeset in set.
1472 1482 """
1473 1483 s = getset(repo, baseset(repo.changelog), x).set()
1474 1484 subset = baseset([r for r in subset if r in s])
1475 1485 cs = _children(repo, subset, s)
1476 1486 return subset - cs
1477 1487
1478 1488 def secret(repo, subset, x):
1479 1489 """``secret()``
1480 1490 Changeset in secret phase."""
1481 1491 # i18n: "secret" is a keyword
1482 1492 getargs(x, 0, 0, _("secret takes no arguments"))
1483 1493 pc = repo._phasecache
1484 1494 return subset.filter(lambda x: pc.phase(repo, x) == phases.secret)
1485 1495
1486 1496 def sort(repo, subset, x):
1487 1497 """``sort(set[, [-]key...])``
1488 1498 Sort set by keys. The default sort order is ascending, specify a key
1489 1499 as ``-key`` to sort in descending order.
1490 1500
1491 1501 The keys can be:
1492 1502
1493 1503 - ``rev`` for the revision number,
1494 1504 - ``branch`` for the branch name,
1495 1505 - ``desc`` for the commit message (description),
1496 1506 - ``user`` for user name (``author`` can be used as an alias),
1497 1507 - ``date`` for the commit date
1498 1508 """
1499 1509 # i18n: "sort" is a keyword
1500 1510 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1501 1511 keys = "rev"
1502 1512 if len(l) == 2:
1503 1513 # i18n: "sort" is a keyword
1504 1514 keys = getstring(l[1], _("sort spec must be a string"))
1505 1515
1506 1516 s = l[0]
1507 1517 keys = keys.split()
1508 1518 l = []
1509 1519 def invert(s):
1510 1520 return "".join(chr(255 - ord(c)) for c in s)
1511 1521 revs = getset(repo, subset, s)
1512 1522 if keys == ["rev"]:
1513 1523 revs.sort()
1514 1524 return revs
1515 1525 elif keys == ["-rev"]:
1516 1526 revs.sort(reverse=True)
1517 1527 return revs
1518 1528 for r in revs:
1519 1529 c = repo[r]
1520 1530 e = []
1521 1531 for k in keys:
1522 1532 if k == 'rev':
1523 1533 e.append(r)
1524 1534 elif k == '-rev':
1525 1535 e.append(-r)
1526 1536 elif k == 'branch':
1527 1537 e.append(c.branch())
1528 1538 elif k == '-branch':
1529 1539 e.append(invert(c.branch()))
1530 1540 elif k == 'desc':
1531 1541 e.append(c.description())
1532 1542 elif k == '-desc':
1533 1543 e.append(invert(c.description()))
1534 1544 elif k in 'user author':
1535 1545 e.append(c.user())
1536 1546 elif k in '-user -author':
1537 1547 e.append(invert(c.user()))
1538 1548 elif k == 'date':
1539 1549 e.append(c.date()[0])
1540 1550 elif k == '-date':
1541 1551 e.append(-c.date()[0])
1542 1552 else:
1543 1553 raise error.ParseError(_("unknown sort key %r") % k)
1544 1554 e.append(r)
1545 1555 l.append(e)
1546 1556 l.sort()
1547 1557 return baseset([e[-1] for e in l])
1548 1558
1549 1559 def _stringmatcher(pattern):
1550 1560 """
1551 1561 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1552 1562 returns the matcher name, pattern, and matcher function.
1553 1563 missing or unknown prefixes are treated as literal matches.
1554 1564
1555 1565 helper for tests:
1556 1566 >>> def test(pattern, *tests):
1557 1567 ... kind, pattern, matcher = _stringmatcher(pattern)
1558 1568 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1559 1569
1560 1570 exact matching (no prefix):
1561 1571 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1562 1572 ('literal', 'abcdefg', [False, False, True])
1563 1573
1564 1574 regex matching ('re:' prefix)
1565 1575 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1566 1576 ('re', 'a.+b', [False, False, True])
1567 1577
1568 1578 force exact matches ('literal:' prefix)
1569 1579 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1570 1580 ('literal', 're:foobar', [False, True])
1571 1581
1572 1582 unknown prefixes are ignored and treated as literals
1573 1583 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1574 1584 ('literal', 'foo:bar', [False, False, True])
1575 1585 """
1576 1586 if pattern.startswith('re:'):
1577 1587 pattern = pattern[3:]
1578 1588 try:
1579 1589 regex = re.compile(pattern)
1580 1590 except re.error, e:
1581 1591 raise error.ParseError(_('invalid regular expression: %s')
1582 1592 % e)
1583 1593 return 're', pattern, regex.search
1584 1594 elif pattern.startswith('literal:'):
1585 1595 pattern = pattern[8:]
1586 1596 return 'literal', pattern, pattern.__eq__
1587 1597
1588 1598 def _substringmatcher(pattern):
1589 1599 kind, pattern, matcher = _stringmatcher(pattern)
1590 1600 if kind == 'literal':
1591 1601 matcher = lambda s: pattern in s
1592 1602 return kind, pattern, matcher
1593 1603
1594 1604 def tag(repo, subset, x):
1595 1605 """``tag([name])``
1596 1606 The specified tag by name, or all tagged revisions if no name is given.
1597 1607
1598 1608 If `name` starts with `re:`, the remainder of the name is treated as
1599 1609 a regular expression. To match a tag that actually starts with `re:`,
1600 1610 use the prefix `literal:`.
1601 1611 """
1602 1612 # i18n: "tag" is a keyword
1603 1613 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1604 1614 cl = repo.changelog
1605 1615 if args:
1606 1616 pattern = getstring(args[0],
1607 1617 # i18n: "tag" is a keyword
1608 1618 _('the argument to tag must be a string'))
1609 1619 kind, pattern, matcher = _stringmatcher(pattern)
1610 1620 if kind == 'literal':
1611 1621 # avoid resolving all tags
1612 1622 tn = repo._tagscache.tags.get(pattern, None)
1613 1623 if tn is None:
1614 1624 raise util.Abort(_("tag '%s' does not exist") % pattern)
1615 1625 s = set([repo[tn].rev()])
1616 1626 else:
1617 1627 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1618 1628 else:
1619 1629 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1620 1630 return subset & s
1621 1631
1622 1632 def tagged(repo, subset, x):
1623 1633 return tag(repo, subset, x)
1624 1634
1625 1635 def unstable(repo, subset, x):
1626 1636 """``unstable()``
1627 1637 Non-obsolete changesets with obsolete ancestors.
1628 1638 """
1629 1639 # i18n: "unstable" is a keyword
1630 1640 getargs(x, 0, 0, _("unstable takes no arguments"))
1631 1641 unstables = obsmod.getrevs(repo, 'unstable')
1632 1642 return subset & unstables
1633 1643
1634 1644
1635 1645 def user(repo, subset, x):
1636 1646 """``user(string)``
1637 1647 User name contains string. The match is case-insensitive.
1638 1648
1639 1649 If `string` starts with `re:`, the remainder of the string is treated as
1640 1650 a regular expression. To match a user that actually contains `re:`, use
1641 1651 the prefix `literal:`.
1642 1652 """
1643 1653 return author(repo, subset, x)
1644 1654
1645 1655 # for internal use
1646 1656 def _list(repo, subset, x):
1647 1657 s = getstring(x, "internal error")
1648 1658 if not s:
1649 1659 return baseset([])
1650 1660 ls = [repo[r].rev() for r in s.split('\0')]
1651 1661 s = subset.set()
1652 1662 return baseset([r for r in ls if r in s])
1653 1663
1654 1664 # for internal use
1655 1665 def _intlist(repo, subset, x):
1656 1666 s = getstring(x, "internal error")
1657 1667 if not s:
1658 1668 return baseset([])
1659 1669 ls = [int(r) for r in s.split('\0')]
1660 1670 s = subset.set()
1661 1671 return baseset([r for r in ls if r in s])
1662 1672
1663 1673 # for internal use
1664 1674 def _hexlist(repo, subset, x):
1665 1675 s = getstring(x, "internal error")
1666 1676 if not s:
1667 1677 return baseset([])
1668 1678 cl = repo.changelog
1669 1679 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
1670 1680 s = subset.set()
1671 1681 return baseset([r for r in ls if r in s])
1672 1682
1673 1683 symbols = {
1674 1684 "adds": adds,
1675 1685 "all": getall,
1676 1686 "ancestor": ancestor,
1677 1687 "ancestors": ancestors,
1678 1688 "_firstancestors": _firstancestors,
1679 1689 "author": author,
1680 1690 "only": only,
1681 1691 "bisect": bisect,
1682 1692 "bisected": bisected,
1683 1693 "bookmark": bookmark,
1684 1694 "branch": branch,
1685 1695 "branchpoint": branchpoint,
1686 1696 "bumped": bumped,
1687 1697 "bundle": bundle,
1688 1698 "children": children,
1689 1699 "closed": closed,
1690 1700 "contains": contains,
1691 1701 "converted": converted,
1692 1702 "date": date,
1693 1703 "desc": desc,
1694 1704 "descendants": descendants,
1695 1705 "_firstdescendants": _firstdescendants,
1696 1706 "destination": destination,
1697 1707 "divergent": divergent,
1698 1708 "draft": draft,
1699 1709 "extinct": extinct,
1700 1710 "extra": extra,
1701 1711 "file": hasfile,
1702 1712 "filelog": filelog,
1703 1713 "first": first,
1704 1714 "follow": follow,
1705 1715 "_followfirst": _followfirst,
1706 1716 "grep": grep,
1707 1717 "head": head,
1708 1718 "heads": heads,
1709 1719 "hidden": hidden,
1710 1720 "id": node_,
1711 1721 "keyword": keyword,
1712 1722 "last": last,
1713 1723 "limit": limit,
1714 1724 "_matchfiles": _matchfiles,
1715 1725 "max": maxrev,
1716 1726 "merge": merge,
1717 1727 "min": minrev,
1718 1728 "_missingancestors": _missingancestors,
1719 1729 "modifies": modifies,
1720 1730 "obsolete": obsolete,
1721 1731 "origin": origin,
1722 1732 "outgoing": outgoing,
1723 1733 "p1": p1,
1724 1734 "p2": p2,
1725 1735 "parents": parents,
1726 1736 "present": present,
1727 1737 "public": public,
1728 1738 "remote": remote,
1729 1739 "removes": removes,
1730 1740 "rev": rev,
1731 1741 "reverse": reverse,
1732 1742 "roots": roots,
1733 1743 "sort": sort,
1734 1744 "secret": secret,
1735 1745 "matching": matching,
1736 1746 "tag": tag,
1737 1747 "tagged": tagged,
1738 1748 "user": user,
1739 1749 "unstable": unstable,
1740 1750 "_list": _list,
1741 1751 "_intlist": _intlist,
1742 1752 "_hexlist": _hexlist,
1743 1753 }
1744 1754
1745 1755 # symbols which can't be used for a DoS attack for any given input
1746 1756 # (e.g. those which accept regexes as plain strings shouldn't be included)
1747 1757 # functions that just return a lot of changesets (like all) don't count here
1748 1758 safesymbols = set([
1749 1759 "adds",
1750 1760 "all",
1751 1761 "ancestor",
1752 1762 "ancestors",
1753 1763 "_firstancestors",
1754 1764 "author",
1755 1765 "bisect",
1756 1766 "bisected",
1757 1767 "bookmark",
1758 1768 "branch",
1759 1769 "branchpoint",
1760 1770 "bumped",
1761 1771 "bundle",
1762 1772 "children",
1763 1773 "closed",
1764 1774 "converted",
1765 1775 "date",
1766 1776 "desc",
1767 1777 "descendants",
1768 1778 "_firstdescendants",
1769 1779 "destination",
1770 1780 "divergent",
1771 1781 "draft",
1772 1782 "extinct",
1773 1783 "extra",
1774 1784 "file",
1775 1785 "filelog",
1776 1786 "first",
1777 1787 "follow",
1778 1788 "_followfirst",
1779 1789 "head",
1780 1790 "heads",
1781 1791 "hidden",
1782 1792 "id",
1783 1793 "keyword",
1784 1794 "last",
1785 1795 "limit",
1786 1796 "_matchfiles",
1787 1797 "max",
1788 1798 "merge",
1789 1799 "min",
1790 1800 "_missingancestors",
1791 1801 "modifies",
1792 1802 "obsolete",
1793 1803 "origin",
1794 1804 "outgoing",
1795 1805 "p1",
1796 1806 "p2",
1797 1807 "parents",
1798 1808 "present",
1799 1809 "public",
1800 1810 "remote",
1801 1811 "removes",
1802 1812 "rev",
1803 1813 "reverse",
1804 1814 "roots",
1805 1815 "sort",
1806 1816 "secret",
1807 1817 "matching",
1808 1818 "tag",
1809 1819 "tagged",
1810 1820 "user",
1811 1821 "unstable",
1812 1822 "_list",
1813 1823 "_intlist",
1814 1824 "_hexlist",
1815 1825 ])
1816 1826
1817 1827 methods = {
1818 1828 "range": rangeset,
1819 1829 "dagrange": dagrange,
1820 1830 "string": stringset,
1821 1831 "symbol": symbolset,
1822 1832 "and": andset,
1823 1833 "or": orset,
1824 1834 "not": notset,
1825 1835 "list": listset,
1826 1836 "func": func,
1827 1837 "ancestor": ancestorspec,
1828 1838 "parent": parentspec,
1829 1839 "parentpost": p1,
1830 1840 }
1831 1841
1832 1842 def optimize(x, small):
1833 1843 if x is None:
1834 1844 return 0, x
1835 1845
1836 1846 smallbonus = 1
1837 1847 if small:
1838 1848 smallbonus = .5
1839 1849
1840 1850 op = x[0]
1841 1851 if op == 'minus':
1842 1852 return optimize(('and', x[1], ('not', x[2])), small)
1843 1853 elif op == 'dagrangepre':
1844 1854 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1845 1855 elif op == 'dagrangepost':
1846 1856 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1847 1857 elif op == 'rangepre':
1848 1858 return optimize(('range', ('string', '0'), x[1]), small)
1849 1859 elif op == 'rangepost':
1850 1860 return optimize(('range', x[1], ('string', 'tip')), small)
1851 1861 elif op == 'negate':
1852 1862 return optimize(('string',
1853 1863 '-' + getstring(x[1], _("can't negate that"))), small)
1854 1864 elif op in 'string symbol negate':
1855 1865 return smallbonus, x # single revisions are small
1856 1866 elif op == 'and':
1857 1867 wa, ta = optimize(x[1], True)
1858 1868 wb, tb = optimize(x[2], True)
1859 1869
1860 1870 # (::x and not ::y)/(not ::y and ::x) have a fast path
1861 1871 def ismissingancestors(revs, bases):
1862 1872 return (
1863 1873 revs[0] == 'func'
1864 1874 and getstring(revs[1], _('not a symbol')) == 'ancestors'
1865 1875 and bases[0] == 'not'
1866 1876 and bases[1][0] == 'func'
1867 1877 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
1868 1878
1869 1879 w = min(wa, wb)
1870 1880 if ismissingancestors(ta, tb):
1871 1881 return w, ('func', ('symbol', '_missingancestors'),
1872 1882 ('list', ta[2], tb[1][2]))
1873 1883 if ismissingancestors(tb, ta):
1874 1884 return w, ('func', ('symbol', '_missingancestors'),
1875 1885 ('list', tb[2], ta[1][2]))
1876 1886
1877 1887 if wa > wb:
1878 1888 return w, (op, tb, ta)
1879 1889 return w, (op, ta, tb)
1880 1890 elif op == 'or':
1881 1891 wa, ta = optimize(x[1], False)
1882 1892 wb, tb = optimize(x[2], False)
1883 1893 if wb < wa:
1884 1894 wb, wa = wa, wb
1885 1895 return max(wa, wb), (op, ta, tb)
1886 1896 elif op == 'not':
1887 1897 o = optimize(x[1], not small)
1888 1898 return o[0], (op, o[1])
1889 1899 elif op == 'parentpost':
1890 1900 o = optimize(x[1], small)
1891 1901 return o[0], (op, o[1])
1892 1902 elif op == 'group':
1893 1903 return optimize(x[1], small)
1894 1904 elif op in 'dagrange range list parent ancestorspec':
1895 1905 if op == 'parent':
1896 1906 # x^:y means (x^) : y, not x ^ (:y)
1897 1907 post = ('parentpost', x[1])
1898 1908 if x[2][0] == 'dagrangepre':
1899 1909 return optimize(('dagrange', post, x[2][1]), small)
1900 1910 elif x[2][0] == 'rangepre':
1901 1911 return optimize(('range', post, x[2][1]), small)
1902 1912
1903 1913 wa, ta = optimize(x[1], small)
1904 1914 wb, tb = optimize(x[2], small)
1905 1915 return wa + wb, (op, ta, tb)
1906 1916 elif op == 'func':
1907 1917 f = getstring(x[1], _("not a symbol"))
1908 1918 wa, ta = optimize(x[2], small)
1909 1919 if f in ("author branch closed date desc file grep keyword "
1910 1920 "outgoing user"):
1911 1921 w = 10 # slow
1912 1922 elif f in "modifies adds removes":
1913 1923 w = 30 # slower
1914 1924 elif f == "contains":
1915 1925 w = 100 # very slow
1916 1926 elif f == "ancestor":
1917 1927 w = 1 * smallbonus
1918 1928 elif f in "reverse limit first":
1919 1929 w = 0
1920 1930 elif f in "sort":
1921 1931 w = 10 # assume most sorts look at changelog
1922 1932 else:
1923 1933 w = 1
1924 1934 return w + wa, (op, x[1], ta)
1925 1935 return 1, x
1926 1936
1927 1937 _aliasarg = ('func', ('symbol', '_aliasarg'))
1928 1938 def _getaliasarg(tree):
1929 1939 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1930 1940 return X, None otherwise.
1931 1941 """
1932 1942 if (len(tree) == 3 and tree[:2] == _aliasarg
1933 1943 and tree[2][0] == 'string'):
1934 1944 return tree[2][1]
1935 1945 return None
1936 1946
1937 1947 def _checkaliasarg(tree, known=None):
1938 1948 """Check tree contains no _aliasarg construct or only ones which
1939 1949 value is in known. Used to avoid alias placeholders injection.
1940 1950 """
1941 1951 if isinstance(tree, tuple):
1942 1952 arg = _getaliasarg(tree)
1943 1953 if arg is not None and (not known or arg not in known):
1944 1954 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1945 1955 for t in tree:
1946 1956 _checkaliasarg(t, known)
1947 1957
1948 1958 class revsetalias(object):
1949 1959 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1950 1960 args = None
1951 1961
1952 1962 def __init__(self, name, value):
1953 1963 '''Aliases like:
1954 1964
1955 1965 h = heads(default)
1956 1966 b($1) = ancestors($1) - ancestors(default)
1957 1967 '''
1958 1968 m = self.funcre.search(name)
1959 1969 if m:
1960 1970 self.name = m.group(1)
1961 1971 self.tree = ('func', ('symbol', m.group(1)))
1962 1972 self.args = [x.strip() for x in m.group(2).split(',')]
1963 1973 for arg in self.args:
1964 1974 # _aliasarg() is an unknown symbol only used separate
1965 1975 # alias argument placeholders from regular strings.
1966 1976 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1967 1977 else:
1968 1978 self.name = name
1969 1979 self.tree = ('symbol', name)
1970 1980
1971 1981 self.replacement, pos = parse(value)
1972 1982 if pos != len(value):
1973 1983 raise error.ParseError(_('invalid token'), pos)
1974 1984 # Check for placeholder injection
1975 1985 _checkaliasarg(self.replacement, self.args)
1976 1986
1977 1987 def _getalias(aliases, tree):
1978 1988 """If tree looks like an unexpanded alias, return it. Return None
1979 1989 otherwise.
1980 1990 """
1981 1991 if isinstance(tree, tuple) and tree:
1982 1992 if tree[0] == 'symbol' and len(tree) == 2:
1983 1993 name = tree[1]
1984 1994 alias = aliases.get(name)
1985 1995 if alias and alias.args is None and alias.tree == tree:
1986 1996 return alias
1987 1997 if tree[0] == 'func' and len(tree) > 1:
1988 1998 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1989 1999 name = tree[1][1]
1990 2000 alias = aliases.get(name)
1991 2001 if alias and alias.args is not None and alias.tree == tree[:2]:
1992 2002 return alias
1993 2003 return None
1994 2004
1995 2005 def _expandargs(tree, args):
1996 2006 """Replace _aliasarg instances with the substitution value of the
1997 2007 same name in args, recursively.
1998 2008 """
1999 2009 if not tree or not isinstance(tree, tuple):
2000 2010 return tree
2001 2011 arg = _getaliasarg(tree)
2002 2012 if arg is not None:
2003 2013 return args[arg]
2004 2014 return tuple(_expandargs(t, args) for t in tree)
2005 2015
2006 2016 def _expandaliases(aliases, tree, expanding, cache):
2007 2017 """Expand aliases in tree, recursively.
2008 2018
2009 2019 'aliases' is a dictionary mapping user defined aliases to
2010 2020 revsetalias objects.
2011 2021 """
2012 2022 if not isinstance(tree, tuple):
2013 2023 # Do not expand raw strings
2014 2024 return tree
2015 2025 alias = _getalias(aliases, tree)
2016 2026 if alias is not None:
2017 2027 if alias in expanding:
2018 2028 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2019 2029 'detected') % alias.name)
2020 2030 expanding.append(alias)
2021 2031 if alias.name not in cache:
2022 2032 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2023 2033 expanding, cache)
2024 2034 result = cache[alias.name]
2025 2035 expanding.pop()
2026 2036 if alias.args is not None:
2027 2037 l = getlist(tree[2])
2028 2038 if len(l) != len(alias.args):
2029 2039 raise error.ParseError(
2030 2040 _('invalid number of arguments: %s') % len(l))
2031 2041 l = [_expandaliases(aliases, a, [], cache) for a in l]
2032 2042 result = _expandargs(result, dict(zip(alias.args, l)))
2033 2043 else:
2034 2044 result = tuple(_expandaliases(aliases, t, expanding, cache)
2035 2045 for t in tree)
2036 2046 return result
2037 2047
2038 2048 def findaliases(ui, tree):
2039 2049 _checkaliasarg(tree)
2040 2050 aliases = {}
2041 2051 for k, v in ui.configitems('revsetalias'):
2042 2052 alias = revsetalias(k, v)
2043 2053 aliases[alias.name] = alias
2044 2054 return _expandaliases(aliases, tree, [], {})
2045 2055
2046 2056 def parse(spec, lookup=None):
2047 2057 p = parser.parser(tokenize, elements)
2048 2058 return p.parse(spec, lookup=lookup)
2049 2059
2050 2060 def match(ui, spec, repo=None):
2051 2061 if not spec:
2052 2062 raise error.ParseError(_("empty query"))
2053 2063 lookup = None
2054 2064 if repo:
2055 2065 lookup = repo.__contains__
2056 2066 tree, pos = parse(spec, lookup)
2057 2067 if (pos != len(spec)):
2058 2068 raise error.ParseError(_("invalid token"), pos)
2059 2069 if ui:
2060 2070 tree = findaliases(ui, tree)
2061 2071 weight, tree = optimize(tree, True)
2062 2072 def mfunc(repo, subset):
2063 2073 if util.safehasattr(subset, 'set'):
2064 2074 return getset(repo, subset, tree)
2065 2075 return getset(repo, baseset(subset), tree)
2066 2076 return mfunc
2067 2077
2068 2078 def formatspec(expr, *args):
2069 2079 '''
2070 2080 This is a convenience function for using revsets internally, and
2071 2081 escapes arguments appropriately. Aliases are intentionally ignored
2072 2082 so that intended expression behavior isn't accidentally subverted.
2073 2083
2074 2084 Supported arguments:
2075 2085
2076 2086 %r = revset expression, parenthesized
2077 2087 %d = int(arg), no quoting
2078 2088 %s = string(arg), escaped and single-quoted
2079 2089 %b = arg.branch(), escaped and single-quoted
2080 2090 %n = hex(arg), single-quoted
2081 2091 %% = a literal '%'
2082 2092
2083 2093 Prefixing the type with 'l' specifies a parenthesized list of that type.
2084 2094
2085 2095 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2086 2096 '(10 or 11):: and ((this()) or (that()))'
2087 2097 >>> formatspec('%d:: and not %d::', 10, 20)
2088 2098 '10:: and not 20::'
2089 2099 >>> formatspec('%ld or %ld', [], [1])
2090 2100 "_list('') or 1"
2091 2101 >>> formatspec('keyword(%s)', 'foo\\xe9')
2092 2102 "keyword('foo\\\\xe9')"
2093 2103 >>> b = lambda: 'default'
2094 2104 >>> b.branch = b
2095 2105 >>> formatspec('branch(%b)', b)
2096 2106 "branch('default')"
2097 2107 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2098 2108 "root(_list('a\\x00b\\x00c\\x00d'))"
2099 2109 '''
2100 2110
2101 2111 def quote(s):
2102 2112 return repr(str(s))
2103 2113
2104 2114 def argtype(c, arg):
2105 2115 if c == 'd':
2106 2116 return str(int(arg))
2107 2117 elif c == 's':
2108 2118 return quote(arg)
2109 2119 elif c == 'r':
2110 2120 parse(arg) # make sure syntax errors are confined
2111 2121 return '(%s)' % arg
2112 2122 elif c == 'n':
2113 2123 return quote(node.hex(arg))
2114 2124 elif c == 'b':
2115 2125 return quote(arg.branch())
2116 2126
2117 2127 def listexp(s, t):
2118 2128 l = len(s)
2119 2129 if l == 0:
2120 2130 return "_list('')"
2121 2131 elif l == 1:
2122 2132 return argtype(t, s[0])
2123 2133 elif t == 'd':
2124 2134 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2125 2135 elif t == 's':
2126 2136 return "_list('%s')" % "\0".join(s)
2127 2137 elif t == 'n':
2128 2138 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2129 2139 elif t == 'b':
2130 2140 return "_list('%s')" % "\0".join(a.branch() for a in s)
2131 2141
2132 2142 m = l // 2
2133 2143 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2134 2144
2135 2145 ret = ''
2136 2146 pos = 0
2137 2147 arg = 0
2138 2148 while pos < len(expr):
2139 2149 c = expr[pos]
2140 2150 if c == '%':
2141 2151 pos += 1
2142 2152 d = expr[pos]
2143 2153 if d == '%':
2144 2154 ret += d
2145 2155 elif d in 'dsnbr':
2146 2156 ret += argtype(d, args[arg])
2147 2157 arg += 1
2148 2158 elif d == 'l':
2149 2159 # a list of some type
2150 2160 pos += 1
2151 2161 d = expr[pos]
2152 2162 ret += listexp(list(args[arg]), d)
2153 2163 arg += 1
2154 2164 else:
2155 2165 raise util.Abort('unexpected revspec format character %s' % d)
2156 2166 else:
2157 2167 ret += c
2158 2168 pos += 1
2159 2169
2160 2170 return ret
2161 2171
2162 2172 def prettyformat(tree):
2163 2173 def _prettyformat(tree, level, lines):
2164 2174 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2165 2175 lines.append((level, str(tree)))
2166 2176 else:
2167 2177 lines.append((level, '(%s' % tree[0]))
2168 2178 for s in tree[1:]:
2169 2179 _prettyformat(s, level + 1, lines)
2170 2180 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
2171 2181
2172 2182 lines = []
2173 2183 _prettyformat(tree, 0, lines)
2174 2184 output = '\n'.join((' '*l + s) for l, s in lines)
2175 2185 return output
2176 2186
2177 2187 def depth(tree):
2178 2188 if isinstance(tree, tuple):
2179 2189 return max(map(depth, tree)) + 1
2180 2190 else:
2181 2191 return 0
2182 2192
2183 2193 def funcsused(tree):
2184 2194 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2185 2195 return set()
2186 2196 else:
2187 2197 funcs = set()
2188 2198 for s in tree[1:]:
2189 2199 funcs |= funcsused(s)
2190 2200 if tree[0] == 'func':
2191 2201 funcs.add(tree[1][1])
2192 2202 return funcs
2193 2203
2194 2204 class baseset(list):
2195 2205 """Basic data structure that represents a revset and contains the basic
2196 2206 operation that it should be able to perform.
2197 2207
2198 2208 Every method in this class should be implemented by any smartset class.
2199 2209 """
2200 2210 def __init__(self, data=()):
2201 2211 super(baseset, self).__init__(data)
2202 2212 self._set = None
2203 2213
2204 2214 def ascending(self):
2205 2215 """Sorts the set in ascending order (in place).
2206 2216
2207 2217 This is part of the mandatory API for smartset."""
2208 2218 self.sort()
2209 2219
2210 2220 def descending(self):
2211 2221 """Sorts the set in descending order (in place).
2212 2222
2213 2223 This is part of the mandatory API for smartset."""
2214 2224 self.sort(reverse=True)
2215 2225
2216 2226 def min(self):
2217 2227 return min(self)
2218 2228
2219 2229 def max(self):
2220 2230 return max(self)
2221 2231
2222 2232 def set(self):
2223 2233 """Returns a set or a smartset containing all the elements.
2224 2234
2225 2235 The returned structure should be the fastest option for membership
2226 2236 testing.
2227 2237
2228 2238 This is part of the mandatory API for smartset."""
2229 2239 if not self._set:
2230 2240 self._set = set(self)
2231 2241 return self._set
2232 2242
2233 2243 def __sub__(self, other):
2234 2244 """Returns a new object with the substraction of the two collections.
2235 2245
2236 2246 This is part of the mandatory API for smartset."""
2237 2247 if isinstance(other, baseset):
2238 2248 s = other.set()
2239 2249 else:
2240 2250 s = set(other)
2241 2251 return baseset(self.set() - s)
2242 2252
2243 2253 def __and__(self, other):
2244 2254 """Returns a new object with the intersection of the two collections.
2245 2255
2246 2256 This is part of the mandatory API for smartset."""
2247 2257 if isinstance(other, baseset):
2248 2258 other = other.set()
2249 2259 return baseset([y for y in self if y in other])
2250 2260
2251 2261 def __add__(self, other):
2252 2262 """Returns a new object with the union of the two collections.
2253 2263
2254 2264 This is part of the mandatory API for smartset."""
2255 2265 s = self.set()
2256 2266 l = [r for r in other if r not in s]
2257 2267 return baseset(list(self) + l)
2258 2268
2259 2269 def isascending(self):
2260 2270 """Returns True if the collection is ascending order, False if not.
2261 2271
2262 2272 This is part of the mandatory API for smartset."""
2263 2273 return False
2264 2274
2265 2275 def isdescending(self):
2266 2276 """Returns True if the collection is descending order, False if not.
2267 2277
2268 2278 This is part of the mandatory API for smartset."""
2269 2279 return False
2270 2280
2271 2281 def filter(self, condition):
2272 2282 """Returns this smartset filtered by condition as a new smartset.
2273 2283
2274 2284 `condition` is a callable which takes a revision number and returns a
2275 2285 boolean.
2276 2286
2277 2287 This is part of the mandatory API for smartset."""
2278 2288 return lazyset(self, condition)
2279 2289
2280 2290 class _orderedsetmixin(object):
2281 2291 """Mixin class with utility methods for smartsets
2282 2292
2283 2293 This should be extended by smartsets which have the isascending(),
2284 2294 isdescending() and reverse() methods"""
2285 2295
2286 2296 def _first(self):
2287 2297 """return the first revision in the set"""
2288 2298 for r in self:
2289 2299 return r
2290 2300 raise ValueError('arg is an empty sequence')
2291 2301
2292 2302 def _last(self):
2293 2303 """return the last revision in the set"""
2294 2304 self.reverse()
2295 2305 m = self._first()
2296 2306 self.reverse()
2297 2307 return m
2298 2308
2299 2309 def min(self):
2300 2310 """return the smallest element in the set"""
2301 2311 if self.isascending():
2302 2312 return self._first()
2303 2313 return self._last()
2304 2314
2305 2315 def max(self):
2306 2316 """return the largest element in the set"""
2307 2317 if self.isascending():
2308 2318 return self._last()
2309 2319 return self._first()
2310 2320
2311 2321 class lazyset(object):
2312 2322 """Duck type for baseset class which iterates lazily over the revisions in
2313 2323 the subset and contains a function which tests for membership in the
2314 2324 revset
2315 2325 """
2316 2326 def __init__(self, subset, condition=lambda x: True):
2317 2327 """
2318 2328 condition: a function that decide whether a revision in the subset
2319 2329 belongs to the revset or not.
2320 2330 """
2321 2331 self._subset = subset
2322 2332 self._condition = condition
2323 2333 self._cache = {}
2324 2334
2325 2335 def ascending(self):
2326 2336 self._subset.sort()
2327 2337
2328 2338 def descending(self):
2329 2339 self._subset.sort(reverse=True)
2330 2340
2331 2341 def min(self):
2332 2342 return min(self)
2333 2343
2334 2344 def max(self):
2335 2345 return max(self)
2336 2346
2337 2347 def __contains__(self, x):
2338 2348 c = self._cache
2339 2349 if x not in c:
2340 2350 c[x] = x in self._subset and self._condition(x)
2341 2351 return c[x]
2342 2352
2343 2353 def __iter__(self):
2344 2354 cond = self._condition
2345 2355 for x in self._subset:
2346 2356 if cond(x):
2347 2357 yield x
2348 2358
2349 2359 def __and__(self, x):
2350 2360 return lazyset(self, lambda r: r in x)
2351 2361
2352 2362 def __sub__(self, x):
2353 2363 return lazyset(self, lambda r: r not in x)
2354 2364
2355 2365 def __add__(self, x):
2356 2366 return _addset(self, x)
2357 2367
2358 2368 def __nonzero__(self):
2359 2369 for r in self:
2360 2370 return True
2361 2371 return False
2362 2372
2363 2373 def __len__(self):
2364 2374 # Basic implementation to be changed in future patches.
2365 2375 l = baseset([r for r in self])
2366 2376 return len(l)
2367 2377
2368 2378 def __getitem__(self, x):
2369 2379 # Basic implementation to be changed in future patches.
2370 2380 l = baseset([r for r in self])
2371 2381 return l[x]
2372 2382
2373 2383 def sort(self, reverse=False):
2374 2384 if not util.safehasattr(self._subset, 'sort'):
2375 2385 self._subset = baseset(self._subset)
2376 2386 self._subset.sort(reverse=reverse)
2377 2387
2378 2388 def reverse(self):
2379 2389 self._subset.reverse()
2380 2390
2381 2391 def set(self):
2382 2392 return set([r for r in self])
2383 2393
2384 2394 def isascending(self):
2385 2395 return False
2386 2396
2387 2397 def isdescending(self):
2388 2398 return False
2389 2399
2390 2400 def filter(self, l):
2391 2401 return lazyset(self, l)
2392 2402
2393 2403 class orderedlazyset(_orderedsetmixin, lazyset):
2394 2404 """Subclass of lazyset which subset can be ordered either ascending or
2395 2405 descendingly
2396 2406 """
2397 2407 def __init__(self, subset, condition, ascending=True):
2398 2408 super(orderedlazyset, self).__init__(subset, condition)
2399 2409 self._ascending = ascending
2400 2410
2401 2411 def filter(self, l):
2402 2412 return orderedlazyset(self, l, ascending=self._ascending)
2403 2413
2404 2414 def ascending(self):
2405 2415 if not self._ascending:
2406 2416 self.reverse()
2407 2417
2408 2418 def descending(self):
2409 2419 if self._ascending:
2410 2420 self.reverse()
2411 2421
2412 2422 def __and__(self, x):
2413 2423 return orderedlazyset(self, lambda r: r in x,
2414 2424 ascending=self._ascending)
2415 2425
2416 2426 def __sub__(self, x):
2417 2427 return orderedlazyset(self, lambda r: r not in x,
2418 2428 ascending=self._ascending)
2419 2429
2420 2430 def __add__(self, x):
2421 2431 kwargs = {}
2422 2432 if self.isascending() and x.isascending():
2423 2433 kwargs['ascending'] = True
2424 2434 if self.isdescending() and x.isdescending():
2425 2435 kwargs['ascending'] = False
2426 2436 return _addset(self, x, **kwargs)
2427 2437
2428 2438 def sort(self, reverse=False):
2429 2439 if reverse:
2430 2440 if self._ascending:
2431 2441 self._subset.sort(reverse=reverse)
2432 2442 else:
2433 2443 if not self._ascending:
2434 2444 self._subset.sort(reverse=reverse)
2435 2445 self._ascending = not reverse
2436 2446
2437 2447 def isascending(self):
2438 2448 return self._ascending
2439 2449
2440 2450 def isdescending(self):
2441 2451 return not self._ascending
2442 2452
2443 2453 def reverse(self):
2444 2454 self._subset.reverse()
2445 2455 self._ascending = not self._ascending
2446 2456
2447 2457 class _addset(_orderedsetmixin):
2448 2458 """Represent the addition of two sets
2449 2459
2450 2460 Wrapper structure for lazily adding two structures without losing much
2451 2461 performance on the __contains__ method
2452 2462
2453 2463 If the ascending attribute is set, that means the two structures are
2454 2464 ordered in either an ascending or descending way. Therefore, we can add
2455 2465 them mantaining the order by iterating over both at the same time
2456 2466
2457 2467 This class does not duck-type baseset and it's only supposed to be used
2458 2468 internally
2459 2469 """
2460 2470 def __init__(self, revs1, revs2, ascending=None):
2461 2471 self._r1 = revs1
2462 2472 self._r2 = revs2
2463 2473 self._iter = None
2464 2474 self._ascending = ascending
2465 2475 self._genlist = None
2466 2476
2467 2477 def __len__(self):
2468 2478 return len(self._list)
2469 2479
2470 2480 @util.propertycache
2471 2481 def _list(self):
2472 2482 if not self._genlist:
2473 2483 self._genlist = baseset(self._iterator())
2474 2484 return self._genlist
2475 2485
2476 2486 def filter(self, condition):
2477 2487 if self._ascending is not None:
2478 2488 return orderedlazyset(self, condition, ascending=self._ascending)
2479 2489 return lazyset(self, condition)
2480 2490
2481 2491 def ascending(self):
2482 2492 if self._ascending is None:
2483 2493 self.sort()
2484 2494 self._ascending = True
2485 2495 else:
2486 2496 if not self._ascending:
2487 2497 self.reverse()
2488 2498
2489 2499 def descending(self):
2490 2500 if self._ascending is None:
2491 2501 self.sort(reverse=True)
2492 2502 self._ascending = False
2493 2503 else:
2494 2504 if self._ascending:
2495 2505 self.reverse()
2496 2506
2497 2507 def __and__(self, other):
2498 2508 filterfunc = other.__contains__
2499 2509 if self._ascending is not None:
2500 2510 return orderedlazyset(self, filterfunc, ascending=self._ascending)
2501 2511 return lazyset(self, filterfunc)
2502 2512
2503 2513 def __sub__(self, other):
2504 2514 filterfunc = lambda r: r not in other
2505 2515 if self._ascending is not None:
2506 2516 return orderedlazyset(self, filterfunc, ascending=self._ascending)
2507 2517 return lazyset(self, filterfunc)
2508 2518
2509 2519 def __add__(self, other):
2510 2520 """When both collections are ascending or descending, preserve the order
2511 2521 """
2512 2522 kwargs = {}
2513 2523 if self._ascending is not None:
2514 2524 if self.isascending() and other.isascending():
2515 2525 kwargs['ascending'] = True
2516 2526 if self.isdescending() and other.isdescending():
2517 2527 kwargs['ascending'] = False
2518 2528 return _addset(self, other, **kwargs)
2519 2529
2520 2530 def _iterator(self):
2521 2531 """Iterate over both collections without repeating elements
2522 2532
2523 2533 If the ascending attribute is not set, iterate over the first one and
2524 2534 then over the second one checking for membership on the first one so we
2525 2535 dont yield any duplicates.
2526 2536
2527 2537 If the ascending attribute is set, iterate over both collections at the
2528 2538 same time, yielding only one value at a time in the given order.
2529 2539 """
2530 2540 if not self._iter:
2531 2541 def gen():
2532 2542 if self._ascending is None:
2533 2543 for r in self._r1:
2534 2544 yield r
2535 2545 s = self._r1.set()
2536 2546 for r in self._r2:
2537 2547 if r not in s:
2538 2548 yield r
2539 2549 else:
2540 2550 iter1 = iter(self._r1)
2541 2551 iter2 = iter(self._r2)
2542 2552
2543 2553 val1 = None
2544 2554 val2 = None
2545 2555
2546 2556 choice = max
2547 2557 if self._ascending:
2548 2558 choice = min
2549 2559 try:
2550 2560 # Consume both iterators in an ordered way until one is
2551 2561 # empty
2552 2562 while True:
2553 2563 if val1 is None:
2554 2564 val1 = iter1.next()
2555 2565 if val2 is None:
2556 2566 val2 = iter2.next()
2557 2567 next = choice(val1, val2)
2558 2568 yield next
2559 2569 if val1 == next:
2560 2570 val1 = None
2561 2571 if val2 == next:
2562 2572 val2 = None
2563 2573 except StopIteration:
2564 2574 # Flush any remaining values and consume the other one
2565 2575 it = iter2
2566 2576 if val1 is not None:
2567 2577 yield val1
2568 2578 it = iter1
2569 2579 elif val2 is not None:
2570 2580 # might have been equality and both are empty
2571 2581 yield val2
2572 2582 for val in it:
2573 2583 yield val
2574 2584
2575 2585 self._iter = _generatorset(gen())
2576 2586
2577 2587 return self._iter
2578 2588
2579 2589 def __iter__(self):
2580 2590 if self._genlist:
2581 2591 return iter(self._genlist)
2582 2592 return iter(self._iterator())
2583 2593
2584 2594 def __contains__(self, x):
2585 2595 return x in self._r1 or x in self._r2
2586 2596
2587 2597 def set(self):
2588 2598 return self
2589 2599
2590 2600 def sort(self, reverse=False):
2591 2601 """Sort the added set
2592 2602
2593 2603 For this we use the cached list with all the generated values and if we
2594 2604 know they are ascending or descending we can sort them in a smart way.
2595 2605 """
2596 2606 if self._ascending is None:
2597 2607 self._list.sort(reverse=reverse)
2598 2608 self._ascending = not reverse
2599 2609 else:
2600 2610 if bool(self._ascending) == bool(reverse):
2601 2611 self.reverse()
2602 2612
2603 2613 def isascending(self):
2604 2614 return self._ascending is not None and self._ascending
2605 2615
2606 2616 def isdescending(self):
2607 2617 return self._ascending is not None and not self._ascending
2608 2618
2609 2619 def reverse(self):
2610 2620 self._list.reverse()
2611 2621 if self._ascending is not None:
2612 2622 self._ascending = not self._ascending
2613 2623
2614 2624 class _generatorset(object):
2615 2625 """Wrap a generator for lazy iteration
2616 2626
2617 2627 Wrapper structure for generators that provides lazy membership and can
2618 2628 be iterated more than once.
2619 2629 When asked for membership it generates values until either it finds the
2620 2630 requested one or has gone through all the elements in the generator
2621 2631
2622 2632 This class does not duck-type baseset and it's only supposed to be used
2623 2633 internally
2624 2634 """
2625 2635 def __init__(self, gen):
2626 2636 """
2627 2637 gen: a generator producing the values for the generatorset.
2628 2638 """
2629 2639 self._gen = gen
2630 2640 self._cache = {}
2631 2641 self._genlist = baseset([])
2632 2642 self._finished = False
2633 2643
2634 2644 def __contains__(self, x):
2635 2645 if x in self._cache:
2636 2646 return self._cache[x]
2637 2647
2638 2648 # Use new values only, as existing values would be cached.
2639 2649 for l in self._consumegen():
2640 2650 if l == x:
2641 2651 return True
2642 2652
2643 2653 self._cache[x] = False
2644 2654 return False
2645 2655
2646 2656 def __iter__(self):
2647 2657 if self._finished:
2648 2658 for x in self._genlist:
2649 2659 yield x
2650 2660 return
2651 2661
2652 2662 i = 0
2653 2663 genlist = self._genlist
2654 2664 consume = self._consumegen()
2655 2665 while True:
2656 2666 if i < len(genlist):
2657 2667 yield genlist[i]
2658 2668 else:
2659 2669 yield consume.next()
2660 2670 i += 1
2661 2671
2662 2672 def _consumegen(self):
2663 2673 for item in self._gen:
2664 2674 self._cache[item] = True
2665 2675 self._genlist.append(item)
2666 2676 yield item
2667 2677 self._finished = True
2668 2678
2669 2679 def set(self):
2670 2680 return self
2671 2681
2672 2682 def sort(self, reverse=False):
2673 2683 if not self._finished:
2674 2684 for i in self:
2675 2685 continue
2676 2686 self._genlist.sort(reverse=reverse)
2677 2687
2678 2688 class _ascgeneratorset(_generatorset):
2679 2689 """Wrap a generator of ascending elements for lazy iteration
2680 2690
2681 2691 Same structure as _generatorset but stops iterating after it goes past
2682 2692 the value when asked for membership and the element is not contained
2683 2693
2684 2694 This class does not duck-type baseset and it's only supposed to be used
2685 2695 internally
2686 2696 """
2687 2697 def __contains__(self, x):
2688 2698 if x in self._cache:
2689 2699 return self._cache[x]
2690 2700
2691 2701 # Use new values only, as existing values would be cached.
2692 2702 for l in self._consumegen():
2693 2703 if l == x:
2694 2704 return True
2695 2705 if l > x:
2696 2706 break
2697 2707
2698 2708 self._cache[x] = False
2699 2709 return False
2700 2710
2701 2711 class _descgeneratorset(_generatorset):
2702 2712 """Wrap a generator of descending elements for lazy iteration
2703 2713
2704 2714 Same structure as _generatorset but stops iterating after it goes past
2705 2715 the value when asked for membership and the element is not contained
2706 2716
2707 2717 This class does not duck-type baseset and it's only supposed to be used
2708 2718 internally
2709 2719 """
2710 2720 def __contains__(self, x):
2711 2721 if x in self._cache:
2712 2722 return self._cache[x]
2713 2723
2714 2724 # Use new values only, as existing values would be cached.
2715 2725 for l in self._consumegen():
2716 2726 if l == x:
2717 2727 return True
2718 2728 if l < x:
2719 2729 break
2720 2730
2721 2731 self._cache[x] = False
2722 2732 return False
2723 2733
2724 2734 class spanset(_orderedsetmixin):
2725 2735 """Duck type for baseset class which represents a range of revisions and
2726 2736 can work lazily and without having all the range in memory
2727 2737
2728 2738 Note that spanset(x, y) behave almost like xrange(x, y) except for two
2729 2739 notable points:
2730 2740 - when x < y it will be automatically descending,
2731 2741 - revision filtered with this repoview will be skipped.
2732 2742
2733 2743 """
2734 2744 def __init__(self, repo, start=0, end=None):
2735 2745 """
2736 2746 start: first revision included the set
2737 2747 (default to 0)
2738 2748 end: first revision excluded (last+1)
2739 2749 (default to len(repo)
2740 2750
2741 2751 Spanset will be descending if `end` < `start`.
2742 2752 """
2743 2753 self._start = start
2744 2754 if end is not None:
2745 2755 self._end = end
2746 2756 else:
2747 2757 self._end = len(repo)
2748 2758 self._hiddenrevs = repo.changelog.filteredrevs
2749 2759
2750 2760 def ascending(self):
2751 2761 if self._start > self._end:
2752 2762 self.reverse()
2753 2763
2754 2764 def descending(self):
2755 2765 if self._start < self._end:
2756 2766 self.reverse()
2757 2767
2758 2768 def _contained(self, rev):
2759 2769 return (rev <= self._start and rev > self._end) or (rev >= self._start
2760 2770 and rev < self._end)
2761 2771
2762 2772 def __iter__(self):
2763 2773 if self._start <= self._end:
2764 2774 iterrange = xrange(self._start, self._end)
2765 2775 else:
2766 2776 iterrange = xrange(self._start, self._end, -1)
2767 2777
2768 2778 if self._hiddenrevs:
2769 2779 s = self._hiddenrevs
2770 2780 for r in iterrange:
2771 2781 if r not in s:
2772 2782 yield r
2773 2783 else:
2774 2784 for r in iterrange:
2775 2785 yield r
2776 2786
2777 2787 def __contains__(self, x):
2778 2788 return self._contained(x) and not (self._hiddenrevs and rev in
2779 2789 self._hiddenrevs)
2780 2790
2781 2791 def __nonzero__(self):
2782 2792 for r in self:
2783 2793 return True
2784 2794 return False
2785 2795
2786 2796 def __and__(self, x):
2787 2797 if isinstance(x, baseset):
2788 2798 x = x.set()
2789 2799 if self._start <= self._end:
2790 2800 return orderedlazyset(self, lambda r: r in x)
2791 2801 else:
2792 2802 return orderedlazyset(self, lambda r: r in x, ascending=False)
2793 2803
2794 2804 def __sub__(self, x):
2795 2805 if isinstance(x, baseset):
2796 2806 x = x.set()
2797 2807 if self._start <= self._end:
2798 2808 return orderedlazyset(self, lambda r: r not in x)
2799 2809 else:
2800 2810 return orderedlazyset(self, lambda r: r not in x, ascending=False)
2801 2811
2802 2812 def __add__(self, x):
2803 2813 kwargs = {}
2804 2814 if self.isascending() and x.isascending():
2805 2815 kwargs['ascending'] = True
2806 2816 if self.isdescending() and x.isdescending():
2807 2817 kwargs['ascending'] = False
2808 2818 return _addset(self, x, **kwargs)
2809 2819
2810 2820 def __len__(self):
2811 2821 if not self._hiddenrevs:
2812 2822 return abs(self._end - self._start)
2813 2823 else:
2814 2824 count = 0
2815 2825 for rev in self._hiddenrevs:
2816 2826 if self._contained(rev):
2817 2827 count += 1
2818 2828 return abs(self._end - self._start) - count
2819 2829
2820 2830 def __getitem__(self, x):
2821 2831 # Basic implementation to be changed in future patches.
2822 2832 l = baseset([r for r in self])
2823 2833 return l[x]
2824 2834
2825 2835 def sort(self, reverse=False):
2826 2836 if bool(reverse) != (self._start > self._end):
2827 2837 self.reverse()
2828 2838
2829 2839 def reverse(self):
2830 2840 # Just switch the _start and _end parameters
2831 2841 if self._start <= self._end:
2832 2842 self._start, self._end = self._end - 1, self._start - 1
2833 2843 else:
2834 2844 self._start, self._end = self._end + 1, self._start + 1
2835 2845
2836 2846 def set(self):
2837 2847 return self
2838 2848
2839 2849 def isascending(self):
2840 2850 return self._start < self._end
2841 2851
2842 2852 def isdescending(self):
2843 2853 return self._start > self._end
2844 2854
2845 2855 def filter(self, l):
2846 2856 if self._start <= self._end:
2847 2857 return orderedlazyset(self, l)
2848 2858 else:
2849 2859 return orderedlazyset(self, l, ascending=False)
2850 2860
2851 2861 # tell hggettext to extract docstrings from these functions:
2852 2862 i18nfunctions = symbols.values()
@@ -1,1134 +1,1124 b''
1 1 $ HGENCODING=utf-8
2 2 $ export HGENCODING
3 3
4 4 $ try() {
5 5 > hg debugrevspec --debug "$@"
6 6 > }
7 7
8 8 $ log() {
9 9 > hg log --template '{rev}\n' -r "$1"
10 10 > }
11 11
12 12 $ hg init repo
13 13 $ cd repo
14 14
15 15 $ echo a > a
16 16 $ hg branch a
17 17 marked working directory as branch a
18 18 (branches are permanent and global, did you want a bookmark?)
19 19 $ hg ci -Aqm0
20 20
21 21 $ echo b > b
22 22 $ hg branch b
23 23 marked working directory as branch b
24 24 (branches are permanent and global, did you want a bookmark?)
25 25 $ hg ci -Aqm1
26 26
27 27 $ rm a
28 28 $ hg branch a-b-c-
29 29 marked working directory as branch a-b-c-
30 30 (branches are permanent and global, did you want a bookmark?)
31 31 $ hg ci -Aqm2 -u Bob
32 32
33 33 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
34 34 2
35 35 $ hg log -r "extra('branch')" --template '{rev}\n'
36 36 0
37 37 1
38 38 2
39 39 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
40 40 0 a
41 41 2 a-b-c-
42 42
43 43 $ hg co 1
44 44 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 45 $ hg branch +a+b+c+
46 46 marked working directory as branch +a+b+c+
47 47 (branches are permanent and global, did you want a bookmark?)
48 48 $ hg ci -Aqm3
49 49
50 50 $ hg co 2 # interleave
51 51 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
52 52 $ echo bb > b
53 53 $ hg branch -- -a-b-c-
54 54 marked working directory as branch -a-b-c-
55 55 (branches are permanent and global, did you want a bookmark?)
56 56 $ hg ci -Aqm4 -d "May 12 2005"
57 57
58 58 $ hg co 3
59 59 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 60 $ hg branch !a/b/c/
61 61 marked working directory as branch !a/b/c/
62 62 (branches are permanent and global, did you want a bookmark?)
63 63 $ hg ci -Aqm"5 bug"
64 64
65 65 $ hg merge 4
66 66 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
67 67 (branch merge, don't forget to commit)
68 68 $ hg branch _a_b_c_
69 69 marked working directory as branch _a_b_c_
70 70 (branches are permanent and global, did you want a bookmark?)
71 71 $ hg ci -Aqm"6 issue619"
72 72
73 73 $ hg branch .a.b.c.
74 74 marked working directory as branch .a.b.c.
75 75 (branches are permanent and global, did you want a bookmark?)
76 76 $ hg ci -Aqm7
77 77
78 78 $ hg branch all
79 79 marked working directory as branch all
80 80 (branches are permanent and global, did you want a bookmark?)
81 81
82 82 $ hg co 4
83 83 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 84 $ hg branch Γ©
85 85 marked working directory as branch \xc3\xa9 (esc)
86 86 (branches are permanent and global, did you want a bookmark?)
87 87 $ hg ci -Aqm9
88 88
89 89 $ hg tag -r6 1.0
90 90
91 91 $ hg clone --quiet -U -r 7 . ../remote1
92 92 $ hg clone --quiet -U -r 8 . ../remote2
93 93 $ echo "[paths]" >> .hg/hgrc
94 94 $ echo "default = ../remote1" >> .hg/hgrc
95 95
96 96 names that should work without quoting
97 97
98 98 $ try a
99 99 ('symbol', 'a')
100 100 0
101 101 $ try b-a
102 102 (minus
103 103 ('symbol', 'b')
104 104 ('symbol', 'a'))
105 105 1
106 106 $ try _a_b_c_
107 107 ('symbol', '_a_b_c_')
108 108 6
109 109 $ try _a_b_c_-a
110 110 (minus
111 111 ('symbol', '_a_b_c_')
112 112 ('symbol', 'a'))
113 113 6
114 114 $ try .a.b.c.
115 115 ('symbol', '.a.b.c.')
116 116 7
117 117 $ try .a.b.c.-a
118 118 (minus
119 119 ('symbol', '.a.b.c.')
120 120 ('symbol', 'a'))
121 121 7
122 122 $ try -- '-a-b-c-' # complains
123 123 hg: parse error at 7: not a prefix: end
124 124 [255]
125 125 $ log -a-b-c- # succeeds with fallback
126 126 4
127 127
128 128 $ try -- -a-b-c--a # complains
129 129 (minus
130 130 (minus
131 131 (minus
132 132 (negate
133 133 ('symbol', 'a'))
134 134 ('symbol', 'b'))
135 135 ('symbol', 'c'))
136 136 (negate
137 137 ('symbol', 'a')))
138 138 abort: unknown revision '-a'!
139 139 [255]
140 140 $ try Γ©
141 141 ('symbol', '\xc3\xa9')
142 142 9
143 143
144 144 no quoting needed
145 145
146 146 $ log ::a-b-c-
147 147 0
148 148 1
149 149 2
150 150
151 151 quoting needed
152 152
153 153 $ try '"-a-b-c-"-a'
154 154 (minus
155 155 ('string', '-a-b-c-')
156 156 ('symbol', 'a'))
157 157 4
158 158
159 159 $ log '1 or 2'
160 160 1
161 161 2
162 162 $ log '1|2'
163 163 1
164 164 2
165 165 $ log '1 and 2'
166 166 $ log '1&2'
167 167 $ try '1&2|3' # precedence - and is higher
168 168 (or
169 169 (and
170 170 ('symbol', '1')
171 171 ('symbol', '2'))
172 172 ('symbol', '3'))
173 173 3
174 174 $ try '1|2&3'
175 175 (or
176 176 ('symbol', '1')
177 177 (and
178 178 ('symbol', '2')
179 179 ('symbol', '3')))
180 180 1
181 181 $ try '1&2&3' # associativity
182 182 (and
183 183 (and
184 184 ('symbol', '1')
185 185 ('symbol', '2'))
186 186 ('symbol', '3'))
187 187 $ try '1|(2|3)'
188 188 (or
189 189 ('symbol', '1')
190 190 (group
191 191 (or
192 192 ('symbol', '2')
193 193 ('symbol', '3'))))
194 194 1
195 195 2
196 196 3
197 197 $ log '1.0' # tag
198 198 6
199 199 $ log 'a' # branch
200 200 0
201 201 $ log '2785f51ee'
202 202 0
203 203 $ log 'date(2005)'
204 204 4
205 205 $ log 'date(this is a test)'
206 206 hg: parse error at 10: unexpected token: symbol
207 207 [255]
208 208 $ log 'date()'
209 209 hg: parse error: date requires a string
210 210 [255]
211 211 $ log 'date'
212 212 hg: parse error: can't use date here
213 213 [255]
214 214 $ log 'date('
215 215 hg: parse error at 5: not a prefix: end
216 216 [255]
217 217 $ log 'date(tip)'
218 218 abort: invalid date: 'tip'
219 219 [255]
220 220 $ log '"date"'
221 221 abort: unknown revision 'date'!
222 222 [255]
223 223 $ log 'date(2005) and 1::'
224 224 4
225 225
226 226 ancestor can accept 0 or more arguments
227 227
228 228 $ log 'ancestor()'
229 229 $ log 'ancestor(1)'
230 230 1
231 231 $ log 'ancestor(4,5)'
232 232 1
233 233 $ log 'ancestor(4,5) and 4'
234 234 $ log 'ancestor(0,0,1,3)'
235 235 0
236 236 $ log 'ancestor(3,1,5,3,5,1)'
237 237 1
238 238 $ log 'ancestor(0,1,3,5)'
239 239 0
240 240 $ log 'ancestor(1,2,3,4,5)'
241 241 1
242 242 $ log 'ancestors(5)'
243 243 0
244 244 1
245 245 3
246 246 5
247 247 $ log 'ancestor(ancestors(5))'
248 248 0
249 249 $ log 'author(bob)'
250 250 2
251 251 $ log 'author("re:bob|test")'
252 252 0
253 253 1
254 254 2
255 255 3
256 256 4
257 257 5
258 258 6
259 259 7
260 260 8
261 261 9
262 262 $ log 'branch(Γ©)'
263 263 8
264 264 9
265 265 $ log 'branch(a)'
266 266 0
267 267 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
268 268 0 a
269 269 2 a-b-c-
270 270 3 +a+b+c+
271 271 4 -a-b-c-
272 272 5 !a/b/c/
273 273 6 _a_b_c_
274 274 7 .a.b.c.
275 275 $ log 'children(ancestor(4,5))'
276 276 2
277 277 3
278 278 $ log 'closed()'
279 279 $ log 'contains(a)'
280 280 0
281 281 1
282 282 3
283 283 5
284 284 $ log 'contains("../repo/a")'
285 285 0
286 286 1
287 287 3
288 288 5
289 289 $ log 'desc(B)'
290 290 5
291 291 $ log 'descendants(2 or 3)'
292 292 2
293 293 3
294 294 4
295 295 5
296 296 6
297 297 7
298 298 8
299 299 9
300 300 $ log 'file("b*")'
301 301 1
302 302 4
303 303 $ log 'filelog("b")'
304 304 1
305 305 4
306 306 $ log 'filelog("../repo/b")'
307 307 1
308 308 4
309 309 $ log 'follow()'
310 310 0
311 311 1
312 312 2
313 313 4
314 314 8
315 315 9
316 316 $ log 'grep("issue\d+")'
317 317 6
318 318 $ try 'grep("(")' # invalid regular expression
319 319 (func
320 320 ('symbol', 'grep')
321 321 ('string', '('))
322 322 hg: parse error: invalid match pattern: unbalanced parenthesis
323 323 [255]
324 324 $ try 'grep("\bissue\d+")'
325 325 (func
326 326 ('symbol', 'grep')
327 327 ('string', '\x08issue\\d+'))
328 328 $ try 'grep(r"\bissue\d+")'
329 329 (func
330 330 ('symbol', 'grep')
331 331 ('string', '\\bissue\\d+'))
332 332 6
333 333 $ try 'grep(r"\")'
334 334 hg: parse error at 7: unterminated string
335 335 [255]
336 336 $ log 'head()'
337 337 0
338 338 1
339 339 2
340 340 3
341 341 4
342 342 5
343 343 6
344 344 7
345 345 9
346 346 $ log 'heads(6::)'
347 347 7
348 348 $ log 'keyword(issue)'
349 349 6
350 350 $ log 'keyword("test a")'
351 351 $ log 'limit(head(), 1)'
352 352 0
353 353 $ log 'matching(6)'
354 354 6
355 355 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
356 356 6
357 357 7
358 358
359 359 Testing min and max
360 360
361 361 max: simple
362 362
363 363 $ log 'max(contains(a))'
364 364 5
365 365
366 366 max: simple on unordered set)
367 367
368 368 $ log 'max((4+0+2+5+7) and contains(a))'
369 369 5
370 370
371 371 max: no result
372 372
373 373 $ log 'max(contains(stringthatdoesnotappearanywhere))'
374 374
375 375 max: no result on unordered set
376 376
377 377 $ log 'max((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
378 378
379 379 min: simple
380 380
381 381 $ log 'min(contains(a))'
382 382 0
383 383
384 384 min: simple on unordered set
385 385
386 386 $ log 'min((4+0+2+5+7) and contains(a))'
387 387 0
388 388
389 389 min: empty
390 390
391 391 $ log 'min(contains(stringthatdoesnotappearanywhere))'
392 392
393 393 min: empty on unordered set
394 394
395 395 $ log 'min((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
396 396
397 397
398 398 $ log 'merge()'
399 399 6
400 400 $ log 'branchpoint()'
401 401 1
402 402 4
403 403 $ log 'modifies(b)'
404 404 4
405 405 $ log 'modifies("path:b")'
406 406 4
407 407 $ log 'modifies("*")'
408 408 4
409 409 6
410 410 $ log 'modifies("set:modified()")'
411 411 4
412 412 $ log 'id(5)'
413 413 2
414 414 $ log 'only(9)'
415 415 8
416 416 9
417 417 $ log 'only(8)'
418 418 8
419 419 $ log 'only(9, 5)'
420 420 2
421 421 4
422 422 8
423 423 9
424 424 $ log 'only(7 + 9, 5 + 2)'
425 425 4
426 426 6
427 427 7
428 428 8
429 429 9
430 430 $ log 'outgoing()'
431 431 8
432 432 9
433 433 $ log 'outgoing("../remote1")'
434 434 8
435 435 9
436 436 $ log 'outgoing("../remote2")'
437 437 3
438 438 5
439 439 6
440 440 7
441 441 9
442 442 $ log 'p1(merge())'
443 443 5
444 444 $ log 'p2(merge())'
445 445 4
446 446 $ log 'parents(merge())'
447 447 4
448 448 5
449 449 $ log 'p1(branchpoint())'
450 450 0
451 451 2
452 452 $ log 'p2(branchpoint())'
453 453 $ log 'parents(branchpoint())'
454 454 0
455 455 2
456 456 $ log 'removes(a)'
457 457 2
458 458 6
459 459 $ log 'roots(all())'
460 460 0
461 461 $ log 'reverse(2 or 3 or 4 or 5)'
462 462 5
463 463 4
464 464 3
465 465 2
466 466 $ log 'reverse(all())'
467 467 9
468 468 8
469 469 7
470 470 6
471 471 5
472 472 4
473 473 3
474 474 2
475 475 1
476 476 0
477 $ log '1:: and reverse(all())'
478 9
479 8
480 7
481 6
482 5
483 4
484 3
485 2
486 1
487 477 $ log 'rev(5)'
488 478 5
489 479 $ log 'sort(limit(reverse(all()), 3))'
490 480 7
491 481 8
492 482 9
493 483 $ log 'sort(2 or 3 or 4 or 5, date)'
494 484 2
495 485 3
496 486 5
497 487 4
498 488 $ log 'tagged()'
499 489 6
500 490 $ log 'tag()'
501 491 6
502 492 $ log 'tag(1.0)'
503 493 6
504 494 $ log 'tag(tip)'
505 495 9
506 496
507 497 test sort revset
508 498 --------------------------------------------
509 499
510 500 test when adding two unordered revsets
511 501
512 502 $ log 'sort(keyword(issue) or modifies(b))'
513 503 4
514 504 6
515 505
516 506 test when sorting a reversed collection in the same way it is
517 507
518 508 $ log 'sort(reverse(all()), -rev)'
519 509 9
520 510 8
521 511 7
522 512 6
523 513 5
524 514 4
525 515 3
526 516 2
527 517 1
528 518 0
529 519
530 520 test when sorting a reversed collection
531 521
532 522 $ log 'sort(reverse(all()), rev)'
533 523 0
534 524 1
535 525 2
536 526 3
537 527 4
538 528 5
539 529 6
540 530 7
541 531 8
542 532 9
543 533
544 534
545 535 test sorting two sorted collections in different orders
546 536
547 537 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
548 538 2
549 539 6
550 540 8
551 541 9
552 542
553 543 test sorting two sorted collections in different orders backwards
554 544
555 545 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
556 546 9
557 547 8
558 548 6
559 549 2
560 550
561 551 test substracting something from an addset
562 552
563 553 $ log '(outgoing() or removes(a)) - removes(a)'
564 554 8
565 555 9
566 556
567 557 test intersecting something with an addset
568 558
569 559 $ log 'parents(outgoing() or removes(a))'
570 560 1
571 561 4
572 562 5
573 563 8
574 564
575 565 check that conversion to _missingancestors works
576 566 $ try --optimize '::3 - ::1'
577 567 (minus
578 568 (dagrangepre
579 569 ('symbol', '3'))
580 570 (dagrangepre
581 571 ('symbol', '1')))
582 572 * optimized:
583 573 (func
584 574 ('symbol', '_missingancestors')
585 575 (list
586 576 ('symbol', '3')
587 577 ('symbol', '1')))
588 578 3
589 579 $ try --optimize 'ancestors(1) - ancestors(3)'
590 580 (minus
591 581 (func
592 582 ('symbol', 'ancestors')
593 583 ('symbol', '1'))
594 584 (func
595 585 ('symbol', 'ancestors')
596 586 ('symbol', '3')))
597 587 * optimized:
598 588 (func
599 589 ('symbol', '_missingancestors')
600 590 (list
601 591 ('symbol', '1')
602 592 ('symbol', '3')))
603 593 $ try --optimize 'not ::2 and ::6'
604 594 (and
605 595 (not
606 596 (dagrangepre
607 597 ('symbol', '2')))
608 598 (dagrangepre
609 599 ('symbol', '6')))
610 600 * optimized:
611 601 (func
612 602 ('symbol', '_missingancestors')
613 603 (list
614 604 ('symbol', '6')
615 605 ('symbol', '2')))
616 606 3
617 607 4
618 608 5
619 609 6
620 610 $ try --optimize 'ancestors(6) and not ancestors(4)'
621 611 (and
622 612 (func
623 613 ('symbol', 'ancestors')
624 614 ('symbol', '6'))
625 615 (not
626 616 (func
627 617 ('symbol', 'ancestors')
628 618 ('symbol', '4'))))
629 619 * optimized:
630 620 (func
631 621 ('symbol', '_missingancestors')
632 622 (list
633 623 ('symbol', '6')
634 624 ('symbol', '4')))
635 625 3
636 626 5
637 627 6
638 628
639 629 we can use patterns when searching for tags
640 630
641 631 $ log 'tag("1..*")'
642 632 abort: tag '1..*' does not exist
643 633 [255]
644 634 $ log 'tag("re:1..*")'
645 635 6
646 636 $ log 'tag("re:[0-9].[0-9]")'
647 637 6
648 638 $ log 'tag("literal:1.0")'
649 639 6
650 640 $ log 'tag("re:0..*")'
651 641
652 642 $ log 'tag(unknown)'
653 643 abort: tag 'unknown' does not exist
654 644 [255]
655 645 $ log 'branch(unknown)'
656 646 abort: unknown revision 'unknown'!
657 647 [255]
658 648 $ log 'user(bob)'
659 649 2
660 650
661 651 $ log '4::8'
662 652 4
663 653 8
664 654 $ log '4:8'
665 655 4
666 656 5
667 657 6
668 658 7
669 659 8
670 660
671 661 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
672 662 4
673 663 2
674 664 5
675 665
676 666 $ log 'not 0 and 0:2'
677 667 1
678 668 2
679 669 $ log 'not 1 and 0:2'
680 670 0
681 671 2
682 672 $ log 'not 2 and 0:2'
683 673 0
684 674 1
685 675 $ log '(1 and 2)::'
686 676 $ log '(1 and 2):'
687 677 $ log '(1 and 2):3'
688 678 $ log 'sort(head(), -rev)'
689 679 9
690 680 7
691 681 6
692 682 5
693 683 4
694 684 3
695 685 2
696 686 1
697 687 0
698 688 $ log '4::8 - 8'
699 689 4
700 690 $ log 'matching(1 or 2 or 3) and (2 or 3 or 1)'
701 691 2
702 692 3
703 693 1
704 694
705 695 issue2437
706 696
707 697 $ log '3 and p1(5)'
708 698 3
709 699 $ log '4 and p2(6)'
710 700 4
711 701 $ log '1 and parents(:2)'
712 702 1
713 703 $ log '2 and children(1:)'
714 704 2
715 705 $ log 'roots(all()) or roots(all())'
716 706 0
717 707 $ hg debugrevspec 'roots(all()) or roots(all())'
718 708 0
719 709 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
720 710 9
721 711 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
722 712 4
723 713
724 714 issue2654: report a parse error if the revset was not completely parsed
725 715
726 716 $ log '1 OR 2'
727 717 hg: parse error at 2: invalid token
728 718 [255]
729 719
730 720 or operator should preserve ordering:
731 721 $ log 'reverse(2::4) or tip'
732 722 4
733 723 2
734 724 9
735 725
736 726 parentrevspec
737 727
738 728 $ log 'merge()^0'
739 729 6
740 730 $ log 'merge()^'
741 731 5
742 732 $ log 'merge()^1'
743 733 5
744 734 $ log 'merge()^2'
745 735 4
746 736 $ log 'merge()^^'
747 737 3
748 738 $ log 'merge()^1^'
749 739 3
750 740 $ log 'merge()^^^'
751 741 1
752 742
753 743 $ log 'merge()~0'
754 744 6
755 745 $ log 'merge()~1'
756 746 5
757 747 $ log 'merge()~2'
758 748 3
759 749 $ log 'merge()~2^1'
760 750 1
761 751 $ log 'merge()~3'
762 752 1
763 753
764 754 $ log '(-3:tip)^'
765 755 4
766 756 6
767 757 8
768 758
769 759 $ log 'tip^foo'
770 760 hg: parse error: ^ expects a number 0, 1, or 2
771 761 [255]
772 762
773 763 multiple revspecs
774 764
775 765 $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
776 766 8
777 767 9
778 768 4
779 769 5
780 770 6
781 771 7
782 772
783 773 test usage in revpair (with "+")
784 774
785 775 (real pair)
786 776
787 777 $ hg diff -r 'tip^^' -r 'tip'
788 778 diff -r 2326846efdab -r 24286f4ae135 .hgtags
789 779 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
790 780 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
791 781 @@ -0,0 +1,1 @@
792 782 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
793 783 $ hg diff -r 'tip^^::tip'
794 784 diff -r 2326846efdab -r 24286f4ae135 .hgtags
795 785 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
796 786 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
797 787 @@ -0,0 +1,1 @@
798 788 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
799 789
800 790 (single rev)
801 791
802 792 $ hg diff -r 'tip^' -r 'tip^'
803 793 $ hg diff -r 'tip^::tip^ or tip^'
804 794
805 795 (single rev that does not looks like a range)
806 796
807 797 $ hg diff -r 'tip^ or tip^'
808 798 diff -r d5d0dcbdc4d9 .hgtags
809 799 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
810 800 +++ b/.hgtags * (glob)
811 801 @@ -0,0 +1,1 @@
812 802 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
813 803
814 804 (no rev)
815 805
816 806 $ hg diff -r 'author("babar") or author("celeste")'
817 807 abort: empty revision range
818 808 [255]
819 809
820 810 aliases:
821 811
822 812 $ echo '[revsetalias]' >> .hg/hgrc
823 813 $ echo 'm = merge()' >> .hg/hgrc
824 814 $ echo 'sincem = descendants(m)' >> .hg/hgrc
825 815 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
826 816 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
827 817 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
828 818
829 819 $ try m
830 820 ('symbol', 'm')
831 821 (func
832 822 ('symbol', 'merge')
833 823 None)
834 824 6
835 825
836 826 test alias recursion
837 827
838 828 $ try sincem
839 829 ('symbol', 'sincem')
840 830 (func
841 831 ('symbol', 'descendants')
842 832 (func
843 833 ('symbol', 'merge')
844 834 None))
845 835 6
846 836 7
847 837
848 838 test infinite recursion
849 839
850 840 $ echo 'recurse1 = recurse2' >> .hg/hgrc
851 841 $ echo 'recurse2 = recurse1' >> .hg/hgrc
852 842 $ try recurse1
853 843 ('symbol', 'recurse1')
854 844 hg: parse error: infinite expansion of revset alias "recurse1" detected
855 845 [255]
856 846
857 847 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
858 848 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
859 849 $ try "level2(level1(1, 2), 3)"
860 850 (func
861 851 ('symbol', 'level2')
862 852 (list
863 853 (func
864 854 ('symbol', 'level1')
865 855 (list
866 856 ('symbol', '1')
867 857 ('symbol', '2')))
868 858 ('symbol', '3')))
869 859 (or
870 860 ('symbol', '3')
871 861 (or
872 862 ('symbol', '1')
873 863 ('symbol', '2')))
874 864 3
875 865 1
876 866 2
877 867
878 868 test nesting and variable passing
879 869
880 870 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
881 871 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
882 872 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
883 873 $ try 'nested(2:5)'
884 874 (func
885 875 ('symbol', 'nested')
886 876 (range
887 877 ('symbol', '2')
888 878 ('symbol', '5')))
889 879 (func
890 880 ('symbol', 'max')
891 881 (range
892 882 ('symbol', '2')
893 883 ('symbol', '5')))
894 884 5
895 885
896 886 test variable isolation, variable placeholders are rewritten as string
897 887 then parsed and matched again as string. Check they do not leak too
898 888 far away.
899 889
900 890 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
901 891 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
902 892 $ try 'callinjection(2:5)'
903 893 (func
904 894 ('symbol', 'callinjection')
905 895 (range
906 896 ('symbol', '2')
907 897 ('symbol', '5')))
908 898 (func
909 899 ('symbol', 'descendants')
910 900 (func
911 901 ('symbol', 'max')
912 902 ('string', '$1')))
913 903 abort: unknown revision '$1'!
914 904 [255]
915 905
916 906 $ echo 'injectparamasstring2 = max(_aliasarg("$1"))' >> .hg/hgrc
917 907 $ echo 'callinjection2($1) = descendants(injectparamasstring2)' >> .hg/hgrc
918 908 $ try 'callinjection2(2:5)'
919 909 (func
920 910 ('symbol', 'callinjection2')
921 911 (range
922 912 ('symbol', '2')
923 913 ('symbol', '5')))
924 914 hg: parse error: not a function: _aliasarg
925 915 [255]
926 916 >>> data = file('.hg/hgrc', 'rb').read()
927 917 >>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
928 918
929 919 $ try 'd(2:5)'
930 920 (func
931 921 ('symbol', 'd')
932 922 (range
933 923 ('symbol', '2')
934 924 ('symbol', '5')))
935 925 (func
936 926 ('symbol', 'reverse')
937 927 (func
938 928 ('symbol', 'sort')
939 929 (list
940 930 (range
941 931 ('symbol', '2')
942 932 ('symbol', '5'))
943 933 ('symbol', 'date'))))
944 934 4
945 935 5
946 936 3
947 937 2
948 938 $ try 'rs(2 or 3, date)'
949 939 (func
950 940 ('symbol', 'rs')
951 941 (list
952 942 (or
953 943 ('symbol', '2')
954 944 ('symbol', '3'))
955 945 ('symbol', 'date')))
956 946 (func
957 947 ('symbol', 'reverse')
958 948 (func
959 949 ('symbol', 'sort')
960 950 (list
961 951 (or
962 952 ('symbol', '2')
963 953 ('symbol', '3'))
964 954 ('symbol', 'date'))))
965 955 3
966 956 2
967 957 $ try 'rs()'
968 958 (func
969 959 ('symbol', 'rs')
970 960 None)
971 961 hg: parse error: invalid number of arguments: 0
972 962 [255]
973 963 $ try 'rs(2)'
974 964 (func
975 965 ('symbol', 'rs')
976 966 ('symbol', '2'))
977 967 hg: parse error: invalid number of arguments: 1
978 968 [255]
979 969 $ try 'rs(2, data, 7)'
980 970 (func
981 971 ('symbol', 'rs')
982 972 (list
983 973 (list
984 974 ('symbol', '2')
985 975 ('symbol', 'data'))
986 976 ('symbol', '7')))
987 977 hg: parse error: invalid number of arguments: 3
988 978 [255]
989 979 $ try 'rs4(2 or 3, x, x, date)'
990 980 (func
991 981 ('symbol', 'rs4')
992 982 (list
993 983 (list
994 984 (list
995 985 (or
996 986 ('symbol', '2')
997 987 ('symbol', '3'))
998 988 ('symbol', 'x'))
999 989 ('symbol', 'x'))
1000 990 ('symbol', 'date')))
1001 991 (func
1002 992 ('symbol', 'reverse')
1003 993 (func
1004 994 ('symbol', 'sort')
1005 995 (list
1006 996 (or
1007 997 ('symbol', '2')
1008 998 ('symbol', '3'))
1009 999 ('symbol', 'date'))))
1010 1000 3
1011 1001 2
1012 1002
1013 1003 issue2549 - correct optimizations
1014 1004
1015 1005 $ log 'limit(1 or 2 or 3, 2) and not 2'
1016 1006 1
1017 1007 $ log 'max(1 or 2) and not 2'
1018 1008 $ log 'min(1 or 2) and not 1'
1019 1009 $ log 'last(1 or 2, 1) and not 2'
1020 1010
1021 1011 test revsets started with 40-chars hash (issue3669)
1022 1012
1023 1013 $ ISSUE3669_TIP=`hg tip --template '{node}'`
1024 1014 $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
1025 1015 9
1026 1016 $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
1027 1017 8
1028 1018
1029 1019 test or-ed indirect predicates (issue3775)
1030 1020
1031 1021 $ log '6 or 6^1' | sort
1032 1022 5
1033 1023 6
1034 1024 $ log '6^1 or 6' | sort
1035 1025 5
1036 1026 6
1037 1027 $ log '4 or 4~1' | sort
1038 1028 2
1039 1029 4
1040 1030 $ log '4~1 or 4' | sort
1041 1031 2
1042 1032 4
1043 1033 $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
1044 1034 0
1045 1035 1
1046 1036 2
1047 1037 3
1048 1038 4
1049 1039 5
1050 1040 6
1051 1041 $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
1052 1042 0
1053 1043 1
1054 1044 2
1055 1045 3
1056 1046 4
1057 1047 5
1058 1048 6
1059 1049
1060 1050 tests for 'remote()' predicate:
1061 1051 #. (csets in remote) (id) (remote)
1062 1052 1. less than local current branch "default"
1063 1053 2. same with local specified "default"
1064 1054 3. more than local specified specified
1065 1055
1066 1056 $ hg clone --quiet -U . ../remote3
1067 1057 $ cd ../remote3
1068 1058 $ hg update -q 7
1069 1059 $ echo r > r
1070 1060 $ hg ci -Aqm 10
1071 1061 $ log 'remote()'
1072 1062 7
1073 1063 $ log 'remote("a-b-c-")'
1074 1064 2
1075 1065 $ cd ../repo
1076 1066 $ log 'remote(".a.b.c.", "../remote3")'
1077 1067
1078 1068 $ cd ..
1079 1069
1080 1070 test author/desc/keyword in problematic encoding
1081 1071 # unicode: cp932:
1082 1072 # u30A2 0x83 0x41(= 'A')
1083 1073 # u30C2 0x83 0x61(= 'a')
1084 1074
1085 1075 $ hg init problematicencoding
1086 1076 $ cd problematicencoding
1087 1077
1088 1078 $ python > setup.sh <<EOF
1089 1079 > print u'''
1090 1080 > echo a > text
1091 1081 > hg add text
1092 1082 > hg --encoding utf-8 commit -u '\u30A2' -m none
1093 1083 > echo b > text
1094 1084 > hg --encoding utf-8 commit -u '\u30C2' -m none
1095 1085 > echo c > text
1096 1086 > hg --encoding utf-8 commit -u none -m '\u30A2'
1097 1087 > echo d > text
1098 1088 > hg --encoding utf-8 commit -u none -m '\u30C2'
1099 1089 > '''.encode('utf-8')
1100 1090 > EOF
1101 1091 $ sh < setup.sh
1102 1092
1103 1093 test in problematic encoding
1104 1094 $ python > test.sh <<EOF
1105 1095 > print u'''
1106 1096 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
1107 1097 > echo ====
1108 1098 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
1109 1099 > echo ====
1110 1100 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
1111 1101 > echo ====
1112 1102 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
1113 1103 > echo ====
1114 1104 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
1115 1105 > echo ====
1116 1106 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
1117 1107 > '''.encode('cp932')
1118 1108 > EOF
1119 1109 $ sh < test.sh
1120 1110 0
1121 1111 ====
1122 1112 1
1123 1113 ====
1124 1114 2
1125 1115 ====
1126 1116 3
1127 1117 ====
1128 1118 0
1129 1119 2
1130 1120 ====
1131 1121 1
1132 1122 3
1133 1123
1134 1124 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now