From d5f154f0c35d217fe76ebbeab2fb27fdf1ae909f Mon Sep 17 00:00:00 2001 From: xmo-odoo Date: Fri, 9 Aug 2024 09:55:57 +0200 Subject: [PATCH 1/9] Remove Python 2 compatibility shims Python 2 compatibility was removed in #31 so these should not be useful / necessary anymore. --- patch_ng.py | 100 ++++++++++++---------------------------------------- 1 file changed, 23 insertions(+), 77 deletions(-) diff --git a/patch_ng.py b/patch_ng.py index e1114fb..4753c1d 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -28,56 +28,24 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ -from __future__ import print_function - __author__ = "Conan.io " __version__ = "1.18.0" __license__ = "MIT" __url__ = "https://github.com/conan-io/python-patch" +import codecs import copy +import io import logging -import re -import tempfile -import codecs - -# cStringIO doesn't support unicode in 2.5 -try: - from StringIO import StringIO -except ImportError: - from io import BytesIO as StringIO # python 3 -try: - import urllib2 as urllib_request -except ImportError: - import urllib.request as urllib_request - -from os.path import exists, isfile, abspath import os import posixpath +import re import shutil -import sys import stat - - -PY3K = sys.version_info >= (3, 0) - -# PEP 3114 -if not PY3K: - compat_next = lambda gen: gen.next() -else: - compat_next = lambda gen: gen.__next__() - -def tostr(b): - """ Python 3 bytes encoder. Used to print filename in - diffstat output. Assumes that filenames are in utf-8. - """ - if not PY3K: - return b - - # [ ] figure out how to print non-utf-8 filenames without - # information loss - return b.decode('utf-8') - +import sys +import tempfile +import urllib.request +from os.path import exists, isfile, abspath #------------------------------------------------ # Logging is controlled by logger named after the @@ -90,22 +58,10 @@ def tostr(b): warning = logger.warning error = logger.error -class NullHandler(logging.Handler): - """ Copied from Python 2.7 to avoid getting - `No handlers could be found for logger "patch"` - http://bugs.python.org/issue16539 - """ - def handle(self, record): - pass - def emit(self, record): - pass - def createLock(self): - self.lock = None - streamhandler = logging.StreamHandler() # initialize logger itself -logger.addHandler(NullHandler()) +logger.addHandler(logging.NullHandler()) debugmode = False @@ -194,10 +150,9 @@ def fromfile(filename): """ patchset = PatchSet() debug("reading %s" % filename) - fp = open(filename, "rb") - res = patchset.parse(fp) - fp.close() - if res == True: + with open(filename, "rb") as fp: + res = patchset.parse(fp) + if res is True: return patchset return False @@ -206,7 +161,7 @@ def fromstring(s): """ Parse text string and return PatchSet() object (or False if parsing fails) """ - ps = PatchSet( StringIO(s) ) + ps = PatchSet( io.BytesIO(s) ) if ps.errors == 0: return ps return False @@ -217,7 +172,7 @@ def fromurl(url): if an error occured. Note that this also can throw urlopen() exceptions. """ - ps = PatchSet( urllib_request.urlopen(url) ) + ps = PatchSet( urllib.request.urlopen(url) ) if ps.errors == 0: return ps return False @@ -262,11 +217,8 @@ def decode_text(text): def to_file_bytes(content): - if PY3K: - if not isinstance(content, bytes): - content = bytes(content, "utf-8") - elif isinstance(content, unicode): - content = content.encode("utf-8") + if not isinstance(content, bytes): + content = bytes(content, "utf-8") return content @@ -328,8 +280,7 @@ def __init__(self): self.type = None def __iter__(self): - for h in self.hunks: - yield h + return iter(self.hunks) class PatchSet(object): @@ -359,8 +310,7 @@ def __len__(self): return len(self.items) def __iter__(self): - for i in self.items: - yield i + return iter(self.items) def parse(self, stream): """ parse unified diff @@ -394,7 +344,7 @@ def next(self): return False try: - self._lineno, self._line = compat_next(super(wrapumerate, self)) + self._lineno, self._line = next(super(wrapumerate, self)) except StopIteration: self._exhausted = True self._line = False @@ -902,7 +852,7 @@ def diffstat(self): #print(iratio, dratio, iwidth, dwidth, histwidth) hist = "+"*int(iwidth) + "-"*int(dwidth) # -- /calculating +- histogram -- - output += (format % (tostr(names[i]), str(insert[i] + delete[i]), hist)) + output += (format % (names[i].decode(), str(insert[i] + delete[i]), hist)) output += (" %d files changed, %d insertions(+), %d deletions(-), %+d bytes" % (len(names), sum(insert), sum(delete), delta)) @@ -1270,15 +1220,11 @@ def get_line(): def write_hunks(self, srcname, tgtname, hunks): - src = open(srcname, "rb") - tgt = open(tgtname, "wb") - - debug("processing target file %s" % tgtname) - - tgt.writelines(self.patch_stream(src, hunks)) + with open(srcname, "rb") as src, open(tgtname, "wb") as tgt: + debug("processing target file %s" % tgtname) + + tgt.writelines(self.patch_stream(src, hunks)) - tgt.close() - src.close() # [ ] TODO: add test for permission copy shutil.copymode(srcname, tgtname) return True From 810fa438701a9a6cc8ea6f5680c1c4b4726000ba Mon Sep 17 00:00:00 2001 From: xmo-odoo Date: Fri, 9 Aug 2024 09:58:55 +0200 Subject: [PATCH 2/9] Remove dead `sys` import The CLI mode uses `sys` internally but has its own import. --- patch_ng.py | 1 - 1 file changed, 1 deletion(-) diff --git a/patch_ng.py b/patch_ng.py index 4753c1d..c964f92 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -42,7 +42,6 @@ import re import shutil import stat -import sys import tempfile import urllib.request from os.path import exists, isfile, abspath From af698b8935f3e4b2be1f0c84db5c7e5acc2d8eff Mon Sep 17 00:00:00 2001 From: xmo-odoo Date: Fri, 9 Aug 2024 10:02:31 +0200 Subject: [PATCH 3/9] Remove `to_file_bytes` It does almost nothing at this point and is only used by one caller, so can be inlined. --- patch_ng.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/patch_ng.py b/patch_ng.py index c964f92..c8bcefa 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -215,12 +215,6 @@ def decode_text(text): return text.decode("utf-8", "ignore") # Ignore not compatible characters -def to_file_bytes(content): - if not isinstance(content, bytes): - content = bytes(content, "utf-8") - return content - - def load(path, binary=False): """ Loads a file content """ with open(path, 'rb') as handle: @@ -241,7 +235,8 @@ def save(path, content, only_if_modified=False): except Exception: pass - new_content = to_file_bytes(content) + if isinstance(new_content, str): + new_content = new_content.encode() if only_if_modified and os.path.exists(path): old_content = load(path, binary=True) From cf9265fce6e15fc0267a340ec48727bb649d1a45 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 1 Oct 2025 08:14:16 +0200 Subject: [PATCH 4/9] Minor fixes for Python 3 compat Signed-off-by: Uilian Ries --- example/example.py | 2 +- patch_ng.py | 13 +++++++------ tests/06nested/tests/app/EVENT_LOOP.py | 6 +++--- tests/run_tests.py | 11 +++-------- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/example/example.py b/example/example.py index b9db850..815aecc 100644 --- a/example/example.py +++ b/example/example.py @@ -8,7 +8,7 @@ def __init__(self): def emit(self, record): logstr = self.format(record) - print logstr + print(logstr) patchlog = logging.getLogger("patch") patchlog.handlers = [] diff --git a/patch_ng.py b/patch_ng.py index c8bcefa..aefb0ae 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -150,9 +150,9 @@ def fromfile(filename): patchset = PatchSet() debug("reading %s" % filename) with open(filename, "rb") as fp: - res = patchset.parse(fp) - if res is True: - return patchset + res = patchset.parse(fp) + if res == True: + return patchset return False @@ -235,8 +235,9 @@ def save(path, content, only_if_modified=False): except Exception: pass - if isinstance(new_content, str): - new_content = new_content.encode() + new_content = content + if not isinstance(content, bytes): + new_content = bytes(content, "utf-8") if only_if_modified and os.path.exists(path): old_content = load(path, binary=True) @@ -846,7 +847,7 @@ def diffstat(self): #print(iratio, dratio, iwidth, dwidth, histwidth) hist = "+"*int(iwidth) + "-"*int(dwidth) # -- /calculating +- histogram -- - output += (format % (names[i].decode(), str(insert[i] + delete[i]), hist)) + output += (format % (names[i].decode('utf-8'), str(insert[i] + delete[i]), hist)) output += (" %d files changed, %d insertions(+), %d deletions(-), %+d bytes" % (len(names), sum(insert), sum(delete), delta)) diff --git a/tests/06nested/tests/app/EVENT_LOOP.py b/tests/06nested/tests/app/EVENT_LOOP.py index 782de99..28c7e9e 100644 --- a/tests/06nested/tests/app/EVENT_LOOP.py +++ b/tests/06nested/tests/app/EVENT_LOOP.py @@ -21,8 +21,8 @@ class EVENT_LOOP(unittest.TestCase): def t_scheduled(self, interval, iterations, sleep_time=0): - print 'Test interval=%s, iterations=%s, sleep=%s' % (interval, - iterations, sleep_time) + print('Test interval=%s, iterations=%s, sleep=%s' % (interval, + iterations, sleep_time)) warmup_iterations = iterations self.last_t = 0. @@ -76,6 +76,6 @@ def test_d01_50(self): if __name__ == '__main__': if pyglet.version != '1.2dev': - print 'Wrong version of pyglet imported; please check your PYTHONPATH' + print('Wrong version of pyglet imported; please check your PYTHONPATH') else: unittest.main() diff --git a/tests/run_tests.py b/tests/run_tests.py index 6c16ef2..a14e068 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -36,15 +36,10 @@ import re import shutil import unittest -import copy import stat from os import listdir, chmod from os.path import abspath, dirname, exists, join, isdir, isfile from tempfile import mkdtemp -try: - getcwdu = os.getcwdu -except AttributeError: - getcwdu = os.getcwd # python 3, where getcwd always returns a unicode object verbose = False if "-v" in sys.argv or "--verbose" in sys.argv: @@ -150,7 +145,7 @@ def _run_test(self, testname): # 3. # test utility as a whole patch_tool = join(dirname(TESTS), "patch_ng.py") - save_cwd = getcwdu() + save_cwd = os.getcwd() os.chdir(tmpdir) extra = "-f" if "10fuzzy" in testname else "" if verbose: @@ -204,7 +199,7 @@ def create_closure(): class TestCheckPatched(unittest.TestCase): def setUp(self): - self.save_cwd = getcwdu() + self.save_cwd = os.getcwd() os.chdir(TESTS) def tearDown(self): @@ -355,7 +350,7 @@ def test(self): class TestPatchApply(unittest.TestCase): def setUp(self): - self.save_cwd = getcwdu() + self.save_cwd = os.getcwd() self.tmpdir = mkdtemp(prefix=self.__class__.__name__) os.chdir(self.tmpdir) From 6276cf81d09615b62e805ad35d737f6e55d2819f Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 1 Oct 2025 08:31:31 +0200 Subject: [PATCH 5/9] Fix nested next method Signed-off-by: Uilian Ries --- patch_ng.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patch_ng.py b/patch_ng.py index aefb0ae..ef2af6c 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -339,7 +339,7 @@ def next(self): return False try: - self._lineno, self._line = next(super(wrapumerate, self)) + self._lineno, self._line = super(wrapumerate, self).__next__() except StopIteration: self._exhausted = True self._line = False From 87f422023b3e1932e22c6b6cc591061b530938d1 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 1 Oct 2025 08:54:42 +0200 Subject: [PATCH 6/9] Validate applying 30MB patch file Signed-off-by: Uilian Ries --- tests/run_tests.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/run_tests.py b/tests/run_tests.py index a14e068..cc1814b 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -452,6 +452,41 @@ def test_unlink_backup_windows(self): self.assertTrue(os.stat(some_file).st_mode, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) +class TestHugePatchFile(unittest.TestCase): + + def setUp(self): + self.save_cwd = os.getcwd() + self.tmpdir = mkdtemp(prefix=self.__class__.__name__) + os.chdir(self.tmpdir) + self.huge_patchfile = self._create_huge_patchfile(self.tmpdir) + + def _create_huge_patchfile(self, tmpdir): + """ Create a patch file with ~30MB of data + """ + hugefile = join(tmpdir, 'hugefile') + with open(hugefile, 'wb') as f: + for i in range(2500000): + f.write(b'Line %d\n' % i) + huge_patchfile = join(tmpdir, 'huge.patch') + with open(huge_patchfile, 'wb') as f: + f.write(b'--- a/hugefile\n') + f.write(b'+++ b/hugefile\n') + f.write(b'@@ -1,2500000 +1,2500000 @@\n') + for i in range(2500000): + f.write(b' Line %d\n' % i) + return huge_patchfile + + def tearDown(self): + os.chdir(self.save_cwd) + remove_tree_force(self.tmpdir) + + def test_apply_huge_patch(self): + """ Test that a huge patch file can be applied without issues + """ + pto = patch_ng.fromfile(self.huge_patchfile) + self.assertTrue(pto.apply(root=self.tmpdir)) + + class TestHelpers(unittest.TestCase): # unittest setting longMessage = True From 8459065db4b06cf3e6624cd79b49f15c41eda53b Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 1 Oct 2025 09:13:27 +0200 Subject: [PATCH 7/9] Update CI to fix hanging Linux build Signed-off-by: Uilian Ries --- .github/workflows/workflow.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 7204f9e..5a3c93c 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -21,15 +21,15 @@ on: jobs: linux-validate: name: Validate on Linux - Python ${{ matrix.python }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: python: [ '3.6', '3.8', '3.12' ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} architecture: x64 @@ -44,10 +44,10 @@ jobs: matrix: python: [ '3.6', '3.8', '3.12' ] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python }} architecture: x64 From 0494fc15b4f5ee6407882b43e22f933e5018ad37 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 1 Oct 2025 09:16:39 +0200 Subject: [PATCH 8/9] Use specific patch version for python due python-setup rule Signed-off-by: Uilian Ries --- .github/workflows/workflow.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 5a3c93c..2d2ba41 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -24,7 +24,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [ '3.6', '3.8', '3.12' ] + python: [ '3.6.15', '3.8.18', '3.12.11' ] steps: - uses: actions/checkout@v5 @@ -42,7 +42,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python: [ '3.6', '3.8', '3.12' ] + python: [ '3.6.15', '3.8.18', '3.12.11' ] steps: - uses: actions/checkout@v5 From 45daaa9b3eef6adbcd162491976e3f9e5d42e013 Mon Sep 17 00:00:00 2001 From: Uilian Ries Date: Wed, 1 Oct 2025 09:21:13 +0200 Subject: [PATCH 9/9] Use ubuntu 22.04 to have python 3.7 available Signed-off-by: Uilian Ries --- .github/workflows/workflow.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 2d2ba41..b5cac3a 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -21,10 +21,10 @@ on: jobs: linux-validate: name: Validate on Linux - Python ${{ matrix.python }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: - python: [ '3.6.15', '3.8.18', '3.12.11' ] + python: [ '3.7', '3.8', '3.12' ] steps: - uses: actions/checkout@v5 @@ -42,7 +42,7 @@ jobs: runs-on: windows-latest strategy: matrix: - python: [ '3.6.15', '3.8.18', '3.12.11' ] + python: [ '3.7', '3.8', '3.12' ] steps: - uses: actions/checkout@v5