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