]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/hgext/churn.py
hgwebfs: write headers individually, so they are not limited by webfs iounit (thanks...
[plan9front.git] / sys / lib / python / hgext / churn.py
1 # churn.py - create a graph of revisions count grouped by template
2 #
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
5 #
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2, incorporated herein by reference.
8
9 '''command to display statistics about repository history'''
10
11 from mercurial.i18n import _
12 from mercurial import patch, cmdutil, util, templater
13 import sys, os
14 import time, datetime
15
16 def maketemplater(ui, repo, tmpl):
17     tmpl = templater.parsestring(tmpl, quoted=False)
18     try:
19         t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
20     except SyntaxError, inst:
21         raise util.Abort(inst.args[0])
22     t.use_template(tmpl)
23     return t
24
25 def changedlines(ui, repo, ctx1, ctx2, fns):
26     lines = 0
27     fmatch = cmdutil.matchfiles(repo, fns)
28     diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
29     for l in diff.split('\n'):
30         if (l.startswith("+") and not l.startswith("+++ ") or
31             l.startswith("-") and not l.startswith("--- ")):
32             lines += 1
33     return lines
34
35 def countrate(ui, repo, amap, *pats, **opts):
36     """Calculate stats"""
37     if opts.get('dateformat'):
38         def getkey(ctx):
39             t, tz = ctx.date()
40             date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
41             return date.strftime(opts['dateformat'])
42     else:
43         tmpl = opts.get('template', '{author|email}')
44         tmpl = maketemplater(ui, repo, tmpl)
45         def getkey(ctx):
46             ui.pushbuffer()
47             tmpl.show(ctx)
48             return ui.popbuffer()
49
50     count = pct = 0
51     rate = {}
52     df = False
53     if opts.get('date'):
54         df = util.matchdate(opts['date'])
55
56     get = util.cachefunc(lambda r: repo[r].changeset())
57     changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
58     for st, rev, fns in changeiter:
59         if not st == 'add':
60             continue
61         if df and not df(get(rev)[2][0]): # doesn't match date format
62             continue
63
64         ctx = repo[rev]
65         key = getkey(ctx)
66         key = amap.get(key, key) # alias remap
67         if opts.get('changesets'):
68             rate[key] = rate.get(key, 0) + 1
69         else:
70             parents = ctx.parents()
71             if len(parents) > 1:
72                 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
73                 continue
74
75             ctx1 = parents[0]
76             lines = changedlines(ui, repo, ctx1, ctx, fns)
77             rate[key] = rate.get(key, 0) + lines
78
79         if opts.get('progress'):
80             count += 1
81             newpct = int(100.0 * count / max(len(repo), 1))
82             if pct < newpct:
83                 pct = newpct
84                 ui.write("\r" + _("generating stats: %d%%") % pct)
85                 sys.stdout.flush()
86
87     if opts.get('progress'):
88         ui.write("\r")
89         sys.stdout.flush()
90
91     return rate
92
93
94 def churn(ui, repo, *pats, **opts):
95     '''histogram of changes to the repository
96
97     This command will display a histogram representing the number
98     of changed lines or revisions, grouped according to the given
99     template. The default template will group changes by author.
100     The --dateformat option may be used to group the results by
101     date instead.
102
103     Statistics are based on the number of changed lines, or
104     alternatively the number of matching revisions if the
105     --changesets option is specified.
106
107     Examples::
108
109       # display count of changed lines for every committer
110       hg churn -t '{author|email}'
111
112       # display daily activity graph
113       hg churn -f '%H' -s -c
114
115       # display activity of developers by month
116       hg churn -f '%Y-%m' -s -c
117
118       # display count of lines changed in every year
119       hg churn -f '%Y' -s
120
121     It is possible to map alternate email addresses to a main address
122     by providing a file using the following format::
123
124       <alias email> <actual email>
125
126     Such a file may be specified with the --aliases option, otherwise
127     a .hgchurn file will be looked for in the working directory root.
128     '''
129     def pad(s, l):
130         return (s + " " * l)[:l]
131
132     amap = {}
133     aliases = opts.get('aliases')
134     if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
135         aliases = repo.wjoin('.hgchurn')
136     if aliases:
137         for l in open(aliases, "r"):
138             l = l.strip()
139             alias, actual = l.split()
140             amap[alias] = actual
141
142     rate = countrate(ui, repo, amap, *pats, **opts).items()
143     if not rate:
144         return
145
146     sortkey = ((not opts.get('sort')) and (lambda x: -x[1]) or None)
147     rate.sort(key=sortkey)
148
149     maxcount = float(max([v for k, v in rate]))
150     maxname = max([len(k) for k, v in rate])
151
152     ttywidth = util.termwidth()
153     ui.debug(_("assuming %i character terminal\n") % ttywidth)
154     width = ttywidth - maxname - 2 - 6 - 2 - 2
155
156     for date, count in rate:
157         print "%s %6d %s" % (pad(date, maxname), count,
158                              "*" * int(count * width / maxcount))
159
160
161 cmdtable = {
162     "churn":
163         (churn,
164          [('r', 'rev', [], _('count rate for the specified revision or range')),
165           ('d', 'date', '', _('count rate for revisions matching date spec')),
166           ('t', 'template', '{author|email}', _('template to group changesets')),
167           ('f', 'dateformat', '',
168               _('strftime-compatible format for grouping by date')),
169           ('c', 'changesets', False, _('count rate by number of changesets')),
170           ('s', 'sort', False, _('sort by key (default: sort by count)')),
171           ('', 'aliases', '', _('file with email aliases')),
172           ('', 'progress', None, _('show progress'))],
173          _("hg churn [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
174 }