##// END OF EJS Templates
churn: use ui.progress instead of --progress
Eric Eisner -
r10647:4ba41eeb default
parent child Browse files
Show More
@@ -1,193 +1,186
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 or any later version.
7 # GNU General Public License version 2 or any later version.
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 added, removed = 0, 0
26 added, removed = 0, 0
27 fmatch = cmdutil.matchfiles(repo, fns)
27 fmatch = cmdutil.matchfiles(repo, 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("+++ "):
30 if l.startswith("+") and not l.startswith("+++ "):
31 added += 1
31 added += 1
32 elif l.startswith("-") and not l.startswith("--- "):
32 elif l.startswith("-") and not l.startswith("--- "):
33 removed += 1
33 removed += 1
34 return (added, removed)
34 return (added, removed)
35
35
36 def countrate(ui, repo, amap, *pats, **opts):
36 def countrate(ui, repo, amap, *pats, **opts):
37 """Calculate stats"""
37 """Calculate stats"""
38 if opts.get('dateformat'):
38 if opts.get('dateformat'):
39 def getkey(ctx):
39 def getkey(ctx):
40 t, tz = ctx.date()
40 t, tz = ctx.date()
41 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
41 date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
42 return date.strftime(opts['dateformat'])
42 return date.strftime(opts['dateformat'])
43 else:
43 else:
44 tmpl = opts.get('template', '{author|email}')
44 tmpl = opts.get('template', '{author|email}')
45 tmpl = maketemplater(ui, repo, tmpl)
45 tmpl = maketemplater(ui, repo, tmpl)
46 def getkey(ctx):
46 def getkey(ctx):
47 ui.pushbuffer()
47 ui.pushbuffer()
48 tmpl.show(ctx)
48 tmpl.show(ctx)
49 return ui.popbuffer()
49 return ui.popbuffer()
50
50
51 state = {'count': 0, 'pct': 0}
51 state = {'count': 0}
52 rate = {}
52 rate = {}
53 df = False
53 df = False
54 if opts.get('date'):
54 if opts.get('date'):
55 df = util.matchdate(opts['date'])
55 df = util.matchdate(opts['date'])
56
56
57 m = cmdutil.match(repo, pats, opts)
57 m = cmdutil.match(repo, pats, opts)
58 def prep(ctx, fns):
58 def prep(ctx, fns):
59 rev = ctx.rev()
59 rev = ctx.rev()
60 if df and not df(ctx.date()[0]): # doesn't match date format
60 if df and not df(ctx.date()[0]): # doesn't match date format
61 return
61 return
62
62
63 key = getkey(ctx)
63 key = getkey(ctx)
64 key = amap.get(key, key) # alias remap
64 key = amap.get(key, key) # alias remap
65 if opts.get('changesets'):
65 if opts.get('changesets'):
66 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
66 rate[key] = (rate.get(key, (0,))[0] + 1, 0)
67 else:
67 else:
68 parents = ctx.parents()
68 parents = ctx.parents()
69 if len(parents) > 1:
69 if len(parents) > 1:
70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
70 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
71 return
71 return
72
72
73 ctx1 = parents[0]
73 ctx1 = parents[0]
74 lines = changedlines(ui, repo, ctx1, ctx, fns)
74 lines = changedlines(ui, repo, ctx1, ctx, fns)
75 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
75 rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
76
76
77 if opts.get('progress'):
78 state['count'] += 1
77 state['count'] += 1
79 newpct = int(100.0 * state['count'] / max(len(repo), 1))
78 ui.progress(_('churning changes'), state['count'], total=len(repo))
80 if state['pct'] < newpct:
81 state['pct'] = newpct
82 ui.write("\r" + _("generating stats: %d%%") % state['pct'])
83 sys.stdout.flush()
84
79
85 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
80 for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
86 continue
81 continue
87
82
88 if opts.get('progress'):
83 ui.progress(_('churning changes'), None)
89 ui.write("\r")
90 sys.stdout.flush()
91
84
92 return rate
85 return rate
93
86
94
87
95 def churn(ui, repo, *pats, **opts):
88 def churn(ui, repo, *pats, **opts):
96 '''histogram of changes to the repository
89 '''histogram of changes to the repository
97
90
98 This command will display a histogram representing the number
91 This command will display a histogram representing the number
99 of changed lines or revisions, grouped according to the given
92 of changed lines or revisions, grouped according to the given
100 template. The default template will group changes by author.
93 template. The default template will group changes by author.
101 The --dateformat option may be used to group the results by
94 The --dateformat option may be used to group the results by
102 date instead.
95 date instead.
103
96
104 Statistics are based on the number of changed lines, or
97 Statistics are based on the number of changed lines, or
105 alternatively the number of matching revisions if the
98 alternatively the number of matching revisions if the
106 --changesets option is specified.
99 --changesets option is specified.
107
100
108 Examples::
101 Examples::
109
102
110 # display count of changed lines for every committer
103 # display count of changed lines for every committer
111 hg churn -t '{author|email}'
104 hg churn -t '{author|email}'
112
105
113 # display daily activity graph
106 # display daily activity graph
114 hg churn -f '%H' -s -c
107 hg churn -f '%H' -s -c
115
108
116 # display activity of developers by month
109 # display activity of developers by month
117 hg churn -f '%Y-%m' -s -c
110 hg churn -f '%Y-%m' -s -c
118
111
119 # display count of lines changed in every year
112 # display count of lines changed in every year
120 hg churn -f '%Y' -s
113 hg churn -f '%Y' -s
121
114
122 It is possible to map alternate email addresses to a main address
115 It is possible to map alternate email addresses to a main address
123 by providing a file using the following format::
116 by providing a file using the following format::
124
117
125 <alias email> <actual email>
118 <alias email> <actual email>
126
119
127 Such a file may be specified with the --aliases option, otherwise
120 Such a file may be specified with the --aliases option, otherwise
128 a .hgchurn file will be looked for in the working directory root.
121 a .hgchurn file will be looked for in the working directory root.
129 '''
122 '''
130 def pad(s, l):
123 def pad(s, l):
131 return (s + " " * l)[:l]
124 return (s + " " * l)[:l]
132
125
133 amap = {}
126 amap = {}
134 aliases = opts.get('aliases')
127 aliases = opts.get('aliases')
135 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
128 if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
136 aliases = repo.wjoin('.hgchurn')
129 aliases = repo.wjoin('.hgchurn')
137 if aliases:
130 if aliases:
138 for l in open(aliases, "r"):
131 for l in open(aliases, "r"):
139 l = l.strip()
132 l = l.strip()
140 alias, actual = l.split()
133 alias, actual = l.split()
141 amap[alias] = actual
134 amap[alias] = actual
142
135
143 rate = countrate(ui, repo, amap, *pats, **opts).items()
136 rate = countrate(ui, repo, amap, *pats, **opts).items()
144 if not rate:
137 if not rate:
145 return
138 return
146
139
147 sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
140 sortkey = ((not opts.get('sort')) and (lambda x: -sum(x[1])) or None)
148 rate.sort(key=sortkey)
141 rate.sort(key=sortkey)
149
142
150 # Be careful not to have a zero maxcount (issue833)
143 # Be careful not to have a zero maxcount (issue833)
151 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
144 maxcount = float(max(sum(v) for k, v in rate)) or 1.0
152 maxname = max(len(k) for k, v in rate)
145 maxname = max(len(k) for k, v in rate)
153
146
154 ttywidth = util.termwidth()
147 ttywidth = util.termwidth()
155 ui.debug("assuming %i character terminal\n" % ttywidth)
148 ui.debug("assuming %i character terminal\n" % ttywidth)
156 width = ttywidth - maxname - 2 - 2 - 2
149 width = ttywidth - maxname - 2 - 2 - 2
157
150
158 if opts.get('diffstat'):
151 if opts.get('diffstat'):
159 width -= 15
152 width -= 15
160 def format(name, (added, removed)):
153 def format(name, (added, removed)):
161 return "%s %15s %s%s\n" % (pad(name, maxname),
154 return "%s %15s %s%s\n" % (pad(name, maxname),
162 '+%d/-%d' % (added, removed),
155 '+%d/-%d' % (added, removed),
163 '+' * charnum(added),
156 '+' * charnum(added),
164 '-' * charnum(removed))
157 '-' * charnum(removed))
165 else:
158 else:
166 width -= 6
159 width -= 6
167 def format(name, count):
160 def format(name, count):
168 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
161 return "%s %6d %s\n" % (pad(name, maxname), sum(count),
169 '*' * charnum(sum(count)))
162 '*' * charnum(sum(count)))
170
163
171 def charnum(count):
164 def charnum(count):
172 return int(round(count * width / maxcount))
165 return int(round(count * width / maxcount))
173
166
174 for name, count in rate:
167 for name, count in rate:
175 ui.write(format(name, count))
168 ui.write(format(name, count))
176
169
177
170
178 cmdtable = {
171 cmdtable = {
179 "churn":
172 "churn":
180 (churn,
173 (churn,
181 [('r', 'rev', [], _('count rate for the specified revision or range')),
174 [('r', 'rev', [], _('count rate for the specified revision or range')),
182 ('d', 'date', '', _('count rate for revisions matching date spec')),
175 ('d', 'date', '', _('count rate for revisions matching date spec')),
183 ('t', 'template', '{author|email}',
176 ('t', 'template', '{author|email}',
184 _('template to group changesets')),
177 _('template to group changesets')),
185 ('f', 'dateformat', '',
178 ('f', 'dateformat', '',
186 _('strftime-compatible format for grouping by date')),
179 _('strftime-compatible format for grouping by date')),
187 ('c', 'changesets', False, _('count rate by number of changesets')),
180 ('c', 'changesets', False, _('count rate by number of changesets')),
188 ('s', 'sort', False, _('sort by key (default: sort by count)')),
181 ('s', 'sort', False, _('sort by key (default: sort by count)')),
189 ('', 'diffstat', False, _('display added/removed lines separately')),
182 ('', 'diffstat', False, _('display added/removed lines separately')),
190 ('', 'aliases', '', _('file with email aliases')),
183 ('', 'aliases', '', _('file with email aliases')),
191 ('', 'progress', None, _('show progress'))],
184 ],
192 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [--progress] [FILE]")),
185 _("hg churn [-d DATE] [-r REV] [--aliases FILE] [FILE]")),
193 }
186 }
General Comments 0
You need to be logged in to leave comments. Login now