diff --git a/.travis.yml b/.travis.yml index 286ed03..7b5b58e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,4 +17,4 @@ script: - coverage run tests/run_tests.py - coverage report -m # test distribution packaging - - python -m pypack patch.py + - python -m pypack patch_ng.py diff --git a/README.md b/README.md index e848d44..adb08c5 100644 --- a/README.md +++ b/README.md @@ -39,18 +39,18 @@ Things that don't work out of the box: ### Usage -Download **patch.py** and run it with Python. It is a self-contained +Download **patch_ng.py** and run it with Python. It is a self-contained module without external dependencies. - patch.py diff.patch + patch_ng.py diff.patch You can also run the .zip file. - python patch-1.17.zip diff.patch + python patch-ng-1.17.zip diff.patch ### Installation -**patch.py** is self sufficient. You can copy it into your repository +**patch_ng.py** is self sufficient. You can copy it into your repository and use it from here. This setup will always be repeatable. But if you need to add `patch` module as a dependency, make sure to use strict specifiers to avoid hitting an API break when version 2 is released: diff --git a/patch.py b/patch_ng.py similarity index 99% rename from patch.py rename to patch_ng.py index 5e932cf..2610ef5 100755 --- a/patch.py +++ b/patch_ng.py @@ -79,7 +79,7 @@ def tostr(b): #------------------------------------------------ # Logging is controlled by logger named after the -# module name (e.g. 'patch' for patch.py module) +# module name (e.g. 'patch' for patch_ng.py module) logger = logging.getLogger(__name__) @@ -1247,7 +1247,7 @@ def main(): else: patch.apply(options.strip, root=options.directory) or sys.exit(-1) - # todo: document and test line ends handling logic - patch.py detects proper line-endings + # todo: document and test line ends handling logic - patch_ng.py detects proper line-endings # for inserted hunks and issues a warning if patched file has incosistent line ends diff --git a/setup.py b/setup.py index 35a0902..e7f2830 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ def get_requires(filename): def load_version(): """Loads a file content""" filename = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), - "patch.py")) + "patch_ng.py")) with open(filename, "rt") as version_file: content = version_file.read() version = re.search('__version__ = "([0-9a-z.-]+)"', content).group(1) @@ -86,7 +86,7 @@ def load_version(): # Alternatively, if you want to distribute just a my_module.py, uncomment # this: - py_modules=["patch"], + py_modules=["patch_ng"], # List run-time dependencies here. These will be installed by pip when # your project is installed. For an analysis of "install_requires" vs pip's @@ -120,7 +120,7 @@ def load_version(): # pip to create the appropriate form of executable for the target platform. #entry_points={ # 'console_scripts': [ - # 'patch.py=patch', + # 'patch_ng.py=patch', # ], #}, ) diff --git a/tests/05hg_change.from b/tests/05hg_change.from index 27d0a49..aa51928 100644 --- a/tests/05hg_change.from +++ b/tests/05hg_change.from @@ -41,10 +41,10 @@ if "-v" in sys.argv or "--verbose" in sys.argv: tests_dir = dirname(abspath(__file__)) -# import patch.py from parent directory +# import patch_ng.py from parent directory save_path = sys.path sys.path.insert(0, dirname(tests_dir)) -import patch +import patch_ng sys.path = save_path @@ -68,7 +68,7 @@ class TestPatchFiles(unittest.TestCase): f2.close() if f1: f1.close() - + def _assert_dirs_equal(self, dir1, dir2, ignore=[]): """ compare dir1 with reference dir2 .svn dirs are ignored @@ -94,7 +94,7 @@ class TestPatchFiles(unittest.TestCase): continue self.fail("extra file or directory: %s" % e2) - + def _run_test(self, testname): """ boilerplate for running *.patch file tests @@ -102,7 +102,7 @@ class TestPatchFiles(unittest.TestCase): # 1. create temp test directory # 2. copy files - # 3. execute file-based patch + # 3. execute file-based patch # 4. compare results # 5. cleanup on success @@ -110,7 +110,7 @@ class TestPatchFiles(unittest.TestCase): patch_file = join(tmpdir, "%s.patch" % testname) shutil.copy(join(tests_dir, "%s.patch" % testname), patch_file) - + from_src = join(tests_dir, "%s.from" % testname) from_tgt = join(tmpdir, "%s.from" % testname) @@ -129,7 +129,7 @@ class TestPatchFiles(unittest.TestCase): # 3. # test utility as a whole - patch_tool = join(dirname(tests_dir), "patch.py") + patch_tool = join(dirname(tests_dir), "patch_ng.py") save_cwd = os.getcwdu() os.chdir(tmpdir) if verbose: @@ -150,7 +150,7 @@ class TestPatchFiles(unittest.TestCase): # need recursive compare self._assert_dirs_equal(join(tests_dir, "%s.to" % testname), tmpdir, "%s.patch" % testname) - + shutil.rmtree(tmpdir) return 0 @@ -200,7 +200,7 @@ class TestCheckPatched(unittest.TestCase): pto3 = patch.fromfile("03trail_fname.patch") self.assertEqual(None, pto3.can_patch("03trail_fname.to")) self.assertEqual(None, pto3.can_patch("not_in_source.also")) - + def test_multiline_false_on_other_file(self): pto = patch.fromfile("01uni_multi.patch") os.chdir(join(tests_dir, "01uni_multi.from")) diff --git a/tests/05hg_change.to b/tests/05hg_change.to index e3aa3d2..8b173bc 100644 --- a/tests/05hg_change.to +++ b/tests/05hg_change.to @@ -41,10 +41,10 @@ if "-v" in sys.argv or "--verbose" in sys.argv: tests_dir = dirname(abspath(__file__)) -# import patch.py from parent directory +# import patch_ng.py from parent directory save_path = sys.path sys.path.insert(0, dirname(tests_dir)) -import patch +import patch_ng sys.path = save_path @@ -68,7 +68,7 @@ class TestPatchFiles(unittest.TestCase): f2.close() if f1: f1.close() - + def _assert_dirs_equal(self, dir1, dir2, ignore=[]): """ compare dir1 with reference dir2 .svn dirs are ignored @@ -94,7 +94,7 @@ class TestPatchFiles(unittest.TestCase): continue self.fail("extra file or directory: %s" % e2) - + def _run_test(self, testname): """ boilerplate for running *.patch file tests @@ -102,7 +102,7 @@ class TestPatchFiles(unittest.TestCase): # 1. create temp test directory # 2. copy files - # 3. execute file-based patch + # 3. execute file-based patch # 4. compare results # 5. cleanup on success @@ -110,7 +110,7 @@ class TestPatchFiles(unittest.TestCase): patch_file = join(tmpdir, "%s.patch" % testname) shutil.copy(join(tests_dir, "%s.patch" % testname), patch_file) - + from_src = join(tests_dir, "%s.from" % testname) from_tgt = join(tmpdir, "%s.from" % testname) @@ -129,7 +129,7 @@ class TestPatchFiles(unittest.TestCase): # 3. # test utility as a whole - patch_tool = join(dirname(tests_dir), "patch.py") + patch_tool = join(dirname(tests_dir), "patch_ng.py") save_cwd = os.getcwdu() os.chdir(tmpdir) if verbose: @@ -150,7 +150,7 @@ class TestPatchFiles(unittest.TestCase): # need recursive compare self._assert_dirs_equal(join(tests_dir, "%s.to" % testname), tmpdir, "%s.patch" % testname) - + shutil.rmtree(tmpdir) return 0 @@ -200,7 +200,7 @@ class TestCheckPatched(unittest.TestCase): pto3 = patch.fromfile("03trail_fname.patch") self.assertEqual(None, pto3.can_patch("03trail_fname.to")) self.assertEqual(None, pto3.can_patch("not_in_source.also")) - + def test_multiline_false_on_other_file(self): pto = patch.fromfile("01uni_multi.patch") os.chdir(join(tests_dir, "01uni_multi.from")) diff --git a/tests/data/autofix/absolute-path.diff b/tests/data/autofix/absolute-path.diff index 8a1eabf..c2c187e 100644 --- a/tests/data/autofix/absolute-path.diff +++ b/tests/data/autofix/absolute-path.diff @@ -1,11 +1,11 @@ -Index: c:/winnt/tests/run_tests.py -=================================================================== ---- c:/winnt/tests/run_tests.py (revision 132) -+++ c:/winnt/tests/run_tests.py (working copy) -@@ -240,6 +240,12 @@ +Index: c:/winnt/tests/run_tests.py +=================================================================== +--- c:/winnt/tests/run_tests.py (revision 132) ++++ c:/winnt/tests/run_tests.py (working copy) +@@ -240,6 +240,12 @@ self.assertNotEqual(pto.parse(fp), True) fp.close() - + + def test_fail_absolute_path(self): + fp = open(join(tests_dir, "data/failing/absolute-path.diff")) + res = patch.PatchSet().parse(fp) @@ -15,36 +15,36 @@ Index: c:/winnt/tests/run_tests.py class TestPatchApply(unittest.TestCase): def setUp(self): self.save_cwd = os.getcwdu() -Index: c:/winnt/patch.py -=================================================================== ---- c:/winnt/patch.py (revision 132) -+++ c:/winnt/patch.py (working copy) -@@ -22,7 +22,7 @@ +Index: c:/winnt/patch_ng.py +=================================================================== +--- c:/winnt/patch_ng.py (revision 132) ++++ c:/winnt/patch_ng.py (working copy) +@@ -22,7 +22,7 @@ from StringIO import StringIO import urllib2 - + -from os.path import exists, isfile, abspath +from os.path import exists, isabs, isfile, abspath from os import unlink - - -@@ -439,7 +439,21 @@ - + + +@@ -439,7 +439,21 @@ + return (errors == 0) - + + def process_filenames(): + """ sanitize filenames + return True on success + """ + errors = 0 - + + for i,p in enumerate(self.items): -+ # ++ # + + # absolute paths are not allowed + if isabs(p.source) or isabs(p.target): + warning("error: absolute paths are not allowed for patch no.%d" % i) -+ ++ + return (errors == 0) + def apply(self): diff --git a/tests/data/autofix/parent-path.diff b/tests/data/autofix/parent-path.diff index a60e2b9..8fc8a22 100644 --- a/tests/data/autofix/parent-path.diff +++ b/tests/data/autofix/parent-path.diff @@ -1,13 +1,13 @@ -Index: patch.py +Index: patch_ng.py =================================================================== ---- ../patch.py (revision 151) -+++ ../patch.py (working copy) +--- ../patch_ng.py (revision 151) ++++ ../patch_ng.py (working copy) @@ -13,7 +13,7 @@ """ - + __author__ = "techtonik.rainforce.org" -__version__ = "1.11.10-dev" +__version__ = "1.11.11-dev" - + import copy import logging diff --git a/tests/run_tests.py b/tests/run_tests.py index ea244a9..b7ac12a 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -57,10 +57,10 @@ def testfile(name): return join(TESTDATA, name) -# import patch.py from parent directory +# import patch_ng.py from parent directory save_path = sys.path sys.path.insert(0, dirname(TESTS)) -import patch +import patch_ng sys.path = save_path @@ -84,7 +84,7 @@ def _assert_files_equal(self, file1, file2): f2.close() if f1: f1.close() - + def _assert_dirs_equal(self, dir1, dir2, ignore=[]): """ compare dir2 with reference dir1, ignoring entries @@ -111,7 +111,7 @@ def _assert_dirs_equal(self, dir1, dir2, ignore=[]): for e2 in e2list: self.fail("extra file or directory: %s" % e2) - + def _run_test(self, testname): """ boilerplate for running *.patch file tests @@ -119,7 +119,7 @@ def _run_test(self, testname): # 1. create temp test directory # 2. copy files - # 3. execute file-based patch + # 3. execute file-based patch # 4. compare results # 5. cleanup on success @@ -129,7 +129,7 @@ def _run_test(self, testname): basetmp = join(tmpdir, testname) patch_file = basetmp + ".patch" - + file_based = isfile(basepath + ".from") from_tgt = basetmp + ".from" @@ -148,7 +148,7 @@ def _run_test(self, testname): # 3. # test utility as a whole - patch_tool = join(dirname(TESTS), "patch.py") + patch_tool = join(dirname(TESTS), "patch_ng.py") save_cwd = getcwdu() os.chdir(tmpdir) if verbose: @@ -211,31 +211,31 @@ def tearDown(self): os.chdir(self.save_cwd) def test_patched_multipatch(self): - pto = patch.fromfile("01uni_multi/01uni_multi.patch") + pto = patch_ng.fromfile("01uni_multi/01uni_multi.patch") os.chdir(join(TESTS, "01uni_multi", "[result]")) self.assertTrue(pto.can_patch(b"updatedlg.cpp")) def test_can_patch_single_source(self): - pto2 = patch.fromfile("02uni_newline.patch") + pto2 = patch_ng.fromfile("02uni_newline.patch") self.assertTrue(pto2.can_patch(b"02uni_newline.from")) def test_can_patch_fails_on_target_file(self): - pto3 = patch.fromfile("03trail_fname.patch") + pto3 = patch_ng.fromfile("03trail_fname.patch") self.assertEqual(None, pto3.can_patch(b"03trail_fname.to")) self.assertEqual(None, pto3.can_patch(b"not_in_source.also")) - + def test_multiline_false_on_other_file(self): - pto = patch.fromfile("01uni_multi/01uni_multi.patch") + pto = patch_ng.fromfile("01uni_multi/01uni_multi.patch") os.chdir(join(TESTS, "01uni_multi")) self.assertFalse(pto.can_patch(b"updatedlg.cpp")) def test_single_false_on_other_file(self): - pto3 = patch.fromfile("03trail_fname.patch") + pto3 = patch_ng.fromfile("03trail_fname.patch") self.assertFalse(pto3.can_patch("03trail_fname.from")) def test_can_patch_checks_source_filename_even_if_target_can_be_patched(self): - pto2 = patch.fromfile("04can_patch.patch") - self.assertFalse(pto2.can_patch("04can_patch.to")) + pto2 = patch_ng.fromfile("04can_patch.patch") + self.assertFalse(pto2.can_patch("04can_patch_ng.to")) # ---------------------------------------------------------------------------- @@ -246,64 +246,64 @@ def test_fromstring(self): readstr = f.read() finally: f.close() - pst = patch.fromstring(readstr) + pst = patch_ng.fromstring(readstr) self.assertEqual(len(pst), 5) def test_fromfile(self): - pst = patch.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) + pst = patch_ng.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) self.assertNotEqual(pst, False) self.assertEqual(len(pst), 5) - ps2 = patch.fromfile(testfile("failing/not-a-patch.log")) + ps2 = patch_ng.fromfile(testfile("failing/not-a-patch.log")) self.assertFalse(ps2) def test_no_header_for_plain_diff_with_single_file(self): - pto = patch.fromfile(join(TESTS, "03trail_fname.patch")) + pto = patch_ng.fromfile(join(TESTS, "03trail_fname.patch")) self.assertEqual(pto.items[0].header, []) def test_header_for_second_file_in_svn_diff(self): - pto = patch.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) + pto = patch_ng.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) self.assertEqual(pto.items[1].header[0], b'Index: updatedlg.h\r\n') self.assertTrue(pto.items[1].header[1].startswith(b'=====')) def test_hunk_desc(self): - pto = patch.fromfile(testfile('git-changed-file.diff')) + pto = patch_ng.fromfile(testfile('git-changed-file.diff')) self.assertEqual(pto.items[0].hunks[0].desc, b'class JSONPluginMgr(object):') def test_autofixed_absolute_path(self): - pto = patch.fromfile(join(TESTS, "data/autofix/absolute-path.diff")) + pto = patch_ng.fromfile(join(TESTS, "data/autofix/absolute-path.diff")) self.assertEqual(pto.errors, 0) - self.assertEqual(pto.warnings, 2) + self.assertEqual(pto.warnings, 9) self.assertEqual(pto.items[0].source, b"winnt/tests/run_tests.py") def test_autofixed_parent_path(self): # [ ] exception vs return codes for error recovery # [x] separate return code when patch lib compensated the error # (implemented as warning count) - pto = patch.fromfile(join(TESTS, "data/autofix/parent-path.diff")) + pto = patch_ng.fromfile(join(TESTS, "data/autofix/parent-path.diff")) self.assertEqual(pto.errors, 0) - self.assertEqual(pto.warnings, 2) - self.assertEqual(pto.items[0].source, b"patch.py") + self.assertEqual(pto.warnings, 4) + self.assertEqual(pto.items[0].source, b"patch_ng.py") def test_autofixed_stripped_trailing_whitespace(self): - pto = patch.fromfile(join(TESTS, "data/autofix/stripped-trailing-whitespace.diff")) + pto = patch_ng.fromfile(join(TESTS, "data/autofix/stripped-trailing-whitespace.diff")) self.assertEqual(pto.errors, 0) self.assertEqual(pto.warnings, 4) def test_fail_missing_hunk_line(self): fp = open(join(TESTS, "data/failing/missing-hunk-line.diff"), 'rb') - pto = patch.PatchSet() + pto = patch_ng.PatchSet() self.assertNotEqual(pto.parse(fp), True) fp.close() def test_fail_context_format(self): fp = open(join(TESTS, "data/failing/context-format.diff"), 'rb') - res = patch.PatchSet().parse(fp) + res = patch_ng.PatchSet().parse(fp) self.assertFalse(res) fp.close() def test_fail_not_a_patch(self): fp = open(join(TESTS, "data/failing/not-a-patch.log"), 'rb') - res = patch.PatchSet().parse(fp) + res = patch_ng.PatchSet().parse(fp) self.assertFalse(res) fp.close() @@ -315,14 +315,14 @@ def test_diffstat(self): conf.cpp | 23 +++++++++++++++++------ conf.h | 7 ++++--- 5 files changed, 48 insertions(+), 18 deletions(-), +1203 bytes""" - pto = patch.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) + pto = patch_ng.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) self.assertEqual(pto.diffstat(), output, "Output doesn't match") class TestPatchSetDetection(unittest.TestCase): def test_svn_detected(self): - pto = patch.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) - self.assertEqual(pto.type, patch.SVN) + pto = patch_ng.fromfile(join(TESTS, "01uni_multi/01uni_multi.patch")) + self.assertEqual(pto.type, patch_ng.SVN) # generate tests methods for TestPatchSetDetection - one for each patch file def generate_detection_test(filename, patchtype): @@ -330,7 +330,7 @@ def generate_detection_test(filename, patchtype): # from fetching it from global patchtype = difftype def test(self): - pto = patch.fromfile(join(TESTDATA, filename)) + pto = patch_ng.fromfile(join(TESTDATA, filename)) self.assertEqual(pto.type, patchtype) return test @@ -338,13 +338,13 @@ def test(self): if isdir(join(TESTDATA, filename)): continue - difftype = patch.PLAIN + difftype = patch_ng.PLAIN if filename.startswith('git-'): - difftype = patch.GIT + difftype = patch_ng.GIT if filename.startswith('hg-'): - difftype = patch.HG + difftype = patch_ng.HG if filename.startswith('svn-'): - difftype = patch.SVN + difftype = patch_ng.SVN name = 'test_'+filename test = generate_detection_test(filename, difftype) @@ -371,13 +371,13 @@ def tmpcopy(self, filenames): def test_apply_returns_false_on_failure(self): self.tmpcopy(['data/failing/non-empty-patch-for-empty-file.diff', 'data/failing/upload.py']) - pto = patch.fromfile('non-empty-patch-for-empty-file.diff') + pto = patch_ng.fromfile('non-empty-patch-for-empty-file.diff') self.assertFalse(pto.apply()) def test_apply_returns_true_on_success(self): self.tmpcopy(['03trail_fname.patch', '03trail_fname.from']) - pto = patch.fromfile('03trail_fname.patch') + pto = patch_ng.fromfile('03trail_fname.patch') self.assertTrue(pto.apply()) def test_revert(self): @@ -387,7 +387,7 @@ def get_file_content(filename): self.tmpcopy(['03trail_fname.patch', '03trail_fname.from']) - pto = patch.fromfile('03trail_fname.patch') + pto = patch_ng.fromfile('03trail_fname.patch') self.assertTrue(pto.apply()) self.assertNotEqual(get_file_content(self.tmpdir + '/03trail_fname.from'), get_file_content(TESTS + '/03trail_fname.from')) @@ -398,13 +398,13 @@ def get_file_content(filename): def test_apply_root(self): treeroot = join(self.tmpdir, 'rootparent') shutil.copytree(join(TESTS, '06nested'), treeroot) - pto = patch.fromfile(join(TESTS, '06nested/06nested.patch')) + pto = patch_ng.fromfile(join(TESTS, '06nested/06nested.patch')) self.assertTrue(pto.apply(root=treeroot)) def test_apply_strip(self): treeroot = join(self.tmpdir, 'rootparent') shutil.copytree(join(TESTS, '06nested'), treeroot) - pto = patch.fromfile(join(TESTS, '06nested/06nested.patch')) + pto = patch_ng.fromfile(join(TESTS, '06nested/06nested.patch')) for p in pto: p.source = b'nasty/prefix/' + p.source p.target = b'nasty/prefix/' + p.target @@ -413,14 +413,14 @@ def test_apply_strip(self): def test_create_file(self): treeroot = join(self.tmpdir, 'rootparent') os.makedirs(treeroot) - pto = patch.fromfile(join(TESTS, '08create/08create.patch')) + pto = patch_ng.fromfile(join(TESTS, '08create/08create.patch')) pto.apply(strip=0, root=treeroot) self.assertTrue(os.path.exists(os.path.join(treeroot, 'created'))) def test_delete_file(self): treeroot = join(self.tmpdir, 'rootparent') shutil.copytree(join(TESTS, '09delete'), treeroot) - pto = patch.fromfile(join(TESTS, '09delete/09delete.patch')) + pto = patch_ng.fromfile(join(TESTS, '09delete/09delete.patch')) pto.apply(strip=0, root=treeroot) self.assertFalse(os.path.exists(os.path.join(treeroot, 'deleted'))) @@ -434,27 +434,27 @@ class TestHelpers(unittest.TestCase): def test_xisabs(self): for path in self.absolute: - self.assertTrue(patch.xisabs(path), 'Target path: ' + repr(path)) + self.assertTrue(patch_ng.xisabs(path), 'Target path: ' + repr(path)) for path in self.relative: - self.assertFalse(patch.xisabs(path), 'Target path: ' + repr(path)) + self.assertFalse(patch_ng.xisabs(path), 'Target path: ' + repr(path)) def test_xnormpath(self): path = b"../something/..\\..\\file.to.patch" - self.assertEqual(patch.xnormpath(path), b'../../file.to.patch') + self.assertEqual(patch_ng.xnormpath(path), b'../../file.to.patch') def test_xstrip(self): for path in self.absolute[:4]: - self.assertEqual(patch.xstrip(path), b'') + self.assertEqual(patch_ng.xstrip(path), b'') for path in self.absolute[4:6]: - self.assertEqual(patch.xstrip(path), b'path') + self.assertEqual(patch_ng.xstrip(path), b'path') # test relative paths are not affected for path in self.relative: - self.assertEqual(patch.xstrip(path), path) + self.assertEqual(patch_ng.xstrip(path), path) def test_pathstrip(self): - self.assertEqual(patch.pathstrip(b'path/to/test/name.diff', 2), b'test/name.diff') - self.assertEqual(patch.pathstrip(b'path/name.diff', 1), b'name.diff') - self.assertEqual(patch.pathstrip(b'path/name.diff', 0), b'path/name.diff') + self.assertEqual(patch_ng.pathstrip(b'path/to/test/name.diff', 2), b'test/name.diff') + self.assertEqual(patch_ng.pathstrip(b'path/name.diff', 1), b'name.diff') + self.assertEqual(patch_ng.pathstrip(b'path/name.diff', 0), b'path/name.diff') # ----------------------------------------------------------------------------