Mercurial > public > mercurial-scm > hg
comparison mercurial/cmdutil.py @ 3650: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
3649:e50891e461e4 | 3650: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 |