hgext/fastannotate/support.py
author Matt Harbison <matt_harbison@yahoo.com>
Wed, 09 Jan 2019 16:02:05 -0500
changeset 41180 69804c040a04
parent 39213 303dae0136b0
child 41365 876494fd967d
permissions -rw-r--r--
convert: don't drop commits that are empty in the source when using --filemap I ran into this when using `hg lfconvert --to-normal` (which uses the filemap class internally), and saw that commits with nothing but a branch change were dropped. We could put in an option that only lfconvert uses internally. But silently dropping anything other than a commit where all changes were excluded seems unintended. For example, there's a message in mercurial_sink.putcommit() if it drops an empty commit. (And the reason that isn't kicking in here is because lfconvert isn't passing --filemap, so the self.filemapmode conditional there is always False.) The naive change of `return not files` broke test-convert-filemap.t, so this is a little more elaborate than needed for converting from largefiles.

# 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)