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