hgext/churn.py
changeset 3037 f74077473b36
child 3038 45942bb49194
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