diff --git a/patch_ng.py b/patch_ng.py index 7e9444b..0ccbc82 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -56,6 +56,7 @@ import posixpath import shutil import sys +import stat PY3K = sys.version_info >= (3, 0) @@ -178,6 +179,12 @@ def xstrip(filename): filename = re.sub(b'^[\\\\/]+', b'', filename) return filename + +def safe_unlink(filepath): + os.chmod(filepath, stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) + os.unlink(filepath) + + #----------------------------------------------- # Main API functions @@ -976,7 +983,7 @@ def apply(self, strip=0, root=None, fuzz=False): save(target, new_file) elif "dev/null" in target: source = self.strip_path(source, root, strip) - os.unlink(source) + safe_unlink(source) else: items.append(item) self.items = items @@ -1106,12 +1113,12 @@ def apply(self, strip=0, root=None, fuzz=False): shutil.move(filenamen, backupname) if self.write_hunks(backupname if filenameo == filenamen else filenameo, filenamen, p.hunks): info("successfully patched %d/%d:\t %s" % (i+1, total, filenamen)) - os.unlink(backupname) + safe_unlink(backupname) if new == b'/dev/null': # check that filename is of size 0 and delete it. if os.path.getsize(filenamen) > 0: warning("expected patched file to be empty as it's marked as deletion:\t %s" % filenamen) - os.unlink(filenamen) + safe_unlink(filenamen) else: errors += 1 warning("error patching file %s" % filenamen) diff --git a/tests/11permission/11permission.patch b/tests/11permission/11permission.patch new file mode 100644 index 0000000..8f959d2 --- /dev/null +++ b/tests/11permission/11permission.patch @@ -0,0 +1,10 @@ +--- some_file ++++ some_file +@@ -2,6 +2,7 @@ + line 2 + line 3 + line 4 ++line 5 + line 6 + line 7 + line 8 diff --git a/tests/11permission/[result]/some_file b/tests/11permission/[result]/some_file new file mode 100644 index 0000000..fa2da6e --- /dev/null +++ b/tests/11permission/[result]/some_file @@ -0,0 +1,10 @@ +line 1 +line 2 +line 3 +line 4 +line 5 +line 6 +line 7 +line 8 +line 9 +line 10 diff --git a/tests/11permission/some_file b/tests/11permission/some_file new file mode 100644 index 0000000..c374ebd --- /dev/null +++ b/tests/11permission/some_file @@ -0,0 +1,9 @@ +line 1 +line 2 +line 3 +line 4 +line 6 +line 7 +line 8 +line 9 +line 10 diff --git a/tests/run_tests.py b/tests/run_tests.py index 3079381..6c16ef2 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -37,7 +37,8 @@ import shutil import unittest import copy -from os import listdir +import stat +from os import listdir, chmod from os.path import abspath, dirname, exists, join, isdir, isfile from tempfile import mkdtemp try: @@ -171,9 +172,7 @@ def _run_test(self, testname): self._assert_dirs_equal(join(basepath, "[result]"), tmpdir, ignore=["%s.patch" % testname, ".svn", ".gitkeep", "[result]"]) - - - shutil.rmtree(tmpdir) + remove_tree_force(tmpdir) return 0 @@ -362,7 +361,7 @@ def setUp(self): def tearDown(self): os.chdir(self.save_cwd) - shutil.rmtree(self.tmpdir) + remove_tree_force(self.tmpdir) def tmpcopy(self, filenames): """copy file(s) from test_dir to self.tmpdir""" @@ -446,6 +445,17 @@ def test_fuzzy_patch_after(self): self.assertTrue(pto.apply(root=treeroot, fuzz=True)) self.assertFalse(pto.apply(root=treeroot, fuzz=False)) + def test_unlink_backup_windows(self): + """ Apply patch to a read-only file and don't change its filemode + """ + treeroot = join(self.tmpdir, 'rootparent') + shutil.copytree(join(TESTS, '11permission'), treeroot) + pto = patch_ng.fromfile(join(TESTS, '11permission', '11permission.patch')) + some_file = join(treeroot, 'some_file') + chmod(some_file, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) + self.assertTrue(pto.apply(root=treeroot)) + self.assertTrue(os.stat(some_file).st_mode, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) + class TestHelpers(unittest.TestCase): # unittest setting @@ -478,6 +488,12 @@ def test_pathstrip(self): 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') +def remove_tree_force(folder): + for root, _, files in os.walk(folder): + for it in files: + chmod(os.path.join(root, it), stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) + shutil.rmtree(folder, ignore_errors=True) + # ---------------------------------------------------------------------------- if __name__ == '__main__':