So you asked an LLM to generate a code diff, tried to apply it with git apply, and got a bunch of malformed patch errors? Well fear no more, patch-fixer is here to save the day... more or less.
This tool can also split patches into separate files based on file lists, making it easy to selectively apply changes.
# Make sure you're using at least python 3.10
python -m venv .venv/
source .venv/bin/activate
pip install patch-fixerAfter installation, patch-fixer provides a unified command-line interface:
patch-fixer fix original broken.patch fixed.patchwhere:
originalis the file or directory you were trying to patchbroken.patchis the malformed patch generated by the LLMfixed.patchis the output file containing the (hopefully) fixed patch
Options:
--fuzzy: enable fuzzy string matching for better context matching (experimental)--add-newline: add final newlines when processing "No newline at end of file" markers
# Split with files specified on command line
patch-fixer split input.patch included.patch excluded.patch -f file1.py file2.py
# Split using a file list
patch-fixer split input.patch included.patch excluded.patch -i files_to_include.txtwhere:
input.patchis the patch file to splitincluded.patchwill contain changes for the specified filesexcluded.patchwill contain changes for all other files-fallows specifying files directly on the command line-ireads the file list from a text file (one file per line)
from patch_fixer import fix_patch
patch_file = "/path/to/broken.patch"
original = "/path/to/original/state" # file or directory being patched
with open(patch_file, encoding="utf-8") as f:
patch_lines = f.readlines()
# basic usage
fixed_lines = fix_patch(patch_lines, original)
# with fuzzy matching enabled
fixed_lines = fix_patch(patch_lines, original, fuzzy=True)
# with final newline addition
fixed_lines = fix_patch(patch_lines, original, add_newline=True)
output_file = "/path/to/fixed.patch"
with open(output_file, 'w', encoding='utf-8') as f:
f.writelines(fixed_lines)from patch_fixer import split_patch
with open("input.patch", encoding="utf-8") as f:
patch_lines = f.readlines()
# split to include only specific files
files_to_include = ["./src/main.py", "./src/utils.py"]
included, excluded = split_patch(patch_lines, files_to_include)
# write the split patches
with open("included.patch", 'w', encoding='utf-8') as f:
f.writelines(included)
with open("excluded.patch", 'w', encoding='utf-8') as f:
f.writelines(excluded)Analyze patches without modifying them:
from patch_fixer.analyzer import analyze_patch, get_patch_summary
with open("patch.diff") as f:
lines = f.readlines()
# Get detailed information
info = analyze_patch(lines)
print(f"Files: {info.total_files}, Added: {info.total_additions}, Removed: {info.total_deletions}")
# Get human-readable summary
print(get_patch_summary(lines))Add custom logic to patch processing:
def log_hunks(hunk_lines, context):
print(f"Processing hunk in {context['current_file']}")
return hunk_lines
fixed = fix_patch(patch_lines, original, pre_hunk_hook=log_hunks)- When fixing patches with missing
indexlines, the tool requires the files to be in a git repository to regenerate the index. This is only needed for file deletions and renames. patch-fixerassumes the patch follows git's unified diff format.- Current implementation is not very robust to corrupted hunk content
- Much more comprehensive fuzzy string matching is planned
git clone https://github.com/ajcm474/patch-fixer.git
cd patch-fixer
pip install -e .[test]
pytestFrom version 0.3.0 onward (at least until version 1.0), some test failures are expected
in bugfix versions as I like to use test-driven development to build out new features.
Please only report test failures if the same test existed and passed in the most recent 0.x.0 version.
This is free and open source software, released under the Apache 2.0 License. See LICENSE for details.