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