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