##// END OF EJS Templates
i18n: the message should not contain '\r'...
Benoit Boissinot -
r8085:404a2c31 default
parent child Browse files
Show More
@@ -1,162 +1,162 b''
1 # churn.py - create a graph of revisions count grouped by template
1 # churn.py - create a graph of revisions count grouped by template
2 #
2 #
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
4 # Copyright 2008 Alexander Solovyov <piranha@piranha.org.ua>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8 '''command to show certain statistics about revision history'''
8 '''command to show certain statistics about revision history'''
9
9
10 from mercurial.i18n import _
10 from mercurial.i18n import _
11 from mercurial import patch, cmdutil, util, templater
11 from mercurial import patch, cmdutil, util, templater
12 import sys
12 import sys
13 import time, datetime
13 import time, datetime
14
14
15 def maketemplater(ui, repo, tmpl):
15 def maketemplater(ui, repo, tmpl):
16 tmpl = templater.parsestring(tmpl, quoted=False)
16 tmpl = templater.parsestring(tmpl, quoted=False)
17 try:
17 try:
18 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
18 t = cmdutil.changeset_templater(ui, repo, False, None, None, False)
19 except SyntaxError, inst:
19 except SyntaxError, inst:
20 raise util.Abort(inst.args[0])
20 raise util.Abort(inst.args[0])
21 t.use_template(tmpl)
21 t.use_template(tmpl)
22 return t
22 return t
23
23
24 def changedlines(ui, repo, ctx1, ctx2, fns):
24 def changedlines(ui, repo, ctx1, ctx2, fns):
25 lines = 0
25 lines = 0
26 fmatch = cmdutil.match(repo, pats=fns)
26 fmatch = cmdutil.match(repo, pats=fns)
27 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
27 diff = ''.join(patch.diff(repo, ctx1.node(), ctx2.node(), fmatch))
28 for l in diff.split('\n'):
28 for l in diff.split('\n'):
29 if (l.startswith("+") and not l.startswith("+++ ") or
29 if (l.startswith("+") and not l.startswith("+++ ") or
30 l.startswith("-") and not l.startswith("--- ")):
30 l.startswith("-") and not l.startswith("--- ")):
31 lines += 1
31 lines += 1
32 return lines
32 return lines
33
33
34 def countrate(ui, repo, amap, *pats, **opts):
34 def countrate(ui, repo, amap, *pats, **opts):
35 """Calculate stats"""
35 """Calculate stats"""
36 if opts.get('dateformat'):
36 if opts.get('dateformat'):
37 def getkey(ctx):
37 def getkey(ctx):
38 t, tz = ctx.date()
38 t, tz = ctx.date()
39 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
39 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
40 return date.strftime(opts['dateformat'])
40 return date.strftime(opts['dateformat'])
41 else:
41 else:
42 tmpl = opts.get('template', '{author|email}')
42 tmpl = opts.get('template', '{author|email}')
43 tmpl = maketemplater(ui, repo, tmpl)
43 tmpl = maketemplater(ui, repo, tmpl)
44 def getkey(ctx):
44 def getkey(ctx):
45 ui.pushbuffer()
45 ui.pushbuffer()
46 tmpl.show(ctx)
46 tmpl.show(ctx)
47 return ui.popbuffer()
47 return ui.popbuffer()
48
48
49 count = pct = 0
49 count = pct = 0
50 rate = {}
50 rate = {}
51 df = False
51 df = False
52 if opts.get('date'):
52 if opts.get('date'):
53 df = util.matchdate(opts['date'])
53 df = util.matchdate(opts['date'])
54
54
55 get = util.cachefunc(lambda r: repo[r].changeset())
55 get = util.cachefunc(lambda r: repo[r].changeset())
56 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
56 changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts)
57 for st, rev, fns in changeiter:
57 for st, rev, fns in changeiter:
58 if not st == 'add':
58 if not st == 'add':
59 continue
59 continue
60 if df and not df(get(rev)[2][0]): # doesn't match date format
60 if df and not df(get(rev)[2][0]): # doesn't match date format
61 continue
61 continue
62
62
63 ctx = repo[rev]
63 ctx = repo[rev]
64 key = getkey(ctx)
64 key = getkey(ctx)
65 key = amap.get(key, key) # alias remap
65 key = amap.get(key, key) # alias remap
66 if opts.get('changesets'):
66 if opts.get('changesets'):
67 rate[key] = rate.get(key, 0) + 1
67 rate[key] = rate.get(key, 0) + 1
68 else:
68 else:
69 parents = ctx.parents()
69 parents = ctx.parents()
70 if len(parents) > 1:
70 if len(parents) > 1:
71 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
71 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
72 continue
72 continue
73
73
74 ctx1 = parents[0]
74 ctx1 = parents[0]
75 lines = changedlines(ui, repo, ctx1, ctx, fns)
75 lines = changedlines(ui, repo, ctx1, ctx, fns)
76 rate[key] = rate.get(key, 0) + lines
76 rate[key] = rate.get(key, 0) + lines
77
77
78 if opts.get('progress'):
78 if opts.get('progress'):
79 count += 1
79 count += 1
80 newpct = int(100.0 * count / max(len(repo), 1))
80 newpct = int(100.0 * count / max(len(repo), 1))
81 if pct < newpct:
81 if pct < newpct:
82 pct = newpct
82 pct = newpct
83 ui.write(_("\rgenerating stats: %d%%") % pct)
83 ui.write("\r" + _("generating stats: %d%%") % pct)
84 sys.stdout.flush()
84 sys.stdout.flush()
85
85
86 if opts.get('progress'):
86 if opts.get('progress'):
87 ui.write("\r")
87 ui.write("\r")
88 sys.stdout.flush()
88 sys.stdout.flush()
89
89
90 return rate
90 return rate
91
91
92
92
93 def churn(ui, repo, *pats, **opts):
93 def churn(ui, repo, *pats, **opts):
94 '''graph count of revisions grouped by template
94 '''graph count of revisions grouped by template
95
95
96 Will graph count of changed lines or revisions grouped by template
96 Will graph count of changed lines or revisions grouped by template
97 or alternatively by date, if dateformat is used. In this case it
97 or alternatively by date, if dateformat is used. In this case it
98 will override template.
98 will override template.
99
99
100 By default statistics are counted for number of changed lines.
100 By default statistics are counted for number of changed lines.
101
101
102 Examples:
102 Examples:
103
103
104 # display count of changed lines for every committer
104 # display count of changed lines for every committer
105 hg churn -t '{author|email}'
105 hg churn -t '{author|email}'
106
106
107 # display daily activity graph
107 # display daily activity graph
108 hg churn -f '%H' -s -c
108 hg churn -f '%H' -s -c
109
109
110 # display activity of developers by month
110 # display activity of developers by month
111 hg churn -f '%Y-%m' -s -c
111 hg churn -f '%Y-%m' -s -c
112
112
113 # display count of lines changed in every year
113 # display count of lines changed in every year
114 hg churn -f '%Y' -s
114 hg churn -f '%Y' -s
115
115
116 The map file format used to specify aliases is fairly simple:
116 The map file format used to specify aliases is fairly simple:
117
117
118 <alias email> <actual email>'''
118 <alias email> <actual email>'''
119 def pad(s, l):
119 def pad(s, l):
120 return (s + " " * l)[:l]
120 return (s + " " * l)[:l]
121
121
122 amap = {}
122 amap = {}
123 aliases = opts.get('aliases')
123 aliases = opts.get('aliases')
124 if aliases:
124 if aliases:
125 for l in open(aliases, "r"):
125 for l in open(aliases, "r"):
126 l = l.strip()
126 l = l.strip()
127 alias, actual = l.split()
127 alias, actual = l.split()
128 amap[alias] = actual
128 amap[alias] = actual
129
129
130 rate = countrate(ui, repo, amap, *pats, **opts).items()
130 rate = countrate(ui, repo, amap, *pats, **opts).items()
131 if not rate:
131 if not rate:
132 return
132 return
133
133
134 sortfn = ((not opts.get('sort')) and (lambda a, b: cmp(b[1], a[1])) or None)
134 sortfn = ((not opts.get('sort')) and (lambda a, b: cmp(b[1], a[1])) or None)
135 rate.sort(sortfn)
135 rate.sort(sortfn)
136
136
137 maxcount = float(max([v for k, v in rate]))
137 maxcount = float(max([v for k, v in rate]))
138 maxname = max([len(k) for k, v in rate])
138 maxname = max([len(k) for k, v in rate])
139
139
140 ttywidth = util.termwidth()
140 ttywidth = util.termwidth()
141 ui.debug(_("assuming %i character terminal\n") % ttywidth)
141 ui.debug(_("assuming %i character terminal\n") % ttywidth)
142 width = ttywidth - maxname - 2 - 6 - 2 - 2
142 width = ttywidth - maxname - 2 - 6 - 2 - 2
143
143
144 for date, count in rate:
144 for date, count in rate:
145 print "%s %6d %s" % (pad(date, maxname), count,
145 print "%s %6d %s" % (pad(date, maxname), count,
146 "*" * int(count * width / maxcount))
146 "*" * int(count * width / maxcount))
147
147
148
148
149 cmdtable = {
149 cmdtable = {
150 "churn":
150 "churn":
151 (churn,
151 (churn,
152 [('r', 'rev', [], _('count rate for the specified revision or range')),
152 [('r', 'rev', [], _('count rate for the specified revision or range')),
153 ('d', 'date', '', _('count rate for revisions matching date spec')),
153 ('d', 'date', '', _('count rate for revisions matching date spec')),
154 ('t', 'template', '{author|email}', _('template to group changesets')),
154 ('t', 'template', '{author|email}', _('template to group changesets')),
155 ('f', 'dateformat', '',
155 ('f', 'dateformat', '',
156 _('strftime-compatible format for grouping by date')),
156 _('strftime-compatible format for grouping by date')),
157 ('c', 'changesets', False, _('count rate by number of changesets')),
157 ('c', 'changesets', False, _('count rate by number of changesets')),
158 ('s', 'sort', False, _('sort by key (default: sort by count)')),
158 ('s', 'sort', False, _('sort by key (default: sort by count)')),
159 ('', 'aliases', '', _('file with email aliases')),
159 ('', 'aliases', '', _('file with email aliases')),
160 ('', 'progress', None, _('show progress'))],
160 ('', 'progress', None, _('show progress'))],
161 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
161 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
162 }
162 }
General Comments 0
You need to be logged in to leave comments. Login now