##// END OF EJS Templates
fileset: use ctx1.status(ctx2) instead of repo.status(ctx1, ctx2)...
fileset: use ctx1.status(ctx2) instead of repo.status(ctx1, ctx2) Differential Revision: https://phab.mercurial-scm.org/D3999

File last commit:

r38589:2523c020 default
r38795:af5c0c93 default
Show More
logcmdutil.py
912 lines | 33.0 KiB | text/x-python | PythonLexer
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 # logcmdutil.py - utility for log-like commands
#
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from __future__ import absolute_import
import itertools
import os
from .i18n import _
from .node import (
nullid,
)
from . import (
dagop,
error,
formatter,
graphmod,
match as matchmod,
mdiff,
patch,
pathutil,
pycompat,
revset,
revsetlang,
scmutil,
smartset,
templatekw,
templater,
util,
)
Yuya Nishihara
stringutil: bulk-replace call sites to point to new module...
r37102 from .utils import (
dateutil,
stringutil,
)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def getlimit(opts):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 """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 diffordiffstat(ui, repo, diffopts, node1, node2, match,
changes=None, stat=False, fp=None, prefix='',
root='', listsubrepos=False, hunksfilterfn=None):
'''show diff or diffstat.'''
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()
Joerg Sonnenberger
diff: improve ui.write performance when not coloring on Windows...
r35979
Boris Feld
diff: use `context.diff` to produce diff...
r38589 chunks = repo[node2].diff(repo[node1], match, changes, opts=diffopts,
prefix=prefix, relroot=relroot,
hunksfilterfn=hunksfilterfn)
Joerg Sonnenberger
diff: improve ui.write performance when not coloring on Windows...
r35979
if fp is not None or ui.canwritewithoutlabels():
Yuya Nishihara
diff: remove fp.write() wrapper which drops label argument...
r36025 out = fp or ui
Joerg Sonnenberger
diff: improve ui.write performance when not coloring on Windows...
r35979 if stat:
Gregory Szorc
py3: preserve chunks as an iterable of bytes...
r36132 chunks = [patch.diffstat(util.iterlines(chunks), width=width)]
Joerg Sonnenberger
diff: improve ui.write performance when not coloring on Windows...
r35979 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
Yuya Nishihara
diff: remove fp.write() wrapper which drops label argument...
r36025 out.write(chunk)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 else:
Joerg Sonnenberger
diff: improve ui.write performance when not coloring on Windows...
r35979 if stat:
chunks = patch.diffstatui(util.iterlines(chunks), width=width)
else:
chunks = patch.difflabel(lambda chunks, **kwargs: chunks, chunks,
opts=diffopts)
if ui.canbatchlabeledwrites():
def gen():
for chunk, label in chunks:
yield ui.label(chunk, label=label)
for chunk in util.filechunkiter(util.chunkbuffer(gen())):
Yuya Nishihara
diff: remove fp.write() wrapper which drops label argument...
r36025 ui.write(chunk)
Joerg Sonnenberger
diff: improve ui.write performance when not coloring on Windows...
r35979 else:
for chunk, label in chunks:
Yuya Nishihara
diff: remove fp.write() wrapper which drops label argument...
r36025 ui.write(chunk, label=label)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
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)
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 class changesetdiffer(object):
"""Generate diff of changeset with pre-configured filtering functions"""
def _makefilematcher(self, ctx):
return scmutil.matchall(ctx.repo())
def _makehunksfilter(self, ctx):
return None
def showdiff(self, ui, ctx, diffopts, stat=False):
repo = ctx.repo()
node = ctx.node()
prev = ctx.p1().node()
diffordiffstat(ui, repo, diffopts, prev, node,
match=self._makefilematcher(ctx), stat=stat,
hunksfilterfn=self._makehunksfilter(ctx))
Yuya Nishihara
logcmdutil: rename classes and functions to conform to our coding style (API)...
r35904 def changesetlabels(ctx):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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)
Yuya Nishihara
logcmdutil: rename classes and functions to conform to our coding style (API)...
r35904 class changesetprinter(object):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 '''show changeset information when templating not requested.'''
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 self.ui = ui
self.repo = repo
self.buffered = buffered
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 self._differ = differ or changesetdiffer()
Yuya Nishihara
log: cache diffopts instance...
r37858 self._diffopts = patch.diffallopts(ui, diffopts)
Yuya Nishihara
log: consume --stat/patch options at constructor of changesetprinter...
r37859 self._includestat = diffopts and diffopts.get('stat')
self._includediff = diffopts and diffopts.get('patch')
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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)
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 def show(self, ctx, copies=None, **props):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 props = pycompat.byteskwargs(props)
if self.buffered:
self.ui.pushbuffer(labeled=True)
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 self._show(ctx, copies, props)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 self.hunk[ctx.rev()] = self.ui.popbuffer()
else:
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 self._show(ctx, copies, props)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 def _show(self, ctx, copies, props):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 '''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),
Yuya Nishihara
logcmdutil: rename classes and functions to conform to our coding style (API)...
r35904 label=changesetlabels(ctx))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
# 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()
Gregory Szorc
manifest: define and implement rev() on manifestlog...
r38573 mrev = self.repo.manifestlog.rev(mnode)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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')
Boris Feld
util: extract all date-related utils in utils/dateutil module...
r36625 self.ui.write(columns['date'] % dateutil.datestr(ctx.date()),
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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()):
Yuya Nishihara
stringutil: bulk-replace call sites to point to new module...
r37102 self.ui.write(columns['extra']
% (key, stringutil.escapestr(value)),
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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")
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 self._showpatch(ctx)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
def _showobsfate(self, ctx):
Yuya Nishihara
log: do not invoke templatekw.showobsfate() as a function...
r36534 # TODO: do not depend on templater
tres = formatter.templateresources(self.repo.ui, self.repo)
t = formatter.maketemplater(self.repo.ui, '{join(obsfate, "\n")}',
defaults=templatekw.keywords,
resources=tres)
Yuya Nishihara
templater: switch 'revcache' based on new mapping items...
r37121 obsfate = t.renderdefault({'ctx': ctx}).splitlines()
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
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
'''
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 def _showpatch(self, ctx):
Yuya Nishihara
log: consume --stat/patch options at constructor of changesetprinter...
r37859 if self._includestat:
Yuya Nishihara
log: cache diffopts instance...
r37858 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
Yuya Nishihara
log: consume --stat/patch options at constructor of changesetprinter...
r37859 if self._includestat and self._includediff:
Yuya Nishihara
logcmdutil: unindent diff generator of changesetprinter...
r36021 self.ui.write("\n")
Yuya Nishihara
log: consume --stat/patch options at constructor of changesetprinter...
r37859 if self._includediff:
Yuya Nishihara
log: cache diffopts instance...
r37858 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
Yuya Nishihara
log: consume --stat/patch options at constructor of changesetprinter...
r37859 if self._includestat or self._includediff:
Yuya Nishihara
logcmdutil: unindent diff generator of changesetprinter...
r36021 self.ui.write("\n")
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: pass formatter to jsonchangeset as argument...
r37791 class changesetformatter(changesetprinter):
"""Format changeset information by generic formatter"""
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: pass formatter to jsonchangeset as argument...
r37791 def __init__(self, ui, repo, fm, differ=None, diffopts=None,
buffered=False):
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
Yuya Nishihara
log: cache diffopts instance...
r37858 self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
Yuya Nishihara
logcmdutil: pass formatter to jsonchangeset as argument...
r37791 self._fm = fm
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
def close(self):
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 self._fm.end()
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 def _show(self, ctx, copies, props):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 '''show a single changeset or file revision'''
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 fm = self._fm
fm.startitem()
# TODO: maybe this should be wdirrev/wdirnode?
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 rev = ctx.rev()
if rev is None:
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 hexnode = None
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 else:
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 hexnode = fm.hexfunc(ctx.node())
fm.data(rev=rev,
node=hexnode)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
if self.ui.quiet:
return
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 fm.data(branch=ctx.branch(),
phase=ctx.phasestr(),
user=ctx.user(),
date=fm.formatdate(ctx.date()),
desc=ctx.description(),
bookmarks=fm.formatlist(ctx.bookmarks(), name='bookmark'),
tags=fm.formatlist(ctx.tags(), name='tag'),
parents=fm.formatlist([fm.hexfunc(c.node())
for c in ctx.parents()], name='node'))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
if self.ui.debugflag:
if rev is None:
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 hexnode = None
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 else:
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 hexnode = fm.hexfunc(ctx.manifestnode())
fm.data(manifest=hexnode,
extra=fm.formatdict(ctx.extra()))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
files = ctx.p1().status(ctx)
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 fm.data(modified=fm.formatlist(files[0], name='file'),
added=fm.formatlist(files[1], name='file'),
removed=fm.formatlist(files[2], name='file'))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
elif self.ui.verbose:
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 fm.data(files=fm.formatlist(ctx.files(), name='file'))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 if copies:
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 fm.data(copies=fm.formatdict(copies,
key='name', value='source'))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
log: consume --stat/patch options at constructor of changesetprinter...
r37859 if self._includestat:
Yuya Nishihara
logcmdutil: unindent diff generator of changesetprinter...
r36021 self.ui.pushbuffer()
Yuya Nishihara
log: cache diffopts instance...
r37858 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 fm.data(diffstat=self.ui.popbuffer())
Yuya Nishihara
log: consume --stat/patch options at constructor of changesetprinter...
r37859 if self._includediff:
Yuya Nishihara
logcmdutil: unindent diff generator of changesetprinter...
r36021 self.ui.pushbuffer()
Yuya Nishihara
log: cache diffopts instance...
r37858 self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
Yuya Nishihara
logcmdutil: rewrite jsonchangeset printer to be backed by jsonformatter...
r37790 fm.data(diff=self.ui.popbuffer())
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: rename classes and functions to conform to our coding style (API)...
r35904 class changesettemplater(changesetprinter):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 '''format changeset information.
Note: there are a variety of convenience functions to build a
Yuya Nishihara
logcmdutil: rename classes and functions to conform to our coding style (API)...
r35904 changesettemplater for common cases. See functions such as:
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 maketemplater, changesetdisplayer, buildcommittemplate, or other
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 functions that use changesest_templater.
'''
# Arguments before "buffered" used to be positional. Consider not
# adding/removing arguments before "buffered" to not break callers.
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 def __init__(self, ui, repo, tmplspec, differ=None, diffopts=None,
buffered=False):
changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
Yuya Nishihara
log: do no expect templateresources() returning a dict...
r37090 # tres is shared with _graphnodeformatter()
self._tresources = tres = formatter.templateresources(ui, repo)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 self.t = formatter.loadtemplater(ui, tmplspec,
defaults=templatekw.keywords,
resources=tres,
cache=templatekw.defaulttempl)
self._counter = itertools.count()
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']:
Yuya Nishihara
templater: factor out helper that renders named template as string...
r37004 self.ui.write(self.t.render(self._parts['docheader'], {}))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
def close(self):
if self._parts['docfooter']:
if not self.footer:
self.footer = ""
Yuya Nishihara
templater: factor out helper that renders named template as string...
r37004 self.footer += self.t.render(self._parts['docfooter'], {})
Yuya Nishihara
logcmdutil: rename classes and functions to conform to our coding style (API)...
r35904 return super(changesettemplater, self).close()
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 def _show(self, ctx, copies, props):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 '''show a single changeset or file revision'''
props = props.copy()
props['ctx'] = ctx
props['index'] = index = next(self._counter)
props['revcache'] = {'copies': copies}
# 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:
Yuya Nishihara
templater: factor out helper that renders named template as string...
r37004 self.ui.write(self.t.render(self._parts['separator'], {}))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
# write header
if self._parts['header']:
Yuya Nishihara
templater: factor out helper that renders named template as string...
r37004 h = self.t.render(self._parts['header'], props)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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]
Yuya Nishihara
templater: factor out helper that renders named template as string...
r37004 self.ui.write(self.t.render(key, props))
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 self._showpatch(ctx)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
if self._parts['footer']:
if not self.footer:
Yuya Nishihara
templater: factor out helper that renders named template as string...
r37004 self.footer = self.t.render(self._parts['footer'], props)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def templatespec(tmpl, mapfile):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 if mapfile:
return formatter.templatespec('changeset', tmpl, mapfile)
else:
return formatter.templatespec('', tmpl, None)
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def _lookuptemplate(ui, tmpl, style):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 """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:
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 return templatespec(templater.unquotestring(tmpl), None)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 return templatespec(None, mapfile)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
if not tmpl:
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 return templatespec(None, None)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
return formatter.lookuptemplate(ui, 'changeset', tmpl)
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def maketemplater(ui, repo, tmpl, buffered=False):
Yuya Nishihara
logcmdutil: rename classes and functions to conform to our coding style (API)...
r35904 """Create a changesettemplater from a literal template 'tmpl'
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 byte-string."""
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 spec = templatespec(tmpl, None)
Yuya Nishihara
logcmdutil: rename classes and functions to conform to our coding style (API)...
r35904 return changesettemplater(ui, repo, spec, buffered=buffered)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 def changesetdisplayer(ui, repo, opts, differ=None, buffered=False):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 """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,
Yuya Nishihara
logcmdutil: rename classes and functions to conform to our coding style (API)...
r35904 regular display via changesetprinter() is done.
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 """
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 postargs = (differ, opts, buffered)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 if opts.get('template') == 'json':
Yuya Nishihara
logcmdutil: pass formatter to jsonchangeset as argument...
r37791 fm = ui.formatter('log', opts)
return changesetformatter(ui, repo, fm, *postargs)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 spec = _lookuptemplate(ui, opts.get('template'), opts.get('style'))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
if not spec.ref and not spec.tmpl and not spec.mapfile:
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 return changesetprinter(ui, repo, *postargs)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 return changesettemplater(ui, repo, spec, *postargs)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def _makematcher(repo, revs, pats, opts):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 """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 = {}
Yuya Nishihara
log: pass ctx to makefilematcher() and makehunksfilter() functions...
r36019 def filematcher(ctx):
return scmutil.matchfiles(repo, fcache.get(ctx.rev(), []))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
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
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def _makenofollowfilematcher(repo, pats, opts):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 '''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'),
}
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def _makerevset(repo, match, pats, slowpath, opts):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 """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
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def _initialrevs(repo, opts):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 """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
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def getrevs(repo, pats, opts):
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 """Return (revs, differ) where revs is a smartset
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 differ is a changesetdiffer with pre-configured file matcher.
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 """
follow = opts.get('follow') or opts.get('follow_first')
followfirst = opts.get('follow_first')
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 limit = getlimit(opts)
revs = _initialrevs(repo, opts)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 if not revs:
return smartset.baseset(), None
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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:
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 filematcher = _makenofollowfilematcher(repo, pats, opts)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 if filematcher is None:
Yuya Nishihara
log: pass ctx to makefilematcher() and makehunksfilter() functions...
r36019 def filematcher(ctx):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 return match
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 expr = _makerevset(repo, match, pats, slowpath, opts)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 if opts.get('graph') and opts.get('rev'):
# User-specified revs might be unsorted, but don't sort before
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 # _makerevset because it might depend on the order of revs
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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)
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024
differ = changesetdiffer()
differ._makefilematcher = filematcher
return revs, differ
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def _parselinerangeopt(repo, opts):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 """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
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 def getlinerangerevs(repo, userrevs, opts):
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 """Return (revs, differ).
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
"revs" are revisions obtained by processing "line-range" log options and
walking block ancestors of each specified file/line-range.
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 "differ" is a changesetdiffer with pre-configured file matcher and hunks
filter.
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 """
wctx = repo[None]
# Two-levels map of "rev -> file ctx -> [line range]".
linerangesbyrev = {}
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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)
Yuya Nishihara
logcmdutil: create hunksfilter and filematcher even if no diff option given...
r36022 def nofilterhunksfn(fctx, hunks):
return hunks
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: create hunksfilter and filematcher even if no diff option given...
r36022 def hunksfilter(ctx):
fctxlineranges = linerangesbyrev.get(ctx.rev())
if fctxlineranges is None:
return nofilterhunksfn
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: create hunksfilter and filematcher even if no diff option given...
r36022 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
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: create hunksfilter and filematcher even if no diff option given...
r36022 return filterfn
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
logcmdutil: create hunksfilter and filematcher even if no diff option given...
r36022 def filematcher(ctx):
files = list(linerangesbyrev.get(ctx.rev(), []))
return scmutil.matchfiles(repo, files)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
revs = sorted(linerangesbyrev, reverse=True)
Yuya Nishihara
log: pack filematcher and hunksfilter into changesetdiffer object...
r36024 differ = changesetdiffer()
differ._makefilematcher = filematcher
differ._makehunksfilter = hunksfilter
return revs, differ
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
def _graphnodeformatter(ui, displayer):
spec = ui.config('ui', 'graphnodetemplate')
if not spec:
Yuya Nishihara
templatekw: extract non-templatekw function as getgraphnode()...
r36530 return templatekw.getgraphnode # fast path for "{graphnode}"
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
spec = templater.unquotestring(spec)
Yuya Nishihara
logcmdutil: rename classes and functions to conform to our coding style (API)...
r35904 if isinstance(displayer, changesettemplater):
Yuya Nishihara
templater: convert resources to a table of callables for future extension...
r36997 # reuse cache of slow templates
Yuya Nishihara
log: do no expect templateresources() returning a dict...
r37090 tres = displayer._tresources
else:
tres = formatter.templateresources(ui)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
resources=tres)
def formatnode(repo, ctx):
Yuya Nishihara
templater: switch 'revcache' based on new mapping items...
r37121 props = {'ctx': ctx, 'repo': repo}
Yuya Nishihara
templater: rename .render(mapping) to .renderdefault(mapping) (API)...
r37003 return templ.renderdefault(props)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 return formatnode
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None, props=None):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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:
Martin von Zweigbergk
templatekw: make getrenamed() return only filename, not nodeid...
r38185 copies.append((fn, rename))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 edges = edgefn(type, char, state, rev, parents)
firstedge = next(edges)
width = firstedge[2]
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 displayer.show(ctx, copies=copies,
Yuya Nishihara
templatekw: simply override {graphwidth} function by mapping variable
r36459 graphwidth=width, **pycompat.strkwargs(props))
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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()
Yuya Nishihara
log: factor out function to feed revisions to displayer
r36216 def displaygraphrevs(ui, repo, revs, displayer, getrenamed):
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 revdag = graphmod.dagwalker(repo, revs)
Yuya Nishihara
logcmdutil: hold makefilematcher/makehunksfilter() by changesetpriner (API)...
r36020 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903
Yuya Nishihara
log: factor out function to feed revisions to displayer
r36216 def displayrevs(ui, repo, revs, displayer, getrenamed):
for rev in revs:
ctx = repo[rev]
copies = None
if getrenamed is not None and rev:
copies = []
for fn in ctx.files():
rename = getrenamed(fn, rev)
if rename:
Martin von Zweigbergk
templatekw: make getrenamed() return only filename, not nodeid...
r38185 copies.append((fn, rename))
Yuya Nishihara
log: factor out function to feed revisions to displayer
r36216 displayer.show(ctx, copies=copies)
displayer.flush(ctx)
displayer.close()
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 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):
Yuya Nishihara
logcmdutil: drop redundant "log" from function names (API)...
r35905 limit = getlimit(opts)
Yuya Nishihara
cmdutil: split functions of log-like commands to new module (API)...
r35903 nodes.reverse()
if limit is not None:
nodes = nodes[:limit]
return graphmod.nodes(repo, nodes)