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