sapling/hgext/churn.py

183 lines
5.7 KiB
Python
Raw Normal View History

# churn.py - create a graph of revisions count grouped by template
2006-07-27 03:42:56 +04:00
#
# Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
# Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
2006-07-27 03:42:56 +04:00
#
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
'''command to show certain statistics about revision history'''
2006-07-27 03:42:56 +04:00
from mercurial.i18n import _
from mercurial import patch, cmdutil, util, templater
import os, sys
import time, datetime
def get_tty_width():
if 'COLUMNS' in os.environ:
try:
return int(os.environ['COLUMNS'])
except ValueError:
pass
try:
import termios, array, fcntl
for dev in (sys.stdout, sys.stdin):
try:
fd = dev.fileno()
if not os.isatty(fd):
continue
arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
return array.array('h', arri)[1]
except ValueError:
pass
except ImportError:
pass
return 80
2006-07-27 03:42:56 +04:00
def maketemplater(ui, repo, tmpl):
tmpl = templater.parsestring(tmpl, quoted=False)
try:
t = cmdutil.changeset_templater(ui, repo, False, None, False)
except SyntaxError, inst:
raise util.Abort(inst.args[0])
t.use_template(tmpl)
return t
def changedlines(ui, repo, ctx1, ctx2):
lines = 0
diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node()))
for l in diff.split('\n'):
if (l.startswith("+") and not l.startswith("+++ ") or
l.startswith("-") and not l.startswith("--- ")):
lines += 1
return lines
def countrate(ui, repo, amap, *pats, **opts):
"""Calculate stats"""
if opts.get('dateformat'):
def getkey(ctx):
t, tz = ctx.date()
date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
return date.strftime(opts['dateformat'])
else:
tmpl = opts.get('template', '{author|email}')
tmpl = maketemplater(ui, repo, tmpl)
def getkey(ctx):
ui.pushbuffer()
tmpl.show(changenode=ctx.node())
return ui.popbuffer()
count = pct = 0
rate = {}
df = False
if opts.get('date'):
df = util.matchdate(opts['date'])
get = util.cachefunc(lambda r: repo[r].changeset())
changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
for st, rev, fns in changeiter:
if not st == 'add':
continue
if df and not df(get(rev)[2][0]): # doesn't match date format
2006-09-04 00:30:07 +04:00
continue
ctx = repo[rev]
key = getkey(ctx)
key = amap.get(key, key) # alias remap
2008-10-09 01:14:20 +04:00
if opts.get('changesets'):
rate[key] = rate.get(key, 0) + 1
else:
parents = ctx.parents()
if len(parents) > 1:
ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
continue
ctx1 = parents[0]
lines = changedlines(ui, repo, ctx1, ctx)
rate[key] = rate.get(key, 0) + lines
if opts.get('progress'):
count += 1
newpct = int(100.0 * count / max(len(repo), 1))
if pct < newpct:
pct = newpct
ui.write(_("\rGenerating stats: %d%%") % pct)
2006-09-04 00:30:07 +04:00
sys.stdout.flush()
if opts.get('progress'):
ui.write("\r")
2006-09-04 00:30:07 +04:00
sys.stdout.flush()
return rate
2006-07-27 03:42:56 +04:00
2008-10-09 01:14:20 +04:00
def churn(ui, repo, *pats, **opts):
'''Graph count of revisions grouped by template
2008-10-09 01:14:20 +04:00
Will graph count of changed lines or revisions grouped by template or
alternatively by date, if dateformat is used. In this case it will override
template.
2008-10-09 01:14:20 +04:00
By default statistics are counted for number of changed lines.
Examples:
2006-10-01 21:26:33 +04:00
2008-10-09 01:14:20 +04:00
# display count of changed lines for every committer
hg churn -t '{author|email}'
# display daily activity graph
2008-10-09 01:14:20 +04:00
hg churn -f '%H' -s -c
# display activity of developers by month
2008-10-09 01:14:20 +04:00
hg churn -f '%Y-%m' -s -c
# display count of lines changed in every year
2008-10-09 01:14:20 +04:00
hg churn -f '%Y' -s
The map file format used to specify aliases is fairly simple:
<alias email> <actual email>'''
2006-07-27 03:42:56 +04:00
def pad(s, l):
return (s + " " * l)[:l]
2006-10-01 21:26:33 +04:00
2006-07-27 03:42:56 +04:00
amap = {}
2006-08-19 09:08:48 +04:00
aliases = opts.get('aliases')
2006-07-27 03:42:56 +04:00
if aliases:
for l in open(aliases, "r"):
l = l.strip()
alias, actual = l.split()
amap[alias] = actual
2006-08-19 09:08:48 +04:00
rate = countrate(ui, repo, amap, *pats, **opts).items()
if not rate:
2007-12-03 02:04:16 +03:00
return
sortfn = ((not opts.get('sort')) and (lambda a, b: cmp(b[1], a[1])) or None)
rate.sort(sortfn)
maxcount = float(max([v for k, v in rate]))
maxname = max([len(k) for k, v in rate])
2006-07-27 03:42:56 +04:00
ttywidth = get_tty_width()
ui.debug(_("assuming %i character terminal\n") % ttywidth)
width = ttywidth - maxname - 2 - 6 - 2 - 2
for date, count in rate:
print "%s %6d %s" % (pad(date, maxname), count,
"*" * int(count * width / maxcount))
2006-07-27 03:42:56 +04:00
cmdtable = {
2008-10-09 01:14:20 +04:00
"churn":
(churn,
[('r', 'rev', [], _('count rate for the specified revision or range')),
('d', 'date', '', _('count rate for revs matching date spec')),
('t', 'template', '{author|email}', _('template to group changesets')),
('f', 'dateformat', '',
_('strftime-compatible format for grouping by date')),
2008-10-09 01:14:20 +04:00
('c', 'changesets', False, _('count rate by number of changesets')),
('s', 'sort', False, _('sort by key (default: sort by count)')),
('', 'aliases', '', _('file with email aliases')),
('', 'progress', None, _('show progress'))],
2008-10-18 18:51:26 +04:00
_("hg churn [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
2006-07-27 03:42:56 +04:00
}