comparison mercurial/cmdutil.py @ 3657:731e739b8659

move walkchangerevs to cmdutils
author Matt Mackall <mpm@selenic.com>
date Wed, 15 Nov 2006 15:51:58 -0600
parents e50891e461e4
children 4d988b7412f2
comparison
equal deleted inserted replaced
3656:e50891e461e4 3657:731e739b8659
590 raise util.Abort(inst.args[0]) 590 raise util.Abort(inst.args[0])
591 if tmpl: t.use_template(tmpl) 591 if tmpl: t.use_template(tmpl)
592 return t 592 return t
593 return changeset_printer(ui, repo, patch, br, buffered) 593 return changeset_printer(ui, repo, patch, br, buffered)
594 594
595 def walkchangerevs(ui, repo, pats, change, opts):
596 '''Iterate over files and the revs they changed in.
597
598 Callers most commonly need to iterate backwards over the history
599 it is interested in. Doing so has awful (quadratic-looking)
600 performance, so we use iterators in a "windowed" way.
601
602 We walk a window of revisions in the desired order. Within the
603 window, we first walk forwards to gather data, then in the desired
604 order (usually backwards) to display it.
605
606 This function returns an (iterator, matchfn) tuple. The iterator
607 yields 3-tuples. They will be of one of the following forms:
608
609 "window", incrementing, lastrev: stepping through a window,
610 positive if walking forwards through revs, last rev in the
611 sequence iterated over - use to reset state for the current window
612
613 "add", rev, fns: out-of-order traversal of the given file names
614 fns, which changed during revision rev - use to gather data for
615 possible display
616
617 "iter", rev, None: in-order traversal of the revs earlier iterated
618 over with "add" - use to display data'''
619
620 def increasing_windows(start, end, windowsize=8, sizelimit=512):
621 if start < end:
622 while start < end:
623 yield start, min(windowsize, end-start)
624 start += windowsize
625 if windowsize < sizelimit:
626 windowsize *= 2
627 else:
628 while start > end:
629 yield start, min(windowsize, start-end-1)
630 start -= windowsize
631 if windowsize < sizelimit:
632 windowsize *= 2
633
634 files, matchfn, anypats = matchpats(repo, pats, opts)
635 follow = opts.get('follow') or opts.get('follow_first')
636
637 if repo.changelog.count() == 0:
638 return [], matchfn
639
640 if follow:
641 defrange = '%s:0' % repo.changectx().rev()
642 else:
643 defrange = 'tip:0'
644 revs = revrange(ui, repo, opts['rev'] or [defrange])
645 wanted = {}
646 slowpath = anypats
647 fncache = {}
648
649 if not slowpath and not files:
650 # No files, no patterns. Display all revs.
651 wanted = dict.fromkeys(revs)
652 copies = []
653 if not slowpath:
654 # Only files, no patterns. Check the history of each file.
655 def filerevgen(filelog, node):
656 cl_count = repo.changelog.count()
657 if node is None:
658 last = filelog.count() - 1
659 else:
660 last = filelog.rev(node)
661 for i, window in increasing_windows(last, nullrev):
662 revs = []
663 for j in xrange(i - window, i + 1):
664 n = filelog.node(j)
665 revs.append((filelog.linkrev(n),
666 follow and filelog.renamed(n)))
667 revs.reverse()
668 for rev in revs:
669 # only yield rev for which we have the changelog, it can
670 # happen while doing "hg log" during a pull or commit
671 if rev[0] < cl_count:
672 yield rev
673 def iterfiles():
674 for filename in files:
675 yield filename, None
676 for filename_node in copies:
677 yield filename_node
678 minrev, maxrev = min(revs), max(revs)
679 for file_, node in iterfiles():
680 filelog = repo.file(file_)
681 # A zero count may be a directory or deleted file, so
682 # try to find matching entries on the slow path.
683 if filelog.count() == 0:
684 slowpath = True
685 break
686 for rev, copied in filerevgen(filelog, node):
687 if rev <= maxrev:
688 if rev < minrev:
689 break
690 fncache.setdefault(rev, [])
691 fncache[rev].append(file_)
692 wanted[rev] = 1
693 if follow and copied:
694 copies.append(copied)
695 if slowpath:
696 if follow:
697 raise util.Abort(_('can only follow copies/renames for explicit '
698 'file names'))
699
700 # The slow path checks files modified in every changeset.
701 def changerevgen():
702 for i, window in increasing_windows(repo.changelog.count()-1,
703 nullrev):
704 for j in xrange(i - window, i + 1):
705 yield j, change(j)[3]
706
707 for rev, changefiles in changerevgen():
708 matches = filter(matchfn, changefiles)
709 if matches:
710 fncache[rev] = matches
711 wanted[rev] = 1
712
713 class followfilter:
714 def __init__(self, onlyfirst=False):
715 self.startrev = nullrev
716 self.roots = []
717 self.onlyfirst = onlyfirst
718
719 def match(self, rev):
720 def realparents(rev):
721 if self.onlyfirst:
722 return repo.changelog.parentrevs(rev)[0:1]
723 else:
724 return filter(lambda x: x != nullrev,
725 repo.changelog.parentrevs(rev))
726
727 if self.startrev == nullrev:
728 self.startrev = rev
729 return True
730
731 if rev > self.startrev:
732 # forward: all descendants
733 if not self.roots:
734 self.roots.append(self.startrev)
735 for parent in realparents(rev):
736 if parent in self.roots:
737 self.roots.append(rev)
738 return True
739 else:
740 # backwards: all parents
741 if not self.roots:
742 self.roots.extend(realparents(self.startrev))
743 if rev in self.roots:
744 self.roots.remove(rev)
745 self.roots.extend(realparents(rev))
746 return True
747
748 return False
749
750 # it might be worthwhile to do this in the iterator if the rev range
751 # is descending and the prune args are all within that range
752 for rev in opts.get('prune', ()):
753 rev = repo.changelog.rev(repo.lookup(rev))
754 ff = followfilter()
755 stop = min(revs[0], revs[-1])
756 for x in xrange(rev, stop-1, -1):
757 if ff.match(x) and x in wanted:
758 del wanted[x]
759
760 def iterate():
761 if follow and not files:
762 ff = followfilter(onlyfirst=opts.get('follow_first'))
763 def want(rev):
764 if ff.match(rev) and rev in wanted:
765 return True
766 return False
767 else:
768 def want(rev):
769 return rev in wanted
770
771 for i, window in increasing_windows(0, len(revs)):
772 yield 'window', revs[0] < revs[-1], revs[-1]
773 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
774 srevs = list(nrevs)
775 srevs.sort()
776 for rev in srevs:
777 fns = fncache.get(rev)
778 if not fns:
779 def fns_generator():
780 for f in change(rev)[3]:
781 if matchfn(f):
782 yield f
783 fns = fns_generator()
784 yield 'add', rev, fns
785 for rev in nrevs:
786 yield 'iter', rev, None
787 return iterate(), matchfn