From 6eb14ade7306137d099c31b27f204c474e564a76 Mon Sep 17 00:00:00 2001 From: KOLANICH Date: Fri, 18 Oct 2019 19:39:14 +0300 Subject: [PATCH 1/5] Fetched https://files.pythonhosted.org/packages/da/74/0815f03c82f4dc738e2bfc5f8966f682bebcc809f30c8e306e6cc7156a99/patch-1.16.zip and have taken setup.py from it. Converted the setup.py into setup.cfg. --- setup.cfg | 14 ++++++++++++++ setup.py | 4 ++++ 2 files changed, 18 insertions(+) create mode 100644 setup.cfg create mode 100644 setup.py diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..530bed1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,14 @@ +[metadata] +name = patch +version = 1.16 +author = anatoly techtonik +license = MIT +description = Patch utility to apply unified diffs +url = https://github.com/techtonik/python-patch/ +classifiers = + Classifier: Programming Language :: Python :: 2 + Classifier: Programming Language :: Python :: 3 + +[options] +py_modules = patch +setup_requires = setuptools; setuptools_scm diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d808dd6 --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 +from setuptools import setup +if __name__ == "__main__": + setup(use_scm_version = True) From 5d9e4306422d4040211f9f2bd0212385ba9f630b Mon Sep 17 00:00:00 2001 From: KOLANICH Date: Fri, 18 Oct 2019 19:59:25 +0300 Subject: [PATCH 2/5] Increased count of errors if failed to match a hunk. --- patch.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/patch.py b/patch.py index 4b82af0..e160b6d 100755 --- a/patch.py +++ b/patch.py @@ -697,8 +697,8 @@ def _normalize_filenames(self): for i,p in enumerate(self.items): if debugmode: debug(" patch type = " + p.type) - debug(" source = " + p.source) - debug(" target = " + p.target) + debug(" source = " + str(p.source, encoding="utf-8")) + debug(" target = " + str(p.target, encoding="utf-8")) if p.type in (HG, GIT): # TODO: figure out how to deal with /dev/null entries debug("stripping a/ and b/ prefixes") @@ -892,6 +892,7 @@ def apply(self, strip=0, root=None): if line.rstrip(b"\r\n") == hunkfind[hunklineno]: hunklineno+=1 else: + errors += 1 info("file %d/%d:\t %s" % (i+1, total, filename)) info(" hunk no.%d doesn't match source file at line %d" % (hunkno+1, lineno+1)) info(" expected: %s" % hunkfind[hunklineno]) From 1bf17c0c7afe06782304995a3c7848f77f23b7e6 Mon Sep 17 00:00:00 2001 From: KOLANICH Date: Fri, 18 Oct 2019 20:00:30 +0300 Subject: [PATCH 3/5] Added .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7972a2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +__pycache__ +*.pyc +*.pyo +./*.egg-info +./build +./dist +./.eggs From c41683d9632082eb6248939a1bd2b4d21eff6ce8 Mon Sep 17 00:00:00 2001 From: KOLANICH Date: Mon, 16 Dec 2019 15:59:31 +0300 Subject: [PATCH 4/5] Added allowed paths: /dev/null --- patch.py | 53 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/patch.py b/patch.py index e160b6d..5271610 100755 --- a/patch.py +++ b/patch.py @@ -132,6 +132,7 @@ def xisabs(filename): elif re.match(b'\\w:[\\\\/]', filename): # Windows return True return False + def xnormpath(path): """ Cross-platform version of os.path.normpath """ @@ -697,8 +698,8 @@ def _normalize_filenames(self): for i,p in enumerate(self.items): if debugmode: debug(" patch type = " + p.type) - debug(" source = " + str(p.source, encoding="utf-8")) - debug(" target = " + str(p.target, encoding="utf-8")) + debug(" source = " + p.source.decode(encoding="utf-8")) + debug(" target = " + p.target.decode(encoding="utf-8")) if p.type in (HG, GIT): # TODO: figure out how to deal with /dev/null entries debug("stripping a/ and b/ prefixes") @@ -730,16 +731,24 @@ def _normalize_filenames(self): while p.target.startswith(b".." + sep): p.target = p.target.partition(sep)[2] # absolute paths are not allowed - if xisabs(p.source) or xisabs(p.target): - warning("error: absolute paths are not allowed - file no.%d" % (i+1)) - self.warnings += 1 - if xisabs(p.source): - warning("stripping absolute path from source name '%s'" % p.source) - p.source = xstrip(p.source) - if xisabs(p.target): - warning("stripping absolute path from target name '%s'" % p.target) - p.target = xstrip(p.target) - + + allowedPaths = {b"/dev/null"} + if xisabs(p.source): + if p.source not in allowedPaths: + warning("error: absolute source paths are not allowed - file no.%d" % (i+1)) + self.warnings += 1 + if xisabs(p.source): + warning("stripping absolute path from source name '%s'" % p.source) + p.source = xstrip(p.source) + + if xisabs(p.target): + if p.target not in allowedPaths: + warning("error: absolute target paths are not allowed - file no.%d" % (i+1)) + self.warnings += 1 + if xisabs(p.target): + warning("stripping absolute path from source name '%s'" % p.target) + p.source = xstrip(p.target) + self.items[i].source = p.source self.items[i].target = p.target @@ -855,21 +864,26 @@ def apply(self, strip=0, root=None): old, new = p.source, p.target filename = self.findfile(old, new) + isDevNull = filename in {b"/dev/null"} if not filename: warning("source/target file does not exist:\n --- %s\n +++ %s" % (old, new)) errors += 1 continue if not isfile(filename): - warning("not a file - %s" % filename) - errors += 1 - continue + if not isDevNull: + warning("not a file - %s" % filename) + errors += 1 + continue # [ ] check absolute paths security here debug("processing %d/%d:\t %s" % (i+1, total, filename)) # validate before patching - f2fp = open(filename, 'rb') + if not isDevNull: + f2fp = open(filename, 'rb') + else: + f2fp = () hunkno = 0 hunk = p.hunks[hunkno] hunkfind = [] @@ -925,13 +939,14 @@ def apply(self, strip=0, root=None): canpatch = True break else: - if hunkno < len(p.hunks): + if not isDevNull and hunkno < len(p.hunks): warning("premature end of source file %s at hunk %d" % (filename, hunkno+1)) errors += 1 - f2fp.close() + if not isinstance(f2fp, tuple): # techtonic, I am not going to do your job instead of you. It is YOUR responsibility to use context managers instead of this shit + f2fp.close() - if validhunks < len(p.hunks): + if not isDevNull and validhunks < len(p.hunks): if self._match_file_hunks(filename, p.hunks): warning("already patched %s" % filename) else: From 9c5b9b8dd70bf16a5419b1d7e4a4a5228cd384e0 Mon Sep 17 00:00:00 2001 From: KOLANICH Date: Mon, 16 Dec 2019 16:16:44 +0300 Subject: [PATCH 5/5] Added .editorconfig. --- .editorconfig | 8 ++++ patch.py | 104 +++++++++++++++++++++++++++++++------------------- 2 files changed, 73 insertions(+), 39 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4128ae5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +end_of_line = lf diff --git a/patch.py b/patch.py index 5271610..d9c7766 100755 --- a/patch.py +++ b/patch.py @@ -812,22 +812,30 @@ def diffstat(self): def findfile(self, old, new): """ return name of file to be patched or None """ - if exists(old): - return old - elif exists(new): - return new + if exists(old) and exists(new): + return old, new else: # [w] Google Code generates broken patches with its online editor debug("broken patch from Google Code, stripping prefixes..") - if old.startswith(b'a/') and new.startswith(b'b/'): - old, new = old[2:], new[2:] + oldPrefixed = old.startswith(b'a/') + newPrefixed = new.startswith(b'b/') + oldIsDevNull = False + newIsDevNull = False + if not oldPrefixed: + if old in {b"/dev/null"}: + oldIsDevNull = True + if not newPrefixed: + if new in {b"/dev/null"}: + newIsDevNull = True + if (oldPrefixed or oldIsDevNull) and (newPrefixed or newIsDevNull): + if oldPrefixed: + old = old[2:] + if newPrefixed: + new = new[2:] debug(" %s" % old) debug(" %s" % new) - if exists(old): - return old - elif exists(new): - return new - return None + return old, new + return None, None def apply(self, strip=0, root=None): @@ -863,25 +871,25 @@ def apply(self, strip=0, root=None): else: old, new = p.source, p.target - filename = self.findfile(old, new) - isDevNull = filename in {b"/dev/null"} + resolvedOld, resolvedNew = self.findfile(old, new) + isDevNull = resolvedOld in {b"/dev/null"} - if not filename: + if not resolvedOld: warning("source/target file does not exist:\n --- %s\n +++ %s" % (old, new)) errors += 1 continue - if not isfile(filename): + if not isfile(resolvedOld): if not isDevNull: - warning("not a file - %s" % filename) + warning("not a file - %s" % resolvedOld) errors += 1 continue # [ ] check absolute paths security here - debug("processing %d/%d:\t %s" % (i+1, total, filename)) + debug("processing %d/%d:\t %s -> %s" % (i+1, total, resolvedOld, resolvedNew)) # validate before patching if not isDevNull: - f2fp = open(filename, 'rb') + f2fp = open(resolvedOld, 'rb') else: f2fp = () hunkno = 0 @@ -889,7 +897,7 @@ def apply(self, strip=0, root=None): hunkfind = [] hunkreplace = [] validhunks = 0 - canpatch = False + canpatch = isDevNull for lineno, line in enumerate(f2fp): if lineno+1 < hunk.startsrc: continue @@ -907,7 +915,7 @@ def apply(self, strip=0, root=None): hunklineno+=1 else: errors += 1 - info("file %d/%d:\t %s" % (i+1, total, filename)) + info("file %d/%d:\t %s" % (i+1, total, resolvedOld)) info(" hunk no.%d doesn't match source file at line %d" % (hunkno+1, lineno+1)) info(" expected: %s" % hunkfind[hunklineno]) info(" actual : %s" % line.rstrip(b"\r\n")) @@ -928,7 +936,7 @@ def apply(self, strip=0, root=None): # check if processed line is the last line if lineno+1 == hunk.startsrc+len(hunkfind)-1: - debug(" hunk no.%d for file %s -- is ready to be patched" % (hunkno+1, filename)) + debug(" hunk no.%d for file %s -- is ready to be patched" % (hunkno+1, resolvedOld)) hunkno+=1 validhunks+=1 if hunkno < len(p.hunks): @@ -939,36 +947,49 @@ def apply(self, strip=0, root=None): canpatch = True break else: - if not isDevNull and hunkno < len(p.hunks): - warning("premature end of source file %s at hunk %d" % (filename, hunkno+1)) - errors += 1 + if not isDevNull: + if hunkno < len(p.hunks): + warning("premature end of source file %s at hunk %d" % (resolvedOld, hunkno+1)) + errors += 1 + else: + pass if not isinstance(f2fp, tuple): # techtonic, I am not going to do your job instead of you. It is YOUR responsibility to use context managers instead of this shit f2fp.close() if not isDevNull and validhunks < len(p.hunks): - if self._match_file_hunks(filename, p.hunks): - warning("already patched %s" % filename) + if self._match_file_hunks(resolvedOld, p.hunks): + warning("already patched %s" % resolvedOld) else: - warning("source file is different - %s" % filename) + warning("source file is different - %s" % resolvedOld) errors += 1 if canpatch: - backupname = filename+b".orig" + backupname = resolvedNew+b".orig" if exists(backupname): warning("can't backup original file to %s - aborting" % backupname) else: import shutil - shutil.move(filename, backupname) - if self.write_hunks(backupname, filename, p.hunks): - info("successfully patched %d/%d:\t %s" % (i+1, total, filename)) - os.unlink(backupname) + resNewExists = exists(resolvedNew) + if resNewExists == isDevNull: + errors += 1 + warning("error: requested file creation but it already exists: " + str(resolvedNew)) + continue + if not isDevNull: + shutil.move(resolvedNew, backupname) + else: + backupname = None + + if self.write_hunks(backupname, resolvedNew, p.hunks): + info("successfully patched %d/%d:\t %s" % (i+1, total, resolvedNew)) + if backupname: + os.unlink(backupname) else: errors += 1 - warning("error patching file %s" % filename) - shutil.copy(filename, filename+".invalid") - warning("invalid version is saved to %s" % filename+".invalid") + warning("error patching file %s" % new) + shutil.copy(new, new+".invalid") + warning("invalid version is saved to %s" % new+".invalid") # todo: proper rejects - shutil.move(backupname, filename) + shutil.move(backupname, new) if root: os.chdir(prevdir) @@ -1113,7 +1134,10 @@ def get_line(): def write_hunks(self, srcname, tgtname, hunks): - src = open(srcname, "rb") + if srcname: + src = open(srcname, "rb") + else: + src = () tgt = open(tgtname, "wb") debug("processing target file %s" % tgtname) @@ -1121,9 +1145,11 @@ def write_hunks(self, srcname, tgtname, hunks): tgt.writelines(self.patch_stream(src, hunks)) tgt.close() - src.close() + if srcname: + src.close() # [ ] TODO: add test for permission copy - shutil.copymode(srcname, tgtname) + if srcname: + shutil.copymode(srcname, tgtname) return True