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