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