Mercurial > hg > mercurial-crew-with-dirclash
comparison hgext/churn.py @ 3037:f74077473b36
Churn extension
author | Josef "Jeff" Sipek <jeff@josefsipek.net> |
---|---|
date | Wed, 26 Jul 2006 19:42:56 -0400 |
parents | |
children | 45942bb49194 |
comparison
equal
deleted
inserted
replaced
2682:4e2dc5c16e61 | 3037:f74077473b36 |
---|---|
1 # churn.py - create a graph showing who changed the most lines | |
2 # | |
3 # Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net> | |
4 # | |
5 # This software may be used and distributed according to the terms | |
6 # of the GNU General Public License, incorporated herein by reference. | |
7 # | |
8 # | |
9 # Aliases map file format is simple one alias per line in the following | |
10 # format: | |
11 # | |
12 # <alias email> <actual email> | |
13 | |
14 import time, sys, signal, os | |
15 from mercurial import hg, mdiff, fancyopts, commands, ui, util, templater | |
16 | |
17 def __gather(ui, repo, node1, node2): | |
18 def dirtywork(f, mmap1, mmap2): | |
19 lines = 0 | |
20 | |
21 to = None | |
22 if mmap1: | |
23 to = repo.file(f).read(mmap1[f]) | |
24 tn = None | |
25 if mmap2: | |
26 tn = repo.file(f).read(mmap2[f]) | |
27 | |
28 diff = mdiff.unidiff(to, "", tn, "", f).split("\n") | |
29 | |
30 for line in diff: | |
31 if len(line) <= 0: | |
32 continue # skip EOF | |
33 if line[0] == " ": | |
34 continue # context line | |
35 if line[0:4] == "--- " or line[0:4] == "+++ ": | |
36 continue # begining of diff | |
37 if line[0:3] == "@@ ": | |
38 continue # info line | |
39 | |
40 # changed lines | |
41 lines += 1 | |
42 | |
43 return lines | |
44 | |
45 ## | |
46 | |
47 lines = 0 | |
48 | |
49 changes = repo.changes(node1, node2, None, util.always) | |
50 | |
51 modified, added, removed, deleted, unknown = changes | |
52 | |
53 who = repo.changelog.read(node2)[1] | |
54 who = templater.email(who) # get the email of the person | |
55 | |
56 mmap1 = repo.manifest.read(repo.changelog.read(node1)[0]) | |
57 mmap2 = repo.manifest.read(repo.changelog.read(node2)[0]) | |
58 for f in modified: | |
59 lines += dirtywork(f, mmap1, mmap2) | |
60 | |
61 for f in added: | |
62 lines += dirtywork(f, None, mmap2) | |
63 | |
64 for f in removed: | |
65 lines += dirtywork(f, mmap1, None) | |
66 | |
67 for f in deleted: | |
68 lines += dirtywork(f, mmap1, mmap2) | |
69 | |
70 for f in unknown: | |
71 lines += dirtywork(f, mmap1, mmap2) | |
72 | |
73 return (who, lines) | |
74 | |
75 def gather_stats(ui, repo, amap): | |
76 stats = {} | |
77 | |
78 cl = repo.changelog | |
79 | |
80 for rev in range(1,cl.count()): | |
81 node2 = cl.node(rev) | |
82 node1 = cl.parents(node2)[0] | |
83 | |
84 who, lines = __gather(ui, repo, node1, node2) | |
85 | |
86 # remap the owner if possible | |
87 if amap.has_key(who): | |
88 ui.note("using '%s' alias for '%s'\n" % (amap[who], who)) | |
89 who = amap[who] | |
90 | |
91 if not stats.has_key(who): | |
92 stats[who] = 0 | |
93 stats[who] += lines | |
94 | |
95 ui.note("rev %d: %d lines by %s\n" % (rev, lines, who)) | |
96 | |
97 return stats | |
98 | |
99 def churn(ui, repo, aliases): | |
100 "Graphs the number of lines changed" | |
101 | |
102 def pad(s, l): | |
103 if len(s) < l: | |
104 return s + " " * (l-len(s)) | |
105 return s[0:l] | |
106 | |
107 def graph(n, maximum, width, char): | |
108 n = int(n * width / float(maximum)) | |
109 | |
110 return char * (n) | |
111 | |
112 def get_aliases(f): | |
113 aliases = {} | |
114 | |
115 for l in f.readlines(): | |
116 l = l.strip() | |
117 alias, actual = l.split(" ") | |
118 aliases[alias] = actual | |
119 | |
120 return aliases | |
121 | |
122 amap = {} | |
123 if aliases: | |
124 try: | |
125 f = open(aliases,"r") | |
126 except OSError, e: | |
127 print "Error: " + e | |
128 return | |
129 | |
130 amap = get_aliases(f) | |
131 f.close() | |
132 | |
133 os.chdir(repo.root) | |
134 stats = gather_stats(ui, repo, amap) | |
135 | |
136 # make a list of tuples (name, lines) and sort it in descending order | |
137 ordered = stats.items() | |
138 ordered.sort(cmp=lambda x,y:cmp(x[1], y[1])) | |
139 ordered.reverse() | |
140 | |
141 maximum = ordered[0][1] | |
142 | |
143 ui.note("Assuming 80 character terminal\n") | |
144 width = 80 - 1 | |
145 | |
146 for i in ordered: | |
147 person = i[0] | |
148 lines = i[1] | |
149 print "%s %6d %s" % (pad(person, 20), lines, | |
150 graph(lines, maximum, width - 20 - 1 - 6 - 2 - 2, '*')) | |
151 | |
152 cmdtable = { | |
153 "churn": | |
154 (churn, | |
155 [('', 'aliases', '', 'file with email aliases')], | |
156 'hg churn [-a file]'), | |
157 } | |
158 | |
159 def reposetup(ui, repo): | |
160 pass | |
161 |