##// END OF EJS Templates
move walkchangerevs to cmdutils
Matt Mackall -
r3650:731e739b default
parent child Browse files
Show More
@@ -592,3 +592,196 b' def show_changeset(ui, repo, opts, buffe'
592 592 return t
593 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
@@ -49,200 +49,6 b' def logmessage(opts):'
49 49 (logfile, inst.strerror))
50 50 return message
51 51
52 def walkchangerevs(ui, repo, pats, change, opts):
53 '''Iterate over files and the revs they changed in.
54
55 Callers most commonly need to iterate backwards over the history
56 it is interested in. Doing so has awful (quadratic-looking)
57 performance, so we use iterators in a "windowed" way.
58
59 We walk a window of revisions in the desired order. Within the
60 window, we first walk forwards to gather data, then in the desired
61 order (usually backwards) to display it.
62
63 This function returns an (iterator, matchfn) tuple. The iterator
64 yields 3-tuples. They will be of one of the following forms:
65
66 "window", incrementing, lastrev: stepping through a window,
67 positive if walking forwards through revs, last rev in the
68 sequence iterated over - use to reset state for the current window
69
70 "add", rev, fns: out-of-order traversal of the given file names
71 fns, which changed during revision rev - use to gather data for
72 possible display
73
74 "iter", rev, None: in-order traversal of the revs earlier iterated
75 over with "add" - use to display data'''
76
77 def increasing_windows(start, end, windowsize=8, sizelimit=512):
78 if start < end:
79 while start < end:
80 yield start, min(windowsize, end-start)
81 start += windowsize
82 if windowsize < sizelimit:
83 windowsize *= 2
84 else:
85 while start > end:
86 yield start, min(windowsize, start-end-1)
87 start -= windowsize
88 if windowsize < sizelimit:
89 windowsize *= 2
90
91 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
92 follow = opts.get('follow') or opts.get('follow_first')
93
94 if repo.changelog.count() == 0:
95 return [], matchfn
96
97 if follow:
98 defrange = '%s:0' % repo.changectx().rev()
99 else:
100 defrange = 'tip:0'
101 revs = cmdutil.revrange(ui, repo, opts['rev'] or [defrange])
102 wanted = {}
103 slowpath = anypats
104 fncache = {}
105
106 if not slowpath and not files:
107 # No files, no patterns. Display all revs.
108 wanted = dict.fromkeys(revs)
109 copies = []
110 if not slowpath:
111 # Only files, no patterns. Check the history of each file.
112 def filerevgen(filelog, node):
113 cl_count = repo.changelog.count()
114 if node is None:
115 last = filelog.count() - 1
116 else:
117 last = filelog.rev(node)
118 for i, window in increasing_windows(last, nullrev):
119 revs = []
120 for j in xrange(i - window, i + 1):
121 n = filelog.node(j)
122 revs.append((filelog.linkrev(n),
123 follow and filelog.renamed(n)))
124 revs.reverse()
125 for rev in revs:
126 # only yield rev for which we have the changelog, it can
127 # happen while doing "hg log" during a pull or commit
128 if rev[0] < cl_count:
129 yield rev
130 def iterfiles():
131 for filename in files:
132 yield filename, None
133 for filename_node in copies:
134 yield filename_node
135 minrev, maxrev = min(revs), max(revs)
136 for file_, node in iterfiles():
137 filelog = repo.file(file_)
138 # A zero count may be a directory or deleted file, so
139 # try to find matching entries on the slow path.
140 if filelog.count() == 0:
141 slowpath = True
142 break
143 for rev, copied in filerevgen(filelog, node):
144 if rev <= maxrev:
145 if rev < minrev:
146 break
147 fncache.setdefault(rev, [])
148 fncache[rev].append(file_)
149 wanted[rev] = 1
150 if follow and copied:
151 copies.append(copied)
152 if slowpath:
153 if follow:
154 raise util.Abort(_('can only follow copies/renames for explicit '
155 'file names'))
156
157 # The slow path checks files modified in every changeset.
158 def changerevgen():
159 for i, window in increasing_windows(repo.changelog.count()-1,
160 nullrev):
161 for j in xrange(i - window, i + 1):
162 yield j, change(j)[3]
163
164 for rev, changefiles in changerevgen():
165 matches = filter(matchfn, changefiles)
166 if matches:
167 fncache[rev] = matches
168 wanted[rev] = 1
169
170 class followfilter:
171 def __init__(self, onlyfirst=False):
172 self.startrev = nullrev
173 self.roots = []
174 self.onlyfirst = onlyfirst
175
176 def match(self, rev):
177 def realparents(rev):
178 if self.onlyfirst:
179 return repo.changelog.parentrevs(rev)[0:1]
180 else:
181 return filter(lambda x: x != nullrev,
182 repo.changelog.parentrevs(rev))
183
184 if self.startrev == nullrev:
185 self.startrev = rev
186 return True
187
188 if rev > self.startrev:
189 # forward: all descendants
190 if not self.roots:
191 self.roots.append(self.startrev)
192 for parent in realparents(rev):
193 if parent in self.roots:
194 self.roots.append(rev)
195 return True
196 else:
197 # backwards: all parents
198 if not self.roots:
199 self.roots.extend(realparents(self.startrev))
200 if rev in self.roots:
201 self.roots.remove(rev)
202 self.roots.extend(realparents(rev))
203 return True
204
205 return False
206
207 # it might be worthwhile to do this in the iterator if the rev range
208 # is descending and the prune args are all within that range
209 for rev in opts.get('prune', ()):
210 rev = repo.changelog.rev(repo.lookup(rev))
211 ff = followfilter()
212 stop = min(revs[0], revs[-1])
213 for x in xrange(rev, stop-1, -1):
214 if ff.match(x) and x in wanted:
215 del wanted[x]
216
217 def iterate():
218 if follow and not files:
219 ff = followfilter(onlyfirst=opts.get('follow_first'))
220 def want(rev):
221 if ff.match(rev) and rev in wanted:
222 return True
223 return False
224 else:
225 def want(rev):
226 return rev in wanted
227
228 for i, window in increasing_windows(0, len(revs)):
229 yield 'window', revs[0] < revs[-1], revs[-1]
230 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
231 srevs = list(nrevs)
232 srevs.sort()
233 for rev in srevs:
234 fns = fncache.get(rev)
235 if not fns:
236 def fns_generator():
237 for f in change(rev)[3]:
238 if matchfn(f):
239 yield f
240 fns = fns_generator()
241 yield 'add', rev, fns
242 for rev in nrevs:
243 yield 'iter', rev, None
244 return iterate(), matchfn
245
246 52 def write_bundle(cg, filename=None, compress=True):
247 53 """Write a bundle file and return its filename.
248 54
@@ -1352,7 +1158,7 b' def grep(ui, repo, pattern, *pats, **opt'
1352 1158 if opts['all']:
1353 1159 cols.append(change)
1354 1160 if opts['user']:
1355 cols.append(ui.shortuser(getchange(r)[1]))
1161 cols.append(ui.shortuser(get(r)[1]))
1356 1162 if opts['files_with_matches']:
1357 1163 c = (fn, r)
1358 1164 if c in filerevmatches:
@@ -1366,8 +1172,8 b' def grep(ui, repo, pattern, *pats, **opt'
1366 1172
1367 1173 fstate = {}
1368 1174 skip = {}
1369 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset())
1370 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts)
1175 get = util.cachefunc(lambda r:repo.changectx(r).changeset())
1176 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1371 1177 count = 0
1372 1178 incrementing = False
1373 1179 follow = opts.get('follow')
@@ -1664,8 +1470,8 b' def log(ui, repo, *pats, **opts):'
1664 1470 files and full commit message is shown.
1665 1471 """
1666 1472
1667 getchange = util.cachefunc(lambda r:repo.changectx(r).changeset())
1668 changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts)
1473 get = util.cachefunc(lambda r:repo.changectx(r).changeset())
1474 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
1669 1475
1670 1476 if opts['limit']:
1671 1477 try:
@@ -1725,7 +1531,7 b' def log(ui, repo, *pats, **opts):'
1725 1531 continue
1726 1532
1727 1533 if opts['keyword']:
1728 changes = getchange(rev)
1534 changes = get(rev)
1729 1535 miss = 0
1730 1536 for k in [kw.lower() for kw in opts['keyword']]:
1731 1537 if not (k in changes[1].lower() or
@@ -1738,8 +1544,8 b' def log(ui, repo, *pats, **opts):'
1738 1544
1739 1545 copies = []
1740 1546 if opts.get('copies') and rev:
1741 mf = getchange(rev)[0]
1742 for fn in getchange(rev)[3]:
1547 mf = get(rev)[0]
1548 for fn in get(rev)[3]:
1743 1549 rename = getrenamed(fn, rev, mf)
1744 1550 if rename:
1745 1551 copies.append((fn, rename[0]))
General Comments 0
You need to be logged in to leave comments. Login now