patch-ng
Library to parse and apply unified diffs. `patch-ng` is a fork of the original `python-patch` project, providing active maintenance and bug fixes. The current version is 1.19.0, released in October 2025. It is actively maintained by Conan-io with regular releases and is compatible with Python 3.6+.
Warnings
- breaking The library was renamed from `python-patch` to `patch-ng` at version 1.17. Users migrating from the old library must update their `pip install` command and `import` statements.
- breaking As of version 1.18.0, `patch-ng` requires Python 3.6 or newer. Older Python versions are no longer supported.
- gotcha Prior to version 1.19.0, `patch-ng` had known issues with applying Git patches involving file renames/moves, changes in file permissions, incorrect handling of Git full index format, and general issues with large patch files or quoted file paths. These issues could lead to incomplete or incorrect patch application.
Install
-
pip install patch-ng
Imports
- PatchSet
from patch_ng import from_string, from_file, PatchSet
- from_string
from patch_ng import from_string
- from_file
from patch_ng import from_file
- patch
from patch_ng import from_string, PatchSet
Quickstart
import os
import tempfile
import shutil
from patch_ng import from_string, PatchSet
# Create a temporary directory to work in
temp_dir = tempfile.mkdtemp()
original_file_path = os.path.join(temp_dir, "my_file.txt")
try:
# 1. Create an original file
with open(original_file_path, "w") as f:
f.write("Line 1: Hello world\n")
f.write("Line 2: This is a test.\n")
f.write("Line 3: End of file.\n")
print(f"Original file '{os.path.basename(original_file_path)}' created in {temp_dir}:\n" \
f"{open(original_file_path, 'r').read()}")
# 2. Define a unified diff patch string
# This patch changes 'Line 2' and adds 'New Line 2.5'
patch_content = """--- a/my_file.txt
+++ b/my_file.txt
@@ -1,3 +1,4 @@
Line 1: Hello world
-Line 2: This is a test.
+Line 2: This line was changed.
+New Line 2.5: Inserted line.
Line 3: End of file.
"""
# 3. Parse the patch string
# The `strip` argument removes leading path components (e.g., 'a/' or 'b/')
patchset = from_string(patch_content)
if not patchset:
print("Failed to parse patch content.")
else:
# 4. Apply the patch to the specified root directory
# The `root` argument is crucial to apply the patch outside the current working directory.
success = patchset.apply(strip=1, root=temp_dir)
if success:
print(f"Patch applied successfully to files in {temp_dir}.")
print(f"\nPatched file content of '{os.path.basename(original_file_path)}':\n" \
f"{open(original_file_path, 'r').read()}")
else:
print("Failed to apply patch.")
finally:
# Clean up the temporary directory
shutil.rmtree(temp_dir)
print(f"\nCleaned up temporary directory: {temp_dir}")