python implementation of diffstat
authorAlexander Solovyov <piranha@piranha.org.ua>
Thu, 25 Dec 2008 10:48:24 +0200
changeset 7547 4949729ee9ee
parent 7546 c7f48414f3ad
child 7549 013a2baf8f85
python implementation of diffstat Implemented as two functions: diffstat, which yields lines of text, formatted as a usual diffstat output, and diffstatdata, which is called inside diffstat to do real performing and yield file names with appropriate data (numbers of added and removed lines).
hgext/churn.py
hgext/patchbomb.py
mercurial/patch.py
mercurial/util.py
tests/test-notify.out
tests/test-patchbomb
tests/test-patchbomb.out
--- a/hgext/churn.py	Sun Dec 28 19:59:42 2008 +0100
+++ b/hgext/churn.py	Thu Dec 25 10:48:24 2008 +0200
@@ -12,27 +12,6 @@
 import os, sys
 import time, datetime
 
-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 maketemplater(ui, repo, tmpl):
     tmpl = templater.parsestring(tmpl, quoted=False)
     try:
@@ -157,7 +136,7 @@
     maxcount = float(max([v for k, v in rate]))
     maxname = max([len(k) for k, v in rate])
 
-    ttywidth = get_tty_width()
+    ttywidth = util.termwidth()
     ui.debug(_("assuming %i character terminal\n") % ttywidth)
     width = ttywidth - maxname - 2 - 6 - 2 - 2
 
--- a/hgext/patchbomb.py	Sun Dec 28 19:59:42 2008 +0100
+++ b/hgext/patchbomb.py	Thu Dec 25 10:48:24 2008 +0200
@@ -9,8 +9,7 @@
 
   The remainder of the changeset description.
 
-  [Optional] If the diffstat program is installed, the result of
-  running diffstat on the patch.
+  [Optional] The result of running diffstat on the patch.
 
   The patch itself, as generated by "hg export".
 
@@ -99,16 +98,12 @@
 
 def cdiffstat(ui, summary, patchlines):
     s = patch.diffstat(patchlines)
-    if s:
-        if summary:
-            ui.write(summary, '\n')
-            ui.write(s, '\n')
-        ans = prompt(ui, _('Does the diffstat above look okay? '), 'y')
-        if not ans.lower().startswith('y'):
-            raise util.Abort(_('diffstat rejected'))
-    elif s is None:
-        ui.warn(_('no diffstat information available\n'))
-        s = ''
+    if summary:
+        ui.write(summary, '\n')
+        ui.write(s, '\n')
+    ans = prompt(ui, _('Does the diffstat above look okay? '), 'y')
+    if not ans.lower().startswith('y'):
+        raise util.Abort(_('diffstat rejected'))
     return s
 
 def makepatch(ui, repo, patch, opts, _charsets, idx, total, patchname=None):
--- a/mercurial/patch.py	Sun Dec 28 19:59:42 2008 +0100
+++ b/mercurial/patch.py	Thu Dec 25 10:48:24 2008 +0200
@@ -9,7 +9,7 @@
 from i18n import _
 from node import hex, nullid, short
 import base85, cmdutil, mdiff, util, revlog, diffhelpers, copies
-import cStringIO, email.Parser, os, re, errno
+import cStringIO, email.Parser, os, re, errno, math
 import sys, tempfile, zlib
 
 gitre = re.compile('diff --git a/(.*) b/(.*)')
@@ -1344,13 +1344,57 @@
     for seqno, rev in enumerate(revs):
         single(rev, seqno+1, fp)
 
