changeset 3821:ce4b7023a492 beta

diff parser: redefined operations stats for changes - don't loose info about multiple operations like rename + chmod - new Binary flag when dealing with binary file operations - fixed diffs after mercurial 2.6 when GIT binary diffs were fixed - added more tests for multiple operations - refactored the way diffprocessor returns data. It's now easier to extract type of operation on binary files - diffprocessor doesn't append that information into the diff itself
author Marcin Kuzminski <marcin@python-works.com>
date Wed, 08 May 2013 01:19:18 +0200
parents 8df1bc51aa9c
children b86229c66c67
files rhodecode/controllers/changeset.py rhodecode/controllers/compare.py rhodecode/controllers/feed.py rhodecode/controllers/pullrequests.py rhodecode/lib/diffs.py rhodecode/lib/helpers.py rhodecode/public/css/style.css rhodecode/templates/changeset/diff_block.html rhodecode/tests/fixtures/hg_diff_binary_and_normal.diff rhodecode/tests/fixtures/hg_diff_chmod_and_mod_single_binary_file.diff rhodecode/tests/fixtures/hg_diff_del_single_binary_file.diff rhodecode/tests/fixtures/hg_diff_mod_file_and_rename.diff rhodecode/tests/fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff rhodecode/tests/fixtures/hg_diff_rename_and_chmod_file.diff rhodecode/tests/models/test_diff_parsers.py
diffstat 15 files changed, 401 insertions(+), 127 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/controllers/changeset.py	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/controllers/changeset.py	Wed May 08 01:19:18 2013 +0200
@@ -265,9 +265,8 @@
                     c.limited_diff = True
                 for f in _parsed:
                     st = f['stats']
-                    if st[0] != 'b':
-                        c.lines_added += st[0]
-                        c.lines_deleted += st[1]
+                    c.lines_added += st['added']
+                    c.lines_deleted += st['deleted']
                     fid = h.FID(changeset.raw_id, f['filename'])
                     diff = diff_processor.as_html(enable_comments=enable_comments,
                                                   parsed_lines=[f])
--- a/rhodecode/controllers/compare.py	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/controllers/compare.py	Wed May 08 01:19:18 2013 +0200
@@ -258,9 +258,9 @@
         c.lines_deleted = 0
         for f in _parsed:
             st = f['stats']
-            if st[0] != 'b':
-                c.lines_added += st[0]
-                c.lines_deleted += st[1]
+            if not st['binary']:
+                c.lines_added += st['added']
+                c.lines_deleted += st['deleted']
             fid = h.FID('', f['filename'])
             c.files.append([fid, f['operation'], f['filename'], f['stats']])
             htmldiff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
--- a/rhodecode/controllers/feed.py	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/controllers/feed.py	Wed May 08 01:19:18 2013 +0200
@@ -76,8 +76,8 @@
             limited_diff = True
 
         for st in _parsed:
-            st.update({'added': st['stats'][0],
-                       'removed': st['stats'][1]})
+            st.update({'added': st['stats']['added'],
+                       'removed': st['stats']['deleted']})
             changes.append('\n %(operation)s %(filename)s '
                            '(%(added)s lines added, %(removed)s lines removed)'
                             % st)
--- a/rhodecode/controllers/pullrequests.py	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/controllers/pullrequests.py	Wed May 08 01:19:18 2013 +0200
@@ -213,13 +213,10 @@
 
         c.files = []
         c.changes = {}
-        c.lines_added = 0
-        c.lines_deleted = 0
         for f in _parsed:
             st = f['stats']
