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