changeset 3657:731e739b8659

move walkchangerevs to cmdutils
author Matt Mackall <mpm@selenic.com>
date Wed, 15 Nov 2006 15:51:58 -0600
parents e50891e461e4
children 6389205291c6
files mercurial/cmdutil.py mercurial/commands.py
diffstat 2 files changed, 201 insertions(+), 202 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/cmdutil.py
+++ b/mercurial/cmdutil.py
@@ -592,3 +592,196 @@ def show_changeset(ui, repo, opts, buffe
         return t
     return changeset_printer(ui, repo, patch, br, buffered)
 
+def walkchangerevs(ui, repo, pats, change, opts):
+    '''Iterate over files and the revs they changed in.
+
+    Callers most commonly need to iterate backwards over the history
+    it is interested in.  Doing so has awful (quadratic-looking)
+    performance, so we use iterators in a "windowed" way.
+
+    We walk a window of revisions in the desired order.  Within the
+    window, we first walk forwards to gather data, then in the desired
+    order (usually backwards) to display it.
+
+    This function returns an (iterator, matchfn) tuple. The iterator
+    yields 3-tuples. They will be of one of the following forms:
+
+    "window", incrementing, lastrev: stepping through a window,
+    positive if walking forwards through revs, last rev in the
+    sequence iterated over - use to reset state for the current window
+
+    "add", rev, fns: out-of-order traversal of the given file names
+    fns, which changed during revision rev - use to gather data for
+    possible display
+
+    "iter", rev, None: in-order traversal of the revs earlier iterated
+    over with "add" - use to display data'''
+
+    def increasing_windows(start, end, windowsize=8, sizelimit=512):
+        if start < end:
+            while start < end:
+                yield start, min(windowsize, end-start)
+                start += windowsize
+                if windowsize < sizelimit:
+                    windowsize *= 2
+        else:
+            while start > end:
+                yield start, min(windowsize, start-end-1)
+                start -= windowsize
+                if windowsize < sizelimit:
+                    windowsize *= 2
+
+    files, matchfn, anypats = matchpats(repo, pats, opts)
+    follow = opts.get('follow') or opts.get('follow_first')
+
+    if repo.changelog.count() == 0:
+        return [], matchfn
+
+    if follow:
+        defrange = '%s:0' % repo.changectx().rev()
+    else:
+        defrange = 'tip:0'
+    revs = revrange(ui, repo, opts['rev'] or [defrange])
+    wanted = {}
+    slowpath = anypats
+    fncache = {}
+
+    if not slowpath and not files:
+        # No files, no patterns.  Display all revs.
+        wanted = dict.fromkeys(revs)
+    copies = []
+    if not slowpath:
+        # Only files, no patterns.  Check the history of each file.
+        def filerevgen(filelog, node):
+            cl_count = repo.changelog.count()
+            if node is None:
+                last = filelog.count() - 1
+            else:
+                last = filelog.rev(node)
+            for i, window in increasing_windows(last, nullrev):
+                revs = []
+                for j in xrange(i - window, i + 1):
+                    n = filelog.node(j)
+                    revs.append((filelog.linkrev(n),
+                                 follow and filelog.renamed(n)))
+                revs.reverse()
+                for rev in revs:
+                    # only yield rev for which we have the changelog, it can
+                    # happen while doing "hg log" during a pull or commit
+                    if rev[0] < cl_count:
+                        yield rev
+        def iterfiles():
+            for filename in files:
+                yield filename, None
+            for filename_node in copies:
+                yield filename_node
+        minrev, maxrev = min(revs), max(revs)
+        for file_, node in iterfiles():
+            filelog = repo.file(file_)
+            # A zero count may be a directory or deleted file, so
+            # try to find matching entries on the slow path.
+            if filelog.count() == 0:
+                slowpath = True
+                break
+            for rev, copied in filerevgen(filelog, node):
+                if rev <= maxrev:
+                    if rev < minrev:
+                        break
+                    fncache.setdefault(rev, [])
+                    fncache[rev].append(file_)
+                    wanted[rev] = 1
+                    if follow and copied:
+                        copies.append(copied)
+    if slowpath:
+        if follow:
+            raise util.Abort(_('can only follow copies/renames for explicit '
+                               'file names'))
+
+        # The slow path checks files modified in every changeset.
+        def changerevgen():
+            for i, window in increasing_windows(repo.changelog.count()-1,
+                                                nullrev):
+                for j in xrange(i - window, i + 1):
+                    yield j, change(j)[3]
+
+        for rev, changefiles in changerevgen():
+            matches = filter(matchfn, changefiles)
+            if matches:
+                fncache[rev] = matches
+                wanted[rev] = 1
+
+    class followfilter:
+        def __init__(self, onlyfirst=False):
+            self.startrev = nullrev
+            self.roots = []
+            self.onlyfirst = onlyfirst
+
+        def match(self, rev):
+            def realparents(rev):
+                if self.onlyfirst:
+                    return repo.changelog.parentrevs(rev)[0:1]
+                else:
+                    return filter(lambda x: x != nullrev,
+                                  repo.changelog.parentrevs(rev))
+
+            if self.startrev == nullrev:
+                self.startrev = rev
+                return True
+
+            if rev > self.startrev:
+                # forward: all descendants
+                if not self.roots:
+                    self.roots.append(self.startrev)
+                for parent in realparents(rev):
+                    if parent in self.roots:
+                        self.roots.append(rev)
+                        return True
+            else:
+                # backwards: all parents
+                if not self.roots:
+                    self.roots.extend(realparents(self.startrev))
+                if rev in self.roots:
+                    self.roots.remove(rev)
+                    self.roots.extend(realparents(rev))
+                    return True
+
+            return False
+
+    # it might be worthwhile to do this in the iterator if the rev range
+    # is descending and the prune args are all within that range
+    for rev in opts.get('prune', ()):
+        rev = repo.changelog.rev(repo.lookup(rev))
+        ff = followfilter()
+        stop = min(revs[0], revs[-1])
+        for x in xrange(rev, stop-1, -1):
+            if ff.match(x) and x in wanted:
+                del wanted[x]
+
+    def iterate():
+        if follow and not files:
+            ff = followfilter(onlyfirst=opts.get('follow_first'))
+            def want(rev):
+                if ff.match(rev) and rev in wanted:
+                    return True
+                return False
+        else:
+            def want(rev):
+                return rev in wanted
+
+        for i, window in increasing_windows(0, len(revs)):
+            yield 'window', revs[0] < revs[-1], revs[-1]
+            nrevs = [rev for rev in revs[i:i+window] if want(rev)]
+            srevs = list(nrevs)
+            srevs.sort()
+            for rev in srevs:
+                fns = fncache.get(rev)
+                if not fns:
+                    def fns_generator():
+                        for f in change(rev)[3]:
+                            if matchfn(f):
+                                yield f
+                    fns = fns_generator()
+                yield 'add', rev, fns
+            for rev in nrevs:
+                yield 'iter', rev, None
+    return iterate(), matchfn
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -49,200 +49,6 @@ def logmessage(opts):
                              (logfile, inst.strerror))
     return message
 
