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