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