##// END OF EJS Templates
churn: declare command using decorator
Gregory Szorc -
r21245:75c87200 default
parent child Browse files
Show More
@@ -1,205 +1,201
1 1 # churn.py - create a graph of revisions count grouped by template
2 2 #
3 3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''command to display statistics about repository history'''
10 10
11 11 from mercurial.i18n import _
12 12 from mercurial import patch, cmdutil, scmutil, util, templater, commands
13 13 from mercurial import encoding
14 14 import os
15 15 import time, datetime
16 16
17 cmdtable = {}
18 command = cmdutil.command(cmdtable)
17 19 testedwith = 'internal'
18 20
19 21 def maketemplater(ui, repo, tmpl):
20 22 tmpl = templater.parsestring(tmpl, quoted=False)
21 23 try:
22 24 t = cmdutil.changeset_templater(ui, repo, False, None, tmpl,
23 25 None, False)
24 26 except SyntaxError, inst:
25 27 raise util.Abort(inst.args[0])
26 28 return t
27 29
28 30 def changedlines(ui, repo, ctx1, ctx2, fns):
29 31 added, removed = 0, 0
30 32 fmatch = scmutil.matchfiles(repo, fns)
31 33 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
32 34 for l in diff.split('\n'):
33 35 if l.startswith("+") and not l.startswith("+++ "):
34 36 added += 1
35 37 elif l.startswith("-") and not l.startswith("--- "):
36 38 removed += 1
37 39 return (added, removed)
38 40
39 41 def countrate(ui, repo, amap, *pats, **opts):
40 42 """Calculate stats"""
41 43 if opts.get('dateformat'):
42 44 def getkey(ctx):
43 45 t, tz = ctx.date()
44 46 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
45 47 return date.strftime(opts['dateformat'])
46 48 else:
47 49 tmpl = opts.get('template', '{author|email}')
48 50 tmpl = maketemplater(ui, repo, tmpl)
49 51 def getkey(ctx):
50 52 ui.pushbuffer()
51 53 tmpl.show(ctx)
52 54 return ui.popbuffer()
53 55
54 56 state = {'count': 0}
55 57 rate = {}
56 58 df = False
57 59 if opts.get('date'):
58 60 df = util.matchdate(opts['date'])
59 61
60 62 m = scmutil.match(repo[None], pats, opts)
61 63 def prep(ctx, fns):
62 64 rev = ctx.rev()
63 65 if df and not df(ctx.date()[0]): # doesn't match date format
64 66 return
65 67
66 68 key = getkey(ctx).strip()
67 69 key = amap.get(key, key) # alias remap
68 70 if opts.get('changesets'):
69 71 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
70 72 else:
71 73 parents = ctx.parents()
72 74 if len(parents) > 1:
73 75 ui.note(_('revision %d is a merge, ignoring...\n') % (rev,))
74 76 return
75 77
76 78 ctx1 = parents[0]
77 79 lines = changedlines(ui, repo, ctx1, ctx, fns)
78 80 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
79 81
80 82 state['count'] += 1
81 83 ui.progress(_('analyzing'), state['count'], total=len(repo))
82 84
83 85 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
84 86 continue
85 87
86 88 ui.progress(_('analyzing'), None)
87 89
88 90 return rate
89 91
90 92
93 @command('churn',
94 [('r', 'rev', [],
95 _('count rate for the specified revision or range'), _('REV')),
96 ('d', 'date', '',
97 _('count rate for revisions matching date spec'), _('DATE')),
98 ('t', 'template', '{author|email}',
99 _('template to group changesets'), _('TEMPLATE')),
100 ('f', 'dateformat', '',
101 _('strftime-compatible format for grouping by date'), _('FORMAT')),
102 ('c', 'changesets', False, _('count rate by number of changesets')),
103 ('s', 'sort', False, _('sort by key (default: sort by count)')),
104 ('', 'diffstat', False, _('display added/removed lines separately')),
105 ('', 'aliases', '', _('file with email aliases'), _('FILE')),
106 ] + commands.walkopts,
107 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]"))
91 108 def churn(ui, repo, *pats, **opts):
92 109 '''histogram of changes to the repository
93 110
94 111 This command will display a histogram representing the number
95 112 of changed lines or revisions, grouped according to the given
96 113 template. The default template will group changes by author.
97 114 The --dateformat option may be used to group the results by
98 115 date instead.
99 116
100 117 Statistics are based on the number of changed lines, or
101 118 alternatively the number of matching revisions if the
102 119 --changesets option is specified.
103 120
104 121 Examples::
105 122
106 123 # display count of changed lines for every committer
107 124 hg churn -t "{author|email}"
108 125
109 126 # display daily activity graph
110 127 hg churn -f "%H" -s -c
111 128
112 129 # display activity of developers by month
113 130 hg churn -f "%Y-%m" -s -c
114 131
115 132 # display count of lines changed in every year
116 133 hg churn -f "%Y" -s
117 134
118 135 It is possible to map alternate email addresses to a main address
119 136 by providing a file using the following format::
120 137
121 138 <alias email> = <actual email>
122 139
123 140 Such a file may be specified with the --aliases option, otherwise
124 141 a .hgchurn file will be looked for in the working directory root.
125 142 Aliases will be split from the rightmost "=".
126 143 '''
127 144 def pad(s, l):
128 145 return s + " " * (l - encoding.colwidth(s))
129 146
130 147 amap = {}
131 148 aliases = opts.get('aliases')
132 149 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
133 150 aliases = repo.wjoin('.hgchurn')
134 151 if aliases:
135 152 for l in open(aliases, "r"):
136 153 try:
137 154 alias, actual = l.rsplit('=' in l and '=' or None, 1)
138 155 amap[alias.strip()] = actual.strip()
139 156 except ValueError:
140 157 l = l.strip()
141 158 if l:
142 159 ui.warn(_("skipping malformed alias: %s\n") % l)
143 160 continue
144 161
145 162 rate = countrate(ui, repo, amap, *pats, **opts).items()
146 163 if not rate:
147 164 return
148 165
149 166 if opts.get('sort'):
150 167 rate.sort()
151 168 else:
152 169 rate.sort(key=lambda x: (-sum(x[1]), x))
153 170
154 171 # Be careful not to have a zero maxcount (issue833)
155 172 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
156 173 maxname = max(len(k) for k, v in rate)
157 174
158 175 ttywidth = ui.termwidth()
159 176 ui.debug("assuming %i character terminal\n" % ttywidth)
160 177 width = ttywidth - maxname - 2 - 2 - 2
161 178
162 179 if opts.get('diffstat'):
163 180 width -= 15
164 181 def format(name, diffstat):
165 182 added, removed = diffstat
166 183 return "%s %15s %s%s\n" % (pad(name, maxname),
167 184 '+%d/-%d' % (added, removed),
168 185 ui.label('+' * charnum(added),
169 186 'diffstat.inserted'),
170 187 ui.label('-' * charnum(removed),
171 188 'diffstat.deleted'))
172 189 else:
173 190 width -= 6
174 191 def format(name, count):
175 192 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
176 193 '*' * charnum(sum(count)))
177 194
178 195 def charnum(count):
179 196 return int(round(count * width / maxcount))
180 197
181 198 for name, count in rate:
182 199 ui.write(format(name, count))
183 200
184
185 cmdtable = {
186 "churn":
187 (churn,
188 [('r', 'rev', [],
189 _('count rate for the specified revision or range'), _('REV')),
190 ('d', 'date', '',
191 _('count rate for revisions matching date spec'), _('DATE')),
192 ('t', 'template', '{author|email}',
193 _('template to group changesets'), _('TEMPLATE')),
194 ('f', 'dateformat', '',
195 _('strftime-compatible format for grouping by date'), _('FORMAT')),
196 ('c', 'changesets', False, _('count rate by number of changesets')),
197 ('s', 'sort', False, _('sort by key (default: sort by count)')),
198 ('', 'diffstat', False, _('display added/removed lines separately')),
199 ('', 'aliases', '',
200 _('file with email aliases'), _('FILE')),
201 ] + commands.walkopts,
202 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
203 }
204
205 201 commands.inferrepo += " churn"
General Comments 0
You need to be logged in to leave comments. Login now