-def walkchangerevs(ui, repo, pats, change, opts):
-    '''Iterate over files and the revs they changed in.
-
-    Callers most commonly need to iterate backwards over the history
-    it is interested in.  Doing so has awful (quadratic-looking)
-    performance, so we use iterators in a "windowed" way.
-
-    We walk a window of revisions in the desired order.  Within the
-    window, we first walk forwards to gather data, then in the desired
-    order (usually backwards) to display it.
-
-    This function returns an (iterator, matchfn) tuple. The iterator
-    yields 3-tuples. They will be of one of the following forms:
-
-    "window", incrementing, lastrev: stepping through a window,
-    positive if walking forwards through revs, last rev in the
-    sequence iterated over - use to reset state for the current window
-
-    "add", rev, fns: out-of-order traversal of the given file names
-    fns, which changed during revision rev - use to gather data for
-    possible display
-
-    "iter", rev, None: in-order traversal of the revs earlier iterated
-    over with "add" - use to display data'''
-
-    def increasing_windows(start, end, windowsize=8, sizelimit=512):
-        if start < end:
-            while start < end:
-                yield start, min(windowsize, end-start)
-                start += windowsize
-                if windowsize < sizelimit:
-                    windowsize *= 2
-        else:
-            while start > end:
-                yield start, min(windowsize, start-end-1)
-                start -= windowsize
-                if windowsize < sizelimit:
-                    windowsize *= 2
-
-    files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
-    follow = opts.get('follow') or opts.get('follow_first')
-
-    if repo.changelog.count() == 0:
-        return [], matchfn
-
-    if follow:
-        defrange = '%s:0' % repo.changectx().rev()
-    else:
-        defrange = 'tip:0'
-    revs = cmdutil.revrange(ui, repo, opts['rev'] or [defrange])
-    wanted = {}
-    slowpath = anypats
-    fncache = {}
-
-    if not slowpath and not files:
-        # No files, no patterns.  Display all revs.
-        wanted = dict.fromkeys(revs)
-    copies = []
-    if not slowpath:
-        # Only files, no patterns.  Check the history of each file.
-        def filerevgen(filelog, node):
-            cl_count = repo.changelog.count()
-            if node is None:
-                last = filelog.count() - 1
-            else:
-                last = filelog.rev(node)
-            for i, window in increasing_windows(last, nullrev):
-                revs = []
-                for j in xrange(i - window, i + 1):
-                    n = filelog.node(j)
-                    revs.append((filelog.linkrev(n),
-                                 follow and filelog.renamed(n)))
-                revs.reverse()
-                for rev in revs:
-                    # only yield rev for which we have the changelog, it can
-                    # happen while doing "hg log" during a pull or commit
-                    if rev[0] < cl_count:
-                        yield rev
-        def iterfiles():
-            for filename in files:
-                yield filename, None
-            for filename_node in copies:
-                yield filename_node
-        minrev, maxrev = min(revs), max(revs)
-        for file_, node in iterfiles():
-            filelog = repo.file(file_)
-            # A zero count may be a directory or deleted file, so
-            # try to find matching entries on the slow path.
-            if filelog.count() == 0:
-                slowpath = True
-                break
-            for rev, copied in filerevgen(filelog, node):
-                if rev <= maxrev:
-                    if rev < minrev:
-                        break
-                    fncache.setdefault(rev, [])
-                    fncache[rev].append(file_)
-                    wanted[rev] = 1
-                    if follow and copied:
-                        copies.append(copied)
-    if slowpath:
-        if follow:
-            raise util.Abort(_('can only follow copies/renames for explicit '
-                               'file names'))
-
-        # The slow path checks files modified in every changeset.
-        def changerevgen():
-            for i, window in increasing_windows(repo.changelog.count()-1,
-                                                nullrev):
-                for j in xrange(i - window, i + 1):
-                    yield j, change(j)[3]
-
-        for rev, changefiles in changerevgen():
-            matches = filter(matchfn, changefiles)
-            if matches:
-                fncache[rev] = matches
-                wanted[rev] = 1
-
-    class followfilter:
-        def __init__(self, onlyfirst=False):
-            self.startrev = nullrev
-            self.roots = []
-            self.onlyfirst = onlyfirst
-
-        def match(self, rev):
-            def realparents(rev):
-                if self.onlyfirst:
-                    return repo.changelog.parentrevs(rev)[0:1]
-                else:
-                    return filter(lambda x: x != nullrev,
-                                  repo.changelog.parentrevs(rev))
-
-            if self.startrev == nullrev:
-                self.startrev = rev
-                return True
-
-            if rev > self.startrev:
-                # forward: all descendants
-                if not self.roots:
-                    self.roots.append(self.startrev)
-                for parent in realparents(rev):
-                    if parent in self.roots:
-                        self.roots.append(rev)
-                        return True
-            else:
-                # backwards: all parents
-                if not self.roots:
-                    self.roots.extend(realparents(self.startrev))
-                if rev in self.roots:
-                    self.roots.remove(rev)
-                    self.roots.extend(realparents(rev))
-                    return True
-
-            return False
-
-    # it might be worthwhile to do this in the iterator if the rev range
-    # is descending and the prune args are all within that range
-    for rev in opts.get('prune', ()):
-        rev = repo.changelog.rev(repo.lookup(rev))
-        ff = followfilter()
-        stop = min(revs[0], revs[-1])
-        for x in xrange(rev, stop-1, -1):
-            if ff.match(x) and x in wanted:
-                del wanted[x]
-
-    def iterate():
-        if follow and not files:
-            ff = followfilter(onlyfirst=opts.get('follow_first'))
-            def want(rev):
-                if ff.match(rev) and rev in wanted:
-                    return True
-                return False
-        else:
-            def want(rev):
-                return rev in wanted
-
-        for i, window in increasing_windows(0, len(revs)):
-            yield 'window', revs[0] < revs[-1], revs[-1]
-            nrevs = [rev for rev in revs[i:i+window] if want(rev)]
-            srevs = list(nrevs)
-            srevs.sort()
-            for rev in srevs:
-                fns = fncache.get(rev)
-                if not fns:
-                    def fns_generator():
-                        for f in change(rev)[3]:
-                            if matchfn(f):
-                                yield f
-                    fns = fns_generator()
-                yield 'add', rev, fns
-            for rev in nrevs:
-                yield 'iter', rev, None
-    return iterate(), matchfn
-
 def write_bundle(cg, filename=None, compress=True):
     """Write a bundle file and return its filename.
 
@@ -1352,7 +1158,7 @@ def grep(ui, repo, pattern, *pats, **opt
             if opts['all']:
                 cols.append(change)
             if opts['user']:
-                cols.append(ui.shortuser(getchange(r)[1]))
+                cols.append(ui.shortuser(get(r)[1]))
             if opts['files_with_matches']:
                 c = (fn, r)
                 if c in filerevmatches:
@@ -1366,8 +1172,8 @@ def grep(ui, repo, pattern, *pats, **opt
 
     fstate = {}
     skip = {}
-    getchange = util.cachefunc(lambda r:repo.changectx(r).changeset())
-    changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts)
+    get = util.cachefunc(lambda r:repo.changectx(r).changeset())
+    changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
     count = 0
     incrementing = False
     follow = opts.get('follow')
@@ -1664,8 +1470,8 @@ def log(ui, repo, *pats, **opts):
     files and full commit message is shown.
     """
 
-    getchange = util.cachefunc(lambda r:repo.changectx(r).changeset())
-    changeiter, matchfn = walkchangerevs(ui, repo, pats, getchange, opts)
+    get = util.cachefunc(lambda r:repo.changectx(r).changeset())
+    changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
 
     if opts['limit']:
         try:
@@ -1725,7 +1531,7 @@ def log(ui, repo, *pats, **opts):
                 continue
 
             if opts['keyword']:
-                changes = getchange(rev)
+                changes = get(rev)
                 miss = 0
                 for k in [kw.lower() for kw in opts['keyword']]:
                     if not (k in changes[1].lower() or
@@ -1738,8 +1544,8 @@ def log(ui, repo, *pats, **opts):
 
             copies = []
             if opts.get('copies') and rev:
-                mf = getchange(rev)[0]
-                for fn in getchange(rev)[3]:
+                mf = get(rev)[0]
+                for fn in get(rev)[3]:
                     rename = getrenamed(fn, rev, mf)
                     if rename:
                         copies.append((fn, rename[0]))