From 65887e01a1747ea61559ce15340789a508f73c36 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 28 Oct 2024 09:16:48 +0100 Subject: [PATCH 01/15] Support -i option like GNU patch --- patch_ng.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/patch_ng.py b/patch_ng.py index e1114fb..f3354b0 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -1303,7 +1303,8 @@ def main(): opt = OptionParser(usage="1. %prog [options] unified.diff\n" " 2. %prog [options] http://host/patch\n" - " 3. %prog [options] -- < unified.diff", + " 3. %prog [options] -- < unified.diff" + " 4. %prog [options] -i unified.diff", version="python-patch %s" % __version__) opt.add_option("-q", "--quiet", action="store_const", dest="verbosity", const=0, help="print only warnings and errors", default=1) @@ -1319,13 +1320,15 @@ def main(): opt.add_option("--revert", action="store_true", help="apply patch in reverse order (unpatch)") opt.add_option("-f", "--fuzz", action="store_true", dest="fuzz", help="Accept fuuzzy patches") + opt.add_option("-i", "--input", metavar='PATCHFILE', + help="Read patch from PATCHFILE instead of stdin.") (options, args) = opt.parse_args() - if not args and sys.argv[-1:] != ['--']: + if not args and not options.input and sys.argv[-1:] != ['--']: opt.print_version() opt.print_help() sys.exit() - readstdin = (sys.argv[-1:] == ['--'] and not args) + readstdin = (sys.argv[-1:] == ['--'] and not args and not options.input) verbosity_levels = {0:logging.WARNING, 1:logging.INFO, 2:logging.DEBUG} loglevel = verbosity_levels[options.verbosity] @@ -1339,7 +1342,7 @@ def main(): if readstdin: patch = PatchSet(sys.stdin) else: - patchfile = args[0] + patchfile = options.input if options.input else args[0] urltest = patchfile.split(':')[0] if (':' in patchfile and urltest.isalpha() and len(urltest) > 1): # one char before : is a windows drive letter From d3b7b33ea8ab4e62a404f132df90ea090cd6650f Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 28 Oct 2024 10:10:17 +0100 Subject: [PATCH 02/15] Collect coverage also for subprocesses --- pyproject.toml | 2 ++ tests/recoverage.bat | 1 + tests/run_tests.py | 23 ++++++++++++++++++----- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9787c3b..dcfeac9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,5 @@ [build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" +[tool.coverage.run] +parallel = true diff --git a/tests/recoverage.bat b/tests/recoverage.bat index 2792a93..91f1af5 100644 --- a/tests/recoverage.bat +++ b/tests/recoverage.bat @@ -1,4 +1,5 @@ cd .. python -m coverage run tests/run_tests.py +python -m coverage combine python -m coverage html -d tests/coverage python -m coverage report -m diff --git a/tests/run_tests.py b/tests/run_tests.py index 6c16ef2..81bd524 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -24,10 +24,11 @@ http://pypi.python.org/pypi/coverage/ and run this file with: coverage run run_tests.py + coverage combine coverage html -d coverage On Windows it may be more convenient instead of `coverage` call -`python -m coverage.__main__` +`python -m coverage` """ from __future__ import print_function @@ -126,6 +127,16 @@ def _run_test(self, testname): tmpdir = mkdtemp(prefix="%s."%testname) + # Ensure we collect coverage for subprocesses into the parent + if 'coverage' in sys.modules.keys(): + with open(join(tmpdir, ".coveragerc"), "w") as f: + f.write("[run]\n") + f.write("parallel = true\n") + f.write("data_file = " + join(dirname(TESTS), ".coverage") + "\n") + exe = [sys.executable, "-m", "coverage", "run"] + else: + exe = [sys.executable] + basepath = join(TESTS, testname) basetmp = join(tmpdir, testname) @@ -154,10 +165,10 @@ def _run_test(self, testname): os.chdir(tmpdir) extra = "-f" if "10fuzzy" in testname else "" if verbose: - cmd = '%s %s %s "%s"' % (sys.executable, patch_tool, extra, patch_file) + cmd = '%s %s %s "%s"' % (" ".join(exe), patch_tool, extra, patch_file) print("\n"+cmd) else: - cmd = '%s %s -q %s "%s"' % (sys.executable, patch_tool, extra, patch_file) + cmd = '%s %s -q %s "%s"' % (" ".join(exe), patch_tool, extra, patch_file) ret = os.system(cmd) assert ret == 0, "Error %d running test %s" % (ret, testname) os.chdir(save_cwd) @@ -171,7 +182,7 @@ def _run_test(self, testname): # recursive comparison self._assert_dirs_equal(join(basepath, "[result]"), tmpdir, - ignore=["%s.patch" % testname, ".svn", ".gitkeep", "[result]"]) + ignore=["%s.patch" % testname, ".svn", ".gitkeep", "[result]", ".coveragerc"]) remove_tree_force(tmpdir) return 0 @@ -193,7 +204,9 @@ def add_test_methods(cls): methname = 'test_' + filename def create_closure(): name = filename - return lambda self: self._run_test(name) + def test(self): + self._run_test(name) + return test test = create_closure() setattr(cls, methname, test) if verbose: From b21b64f4c91ea94a3fe79edea1732dca65734072 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Mon, 28 Oct 2024 10:38:34 +0100 Subject: [PATCH 03/15] Test (and fix) reading patch files from stdin --- patch_ng.py | 2 +- tests/run_tests.py | 25 +++++++++++++++++-------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/patch_ng.py b/patch_ng.py index f3354b0..90322b8 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -1340,7 +1340,7 @@ def main(): setdebug() # this sets global debugmode variable if readstdin: - patch = PatchSet(sys.stdin) + patch = PatchSet(sys.stdin.buffer) else: patchfile = options.input if options.input else args[0] urltest = patchfile.split(':')[0] diff --git a/tests/run_tests.py b/tests/run_tests.py index 81bd524..4fa5db3 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -39,6 +39,7 @@ import unittest import copy import stat +import subprocess from os import listdir, chmod from os.path import abspath, dirname, exists, join, isdir, isfile from tempfile import mkdtemp @@ -114,7 +115,7 @@ def _assert_dirs_equal(self, dir1, dir2, ignore=[]): self.fail("extra file or directory: %s" % e2) - def _run_test(self, testname): + def _run_test(self, testname, inputarg): """ boilerplate for running *.patch file tests """ @@ -164,13 +165,20 @@ def _run_test(self, testname): save_cwd = getcwdu() os.chdir(tmpdir) extra = "-f" if "10fuzzy" in testname else "" - if verbose: + if not verbose: + extra += " -q " + extra += " " + inputarg + " " + if "--" in inputarg: + cmd = '%s %s %s' % (" ".join(exe), patch_tool, extra) + with open(patch_file, "rb") as f: + input = f.read() + else: cmd = '%s %s %s "%s"' % (" ".join(exe), patch_tool, extra, patch_file) + input = None + if verbose: print("\n"+cmd) - else: - cmd = '%s %s -q %s "%s"' % (" ".join(exe), patch_tool, extra, patch_file) - ret = os.system(cmd) - assert ret == 0, "Error %d running test %s" % (ret, testname) + proc = subprocess.run(cmd, shell=True, input=input) + assert proc.returncode == 0, "Error %d running test %s" % (proc.returncode, testname) os.chdir(save_cwd) @@ -200,12 +208,13 @@ def add_test_methods(cls): testset = [testptn.match(e).group('name') for e in listdir(TESTS) if testptn.match(e)] testset = sorted(set(testset)) - for filename in testset: + for idx, filename in enumerate(testset): methname = 'test_' + filename def create_closure(): name = filename + inputarg = ["", "-i", "--"][idx % 3] def test(self): - self._run_test(name) + self._run_test(name, inputarg) return test test = create_closure() setattr(cls, methname, test) From da5cdabadc293cd4e575f8c1d11234bca4b7e3d2 Mon Sep 17 00:00:00 2001 From: pavelgood Date: Sat, 30 Nov 2024 11:49:32 +0100 Subject: [PATCH 04/15] Fixed: incorrect GIT format patches detection Index length is not fixed to 7 and can vary up to 40 characters. --- patch_ng.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/patch_ng.py b/patch_ng.py index 90322b8..9de5668 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -761,8 +761,10 @@ def _detect_type(self, p): if p.header[idx].startswith(b"diff --git"): break if p.header[idx].startswith(b'diff --git a/'): + # git-format-patch with --full-index generates full blob index, which is 40 symbols length + # shortcut index length may vary, seems to depend on the project size if (idx+1 < len(p.header) - and re.match(b'(?:index \\w{7}..\\w{7} \\d{6}|new file mode \\d*)', p.header[idx+1])): + and re.match(b'(?:index \\w{7,40}..\\w{7,40} \\d{6}|new file mode \\d*)', p.header[idx+1])): if DVCS: return GIT From 5315002de3737ebce081fb957b7fe509e51aa458 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 25 Mar 2025 10:40:03 +0100 Subject: [PATCH 05/15] Remove eager handling of a/ and b/ prefixes, should be done with strip option --- patch_ng.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/patch_ng.py b/patch_ng.py index 9de5668..47f5c01 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -793,12 +793,14 @@ def _detect_type(self, p): def _normalize_filenames(self): """ sanitize filenames, normalizing paths, i.e.: - 1. strip a/ and b/ prefixes from GIT and HG style patches - 2. remove all references to parent directories (with warning) - 3. translate any absolute paths to relative (with warning) + 1. remove all references to parent directories (with warning) + 2. translate any absolute paths to relative (with warning) [x] always use forward slashes to be crossplatform (diff/patch were born as a unix utility after all) + [x] Do *not* strip a/ and b/ prefixes from GIT and HG style + patches, GNU patch would not do so, instead the user + *must* account for this in the -p/--strip option return None """ @@ -809,18 +811,6 @@ def _normalize_filenames(self): debug(" patch type = %s" % p.type) debug(" source = %s" % p.source) debug(" target = %s" % p.target) - if p.type in (HG, GIT): - debug("stripping a/ and b/ prefixes") - if p.source != b'/dev/null': - if not p.source.startswith(b"a/"): - warning("invalid source filename") - else: - p.source = p.source[2:] - if p.target != b'/dev/null': - if not p.target.startswith(b"b/"): - warning("invalid target filename") - else: - p.target = p.target[2:] p.source = xnormpath(p.source) p.target = xnormpath(p.target) From 044cdfe9dabdb9bab3d016abcaa2f3e8d86e705b Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 25 Mar 2025 10:40:19 +0100 Subject: [PATCH 06/15] Fix registering logging handler so that INFO messages actually print --- patch_ng.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/patch_ng.py b/patch_ng.py index 47f5c01..59e62da 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -109,20 +109,18 @@ def createLock(self): debugmode = False -def setdebug(): - global debugmode, streamhandler +def setdebug(): + global debugmode debugmode = True - loglevel = logging.DEBUG - logformat = "%(levelname)8s %(message)s" - logger.setLevel(loglevel) + logger.setLevel(logging.DEBUG) + streamhandler.setFormatter(logging.Formatter("%(levelname)8s %(message)s")) - if streamhandler not in logger.handlers: - # when used as a library, streamhandler is not added - # by default - logger.addHandler(streamhandler) - streamhandler.setFormatter(logging.Formatter(logformat)) +if streamhandler not in logger.handlers: + # when used as a library, streamhandler is not added + # by default + logger.addHandler(streamhandler) #------------------------------------------------ From 3ac1af79605c8827ef4d5742ce2ad3d29fa76739 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 25 Mar 2025 10:55:46 +0100 Subject: [PATCH 07/15] Add a workflow to build a patch.exe --- .github/workflows/build_exe.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/build_exe.yml diff --git a/.github/workflows/build_exe.yml b/.github/workflows/build_exe.yml new file mode 100644 index 0000000..a25a48b --- /dev/null +++ b/.github/workflows/build_exe.yml @@ -0,0 +1,26 @@ +name: Build patch exe for Windows + +on: workflow_dispatch + +jobs: + windows_exe: + name: Create windows executable + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.12 + architecture: x64 + - uses: Nuitka/Nuitka-Action@main + with: + nuitka-version: main + script-name: patch_ng.py + mode: onefile + output-dir: build + output-file: patch.exe + - uses: actions/upload-artifact@v4 + with: + name: exe + path: build/patch.exe + include-hidden-files: true From 328e6c79f173c202561ef6388c604c360dc114d5 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 25 Mar 2025 11:36:17 +0100 Subject: [PATCH 08/15] Publish patch.exe --- .github/workflows/build_exe.yml | 35 ++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build_exe.yml b/.github/workflows/build_exe.yml index a25a48b..35af13e 100644 --- a/.github/workflows/build_exe.yml +++ b/.github/workflows/build_exe.yml @@ -1,6 +1,9 @@ name: Build patch exe for Windows -on: workflow_dispatch +on: + push: + tags: + - '*' jobs: windows_exe: @@ -19,8 +22,30 @@ jobs: mode: onefile output-dir: build output-file: patch.exe - - uses: actions/upload-artifact@v4 + - name: Create release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - name: exe - path: build/patch.exe - include-hidden-files: true + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body: | + Automatic build of patch.exe + draft: true + prerelease: false + - name: Upload release asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./build/patch.exe + asset_name: patch.exe + asset_content_type: application/vnd.microsoft.portable-executable + - name: Publish release + uses: StuYarrow/publish-release@v1.1.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + id: ${{ steps.create_release.outputs.id }} From 2d11f033869ddbc7de8aa4c2e7a14f71b3e2d1c1 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 25 Mar 2025 11:51:45 +0100 Subject: [PATCH 09/15] Bump version --- patch_ng.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patch_ng.py b/patch_ng.py index 59e62da..272dbc8 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -31,7 +31,7 @@ from __future__ import print_function __author__ = "Conan.io " -__version__ = "1.18.0" +__version__ = "1.19.0" __license__ = "MIT" __url__ = "https://github.com/conan-io/python-patch" From e27cb2d45cf9acf9c15bfb546fdd3ccfb4dd44d8 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Tue, 25 Mar 2025 12:34:04 +0100 Subject: [PATCH 10/15] Add patch.exe as entry point --- .github/workflows/build_exe.yml | 2 +- setup.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build_exe.yml b/.github/workflows/build_exe.yml index 35af13e..d712952 100644 --- a/.github/workflows/build_exe.yml +++ b/.github/workflows/build_exe.yml @@ -2,7 +2,7 @@ name: Build patch exe for Windows on: push: - tags: + tags: - '*' jobs: diff --git a/setup.py b/setup.py index c3f6cd4..15170c8 100644 --- a/setup.py +++ b/setup.py @@ -120,9 +120,9 @@ def load_version(): # To provide executable scripts, use entry points in preference to the # "scripts" keyword. Entry points provide cross-platform support and allow # pip to create the appropriate form of executable for the target platform. - #entry_points={ - # 'console_scripts': [ - # 'patch_ng.py=patch', - # ], - #}, + entry_points={ + 'console_scripts': [ + 'patch = patch_ng:main', + ], + }, ) From 28c0f1a445e42b3f058d9c15ac93c3d058d0479b Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 11 Apr 2025 11:32:58 +0200 Subject: [PATCH 11/15] Make -i option more compatible with gnu patch --- patch_ng.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/patch_ng.py b/patch_ng.py index 272dbc8..3948918 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -1333,6 +1333,8 @@ def main(): patch = PatchSet(sys.stdin.buffer) else: patchfile = options.input if options.input else args[0] + if options.directory and not os.path.isabs(patchfile): + patchfile = os.path.join(options.directory, patchfile) urltest = patchfile.split(':')[0] if (':' in patchfile and urltest.isalpha() and len(urltest) > 1): # one char before : is a windows drive letter From e83e31d849c8d5bf758cf04e31d98a5200c4e603 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 4 Apr 2025 14:09:15 +0200 Subject: [PATCH 12/15] Log creation and deletion of files, too --- patch_ng.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/patch_ng.py b/patch_ng.py index 3948918..4afc944 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -971,9 +971,11 @@ def apply(self, strip=0, root=None, fuzz=False): hunks = [s.decode("utf-8") for s in item.hunks[0].text] new_file = "".join(hunk[1:] for hunk in hunks) save(target, new_file) + info(f"successfully created {target}") elif "dev/null" in target: source = self.strip_path(source, root, strip) safe_unlink(source) + info(f"successfully deleted {target}") else: items.append(item) self.items = items From 08118b963caa100d6716652afbd35532376f12b0 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 4 Apr 2025 14:02:43 +0200 Subject: [PATCH 13/15] Support patches that append without context --- patch_ng.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/patch_ng.py b/patch_ng.py index 4afc944..2b44684 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -1031,9 +1031,13 @@ def apply(self, strip=0, root=None, fuzz=False): validhunks = 0 canpatch = False for lineno, line in enumerate(f2fp): - if lineno+1 < hunk.startsrc: + last_line = f2fp.peek(1) == b'' + if lineno + 1 < hunk.startsrc and not last_line: continue - elif lineno+1 == hunk.startsrc: + elif lineno + 1 == hunk.startsrc or last_line: + # If the patch just appends without context, be gracious + if lineno + 1 != hunk.startsrc: + lineno = hunk.startsrc - 1 hunkfind = [x[1:].rstrip(b"\r\n") for x in hunk.text if x[0] in b" -"] hunkreplace = [x[1:].rstrip(b"\r\n") for x in hunk.text if x[0] in b" +"] #pprint(hunkreplace) From b25a57c545127e7af1028b28369cd20cb0874415 Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 11 Apr 2025 12:46:39 +0200 Subject: [PATCH 14/15] Fix tests and add test for appendonly patches --- patch_ng.py | 4 ++-- tests/12appendonly/12appendonly.patch | 8 ++++++++ tests/12appendonly/Jamroot | 3 +++ tests/12appendonly/[result]/Jamroot | 6 ++++++ 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 tests/12appendonly/12appendonly.patch create mode 100644 tests/12appendonly/Jamroot create mode 100644 tests/12appendonly/[result]/Jamroot diff --git a/patch_ng.py b/patch_ng.py index 2b44684..0654667 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -1034,9 +1034,9 @@ def apply(self, strip=0, root=None, fuzz=False): last_line = f2fp.peek(1) == b'' if lineno + 1 < hunk.startsrc and not last_line: continue - elif lineno + 1 == hunk.startsrc or last_line: + elif lineno + 1 == hunk.startsrc or (last_line and lineno + 1 < hunk.startsrc): # If the patch just appends without context, be gracious - if lineno + 1 != hunk.startsrc: + if last_line and lineno + 1 < hunk.startsrc: lineno = hunk.startsrc - 1 hunkfind = [x[1:].rstrip(b"\r\n") for x in hunk.text if x[0] in b" -"] hunkreplace = [x[1:].rstrip(b"\r\n") for x in hunk.text if x[0] in b" +"] diff --git a/tests/12appendonly/12appendonly.patch b/tests/12appendonly/12appendonly.patch new file mode 100644 index 0000000..ff9e662 --- /dev/null +++ b/tests/12appendonly/12appendonly.patch @@ -0,0 +1,8 @@ +diff --git a/Jamroot b/Jamroot +index a6981dd..0c08f09 100644 +--- a/Jamroot ++++ b/Jamroot +@@ -4,0 +4,3 @@ ++X ++Y ++Z diff --git a/tests/12appendonly/Jamroot b/tests/12appendonly/Jamroot new file mode 100644 index 0000000..186401d --- /dev/null +++ b/tests/12appendonly/Jamroot @@ -0,0 +1,3 @@ +X +Y +Z diff --git a/tests/12appendonly/[result]/Jamroot b/tests/12appendonly/[result]/Jamroot new file mode 100644 index 0000000..9ad6882 --- /dev/null +++ b/tests/12appendonly/[result]/Jamroot @@ -0,0 +1,6 @@ +X +Y +Z +X +Y +Z From 6d2b1f28168535e22eae09cc2652cd5bbb85f1bf Mon Sep 17 00:00:00 2001 From: Tim Felgentreff Date: Fri, 11 Apr 2025 14:05:14 +0200 Subject: [PATCH 15/15] Relicense unnder UPL --- LICENSE | 39 +++++++++++++++++++++++++++++++++++++++ patch_ng.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/LICENSE b/LICENSE index 54060ca..fa28b26 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,42 @@ +Copyright (c) 2025 Tim Felgentreff + +The Universal Permissive License (UPL), Version 1.0 + +Subject to the condition set forth below, permission is hereby granted to any +person obtaining a copy of this software, associated documentation and/or data +(collectively the "Software"), free of charge and under any and all copyright +rights in the Software, and any and all patent rights owned or freely +licensable by each licensor hereunder covering either (i) the unmodified +Software as contributed to or provided by such licensor, or (ii) the Larger +Works (as defined below), to deal in both + +(a) the Software, and +(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +one is included with the Software (each a "Larger Work" to which the Software +is contributed by such licensors), + +without restriction, including without limitation the rights to copy, create +derivative works of, display, perform, and distribute the Software and make, +use, sell, offer for sale, import, export, have made, and have sold the +Software and the Larger Work(s), and to sublicense the foregoing rights on +either these or other terms. + +This license is subject to the following condition: +The above copyright notice and either this complete permission notice or at +a minimum a reference to the UPL must be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +This file incorporates prior work covered by the following copyright and +permission notice: + MIT License ----------- diff --git a/patch_ng.py b/patch_ng.py index 0654667..e6a8b66 100755 --- a/patch_ng.py +++ b/patch_ng.py @@ -4,6 +4,47 @@ Brute-force line-by-line non-recursive parsing + Copyright (c) 2025 Tim Felgentreff + + The Universal Permissive License (UPL), Version 1.0 + + Subject to the condition set forth below, permission is hereby granted to any + person obtaining a copy of this software, associated documentation and/or data + (collectively the "Software"), free of charge and under any and all copyright + rights in the Software, and any and all patent rights owned or freely + licensable by each licensor hereunder covering either (i) the unmodified + Software as contributed to or provided by such licensor, or (ii) the Larger + Works (as defined below), to deal in both + + (a) the Software, and + (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + one is included with the Software (each a "Larger Work" to which the Software + is contributed by such licensors), + + without restriction, including without limitation the rights to copy, create + derivative works of, display, perform, and distribute the Software and make, + use, sell, offer for sale, import, export, have made, and have sold the + Software and the Larger Work(s), and to sublicense the foregoing rights on + either these or other terms. + + This license is subject to the following condition: + The above copyright notice and either this complete permission notice or at + a minimum a reference to the UPL must be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + This file incorporates prior work covered by the following copyright and + permission notice: + +--- + Copyright (c) 2008-2016 anatoly techtonik Available under the terms of MIT license