-            if st[0] != 'b':
-                c.lines_added += st[0]
-                c.lines_deleted += st[1]
+            c.lines_added += st['added']
+            c.lines_deleted += st['deleted']
             fid = h.FID('', f['filename'])
             c.files.append([fid, f['operation'], f['filename'], f['stats']])
             htmldiff = diff_processor.as_html(enable_comments=enable_comments,
--- a/rhodecode/lib/diffs.py	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/lib/diffs.py	Wed May 08 01:19:18 2013 +0200
@@ -128,6 +128,7 @@
 MOD_FILENODE = 3
 RENAMED_FILENODE = 4
 CHMOD_FILENODE = 5
+BIN_FILENODE = 6
 
 
 class DiffLimitExceeded(Exception):
@@ -166,6 +167,7 @@
         (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
         (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
             \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
+        (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
         (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
         (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
     """, re.VERBOSE | re.MULTILINE)
@@ -181,6 +183,7 @@
         (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
         (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
             \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
+        (?:^(?P<bin_patch>GIT[ ]binary[ ]patch)(?:\n|$))?
         (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
         (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
     """, re.VERBOSE | re.MULTILINE)
@@ -357,62 +360,84 @@
             head, diff = self._get_header(raw_diff)
 
             op = None
-            stats = None
-            msgs = []
+            stats = {
+                'added': 0,
+                'deleted': 0,
+                'binary': False,
+                'ops': {},
+            }
 
             if head['deleted_file_mode']:
                 op = 'D'
-                stats = ['b', DEL_FILENODE]
-                msgs.append('deleted file')
+                stats['binary'] = True
+                stats['ops'][DEL_FILENODE] = 'deleted file'
+
             elif head['new_file_mode']:
                 op = 'A'
-                stats = ['b', NEW_FILENODE]
-                msgs.append('new file %s' % head['new_file_mode'])
-            else:
+                stats['binary'] = True
+                stats['ops'][NEW_FILENODE] = 'new file %s' % head['new_file_mode']
+            else:  # modify operation, can be cp, rename, chmod
+                # CHMOD
                 if head['new_mode'] and head['old_mode']:
                     op = 'M'
-                    stats = ['b', CHMOD_FILENODE]
-                    msgs.append('modified file chmod %s => %s'
-                                  % (head['old_mode'], head['new_mode']))
+                    stats['binary'] = True
+                    stats['ops'][CHMOD_FILENODE] = ('modified file chmod %s => %s'
+                                        % (head['old_mode'], head['new_mode']))
+                # RENAME
                 if (head['rename_from'] and head['rename_to']
                       and head['rename_from'] != head['rename_to']):
                     op = 'M'
-                    stats = ['b', RENAMED_FILENODE] # might overwrite CHMOD_FILENODE
-                    msgs.append('file renamed from %s to %s'
-                                  % (head['rename_from'], head['rename_to']))
-                if op is None: # fall back: detect missed old style add or remove
+                    stats['binary'] = True
+                    stats['ops'][RENAMED_FILENODE] = ('file renamed from %s to %s'
+                                    % (head['rename_from'], head['rename_to']))
+
+                # FALL BACK: detect missed old style add or remove
+                if op is None:
                     if not head['a_file'] and head['b_file']:
                         op = 'A'
-                        stats = ['b', NEW_FILENODE]
-                        msgs.append('new file')
+                        stats['binary'] = True
+                        stats['ops'][NEW_FILENODE] = 'new file'
+
                     elif head['a_file'] and not head['b_file']:
                         op = 'D'
-                        stats = ['b', DEL_FILENODE]
-                        msgs.append('deleted file')
+                        stats['binary'] = True
+                        stats['ops'][DEL_FILENODE] = 'deleted file'
+
+                # it's not ADD not DELETE
                 if op is None:
                     op = 'M'
-                    stats = ['b', MOD_FILENODE]
+                    stats['binary'] = True
+                    stats['ops'][MOD_FILENODE] = 'modified file'
 
-            if head['a_file'] or head['b_file']: # a real diff
+            # a real non-binary diff
+            if head['a_file'] or head['b_file']:
                 try:
-                    chunks, stats = self._parse_lines(diff)
+                    chunks, _stats = self._parse_lines(diff)
+                    stats['binary'] = False
+                    stats['added'] = _stats[0]
+                    stats['deleted'] = _stats[1]
+                    # explicit mark that it's a modified file
+                    if op == 'M':
+                        stats['ops'][MOD_FILENODE] = 'modified file'
+
                 except DiffLimitExceeded:
-                    diff_container = lambda _diff: LimitedDiffContainer(
-                                                self.diff_limit,
-                                                self.cur_diff_size,
-                                                _diff)
+                    diff_container = lambda _diff: \
+                        LimitedDiffContainer(self.diff_limit,
+                                            self.cur_diff_size, _diff)
                     break
-            else: # GIT binary patch (or empty diff)
+            else:  # GIT binary patch (or empty diff)
+                # GIT Binary patch
+                if head['bin_patch']:
+                    stats['ops'][BIN_FILENODE] = 'binary diff not shown'
                 chunks = []
-                msgs.append('binary diff not shown') # or no diff because it was a rename or chmod or add/remove of empty file
 
-            if msgs:
-                chunks.insert(0, [{
-                    'old_lineno': '',
-                    'new_lineno': '',
-                    'action':     'binary',
-                    'line':       msg,
-                    } for msg in msgs])
+            chunks.insert(0, [{
+                'old_lineno': '',
+                'new_lineno': '',
+                'action':     'context',
+                'line':       msg,
+                } for _op, msg in stats['ops'].iteritems()
+                  if _op not in [MOD_FILENODE]])
 
             _files.append({
                 'filename':         head['b_path'],
--- a/rhodecode/lib/helpers.py	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/lib/helpers.py	Wed May 08 01:19:18 2013 +0200
@@ -1078,6 +1078,9 @@
 
     :param stats: two element list of added/deleted lines of code
     """
+    from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
+        MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
+
     def cgen(l_type, a_v, d_v):
         mapping = {'tr': 'top-right-rounded-corner-mid',
                    'tl': 'top-left-rounded-corner-mid',
@@ -1098,16 +1101,41 @@
         if l_type == 'd' and not a_v:
             return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
 
-    a, d = stats[0], stats[1]
+    a, d = stats['added'], stats['deleted']
     width = 100
 
-    if a == 'b':
+    if stats['binary']:
         #binary mode
-        b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
-        b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
+        lbl = ''
+        bin_op = 1
+
+        if BIN_FILENODE in stats['ops']:
+            lbl = 'bin+'
+
+        if NEW_FILENODE in stats['ops']:
+            lbl += _('new file')
+            bin_op = NEW_FILENODE
+        elif MOD_FILENODE in stats['ops']:
+            lbl += _('mod')
+            bin_op = MOD_FILENODE
+        elif DEL_FILENODE in stats['ops']:
+            lbl += _('del')
+            bin_op = DEL_FILENODE
+        elif RENAMED_FILENODE in stats['ops']:
+            lbl += _('rename')
+            bin_op = RENAMED_FILENODE
+
+        #chmod can go with other operations
+        if CHMOD_FILENODE in stats['ops']:
+            _org_lbl = _('chmod')
+            lbl += _org_lbl if lbl.endswith('+') else '+%s' % _org_lbl
+
+        #import ipdb;ipdb.set_trace()
+        b_d = '<div class="bin bin%s %s" style="width:100%%">%s</div>' % (bin_op, cgen('a', a_v='', d_v=0), lbl)
+        b_a = '<div class="bin bin1" style="width:0%%"></div>'
         return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
 
-    t = stats[0] + stats[1]
+    t = stats['added'] + stats['deleted']
     unit = float(width) / (t or 1)
 
     # needs > 9% of width to be visible or 0 to be hidden
--- a/rhodecode/public/css/style.css	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/public/css/style.css	Wed May 08 01:19:18 2013 +0200
@@ -2404,42 +2404,49 @@
     font-size: 9px;
     padding: 2px 0px 2px 0px;
 }
-/*new binary*/
-.cs_files .changes .bin1 {
+/*new binary
+NEW_FILENODE = 1
+DEL_FILENODE = 2
+MOD_FILENODE = 3
+RENAMED_FILENODE = 4
+CHMOD_FILENODE = 5
+BIN_FILENODE = 6
+*/
+.cs_files .changes .bin {
     background-color: #BBFFBB;
     float: left;
     text-align: center;
     font-size: 9px;
     padding: 2px 0px 2px 0px;
 }
+.cs_files .changes .bin.bin1 {
+    background-color: #BBFFBB;
+}
 
 /*deleted binary*/
-.cs_files .changes .bin2 {
+.cs_files .changes .bin.bin2 {
     background-color: #FF8888;
-    float: left;
-    text-align: center;
-    font-size: 9px;
-    padding: 2px 0px 2px 0px;
 }
 
 /*mod binary*/
-.cs_files .changes .bin3 {
+.cs_files .changes .bin.bin3 {
     background-color: #DDDDDD;
-    float: left;
-    text-align: center;
-    font-size: 9px;
-    padding: 2px 0px 2px 0px;
 }
 
 /*rename file*/
-.cs_files .changes .bin4 {
+.cs_files .changes .bin.bin4 {
+    background-color: #6D99FF;
+}
+
+/*rename file*/
+.cs_files .changes .bin.bin4 {
     background-color: #6D99FF;
-    float: left;
-    text-align: center;
-    font-size: 9px;
-    padding: 2px 0px 2px 0px;
-}
-
+}
+
+/*chmod file*/
+.cs_files .changes .bin.bin5 {
+    background-color: #6D99FF;
+}
 
 .cs_files .cs_added, .cs_files .cs_A {
     background: url("../images/icons/page_white_add.png") no-repeat scroll
--- a/rhodecode/templates/changeset/diff_block.html	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/templates/changeset/diff_block.html	Wed May 08 01:19:18 2013 +0200
@@ -9,7 +9,6 @@
 </div>
 <div class="diff-container" id="${'diff-container-%s' % (id(change))}">
 %for FID,(cs1, cs2, change, path, diff, stats) in change.iteritems():
-    ##%if op !='removed':
     <div id="${FID}_target" style="clear:both;margin-top:25px"></div>
     <div id="${FID}" class="diffblock  margined comm">
         <div class="code-header">
@@ -38,7 +37,6 @@
             ${diff|n}
         </div>
     </div>
-    ##%endif
 %endfor
 </div>
 </%def>
--- a/rhodecode/tests/fixtures/hg_diff_binary_and_normal.diff	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/tests/fixtures/hg_diff_binary_and_normal.diff	Wed May 08 01:19:18 2013 +0200
@@ -9,7 +9,11 @@
 
 diff --git a/img/baseline-20px.png b/img/baseline-20px.png
 deleted file mode 100644
-Binary file img/baseline-20px.png has changed
+index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+Hc$@<O00001
+
 diff --git a/index.html b/index.html
 --- a/index.html
 +++ b/index.html
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/fixtures/hg_diff_chmod_and_mod_single_binary_file.diff	Wed May 08 01:19:18 2013 +0200
@@ -0,0 +1,18 @@
+diff --git a/gravatar.png b/gravatar.png
+old mode 100644
+new mode 100755
+index 54d2d129e3372c45a4c68a742e269864222e99ae..0c1d65410bd51fdff935443e01931c9fc3ad7def
+GIT binary patch
+literal 740
+zc%17D@N?(olHy`uVBq!ia0vp^dx3Z#2NRHduQ>B0kYX$ja(7}_cTVOdki%Kv5n0T@
+zz%2~Ij105pNB{-dOFVsD*>AH+2@7*F&3eGWz`&H|>EaktaqI0JMZd!V5(hqpU(%ki
+zz<{N_Dc~^g1GeA{8S#b#jBU<`Ij0FINT{SO2`=WI*Kku%Kte?~QvUtk`~FuatbIH`
+zU*0#<bY2fr$(Coz4(2b|?k<qNz`M&4#A?WcvPzmkEJg29#pjiUe<s|iw%c!Ml>foD
+zHLr#RtfPb(q61ZL^D3dLy27sS9LG1ks-ApS8e$A1NIi%J(b=2_we?fTsn6Zh<#UVP
+z|9tiF%$|eaMRt7@#I6)<9go)1-`(@)=Q(p!@3vNKDKmRt(o@-7dHLx1lNY~Ad}g~_
+za{b)1``zCC$!DbBm~WE-irmOC$k!;|zwzb_^B2daEtWo!yMN=;86Y!nbQtDu5Wn34
+zbmZh?b(0@$%sb%=43(hD^~>9*yZ+6yNtdtp|J$sq{^jrc-CO$Jn%~HOWByJ0bK9TY
+zg@4_kydS*R^JDe{4Z08FKRNbQ`g%Ud?r+k+d3UblwmiT6sL83<_jKRAJ-Pr-V6<tp
+z?epIfY2R5iFZ#Ccl<T?EX78;p`&F5H??E-!-5(q%k>Y5MY!uto=R2=&d?)y?yZLYL
+zJ^SCEe$Bh{bKS3dDW9<fAX0p_*<5X%lMalj*{B8|p0JPMtzFP9%Uvy?WXj;_>gTe~
+RDWNH`I3+QqIFSoR0|1y<SJMCh
\ No newline at end of file
--- a/rhodecode/tests/fixtures/hg_diff_del_single_binary_file.diff	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/tests/fixtures/hg_diff_del_single_binary_file.diff	Wed May 08 01:19:18 2013 +0200
@@ -1,3 +1,6 @@
 diff --git a/US Warszawa.jpg b/US Warszawa.jpg
 deleted file mode 100755
-Binary file US Warszawa.jpg has changed
+index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000
+GIT binary patch
+literal 0
+Hc$@<O00001
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/fixtures/hg_diff_mod_file_and_rename.diff	Wed May 08 01:19:18 2013 +0200
@@ -0,0 +1,10 @@
+diff --git a/README b/README.rst
+rename from README
+rename to README.rst
+--- a/README
++++ b/README.rst
+@@ -1,1 +1,4 @@
+ readme2
++line 1
++ line2
++
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/fixtures/hg_diff_mod_single_file_and_rename_and_chmod.diff	Wed May 08 01:19:18 2013 +0200
@@ -0,0 +1,15 @@
+diff --git a/README.rst b/README
+old mode 100755
+new mode 100644
+rename from README.rst
+rename to README
+--- a/README.rst
++++ b/README
+@@ -1,4 +1,7 @@
+ readme2
+ line 1
+  line2
+
++line 1
++ line2
++
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/tests/fixtures/hg_diff_rename_and_chmod_file.diff	Wed May 08 01:19:18 2013 +0200
@@ -0,0 +1,5 @@
+diff --git a/README.rst b/README
+old mode 100644
+new mode 100755
+rename from README.rst
+rename to README
\ No newline at end of file
--- a/rhodecode/tests/models/test_diff_parsers.py	Wed Apr 10 03:00:20 2013 +0200
+++ b/rhodecode/tests/models/test_diff_parsers.py	Wed May 08 01:19:18 2013 +0200
@@ -3,84 +3,249 @@
 import unittest
 from rhodecode.tests import *
 from rhodecode.lib.diffs import DiffProcessor, NEW_FILENODE, DEL_FILENODE, \
-    MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE
+    MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
 
 dn = os.path.dirname
 FIXTURES = os.path.join(dn(dn(os.path.abspath(__file__))), 'fixtures')
 
 DIFF_FIXTURES = {
     'hg_diff_add_single_binary_file.diff': [
-        (u'US Warszawa.jpg', 'A', ['b', NEW_FILENODE]),
+        ('US Warszawa.jpg', 'A',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {NEW_FILENODE: 'new file 100755',
+                  BIN_FILENODE: 'binary diff not shown'}}),
     ],
     'hg_diff_mod_single_binary_file.diff': [
-        (u'US Warszawa.jpg', 'M', ['b', MOD_FILENODE]),
+        ('US Warszawa.jpg', 'M',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {MOD_FILENODE: 'modified file',
+                  BIN_FILENODE: 'binary diff not shown'}}),
+    ],
+
+    'hg_diff_mod_single_file_and_rename_and_chmod.diff': [
+        ('README', 'M',
+         {'added': 3,
+          'deleted': 0,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file',
+                  RENAMED_FILENODE: 'file renamed from README.rst to README',
+                  CHMOD_FILENODE: 'modified file chmod 100755 => 100644'}}),
+    ],
+    'hg_diff_rename_and_chmod_file.diff': [
+        ('README', 'M',
+         {'added': 3,
+          'deleted': 0,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file',
+                  BIN_FILENODE: 'binary diff not shown'}}),
     ],
     'hg_diff_del_single_binary_file.diff': [
-        (u'US Warszawa.jpg', 'D', ['b', DEL_FILENODE]),
+        ('US Warszawa.jpg', 'D',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {DEL_FILENODE: 'deleted file',
+                  BIN_FILENODE: 'binary diff not shown'}}),
+    ],
+    'hg_diff_chmod_and_mod_single_binary_file.diff': [
+        ('gravatar.png', 'M',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {CHMOD_FILENODE: 'modified file chmod 100644 => 100755',
+                  BIN_FILENODE: 'binary diff not shown'}}),
+    ],
+    'hg_diff_chmod.diff': [
+        ('file', 'M',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {CHMOD_FILENODE: 'modified file chmod 100755 => 100644'}}),
+    ],
+    'hg_diff_rename_file.diff': [
+        ('file_renamed', 'M',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {RENAMED_FILENODE: 'file renamed from file to file_renamed'}}),
+    ],
+    'hg_diff_rename_and_chmod_file.diff': [
+        ('README', 'M',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {CHMOD_FILENODE: 'modified file chmod 100644 => 100755',
+                  RENAMED_FILENODE: 'file renamed from README.rst to README'}}),
     ],
     'hg_diff_binary_and_normal.diff': [
-        (u'img/baseline-10px.png', 'A', ['b', NEW_FILENODE]),
-        (u'js/jquery/hashgrid.js', 'A', [340, 0]),
-        (u'index.html',            'M', [3, 2]),
-        (u'less/docs.less',        'M', [34, 0]),
-        (u'less/scaffolding.less', 'M', [1, 3]),
-        (u'readme.markdown',       'M', [1, 10]),
-        (u'img/baseline-20px.png', 'D', ['b', DEL_FILENODE]),
-        (u'js/global.js',          'D', [0, 75])
-    ],
-    'hg_diff_chmod.diff': [
-        (u'file', 'M', ['b', CHMOD_FILENODE]),
-    ],
-    'hg_diff_rename_file.diff': [
-        (u'file_renamed', 'M', ['b', RENAMED_FILENODE]),
+        ('img/baseline-10px.png', 'A',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {NEW_FILENODE: 'new file 100644',
+                  BIN_FILENODE: 'binary diff not shown'}}),
+        ('js/jquery/hashgrid.js', 'A',
+         {'added': 340,
+          'deleted': 0,
+          'binary': False,
+          'ops': {NEW_FILENODE: 'new file 100755'}}),
+        ('index.html', 'M',
+         {'added': 3,
+          'deleted': 2,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('less/docs.less', 'M',
+         {'added': 34,
+          'deleted': 0,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('less/scaffolding.less', 'M',
+         {'added': 1,
+          'deleted': 3,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('readme.markdown', 'M',
+         {'added': 1,
+          'deleted': 10,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('img/baseline-20px.png', 'D',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {DEL_FILENODE: 'deleted file',
+                  BIN_FILENODE: 'binary diff not shown'}}),
+        ('js/global.js', 'D',
+         {'added': 0,
+          'deleted': 75,
+          'binary': False,
+          'ops': {DEL_FILENODE: 'deleted file'}})
     ],
     'git_diff_chmod.diff': [
-        (u'work-horus.xls', 'M', ['b', CHMOD_FILENODE]),
+        ('work-horus.xls', 'M',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {CHMOD_FILENODE: 'modified file chmod 100644 => 100755'}})
     ],
     'git_diff_rename_file.diff': [
-        (u'file.xls', 'M', ['b', RENAMED_FILENODE]),
+        ('file.xls', 'M',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {RENAMED_FILENODE: 'file renamed from work-horus.xls to file.xls'}})
     ],
     'git_diff_mod_single_binary_file.diff': [
-        ('US Warszawa.jpg', 'M', ['b', MOD_FILENODE])
-
+        ('US Warszawa.jpg', 'M',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {MOD_FILENODE: 'modified file',
+                  BIN_FILENODE: 'binary diff not shown'}})
     ],
     'git_diff_binary_and_normal.diff': [
-        (u'img/baseline-10px.png', 'A', ['b', NEW_FILENODE]),
-        (u'js/jquery/hashgrid.js', 'A', [340, 0]),
-        (u'index.html',            'M', [3, 2]),
-        (u'less/docs.less',        'M', [34, 0]),
-        (u'less/scaffolding.less', 'M', [1, 3]),
-        (u'readme.markdown',       'M', [1, 10]),
-        (u'img/baseline-20px.png', 'D', ['b', DEL_FILENODE]),
-        (u'js/global.js',          'D', [0, 75])
+        ('img/baseline-10px.png', 'A',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {NEW_FILENODE: 'new file 100644',
+                  BIN_FILENODE: 'binary diff not shown'}}),
+        ('js/jquery/hashgrid.js', 'A',
+         {'added': 340,
+          'deleted': 0,
+          'binary': False,
+          'ops': {NEW_FILENODE: 'new file 100755'}}),
+        ('index.html', 'M',
+         {'added': 3,
+          'deleted': 2,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('less/docs.less', 'M',
+         {'added': 34,
+          'deleted': 0,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('less/scaffolding.less', 'M',
+         {'added': 1,
+          'deleted': 3,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('readme.markdown', 'M',
+         {'added': 1,
+          'deleted': 10,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('img/baseline-20px.png', 'D',
+         {'added': 0,
+          'deleted': 0,
+          'binary': True,
+          'ops': {DEL_FILENODE: 'deleted file',
+                  BIN_FILENODE: 'binary diff not shown'}}),
+        ('js/global.js', 'D',
+         {'added': 0,
+          'deleted': 75,
+          'binary': False,
+          'ops': {DEL_FILENODE: 'deleted file'}}),
     ],
     'diff_with_diff_data.diff': [
-        (u'vcs/backends/base.py', 'M', [18, 2]),
-        (u'vcs/backends/git/repository.py', 'M', [46, 15]),
-        (u'vcs/backends/hg.py', 'M', [22, 3]),
-        (u'vcs/tests/test_git.py', 'M', [5, 5]),
-        (u'vcs/tests/test_repository.py', 'M', [174, 2])
+        ('vcs/backends/base.py', 'M',
+         {'added': 18,
+          'deleted': 2,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('vcs/backends/git/repository.py', 'M',
+         {'added': 46,
+          'deleted': 15,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('vcs/backends/hg.py', 'M',
+         {'added': 22,
+          'deleted': 3,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('vcs/tests/test_git.py', 'M',
+         {'added': 5,
+          'deleted': 5,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
+        ('vcs/tests/test_repository.py', 'M',
+         {'added': 174,
+          'deleted': 2,
+          'binary': False,
+          'ops': {MOD_FILENODE: 'modified file'}}),
     ],
-#    'large_diff.diff': [
-#
-#    ],
-
+#     'large_diff.diff': [
+#         ('.hgignore', 'A', {'deleted': 0, 'binary': False, 'added': 3, 'ops': {1: 'new file 100644'}}),
+#         ('MANIFEST.in', 'A', {'deleted': 0, 'binary': False, 'added': 3, 'ops': {1: 'new file 100644'}}),
+#         ('README.txt', 'A', {'deleted': 0, 'binary': False, 'added': 19, 'ops': {1: 'new file 100644'}}),
+#         ('development.ini', 'A', {'deleted': 0, 'binary': False, 'added': 116, 'ops': {1: 'new file 100644'}}),
+#         ('docs/index.txt', 'A', {'deleted': 0, 'binary': False, 'added': 19, 'ops': {1: 'new file 100644'}}),
+#         ('ez_setup.py', 'A', {'deleted': 0, 'binary': False, 'added': 276, 'ops': {1: 'new file 100644'}}),
+#         ('hgapp.py', 'A', {'deleted': 0, 'binary': False, 'added': 26, 'ops': {1: 'new file 100644'}}),
+#         ('hgwebdir.config', 'A', {'deleted': 0, 'binary': False, 'added': 21, 'ops': {1: 'new file 100644'}}),
+#         ('pylons_app.egg-info/PKG-INFO', 'A', {'deleted': 0, 'binary': False, 'added': 10, 'ops': {1: 'new file 100644'}}),
+#         ('pylons_app.egg-info/SOURCES.txt', 'A', {'deleted': 0, 'binary': False, 'added': 33, 'ops': {1: 'new file 100644'}}),
+#         ('pylons_app.egg-info/dependency_links.txt', 'A', {'deleted': 0, 'binary': False, 'added': 1, 'ops': {1: 'new file 100644'}}),
+#         #TODO:
+#     ],
 
 }
 
 
-def _diff_checker(fixture):
-    with open(os.path.join(FIXTURES, fixture)) as f:
-        diff = f.read()
+class DiffLibTest(unittest.TestCase):
+
+    @parameterized.expand([(x,) for x in DIFF_FIXTURES])
+    def test_diff(self, diff_fixture):
 
-    diff_proc = DiffProcessor(diff)
-    diff_proc_d = diff_proc.prepare()
-    data = [(x['filename'], x['operation'], x['stats']) for x in diff_proc_d]
-    expected_data = DIFF_FIXTURES[fixture]
+        with open(os.path.join(FIXTURES, diff_fixture)) as f:
+            diff = f.read()
 
-    assert expected_data == data
-
-
-def test_parse_diff():
-    for fixture in DIFF_FIXTURES:
-        yield _diff_checker, fixture
+        diff_proc = DiffProcessor(diff)
+        diff_proc_d = diff_proc.prepare()
+        data = [(x['filename'], x['operation'], x['stats']) for x in diff_proc_d]
+        expected_data = DIFF_FIXTURES[diff_fixture]
+        self.assertListEqual(expected_data, data)