diff mercurial/cmdutil.py @ 35925:7625b4f7db70

cmdutil: split functions of log-like commands to new module (API) cmdutil.py is painfully big and makes Emacs slow. Let's split log-related functions. % wc -l mercurial/cmdutil.py 4027 mercurial/cmdutil.py % wc -l mercurial/cmdutil.py mercurial/logcmdutil.py 3141 mercurial/cmdutil.py 933 mercurial/logcmdutil.py 4074 total
author Yuya Nishihara <yuya@tcha.org>
date Sun, 21 Jan 2018 12:26:42 +0900
parents 1bee7762fd46
children b0014780c7fc
line wrap: on
line diff
--- a/mercurial/cmdutil.py	Fri Feb 02 13:13:46 2018 -0800
+++ b/mercurial/cmdutil.py	Sun Jan 21 12:26:42 2018 +0900
@@ -8,7 +8,6 @@
 from __future__ import absolute_import
 
 import errno
-import itertools
 import os
 import re
 import tempfile
@@ -26,32 +25,43 @@
     changelog,
     copies,
     crecord as crecordmod,
-    dagop,
     dirstateguard,
     encoding,
     error,
     formatter,
-    graphmod,
+    logcmdutil,
     match as matchmod,
-    mdiff,
     obsolete,
     patch,
     pathutil,
     pycompat,
     registrar,
     revlog,
-    revset,
-    revsetlang,
     rewriteutil,
     scmutil,
     smartset,
-    templatekw,
     templater,
     util,
     vfs as vfsmod,
 )
 stringio = util.stringio
 
