Mercurial > public > mercurial-scm > hg-stable
diff hgext/fastannotate/support.py @ 39238:1ddb296e0dee
fastannotate: initial import from Facebook's hg-experimental
I made as few changes as I could to get the tests to pass, but this
was a bit involved due to some churn in the blame code since someone
last gave fastannotate any TLC.
There's still follow-up work here to rip out support for old versions
of hg and to integrate the protocol with modern standards.
Some performance numbers (all on my 2016 MacBook Pro with a 2.6Ghz i7):
Mercurial mercurial/manifest.py
traditional blame
time: real 1.050 secs (user 0.990+0.000 sys 0.060+0.000)
build cache
time: real 5.900 secs (user 5.720+0.000 sys 0.110+0.000)
fastannotate
time: real 0.120 secs (user 0.100+0.000 sys 0.020+0.000)
Mercurial mercurial/localrepo.py
traditional blame
time: real 3.330 secs (user 3.220+0.000 sys 0.070+0.000)
build cache
time: real 30.610 secs (user 30.190+0.000 sys 0.230+0.000)
fastannotate
time: real 0.180 secs (user 0.160+0.000 sys 0.020+0.000)
mozilla-central dom/ipc/ContentParent.cpp
traditional blame
time: real 7.640 secs (user 7.210+0.000 sys 0.380+0.000)
build cache
time: real 98.650 secs (user 97.000+0.000 sys 0.950+0.000)
fastannotate
time: real 1.580 secs (user 1.340+0.000 sys 0.240+0.000)
mozilla-central dom/base/nsDocument.cpp
traditional blame
time: real 17.110 secs (user 16.490+0.000 sys 0.500+0.000)
build cache
time: real 399.750 secs (user 394.520+0.000 sys 2.610+0.000)
fastannotate
time: real 1.780 secs (user 1.530+0.000 sys 0.240+0.000)
So building the cache is expensive (but might be faster with xdiff
enabled), but the blame results are *way* faster.
Differential Revision: https://phab.mercurial-scm.org/D3994
author | Augie Fackler <augie@google.com> |
---|---|
date | Mon, 30 Jul 2018 22:50:00 -0400 |
parents | |
children | 303dae0136b0 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/fastannotate/support.py Mon Jul 30 22:50:00 2018 -0400 @@ -0,0 +1,131 @@ +# Copyright 2016-present Facebook. All Rights Reserved. +# +# support: fastannotate support for hgweb, and filectx +# +# 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 + +from mercurial import ( + context as hgcontext, + dagop, + extensions, + hgweb, + patch, + util, +) + +from . import ( + context, + revmap, +) + +class _lazyfctx(object): + """delegates to fctx but do not construct fctx when unnecessary""" + + def __init__(self, repo, node, path): + self._node = node + self._path = path + self._repo = repo + + def node(self): + return self._node + + def path(self): + return self._path + + @util.propertycache + def _fctx(self): + return context.resolvefctx(self._repo, self._node, self._path) + + def __getattr__(self, name): + return getattr(self._fctx, name) + +def _convertoutputs(repo, annotated, contents): + """convert fastannotate outputs to vanilla annotate format""" + # fastannotate returns: [(nodeid, linenum, path)], [linecontent] + # convert to what fctx.annotate returns: [annotateline] + results = [] + fctxmap = {} + annotateline = dagop.annotateline + for i, (hsh, linenum, path) in enumerate(annotated): + if (hsh, path) not in fctxmap: + fctxmap[(hsh, path)] = _lazyfctx(repo, hsh, path) + # linenum: the user wants 1-based, we have 0-based. + lineno = linenum + 1 + fctx = fctxmap[(hsh, path)] + line = contents[i] + results.append(annotateline(fctx=fctx, lineno=lineno, text=line)) + return results + +def _getmaster(fctx): + """(fctx) -> str""" + return fctx._repo.ui.config('fastannotate', 'mainbranch') or 'default' + +def _doannotate(fctx, follow=True, diffopts=None): + """like the vanilla fctx.annotate, but do it via fastannotate, and make + the output format compatible with the vanilla fctx.annotate. + may raise Exception, and always return line numbers. + """ + master = _getmaster(fctx) + annotated = contents = None + + with context.fctxannotatecontext(fctx, follow, diffopts) as ac: + try: + annotated, contents = ac.annotate(fctx.rev(), master=master, + showpath=True, showlines=True) + except Exception: + ac.rebuild() # try rebuild once + fctx._repo.ui.debug('fastannotate: %s: rebuilding broken cache\n' + % fctx._path) + try: + annotated, contents = ac.annotate(fctx.rev(), master=master, + showpath=True, showlines=True) + except Exception: + raise + + assert annotated and contents + return _convertoutputs(fctx._repo, annotated, contents) + +def _hgwebannotate(orig, fctx, ui): + diffopts = patch.difffeatureopts(ui, untrusted=True, + section='annotate', whitespace=True) + return _doannotate(fctx, diffopts=diffopts) + +def _fctxannotate(orig, self, follow=False, linenumber=False, skiprevs=None, + diffopts=None): + if skiprevs: + # skiprevs is not supported yet + return orig(self, follow, linenumber, skiprevs=skiprevs, + diffopts=diffopts) + try: + return _doannotate(self, follow, diffopts) + except Exception as ex: + self._repo.ui.debug('fastannotate: falling back to the vanilla ' + 'annotate: %r\n' % ex) + return orig(self, follow=follow, skiprevs=skiprevs, + diffopts=diffopts) + +def _remotefctxannotate(orig, self, follow=False, skiprevs=None, diffopts=None): + # skipset: a set-like used to test if a fctx needs to be downloaded + skipset = None + with context.fctxannotatecontext(self, follow, diffopts) as ac: + skipset = revmap.revmap(ac.revmappath) + return orig(self, follow, skiprevs=skiprevs, diffopts=diffopts, + prefetchskip=skipset) + +def replacehgwebannotate(): + extensions.wrapfunction(hgweb.webutil, 'annotate', _hgwebannotate) + +def replacefctxannotate(): + extensions.wrapfunction(hgcontext.basefilectx, 'annotate', _fctxannotate) + +def replaceremotefctxannotate(): + try: + r = extensions.find('remotefilelog') + except KeyError: + return + else: + extensions.wrapfunction(r.remotefilectx.remotefilectx, 'annotate', + _remotefctxannotate)