-def diffstat(patchlines):
-    if not util.find_exe('diffstat'):
-        return
-    output = util.filter('\n'.join(patchlines),
-                         'diffstat -p1 -w79 2>%s' % util.nulldev)
-    stat = [l.lstrip() for l in output.splitlines(True)]
-    last = stat.pop()
-    stat.insert(0, last)
-    stat = ''.join(stat)
-    return stat
+def diffstatdata(lines):
+    filename = None
+    for line in lines:
+        if line.startswith('diff'):
+            if filename:
+                yield (filename, adds, removes)
+            # set numbers to 0 anyway when starting new file
+            adds = 0
+            removes = 0
+            if line.startswith('diff --git'):
+                filename = gitre.search(line).group(1)
+            else:
+                # format: "diff -r ... -r ... file name"
+                filename = line.split(None, 5)[-1]
+        elif line.startswith('+') and not line.startswith('+++'):
+            adds += 1
+        elif line.startswith('-') and not line.startswith('---'):
+            removes += 1
+    yield (filename, adds, removes)
+
+def diffstat(lines):
+    output = []
+    stats = list(diffstatdata(lines))
+    width = util.termwidth() - 2
+
+    maxtotal, maxname = 0, 0
+    totaladds, totalremoves = 0, 0
+    for filename, adds, removes in stats:
+        totaladds += adds
+        totalremoves += removes
+        maxname = max(maxname, len(filename))
+        maxtotal = max(maxtotal, adds+removes)
+
+    countwidth = len(str(maxtotal))
+    graphwidth = width - countwidth - maxname
+    if graphwidth < 10:
+        graphwidth = 10
+
+    factor = int(math.ceil(float(maxtotal) / graphwidth))
+
+    for filename, adds, removes in stats:
+        # If diffstat runs out of room it doesn't print anything, which
+        # isn't very useful, so always print at least one + or - if there
+        # were at least some changes
+        pluses = '+' * max(adds/factor, int(bool(adds)))
+        minuses = '-' * max(removes/factor, int(bool(removes)))
+        output.append(' %-*s |  %*.d %s%s\n' % (maxname, filename, countwidth,
+                                                adds+removes, pluses, minuses))
+
+    if stats:
+        output.append(' %d files changed, %d insertions(+), %d deletions(-)\n' %
+                      (len(stats), totaladds, totalremoves))
+
+    return ''.join(output)
--- a/mercurial/util.py	Sun Dec 28 19:59:42 2008 +0100
+++ b/mercurial/util.py	Thu Dec 25 10:48:24 2008 +0200
@@ -1985,3 +1985,24 @@
 def uirepr(s):
     # Avoid double backslash in Windows path repr()
     return repr(s).replace('\\\\', '\\')
+
+def termwidth():
+    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
--- a/tests/test-notify.out	Sun Dec 28 19:59:42 2008 +0100
+++ b/tests/test-notify.out	Thu Dec 25 10:48:24 2008 +0200
@@ -150,7 +150,8 @@
 	b
 diffstat:
 
-files patched: 1
+ a |  1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
 
 diffs (6 lines):
 
--- a/tests/test-patchbomb	Sun Dec 28 19:59:42 2008 +0100
+++ b/tests/test-patchbomb	Thu Dec 25 10:48:24 2008 +0200
@@ -11,6 +11,8 @@
 echo "[extensions]" >> $HGRCPATH
 echo "patchbomb=" >> $HGRCPATH
 
+COLUMNS=80; export COLUMNS
+
 hg init t
 cd t
 echo a > a
--- a/tests/test-patchbomb.out	Sun Dec 28 19:59:42 2008 +0100
+++ b/tests/test-patchbomb.out	Thu Dec 25 10:48:24 2008 +0200
@@ -196,7 +196,8 @@
 
 c
 
-files patched: 1
+ c |  1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
 
 
 Displaying [PATCH] test ...
@@ -211,7 +212,8 @@
 To: foo
 Cc: bar
 
-files patched: 1
+ c |  1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
 
 
 # HG changeset patch
@@ -232,15 +234,19 @@
 
 a
 
-files patched: 1
+ a |  1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
 
 b
 
-files patched: 1
+ b |  1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
 
 Final summary:
 
-files patched: 2
+ a |  1 +
+ b |  1 +
+ 2 files changed, 2 insertions(+), 0 deletions(-)
 
 
 Write the introductory message for the patch series.
@@ -258,7 +264,9 @@
 Cc: bar
 
 
-files patched: 2
+ a |  1 +
+ b |  1 +
+ 2 files changed, 2 insertions(+), 0 deletions(-)
 
 Displaying [PATCH 1 of 2] a ...
 Content-Type: text/plain; charset="us-ascii"
@@ -274,7 +282,8 @@
 To: foo
 Cc: bar
 
-files patched: 1
+ a |  1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
 
 
 # HG changeset patch
@@ -304,7 +313,8 @@
 To: foo
 Cc: bar
 
-files patched: 1
+ b |  1 +
+ 1 files changed, 1 insertions(+), 0 deletions(-)
 
 
 # HG changeset patch