+loglimit = logcmdutil.loglimit
+diffordiffstat = logcmdutil.diffordiffstat
+_changesetlabels = logcmdutil._changesetlabels
+changeset_printer = logcmdutil.changeset_printer
+jsonchangeset = logcmdutil.jsonchangeset
+changeset_templater = logcmdutil.changeset_templater
+logtemplatespec = logcmdutil.logtemplatespec
+makelogtemplater = logcmdutil.makelogtemplater
+show_changeset = logcmdutil.show_changeset
+getlogrevs = logcmdutil.getlogrevs
+getloglinerangerevs = logcmdutil.getloglinerangerevs
+displaygraph = logcmdutil.displaygraph
+graphlog = logcmdutil.graphlog
+checkunsupportedgraphflags = logcmdutil.checkunsupportedgraphflags
+graphrevs = logcmdutil.graphrevs
+
 # templates of common command options
 
 dryrunopts = [
@@ -898,20 +908,6 @@
     else:
         return commiteditor
 
-def loglimit(opts):
-    """get the log limit according to option -l/--limit"""
-    limit = opts.get('limit')
-    if limit:
-        try:
-            limit = int(limit)
-        except ValueError:
-            raise error.Abort(_('limit must be a positive integer'))
-        if limit <= 0:
-            raise error.Abort(_('limit must be positive'))
-    else:
-        limit = None
-    return limit
-
 def makefilename(repo, pat, node, desc=None,
                   total=None, seqno=None, revwidth=None, pathname=None):
     node_expander = {
@@ -1583,500 +1579,6 @@
         if fo is not None:
             fo.close()
 
-def diffordiffstat(ui, repo, diffopts, node1, node2, match,
-                   changes=None, stat=False, fp=None, prefix='',
-                   root='', listsubrepos=False, hunksfilterfn=None):
-    '''show diff or diffstat.'''
-    if fp is None:
-        write = ui.write
-    else:
-        def write(s, **kw):
-            fp.write(s)
-
-    if root:
-        relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
-    else:
-        relroot = ''
-    if relroot != '':
-        # XXX relative roots currently don't work if the root is within a
-        # subrepo
-        uirelroot = match.uipath(relroot)
-        relroot += '/'
-        for matchroot in match.files():
-            if not matchroot.startswith(relroot):
-                ui.warn(_('warning: %s not inside relative root %s\n') % (
-                    match.uipath(matchroot), uirelroot))
-
-    if stat:
-        diffopts = diffopts.copy(context=0, noprefix=False)
-        width = 80
-        if not ui.plain():
-            width = ui.termwidth()
-        chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
-                            prefix=prefix, relroot=relroot,
-                            hunksfilterfn=hunksfilterfn)
-        for chunk, label in patch.diffstatui(util.iterlines(chunks),
-                                             width=width):
-            write(chunk, label=label)
-    else:
-        for chunk, label in patch.diffui(repo, node1, node2, match,
-                                         changes, opts=diffopts, prefix=prefix,
-                                         relroot=relroot,
-                                         hunksfilterfn=hunksfilterfn):
-            write(chunk, label=label)
-
-    if listsubrepos:
-        ctx1 = repo[node1]
-        ctx2 = repo[node2]
-        for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
-            tempnode2 = node2
-            try:
-                if node2 is not None:
-                    tempnode2 = ctx2.substate[subpath][1]
-            except KeyError:
-                # A subrepo that existed in node1 was deleted between node1 and
-                # node2 (inclusive). Thus, ctx2's substate won't contain that
-                # subpath. The best we can do is to ignore it.
-                tempnode2 = None
-            submatch = matchmod.subdirmatcher(subpath, match)
-            sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
-                     stat=stat, fp=fp, prefix=prefix)
-
-def _changesetlabels(ctx):
-    labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
-    if ctx.obsolete():
-        labels.append('changeset.obsolete')
-    if ctx.isunstable():
-        labels.append('changeset.unstable')
-        for instability in ctx.instabilities():
-            labels.append('instability.%s' % instability)
-    return ' '.join(labels)
-
-class changeset_printer(object):
-    '''show changeset information when templating not requested.'''
-
-    def __init__(self, ui, repo, matchfn, diffopts, buffered):
-        self.ui = ui
-        self.repo = repo
-        self.buffered = buffered
-        self.matchfn = matchfn
-        self.diffopts = diffopts
-        self.header = {}
-        self.hunk = {}
-        self.lastheader = None
-        self.footer = None
-        self._columns = templatekw.getlogcolumns()
-
-    def flush(self, ctx):
-        rev = ctx.rev()
-        if rev in self.header:
-            h = self.header[rev]
-            if h != self.lastheader:
-                self.lastheader = h
-                self.ui.write(h)
-            del self.header[rev]
-        if rev in self.hunk:
-            self.ui.write(self.hunk[rev])
-            del self.hunk[rev]
-
-    def close(self):
-        if self.footer:
-            self.ui.write(self.footer)
-
-    def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
-             **props):
-        props = pycompat.byteskwargs(props)
-        if self.buffered:
-            self.ui.pushbuffer(labeled=True)
-            self._show(ctx, copies, matchfn, hunksfilterfn, props)
-            self.hunk[ctx.rev()] = self.ui.popbuffer()
-        else:
-            self._show(ctx, copies, matchfn, hunksfilterfn, props)
-
-    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
-        '''show a single changeset or file revision'''
-        changenode = ctx.node()
-        rev = ctx.rev()
-
-        if self.ui.quiet:
-            self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
-                          label='log.node')
-            return
-
-        columns = self._columns
-        self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
-                      label=_changesetlabels(ctx))
-
-        # branches are shown first before any other names due to backwards
-        # compatibility
-        branch = ctx.branch()
-        # don't show the default branch name
-        if branch != 'default':
-            self.ui.write(columns['branch'] % branch, label='log.branch')
-
-        for nsname, ns in self.repo.names.iteritems():
-            # branches has special logic already handled above, so here we just
-            # skip it
-            if nsname == 'branches':
-                continue
-            # we will use the templatename as the color name since those two
-            # should be the same
-            for name in ns.names(self.repo, changenode):
-                self.ui.write(ns.logfmt % name,
-                              label='log.%s' % ns.colorname)
-        if self.ui.debugflag:
-            self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
-        for pctx in scmutil.meaningfulparents(self.repo, ctx):
-            label = 'log.parent changeset.%s' % pctx.phasestr()
-            self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
-                          label=label)
-
-        if self.ui.debugflag and rev is not None:
-            mnode = ctx.manifestnode()
-            mrev = self.repo.manifestlog._revlog.rev(mnode)
-            self.ui.write(columns['manifest']
-                          % scmutil.formatrevnode(self.ui, mrev, mnode),
-                          label='ui.debug log.manifest')
-        self.ui.write(columns['user'] % ctx.user(), label='log.user')
-        self.ui.write(columns['date'] % util.datestr(ctx.date()),
-                      label='log.date')
-
-        if ctx.isunstable():
-            instabilities = ctx.instabilities()
-            self.ui.write(columns['instability'] % ', '.join(instabilities),
-                          label='log.instability')
-
-        elif ctx.obsolete():
-            self._showobsfate(ctx)
-
-        self._exthook(ctx)
-
-        if self.ui.debugflag:
-            files = ctx.p1().status(ctx)[:3]
-            for key, value in zip(['files', 'files+', 'files-'], files):
-                if value:
-                    self.ui.write(columns[key] % " ".join(value),
-                                  label='ui.debug log.files')
-        elif ctx.files() and self.ui.verbose:
-            self.ui.write(columns['files'] % " ".join(ctx.files()),
-                          label='ui.note log.files')
-        if copies and self.ui.verbose:
-            copies = ['%s (%s)' % c for c in copies]
-            self.ui.write(columns['copies'] % ' '.join(copies),
-                          label='ui.note log.copies')
-
-        extra = ctx.extra()
-        if extra and self.ui.debugflag:
-            for key, value in sorted(extra.items()):
-                self.ui.write(columns['extra'] % (key, util.escapestr(value)),
-                              label='ui.debug log.extra')
-
-        description = ctx.description().strip()
-        if description:
-            if self.ui.verbose:
-                self.ui.write(_("description:\n"),
-                              label='ui.note log.description')
-                self.ui.write(description,
-                              label='ui.note log.description')
-                self.ui.write("\n\n")
-            else:
-                self.ui.write(columns['summary'] % description.splitlines()[0],
-                              label='log.summary')
-        self.ui.write("\n")
-
-        self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
-
-    def _showobsfate(self, ctx):
-        obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
-
-        if obsfate:
-            for obsfateline in obsfate:
-                self.ui.write(self._columns['obsolete'] % obsfateline,
-                              label='log.obsfate')
-
-    def _exthook(self, ctx):
-        '''empty method used by extension as a hook point
-        '''
-
-    def showpatch(self, ctx, matchfn, hunksfilterfn=None):
-        if not matchfn:
-            matchfn = self.matchfn
-        if matchfn:
-            stat = self.diffopts.get('stat')
-            diff = self.diffopts.get('patch')
-            diffopts = patch.diffallopts(self.ui, self.diffopts)
-            node = ctx.node()
-            prev = ctx.p1().node()
-            if stat:
-                diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=True,
-                               hunksfilterfn=hunksfilterfn)
-            if diff:
-                if stat:
-                    self.ui.write("\n")
-                diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=False,
-                               hunksfilterfn=hunksfilterfn)
-            if stat or diff:
-                self.ui.write("\n")
-
-class jsonchangeset(changeset_printer):
-    '''format changeset information.'''
-
-    def __init__(self, ui, repo, matchfn, diffopts, buffered):
-        changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
-        self.cache = {}
-        self._first = True
-
-    def close(self):
-        if not self._first:
-            self.ui.write("\n]\n")
-        else:
-            self.ui.write("[]\n")
-
-    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
-        '''show a single changeset or file revision'''
-        rev = ctx.rev()
-        if rev is None:
-            jrev = jnode = 'null'
-        else:
-            jrev = '%d' % rev
-            jnode = '"%s"' % hex(ctx.node())
-        j = encoding.jsonescape
-
-        if self._first:
-            self.ui.write("[\n {")
-            self._first = False
-        else:
-            self.ui.write(",\n {")
-
-        if self.ui.quiet:
-            self.ui.write(('\n  "rev": %s') % jrev)
-            self.ui.write((',\n  "node": %s') % jnode)
-            self.ui.write('\n }')
-            return
-
-        self.ui.write(('\n  "rev": %s') % jrev)
-        self.ui.write((',\n  "node": %s') % jnode)
-        self.ui.write((',\n  "branch": "%s"') % j(ctx.branch()))
-        self.ui.write((',\n  "phase": "%s"') % ctx.phasestr())
-        self.ui.write((',\n  "user": "%s"') % j(ctx.user()))
-        self.ui.write((',\n  "date": [%d, %d]') % ctx.date())
-        self.ui.write((',\n  "desc": "%s"') % j(ctx.description()))
-
-        self.ui.write((',\n  "bookmarks": [%s]') %
-                      ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
-        self.ui.write((',\n  "tags": [%s]') %
-                      ", ".join('"%s"' % j(t) for t in ctx.tags()))
-        self.ui.write((',\n  "parents": [%s]') %
-                      ", ".join('"%s"' % c.hex() for c in ctx.parents()))
-
-        if self.ui.debugflag:
-            if rev is None:
-                jmanifestnode = 'null'
-            else:
-                jmanifestnode = '"%s"' % hex(ctx.manifestnode())
-            self.ui.write((',\n  "manifest": %s') % jmanifestnode)
-
-            self.ui.write((',\n  "extra": {%s}') %
-                          ", ".join('"%s": "%s"' % (j(k), j(v))
-                                    for k, v in ctx.extra().items()))
-
-            files = ctx.p1().status(ctx)
-            self.ui.write((',\n  "modified": [%s]') %
-                          ", ".join('"%s"' % j(f) for f in files[0]))
-            self.ui.write((',\n  "added": [%s]') %
-                          ", ".join('"%s"' % j(f) for f in files[1]))
-            self.ui.write((',\n  "removed": [%s]') %
-                          ", ".join('"%s"' % j(f) for f in files[2]))
-
-        elif self.ui.verbose:
-            self.ui.write((',\n  "files": [%s]') %
-                          ", ".join('"%s"' % j(f) for f in ctx.files()))
-
-            if copies:
-                self.ui.write((',\n  "copies": {%s}') %
-                              ", ".join('"%s": "%s"' % (j(k), j(v))
-                                                        for k, v in copies))
-
-        matchfn = self.matchfn
-        if matchfn:
-            stat = self.diffopts.get('stat')
-            diff = self.diffopts.get('patch')
-            diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
-            node, prev = ctx.node(), ctx.p1().node()
-            if stat:
-                self.ui.pushbuffer()
-                diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=True)
-                self.ui.write((',\n  "diffstat": "%s"')
-                              % j(self.ui.popbuffer()))
-            if diff:
-                self.ui.pushbuffer()
-                diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=False)
-                self.ui.write((',\n  "diff": "%s"') % j(self.ui.popbuffer()))
-
-        self.ui.write("\n }")
-
-class changeset_templater(changeset_printer):
-    '''format changeset information.
-
-    Note: there are a variety of convenience functions to build a
-    changeset_templater for common cases. See functions such as:
-    makelogtemplater, show_changeset, buildcommittemplate, or other
-    functions that use changesest_templater.
-    '''
-
-    # Arguments before "buffered" used to be positional. Consider not
-    # adding/removing arguments before "buffered" to not break callers.
-    def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
-                 buffered=False):
-        diffopts = diffopts or {}
-
-        changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
-        tres = formatter.templateresources(ui, repo)
-        self.t = formatter.loadtemplater(ui, tmplspec,
-                                         defaults=templatekw.keywords,
-                                         resources=tres,
-                                         cache=templatekw.defaulttempl)
-        self._counter = itertools.count()
-        self.cache = tres['cache']  # shared with _graphnodeformatter()
-
-        self._tref = tmplspec.ref
-        self._parts = {'header': '', 'footer': '',
-                       tmplspec.ref: tmplspec.ref,
-                       'docheader': '', 'docfooter': '',
-                       'separator': ''}
-        if tmplspec.mapfile:
-            # find correct templates for current mode, for backward
-            # compatibility with 'log -v/-q/--debug' using a mapfile
-            tmplmodes = [
-                (True, ''),
-                (self.ui.verbose, '_verbose'),
-                (self.ui.quiet, '_quiet'),
-                (self.ui.debugflag, '_debug'),
-            ]
-            for mode, postfix in tmplmodes:
-                for t in self._parts:
-                    cur = t + postfix
-                    if mode and cur in self.t:
-                        self._parts[t] = cur
-        else:
-            partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
-            m = formatter.templatepartsmap(tmplspec, self.t, partnames)
-            self._parts.update(m)
-
-        if self._parts['docheader']:
-            self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
-
-    def close(self):
-        if self._parts['docfooter']:
-            if not self.footer:
-                self.footer = ""
-            self.footer += templater.stringify(self.t(self._parts['docfooter']))
-        return super(changeset_templater, self).close()
-
-    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
-        '''show a single changeset or file revision'''
-        props = props.copy()
-        props['ctx'] = ctx
-        props['index'] = index = next(self._counter)
-        props['revcache'] = {'copies': copies}
-        props = pycompat.strkwargs(props)
-
-        # write separator, which wouldn't work well with the header part below
-        # since there's inherently a conflict between header (across items) and
-        # separator (per item)
-        if self._parts['separator'] and index > 0:
-            self.ui.write(templater.stringify(self.t(self._parts['separator'])))
-
-        # write header
-        if self._parts['header']:
-            h = templater.stringify(self.t(self._parts['header'], **props))
-            if self.buffered:
-                self.header[ctx.rev()] = h
-            else:
-                if self.lastheader != h:
-                    self.lastheader = h
-                    self.ui.write(h)
-
-        # write changeset metadata, then patch if requested
-        key = self._parts[self._tref]
-        self.ui.write(templater.stringify(self.t(key, **props)))
-        self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
-
-        if self._parts['footer']:
-            if not self.footer:
-                self.footer = templater.stringify(
-                    self.t(self._parts['footer'], **props))
-
-def logtemplatespec(tmpl, mapfile):
-    if mapfile:
-        return formatter.templatespec('changeset', tmpl, mapfile)
-    else:
-        return formatter.templatespec('', tmpl, None)
-
-def _lookuplogtemplate(ui, tmpl, style):
-    """Find the template matching the given template spec or style
-
-    See formatter.lookuptemplate() for details.
-    """
-
-    # ui settings
-    if not tmpl and not style: # template are stronger than style
-        tmpl = ui.config('ui', 'logtemplate')
-        if tmpl:
-            return logtemplatespec(templater.unquotestring(tmpl), None)
-        else:
-            style = util.expandpath(ui.config('ui', 'style'))
-
-    if not tmpl and style:
-        mapfile = style
-        if not os.path.split(mapfile)[0]:
-            mapname = (templater.templatepath('map-cmdline.' + mapfile)
-                       or templater.templatepath(mapfile))
-            if mapname:
-                mapfile = mapname
-        return logtemplatespec(None, mapfile)
-
-    if not tmpl:
-        return logtemplatespec(None, None)
-
-    return formatter.lookuptemplate(ui, 'changeset', tmpl)
-
-def makelogtemplater(ui, repo, tmpl, buffered=False):
-    """Create a changeset_templater from a literal template 'tmpl'
-    byte-string."""
-    spec = logtemplatespec(tmpl, None)
-    return changeset_templater(ui, repo, spec, buffered=buffered)
-
-def show_changeset(ui, repo, opts, buffered=False):
-    """show one changeset using template or regular display.
-
-    Display format will be the first non-empty hit of:
-    1. option 'template'
-    2. option 'style'
-    3. [ui] setting 'logtemplate'
-    4. [ui] setting 'style'
-    If all of these values are either the unset or the empty string,
-    regular display via changeset_printer() is done.
-    """
-    # options
-    match = None
-    if opts.get('patch') or opts.get('stat'):
-        match = scmutil.matchall(repo)
-
-    if opts.get('template') == 'json':
-        return jsonchangeset(ui, repo, match, opts, buffered)
-
-    spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
-
-    if not spec.ref and not spec.tmpl and not spec.mapfile:
-        return changeset_printer(ui, repo, match, opts, buffered)
-
-    return changeset_templater(ui, repo, spec, match, opts, buffered)
-
 class _regrettablereprbytes(bytes):
     """Bytes subclass that makes the repr the same on Python 3 as Python 2.
 
@@ -2429,394 +1931,6 @@
 
     return iterate()
 
-def _makelogmatcher(repo, revs, pats, opts):
-    """Build matcher and expanded patterns from log options
-
-    If --follow, revs are the revisions to follow from.
-
-    Returns (match, pats, slowpath) where
-    - match: a matcher built from the given pats and -I/-X opts
-    - pats: patterns used (globs are expanded on Windows)
-    - slowpath: True if patterns aren't as simple as scanning filelogs
-    """
-    # pats/include/exclude are passed to match.match() directly in
-    # _matchfiles() revset but walkchangerevs() builds its matcher with
-    # scmutil.match(). The difference is input pats are globbed on
-    # platforms without shell expansion (windows).
-    wctx = repo[None]
-    match, pats = scmutil.matchandpats(wctx, pats, opts)
-    slowpath = match.anypats() or (not match.always() and opts.get('removed'))
-    if not slowpath:
-        follow = opts.get('follow') or opts.get('follow_first')
-        startctxs = []
-        if follow and opts.get('rev'):
-            startctxs = [repo[r] for r in revs]
-        for f in match.files():
-            if follow and startctxs:
-                # No idea if the path was a directory at that revision, so
-                # take the slow path.
-                if any(f not in c for c in startctxs):
-                    slowpath = True
-                    continue
-            elif follow and f not in wctx:
-                # If the file exists, it may be a directory, so let it
-                # take the slow path.
-                if os.path.exists(repo.wjoin(f)):
-                    slowpath = True
-                    continue
-                else:
-                    raise error.Abort(_('cannot follow file not in parent '
-                                        'revision: "%s"') % f)
-            filelog = repo.file(f)
-            if not filelog:
-                # A zero count may be a directory or deleted file, so
-                # try to find matching entries on the slow path.
-                if follow:
-                    raise error.Abort(
-                        _('cannot follow nonexistent file: "%s"') % f)
-                slowpath = True
-
-        # We decided to fall back to the slowpath because at least one
-        # of the paths was not a file. Check to see if at least one of them
-        # existed in history - in that case, we'll continue down the
-        # slowpath; otherwise, we can turn off the slowpath
-        if slowpath:
-            for path in match.files():
-                if path == '.' or path in repo.store:
-                    break
-            else:
-                slowpath = False
-
-    return match, pats, slowpath
-
-def _fileancestors(repo, revs, match, followfirst):
-    fctxs = []
-    for r in revs:
-        ctx = repo[r]
-        fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
-
-    # When displaying a revision with --patch --follow FILE, we have
-    # to know which file of the revision must be diffed. With
-    # --follow, we want the names of the ancestors of FILE in the
-    # revision, stored in "fcache". "fcache" is populated as a side effect
-    # of the graph traversal.
-    fcache = {}
-    def filematcher(rev):
-        return scmutil.matchfiles(repo, fcache.get(rev, []))
-
-    def revgen():
-        for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
-            fcache[rev] = [c.path() for c in cs]
-            yield rev
-    return smartset.generatorset(revgen(), iterasc=False), filematcher
-
-def _makenofollowlogfilematcher(repo, pats, opts):
-    '''hook for extensions to override the filematcher for non-follow cases'''
-    return None
-
-_opt2logrevset = {
-    'no_merges':        ('not merge()', None),
-    'only_merges':      ('merge()', None),
-    '_matchfiles':      (None, '_matchfiles(%ps)'),
-    'date':             ('date(%s)', None),
-    'branch':           ('branch(%s)', '%lr'),
-    '_patslog':         ('filelog(%s)', '%lr'),
-    'keyword':          ('keyword(%s)', '%lr'),
-    'prune':            ('ancestors(%s)', 'not %lr'),
-    'user':             ('user(%s)', '%lr'),
-}
-
-def _makelogrevset(repo, match, pats, slowpath, opts):
-    """Return a revset string built from log options and file patterns"""
-    opts = dict(opts)
-    # follow or not follow?
-    follow = opts.get('follow') or opts.get('follow_first')
-
-    # branch and only_branch are really aliases and must be handled at
-    # the same time
-    opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
-    opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
-
-    if slowpath:
-        # See walkchangerevs() slow path.
-        #
-        # pats/include/exclude cannot be represented as separate
-        # revset expressions as their filtering logic applies at file
-        # level. For instance "-I a -X b" matches a revision touching
-        # "a" and "b" while "file(a) and not file(b)" does
-        # not. Besides, filesets are evaluated against the working
-        # directory.
-        matchargs = ['r:', 'd:relpath']
-        for p in pats:
-            matchargs.append('p:' + p)
-        for p in opts.get('include', []):
-            matchargs.append('i:' + p)
-        for p in opts.get('exclude', []):
-            matchargs.append('x:' + p)
-        opts['_matchfiles'] = matchargs
-    elif not follow:
-        opts['_patslog'] = list(pats)
-
-    expr = []
-    for op, val in sorted(opts.iteritems()):
-        if not val:
-            continue
-        if op not in _opt2logrevset:
-            continue
-        revop, listop = _opt2logrevset[op]
-        if revop and '%' not in revop:
-            expr.append(revop)
-        elif not listop:
-            expr.append(revsetlang.formatspec(revop, val))
-        else:
-            if revop:
-                val = [revsetlang.formatspec(revop, v) for v in val]
-            expr.append(revsetlang.formatspec(listop, val))
-
-    if expr:
-        expr = '(' + ' and '.join(expr) + ')'
-    else:
-        expr = None
-    return expr
-
-def _logrevs(repo, opts):
-    """Return the initial set of revisions to be filtered or followed"""
-    follow = opts.get('follow') or opts.get('follow_first')
-    if opts.get('rev'):
-        revs = scmutil.revrange(repo, opts['rev'])
-    elif follow and repo.dirstate.p1() == nullid:
-        revs = smartset.baseset()
-    elif follow:
-        revs = repo.revs('.')
-    else:
-        revs = smartset.spanset(repo)
-        revs.reverse()
-    return revs
-
-def getlogrevs(repo, pats, opts):
-    """Return (revs, filematcher) where revs is a smartset
-
-    filematcher is a callable taking a revision number and returning a match
-    objects filtering the files to be detailed when displaying the revision.
-    """
-    follow = opts.get('follow') or opts.get('follow_first')
-    followfirst = opts.get('follow_first')
-    limit = loglimit(opts)
-    revs = _logrevs(repo, opts)
-    if not revs:
-        return smartset.baseset(), None
-    match, pats, slowpath = _makelogmatcher(repo, revs, pats, opts)
-    filematcher = None
-    if follow:
-        if slowpath or match.always():
-            revs = dagop.revancestors(repo, revs, followfirst=followfirst)
-        else:
-            revs, filematcher = _fileancestors(repo, revs, match, followfirst)
-        revs.reverse()
-    if filematcher is None:
-        filematcher = _makenofollowlogfilematcher(repo, pats, opts)
-    if filematcher is None:
-        def filematcher(rev):
-            return match
-
-    expr = _makelogrevset(repo, match, pats, slowpath, opts)
-    if opts.get('graph') and opts.get('rev'):
-        # User-specified revs might be unsorted, but don't sort before
-        # _makelogrevset because it might depend on the order of revs
-        if not (revs.isdescending() or revs.istopo()):
-            revs.sort(reverse=True)
-    if expr:
-        matcher = revset.match(None, expr)
-        revs = matcher(repo, revs)
-    if limit is not None:
-        revs = revs.slice(0, limit)
-    return revs, filematcher
-
-def _parselinerangelogopt(repo, opts):
-    """Parse --line-range log option and return a list of tuples (filename,
-    (fromline, toline)).
-    """
-    linerangebyfname = []
-    for pat in opts.get('line_range', []):
-        try:
-            pat, linerange = pat.rsplit(',', 1)
-        except ValueError:
-            raise error.Abort(_('malformatted line-range pattern %s') % pat)
-        try:
-            fromline, toline = map(int, linerange.split(':'))
-        except ValueError:
-            raise error.Abort(_("invalid line range for %s") % pat)
-        msg = _("line range pattern '%s' must match exactly one file") % pat
-        fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
-        linerangebyfname.append(
-            (fname, util.processlinerange(fromline, toline)))
-    return linerangebyfname
-
-def getloglinerangerevs(repo, userrevs, opts):
-    """Return (revs, filematcher, hunksfilter).
-
-    "revs" are revisions obtained by processing "line-range" log options and
-    walking block ancestors of each specified file/line-range.
-
-    "filematcher(rev) -> match" is a factory function returning a match object
-    for a given revision for file patterns specified in --line-range option.
-    If neither --stat nor --patch options are passed, "filematcher" is None.
-
-    "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
-    returning a hunks filtering function.
-    If neither --stat nor --patch options are passed, "filterhunks" is None.
-    """
-    wctx = repo[None]
-
-    # Two-levels map of "rev -> file ctx -> [line range]".
-    linerangesbyrev = {}
-    for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
-        if fname not in wctx:
-            raise error.Abort(_('cannot follow file not in parent '
-                                'revision: "%s"') % fname)
-        fctx = wctx.filectx(fname)
-        for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
-            rev = fctx.introrev()
-            if rev not in userrevs:
-                continue
-            linerangesbyrev.setdefault(
-                rev, {}).setdefault(
-                    fctx.path(), []).append(linerange)
-
-    filematcher = None
-    hunksfilter = None
-    if opts.get('patch') or opts.get('stat'):
-
-        def nofilterhunksfn(fctx, hunks):
-            return hunks
-
-        def hunksfilter(rev):
-            fctxlineranges = linerangesbyrev.get(rev)
-            if fctxlineranges is None:
-                return nofilterhunksfn
-
-            def filterfn(fctx, hunks):
-                lineranges = fctxlineranges.get(fctx.path())
-                if lineranges is not None:
-                    for hr, lines in hunks:
-                        if hr is None: # binary
-                            yield hr, lines
-                            continue
-                        if any(mdiff.hunkinrange(hr[2:], lr)
-                               for lr in lineranges):
-                            yield hr, lines
-                else:
-                    for hunk in hunks:
-                        yield hunk
-
-            return filterfn
-
-        def filematcher(rev):
-            files = list(linerangesbyrev.get(rev, []))
-            return scmutil.matchfiles(repo, files)
-
-    revs = sorted(linerangesbyrev, reverse=True)
-
-    return revs, filematcher, hunksfilter
-
-def _graphnodeformatter(ui, displayer):
-    spec = ui.config('ui', 'graphnodetemplate')
-    if not spec:
-        return templatekw.showgraphnode  # fast path for "{graphnode}"
-
-    spec = templater.unquotestring(spec)
-    tres = formatter.templateresources(ui)
-    if isinstance(displayer, changeset_templater):
-        tres['cache'] = displayer.cache  # reuse cache of slow templates
-    templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
-                                    resources=tres)
-    def formatnode(repo, ctx):
-        props = {'ctx': ctx, 'repo': repo, 'revcache': {}}
-        return templ.render(props)
-    return formatnode
-
-def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
-                 filematcher=None, props=None):
-    props = props or {}
-    formatnode = _graphnodeformatter(ui, displayer)
-    state = graphmod.asciistate()
-    styles = state['styles']
-
-    # only set graph styling if HGPLAIN is not set.
-    if ui.plain('graph'):
-        # set all edge styles to |, the default pre-3.8 behaviour
-        styles.update(dict.fromkeys(styles, '|'))
-    else:
-        edgetypes = {
-            'parent': graphmod.PARENT,
-            'grandparent': graphmod.GRANDPARENT,
-            'missing': graphmod.MISSINGPARENT
-        }
-        for name, key in edgetypes.items():
-            # experimental config: experimental.graphstyle.*
-            styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
-                                    styles[key])
-            if not styles[key]:
-                styles[key] = None
-
-        # experimental config: experimental.graphshorten
-        state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
-
-    for rev, type, ctx, parents in dag:
-        char = formatnode(repo, ctx)
-        copies = None
-        if getrenamed and ctx.rev():
-            copies = []
-            for fn in ctx.files():
-                rename = getrenamed(fn, ctx.rev())
-                if rename:
-                    copies.append((fn, rename[0]))
-        revmatchfn = None
-        if filematcher is not None:
-            revmatchfn = filematcher(ctx.rev())
-        edges = edgefn(type, char, state, rev, parents)
-        firstedge = next(edges)
-        width = firstedge[2]
-        displayer.show(ctx, copies=copies, matchfn=revmatchfn,
-                       _graphwidth=width, **pycompat.strkwargs(props))
-        lines = displayer.hunk.pop(rev).split('\n')
-        if not lines[-1]:
-            del lines[-1]
-        displayer.flush(ctx)
-        for type, char, width, coldata in itertools.chain([firstedge], edges):
-            graphmod.ascii(ui, state, type, char, lines, coldata)
-            lines = []
-    displayer.close()
-
-def graphlog(ui, repo, revs, filematcher, opts):
-    # Parameters are identical to log command ones
-    revdag = graphmod.dagwalker(repo, revs)
-
-    getrenamed = None
-    if opts.get('copies'):
-        endrev = None
-        if opts.get('rev'):
-            endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
-        getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
-
-    ui.pager('log')
-    displayer = show_changeset(ui, repo, opts, buffered=True)
-    displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
-                 filematcher)
-
-def checkunsupportedgraphflags(pats, opts):
-    for op in ["newest_first"]:
-        if op in opts and opts[op]:
-            raise error.Abort(_("-G/--graph option is incompatible with --%s")
-                             % op.replace("_", "-"))
-
-def graphrevs(repo, nodes, opts):
-    limit = loglimit(opts)
-    nodes.reverse()
-    if limit is not None:
-        nodes = nodes[:limit]
-    return graphmod.nodes(repo, nodes)
-
 def add(ui, repo, match, prefix, explicitonly, **opts):
     join = lambda f: os.path.join(prefix, f)
     bad = []