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