2008-10-03 01:07:38 +04:00
|
|
|
# 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>
|
2008-10-03 01:07:38 +04:00
|
|
|
# Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
|
2006-07-27 03:42:56 +04:00
|
|
|
#
|
2009-04-26 03:08:54 +04:00
|
|
|
# This software may be used and distributed according to the terms of the
|
2010-01-20 07:20:08 +03:00
|
|
|
# GNU General Public License version 2 or any later version.
|
2009-04-26 03:25:53 +04:00
|
|
|
|
2009-06-24 15:42:02 +04:00
|
|
|
'''command to display statistics about repository history'''
|
2006-07-27 03:42:56 +04:00
|
|
|
|
2016-02-10 04:50:45 +03:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
|
|
|
import datetime
|
2010-04-14 12:58:10 +04:00
|
|
|
import os
|
2016-02-10 04:50:45 +03:00
|
|
|
import time
|
|
|
|
|
|
|
|
from mercurial.i18n import _
|
|
|
|
from mercurial import (
|
|
|
|
cmdutil,
|
|
|
|
encoding,
|
|
|
|
patch,
|
2017-10-22 21:31:16 +03:00
|
|
|
pycompat,
|
2016-01-09 17:07:20 +03:00
|
|
|
registrar,
|
2016-02-10 04:50:45 +03:00
|
|
|
scmutil,
|
|
|
|
util,
|
|
|
|
)
|
2007-07-19 20:39:51 +04:00
|
|
|
|
2014-05-05 08:46:24 +04:00
|
|
|
cmdtable = {}
|
2016-01-09 17:07:20 +03:00
|
|
|
command = registrar.command(cmdtable)
|
2016-08-23 18:26:08 +03:00
|
|
|
# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
|
2015-04-28 23:44:37 +03:00
|
|
|
# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
|
|
|
|
# be specifying the version(s) of Mercurial they are tested with, or
|
|
|
|
# leave the attribute unspecified.
|
2016-08-23 18:26:08 +03:00
|
|
|
testedwith = 'ships-with-hg-core'
|
2012-05-15 23:37:49 +04:00
|
|
|
|
2009-03-24 23:19:03 +03:00
|
|
|
def changedlines(ui, repo, ctx1, ctx2, fns):
|
2009-10-29 21:50:24 +03:00
|
|
|
added, removed = 0, 0
|
2011-05-13 23:58:24 +04:00
|
|
|
fmatch = scmutil.matchfiles(repo, fns)
|
2009-03-24 23:19:03 +03:00
|
|
|
diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
|
2008-10-03 01:07:38 +04:00
|
|
|
for l in diff.split('\n'):
|
2009-10-29 21:50:24 +03:00
|
|
|
if l.startswith("+") and not l.startswith("+++ "):
|
|
|
|
added += 1
|
|
|
|
elif l.startswith("-") and not l.startswith("--- "):
|
|
|
|
removed += 1
|
|
|
|
return (added, removed)
|
2008-10-03 01:07:38 +04:00
|
|
|
|
|
|
|
def countrate(ui, repo, amap, *pats, **opts):
|
|
|
|
"""Calculate stats"""
|
2017-10-22 21:31:16 +03:00
|
|
|
opts = pycompat.byteskwargs(opts)
|
2008-10-03 01:07:38 +04:00
|
|
|
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:
|
2015-02-24 19:37:07 +03:00
|
|
|
tmpl = opts.get('oldtemplate') or opts.get('template')
|
2017-04-22 12:42:03 +03:00
|
|
|
tmpl = cmdutil.makelogtemplater(ui, repo, tmpl)
|
2008-10-03 01:07:38 +04:00
|
|
|
def getkey(ctx):
|
|
|
|
ui.pushbuffer()
|
2008-11-14 15:59:25 +03:00
|
|
|
tmpl.show(ctx)
|
2008-10-03 01:07:38 +04:00
|
|
|
return ui.popbuffer()
|
|
|
|
|
2010-03-11 17:11:01 +03:00
|
|
|
state = {'count': 0}
|
2008-10-03 01:07:38 +04:00
|
|
|
rate = {}
|
|
|
|
df = False
|
|
|
|
if opts.get('date'):
|
|
|
|
df = util.matchdate(opts['date'])
|
|
|
|
|
2011-06-19 01:52:51 +04:00
|
|
|
m = scmutil.match(repo[None], pats, opts)
|
2009-10-30 01:07:51 +03:00
|
|
|
def prep(ctx, fns):
|
2009-10-26 02:43:59 +03:00
|
|
|
rev = ctx.rev()
|
2009-08-20 10:34:22 +04:00
|
|
|
if df and not df(ctx.date()[0]): # doesn't match date format
|
2009-10-30 01:07:51 +03:00
|
|
|
return
|
2006-09-04 00:25:41 +04:00
|
|
|
|
2011-04-29 18:34:52 +04:00
|
|
|
key = getkey(ctx).strip()
|
2008-10-03 01:07:38 +04:00
|
|
|
key = amap.get(key, key) # alias remap
|
2008-10-09 01:14:20 +04:00
|
|
|
if opts.get('changesets'):
|
2009-10-30 14:40:23 +03:00
|
|
|
rate[key] = (rate.get(key, (0,))[0] + 1, 0)
|
2008-10-09 01:14:20 +04:00
|
|
|
else:
|
2008-10-03 01:07:38 +04:00
|
|
|
parents = ctx.parents()
|
|
|
|
if len(parents) > 1:
|
2012-06-12 16:18:18 +04:00
|
|
|
ui.note(_('revision %d is a merge, ignoring...\n') % (rev,))
|
2009-10-30 01:07:51 +03:00
|
|
|
return
|
2008-10-03 01:07:38 +04:00
|
|
|
|
|
|
|
ctx1 = parents[0]
|
2009-03-24 23:19:03 +03:00
|
|
|
lines = changedlines(ui, repo, ctx1, ctx, fns)
|
2009-10-29 21:50:24 +03:00
|
|
|
rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
|
2008-10-03 01:07:38 +04:00
|
|
|
|
2010-03-11 17:11:01 +03:00
|
|
|
state['count'] += 1
|
2016-03-11 17:30:29 +03:00
|
|
|
ui.progress(_('analyzing'), state['count'], total=len(repo),
|
|
|
|
unit=_('revisions'))
|
2006-09-04 00:30:07 +04:00
|
|
|
|
2009-10-30 03:03:16 +03:00
|
|
|
for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
|
2009-10-30 01:07:51 +03:00
|
|
|
continue
|
|
|
|
|
2010-03-15 20:40:00 +03:00
|
|
|
ui.progress(_('analyzing'), None)
|
2006-09-04 00:30:07 +04:00
|
|
|
|
2008-10-03 01:07:38 +04:00
|
|
|
return rate
|
2006-07-27 03:42:56 +04:00
|
|
|
|
2008-06-12 13:33:47 +04:00
|
|
|
|
2014-05-05 08:46:24 +04:00
|
|
|
@command('churn',
|
|
|
|
[('r', 'rev', [],
|
2014-10-24 21:50:00 +04:00
|
|
|
_('count rate for the specified revision or revset'), _('REV')),
|
2014-05-05 08:46:24 +04:00
|
|
|
('d', 'date', '',
|
|
|
|
_('count rate for revisions matching date spec'), _('DATE')),
|
2015-02-24 19:37:07 +03:00
|
|
|
('t', 'oldtemplate', '',
|
|
|
|
_('template to group changesets (DEPRECATED)'), _('TEMPLATE')),
|
|
|
|
('T', 'template', '{author|email}',
|
2014-05-05 08:46:24 +04:00
|
|
|
_('template to group changesets'), _('TEMPLATE')),
|
|
|
|
('f', 'dateformat', '',
|
|
|
|
_('strftime-compatible format for grouping by date'), _('FORMAT')),
|
|
|
|
('c', 'changesets', False, _('count rate by number of changesets')),
|
|
|
|
('s', 'sort', False, _('sort by key (default: sort by count)')),
|
|
|
|
('', 'diffstat', False, _('display added/removed lines separately')),
|
|
|
|
('', 'aliases', '', _('file with email aliases'), _('FILE')),
|
2017-05-14 10:19:47 +03:00
|
|
|
] + cmdutil.walkopts,
|
2014-05-05 09:24:38 +04:00
|
|
|
_("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"),
|
|
|
|
inferrepo=True)
|
2008-10-09 01:14:20 +04:00
|
|
|
def churn(ui, repo, *pats, **opts):
|
2009-06-17 00:24:46 +04:00
|
|
|
'''histogram of changes to the repository
|
2008-06-12 13:33:47 +04:00
|
|
|
|
2009-07-26 03:41:31 +04:00
|
|
|
This command will display a histogram representing the number
|
|
|
|
of changed lines or revisions, grouped according to the given
|
|
|
|
template. The default template will group changes by author.
|
|
|
|
The --dateformat option may be used to group the results by
|
|
|
|
date instead.
|
2008-10-03 01:07:38 +04:00
|
|
|
|
2009-07-26 03:41:31 +04:00
|
|
|
Statistics are based on the number of changed lines, or
|
|
|
|
alternatively the number of matching revisions if the
|
|
|
|
--changesets option is specified.
|
2008-10-03 01:07:38 +04:00
|
|
|
|
2009-07-23 02:22:05 +04:00
|
|
|
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
|
2017-05-09 06:05:01 +03:00
|
|
|
hg churn -T "{author|email}"
|
2008-10-03 01:07:38 +04:00
|
|
|
|
|
|
|
# display daily activity graph
|
2013-10-24 20:14:18 +04:00
|
|
|
hg churn -f "%H" -s -c
|
2008-10-03 01:07:38 +04:00
|
|
|
|
|
|
|
# display activity of developers by month
|
2013-10-24 20:14:18 +04:00
|
|
|
hg churn -f "%Y-%m" -s -c
|
2008-10-03 01:07:38 +04:00
|
|
|
|
|
|
|
# display count of lines changed in every year
|
2013-10-24 20:14:18 +04:00
|
|
|
hg churn -f "%Y" -s
|
2008-10-09 01:14:20 +04:00
|
|
|
|
2009-07-26 03:41:31 +04:00
|
|
|
It is possible to map alternate email addresses to a main address
|
|
|
|
by providing a file using the following format::
|
2009-06-19 15:47:50 +04:00
|
|
|
|
2010-06-02 16:28:45 +04:00
|
|
|
<alias email> = <actual email>
|
2009-04-29 22:03:54 +04:00
|
|
|
|
2009-07-26 03:41:31 +04:00
|
|
|
Such a file may be specified with the --aliases option, otherwise
|
|
|
|
a .hgchurn file will be looked for in the working directory root.
|
2013-07-17 18:40:40 +04:00
|
|
|
Aliases will be split from the rightmost "=".
|
2009-04-29 22:03:54 +04:00
|
|
|
'''
|
2006-07-27 03:42:56 +04:00
|
|
|
def pad(s, l):
|
2014-04-19 17:11:25 +04:00
|
|
|
return s + " " * (l - encoding.colwidth(s))
|
2006-10-01 21:26:33 +04:00
|
|
|
|
2006-07-27 03:42:56 +04:00
|
|
|
amap = {}
|
2017-10-22 21:31:16 +03:00
|
|
|
aliases = opts.get(r'aliases')
|
2009-04-29 22:03:54 +04:00
|
|
|
if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
|
|
|
|
aliases = repo.wjoin('.hgchurn')
|
2006-07-27 03:42:56 +04:00
|
|
|
if aliases:
|
2008-06-27 03:49:45 +04:00
|
|
|
for l in open(aliases, "r"):
|
2010-08-30 00:46:00 +04:00
|
|
|
try:
|
2013-07-17 18:40:40 +04:00
|
|
|
alias, actual = l.rsplit('=' in l and '=' or None, 1)
|
2010-08-30 00:46:00 +04:00
|
|
|
amap[alias.strip()] = actual.strip()
|
|
|
|
except ValueError:
|
|
|
|
l = l.strip()
|
|
|
|
if l:
|
2012-03-08 23:35:27 +04:00
|
|
|
ui.warn(_("skipping malformed alias: %s\n") % l)
|
2010-08-29 12:54:22 +04:00
|
|
|
continue
|
2006-08-19 09:08:48 +04:00
|
|
|
|
2008-10-03 01:07:38 +04:00
|
|
|
rate = countrate(ui, repo, amap, *pats, **opts).items()
|
|
|
|
if not rate:
|
2007-12-03 02:04:16 +03:00
|
|
|
return
|
2008-03-09 20:34:55 +03:00
|
|
|
|
2017-10-22 21:31:16 +03:00
|
|
|
if opts.get(r'sort'):
|
2012-12-12 05:38:14 +04:00
|
|
|
rate.sort()
|
|
|
|
else:
|
|
|
|
rate.sort(key=lambda x: (-sum(x[1]), x))
|
2008-10-03 01:07:38 +04:00
|
|
|
|
2009-08-24 14:47:44 +04:00
|
|
|
# Be careful not to have a zero maxcount (issue833)
|
2009-10-29 21:50:24 +03:00
|
|
|
maxcount = float(max(sum(v) for k, v in rate)) or 1.0
|
2009-08-26 15:07:27 +04:00
|
|
|
maxname = max(len(k) for k, v in rate)
|
2006-07-27 03:42:56 +04:00
|
|
|
|
2010-10-10 19:06:36 +04:00
|
|
|
ttywidth = ui.termwidth()
|
2009-09-19 03:15:38 +04:00
|
|
|
ui.debug("assuming %i character terminal\n" % ttywidth)
|
2009-10-29 21:50:24 +03:00
|
|
|
width = ttywidth - maxname - 2 - 2 - 2
|
|
|
|
|
2017-10-22 21:31:16 +03:00
|
|
|
if opts.get(r'diffstat'):
|
2009-10-29 21:50:24 +03:00
|
|
|
width -= 15
|
2010-07-02 02:27:03 +04:00
|
|
|
def format(name, diffstat):
|
|
|
|
added, removed = diffstat
|
2010-06-09 00:52:41 +04:00
|
|
|
return "%s %15s %s%s\n" % (pad(name, maxname),
|
|
|
|
'+%d/-%d' % (added, removed),
|
2010-04-03 00:22:10 +04:00
|
|
|
ui.label('+' * charnum(added),
|
|
|
|
'diffstat.inserted'),
|
|
|
|
ui.label('-' * charnum(removed),
|
|
|
|
'diffstat.deleted'))
|
2009-10-29 21:50:24 +03:00
|
|
|
else:
|
|
|
|
width -= 6
|
|
|
|
def format(name, count):
|
2010-06-09 00:52:41 +04:00
|
|
|
return "%s %6d %s\n" % (pad(name, maxname), sum(count),
|
|
|
|
'*' * charnum(sum(count)))
|
2009-10-29 21:50:24 +03:00
|
|
|
|
|
|
|
def charnum(count):
|
2010-01-25 09:05:27 +03:00
|
|
|
return int(round(count * width / maxcount))
|
2008-10-03 01:07:38 +04:00
|
|
|
|
2009-10-29 21:50:24 +03:00
|
|
|
for name, count in rate:
|
2010-06-09 00:52:41 +04:00
|
|
|
ui.write(format(name, count))
|