##// END OF EJS Templates
churn: wrapped docstrings at 78 characters
Martin Geisler -
r9056:feb3445e default
parent child Browse files
Show More
@@ -1,174 +1,172
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 display statistics about repository history'''
9 '''command to display statistics about repository 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 '''histogram of changes to the repository
95 '''histogram of changes to the repository
96
96
97 This command will display a histogram representing the number
97 This command will display a histogram representing the number of changed
98 of changed lines or revisions, grouped according to the given
98 lines or revisions, grouped according to the given template. The default
99 template. The default template will group changes by author.
99 template will group changes by author. The --dateformat option may be used
100 The --dateformat option may be used to group the results by
100 to group the results by date instead.
101 date instead.
102
101
103 Statistics are based on the number of changed lines, or
102 Statistics are based on the number of changed lines, or alternatively the
104 alternatively the number of matching revisions if the
103 number of matching revisions if the --changesets option is specified.
105 --changesets option is specified.
106
104
107 Examples:
105 Examples:
108
106
109 # display count of changed lines for every committer
107 # display count of changed lines for every committer
110 hg churn -t '{author|email}'
108 hg churn -t '{author|email}'
111
109
112 # display daily activity graph
110 # display daily activity graph
113 hg churn -f '%H' -s -c
111 hg churn -f '%H' -s -c
114
112
115 # display activity of developers by month
113 # display activity of developers by month
116 hg churn -f '%Y-%m' -s -c
114 hg churn -f '%Y-%m' -s -c
117
115
118 # display count of lines changed in every year
116 # display count of lines changed in every year
119 hg churn -f '%Y' -s
117 hg churn -f '%Y' -s
120
118
121 It is possible to map alternate email addresses to a main address
119 It is possible to map alternate email addresses to a main address by
122 by providing a file using the following format:
120 providing a file using the following format:
123
121
124 <alias email> <actual email>
122 <alias email> <actual email>
125
123
126 Such a file may be specified with the --aliases option, otherwise a
124 Such a file may be specified with the --aliases option, otherwise a
127 .hgchurn file will be looked for in the working directory root.
125 .hgchurn file will be looked for in the working directory root.
128 '''
126 '''
129 def pad(s, l):
127 def pad(s, l):
130 return (s + " " * l)[:l]
128 return (s + " " * l)[:l]
131
129
132 amap = {}
130 amap = {}
133 aliases = opts.get('aliases')
131 aliases = opts.get('aliases')
134 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
132 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
135 aliases = repo.wjoin('.hgchurn')
133 aliases = repo.wjoin('.hgchurn')
136 if aliases:
134 if aliases:
137 for l in open(aliases, "r"):
135 for l in open(aliases, "r"):
138 l = l.strip()
136 l = l.strip()
139 alias, actual = l.split()
137 alias, actual = l.split()
140 amap[alias] = actual
138 amap[alias] = actual
141
139
142 rate = countrate(ui, repo, amap, *pats, **opts).items()
140 rate = countrate(ui, repo, amap, *pats, **opts).items()
143 if not rate:
141 if not rate:
144 return
142 return
145
143
146 sortkey = ((not opts.get('sort')) and (lambda x: -x[1]) or None)
144 sortkey = ((not opts.get('sort')) and (lambda x: -x[1]) or None)
147 rate.sort(key=sortkey)
145 rate.sort(key=sortkey)
148
146
149 maxcount = float(max([v for k, v in rate]))
147 maxcount = float(max([v for k, v in rate]))
150 maxname = max([len(k) for k, v in rate])
148 maxname = max([len(k) for k, v in rate])
151
149
152 ttywidth = util.termwidth()
150 ttywidth = util.termwidth()
153 ui.debug(_("assuming %i character terminal\n") % ttywidth)
151 ui.debug(_("assuming %i character terminal\n") % ttywidth)
154 width = ttywidth - maxname - 2 - 6 - 2 - 2
152 width = ttywidth - maxname - 2 - 6 - 2 - 2
155
153
156 for date, count in rate:
154 for date, count in rate:
157 print "%s %6d %s" % (pad(date, maxname), count,
155 print "%s %6d %s" % (pad(date, maxname), count,
158 "*" * int(count * width / maxcount))
156 "*" * int(count * width / maxcount))
159
157
160
158
161 cmdtable = {
159 cmdtable = {
162 "churn":
160 "churn":
163 (churn,
161 (churn,
164 [('r', 'rev', [], _('count rate for the specified revision or range')),
162 [('r', 'rev', [], _('count rate for the specified revision or range')),
165 ('d', 'date', '', _('count rate for revisions matching date spec')),
163 ('d', 'date', '', _('count rate for revisions matching date spec')),
166 ('t', 'template', '{author|email}', _('template to group changesets')),
164 ('t', 'template', '{author|email}', _('template to group changesets')),
167 ('f', 'dateformat', '',
165 ('f', 'dateformat', '',
168 _('strftime-compatible format for grouping by date')),
166 _('strftime-compatible format for grouping by date')),
169 ('c', 'changesets', False, _('count rate by number of changesets')),
167 ('c', 'changesets', False, _('count rate by number of changesets')),
170 ('s', 'sort', False, _('sort by key (default: sort by count)')),
168 ('s', 'sort', False, _('sort by key (default: sort by count)')),
171 ('', 'aliases', '', _('file with email aliases')),
169 ('', 'aliases', '', _('file with email aliases')),
172 ('', 'progress', None, _('show progress'))],
170 ('', 'progress', None, _('show progress'))],
173 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
171 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
174 }
172 }
General Comments 0
You need to be logged in to leave comments. Login now