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