##// END OF EJS Templates
churn: fix documentation for churn options
Dirkjan Ochtman -
r6841:05a62e8d default
parent child Browse files
Show More
@@ -1,206 +1,206 b''
1 1 # churn.py - create a graph showing who changed the most lines
2 2 #
3 3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7 #
8 8 #
9 9 # Aliases map file format is simple one alias per line in the following
10 10 # format:
11 11 #
12 12 # <alias email> <actual email>
13 13
14 14 from mercurial.i18n import gettext as _
15 15 from mercurial import mdiff, cmdutil, util, node
16 16 import os, sys
17 17
18 18 def get_tty_width():
19 19 if 'COLUMNS' in os.environ:
20 20 try:
21 21 return int(os.environ['COLUMNS'])
22 22 except ValueError:
23 23 pass
24 24 try:
25 25 import termios, array, fcntl
26 26 for dev in (sys.stdout, sys.stdin):
27 27 try:
28 28 fd = dev.fileno()
29 29 if not os.isatty(fd):
30 30 continue
31 31 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
32 32 return array.array('h', arri)[1]
33 33 except ValueError:
34 34 pass
35 35 except ImportError:
36 36 pass
37 37 return 80
38 38
39 39 def __gather(ui, repo, node1, node2):
40 40 def dirtywork(f, mmap1, mmap2):
41 41 lines = 0
42 42
43 43 to = mmap1 and repo.file(f).read(mmap1[f]) or None
44 44 tn = mmap2 and repo.file(f).read(mmap2[f]) or None
45 45
46 46 diff = mdiff.unidiff(to, "", tn, "", f, f).split("\n")
47 47
48 48 for line in diff:
49 49 if not line:
50 50 continue # skip EOF
51 51 if line.startswith(" "):
52 52 continue # context line
53 53 if line.startswith("--- ") or line.startswith("+++ "):
54 54 continue # begining of diff
55 55 if line.startswith("@@ "):
56 56 continue # info line
57 57
58 58 # changed lines
59 59 lines += 1
60 60
61 61 return lines
62 62
63 63 ##
64 64
65 65 lines = 0
66 66
67 67 changes = repo.status(node1, node2, None, util.always)[:5]
68 68
69 69 modified, added, removed, deleted, unknown = changes
70 70
71 71 who = repo.changelog.read(node2)[1]
72 72 who = util.email(who) # get the email of the person
73 73
74 74 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
75 75 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
76 76 for f in modified:
77 77 lines += dirtywork(f, mmap1, mmap2)
78 78
79 79 for f in added:
80 80 lines += dirtywork(f, None, mmap2)
81 81
82 82 for f in removed:
83 83 lines += dirtywork(f, mmap1, None)
84 84
85 85 for f in deleted:
86 86 lines += dirtywork(f, mmap1, mmap2)
87 87
88 88 for f in unknown:
89 89 lines += dirtywork(f, mmap1, mmap2)
90 90
91 91 return (who, lines)
92 92
93 93 def gather_stats(ui, repo, amap, revs=None, progress=False):
94 94 stats = {}
95 95
96 96 cl = repo.changelog
97 97
98 98 if not revs:
99 99 revs = range(0, cl.count())
100 100
101 101 nr_revs = len(revs)
102 102 cur_rev = 0
103 103
104 104 for rev in revs:
105 105 cur_rev += 1 # next revision
106 106
107 107 node2 = cl.node(rev)
108 108 node1 = cl.parents(node2)[0]
109 109
110 110 if cl.parents(node2)[1] != node.nullid:
111 111 ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
112 112 continue
113 113
114 114 who, lines = __gather(ui, repo, node1, node2)
115 115
116 116 # remap the owner if possible
117 117 if who in amap:
118 118 ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
119 119 who = amap[who]
120 120
121 121 if not who in stats:
122 122 stats[who] = 0
123 123 stats[who] += lines
124 124
125 125 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
126 126
127 127 if progress:
128 128 nr_revs = max(nr_revs, 1)
129 129 if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
130 130 ui.write("\rGenerating stats: %d%%" % (int(100.0*cur_rev/nr_revs),))
131 131 sys.stdout.flush()
132 132
133 133 if progress:
134 134 ui.write("\r")
135 135 sys.stdout.flush()
136 136
137 137 return stats
138 138
139 139 def churn(ui, repo, **opts):
140 140 "Graphs the number of lines changed"
141 141
142 142 def pad(s, l):
143 143 if len(s) < l:
144 144 return s + " " * (l-len(s))
145 145 return s[0:l]
146 146
147 147 def graph(n, maximum, width, char):
148 148 maximum = max(1, maximum)
149 149 n = int(n * width / float(maximum))
150 150
151 151 return char * (n)
152 152
153 153 def get_aliases(f):
154 154 aliases = {}
155 155
156 156 for l in f.readlines():
157 157 l = l.strip()
158 158 alias, actual = l.split()
159 159 aliases[alias] = actual
160 160
161 161 return aliases
162 162
163 163 amap = {}
164 164 aliases = opts.get('aliases')
165 165 if aliases:
166 166 try:
167 167 f = open(aliases,"r")
168 168 except OSError, e:
169 169 print "Error: " + e
170 170 return
171 171
172 172 amap = get_aliases(f)
173 173 f.close()
174 174
175 175 revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])]
176 176 revs.sort()
177 177 stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
178 178
179 179 # make a list of tuples (name, lines) and sort it in descending order
180 180 ordered = stats.items()
181 181 if not ordered:
182 182 return
183 183 ordered.sort(lambda x, y: cmp(y[1], x[1]))
184 184 max_churn = ordered[0][1]
185 185
186 186 tty_width = get_tty_width()
187 187 ui.note(_("assuming %i character terminal\n") % tty_width)
188 188 tty_width -= 1
189 189
190 190 max_user_width = max([len(user) for user, churn in ordered])
191 191
192 192 graph_width = tty_width - max_user_width - 1 - 6 - 2 - 2
193 193
194 194 for user, churn in ordered:
195 195 print "%s %6d %s" % (pad(user, max_user_width),
196 196 churn,
197 197 graph(churn, max_churn, graph_width, '*'))
198 198
199 199 cmdtable = {
200 200 "churn":
201 201 (churn,
202 202 [('r', 'rev', [], _('limit statistics to the specified revisions')),
203 203 ('', 'aliases', '', _('file with email aliases')),
204 204 ('', 'progress', None, _('show progress'))],
205 'hg churn [-r revision range] [-a file] [--progress]'),
205 'hg churn [-r REVISIONS] [--aliases FILE] [--progress]'),
206 206 }
General Comments 0
You need to be logged in to leave comments. Login now