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