##// END OF EJS Templates
churn: do not crash on empty lines in alias file
Ronny Pfannschmidt -
r12068:2e7647d2 stable
parent child Browse files
Show More
@@ -1,190 +1,192
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, util, templater, commands
13 13 import os
14 14 import time, datetime
15 15
16 16 def maketemplater(ui, repo, tmpl):
17 17 tmpl = templater.parsestring(tmpl, quoted=False)
18 18 try:
19 19 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
20 20 except SyntaxError, inst:
21 21 raise util.Abort(inst.args[0])
22 22 t.use_template(tmpl)
23 23 return t
24 24
25 25 def changedlines(ui, repo, ctx1, ctx2, fns):
26 26 added, removed = 0, 0
27 27 fmatch = cmdutil.matchfiles(repo, fns)
28 28 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
29 29 for l in diff.split('\n'):
30 30 if l.startswith("+") and not l.startswith("+++ "):
31 31 added += 1
32 32 elif l.startswith("-") and not l.startswith("--- "):
33 33 removed += 1
34 34 return (added, removed)
35 35
36 36 def countrate(ui, repo, amap, *pats, **opts):
37 37 """Calculate stats"""
38 38 if opts.get('dateformat'):
39 39 def getkey(ctx):
40 40 t, tz = ctx.date()
41 41 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
42 42 return date.strftime(opts['dateformat'])
43 43 else:
44 44 tmpl = opts.get('template', '{author|email}')
45 45 tmpl = maketemplater(ui, repo, tmpl)
46 46 def getkey(ctx):
47 47 ui.pushbuffer()
48 48 tmpl.show(ctx)
49 49 return ui.popbuffer()
50 50
51 51 state = {'count': 0}
52 52 rate = {}
53 53 df = False
54 54 if opts.get('date'):
55 55 df = util.matchdate(opts['date'])
56 56
57 57 m = cmdutil.match(repo, pats, opts)
58 58 def prep(ctx, fns):
59 59 rev = ctx.rev()
60 60 if df and not df(ctx.date()[0]): # doesn't match date format
61 61 return
62 62
63 63 key = getkey(ctx)
64 64 key = amap.get(key, key) # alias remap
65 65 if opts.get('changesets'):
66 66 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
67 67 else:
68 68 parents = ctx.parents()
69 69 if len(parents) > 1:
70 70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
71 71 return
72 72
73 73 ctx1 = parents[0]
74 74 lines = changedlines(ui, repo, ctx1, ctx, fns)
75 75 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
76 76
77 77 state['count'] += 1
78 78 ui.progress(_('analyzing'), state['count'], total=len(repo))
79 79
80 80 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
81 81 continue
82 82
83 83 ui.progress(_('analyzing'), None)
84 84
85 85 return rate
86 86
87 87
88 88 def churn(ui, repo, *pats, **opts):
89 89 '''histogram of changes to the repository
90 90
91 91 This command will display a histogram representing the number
92 92 of changed lines or revisions, grouped according to the given
93 93 template. The default template will group changes by author.
94 94 The --dateformat option may be used to group the results by
95 95 date instead.
96 96
97 97 Statistics are based on the number of changed lines, or
98 98 alternatively the number of matching revisions if the
99 99 --changesets option is specified.
100 100
101 101 Examples::
102 102
103 103 # display count of changed lines for every committer
104 104 hg churn -t '{author|email}'
105 105
106 106 # display daily activity graph
107 107 hg churn -f '%H' -s -c
108 108
109 109 # display activity of developers by month
110 110 hg churn -f '%Y-%m' -s -c
111 111
112 112 # display count of lines changed in every year
113 113 hg churn -f '%Y' -s
114 114
115 115 It is possible to map alternate email addresses to a main address
116 116 by providing a file using the following format::
117 117
118 118 <alias email> = <actual email>
119 119
120 120 Such a file may be specified with the --aliases option, otherwise
121 121 a .hgchurn file will be looked for in the working directory root.
122 122 '''
123 123 def pad(s, l):
124 124 return (s + " " * l)[:l]
125 125
126 126 amap = {}
127 127 aliases = opts.get('aliases')
128 128 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
129 129 aliases = repo.wjoin('.hgchurn')
130 130 if aliases:
131 131 for l in open(aliases, "r"):
132 if not l.strip():
133 continue
132 134 alias, actual = l.split('=' in l and '=' or None, 1)
133 135 amap[alias.strip()] = actual.strip()
134 136
135 137 rate = countrate(ui, repo, amap, *pats, **opts).items()
136 138 if not rate:
137 139 return
138 140
139 141 sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
140 142 rate.sort(key=sortkey)
141 143
142 144 # Be careful not to have a zero maxcount (issue833)
143 145 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
144 146 maxname = max(len(k) for k, v in rate)
145 147
146 148 ttywidth = util.termwidth()
147 149 ui.debug("assuming %i character terminal\n" % ttywidth)
148 150 width = ttywidth - maxname - 2 - 2 - 2
149 151
150 152 if opts.get('diffstat'):
151 153 width -= 15
152 154 def format(name, (added, removed)):
153 155 return "%s %15s %s%s\n" % (pad(name, maxname),
154 156 '+%d/-%d' % (added, removed),
155 157 ui.label('+' * charnum(added),
156 158 'diffstat.inserted'),
157 159 ui.label('-' * charnum(removed),
158 160 'diffstat.deleted'))
159 161 else:
160 162 width -= 6
161 163 def format(name, count):
162 164 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
163 165 '*' * charnum(sum(count)))
164 166
165 167 def charnum(count):
166 168 return int(round(count * width / maxcount))
167 169
168 170 for name, count in rate:
169 171 ui.write(format(name, count))
170 172
171 173
172 174 cmdtable = {
173 175 "churn":
174 176 (churn,
175 177 [('r', 'rev', [],
176 178 _('count rate for the specified revision or range'), _('REV')),
177 179 ('d', 'date', '',
178 180 _('count rate for revisions matching date spec'), _('DATE')),
179 181 ('t', 'template', '{author|email}',
180 182 _('template to group changesets'), _('TEMPLATE')),
181 183 ('f', 'dateformat', '',
182 184 _('strftime-compatible format for grouping by date'), _('FORMAT')),
183 185 ('c', 'changesets', False, _('count rate by number of changesets')),
184 186 ('s', 'sort', False, _('sort by key (default: sort by count)')),
185 187 ('', 'diffstat', False, _('display added/removed lines separately')),
186 188 ('', 'aliases', '',
187 189 _('file with email aliases'), _('FILE')),
188 190 ] + commands.walkopts,
189 191 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
190 192 }
@@ -1,79 +1,80
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "churn=" >> $HGRCPATH
5 5
6 6 echo % create test repository
7 7 hg init repo
8 8 cd repo
9 9 echo a > a
10 10 hg ci -Am adda -u user1 -d 6:00
11 11 echo b >> a
12 12 echo b > b
13 13 hg ci -m changeba -u user2 -d 9:00 a
14 14 hg ci -Am addb -u user2 -d 9:30
15 15 echo c >> a
16 16 echo c >> b
17 17 echo c > c
18 18 hg ci -m changeca -u user3 -d 12:00 a
19 19 hg ci -m changecb -u user3 -d 12:15 b
20 20 hg ci -Am addc -u user3 -d 12:30
21 21 mkdir -p d/e
22 22 echo abc > d/e/f1.txt
23 23 hg ci -Am "add d/e/f1.txt" -u user1 -d 12:45 d/e/f1.txt
24 24 mkdir -p d/g
25 25 echo def > d/g/f2.txt
26 26 hg ci -Am "add d/g/f2.txt" -u user1 -d 13:00 d/g/f2.txt
27 27
28 28 echo % churn separate directories
29 29 cd d
30 30 hg churn e
31 31 echo % churn all
32 32 hg churn
33 33 echo % churn excluding one dir
34 34 hg churn -X e
35 35 echo % churn up to rev 2
36 36 hg churn -r :2
37 37 cd ..
38 38 echo % churn with aliases
39 39 cat > ../aliases <<EOF
40 40 user1 alias1
41
41 42 user3 alias3
42 43 EOF
43 44 hg churn --aliases ../aliases
44 45 echo % churn with .hgchurn
45 46 mv ../aliases .hgchurn
46 47 hg churn
47 48 rm .hgchurn
48 49 echo % churn with column specifier
49 50 COLUMNS=40 hg churn
50 51 echo % churn by hour
51 52 hg churn -f '%H' -s
52 53
53 54 echo % churn with separated added/removed lines
54 55 hg rm d/g/f2.txt
55 56 hg ci -Am "removed d/g/f2.txt" -u user1 -d 14:00 d/g/f2.txt
56 57 hg churn --diffstat
57 58 echo % churn --diffstat with color
58 59 hg --config extensions.color= churn --config color.mode=ansi \
59 60 --diffstat --color=always
60 61
61 62 echo % changeset number churn
62 63 hg churn -c
63 64
64 65 echo 'with space = no-space' >> ../aliases
65 66 echo a >> a
66 67 hg commit -m a -u 'with space' -d 15:00
67 68 echo % churn with space in alias
68 69 hg churn --aliases ../aliases -r tip
69 70
70 71 cd ..
71 72
72 73 # issue 833: ZeroDivisionError
73 74 hg init issue-833
74 75 cd issue-833
75 76 touch foo
76 77 hg ci -Am foo
77 78 # this was failing with a ZeroDivisionError
78 79 hg churn
79 80 cd ..
General Comments 0
You need to be logged in to leave comments. Login now