--- a/contrib/churn.py Sat Mar 22 12:48:15 2008 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,206 +0,0 @@
-# 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>
-
-from mercurial.i18n import gettext as _
-from mercurial import mdiff, cmdutil, util, node
-import os, sys
-
-def get_tty_width():
- if 'COLUMNS' in os.environ:
- try:
- return int(os.environ['COLUMNS'])
- except ValueError:
- pass
- try:
- import termios, array, fcntl
- for dev in (sys.stdout, sys.stdin):
- try:
- fd = dev.fileno()
- if not os.isatty(fd):
- continue
- arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
- return array.array('h', arri)[1]
- except ValueError:
- pass
- except ImportError:
- pass
- return 80
-
-def __gather(ui, repo, node1, node2):
- def dirtywork(f, mmap1, mmap2):
- lines = 0
-
- to = mmap1 and repo.file(f).read(mmap1[f]) or None
- tn = mmap2 and repo.file(f).read(mmap2[f]) or None
-
- diff = mdiff.unidiff(to, "", tn, "", f, f).split("\n")
-
- for line in diff:
- if not line:
- continue # skip EOF
- if line.startswith(" "):
- continue # context line
- if line.startswith("--- ") or line.startswith("+++ "):
- continue # begining of diff
- if line.startswith("@@ "):
- continue # info line
-
- # changed lines
- lines += 1
-
- return lines
-
- ##
-
- lines = 0
-
- changes = repo.status(node1, node2, None, util.always)[:5]
-
- modified, added, removed, deleted, unknown = changes
-
- who = repo.changelog.read(node2)[1]
- who = util.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, revs=None, progress=False):
- stats = {}
-
- cl = repo.changelog
-
- if not revs:
- revs = range(0, cl.count())
-
- nr_revs = len(revs)
- cur_rev = 0
-
- for rev in revs:
- cur_rev += 1 # next revision
-
- node2 = cl.node(rev)
- node1 = cl.parents(node2)[0]
-
- if cl.parents(node2)[1] != node.nullid:
- ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
- continue
-
- who, lines = __gather(ui, repo, node1, node2)
-
- # remap the owner if possible
- if who in amap:
- ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
- who = amap[who]
-
- if not who in stats:
- stats[who] = 0
- stats[who] += lines
-
- ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
-
- if progress:
- nr_revs = max(nr_revs, 1)
- if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
- ui.write("\rGenerating stats: %d%%" % (int(100.0*cur_rev/nr_revs),))
- sys.stdout.flush()
-
- if progress:
- ui.write("\r")
- sys.stdout.flush()
-
- return stats
-
-def churn(ui, repo, **opts):
- "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):
- maximum = max(1, maximum)
- 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 = {}
- aliases = opts.get('aliases')
- if aliases:
- try:
- f = open(aliases,"r")
- except OSError, e:
- print "Error: " + e
- return
-
- amap = get_aliases(f)
- f.close()
-
- revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])]
- revs.sort()
- stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
-
- # make a list of tuples (name, lines) and sort it in descending order
- ordered = stats.items()
- if not ordered:
- return
- ordered.sort(lambda x, y: cmp(y[1], x[1]))
- max_churn = ordered[0][1]
-
- tty_width = get_tty_width()
- ui.note(_("assuming %i character terminal\n") % tty_width)
- tty_width -= 1
-
- max_user_width = max([len(user) for user, churn in ordered])
-
- graph_width = tty_width - max_user_width - 1 - 6 - 2 - 2
-
- for user, churn in ordered:
- print "%s %6d %s" % (pad(user, max_user_width),
- churn,
- graph(churn, max_churn, graph_width, '*'))
-
-cmdtable = {
- "churn":
- (churn,
- [('r', 'rev', [], _('limit statistics to the specified revisions')),
- ('', 'aliases', '', _('file with email aliases')),
- ('', 'progress', None, _('show progress'))],
- 'hg churn [-r revision range] [-a file] [--progress]'),
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/churn.py Sat Mar 22 18:01:46 2008 +0100
@@ -0,0 +1,206 @@
+# 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>
+
+from mercurial.i18n import gettext as _
+from mercurial import mdiff, cmdutil, util, node
+import os, sys
+
+def get_tty_width():
+ if 'COLUMNS' in os.environ:
+ try:
+ return int(os.environ['COLUMNS'])
+ except ValueError:
+ pass
+ try:
+ import termios, array, fcntl
+ for dev in (sys.stdout, sys.stdin):
+ try:
+ fd = dev.fileno()
+ if not os.isatty(fd):
+ continue
+ arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
+ return array.array('h', arri)[1]
+ except ValueError:
+ pass
+ except ImportError:
+ pass
+ return 80
+
+def __gather(ui, repo, node1, node2):
+ def dirtywork(f, mmap1, mmap2):
+ lines = 0
+
+ to = mmap1 and repo.file(f).read(mmap1[f]) or None
+ tn = mmap2 and repo.file(f).read(mmap2[f]) or None
+
+ diff = mdiff.unidiff(to, "", tn, "", f, f).split("\n")
+
+ for line in diff:
+ if not line:
+ continue # skip EOF
+ if line.startswith(" "):
+ continue # context line
+ if line.startswith("--- ") or line.startswith("+++ "):
+ continue # begining of diff
+ if line.startswith("@@ "):
+ continue # info line
+
+ # changed lines
+ lines += 1
+
+ return lines
+
+ ##
+
+ lines = 0
+
+ changes = repo.status(node1, node2, None, util.always)[:5]
+
+ modified, added, removed, deleted, unknown = changes
+
+ who = repo.changelog.read(node2)[1]
+ who = util.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, revs=None, progress=False):
+ stats = {}
+
+ cl = repo.changelog
+
+ if not revs:
+ revs = range(0, cl.count())
+
+ nr_revs = len(revs)
+ cur_rev = 0
+
+ for rev in revs:
+ cur_rev += 1 # next revision
+
+ node2 = cl.node(rev)
+ node1 = cl.parents(node2)[0]
+
+ if cl.parents(node2)[1] != node.nullid:
+ ui.note(_('Revision %d is a merge, ignoring...\n') % (rev,))
+ continue
+
+ who, lines = __gather(ui, repo, node1, node2)
+
+ # remap the owner if possible
+ if who in amap:
+ ui.note("using '%s' alias for '%s'\n" % (amap[who], who))
+ who = amap[who]
+
+ if not who in stats:
+ stats[who] = 0
+ stats[who] += lines
+
+ ui.note("rev %d: %d lines by %s\n" % (rev, lines, who))
+
+ if progress:
+ nr_revs = max(nr_revs, 1)
+ if int(100.0*(cur_rev - 1)/nr_revs) < int(100.0*cur_rev/nr_revs):
+ ui.write("\rGenerating stats: %d%%" % (int(100.0*cur_rev/nr_revs),))
+ sys.stdout.flush()
+
+ if progress:
+ ui.write("\r")
+ sys.stdout.flush()
+
+ return stats
+
+def churn(ui, repo, **opts):
+ "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):
+ maximum = max(1, maximum)
+ 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 = {}
+ aliases = opts.get('aliases')
+ if aliases:
+ try:
+ f = open(aliases,"r")
+ except OSError, e:
+ print "Error: " + e
+ return
+
+ amap = get_aliases(f)
+ f.close()
+
+ revs = [int(r) for r in cmdutil.revrange(repo, opts['rev'])]
+ revs.sort()
+ stats = gather_stats(ui, repo, amap, revs, opts.get('progress'))
+
+ # make a list of tuples (name, lines) and sort it in descending order
+ ordered = stats.items()
+ if not ordered:
+ return
+ ordered.sort(lambda x, y: cmp(y[1], x[1]))
+ max_churn = ordered[0][1]
+
+ tty_width = get_tty_width()
+ ui.note(_("assuming %i character terminal\n") % tty_width)
+ tty_width -= 1
+
+ max_user_width = max([len(user) for user, churn in ordered])
+
+ graph_width = tty_width - max_user_width - 1 - 6 - 2 - 2
+
+ for user, churn in ordered:
+ print "%s %6d %s" % (pad(user, max_user_width),
+ churn,
+ graph(churn, max_churn, graph_width, '*'))
+
+cmdtable = {
+ "churn":
+ (churn,
+ [('r', 'rev', [], _('limit statistics to the specified revisions')),
+ ('', 'aliases', '', _('file with email aliases')),
+ ('', 'progress', None, _('show progress'))],
+ 'hg churn [-r revision range] [-a file] [--progress]'),
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-churn Sat Mar 22 18:01:46 2008 +0100
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+echo "[extensions]" >> $HGRCPATH
+echo "churn=" >> $HGRCPATH
+
+echo % create test repository
+hg init repo
+cd repo
+echo a > a
+hg ci -Am adda -u user1
+echo b >> a
+echo b > b
+hg ci -Am addb -u user2
+echo c >> a
+echo c >> b
+echo c > c
+hg ci -Am addc -u user3
+
+echo % churn all
+hg churn
+echo % churn up to rev 1
+hg churn -r :1
+echo % churn with aliases
+cat > ../aliases <<EOF
+user1 alias1
+user3 alias3
+EOF
+hg churn --aliases ../aliases
+echo % churn with column specifier
+COLUMNS=40 hg churn
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-churn.out Sat Mar 22 18:01:46 2008 +0100
@@ -0,0 +1,19 @@
+% create test repository
+adding a
+adding b
+adding c
+% churn all
+user3 3 ***************************************************************
+user2 2 ******************************************
+user1 1 *********************
+% churn up to rev 1
+user2 2 ***************************************************************
+user1 1 *******************************
+% churn with aliases
+alias3 3 **************************************************************
+user2 2 *****************************************
+alias1 1 ********************
+% churn with column specifier
+user3 3 ***********************
+user2 2 ***************
+user1 1 *******