fastannotate: implement the command

Summary:
This diff adds the `fastannotate` command, which puts all components
(formatter, annotatecontext - linelog, revmap) together.

Note that `fastannotate.mainbranch` needs to be carefully chosen to avoid
rebuilds. If `mainbranch` moves backwards, the `fastannotate` command
can fail because it could not move linelog backwards and thus cannot
update linelog incrementally.

Test Plan: Code Review. The `.t` file is coming in the next diff.

Reviewers: #sourcecontrol, stash

Reviewed By: stash

Subscribers: stash, mjpieters

Differential Revision: https://phabricator.intern.facebook.com/D3836670

Signature: t1:3836670:1474016576:b323b62db3bfd69bff88f6911abbe26075734737
This commit is contained in:
Jun Wu 2016-09-08 17:59:01 +01:00
parent 316cae170e
commit 96e56210ff
2 changed files with 161 additions and 0 deletions

43
fastannotate/__init__.py Normal file
View File

@ -0,0 +1,43 @@
# Copyright 2016-present Facebook. All Rights Reserved.
#
# fastannotate: faster annotate implementation using linelog
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""yet another annotate implementation that might be faster
The fastannotate extension provides a 'fastannotate' command that makes
use of the linelog data structure as a cache layer and is expected to
be faster than the vanilla 'annotate' if the cache is present.
::
[fastannotate]
# specify the main branch head. the internal linelog will only contain
# the linear (ignoring p2) "mainbranch". since linelog cannot move
# backwards without a rebuild, this should be something that always moves
# forward, usually it is "master" or "@".
mainbranch = master
# use unfiltered repo for better performance
unfilteredrepo = True
"""
from __future__ import absolute_import
from fastannotate import commands
from mercurial import (
cmdutil,
)
testedwith = 'internal'
cmdtable = {}
command = cmdutil.command(cmdtable)
command('^fastannotate|fastblame|fa',
**commands.fastannotatecommandargs
)(commands.fastannotate)

118
fastannotate/commands.py Normal file
View File

@ -0,0 +1,118 @@
# Copyright 2016-present Facebook. All Rights Reserved.
#
# commands: fastannotate commands
#
# 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 fastannotate import (
context as facontext,
error as faerror,
formatter as faformatter,
)
from mercurial import (
commands,
error,
scmutil,
)
from mercurial.i18n import _
fastannotatecommandargs = {
'options': [
('r', 'rev', '.', _('annotate the specified revision'), _('REV')),
('u', 'user', None, _('list the author (long with -v)')),
('f', 'file', None, _('list the filename')),
('d', 'date', None, _('list the date (short with -q)')),
('n', 'number', None, _('list the revision number (default)')),
('c', 'changeset', None, _('list the changeset')),
('l', 'line-number', None, _('show line number at the first '
'appearance')),
('h', 'no-content', None, _('do not show file content')),
('', 'no-follow', None, _("don't follow copies and renames")),
('', 'linear', None, _('enforce linear history, ignore second parent '
'of merges (faster)')),
('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')),
('', 'rebuild', None, _('rebuild cache even if it exists '
'(EXPERIMENTAL)')),
] + commands.walkopts,
'synopsis': _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
'inferrepo': True,
}
def fastannotate(ui, repo, *pats, **opts):
"""show changeset information by line for each file
List changes in files, showing the revision id responsible for each line.
This command is useful for discovering when a change was made and by whom.
If you include --file, --user, or --date, the revision number is suppressed
unless you also include --number.
This command uses an implementation different from the vanilla annotate
command, which may produce slightly different (while still reasonable)
output for some cases.
For the best performance, use -c, -l, avoid -u, -d, -n. Use --linear
and --no-content to make it even faster.
Returns 0 on success.
"""
if not pats:
raise error.Abort(_('at least one filename or pattern is required'))
# performance hack: filtered repo can be slow. unfilter by default.
if ui.configbool('fastannotate', 'unfilteredrepo', True):
repo = repo.unfiltered()
rev = opts.get('rev', '.')
rebuild = opts.get('rebuild', False)
ctx = scmutil.revsingle(repo, rev)
m = scmutil.match(ctx, pats, opts)
aopts = facontext.annotateopts(
followmerge=not opts.get('linear', False),
followrename=not opts.get('no_follow', False))
if not any(opts.get(s)
for s in ['user', 'date', 'file', 'number', 'changeset']):
# default 'number' for compatibility. but fastannotate is more
# efficient with "changeset", "line-number" and "no-content".
for name in ui.configlist('fastannotate', 'defaultformat', ['number']):
opts[name] = True
formatter = faformatter.defaultformatter(ui, repo, opts)
showlines = not bool(opts.get('no_content'))
showpath = opts.get('file', False)
# find the head of the main (master) branch
masterrev = ui.config('fastannotate', 'mainbranch')
if masterrev:
master = lambda: scmutil.revsingle(repo, masterrev).rev()
else:
master = rev
for path in ctx.walk(m):
result = lines = None
while True:
try:
with facontext.annotatecontext(repo, path, aopts, rebuild) as a:
result = a.annotate(rev, master=master, showpath=showpath,
showlines=showlines)
break
except faerror.CannotReuseError: # happens if master moves backwards
if rebuild: # give up since we have tried rebuild alreadyraise
raise
else: # try a second time rebuilding the cache (slow)
rebuild = True
continue
if showlines:
result, lines = result
formatter.write(result, lines)