diff --git a/.gitignore b/.gitignore
index b4e9c1d..f04c045 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,8 @@ pip-log.txt
.coverage
.tox
nosetests.xml
+.noseids
+.hypothesis
# Translations
*.mo
diff --git a/.travis.yml b/.travis.yml
index 3c10227..e96222f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,27 @@
+sudo: false
language: python
-python:
- - "2.6"
- - "2.7"
- - "3.3"
- - "3.4"
- - "3.5"
+python: "3.5"
+env:
+ - TOX_ENV=py27
+ - TOX_ENV=py34
+ - TOX_ENV=py35
+matrix:
+ include:
+ - python: 3.6
+ env: TOX_ENV=py36
+ include:
+ - python: 3.7
+ env: TOX_ENV=py37
+ include:
+ - python: 3.7
+ env: TOX_ENV=lint
+ include:
+ - python: 2.7
+ env: TOX_ENV=lint
addons:
apt:
packages:
- ed
-install:
- - pip install .
-script: nosetests
+install: pip install tox
+script: tox -e $TOX_ENV
+cache: pip
diff --git a/README.rst b/README.rst
index 3565fbc..5e3d91e 100644
--- a/README.rst
+++ b/README.rst
@@ -4,8 +4,7 @@ What The Patch!?
.. image:: https://travis-ci.org/cscorley/whatthepatch.svg?style=flat
:target: https://travis-ci.org/cscorley/whatthepatch
-What The Patch!? is a library for parsing patch files. Its only purpose is to
-read a patch file and get it into some usable form by other programs.
+What The Patch!? is a library for both parsing and applying patch files.
Features
---------
@@ -71,47 +70,41 @@ each diff in the patch:
.. code-block:: python
>>> import whatthepatch
- >>> with open('somechanges.patch') as f:
+ >>> import pprint
+ >>> with open('tests/casefiles/diff-unified.diff') as f:
... text = f.read()
...
>>> for diff in whatthepatch.parse_patch(text):
- ... print(diff)
+ ... print(diff) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
...
- diff(header=header(
- index_path=None,
- old_path='lao',
- old_version='2012-12-26 23:16:54.000000000 -0600',
- new_path='tzu',
- new_version='2012-12-26 23:16:50.000000000 -0600'
- ),
- changes=[
- (1, None, 'The Way that can be told of is not the eternal Way;'),
- (2, None, 'The name that can be named is not the eternal name.'),
- (3, 1, 'The Nameless is the origin of Heaven and Earth;'),
- (4, None, 'The Named is the mother of all things.'),
- (None, 2, 'The named is the mother of all things.'),
- (None, 3, ''),
- (5, 4, 'Therefore let there always be non-being,'),
- (6, 5, ' so we may see their subtlety,'),
- (7, 6, 'And let there always be being,'),
- (9, 8, 'The two are the same,'),
- (10, 9, 'But after they are produced,'),
- (11, 10, ' they have different names.'),
- (None, 11, 'They both may be called deep and profound.'),
- (None, 12, 'Deeper and more profound,'),
- (None, 13, 'The door of all subtleties!')
- ]
- )
-
-*Edited to show structure of the results*
+ diff(header=header(index_path=None,
+ old_path='lao',
+ old_version='2013-01-05 16:56:19.000000000 -0600',
+ new_path='tzu',
+ new_version='2013-01-05 16:56:35.000000000 -0600'),
+ changes=[Change(old=1, new=None, hunk=1, line='The Way that can be told of is not the eternal Way;'),
+ Change(old=2, new=None, hunk=1, line='The name that can be named is not the eternal name.'),
+ Change(old=3, new=1, hunk=1, line='The Nameless is the origin of Heaven and Earth;'),
+ Change(old=4, new=None, hunk=1, line='The Named is the mother of all things.'),
+ Change(old=None, new=2, hunk=1, line='The named is the mother of all things.'),
+ Change(old=None, new=3, hunk=1, line=''), Change(old=5, new=4, hunk=1, line='Therefore let there always be non-being,'),
+ Change(old=6, new=5, hunk=1, line=' so we may see their subtlety,'),
+ Change(old=7, new=6, hunk=1, line='And let there always be being,'),
+ Change(old=9, new=8, hunk=2, line='The two are the same,'),
+ Change(old=10, new=9, hunk=2, line='But after they are produced,'),
+ Change(old=11, new=10, hunk=2, line=' they have different names.'),
+ Change(old=None, new=11, hunk=2, line='They both may be called deep and profound.'),
+ Change(old=None, new=12, hunk=2, line='Deeper and more profound,'),
+ Change(old=None, new=13, hunk=2, line='The door of all subtleties!')],
+ text='...')
The changes are listed as they are in the patch, but instead of the +/- syntax
of the patch, we get a tuple of two numbers and the text of the line.
What these numbers indicate are as follows:
-#. ``( 1, None, ... )`` indicates line 1 of the file lao was **removed**.
-#. ``( None, 2, ... )`` indicates line 2 of the file tzu was **inserted**.
-#. ``( 5, 4, ... )`` indicates that line 5 of lao and line 4 of tzu are **equal**.
+#. ``( old=1, new=None, ... )`` indicates line 1 of the file lao was **removed**.
+#. ``( old=None, new=2, ... )`` indicates line 2 of the file tzu was **inserted**.
+#. ``( old=5, new=4, ... )`` indicates that line 5 of lao and line 4 of tzu are **equal**.
Please note that not all patch formats provide the actual lines modified, so some
results will have the text portion of the tuple set to ``None``.
@@ -124,15 +117,29 @@ To apply a diff to some lines of text, first read the patch and parse it.
.. code-block:: python
>>> import whatthepatch
- >>> with open('somechanges.patch') as f:
+ >>> with open('tests/casefiles/diff-default.diff') as f:
... text = f.read()
...
- >>> with open('lao') as f:
+ >>> with open('tests/casefiles/lao') as f:
... lao = f.read()
...
>>> diff = [x for x in whatthepatch.parse_patch(text)]
>>> diff = diff[0]
>>> tzu = whatthepatch.apply_diff(diff, lao)
+ >>> tzu # doctest: +NORMALIZE_WHITESPACE
+ ['The Nameless is the origin of Heaven and Earth;',
+ 'The named is the mother of all things.',
+ '',
+ 'Therefore let there always be non-being,',
+ ' so we may see their subtlety,',
+ 'And let there always be being,',
+ ' so we may see their outcome.',
+ 'The two are the same,',
+ 'But after they are produced,',
+ ' they have different names.',
+ 'They both may be called deep and profound.',
+ 'Deeper and more profound,',
+ 'The door of all subtleties!']
Contribute
diff --git a/setup.py b/setup.py
index 97696d3..3db4ae6 100644
--- a/setup.py
+++ b/setup.py
@@ -37,12 +37,11 @@
"Topic :: Text Processing",
"Programming Language :: Python",
"Programming Language :: Python :: 2",
- "Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Programming Language :: Python :: 3.7",
],
)
-
diff --git a/tests/casefiles/diff-unified-bad.diff b/tests/casefiles/diff-unified-bad.diff
new file mode 100644
index 0000000..376d751
--- /dev/null
+++ b/tests/casefiles/diff-unified-bad.diff
@@ -0,0 +1,19 @@
+--- lao 2013-01-05 16:56:19.000000000 -0600
++++ tzu 2013-01-05 16:56:35.000000000 -0600
+@@ -1,7 +1,6 @@
+-The Way that can be told of is not the eternal Way;
+-The name that can be named is not the eternal name.
+ The Nameless is the origin of Heaven and Earth;
+-The Named is the mother of all tings.
++The named is the mother of all things.
++
+ Therefore let there always be non-being,
+ so we may see their subtlety,
+ And let there always be being,
+@@ -9,3 +8,6 @@
+ The two are the same,
+ But after they are produced,
+ they have different names.
++They both may be called deep and profound.
++Deeper and more profound,
++The door of all subtleties!
diff --git a/tests/casefiles/diff-unified-bad2.diff b/tests/casefiles/diff-unified-bad2.diff
new file mode 100644
index 0000000..dc6a97c
--- /dev/null
+++ b/tests/casefiles/diff-unified-bad2.diff
@@ -0,0 +1,19 @@
+--- lao 2013-01-05 16:56:19.000000000 -0600
++++ tzu 2013-01-05 16:56:35.000000000 -0600
+@@ -1,7 +1,6 @@
+-The Way that can be told of is not the eternal Way;
+-The name that can be named is not the eternal name.
+ The Nameless is the origin of Heaven and Earth;
+-The Named is the mother of all things.
++The named is the mother of all things.
++
+ Therefore let there always be non-being,
+ so we may see their subtlety,
+ And let there always be being,
+@@ -9,3 +8,6 @@
+ The two are te same,
+ But after they are produced,
+ they have different names.
++They both may be called deep and profound.
++Deeper and more profound,
++The door of all subtleties!
diff --git a/tests/test_apply.py b/tests/test_apply.py
index 2a7dfed..465040b 100644
--- a/tests/test_apply.py
+++ b/tests/test_apply.py
@@ -1,10 +1,25 @@
# -*- coding: utf-8 -*-
+import unittest
+import difflib
+
+from hypothesis import given, assume
+from hypothesis import strategies as st
+from nose.tools import assert_raises
+
+from whatthepatch import exceptions
import whatthepatch as wtp
-import unittest
-from io import StringIO
+def _apply(src, diff_text, reverse=False, use_patch=False):
+ diff = list(wtp.parse_patch(diff_text))
+ assert len(diff) == 1
+ return wtp.apply.apply_diff(diff[0], src, reverse, use_patch)
+
+
+def _apply_r(src, diff_text, reverse=True, use_patch=False):
+ return _apply(src, diff_text, reverse, use_patch)
+
class ApplyTestSuite(unittest.TestCase):
"""Basic test cases."""
@@ -26,62 +41,141 @@ def test_diff_default(self):
with open('tests/casefiles/diff-default.diff') as f:
diff_text = f.read()
- diff = next(wtp.parse_patch(diff_text))
-
- new_text = wtp.apply.apply_diff(diff, self.lao)
- self.assertEqual(new_text, self.tzu)
+ self.assertEqual(_apply(self.lao, diff_text), self.tzu)
+ self.assertEqual(_apply_r(self.tzu, diff_text), self.lao)
def test_diff_context(self):
with open('tests/casefiles/diff-context.diff') as f:
diff_text = f.read()
- diff = next(wtp.parse_patch(diff_text))
-
- new_text = wtp.apply.apply_diff(diff, self.lao)
- self.assertEqual(new_text, self.tzu)
+ self.assertEqual(_apply(self.lao, diff_text), self.tzu)
+ self.assertEqual(_apply_r(self.tzu, diff_text), self.lao)
def test_diff_unified(self):
with open('tests/casefiles/diff-unified.diff') as f:
diff_text = f.read()
- diff = next(wtp.parse_patch(diff_text))
+ self.assertEqual(_apply(self.lao, diff_text), self.tzu)
+ self.assertEqual(_apply_r(self.tzu, diff_text), self.lao)
- new_text = wtp.apply.apply_diff(diff, self.lao)
+ def test_diff_unified_bad(self):
+ with open('tests/casefiles/diff-unified-bad.diff') as f:
+ diff_text = f.read()
- self.assertEqual(new_text, self.tzu)
+ with assert_raises(exceptions.ApplyException) as ec:
+ _apply(self.lao, diff_text)
+
+ e = ec.exception
+ e_str = str(e)
+ assert 'line 4' in e_str
+ assert 'The Named is the mother of all tings.' in e_str
+ assert 'The Named is the mother of all things.' in e_str
+ assert e.hunk == 1
+
+ def test_diff_unified_bad2(self):
+ with open('tests/casefiles/diff-unified-bad2.diff') as f:
+ diff_text = f.read()
+
+ with assert_raises(exceptions.ApplyException) as ec:
+ _apply(self.lao, diff_text)
+
+ e = ec.exception
+ e_str = str(e)
+ assert 'line 9' in e_str
+ assert 'The two are te same,' in e_str
+ assert 'The two are the same,' in e_str
+ assert e.hunk == 2
+
+ def test_diff_unified_bad_backward(self):
+ with open('tests/casefiles/diff-unified-bad2.diff') as f:
+ diff_text = f.read()
+
+ with assert_raises(exceptions.ApplyException) as ec:
+ _apply(self.tzu, diff_text)
+
+ e = ec.exception
+ e_str = str(e)
+ assert 'line 1' in e_str
+ assert 'The Way that can be told of is not the eternal Way;' in e_str
+ assert 'The Nameless is the origin of Heaven and Earth;' in e_str
+ assert e.hunk == 1
+
+ def test_diff_unified_bad_empty_source(self):
+ with open('tests/casefiles/diff-unified-bad2.diff') as f:
+ diff_text = f.read()
+
+ with assert_raises(exceptions.ApplyException) as ec:
+ _apply('', diff_text)
+
+ e = ec.exception
+ e_str = str(e)
+ assert 'line 1' in e_str
+ assert 'The Way that can be told of is not the eternal Way;' in e_str
+ assert 'does not exist in source'
+ assert e.hunk == 1
def test_diff_unified_patchutil(self):
with open('tests/casefiles/diff-unified.diff') as f:
diff_text = f.read()
- diff = next(wtp.parse_patch(diff_text))
+ self.assertEqual(
+ _apply(self.lao, diff_text, use_patch=True),
+ (self.tzu, None),
+ )
+ self.assertEqual(
+ _apply_r(self.tzu, diff_text, use_patch=True),
+ (self.lao, None),
+ )
- new_text = wtp.apply.apply_diff(diff, self.lao, use_patch=True)
+ new_text = _apply(self.lao, diff_text, use_patch=True)
self.assertEqual(new_text, (self.tzu, None))
- self.assertRaises(AssertionError, wtp.apply.apply_diff, diff, [''] + self.lao, use_patch=True)
+ with assert_raises(exceptions.ApplyException):
+ _apply([''] + self.lao, diff_text, use_patch=True)
def test_diff_rcs(self):
with open('tests/casefiles/diff-rcs.diff') as f:
diff_text = f.read()
- diff = next(wtp.parse_patch(diff_text))
+ new_text = _apply(self.lao, diff_text)
- new_text = wtp.apply.apply_diff(diff, self.lao)
self.assertEqual(new_text, self.tzu)
def test_diff_ed(self):
- self.maxDiff = None
with open('tests/casefiles/diff-ed.diff') as f:
diff_text = f.read()
- diff = next(wtp.parse_patch(diff_text))
+ new_text = _apply(self.lao, diff_text)
+ self.assertEqual(self.tzu, new_text)
- new_text = wtp.apply.apply_diff(diff, self.lao)
- self.assertEqual(self.tzu,new_text)
-
- new_text = wtp.apply.apply_diff(diff, self.lao, use_patch=True)
+ new_text = _apply(self.lao, diff_text, use_patch=True)
self.assertEqual(new_text, (self.tzu, None))
+ @given(
+ st.text(), st.text(), st.text(), st.text(), st.datetimes(),
+ st.datetimes(), st.integers(),
+ )
+ def test_apply_reverse(self, a, b, fromfile, tofile, fromdate, todate, n):
+ diff = list(difflib.unified_diff(
+ a.split('\n'),
+ b.split('\n'),
+ fromfile=fromfile,
+ tofile=tofile,
+ fromfiledate=fromdate.isoformat(),
+ tofiledate=todate.isoformat(),
+ n=n,
+ lineterm='',
+ ))
+ assume(diff)
+ a_str = a
+ b_str = b
+
+ self.assertEqual(_apply(a_str.split('\n'), diff[:]), b_str.split('\n'))
+ self.assertEqual(
+ _apply_r(b_str.split('\n'), diff[:]),
+ a_str.split('\n'),
+ )
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/test_patch.py b/tests/test_patch.py
index e1e0744..8728249 100644
--- a/tests/test_patch.py
+++ b/tests/test_patch.py
@@ -1,18 +1,115 @@
# -*- coding: utf-8 -*-
import whatthepatch as wtp
+from whatthepatch.patch import Change, diffobj, header as headerobj
import unittest
import os
-from io import StringIO
module_path = os.path.dirname(__file__)
-datapath = lambda fname: os.path.join(module_path, 'casefiles', fname)
+
+
+def datapath(fname):
+ return os.path.join(module_path, 'casefiles', fname)
+
+
+def indent(amount, changes):
+ indent_str = ' ' * amount
+ return [(l, r, indent_str + t if t else t) for (l, r, t) in changes]
+
+
+CSC_CHANGES = [
+ (
+ None,
+ 1,
+ '# This is a basic script I wrote to run Bugxplore over the dataset',
+ ),
+ (None, 2, ''),
+ (None, 3, ''),
+ (1, 4, 'import os'),
+ (2, 5, 'import sys'),
+ (3, 6, 'import pickle'),
+ (5, 8, 'import copy'),
+ (6, 9, ''),
+ (7, 10, 'from datetime import date'),
+ (8, None, 'from Main import main'),
+ (9, None, 'from Main import _make_dir'),
+ (None, 11, 'from Bugxplore import main'),
+ (None, 12, 'from Bugxplore import _make_dir'),
+ (10, 13, ''),
+ (11, 14, 'storageDir = \'/tmp/csc/bugs/\''),
+ (12, 15, 'argv = []'),
+]
+
+DIFFXPLORE_CHANGES = indent(4, [
+ (46, 46, ''),
+ (47, 47, '# Configure option parser'),
+ (48, 48, "optparser = OptionParser(usage='%prog [options] DIFF_FILE', "
+ "version='0.1')"),
+ (49, None, "optparser.set_defaults(output_dir='/tmp/sctdiffs',"
+ "project_name='default_project')"),
+ (None, 49, "optparser.set_defaults(output_dir='/tmp/diffs')"),
+ (50, 50, "optparser.add_option('-o', '--output-dir', dest='output_dir', "
+ "help='Output directory')"),
+ (51, 51, "optparser.add_option('-p', '--project_name', "
+ "dest='project_name', help='Project name')"),
+ (52, 52, "optparser.add_option('-d', '--delete_cvs_folder', "
+ "dest='cvs_delete', help='Deletable CVS checkout folder')"),
+ (53, None, "optparser.add_option('-a', '--append', action='store_true', "
+ "dest='app', default=False, "
+ "help='Append to existing MethTerms2 document')"),
+ (None, 53, ''),
+ (54, 54, '# Invoke option parser'),
+ (55, 55, '(options,args) = optparser.parse_args(argv)'),
+ (56, 56, ''),
+])
+
+BUGXPLORE_CHANGES = indent(4, [
+ (83, 83, ''),
+ (84, 84, '# Configure option parser'),
+ (85, 85, "optparser = OptionParser(usage='%prog [options] BUG_IDS', "
+ "version='0.1')"),
+ (86, None, "optparser.set_defaults(output_dir='/tmp/bugs',"
+ "project_name='default_project')"),
+ (None, 86, "optparser.set_defaults(output_dir='/tmp/bugs')"),
+ (87, 87, "optparser.add_option('-u', '--bugzilla-url', "
+ "dest='bugzilla_url', help='URL of Bugzilla installation root')"),
+ (88, 88, "optparser.add_option('-o', '--output-dir', dest='output_dir', "
+ "help='Output directory')"),
+ (89, 89, "optparser.add_option('-p', '--project_name', "
+ "dest='project_name', help='Project name')"),
+ (90, 90, "optparser.add_option('-d', '--delete_cvs_folder', "
+ "dest='cvs_delete', help='Deletable CVS checkout folder')"),
+ (91, None, "optparser.add_option('-a', '--append', action='store_true', "
+ "dest='app', default=False, "
+ "help='Append to existing MethTerms2 document')"),
+ (None, 91, ''),
+ (92, 92, '# Invoke option parser'),
+ (93, 93, '(options,args) = optparser.parse_args(argv)'),
+]) + [(94, 94, ' ')]
class PatchTestSuite(unittest.TestCase):
+ def assert_diffs_equal(self, a, b):
+ def _process_change(c):
+ return (c.old, c.new, c.line)
+
+ def _process_diffobj(d):
+ return d._replace(changes=[_process_change(c) for c in d.changes])
+
+ def _process(d_or_c):
+ if isinstance(d_or_c, list):
+ return [_process(o) for o in d_or_c]
+ if isinstance(d_or_c, diffobj):
+ return _process_diffobj(d_or_c)
+ if isinstance(d_or_c, Change):
+ return _process_change(d_or_c)
+ return d_or_c
+
+ return self.assertEqual(_process(a), b)
+
def test_default_diff(self):
with open(datapath('diff-default.diff')) as f:
text = f.read()
@@ -29,11 +126,11 @@ def test_default_diff(self):
]
results = list(wtp.patch.parse_default_diff(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
- expected_main = [wtp.patch.diffobj(header=None, changes=expected, text=text)]
+ expected_main = [diffobj(header=None, changes=expected, text=text)]
results_main = list(wtp.patch.parse_patch(text))
- self.assertEqual(results_main, expected_main)
+ self.assert_diffs_equal(results_main, expected_main)
def test_svn_unified_patch(self):
with open('tests/casefiles/svn-unified.patch') as f:
@@ -42,90 +139,44 @@ def test_svn_unified_patch(self):
lines = text.splitlines()
expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='bugtrace/trunk/src/bugtrace/csc.py',
- old_path='bugtrace/trunk/src/bugtrace/csc.py',
- old_version=12783,
- new_path='bugtrace/trunk/src/bugtrace/csc.py',
- new_version=12784,
- ),
- changes=[
- (None, 1, '# This is a basic script I wrote to run Bugxplore over the dataset'),
- (None, 2, ''),
- (None, 3, ''),
- (1, 4, 'import os'),
- (2, 5, 'import sys'),
- (3, 6, 'import pickle'),
- (5, 8, 'import copy'),
- (6, 9, ''),
- (7, 10, 'from datetime import date'),
- (8, None, 'from Main import main'),
- (9, None, 'from Main import _make_dir'),
- (None, 11, 'from Bugxplore import main'),
- (None, 12, 'from Bugxplore import _make_dir'),
- (10, 13, ''),
- (11, 14, 'storageDir = \'/tmp/csc/bugs/\''),
- (12, 15, 'argv = []'),
- ],
- text = '\n'.join(lines[:22]) + '\n'
- ),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- old_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- old_version=12783,
- new_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- new_version=12784,
- ),
- changes=[
- (46, 46, ''),
- (47, 47, ' # Configure option parser'),
- (48, 48, " optparser = OptionParser(usage='%prog [options] DIFF_FILE', version='0.1')"),
- (49, None, " optparser.set_defaults(output_dir='/tmp/sctdiffs',project_name='default_project')"),
- (None, 49, " optparser.set_defaults(output_dir='/tmp/diffs')"),
- (50, 50, " optparser.add_option('-o', '--output-dir', dest='output_dir', help='Output directory')"),
- (51, 51, " optparser.add_option('-p', '--project_name', dest='project_name', help='Project name')"),
- (52, 52, " optparser.add_option('-d', '--delete_cvs_folder', dest='cvs_delete', help='Deletable CVS checkout folder')"),
- (53, None, " optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document')"),
- (None, 53, ''),
- (54, 54, ' # Invoke option parser'),
- (55, 55, ' (options,args) = optparser.parse_args(argv)'),
- (56, 56, ''),
- ],
- text = '\n'.join(lines[22:40]) + '\n'
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_version=12783,
+ new_path='bugtrace/trunk/src/bugtrace/csc.py',
+ new_version=12784,
),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- old_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- old_version=12783,
- new_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- new_version=12784,
- ),
- changes=[
- (83, 83, ''),
- (84, 84, ' # Configure option parser'),
- (85, 85, " optparser = OptionParser(usage='%prog [options] BUG_IDS', version='0.1')"),
- (86, None, " optparser.set_defaults(output_dir='/tmp/bugs',project_name='default_project')"),
- (None, 86, " optparser.set_defaults(output_dir='/tmp/bugs')"),
- (87, 87, " optparser.add_option('-u', '--bugzilla-url', dest='bugzilla_url', help='URL of Bugzilla installation root')"),
- (88, 88, " optparser.add_option('-o', '--output-dir', dest='output_dir', help='Output directory')"),
- (89, 89, " optparser.add_option('-p', '--project_name', dest='project_name', help='Project name')"),
- (90, 90, " optparser.add_option('-d', '--delete_cvs_folder', dest='cvs_delete', help='Deletable CVS checkout folder')"),
- (91, None, " optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document')"),
- (None, 91, ''),
- (92, 92, ' # Invoke option parser'),
- (93, 93, ' (options,args) = optparser.parse_args(argv)'),
- (94, 94, ' '),
- ],
- text = '\n'.join(lines[40:]) + '\n'
- )
- ]
+ changes=CSC_CHANGES,
+ text='\n'.join(lines[:22]) + '\n'
+ ),
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ old_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ old_version=12783,
+ new_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ new_version=12784,
+ ),
+ changes=DIFFXPLORE_CHANGES,
+ text='\n'.join(lines[22:40]) + '\n'
+ ),
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ old_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ old_version=12783,
+ new_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ new_version=12784,
+ ),
+ changes=BUGXPLORE_CHANGES,
+ text='\n'.join(lines[40:]) + '\n'
+ )
+ ]
results = list(wtp.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_svn_context_patch(self):
with open('tests/casefiles/svn-context.patch') as f:
@@ -134,90 +185,44 @@ def test_svn_context_patch(self):
lines = text.splitlines()
expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='bugtrace/trunk/src/bugtrace/csc.py',
- old_path='bugtrace/trunk/src/bugtrace/csc.py',
- old_version=12783,
- new_path='bugtrace/trunk/src/bugtrace/csc.py',
- new_version=12784,
- ),
- changes=[
- (None, 1, '# This is a basic script I wrote to run Bugxplore over the dataset'),
- (None, 2, ''),
- (None, 3, ''),
- (1, 4, 'import os'),
- (2, 5, 'import sys'),
- (3, 6, 'import pickle'),
- (5, 8, 'import copy'),
- (6, 9, ''),
- (7, 10, 'from datetime import date'),
- (8, None, 'from Main import main'),
- (9, None, 'from Main import _make_dir'),
- (None, 11, 'from Bugxplore import main'),
- (None, 12, 'from Bugxplore import _make_dir'),
- (10, 13, ''),
- (11, 14, 'storageDir = \'/tmp/csc/bugs/\''),
- (12, 15, 'argv = []'),
- ],
- text = '\n'.join(lines[:32]) + '\n'
- ),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- old_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- old_version=12783,
- new_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- new_version=12784,
- ),
- changes=[
- (46, 46, ''),
- (47, 47, ' # Configure option parser'),
- (48, 48, " optparser = OptionParser(usage='%prog [options] DIFF_FILE', version='0.1')"),
- (49, None, " optparser.set_defaults(output_dir='/tmp/sctdiffs',project_name='default_project')"),
- (None, 49, " optparser.set_defaults(output_dir='/tmp/diffs')"),
- (50, 50, " optparser.add_option('-o', '--output-dir', dest='output_dir', help='Output directory')"),
- (51, 51, " optparser.add_option('-p', '--project_name', dest='project_name', help='Project name')"),
- (52, 52, " optparser.add_option('-d', '--delete_cvs_folder', dest='cvs_delete', help='Deletable CVS checkout folder')"),
- (53, None, " optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document')"),
- (None, 53, ''),
- (54, 54, ' # Invoke option parser'),
- (55, 55, ' (options,args) = optparser.parse_args(argv)'),
- (56, 56, ''),
- ],
- text = '\n'.join(lines[32:61]) + '\n'
- ),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- old_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- old_version=12783,
- new_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- new_version=12784,
- ),
- changes=[
- (83, 83, ''),
- (84, 84, ' # Configure option parser'),
- (85, 85, " optparser = OptionParser(usage='%prog [options] BUG_IDS', version='0.1')"),
- (86, None, " optparser.set_defaults(output_dir='/tmp/bugs',project_name='default_project')"),
- (None, 86, " optparser.set_defaults(output_dir='/tmp/bugs')"),
- (87, 87, " optparser.add_option('-u', '--bugzilla-url', dest='bugzilla_url', help='URL of Bugzilla installation root')"),
- (88, 88, " optparser.add_option('-o', '--output-dir', dest='output_dir', help='Output directory')"),
- (89, 89, " optparser.add_option('-p', '--project_name', dest='project_name', help='Project name')"),
- (90, 90, " optparser.add_option('-d', '--delete_cvs_folder', dest='cvs_delete', help='Deletable CVS checkout folder')"),
- (91, None, " optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document')"),
- (None, 91, ''),
- (92, 92, ' # Invoke option parser'),
- (93, 93, ' (options,args) = optparser.parse_args(argv)'),
- (94, 94, ' '),
- ],
- text = '\n'.join(lines[61:]) + '\n'
- )
- ]
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_version=12783,
+ new_path='bugtrace/trunk/src/bugtrace/csc.py',
+ new_version=12784,
+ ),
+ changes=CSC_CHANGES,
+ text='\n'.join(lines[:32]) + '\n'
+ ),
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ old_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ old_version=12783,
+ new_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ new_version=12784,
+ ),
+ changes=DIFFXPLORE_CHANGES,
+ text='\n'.join(lines[32:61]) + '\n'
+ ),
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ old_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ old_version=12783,
+ new_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ new_version=12784,
+ ),
+ changes=BUGXPLORE_CHANGES,
+ text='\n'.join(lines[61:]) + '\n'
+ ),
+ ]
results = list(wtp.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_svn_git_patch(self):
with open('tests/casefiles/svn-git.patch') as f:
@@ -225,155 +230,119 @@ def test_svn_git_patch(self):
lines = text.splitlines()
- expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='bugtrace/trunk/src/bugtrace/csc.py',
- old_path='projects/bugs/bugtrace/trunk/src/bugtrace/csc.py',
- old_version=12783,
- new_path='projects/bugs/bugtrace/trunk/src/bugtrace/csc.py',
- new_version=12784,
- ),
- changes=[
- (None, 1, '# This is a basic script I wrote to run Bugxplore over the dataset'),
- (None, 2, ''),
- (None, 3, ''),
- (1, 4, 'import os'),
- (2, 5, 'import sys'),
- (3, 6, 'import pickle'),
- (5, 8, 'import copy'),
- (6, 9, ''),
- (7, 10, 'from datetime import date'),
- (8, None, 'from Main import main'),
- (9, None, 'from Main import _make_dir'),
- (None, 11, 'from Bugxplore import main'),
- (None, 12, 'from Bugxplore import _make_dir'),
- (10, 13, ''),
- (11, 14, 'storageDir = \'/tmp/csc/bugs/\''),
- (12, 15, 'argv = []'),
- ],
- text = '\n'.join(lines[:23]) + '\n'
- ),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- old_path='projects/bugs/bugtrace/trunk/src/bugtrace/Diffxplore.py',
- old_version=12783,
- new_path='projects/bugs/bugtrace/trunk/src/bugtrace/Diffxplore.py',
- new_version=12784,
- ),
- changes=[
- (46, 46, ''),
- (47, 47, ' # Configure option parser'),
- (48, 48, " optparser = OptionParser(usage='%prog [options] DIFF_FILE', version='0.1')"),
- (49, None, " optparser.set_defaults(output_dir='/tmp/sctdiffs',project_name='default_project')"),
- (None, 49, " optparser.set_defaults(output_dir='/tmp/diffs')"),
- (50, 50, " optparser.add_option('-o', '--output-dir', dest='output_dir', help='Output directory')"),
- (51, 51, " optparser.add_option('-p', '--project_name', dest='project_name', help='Project name')"),
- (52, 52, " optparser.add_option('-d', '--delete_cvs_folder', dest='cvs_delete', help='Deletable CVS checkout folder')"),
- (53, None, " optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document')"),
- (None, 53, ''),
- (54, 54, ' # Invoke option parser'),
- (55, 55, ' (options,args) = optparser.parse_args(argv)'),
- (56, 56, ''),
- ],
- text = '\n'.join(lines[23:42]) + '\n'
- ),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- old_path='projects/bugs/bugtrace/trunk/src/bugtrace/Bugxplore.py',
- old_version=12783,
- new_path='projects/bugs/bugtrace/trunk/src/bugtrace/Bugxplore.py',
- new_version=12784,
- ),
- changes=[
- (83, 83, ''),
- (84, 84, ' # Configure option parser'),
- (85, 85, " optparser = OptionParser(usage='%prog [options] BUG_IDS', version='0.1')"),
- (86, None, " optparser.set_defaults(output_dir='/tmp/bugs',project_name='default_project')"),
- (None, 86, " optparser.set_defaults(output_dir='/tmp/bugs')"),
- (87, 87, " optparser.add_option('-u', '--bugzilla-url', dest='bugzilla_url', help='URL of Bugzilla installation root')"),
- (88, 88, " optparser.add_option('-o', '--output-dir', dest='output_dir', help='Output directory')"),
- (89, 89, " optparser.add_option('-p', '--project_name', dest='project_name', help='Project name')"),
- (90, 90, " optparser.add_option('-d', '--delete_cvs_folder', dest='cvs_delete', help='Deletable CVS checkout folder')"),
- (91, None, " optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document')"),
- (None, 91, ''),
- (92, 92, ' # Invoke option parser'),
- (93, 93, ' (options,args) = optparser.parse_args(argv)'),
- (94, 94, ' '),
- ],
- text = '\n'.join(lines[42:]) + '\n'
- )
- ]
-
+ csc_diff = diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_path='projects/bugs/bugtrace/trunk/src/bugtrace/csc.py',
+ old_version=12783,
+ new_path='projects/bugs/bugtrace/trunk/src/bugtrace/csc.py',
+ new_version=12784,
+ ),
+ changes=CSC_CHANGES,
+ text='\n'.join(lines[:23]) + '\n'
+ )
+
+ diffxplore_path = 'bugtrace/trunk/src/bugtrace/Diffxplore.py'
+ diffxplore_diff = diffobj(
+ header=headerobj(
+ index_path=diffxplore_path,
+ old_path='projects/bugs/' + diffxplore_path,
+ old_version=12783,
+ new_path='projects/bugs/' + diffxplore_path,
+ new_version=12784,
+ ),
+ changes=DIFFXPLORE_CHANGES,
+ text='\n'.join(lines[23:42]) + '\n'
+ )
+
+ bugexplore_path = 'bugtrace/trunk/src/bugtrace/Bugxplore.py'
+ bugxplore_diff = diffobj(
+ header=headerobj(
+ index_path=bugexplore_path,
+ old_path='projects/bugs/' + bugexplore_path,
+ old_version=12783,
+ new_path='projects/bugs/' + bugexplore_path,
+ new_version=12784,
+ ),
+ changes=BUGXPLORE_CHANGES,
+ text='\n'.join(lines[42:]) + '\n'
+ )
+
+ expected = [csc_diff, diffxplore_diff, bugxplore_diff]
results = list(wtp.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_svn_rcs_patch(self):
with open('tests/casefiles/svn-rcs.patch') as f:
text = f.read()
lines = text.splitlines()
+
+ csc_changes = [
+ (None, 1, '# This is a basic script I wrote to run '
+ 'Bugxplore over the dataset'),
+ (None, 2, ''),
+ (None, 3, ''),
+ (8, None, None),
+ (9, None, None),
+ (None, 11, 'from Bugxplore import main'),
+ (None, 12, 'from Bugxplore import _make_dir'),
+ ]
+
+ diffxplore_changes = [
+ (49, None, None),
+ (None, 49, " optparser.set_defaults(output_dir='/tmp/diffs')"),
+ (53, None, None),
+ (None, 53, ''),
+ ]
+
+ bugxplore_changes = [
+ (86, None, None),
+ (None, 86, " optparser.set_defaults(output_dir='/tmp/bugs')"),
+ (91, None, None),
+ (None, 91, ''),
+ ]
+
expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path = 'bugtrace/trunk/src/bugtrace/csc.py',
- old_path='bugtrace/trunk/src/bugtrace/csc.py',
- old_version=None,
- new_path='bugtrace/trunk/src/bugtrace/csc.py',
- new_version=None,
- ),
- changes=[
- (None, 1, '# This is a basic script I wrote to run Bugxplore over the dataset'),
- (None, 2, ''),
- (None, 3, ''),
- (8, None, None),
- (9, None, None),
- (None, 11, 'from Bugxplore import main'),
- (None, 12, 'from Bugxplore import _make_dir'),
- ],
- text = '\n'.join(lines[:10]) + '\n'
- ),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path = 'bugtrace/trunk/src/bugtrace/Diffxplore.py',
- old_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- old_version=None,
- new_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- new_version=None,
- ),
- changes=[
- (49, None, None),
- (None, 49, " optparser.set_defaults(output_dir='/tmp/diffs')"),
- (53, None, None),
- (None, 53, ''),
- ],
- text = '\n'.join(lines[10:18]) + '\n'
- ),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path = 'bugtrace/trunk/src/bugtrace/Bugxplore.py',
- old_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- old_version=None,
- new_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- new_version=None,
- ),
- changes=[
- (86, None, None),
- (None, 86, " optparser.set_defaults(output_dir='/tmp/bugs')"),
- (91, None, None),
- (None, 91, ''),
- ],
- text = '\n'.join(lines[18:]) + '\n'
- )
- ]
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_version=None,
+ new_path='bugtrace/trunk/src/bugtrace/csc.py',
+ new_version=None,
+ ),
+ changes=csc_changes,
+ text='\n'.join(lines[:10]) + '\n'
+ ),
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ old_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ old_version=None,
+ new_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ new_version=None,
+ ),
+ changes=diffxplore_changes,
+ text='\n'.join(lines[10:18]) + '\n'
+ ),
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ old_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ old_version=None,
+ new_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ new_version=None,
+ ),
+ changes=bugxplore_changes,
+ text='\n'.join(lines[18:]) + '\n'
+ ),
+ ]
results = list(wtp.parse_patch(text))
- self.assertEqual(results, expected)
-
+ self.assert_diffs_equal(results, expected)
def test_svn_default_patch(self):
with open('tests/casefiles/svn-default.patch') as f:
@@ -381,62 +350,74 @@ def test_svn_default_patch(self):
lines = text.splitlines()
+ csc_changes = [
+ (None, 1, '# This is a basic script I wrote to run '
+ 'Bugxplore over the dataset'),
+ (None, 2, ''),
+ (None, 3, ''),
+ (8, None, 'from Main import main'),
+ (9, None, 'from Main import _make_dir'),
+ (None, 11, 'from Bugxplore import main'),
+ (None, 12, 'from Bugxplore import _make_dir'),
+ ]
+
+ diffxplore_changes = indent(4, [
+ (49, None, "optparser.set_defaults(output_dir='/tmp/sctdiffs',"
+ "project_name='default_project')"),
+ (None, 49, "optparser.set_defaults(output_dir='/tmp/diffs')"),
+ (53, None, "optparser.add_option('-a', '--append', "
+ "action='store_true', dest='app', default=False, "
+ "help='Append to existing MethTerms2 document')"),
+ (None, 53, ''),
+ ])
+
+ bugxplore_changes = indent(4, [
+ (86, None, "optparser.set_defaults(output_dir='/tmp/bugs',"
+ "project_name='default_project')"),
+ (None, 86, "optparser.set_defaults(output_dir='/tmp/bugs')"),
+ (91, None, "optparser.add_option('-a', '--append', "
+ "action='store_true', dest='app', default=False, "
+ "help='Append to existing MethTerms2 document')"),
+ (None, 91, ''),
+ ])
+
expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path = 'bugtrace/trunk/src/bugtrace/csc.py',
- old_path='bugtrace/trunk/src/bugtrace/csc.py',
- old_version=None,
- new_path='bugtrace/trunk/src/bugtrace/csc.py',
- new_version=None,
- ),
- changes=[
- (None, 1, '# This is a basic script I wrote to run Bugxplore over the dataset'),
- (None, 2, ''),
- (None, 3, ''),
- (8, None, 'from Main import main'),
- (9, None, 'from Main import _make_dir'),
- (None, 11, 'from Bugxplore import main'),
- (None, 12, 'from Bugxplore import _make_dir'),
- ],
- text = '\n'.join(lines[:12]) + '\n'
- ),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path = 'bugtrace/trunk/src/bugtrace/Diffxplore.py',
- old_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- old_version=None,
- new_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
- new_version=None,
- ),
- changes=[
- (49, None, " optparser.set_defaults(output_dir='/tmp/sctdiffs',project_name='default_project')"),
- (None, 49, " optparser.set_defaults(output_dir='/tmp/diffs')"),
- (53, None, " optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document')"),
- (None, 53, ''),
- ],
- text = '\n'.join(lines[12:22]) + '\n'
- ),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path = 'bugtrace/trunk/src/bugtrace/Bugxplore.py',
- old_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- old_version=None,
- new_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
- new_version=None,
- ),
- changes=[
- (86, None, " optparser.set_defaults(output_dir='/tmp/bugs',project_name='default_project')"),
- (None, 86, " optparser.set_defaults(output_dir='/tmp/bugs')"),
- (91, None, " optparser.add_option('-a', '--append', action='store_true', dest='app', default=False, help='Append to existing MethTerms2 document')"),
- (None, 91, ''),
- ],
- text = '\n'.join(lines[22:]) + '\n'
- )
- ]
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_version=None,
+ new_path='bugtrace/trunk/src/bugtrace/csc.py',
+ new_version=None,
+ ),
+ changes=csc_changes,
+ text='\n'.join(lines[:12]) + '\n'
+ ),
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ old_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ old_version=None,
+ new_path='bugtrace/trunk/src/bugtrace/Diffxplore.py',
+ new_version=None,
+ ),
+ changes=diffxplore_changes,
+ text='\n'.join(lines[12:22]) + '\n'
+ ),
+ diffobj(
+ header=headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ old_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ old_version=None,
+ new_path='bugtrace/trunk/src/bugtrace/Bugxplore.py',
+ new_version=None,
+ ),
+ changes=bugxplore_changes,
+ text='\n'.join(lines[22:]) + '\n'
+ )
+ ]
results = list(wtp.parse_patch(text))
- self.assertEqual(results, expected)
-
+ self.assert_diffs_equal(results, expected)
def test_git_patch(self):
with open('tests/casefiles/git.patch') as f:
@@ -444,70 +425,82 @@ def test_git_patch(self):
lines = text.splitlines()
- expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path=None,
- old_path='novel/src/java/edu/ua/eng/software/novel/NovelFrame.java',
- old_version='aae63fe',
- new_path='novel/src/java/edu/ua/eng/software/novel/NovelFrame.java',
- new_version='5abbc99'
- ),
- changes=[
- (135, 135, ' public void actionPerformed(ActionEvent e) {'),
- (136, 136, ''),
- (137, 137, ' if (e.getActionCommand().equals("OPEN")) {'),
- (138, None, ' prefsDialog(prefs.getImportPane());'),
- (None, 138, ' prefs.selectImportPane();'),
- (None, 139, ' prefsDialog();'),
- (139, 140, ' } else if (e.getActionCommand().equals("SET")) {'),
- (140, None, ' prefsDialog(prefs.getRepoPane());'),
- (None, 141, ' prefs.selectRepoPane();'),
- (None, 142, ' prefsDialog();'),
- (141, 143, ' } else if (e.getActionCommand().equals("PREFS"))'),
- (142, 144, ' prefsDialog();'),
- (143, 145, ' else if (e.getActionCommand().equals("EXIT"))'),
- (158, 160, ' * Create dialog to handle user preferences'),
- (159, 161, ' */'),
- (160, 162, ' public void prefsDialog() {'),
- (161, None, ''),
- (162, 163, ' prefs.setVisible(true);'),
- (163, 164, ' }'),
- (164, 165, ''),
- (165, None, ' public void prefsDialog(Component c) {'),
- (166, None, ' prefs.setSelectedComponent(c);'),
- (167, None, ' prefsDialog();'),
- (168, None, ' }'),
- (169, None, ''),
- (170, 166, ' /**'),
- (171, 167, ' * Open software tutorials, most likely to be hosted online'),
- (172, 168, ' * ')],
- text = '\n'.join(lines[:34]) + '\n'
- ),
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path=None,
- old_path='novel/src/java/edu/ua/eng/software/novel/NovelPrefPane.java',
- old_version='a63b57e',
- new_path='novel/src/java/edu/ua/eng/software/novel/NovelPrefPane.java',
- new_version='919f413'
- ),
- changes=[
- (18, 18, ''),
- (19, 19, ' public abstract void apply();'),
- (20, 20, ''),
- (None, 21, ' public abstract void applyPrefs();'),
- (None, 22, ''),
- (21, 23, ' public abstract boolean isChanged();'),
- (22, 24, ''),
- (23, 25, ' protected Preferences prefs;')],
- text = '\n'.join(lines[34:]) + '\n'
- )
- ]
+ novel_frame_changes = indent(4, [
+ (135, 135, 'public void actionPerformed(ActionEvent e) {'),
+ (136, 136, ''),
+ (137, 137, ' if (e.getActionCommand().equals("OPEN")) {'),
+ (138, None, ' prefsDialog(prefs.getImportPane());'),
+ (None, 138, ' prefs.selectImportPane();'),
+ (None, 139, ' prefsDialog();'),
+ (139, 140, ' } else if (e.getActionCommand().equals("SET")) {'),
+ (140, None, ' prefsDialog(prefs.getRepoPane());'),
+ (None, 141, ' prefs.selectRepoPane();'),
+ (None, 142, ' prefsDialog();'),
+ (141, 143, ' } else if (e.getActionCommand().equals("PREFS"))'),
+ (142, 144, ' prefsDialog();'),
+ (143, 145, ' else if (e.getActionCommand().equals("EXIT"))'),
+ (158, 160, ' * Create dialog to handle user preferences'),
+ (159, 161, ' */'),
+ (160, 162, 'public void prefsDialog() {'),
+ (161, None, ''),
+ (162, 163, ' prefs.setVisible(true);'),
+ (163, 164, '}'),
+ (164, 165, ''),
+ (165, None, 'public void prefsDialog(Component c) {'),
+ (166, None, ' prefs.setSelectedComponent(c);'),
+ (167, None, ' prefsDialog();'),
+ (168, None, '}'),
+ (169, None, ''),
+ (170, 166, '/**'),
+ (171, 167, ' * Open software tutorials, '
+ 'most likely to be hosted online'),
+ (172, 168, ' * ')
+ ])
+
+ novel_frame_path = (
+ 'novel/src/java/edu/ua/eng/software/novel/NovelFrame.java'
+ )
+ novel_frame = diffobj(
+ header=headerobj(
+ index_path=None,
+ old_path=novel_frame_path,
+ old_version='aae63fe',
+ new_path=novel_frame_path,
+ new_version='5abbc99'
+ ),
+ changes=novel_frame_changes,
+ text='\n'.join(lines[:34]) + '\n'
+ )
+
+ novel_pref_frame_path = (
+ 'novel/src/java/edu/ua/eng/software/novel/NovelPrefPane.java'
+ )
+
+ novel_pref_frame = diffobj(
+ header=headerobj(
+ index_path=None,
+ old_path=novel_pref_frame_path,
+ old_version='a63b57e',
+ new_path=novel_pref_frame_path,
+ new_version='919f413'
+ ),
+ changes=[
+ (18, 18, ''),
+ (19, 19, ' public abstract void apply();'),
+ (20, 20, ''),
+ (None, 21, ' public abstract void applyPrefs();'),
+ (None, 22, ''),
+ (21, 23, ' public abstract boolean isChanged();'),
+ (22, 24, ''),
+ (23, 25, ' protected Preferences prefs;')],
+ text='\n'.join(lines[34:]) + '\n',
+ )
+
+ expected = [novel_frame, novel_pref_frame]
results = list(wtp.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_git_oneline_add(self):
with open('tests/casefiles/git-oneline-add.diff') as f:
@@ -516,8 +509,8 @@ def test_git_oneline_add(self):
lines = text.splitlines()
expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
+ diffobj(
+ header=headerobj(
index_path=None,
old_path='/dev/null',
old_version='0000000',
@@ -533,7 +526,7 @@ def test_git_oneline_add(self):
results = list(wtp.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_git_oneline_change(self):
with open('tests/casefiles/git-oneline-change.diff') as f:
@@ -542,8 +535,8 @@ def test_git_oneline_change(self):
lines = text.splitlines()
expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
+ diffobj(
+ header=headerobj(
index_path=None,
old_path='oneline.txt',
old_version='f56f98d',
@@ -559,7 +552,7 @@ def test_git_oneline_change(self):
]
results = list(wtp.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_git_oneline_rm(self):
with open('tests/casefiles/git-oneline-rm.diff') as f:
@@ -568,8 +561,8 @@ def test_git_oneline_rm(self):
lines = text.splitlines()
expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
+ diffobj(
+ header=headerobj(
index_path=None,
old_path='oneline.txt',
old_version='169ceeb',
@@ -584,18 +577,19 @@ def test_git_oneline_rm(self):
]
results = list(wtp.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_git_header(self):
with open('tests/casefiles/git-header.diff') as f:
text = f.read()
- expected = wtp.patch.header(
- index_path = None,
- old_path = 'bugtrace/patch.py',
- old_version = '8910dfd',
- new_path = 'bugtrace/patch.py',
- new_version = '456e34f')
+ expected = headerobj(
+ index_path=None,
+ old_path='bugtrace/patch.py',
+ old_version='8910dfd',
+ new_path='bugtrace/patch.py',
+ new_version='456e34f',
+ )
results = wtp.patch.parse_git_header(text)
self.assertEqual(results, expected)
@@ -607,12 +601,13 @@ def test_git_header_long(self):
with open('tests/casefiles/git-header-long.diff') as f:
text = f.read()
- expected = wtp.patch.header(
- index_path = None,
- old_path = 'bugtrace/patch.py',
- old_version = '18910dfd',
- new_path = 'bugtrace/patch.py',
- new_version = '2456e34f')
+ expected = headerobj(
+ index_path=None,
+ old_path='bugtrace/patch.py',
+ old_version='18910dfd',
+ new_path='bugtrace/patch.py',
+ new_version='2456e34f',
+ )
results = wtp.patch.parse_git_header(text)
self.assertEqual(results, expected)
@@ -624,12 +619,13 @@ def test_git_binary_files(self):
with open('tests/casefiles/git-binary-files.diff') as f:
text = f.read()
- expected = wtp.patch.header(
- index_path = None,
- old_path = '/dev/null',
- old_version = '0000000',
- new_path = 'project/media/i/asc.gif',
- new_version = '71e31ac')
+ expected = headerobj(
+ index_path=None,
+ old_path='/dev/null',
+ old_version='0000000',
+ new_path='project/media/i/asc.gif',
+ new_version='71e31ac',
+ )
results = wtp.patch.parse_git_header(text)
self.assertEqual(results, expected)
@@ -641,12 +637,13 @@ def test_svn_header(self):
with open('tests/casefiles/svn-header.diff') as f:
text = f.read()
- expected = wtp.patch.header(
- index_path = 'bugtrace/trunk/src/bugtrace/csc.py',
- old_path = 'bugtrace/trunk/src/bugtrace/csc.py',
- old_version = 12783,
- new_path = 'bugtrace/trunk/src/bugtrace/csc.py',
- new_version = 12784)
+ expected = headerobj(
+ index_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_path='bugtrace/trunk/src/bugtrace/csc.py',
+ old_version=12783,
+ new_path='bugtrace/trunk/src/bugtrace/csc.py',
+ new_version=12784,
+ )
results = wtp.patch.parse_svn_header(text)
self.assertEqual(results, expected)
@@ -657,12 +654,19 @@ def test_cvs_header(self):
with open('tests/casefiles/cvs-header.diff') as f:
text = f.read()
- expected = wtp.patch.header(
- index_path = 'org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java',
- old_path = 'org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java',
- old_version = '1.6.4.1',
- new_path = 'org.eclipse.core.resources/src/org/eclipse/core/internal/localstore/SafeChunkyInputStream.java',
- new_version = '1.8')
+ path = (
+ 'org.eclipse.core.resources'
+ '/src/org/eclipse/core/internal/localstore/'
+ 'SafeChunkyInputStream.java'
+ )
+
+ expected = headerobj(
+ index_path=path,
+ old_path=path,
+ old_version='1.6.4.1',
+ new_path=path,
+ new_version='1.8',
+ )
results = wtp.patch.parse_cvs_header(text)
self.assertEqual(results, expected)
@@ -673,12 +677,13 @@ def test_unified_header(self):
with open('tests/casefiles/unified-header.diff') as f:
text = f.read()
- expected = wtp.patch.header(
- index_path = None,
- old_path = '/tmp/o',
- old_version = '2012-12-22 06:43:35.000000000 -0600',
- new_path = '/tmp/n',
- new_version = '2012-12-23 20:40:50.000000000 -0600')
+ expected = headerobj(
+ index_path=None,
+ old_path='/tmp/o',
+ old_version='2012-12-22 06:43:35.000000000 -0600',
+ new_path='/tmp/n',
+ new_version='2012-12-23 20:40:50.000000000 -0600',
+ )
results = wtp.patch.parse_unified_header(text)
self.assertEqual(results, expected)
@@ -690,12 +695,13 @@ def test_unified_header_notab(self):
with open('tests/casefiles/unified-header-notab.diff') as f:
text = f.read()
- expected = wtp.patch.header(
- index_path = None,
- old_path = '/tmp/some file',
- old_version = '2012-12-22 06:43:35.000000000 -0600',
- new_path = '/tmp/n',
- new_version = '2012-12-23 20:40:50.000000000 -0600')
+ expected = headerobj(
+ index_path=None,
+ old_path='/tmp/some file',
+ old_version='2012-12-22 06:43:35.000000000 -0600',
+ new_path='/tmp/n',
+ new_version='2012-12-23 20:40:50.000000000 -0600',
+ )
results = wtp.patch.parse_unified_header(text)
self.assertEqual(results, expected)
@@ -703,7 +709,6 @@ def test_unified_header_notab(self):
results_main = wtp.patch.parse_header(text)
self.assertEqual(results_main, expected)
-
def test_unified_diff(self):
with open(datapath('diff-unified.diff')) as f:
text = f.read()
@@ -730,129 +735,127 @@ def test_unified_diff(self):
]
results = list(wtp.patch.parse_unified_diff(text_diff))
- self.assertEqual(results, expected)
-
- expected_main = wtp.patch.diffobj(header=
- wtp.patch.header(index_path=None,
- old_path='lao',
- old_version='2013-01-05 16:56:19.000000000 -0600',
- new_path='tzu',
- new_version='2013-01-05 16:56:35.000000000 -0600'
- ),
- changes=expected,
- text=text)
+ self.assert_diffs_equal(results, expected)
+
+ expected_main = diffobj(
+ header=headerobj(
+ index_path=None,
+ old_path='lao',
+ old_version='2013-01-05 16:56:19.000000000 -0600',
+ new_path='tzu',
+ new_version='2013-01-05 16:56:35.000000000 -0600'
+ ),
+ changes=expected,
+ text=text,
+ )
results_main = next(wtp.patch.parse_patch(text))
- self.assertEqual(results_main, expected_main)
+ self.assert_diffs_equal(results_main, expected_main)
def test_diff_unified_with_does_not_include_extra_lines(self):
with open('tests/casefiles/diff-unified-blah.diff') as f:
text = f.read()
+ changes = [
+ (1, None, 'The Way that can be told of is not the eternal Way;'),
+ (2, None, 'The name that can be named is not the eternal name.'),
+ (3, 1, 'The Nameless is the origin of Heaven and Earth;'),
+ (4, None, 'The Named is the mother of all things.'),
+ (None, 2, 'The named is the mother of all things.'),
+ (None, 3, ''),
+ (5, 4, 'Therefore let there always be non-being,'),
+ (6, 5, ' so we may see their subtlety,'),
+ (7, 6, 'And let there always be being,'),
+ (9, 8, 'The two are the same,'),
+ (10, 9, 'But after they are produced,'),
+ (11, 10, ' they have different names.'),
+ (None, 11, 'They both may be called deep and profound.'),
+ (None, 12, 'Deeper and more profound,'),
+ (None, 13, 'The door of all subtleties!'),
+ ]
- expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path=None,
- old_path='lao',
- old_version='2013-01-05 16:56:19.000000000 -0600',
- new_path='tzu',
- new_version='2013-01-05 16:56:35.000000000 -0600'
- ),
- changes=[
- (1, None, 'The Way that can be told of is not the eternal Way;'),
- (2, None, 'The name that can be named is not the eternal name.'),
- (3, 1, 'The Nameless is the origin of Heaven and Earth;'),
- (4, None, 'The Named is the mother of all things.'),
- (None, 2, 'The named is the mother of all things.'),
- (None, 3, ''),
- (5, 4, 'Therefore let there always be non-being,'),
- (6, 5, ' so we may see their subtlety,'),
- (7, 6, 'And let there always be being,'),
- (9, 8, 'The two are the same,'),
- (10, 9, 'But after they are produced,'),
- (11, 10, ' they have different names.'),
- (None, 11, 'They both may be called deep and profound.'),
- (None, 12, 'Deeper and more profound,'),
- (None, 13, 'The door of all subtleties!')],
- text=text)
- ]
-
+ expected = [diffobj(
+ header=headerobj(
+ index_path=None,
+ old_path='lao',
+ old_version='2013-01-05 16:56:19.000000000 -0600',
+ new_path='tzu',
+ new_version='2013-01-05 16:56:35.000000000 -0600',
+ ),
+ changes=changes,
+ text=text,
+ )]
results = list(wtp.patch.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_diff_context_with_does_not_include_extra_lines(self):
with open('tests/casefiles/diff-context-blah.diff') as f:
text = f.read()
+ changes = [
+ (1, None, 'The Way that can be told of is not the eternal Way;'),
+ (2, None, 'The name that can be named is not the eternal name.'),
+ (3, 1, 'The Nameless is the origin of Heaven and Earth;'),
+ (4, None, 'The Named is the mother of all things.'),
+ (None, 2, 'The named is the mother of all things.'),
+ (None, 3, ''),
+ (5, 4, 'Therefore let there always be non-being,'),
+ (6, 5, ' so we may see their subtlety,'),
+ (7, 6, 'And let there always be being,'),
+ (9, 8, 'The two are the same,'),
+ (10, 9, 'But after they are produced,'),
+ (11, 10, ' they have different names.'),
+ (None, 11, 'They both may be called deep and profound.'),
+ (None, 12, 'Deeper and more profound,'),
+ (None, 13, 'The door of all subtleties!'),
+ ]
- expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path=None,
- old_path='lao',
- old_version='2013-01-05 16:56:19.000000000 -0600',
- new_path='tzu',
- new_version='2013-01-05 16:56:35.000000000 -0600'
- ),
- changes=[
- (1, None, 'The Way that can be told of is not the eternal Way;'),
- (2, None, 'The name that can be named is not the eternal name.'),
- (3, 1, 'The Nameless is the origin of Heaven and Earth;'),
- (4, None, 'The Named is the mother of all things.'),
- (None, 2, 'The named is the mother of all things.'),
- (None, 3, ''),
- (5, 4, 'Therefore let there always be non-being,'),
- (6, 5, ' so we may see their subtlety,'),
- (7, 6, 'And let there always be being,'),
- (9, 8, 'The two are the same,'),
- (10, 9, 'But after they are produced,'),
- (11, 10, ' they have different names.'),
- (None, 11, 'They both may be called deep and profound.'),
- (None, 12, 'Deeper and more profound,'),
- (None, 13, 'The door of all subtleties!')],
- text=text)
- ]
-
+ expected = [diffobj(
+ header=headerobj(
+ index_path=None,
+ old_path='lao',
+ old_version='2013-01-05 16:56:19.000000000 -0600',
+ new_path='tzu',
+ new_version='2013-01-05 16:56:35.000000000 -0600'
+ ),
+ changes=changes,
+ text=text,
+ )]
results = list(wtp.patch.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_diff_default_with_does_not_include_extra_lines(self):
with open('tests/casefiles/diff-default-blah.diff') as f:
text = f.read()
- expected = [
- wtp.patch.diffobj(
- header=None,
- changes=[
- (1, None, 'The Way that can be told of is not the eternal Way;'),
- (2, None, 'The name that can be named is not the eternal name.'),
- (4, None, 'The Named is the mother of all things.'),
- (None, 2, 'The named is the mother of all things.'),
- (None, 3, ''),
- (None, 11, 'They both may be called deep and profound.'),
- (None, 12, 'Deeper and more profound,'),
- (None, 13, 'The door of all subtleties!')],
- text=text)
- ]
+ changes = [
+ (1, None, 'The Way that can be told of is not the eternal Way;'),
+ (2, None, 'The name that can be named is not the eternal name.'),
+ (4, None, 'The Named is the mother of all things.'),
+ (None, 2, 'The named is the mother of all things.'),
+ (None, 3, ''),
+ (None, 11, 'They both may be called deep and profound.'),
+ (None, 12, 'Deeper and more profound,'),
+ (None, 13, 'The door of all subtleties!'),
+ ]
+ expected = [diffobj(header=None, changes=changes, text=text)]
results = list(wtp.patch.parse_patch(text))
- self.assertEqual(results, expected)
-
+ self.assert_diffs_equal(results, expected)
def test_context_header(self):
with open('tests/casefiles/context-header.diff') as f:
text = f.read()
-
- expected = wtp.patch.header(
- index_path = None,
- old_path = '/tmp/o',
- old_version = '2012-12-22 06:43:35.000000000 -0600',
- new_path = '/tmp/n',
- new_version = '2012-12-23 20:40:50.000000000 -0600')
+ expected = headerobj(
+ index_path=None,
+ old_path='/tmp/o',
+ old_version='2012-12-22 06:43:35.000000000 -0600',
+ new_path='/tmp/n',
+ new_version='2012-12-23 20:40:50.000000000 -0600',
+ )
results = wtp.patch.parse_context_header(text)
self.assertEqual(results, expected)
@@ -860,7 +863,6 @@ def test_context_header(self):
results_main = wtp.patch.parse_header(text)
self.assertEqual(results_main, expected)
-
def test_context_diff(self):
with open(datapath('diff-context.diff')) as f:
text = f.read()
@@ -869,38 +871,39 @@ def test_context_diff(self):
text_diff = '\n'.join(text.splitlines()[2:]) + '\n'
expected = [
- (1, None, 'The Way that can be told of is not the eternal Way;'),
- (2, None, 'The name that can be named is not the eternal name.'),
- (3, 1, 'The Nameless is the origin of Heaven and Earth;'),
- (4, None, 'The Named is the mother of all things.'),
- (None, 2, 'The named is the mother of all things.'),
- (None, 3, ''),
- (5, 4, 'Therefore let there always be non-being,'),
- (6, 5, ' so we may see their subtlety,'),
- (7, 6, 'And let there always be being,'),
- (9, 8, 'The two are the same,'),
- (10, 9, 'But after they are produced,'),
- (11, 10, ' they have different names.'),
- (None, 11, 'They both may be called deep and profound.'),
- (None, 12, 'Deeper and more profound,'),
- (None, 13, 'The door of all subtleties!'),
- ]
+ (1, None, 'The Way that can be told of is not the eternal Way;'),
+ (2, None, 'The name that can be named is not the eternal name.'),
+ (3, 1, 'The Nameless is the origin of Heaven and Earth;'),
+ (4, None, 'The Named is the mother of all things.'),
+ (None, 2, 'The named is the mother of all things.'),
+ (None, 3, ''),
+ (5, 4, 'Therefore let there always be non-being,'),
+ (6, 5, ' so we may see their subtlety,'),
+ (7, 6, 'And let there always be being,'),
+ (9, 8, 'The two are the same,'),
+ (10, 9, 'But after they are produced,'),
+ (11, 10, ' they have different names.'),
+ (None, 11, 'They both may be called deep and profound.'),
+ (None, 12, 'Deeper and more profound,'),
+ (None, 13, 'The door of all subtleties!'),
+ ]
results = list(wtp.patch.parse_context_diff(text_diff))
- self.assertEqual(results, expected)
-
-
- expected_main = wtp.patch.diffobj(header=
- wtp.patch.header(index_path=None,
- old_path='lao',
- old_version='2013-01-05 16:56:19.000000000 -0600',
- new_path='tzu',
- new_version='2013-01-05 16:56:35.000000000 -0600'
- ),
- changes=expected,
- text=text)
+ self.assert_diffs_equal(results, expected)
+
+ expected_main = diffobj(
+ header=headerobj(
+ index_path=None,
+ old_path='lao',
+ old_version='2013-01-05 16:56:19.000000000 -0600',
+ new_path='tzu',
+ new_version='2013-01-05 16:56:35.000000000 -0600',
+ ),
+ changes=expected,
+ text=text,
+ )
results_main = next(wtp.patch.parse_patch(text))
- self.assertEqual(results_main, expected_main)
+ self.assert_diffs_equal(results_main, expected_main)
def test_ed_diff(self):
with open(datapath('diff-ed.diff')) as f:
@@ -917,13 +920,12 @@ def test_ed_diff(self):
(None, 13, 'The door of all subtleties!')
]
-
results = list(wtp.patch.parse_ed_diff(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
- expected_main = [wtp.patch.diffobj(header=None, changes=expected, text=text)]
+ expected_main = [diffobj(header=None, changes=expected, text=text)]
results_main = list(wtp.patch.parse_patch(text))
- self.assertEqual(results_main, expected_main)
+ self.assert_diffs_equal(results_main, expected_main)
def test_rcs_diff(self):
with open(datapath('diff-rcs.diff')) as f:
@@ -940,93 +942,98 @@ def test_rcs_diff(self):
(None, 13, 'The door of all subtleties!')
]
-
results = list(wtp.patch.parse_rcs_ed_diff(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
- expected_main = [wtp.patch.diffobj(header=None, changes=expected, text=text)]
+ expected_main = [diffobj(header=None, changes=expected, text=text)]
results_main = list(wtp.patch.parse_patch(text))
- self.assertEqual(results_main, expected_main)
+ self.assert_diffs_equal(results_main, expected_main)
def test_embedded_diff_in_comment(self):
with open('tests/casefiles/embedded-diff.comment') as f:
text = f.read()
- expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path=None,
- old_path='src/org/mozilla/javascript/IRFactory.java',
- old_version=None,
- new_path='src/org/mozilla/javascript/IRFactory.java',
- new_version=None,
- ),
- changes=[
- (2182, 2182, ' case Token.GETELEM:'),
- (2183, 2183, ' decompileElementGet((ElementGet) node);'),
- (2184, 2184, ' break;'),
- (None, 2185, ' case Token.THIS:'),
- (None, 2186, ' decompiler.addToken(node.getType());'),
- (None, 2187, ' break;'),
- (2185, 2188, ' default:'),
- (2186, 2189, ' Kit.codeBug("unexpected token: "'),
- (2187, 2190, ' + Token.typeToName(node.getType()));'),
- ],
- text=text
- ),
- ]
+ changes = indent(10, [
+ (2182, 2182, 'case Token.GETELEM:'),
+ (2183, 2183, ' decompileElementGet((ElementGet) node);'),
+ (2184, 2184, ' break;'),
+ (None, 2185, 'case Token.THIS:'),
+ (None, 2186, ' decompiler.addToken(node.getType());'),
+ (None, 2187, ' break;'),
+ (2185, 2188, 'default:'),
+ (2186, 2189, ' Kit.codeBug("unexpected token: "'),
+ (2187, 2190, ' '
+ '+ Token.typeToName(node.getType()));'),
+ ])
+
+ expected = [diffobj(
+ header=headerobj(
+ index_path=None,
+ old_path='src/org/mozilla/javascript/IRFactory.java',
+ old_version=None,
+ new_path='src/org/mozilla/javascript/IRFactory.java',
+ new_version=None,
+ ),
+ changes=changes,
+ text=text,
+ )]
results = list(wtp.patch.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_mozilla_527452_5_comment(self):
with open('tests/casefiles/mozilla-527452-5.comment') as f:
text = f.read()
lines = text.splitlines()
-
- expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='js_instrumentation_proxy/src/org/mozilla/javascript/ast/StringLiteral.java',
- old_path='js_instrumentation_proxy/src/org/mozilla/javascript/ast/StringLiteral.java',
- old_version=5547,
- new_path='js_instrumentation_proxy/src/org/mozilla/javascript/ast/StringLiteral.java',
- new_version=None,
- ),
- changes=[
- (112, 112, ' // TODO(stevey): make sure this unescapes everything properly'),
- (113, 113, ' String q = String.valueOf(getQuoteCharacter());'),
- (114, 114, ' String rep = "\\\\\\\\" + q;'), # escape the escape that's escaping an escape. wut
- (115, None, ' String s = value.replaceAll(q, rep);'),
- (None, 115, ' String s = value.replace("\\\\", "\\\\\\\\");'),
- (None, 116, ' s = s.replaceAll(q, rep);'),
- (116, 117, ' s = s.replaceAll("\\n", "\\\\\\\\n");'),
- (117, 118, ' s = s.replaceAll("\\r", "\\\\\\\\r");'),
- (118, 119, ' s = s.replaceAll("\\t", "\\\\\\\\t");')
- ],
- text = '\n'.join(lines[2:]) + '\n'
- ),
- ]
+ path = (
+ 'js_instrumentation_proxy/src/org/mozilla/'
+ 'javascript/ast/StringLiteral.java'
+ )
+ header = headerobj(
+ index_path=path,
+ old_path=path,
+ old_version=5547,
+ new_path=path,
+ new_version=None,
+ )
+
+ changes = indent(8, [
+ (112, 112, '// TODO(stevey): make sure this unescapes '
+ 'everything properly'),
+ (113, 113, 'String q = String.valueOf(getQuoteCharacter());'),
+ (114, 114, r'String rep = "\\\\" + q;'),
+ (115, None, 'String s = value.replaceAll(q, rep);'),
+ (None, 115, r'String s = value.replace("\\", "\\\\");'),
+ (None, 116, 's = s.replaceAll(q, rep);'),
+ (116, 117, r's = s.replaceAll("\n", "\\\\n");'),
+ (117, 118, r's = s.replaceAll("\r", "\\\\r");'),
+ (118, 119, r's = s.replaceAll("\t", "\\\\t");'),
+ ])
+ text = '\n'.join(lines[2:]) + '\n'
+
+ expected = [diffobj(header=header, changes=changes, text=text)]
results = list(wtp.patch.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_dos_unified_cvs(self):
with open('tests/casefiles/mozilla-560291.diff') as f:
text = f.read()
+ path = 'src/org/mozilla/javascript/ast/ArrayComprehensionLoop.java'
lines = text.splitlines()
+ header = headerobj(
+ index_path=path,
+ old_path=path,
+ old_version='1.1',
+ new_path=path,
+ new_version='15 Sep 2011 02:26:05 -0000'
+ )
expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='src/org/mozilla/javascript/ast/ArrayComprehensionLoop.java',
- old_path='src/org/mozilla/javascript/ast/ArrayComprehensionLoop.java',
- old_version='1.1',
- new_path='src/org/mozilla/javascript/ast/ArrayComprehensionLoop.java',
- new_version='15 Sep 2011 02:26:05 -0000'
- ),
+ diffobj(
+ header=header,
changes=[
(79, 79, ' @Override'),
(80, 80, ' public String toSource(int depth) {'),
@@ -1039,37 +1046,38 @@ def test_dos_unified_cvs(self):
(84, 86, ' + " in "'),
(85, 87, ' + iteratedObject.toSource(0)')
],
- text = '\n'.join(lines[2:]) + '\n'
+ text='\n'.join(lines[2:]) + '\n'
)
]
results = list(wtp.patch.parse_patch(text))
- self.assertEqual(results, expected)
-
+ self.assert_diffs_equal(results, expected)
def test_old_style_cvs(self):
with open('tests/casefiles/mozilla-252983.diff') as f:
text = f.read()
- expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='mozilla/js/rhino/CHANGELOG',
- old_path='mozilla/js/rhino/CHANGELOG',
- old_version='1.1.1.1',
- new_path='mozilla/js/rhino/CHANGELOG',
- new_version='1.1', # or 'Thu Jan 25 10:59:02 2007'
- ),
- changes=[
- (1, None, 'This file version: $Id: CHANGELOG,v 1.1.1.1 2007/01/25 15:59:02 inonit Exp $'),
- (None, 1, 'This file version: $Id: CHANGELOG,v 1.1 2007/01/25 15:59:02 inonit Exp $'),
- (2, 2, ''),
- (3, 3, 'Changes since Rhino 1.6R5'),
- (4, 4, '========================='),
- ],
- text=text
- ),
- ]
+ changes = [
+ (1, None, 'This file version: $Id: CHANGELOG,v 1.1.1.1 '
+ '2007/01/25 15:59:02 inonit Exp $'),
+ (None, 1, 'This file version: $Id: CHANGELOG,v 1.1 '
+ '2007/01/25 15:59:02 inonit Exp $'),
+ (2, 2, ''),
+ (3, 3, 'Changes since Rhino 1.6R5'),
+ (4, 4, '========================='),
+ ]
+
+ expected = [diffobj(
+ header=headerobj(
+ index_path='mozilla/js/rhino/CHANGELOG',
+ old_path='mozilla/js/rhino/CHANGELOG',
+ old_version='1.1.1.1',
+ new_path='mozilla/js/rhino/CHANGELOG',
+ new_version='1.1', # or 'Thu Jan 25 10:59:02 2007'
+ ),
+ changes=changes,
+ text=text,
+ )]
results = wtp.patch.parse_cvs_header(text)
self.assertEqual(results, expected[0].header)
@@ -1078,37 +1086,39 @@ def test_old_style_cvs(self):
self.assertEqual(results, expected[0].header)
results = list(wtp.patch.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_mozilla_252983_versionless(self):
with open('tests/casefiles/mozilla-252983-versionless.diff') as f:
text = f.read()
- expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path='mozilla/js/rhino/CHANGELOG',
- old_path='mozilla/js/rhino/CHANGELOG',
- old_version=None,
- new_path='mozilla/js/rhino/CHANGELOG',
- new_version=None,
- ),
- changes=[
- (1, None, 'This file version: $Id: CHANGELOG,v 1.1.1.1 2007/01/25 15:59:02 inonit Exp $'),
- (None, 1, 'This file version: $Id: CHANGELOG,v 1.1 2007/01/25 15:59:02 inonit Exp $'),
- (2, 2, ''),
- (3, 3, 'Changes since Rhino 1.6R5'),
- (4, 4, '========================='),
- ],
- text=text
- ),
- ]
+ changes = [
+ (1, None, 'This file version: $Id: CHANGELOG,v 1.1.1.1 '
+ '2007/01/25 15:59:02 inonit Exp $'),
+ (None, 1, 'This file version: $Id: CHANGELOG,v 1.1 '
+ '2007/01/25 15:59:02 inonit Exp $'),
+ (2, 2, ''),
+ (3, 3, 'Changes since Rhino 1.6R5'),
+ (4, 4, '========================='),
+ ]
+
+ expected = [diffobj(
+ header=headerobj(
+ index_path='mozilla/js/rhino/CHANGELOG',
+ old_path='mozilla/js/rhino/CHANGELOG',
+ old_version=None,
+ new_path='mozilla/js/rhino/CHANGELOG',
+ new_version=None,
+ ),
+ changes=changes,
+ text=text,
+ )]
results = wtp.patch.parse_header(text)
self.assertEqual(results, expected[0].header)
results = list(wtp.patch.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_apache_attachment_2241(self):
with open('tests/casefiles/apache-attachment-2241.diff') as f:
@@ -1116,43 +1126,59 @@ def test_apache_attachment_2241(self):
lines = text.splitlines()
- expected = [
- wtp.patch.diffobj(
- header=wtp.patch.header(
- index_path=None,
- old_path='src\\main\\org\\apache\\tools\\ant\\taskdefs\\optional\\pvcs\\Pvcs.orig',
- old_version='Sat Jun 22 16:11:58 2002',
- new_path='src\\main\\org\\apache\\tools\\ant\\taskdefs\\optional\\pvcs\\Pvcs.java',
- new_version='Fri Jun 28 10:55:50 2002'
- ),
- changes=[
- (91, 91, ' *'),
- (92, 92, ' * @author Thomas Christensen'),
- (93, 93, ' * @author Don Jeffery'),
- (94, None, ' * @author Steven E. Newton'),
- (None, 94, ' * @author Steven E. Newton'),
- (95, 95, ' */'),
- (96, 96, 'public class Pvcs extends org.apache.tools.ant.Task {'),
- (97, 97, ' private String pvcsbin;')
- ],
- text= '\n'.join(lines) + '\n'
- ),
- ]
+ header = headerobj(
+ index_path=None,
+ old_path=(
+ r'src\main\org\apache\tools\ant'
+ r'\taskdefs\optional\pvcs\Pvcs.orig'
+ ),
+ old_version='Sat Jun 22 16:11:58 2002',
+ new_path=(
+ r'src\main\org\apache\tools\ant'
+ r'\taskdefs\optional\pvcs\Pvcs.java'
+ ),
+ new_version='Fri Jun 28 10:55:50 2002'
+ )
+
+ changes = [
+ (91, 91, ' *'),
+ (92, 92, ' * @author '
+ 'Thomas Christensen'),
+ (93, 93, ' * @author '
+ 'Don Jeffery'),
+ (94, None, ' * @author '
+ 'Steven E. Newton'),
+ (None, 94, ' * @author '
+ 'Steven E. Newton'),
+ (95, 95, ' */'),
+ (96, 96, 'public class Pvcs extends org.apache.tools.ant.Task {'),
+ (97, 97, ' private String pvcsbin;')
+ ]
+
+ text = '\n'.join(lines) + '\n'
+
+ expected = [diffobj(header=header, changes=changes, text=text)]
results = list(wtp.patch.parse_patch(text))
- self.assertEqual(results, expected)
+ self.assert_diffs_equal(results, expected)
def test_space_in_path_header(self):
with open('tests/casefiles/eclipse-attachment-126343.header') as f:
text = f.read()
- expected = wtp.patch.header(
- index_path = 'test plugin/org/eclipse/jdt/debug/testplugin/ResumeBreakpointListener.java',
- old_path = '/dev/null',
- old_version = '1 Jan 1970 00:00:00 -0000',
- new_path = 'test plugin/org/eclipse/jdt/debug/testplugin/ResumeBreakpointListener.java',
- new_version = '1 Jan 1970 00:00:00 -0000'
- )
+ expected = headerobj(
+ index_path=(
+ 'test plugin/org/eclipse/jdt/debug/testplugin/'
+ 'ResumeBreakpointListener.java'
+ ),
+ old_path='/dev/null',
+ old_version='1 Jan 1970 00:00:00 -0000',
+ new_path=(
+ 'test plugin/org/eclipse/jdt/debug/testplugin/'
+ 'ResumeBreakpointListener.java'
+ ),
+ new_version='1 Jan 1970 00:00:00 -0000'
+ )
results = wtp.patch.parse_header(text)
self.assertEqual(results, expected)
@@ -1161,12 +1187,15 @@ def test_svn_mixed_line_ends(self):
with open('tests/casefiles/svn-mixed_line_ends.patch') as f:
text = f.read()
- expected_header = wtp.patch.header(
- index_path='java/org/apache/catalina/loader/WebappClassLoader.java',
- old_path='java/org/apache/catalina/loader/WebappClassLoader.java',
- old_version=1346371,
- new_path='java/org/apache/catalina/loader/WebappClassLoader.java',
- new_version=None)
+ expected_header = headerobj(
+ index_path=(
+ 'java/org/apache/catalina/loader/WebappClassLoader.java'
+ ),
+ old_path='java/org/apache/catalina/loader/WebappClassLoader.java',
+ old_version=1346371,
+ new_path='java/org/apache/catalina/loader/WebappClassLoader.java',
+ new_version=None,
+ )
results = list(wtp.patch.parse_patch(text))
self.assertEqual(results[0].header, expected_header)
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..2638048
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,14 @@
+[tox]
+envlist = py{27,34,35,36,37}, lint
+
+[testenv]
+commands = nosetests --with-doctest --doctest-extension=rst {posargs}
+deps =
+ pytz
+ nose==1.3.7
+ hypothesis==4.14.0
+
+[testenv:lint]
+deps =
+ flake8==3.7.7
+commands=flake8 whatthepatch tests setup.py
diff --git a/whatthepatch/__init__.py b/whatthepatch/__init__.py
index d9e8088..9fb5787 100644
--- a/whatthepatch/__init__.py
+++ b/whatthepatch/__init__.py
@@ -2,3 +2,5 @@
from .patch import parse_patch
from .apply import apply_diff
+
+__all__ = ['parse_patch', 'apply_diff']
diff --git a/whatthepatch/apply.py b/whatthepatch/apply.py
index ed74467..e5d302f 100644
--- a/whatthepatch/apply.py
+++ b/whatthepatch/apply.py
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
-import re
import subprocess
-from . import patch
+from . import patch, exceptions
from .snippets import which, remove
+
def apply_patch(diffs):
""" Not ready for use yet """
pass
@@ -24,69 +24,103 @@ def apply_patch(diffs):
with open(diff.header.new_path, 'w') as f:
f.write(new_text)
-def apply_diff(diff, text, use_patch=False):
+
+def _apply_diff_with_subprocess(diff, lines, reverse=False):
+ # call out to patch program
+ patchexec = which('patch')
+ if not patchexec:
+ raise exceptions.SubprocessException('patch program does not exist')
+
+ filepath = '/tmp/wtp-' + str(hash(diff.header))
+ oldfilepath = filepath + '.old'
+ newfilepath = filepath + '.new'
+ rejfilepath = filepath + '.rej'
+ patchfilepath = filepath + '.patch'
+ with open(oldfilepath, 'w') as f:
+ f.write('\n'.join(lines) + '\n')
+
+ with open(patchfilepath, 'w') as f:
+ f.write(diff.text)
+
+ args = [patchexec,
+ '--reverse' if reverse else '--forward',
+ '--quiet',
+ '-o', newfilepath,
+ '-i', patchfilepath,
+ '-r', rejfilepath,
+ oldfilepath
+ ]
+ ret = subprocess.call(args)
+
+ with open(newfilepath) as f:
+ lines = f.read().splitlines()
+
+ try:
+ with open(rejfilepath) as f:
+ rejlines = f.read().splitlines()
+ except IOError:
+ rejlines = None
+
+ remove(oldfilepath)
+ remove(newfilepath)
+ remove(rejfilepath)
+ remove(patchfilepath)
+
+ # do this last to ensure files get cleaned up
+ if ret != 0:
+ raise exceptions.SubprocessException('patch program failed', code=ret)
+
+ return lines, rejlines
+
+
+def _reverse(changes):
+ def _reverse_change(c):
+ return c._replace(
+ old=c.new,
+ new=c.old,
+ )
+
+ return [_reverse_change(c) for c in changes]
+
+
+def apply_diff(diff, text, reverse=False, use_patch=False):
try:
lines = text.splitlines()
except AttributeError:
lines = list(text)
if use_patch:
- # call out to patch program
- patchexec = which('patch')
- assert patchexec # patch program does not exist
-
- filepath = '/tmp/wtp-' + str(hash(diff.header))
- oldfilepath = filepath + '.old'
- newfilepath = filepath + '.new'
- rejfilepath = filepath + '.rej'
- patchfilepath = filepath + '.patch'
- with open(oldfilepath, 'w') as f:
- f.write('\n'.join(lines) + '\n')
-
- with open(patchfilepath, 'w') as f:
- f.write(diff.text)
-
- args = [patchexec,
- '--quiet',
- '-o', newfilepath,
- '-i', patchfilepath,
- '-r', rejfilepath,
- oldfilepath
- ]
- ret = subprocess.call(args)
-
-
- with open(newfilepath) as f:
- lines = f.read().splitlines()
-
- try:
- with open(rejfilepath) as f:
- rejlines = f.read().splitlines()
- except IOError:
- rejlines = None
-
- remove(oldfilepath)
- remove(newfilepath)
- remove(rejfilepath)
- remove(patchfilepath)
-
- # do this last to ensure files get cleaned up
- assert ret == 0 # patch return code is success
-
- return lines, rejlines
+ return _apply_diff_with_subprocess(diff, lines, reverse)
+ n_lines = len(lines)
+
+ changes = diff.changes or []
+ changes = _reverse(changes) if reverse else changes
# check that the source text matches the context of the diff
- for old, new, line in diff.changes:
+ for old, new, hunk, line in changes:
# might have to check for line is None here for ed scripts
if old is not None and line is not None:
- assert len(lines) >= old
- assert lines[old-1] == line
+ if old > n_lines:
+ raise exceptions.HunkApplyException(
+ 'context line {n}, "{line}" does not exist in source'
+ .format(n=old, line=line),
+ hunk=hunk,
+ )
+ if lines[old-1] != line:
+ raise exceptions.HunkApplyException(
+ 'context line {n}, "{line}" does not match "{sl}"'.format(
+ n=old,
+ line=line,
+ sl=lines[old-1]
+ ),
+ hunk=hunk,
+ )
# for calculating the old line
r = 0
i = 0
- for old, new, line in diff.changes:
+ for old, new, hunk, line in changes:
if old is not None and new is None:
del lines[old-1-r+i]
r += 1
@@ -95,13 +129,10 @@ def apply_diff(diff, text, use_patch=False):
i += 1
elif old is not None and new is not None:
# are we crazy?
- #assert new == old - r + i
+ # assert new == old - r + i
# Sometimes, people remove hunks from patches, making these
# numbers completely unreliable. Because they're jerks.
pass
return lines
-
-
-
diff --git a/whatthepatch/exceptions.py b/whatthepatch/exceptions.py
new file mode 100644
index 0000000..00c53e4
--- /dev/null
+++ b/whatthepatch/exceptions.py
@@ -0,0 +1,32 @@
+class WhatThePatchException(Exception):
+ pass
+
+
+class HunkException(WhatThePatchException):
+ def __init__(self, msg, hunk=None):
+ self.hunk = hunk
+ if hunk is not None:
+ super(HunkException, self).__init__('{msg}, in hunk #{n}'.format(
+ msg=msg,
+ n=hunk,
+ ))
+ else:
+ super(HunkException, self).__init__(msg)
+
+
+class ApplyException(WhatThePatchException):
+ pass
+
+
+class SubprocessException(ApplyException):
+ def __init__(self, msg, code):
+ super(SubprocessException, self).__init__(msg)
+ self.code = code
+
+
+class HunkApplyException(HunkException, ApplyException, ValueError):
+ pass
+
+
+class ParseException(HunkException, ValueError):
+ pass
diff --git a/whatthepatch/patch.py b/whatthepatch/patch.py
index 1a06836..5f09b6a 100644
--- a/whatthepatch/patch.py
+++ b/whatthepatch/patch.py
@@ -4,11 +4,15 @@
from collections import namedtuple
from .snippets import split_by_regex, findall_regex
+from . import exceptions
-header = namedtuple('header',
- 'index_path old_path old_version new_path new_version')
+header = namedtuple(
+ 'header',
+ 'index_path old_path old_version new_path new_version',
+)
diffobj = namedtuple('diff', 'header changes text')
+Change = namedtuple('Change', 'old new hunk line')
file_timestamp_str = '(.+?)(?:\t|:| +)(.*)'
# .+? was previously [^:\t\n\r\f\v]+
@@ -16,24 +20,24 @@
# general diff regex
diffcmd_header = re.compile('^diff.* (.+) (.+)$')
unified_header_index = re.compile('^Index: (.+)$')
-unified_header_old_line = re.compile('^--- ' + file_timestamp_str + '$')
-unified_header_new_line = re.compile('^\+\+\+ ' + file_timestamp_str + '$')
-unified_hunk_start = re.compile('^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@(.*)$')
+unified_header_old_line = re.compile(r'^--- ' + file_timestamp_str + '$')
+unified_header_new_line = re.compile(r'^\+\+\+ ' + file_timestamp_str + '$')
+unified_hunk_start = re.compile(r'^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@(.*)$')
unified_change = re.compile('^([-+ ])(.*)$')
-context_header_old_line = re.compile('^\*\*\* ' + file_timestamp_str + '$')
+context_header_old_line = re.compile(r'^\*\*\* ' + file_timestamp_str + '$')
context_header_new_line = re.compile('^--- ' + file_timestamp_str + '$')
-context_hunk_start = re.compile('^\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*$')
-context_hunk_old = re.compile('^\*\*\* (\d+),?(\d*) \*\*\*\*$')
-context_hunk_new = re.compile('^--- (\d+),?(\d*) ----$')
+context_hunk_start = re.compile(r'^\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*$')
+context_hunk_old = re.compile(r'^\*\*\* (\d+),?(\d*) \*\*\*\*$')
+context_hunk_new = re.compile(r'^--- (\d+),?(\d*) ----$')
context_change = re.compile('^([-+ !]) (.*)$')
-ed_hunk_start = re.compile('^(\d+),?(\d*)([acd])$')
+ed_hunk_start = re.compile(r'^(\d+),?(\d*)([acd])$')
ed_hunk_end = re.compile('^.$')
# much like forward ed, but no 'c' type
-rcs_ed_hunk_start = re.compile('^([ad])(\d+) ?(\d*)$')
+rcs_ed_hunk_start = re.compile(r'^([ad])(\d+) ?(\d*)$')
-default_hunk_start = re.compile('^(\d+),?(\d*)([acd])(\d+),?(\d*)$')
+default_hunk_start = re.compile(r'^(\d+),?(\d*)([acd])(\d+),?(\d*)$')
default_hunk_mid = re.compile('^---$')
default_change = re.compile('^([><]) (.*)$')
@@ -41,10 +45,10 @@
# git has a special index header and no end part
git_diffcmd_header = re.compile('^diff --git a/(.+) b/(.+)$')
-git_header_index = re.compile('^index ([a-f0-9]+)..([a-f0-9]+) ?(\d*)$')
+git_header_index = re.compile(r'^index ([a-f0-9]+)..([a-f0-9]+) ?(\d*)$')
git_header_old_line = re.compile('^--- (.+)$')
-git_header_new_line = re.compile('^\+\+\+ (.+)$')
-git_header_file_mode = re.compile('^(new|deleted) file mode \d{6}$')
+git_header_new_line = re.compile(r'^\+\+\+ (.+)$')
+git_header_file_mode = re.compile(r'^(new|deleted) file mode \d{6}$')
git_header_binary_file = re.compile('^Binary files (.+) and (.+) differ')
bzr_header_index = re.compile("=== (.+)")
@@ -52,13 +56,15 @@
bzr_header_new_line = unified_header_new_line
svn_header_index = unified_header_index
-svn_header_timestamp_version = re.compile('\((?:working copy|revision (\d+))\)')
-svn_header_timestamp = re.compile('.*(\(.*\))$')
+svn_header_timestamp_version = re.compile(
+ r'\((?:working copy|revision (\d+))\)'
+)
+svn_header_timestamp = re.compile(r'.*(\(.*\))$')
cvs_header_index = unified_header_index
-cvs_header_rcs = re.compile('^RCS file: (.+)(?:,\w{1}$|$)')
-cvs_header_timestamp = re.compile('(.+)\t([\d.]+)')
-cvs_header_timestamp_colon = re.compile(':([\d.]+)\t(.+)')
+cvs_header_rcs = re.compile(r'^RCS file: (.+)(?:,\w{1}$|$)')
+cvs_header_timestamp = re.compile(r'(.+)\t([\d.]+)')
+cvs_header_timestamp_colon = re.compile(r':([\d.]+)\t(.+)')
old_cvs_diffcmd_header = re.compile('^diff.* (.+):(.*) (.+):(.*)$')
@@ -69,17 +75,17 @@ def parse_patch(text):
lines = text
# maybe use this to nuke all of those line endings?
- #lines = [x.splitlines()[0] for x in lines]
+ # lines = [x.splitlines()[0] for x in lines]
lines = [x if len(x) == 0 else x.splitlines()[0] for x in lines]
check = [
- unified_header_index,
- diffcmd_header,
- cvs_header_rcs,
- git_header_index,
- context_header_old_line,
- unified_header_old_line,
- ]
+ unified_header_index,
+ diffcmd_header,
+ cvs_header_rcs,
+ git_header_index,
+ context_header_old_line,
+ unified_header_old_line,
+ ]
for c in check:
diffs = split_by_regex(lines, c)
@@ -93,12 +99,14 @@ def parse_patch(text):
if h or d:
yield diffobj(header=h, changes=d, text=difftext)
+
def parse_header(text):
h = parse_scm_header(text)
if h is None:
h = parse_diff_header(text)
return h
+
def parse_scm_header(text):
try:
lines = text.splitlines()
@@ -128,12 +136,12 @@ def parse_scm_header(text):
new_path = new_path[2:]
return header(
- index_path=res.index_path,
- old_path = old_path,
- old_version = res.old_version,
- new_path = new_path,
- new_version = res.new_version
- )
+ index_path=res.index_path,
+ old_path=old_path,
+ old_version=res.old_version,
+ new_path=new_path,
+ new_version=res.new_version
+ )
else:
res = parser(lines)
@@ -141,6 +149,7 @@ def parse_scm_header(text):
return None
+
def parse_diff_header(text):
try:
lines = text.splitlines()
@@ -148,21 +157,21 @@ def parse_diff_header(text):
lines = text
check = [
- (unified_header_new_line, parse_unified_header),
- (context_header_old_line, parse_context_header),
- (diffcmd_header, parse_diffcmd_header),
- # TODO:
- # git_header can handle version-less unified headers, but
- # will trim a/ and b/ in the paths if they exist...
- (git_header_new_line, parse_git_header),
- ]
+ (unified_header_new_line, parse_unified_header),
+ (context_header_old_line, parse_context_header),
+ (diffcmd_header, parse_diffcmd_header),
+ # TODO:
+ # git_header can handle version-less unified headers, but
+ # will trim a/ and b/ in the paths if they exist...
+ (git_header_new_line, parse_git_header),
+ ]
for regex, parser in check:
diffs = findall_regex(lines, regex)
if len(diffs) > 0:
return parser(lines)
- return None # no header?
+ return None # no header?
def parse_diff(text):
@@ -172,18 +181,19 @@ def parse_diff(text):
lines = text
check = [
- (unified_hunk_start, parse_unified_diff),
- (context_hunk_start, parse_context_diff),
- (default_hunk_start, parse_default_diff),
- (ed_hunk_start, parse_ed_diff),
- (rcs_ed_hunk_start, parse_rcs_ed_diff),
- ]
+ (unified_hunk_start, parse_unified_diff),
+ (context_hunk_start, parse_context_diff),
+ (default_hunk_start, parse_default_diff),
+ (ed_hunk_start, parse_ed_diff),
+ (rcs_ed_hunk_start, parse_rcs_ed_diff),
+ ]
for hunk, parser in check:
diffs = findall_regex(lines, hunk)
if len(diffs) > 0:
return parser(lines)
+
def parse_git_header(text):
try:
lines = text.splitlines()
@@ -221,14 +231,16 @@ def parse_git_header(text):
if new_path.startswith('b/'):
new_path = new_path[2:]
return header(
- index_path = None,
- old_path = old_path,
- old_version = over,
- new_path = new_path,
- new_version = nver)
+ index_path=None,
+ old_path=old_path,
+ old_version=over,
+ new_path=new_path,
+ new_version=nver
+ )
return None
+
def parse_svn_header(text):
try:
lines = text.splitlines()
@@ -242,60 +254,64 @@ def parse_svn_header(text):
while len(lines) > 0:
i = svn_header_index.match(lines[0])
del lines[0]
- if i:
- diff_header = parse_diff_header(lines)
- if diff_header:
- opath = diff_header.old_path
- over = diff_header.old_version
- if over:
- oend = svn_header_timestamp_version.match(over)
- if oend and oend.group(1):
- over = int(oend.group(1))
- elif opath:
- ts = svn_header_timestamp.match(opath)
- if ts:
- opath = opath[:-len(ts.group(1))]
- oend = svn_header_timestamp_version.match(ts.group(1))
- if oend and oend.group(1):
- over = int(oend.group(1))
-
- npath = diff_header.new_path
- nver = diff_header.new_version
- if nver:
- nend = svn_header_timestamp_version.match(diff_header.new_version)
- if nend and nend.group(1):
- nver = int(nend.group(1))
- elif npath:
- ts = svn_header_timestamp.match(npath)
- if ts:
- npath = npath[:-len(ts.group(1))]
- nend = svn_header_timestamp_version.match(ts.group(1))
- if nend and nend.group(1):
- nver = int(nend.group(1))
-
- if type(over) != int:
- over = None
-
- if type(nver) != int:
- nver = None
+ if not i:
+ continue
- return header(
- index_path = i.group(1),
- old_path = opath,
- old_version = over,
- new_path = npath,
- new_version = nver,
- )
+ diff_header = parse_diff_header(lines)
+ if not diff_header:
return header(
- index_path = i.group(1),
- old_path = i.group(1),
- old_version = None,
- new_path = i.group(1),
- new_version = None,
- )
+ index_path=i.group(1),
+ old_path=i.group(1),
+ old_version=None,
+ new_path=i.group(1),
+ new_version=None,
+ )
+
+ opath = diff_header.old_path
+ over = diff_header.old_version
+ if over:
+ oend = svn_header_timestamp_version.match(over)
+ if oend and oend.group(1):
+ over = int(oend.group(1))
+ elif opath:
+ ts = svn_header_timestamp.match(opath)
+ if ts:
+ opath = opath[:-len(ts.group(1))]
+ oend = svn_header_timestamp_version.match(ts.group(1))
+ if oend and oend.group(1):
+ over = int(oend.group(1))
+
+ npath = diff_header.new_path
+ nver = diff_header.new_version
+ if nver:
+ nend = svn_header_timestamp_version.match(diff_header.new_version)
+ if nend and nend.group(1):
+ nver = int(nend.group(1))
+ elif npath:
+ ts = svn_header_timestamp.match(npath)
+ if ts:
+ npath = npath[:-len(ts.group(1))]
+ nend = svn_header_timestamp_version.match(ts.group(1))
+ if nend and nend.group(1):
+ nver = int(nend.group(1))
+
+ if type(over) != int:
+ over = None
+
+ if type(nver) != int:
+ nver = None
+
+ return header(
+ index_path=i.group(1),
+ old_path=opath,
+ old_version=over,
+ new_path=npath,
+ new_version=nver,
+ )
return None
+
def parse_cvs_header(text):
try:
lines = text.splitlines()
@@ -310,75 +326,76 @@ def parse_cvs_header(text):
while len(lines) > 0:
i = cvs_header_index.match(lines[0])
del lines[0]
- if i:
- diff_header = parse_diff_header(lines)
- if diff_header:
- over = diff_header.old_version
- if over:
- oend = cvs_header_timestamp.match(over)
- oend_c = cvs_header_timestamp_colon.match(over)
- if oend:
- over = oend.group(2)
- elif oend_c:
- over = oend_c.group(1)
-
- nver = diff_header.new_version
- if nver:
- nend = cvs_header_timestamp.match(nver)
- nend_c = cvs_header_timestamp_colon.match(nver)
- if nend:
- nver = nend.group(2)
- elif nend_c:
- nver = nend_c.group(1)
+ if not i:
+ continue
+
+ diff_header = parse_diff_header(lines)
+ if diff_header:
+ over = diff_header.old_version
+ if over:
+ oend = cvs_header_timestamp.match(over)
+ oend_c = cvs_header_timestamp_colon.match(over)
+ if oend:
+ over = oend.group(2)
+ elif oend_c:
+ over = oend_c.group(1)
+
+ nver = diff_header.new_version
+ if nver:
+ nend = cvs_header_timestamp.match(nver)
+ nend_c = cvs_header_timestamp_colon.match(nver)
+ if nend:
+ nver = nend.group(2)
+ elif nend_c:
+ nver = nend_c.group(1)
- return header(
- index_path = i.group(1),
- old_path = diff_header.old_path,
- old_version = over,
- new_path = diff_header.new_path,
- new_version = nver,
- )
return header(
- index_path = i.group(1),
- old_path = i.group(1),
- old_version = None,
- new_path = i.group(1),
- new_version = None,
- )
+ index_path=i.group(1),
+ old_path=diff_header.old_path,
+ old_version=over,
+ new_path=diff_header.new_path,
+ new_version=nver,
+ )
+ return header(
+ index_path=i.group(1),
+ old_path=i.group(1),
+ old_version=None,
+ new_path=i.group(1),
+ new_version=None,
+ )
elif headers_old:
# parse old style headers
while len(lines) > 0:
i = cvs_header_index.match(lines[0])
del lines[0]
- if i:
- d = old_cvs_diffcmd_header.match(lines[0])
- if d:
- _ = parse_diff_header(lines) # will get rid of the useless stuff for us
- over = d.group(2)
- if not over:
- over = None
-
- nver = d.group(4)
- if not nver:
- nver = None
- return header(
- index_path = i.group(1),
- old_path = d.group(1),
- old_version = over,
- new_path = d.group(3),
- new_version = nver,
- )
+ if not i:
+ continue
+ d = old_cvs_diffcmd_header.match(lines[0])
+ if not d:
return header(
- index_path = i.group(1),
- old_path = i.group(1),
- old_version = None,
- new_path = i.group(1),
- new_version = None,
- )
+ index_path=i.group(1),
+ old_path=i.group(1),
+ old_version=None,
+ new_path=i.group(1),
+ new_version=None,
+ )
+
+ # will get rid of the useless stuff for us
+ parse_diff_header(lines)
+ over = d.group(2) if d.group(2) else None
+ nver = d.group(4) if d.group(4) else None
+ return header(
+ index_path=i.group(1),
+ old_path=d.group(1),
+ old_version=over,
+ new_path=d.group(3),
+ new_version=nver,
+ )
return None
+
def parse_diffcmd_header(text):
try:
lines = text.splitlines()
@@ -394,15 +411,15 @@ def parse_diffcmd_header(text):
del lines[0]
if d:
return header(
- index_path = None,
- old_path = d.group(1),
- old_version = None,
- new_path = d.group(2),
- new_version = None,
- )
-
+ index_path=None,
+ old_path=d.group(1),
+ old_version=None,
+ new_path=d.group(2),
+ new_version=None,
+ )
return None
+
def parse_unified_header(text):
try:
lines = text.splitlines()
@@ -426,18 +443,19 @@ def parse_unified_header(text):
nver = n.group(2)
if len(nver) == 0:
- never = None
+ nver = None
return header(
- index_path = None,
- old_path = o.group(1),
- old_version = over,
- new_path = n.group(1),
- new_version = nver,
- )
+ index_path=None,
+ old_path=o.group(1),
+ old_version=over,
+ new_path=n.group(1),
+ new_version=nver,
+ )
return None
+
def parse_context_header(text):
try:
lines = text.splitlines()
@@ -461,15 +479,15 @@ def parse_context_header(text):
nver = n.group(2)
if len(nver) == 0:
- never = None
+ nver = None
return header(
- index_path = None,
- old_path = o.group(1),
- old_version = over,
- new_path = n.group(1),
- new_version = nver,
- )
+ index_path=None,
+ old_path=o.group(1),
+ old_version=over,
+ new_path=n.group(1),
+ new_version=nver,
+ )
return None
@@ -490,44 +508,46 @@ def parse_default_diff(text):
changes = list()
hunks = split_by_regex(lines, default_hunk_start)
- for hunk in hunks:
- if len(hunk):
- r = 0
- i = 0
- while len(hunk) > 0:
- h = default_hunk_start.match(hunk[0])
- c = default_change.match(hunk[0])
- del hunk[0]
- if h:
- old = int(h.group(1))
- if len(h.group(2)) > 0:
- old_len = int(h.group(2)) - old + 1
- else:
- old_len = 0
-
- new = int(h.group(4))
- if len(h.group(5)) > 0:
- new_len = int(h.group(5)) - new + 1
- else:
- new_len = 0
-
- hunk_kind = h.group(3)
- elif c:
- kind = c.group(1)
- line = c.group(2)
-
- if kind == '<' and (r != old_len or r == 0):
- changes.append((old + r, None, line))
- r += 1
- elif kind == '>' and (i != new_len or i == 0):
- changes.append((None, new + i, line))
- i += 1
+ for hunk_n, hunk in enumerate(hunks):
+ if not len(hunk):
+ continue
+
+ r = 0
+ i = 0
+ while len(hunk) > 0:
+ h = default_hunk_start.match(hunk[0])
+ c = default_change.match(hunk[0])
+ del hunk[0]
+ if h:
+ old = int(h.group(1))
+ if len(h.group(2)) > 0:
+ old_len = int(h.group(2)) - old + 1
+ else:
+ old_len = 0
+
+ new = int(h.group(4))
+ if len(h.group(5)) > 0:
+ new_len = int(h.group(5)) - new + 1
+ else:
+ new_len = 0
+
+ elif c:
+ kind = c.group(1)
+ line = c.group(2)
+
+ if kind == '<' and (r != old_len or r == 0):
+ changes.append(Change(old + r, None, hunk_n, line))
+ r += 1
+ elif kind == '>' and (i != new_len or i == 0):
+ changes.append(Change(None, new + i, hunk_n, line))
+ i += 1
if len(changes) > 0:
return changes
return None
+
def parse_unified_diff(text):
try:
lines = text.splitlines()
@@ -542,7 +562,7 @@ def parse_unified_diff(text):
changes = list()
hunks = split_by_regex(lines, unified_hunk_start)
- for hunk in hunks:
+ for hunk_n, hunk in enumerate(hunks):
# reset counters
r = 0
i = 0
@@ -573,13 +593,13 @@ def parse_unified_diff(text):
c = None
if kind == '-' and (r != old_len or r == 0):
- changes.append((old + r, None, line))
+ changes.append(Change(old + r, None, hunk_n, line))
r += 1
elif kind == '+' and (i != new_len or i == 0):
- changes.append((None, new + i, line))
+ changes.append(Change(None, new + i, hunk_n, line))
i += 1
elif kind == ' ' and r != old_len and i != new_len:
- changes.append((old + r, new + i, line))
+ changes.append(Change(old + r, new + i, hunk_n, line))
r += 1
i += 1
@@ -591,7 +611,6 @@ def parse_unified_diff(text):
return None
-
def parse_context_diff(text):
try:
lines = text.splitlines()
@@ -604,111 +623,128 @@ def parse_context_diff(text):
k = 0
changes = list()
- old_lines = list()
- new_lines = list()
hunks = split_by_regex(lines, context_hunk_start)
- for hunk in hunks:
- if len(hunk):
- j = 0
- k = 0
- parts = split_by_regex(hunk, context_hunk_new)
- if len(parts) != 2:
- raise ValueError("Context diff invalid")
+ for hunk_n, hunk in enumerate(hunks):
+ if not len(hunk):
+ continue
- old_hunk = parts[0]
- new_hunk = parts[1]
+ j = 0
+ k = 0
+ parts = split_by_regex(hunk, context_hunk_new)
+ if len(parts) != 2:
+ raise exceptions.ParseException("Context diff invalid", hunk_n)
+
+ old_hunk = parts[0]
+ new_hunk = parts[1]
+
+ while len(old_hunk) > 0:
+ o = context_hunk_old.match(old_hunk[0])
+ del old_hunk[0]
+
+ if not o:
+ continue
+
+ old = int(o.group(1))
+ old_len = int(o.group(2)) + 1 - old
+ while len(new_hunk) > 0:
+ n = context_hunk_new.match(new_hunk[0])
+ del new_hunk[0]
+
+ if not n:
+ continue
+
+ new = int(n.group(1))
+ new_len = int(n.group(2)) + 1 - new
+ break
+ break
+ # now have old and new set, can start processing?
+ if len(old_hunk) > 0 and len(new_hunk) == 0:
+ msg = "Got unexpected change in removal hunk: "
+ # only removes left?
while len(old_hunk) > 0:
- o = context_hunk_old.match(old_hunk[0])
+ c = context_change.match(old_hunk[0])
del old_hunk[0]
- if o:
- old = int(o.group(1))
- old_len = int(o.group(2)) + 1 - old
- while len(new_hunk) > 0:
- n = context_hunk_new.match(new_hunk[0])
- del new_hunk[0]
- if n:
- new = int(n.group(1))
- new_len = int(n.group(2)) + 1 - new
- break
- break
-
- # now have old and new set, can start processing?
- if len(old_hunk) > 0 and len(new_hunk) == 0:
- # only removes left?
- while len(old_hunk) > 0:
- c = context_change.match(old_hunk[0])
- del old_hunk[0]
- if c:
- kind = c.group(1)
- line = c.group(2)
-
- if kind == '-' and (j != old_len or j == 0):
- changes.append((old + j, None, line))
- j += 1
- elif kind == ' ' and ((j != old_len and k != new_len)
- or (j == 0 or k == 0)):
- changes.append((old + j, new + k, line))
- j += 1
- k += 1
- elif kind == '+' or kind == '!':
- raise ValueError("Got unexpected change in removal hunk: " + kind)
-
- elif len(old_hunk) == 0 and len(new_hunk) > 0:
- # only insertions left?
- while len(new_hunk) > 0:
- c = context_change.match(new_hunk[0])
- del new_hunk[0]
- if c:
- kind = c.group(1)
- line = c.group(2)
-
- if kind == '+' and (k != new_len or k == 0):
- changes.append((None, new + k, line))
- k += 1
- elif kind == ' ' and ((j != old_len and k != new_len)
- or (j == 0 or k == 0)):
- changes.append((old + j, new + k, line))
- j += 1
- k += 1
- elif kind == '-' or kind == '!':
- raise ValueError("Got unexpected change in insertion hunk: " + kind)
+
+ if not c:
+ continue
+
+ kind = c.group(1)
+ line = c.group(2)
+
+ if kind == '-' and (j != old_len or j == 0):
+ changes.append(Change(old + j, None, hunk_n, line))
+ j += 1
+ elif kind == ' ' and ((j != old_len and k != new_len)
+ or (j == 0 or k == 0)):
+ changes.append(Change(old + j, new + k, hunk_n, line))
+ j += 1
+ k += 1
+ elif kind == '+' or kind == '!':
+ raise exceptions.ParseException(msg + kind, hunk_n)
+
+ continue
+
+ if len(old_hunk) == 0 and len(new_hunk) > 0:
+ msg = "Got unexpected change in removal hunk: "
+ # only insertions left?
+ while len(new_hunk) > 0:
+ c = context_change.match(new_hunk[0])
+ del new_hunk[0]
+
+ if not c:
+ continue
+
+ kind = c.group(1)
+ line = c.group(2)
+
+ if kind == '+' and (k != new_len or k == 0):
+ changes.append(Change(None, new + k, hunk_n, line))
+ k += 1
+ elif kind == ' ' and ((j != old_len and k != new_len)
+ or (j == 0 or k == 0)):
+ changes.append(Change(old + j, new + k, hunk_n, line))
+ j += 1
+ k += 1
+ elif kind == '-' or kind == '!':
+ raise exceptions.ParseException(msg + kind, hunk_n)
+ continue
+
+ # both
+ while len(old_hunk) > 0 and len(new_hunk) > 0:
+ oc = context_change.match(old_hunk[0])
+ nc = context_change.match(new_hunk[0])
+ okind = None
+ nkind = None
+
+ if oc:
+ okind = oc.group(1)
+ oline = oc.group(2)
+
+ if nc:
+ nkind = nc.group(1)
+ nline = nc.group(2)
+
+ if not (oc or nc):
+ del old_hunk[0]
+ del new_hunk[0]
+ elif okind == ' ' and nkind == ' ' and oline == nline:
+ changes.append(Change(old + j, new + k, hunk_n, oline))
+ j += 1
+ k += 1
+ del old_hunk[0]
+ del new_hunk[0]
+ elif okind == '-' or okind == '!' and (j != old_len or j == 0):
+ changes.append(Change(old + j, None, hunk_n, oline))
+ j += 1
+ del old_hunk[0]
+ elif nkind == '+' or nkind == '!' and (k != old_len or k == 0):
+ changes.append(Change(None, new + k, hunk_n, nline))
+ k += 1
+ del new_hunk[0]
else:
- # both
- while len(old_hunk) > 0 and len(new_hunk) > 0:
- oc = context_change.match(old_hunk[0])
- nc = context_change.match(new_hunk[0])
- okind = None
- nkind = None
-
- if oc:
- okind = oc.group(1)
- oline = oc.group(2)
-
- if nc:
- nkind = nc.group(1)
- nline = nc.group(2)
-
- if not (oc or nc):
- del old_hunk[0]
- del new_hunk[0]
- elif okind == ' ' and nkind == ' ' and oline == nline:
- changes.append((old + j, new + k, oline))
- j += 1
- k += 1
- del old_hunk[0]
- del new_hunk[0]
- elif okind == '-' or okind == '!' and (j != old_len or j == 0):
- changes.append((old + j, None, oline))
- j += 1
- del old_hunk[0]
- elif nkind == '+' or nkind == '!' and (k != old_len or k == 0):
- changes.append((None, new + k, nline))
- k += 1
- del new_hunk[0]
- else:
- return None
+ return None
if len(changes) > 0:
return changes
@@ -723,7 +759,6 @@ def parse_ed_diff(text):
lines = text
old = 0
- new = 0
j = 0
k = 0
@@ -734,59 +769,68 @@ def parse_ed_diff(text):
hunks = split_by_regex(lines, ed_hunk_start)
hunks.reverse()
- for hunk in hunks:
- if len(hunk):
- j = 0
- k = 0
- while len(hunk) > 0:
- o = ed_hunk_start.match(hunk[0])
- del hunk[0]
- if o:
- old = int(o.group(1))
- if len(o.group(2)):
- old_end = int(o.group(2))
- else:
- old_end = old
-
- hunk_kind = o.group(3)
- if hunk_kind == 'd':
- k = 0
- while old_end >= old:
- changes.append((old + k, None, None))
- r += 1
- k += 1
- old_end -= 1
- else:
- while len(hunk) > 0:
- e = ed_hunk_end.match(hunk[0])
- if e:
- pass
- elif hunk_kind == 'c':
- k = 0
- while old_end >= old:
- changes.append((old + k, None, None))
- r += 1
- k += 1
- old_end -= 1
-
- # I basically have no idea why this works
- # for these tests.
- changes.append((None, old - r + i + k + j, hunk[0]))
- i += 1
- j += 1
- elif hunk_kind == 'a':
- changes.append((None, old - r + i + 1, hunk[0]))
- i += 1
-
- del hunk[0]
+ for hunk_n, hunk in enumerate(hunks):
+ if not len(hunk):
+ continue
+ j = 0
+ k = 0
+ while len(hunk) > 0:
+ o = ed_hunk_start.match(hunk[0])
+ del hunk[0]
+
+ if not o:
+ continue
+ old = int(o.group(1))
+ old_end = int(o.group(2)) if len(o.group(2)) else old
+ hunk_kind = o.group(3)
+ if hunk_kind == 'd':
+ k = 0
+ while old_end >= old:
+ changes.append(Change(old + k, None, hunk_n, None))
+ r += 1
+ k += 1
+ old_end -= 1
+ continue
+
+ while len(hunk) > 0:
+ e = ed_hunk_end.match(hunk[0])
+ if not e and hunk_kind == 'c':
+ k = 0
+ while old_end >= old:
+ changes.append(Change(old + k, None, hunk_n, None))
+ r += 1
+ k += 1
+ old_end -= 1
+
+ # I basically have no idea why this works
+ # for these tests.
+ changes.append(Change(
+ None,
+ old - r + i + k + j,
+ hunk_n,
+ hunk[0],
+ ))
+ i += 1
+ j += 1
+ if not e and hunk_kind == 'a':
+ changes.append(Change(
+ None,
+ old - r + i + 1,
+ hunk_n,
+ hunk[0],
+ ))
+ i += 1
+
+ del hunk[0]
if len(changes) > 0:
return changes
return None
+
def parse_rcs_ed_diff(text):
# much like forward ed, but no 'c' type
try:
@@ -795,43 +839,43 @@ def parse_rcs_ed_diff(text):
lines = text
old = 0
- new = 0
j = 0
size = 0
total_change_size = 0
changes = list()
-
hunks = split_by_regex(lines, rcs_ed_hunk_start)
- for hunk in hunks:
+ for hunk_n, hunk in enumerate(hunks):
if len(hunk):
j = 0
while len(hunk) > 0:
o = rcs_ed_hunk_start.match(hunk[0])
del hunk[0]
- if o:
- hunk_kind = o.group(1)
- old = int(o.group(2))
- size = int(o.group(3))
-
-
- if hunk_kind == 'a':
- old += total_change_size + 1
- total_change_size += size
- while size > 0 and len(hunk) > 0:
- changes.append((None, old + j, hunk[0]))
- j += 1
- size -= 1
-
- del hunk[0]
-
- elif hunk_kind == 'd':
- total_change_size -= size
- while size > 0:
- changes.append((old + j, None, None))
- j += 1
- size -= 1
+
+ if not o:
+ continue
+
+ hunk_kind = o.group(1)
+ old = int(o.group(2))
+ size = int(o.group(3))
+
+ if hunk_kind == 'a':
+ old += total_change_size + 1
+ total_change_size += size
+ while size > 0 and len(hunk) > 0:
+ changes.append(Change(None, old + j, hunk_n, hunk[0]))
+ j += 1
+ size -= 1
+
+ del hunk[0]
+
+ elif hunk_kind == 'd':
+ total_change_size -= size
+ while size > 0:
+ changes.append(Change(old + j, None, hunk_n, None))
+ j += 1
+ size -= 1
if len(changes) > 0:
return changes
diff --git a/whatthepatch/snippets.py b/whatthepatch/snippets.py
index 18f86f0..9edf99d 100644
--- a/whatthepatch/snippets.py
+++ b/whatthepatch/snippets.py
@@ -27,6 +27,7 @@ def make_dir(dir):
else:
raise e
+
def remove(path):
if os.path.exists(path):
if os.path.isdir(path):
@@ -34,6 +35,7 @@ def remove(path):
else:
os.remove(path)
+
# file line length
def file_len(fname):
with open(fname) as f:
@@ -41,6 +43,7 @@ def file_len(fname):
pass
return i + 1
+
# find all indices of a list of strings that match a regex
def findall_regex(l, r):
found = list()
@@ -52,6 +55,7 @@ def findall_regex(l, r):
return found
+
def split_by_regex(l, r):
splits = list()
indices = findall_regex(l, r)
@@ -68,6 +72,7 @@ def split_by_regex(l, r):
return splits
+
# http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
def which(program):
def is_exe(fpath):