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