Churn extension
authorJosef "Jeff" Sipek <jeff@josefsipek.net>
Wed, 26 Jul 2006 19:42:56 -0400
changeset 3040 f74077473b36
parent 2682 4e2dc5c16e61
child 3041 45942bb49194
Churn extension
hgext/churn.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/churn.py	Wed Jul 26 19:42:56 2006 -0400
@@ -0,0 +1,161 @@
+# churn.py - create a graph showing who changed the most lines
+#
+# Copyright 2006 Josef "Jeff" Sipek <jeffpc@josefsipek.net>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+#
+#
+# Aliases map file format is simple one alias per line in the following
+# format:
+#
+# <alias email> <actual email>
+
+import time, sys, signal, os
+from mercurial import hg, mdiff, fancyopts, commands, ui, util, templater
+
+def __gather(ui, repo, node1, node2):
+    def dirtywork(f, mmap1, mmap2):
+        lines = 0
+
+        to = None
+        if mmap1:
+            to = repo.file(f).read(mmap1[f])
+        tn = None
+        if mmap2:
+            tn = repo.file(f).read(mmap2[f])
+
+        diff = mdiff.unidiff(to, "", tn, "", f).split("\n")
+
+        for line in diff:
+            if len(line) <= 0:
+                continue # skip EOF
+            if line[0] == " ":
+                continue # context line
+            if line[0:4] == "--- " or line[0:4] == "+++ ":
+                continue # begining of diff
+            if line[0:3] == "@@ ":
+                continue # info line
+
+            # changed lines
+            lines += 1
+
+        return lines
+
+    ##
+
+    lines = 0
+
+    changes = repo.changes(node1, node2, None, util.always)
+
+    modified, added, removed, deleted, unknown = changes
+
+    who = repo.changelog.read(node2)[1]
+    who = templater.email(who) # get the email of the person
+
+    mmap1 = repo.manifest.read(repo.changelog.read(node1)[0])
+    mmap2 = repo.manifest.read(repo.changelog.read(node2)[0])
+    for f in modified:
+        lines += dirtywork(f, mmap1, mmap2)
+
+    for f in added:
+        lines += dirtywork(f, None, mmap2)
+        
+    for f in removed:
+        lines += dirtywork(f, mmap1, None)
+
+    for f in deleted:
+        lines += dirtywork(f, mmap1, mmap2)
+
+    for f in unknown:
+        lines += dirtywork(f, mmap1, mmap2)
+
+    return (who, lines)
+
+def gather_stats(ui, repo, amap):
+    stats = {}
+    
+    cl    = repo.changelog
+
+    for rev in range(1,cl.count()):
+        node2    = cl.node(rev)
+        node1    = cl.parents(node2)[0]
+
+        who, lines = __gather(ui, repo, node1, node2)
+
+        # remap the owner if possible
+        if amap.has_key(who):
+            ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
+            who = amap[who]
+
+        if not stats.has_key(who):
+            stats[who] = 0
+        stats[who] += lines
+
+        ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
+
+    return stats
+
+def churn(ui, repo, aliases):
+    "Graphs the number of lines changed"
+    
+    def pad(s, l):
+        if len(s) < l:
+            return s + " " * (l-len(s))
+        return s[0:l]
+
+    def graph(n, maximum, width, char):
+        n = int(n * width / float(maximum))
+        
+        return char * (n)
+
+    def get_aliases(f):
+        aliases = {}
+
+        for l in f.readlines():
+            l = l.strip()
+            alias, actual = l.split(" ")
+            aliases[alias] = actual
+
+        return aliases
+    
+    amap = {}
+    if aliases:
+        try:
+            f = open(aliases,"r")
+        except OSError, e:
+            print "Error: " + e
+            return
+
+        amap = get_aliases(f)
+        f.close()
+    
+    os.chdir(repo.root)
+    stats = gather_stats(ui, repo, amap)
+
+    # make a list of tuples (name, lines) and sort it in descending order
+    ordered = stats.items()
+    ordered.sort(cmp=lambda x,y:cmp(x[1], y[1]))
+    ordered.reverse()
+
+    maximum = ordered[0][1]
+
+    ui.note("Assuming 80 character terminal\n")
+    width = 80 - 1
+
+    for i in ordered:
+        person = i[0]
+        lines = i[1]
+        print "%s %6d %s" % (pad(person, 20), lines,
+                graph(lines, maximum, width - 20 - 1 - 6 - 2 - 2, '*'))
+
+cmdtable = {
+    "churn":
+    (churn,
+     [('', 'aliases', '', 'file with email aliases')],
+    'hg churn [-a file]'),
+}
+
+def reposetup(ui, repo):
+    pass
+