[PATCH] python implementation of diffstat

Alexander Solovyov piranha at piranha.org.ua
Sun Dec 21 15:40:26 CST 2008


# HG changeset patch
# User Alexander Solovyov <piranha at piranha.org.ua>
# Date 1229895590 -7200
# Node ID 7014aecd1081755451e6ce742baee291494415b5
# Parent  3896310bd797a3c0db22654321e88db9cc73d3e7
python implementation of diffstat

Implemented as an class, string representation of which is similar to
the output of system diffstat command. There is possibility to access
attributes of a diffstat object to get already parsed statistics in
python code.

diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py
--- a/hgext/patchbomb.py
+++ b/hgext/patchbomb.py
@@ -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".
 
@@ -98,17 +97,13 @@
         ui.warn(_('Please enter a valid value.\n'))
 
 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 = ''
+    s = ''.join(patch.diffstatfmt(patch.diffstat(patchlines)))
+    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):
diff --git a/mercurial/patch.py b/mercurial/patch.py
--- a/mercurial/patch.py
+++ b/mercurial/patch.py
@@ -1344,13 +1344,56 @@
     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 diffstat(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 diffstatfmt(stats):
+    stats = list(stats)
+    width = 78
+
+    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 = 1
+    while (maxtotal / factor) > graphwidth:
+        factor += 1
+
+    if stats:
+        yield (' %d files changed, %d insertions(+), %d deletions(-)\n' %
+               (len(stats), totaladds, totalremoves))
+
+    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 1
+        pluses = '+' * max(adds/factor, 1)
+        minuses = '-' * max(removes/factor, 1)
+        yield ' %-*s |  %*.d %s%s\n' % (maxname, filename, countwidth,
+                                        adds+removes, pluses, minuses)
diff --git a/tests/test-notify.out b/tests/test-notify.out
--- a/tests/test-notify.out
+++ b/tests/test-notify.out
@@ -150,7 +150,8 @@
 	b
 diffstat:
 
-files patched: 1
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ a |  1 +-
 
 diffs (6 lines):
 
diff --git a/tests/test-patchbomb.out b/tests/test-patchbomb.out
--- a/tests/test-patchbomb.out
+++ b/tests/test-patchbomb.out
@@ -196,7 +196,8 @@
 
 c
 
-files patched: 1
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ c |  1 +-
 
 
 Displaying [PATCH] test ...
@@ -211,7 +212,8 @@
 To: foo
 Cc: bar
 
-files patched: 1
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ c |  1 +-
 
 
 # HG changeset patch
@@ -232,15 +234,19 @@
 
 a
 
-files patched: 1
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ a |  1 +-
 
 b
 
-files patched: 1
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ b |  1 +-
 
 Final summary:
 
-files patched: 2
+ 2 files changed, 2 insertions(+), 0 deletions(-)
+ a |  1 +-
+ b |  1 +-
 
 
 Write the introductory message for the patch series.
@@ -258,7 +264,9 @@
 Cc: bar
 
 
-files patched: 2
+ 2 files changed, 2 insertions(+), 0 deletions(-)
+ a |  1 +-
+ b |  1 +-
 
 Displaying [PATCH 1 of 2] a ...
 Content-Type: text/plain; charset="us-ascii"
@@ -274,7 +282,8 @@
 To: foo
 Cc: bar
 
-files patched: 1
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ a |  1 +-
 
 
 # HG changeset patch
@@ -304,7 +313,8 @@
 To: foo
 Cc: bar
 
-files patched: 1
+ 1 files changed, 1 insertions(+), 0 deletions(-)
+ b |  1 +-
 
 
 # HG changeset patch


More information about the Mercurial-devel mailing list