A smart, context-aware patch tool that applies diffs using fuzzy matching, ideal for AI-generated code.
Project description
Mpatch (Python)
mpatch is a blazing-fast, context-aware patching library made with Rust. It applies diffs using fuzzy matching, making it the perfect tool for processing patches generated by LLMs (ChatGPT, Claude, Gemini, etc.) that often hallucinate line numbers or drift in context.
Standard patch or git apply fails if the target file has been modified even slightly. mpatch looks for the surrounding lines, dynamically adjusts indentation, and applies the change safely.
🦀 Looking for the CLI tool or Rust library? This is the documentation for the Python bindings. Check out the main
mpatchrepository and CLI documentation.
💡 The Problem vs. The Solution
When an AI tries to edit a file that has been modified locally since the AI last saw it, standard tools fail.
| Original File | AI-Generated Patch | Standard patch |
mpatch |
|---|---|---|---|
def main(): |
def main(): |
❌ Failed |
def main(): |
✨ Features
- 🧠 Fuzzy Matching: Resilient to stale context, whitespace changes, and minor code drift.
- 🤖 Format Independent: Automatically recognizes Unified Diffs, Markdown code blocks (
```diff), and Conflict Markers (<<<<====>>>>). - ✨ Smart Indentation: Automatically translates tabs/spaces and aligns injected code to match the target file perfectly.
- 🛡️ Secure: Built-in protection against directory traversal attacks (e.g.,
--- a/../../../etc/passwd). - ⚡ Blazing Fast & Concurrent: Written in Rust. It heavily optimizes the diffing algorithms and releases the GIL during patching, allowing true multithreading in Python.
- 🐍 Pythonic & Typed: Supports slice indexing, unaries (
~patchto invert), rich error handling, and ships with comprehensive.pyistubs.
📦 Installation
Install directly from PyPI (pre-compiled wheels are provided for macOS, Linux, and Windows):
pip install mpatch
🚀 Quick Start
The simplest way to use mpatch is the high-level patch_content function. It takes a diff (even one wrapped in Markdown) and applies it to a string.
import mpatch
original_code = """\
def greet():
print("Hello, old friend")
"""
# Input can be a raw diff, or a diff wrapped in Markdown (common chat output)
diff = """\
Here is the fix:
```diff
--- a/greet.py
+++ b/greet.py
@@ -1,2 +1,2 @@
def greet():
- print("Hello, old friend")
+ print("Hello, new world!")
```
"""
# Apply the patch in-memory!
new_code = mpatch.patch_content(diff, original=original_code)
print(new_code)
# Output:
# def greet():
# print("Hello, new world!")
🛠️ Advanced API Tour
For tool builders and Agent frameworks, mpatch provides a rich Object-Oriented API for parsing, inspecting, and applying patches.
1. Parsing Diffs
You can parse a diff string into a list of Patch objects. mpatch automatically detects the format.
import mpatch
patches = mpatch.parse_auto(diff_string)
for patch in patches:
print(f"Target file: {patch.file_path}")
print(f"Number of hunks: {len(patch)}")
if patch.is_creation:
print("This patch creates a new file.")
2. Inspecting Hunks
Patches are composed of Hunk objects. You can slice, index, and inspect the exact lines added or removed.
patch = mpatch.parse_auto(diff_string)[0]
for i, hunk in enumerate(patch):
print(f"Hunk {i+1} changes:")
print(f" Removed: {hunk.removed_lines}")
print(f" Added: {hunk.added_lines}")
3. Applying to the Filesystem (Batch Processing)
Apply patches directly to a directory on disk. mpatch handles file reading, writing, creation, and deletion automatically.
from pathlib import Path
import mpatch
diff = """... your diff here ..."""
target_directory = Path("./my_project")
# Apply all patches in a diff directly to the filesystem
success = mpatch.apply_directory(diff, target_directory)
if success:
print("All files updated successfully!")
For granular control over multiple patches, use apply_patches_to_dir:
patches = mpatch.parse_auto(diff)
batch_result = mpatch.apply_patches_to_dir(patches, target_directory)
if not batch_result.all_succeeded:
# Inspect hard failures (e.g., IO errors, Permission denied, Path traversal)
for path, error_msg in batch_result.hard_failures:
print(f"Critical error on {path}: {error_msg}")
4. Detailed Reporting & Error Handling
When applying a specific Patch, you receive a rich PatchResult or InMemoryResult detailing exactly what happened to every hunk.
patch = mpatch.parse_auto(diff)[0]
result = patch.apply_to_file("./my_project")
if result.report.has_failures:
print(f"Patch partially failed. {result.report.failure_count} hunks failed.")
for failure in result.report.failures:
print(f"Hunk {failure.hunk_index} failed due to: {failure.error_type}")
# If it was a fuzzy match that didn't meet the threshold:
if failure.error_type == "FuzzyMatchBelowThreshold":
print(f"Best score was {failure.best_score}, needed {failure.threshold}")
5. Dry Runs & Fuzz Factor
Want to see what would happen without modifying your files? Use dry_run=True. You can also tweak the fuzz_factor (0.0 to 1.0, default is 0.7).
result = patch.apply_to_file(
"./my_project",
fuzz_factor=0.5, # Lower is more lenient, higher is stricter (0.0 is exact match only)
dry_run=True # Generate a diff without writing to disk
)
if result.diff:
print("Proposed changes:")
print(result.diff)
6. Reversing Patches
You can reverse a patch (swap additions and deletions) easily using Python's bitwise NOT operator (~) or the invert() method.
patch = mpatch.parse_auto(diff)[0]
# Invert the patch
reversed_patch = ~patch
# Apply the reversed patch to undo changes
reversed_patch.apply_to_file("./my_project")
7. Creating Diffs Programmatically
Need to generate a unified diff between two strings? You can generate the raw string representation or get a Patch object directly.
old_text = "apple\nbanana\npineapple\n"
new_text = "apple\norange\npineapple\n"
# 1. Generate a raw unified diff string
diff_str = mpatch.create_unified_diff("fruits.txt", old_text, new_text)
print(diff_str)
# --- a/fruits.txt
# +++ b/fruits.txt
# @@ -1,3 +1,3 @@
# apple
# -banana
# +orange
# pineapple
# 2. Generate a Patch object directly for programmatic manipulation
patch = mpatch.Patch.from_texts("fruits.txt", old_text, new_text)
print(patch[0].removed_lines) # ['banana']
🚨 Exceptions
mpatch provides specific Python exceptions so you can handle failures gracefully:
mpatch.MpatchError: Base exception for all mpatch errors.mpatch.ParseError: Raised when the diff format is malformed or invalid.mpatch.ApplyError: Raised during strict patch application when a hunk fails.mpatch.PathTraversalError: Raised when a patch attempts to modify files outside the target directory (e.g.,../../../etc/passwd).
try:
mpatch.apply_patch_to_file(malicious_patch, "./src")
except mpatch.PathTraversalError as e:
print(f"Security blocked path traversal: {e}")
📄 License
This project is licensed under the MIT License.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mpatch-1.6.2.tar.gz.
File metadata
- Download URL: mpatch-1.6.2.tar.gz
- Upload date:
- Size: 143.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8b5211fdca3f0ef7453dd2e0f4d635d899f0d8daa3aead9f3af39459236661e
|
|
| MD5 |
7c899f3212ef5a195d483377f2df8b8e
|
|
| BLAKE2b-256 |
7d987d88150f9a80432e7b4a66658e2201b4ba48e5cdd3eea8353d39dc4c9de0
|
Provenance
The following attestation bundles were made for mpatch-1.6.2.tar.gz:
Publisher:
pypi.yml on Romelium/mpatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mpatch-1.6.2.tar.gz -
Subject digest:
b8b5211fdca3f0ef7453dd2e0f4d635d899f0d8daa3aead9f3af39459236661e - Sigstore transparency entry: 1702696085
- Sigstore integration time:
-
Permalink:
Romelium/mpatch@082d313bd226662487abbabd8f083e8aee8441a3 -
Branch / Tag:
refs/tags/v1.6.2 - Owner: https://github.com/Romelium
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@082d313bd226662487abbabd8f083e8aee8441a3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file mpatch-1.6.2-cp313-cp313t-win_arm64.whl.
File metadata
- Download URL: mpatch-1.6.2-cp313-cp313t-win_arm64.whl
- Upload date:
- Size: 317.1 kB
- Tags: CPython 3.13t, Windows ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bb5c26b5f6188707d730c9311f0769a4ac16c64e52f669e191b15a2922086283
|
|
| MD5 |
0826b3f20aeec518e1ca81e7bd9273c2
|
|
| BLAKE2b-256 |
278a1d4b19e58528361e4c144097cd2e26e955e932df61ca937f155c3349d241
|
Provenance
The following attestation bundles were made for mpatch-1.6.2-cp313-cp313t-win_arm64.whl:
Publisher:
pypi.yml on Romelium/mpatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mpatch-1.6.2-cp313-cp313t-win_arm64.whl -
Subject digest:
bb5c26b5f6188707d730c9311f0769a4ac16c64e52f669e191b15a2922086283 - Sigstore transparency entry: 1702696089
- Sigstore integration time:
-
Permalink:
Romelium/mpatch@082d313bd226662487abbabd8f083e8aee8441a3 -
Branch / Tag:
refs/tags/v1.6.2 - Owner: https://github.com/Romelium
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@082d313bd226662487abbabd8f083e8aee8441a3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file mpatch-1.6.2-cp38-abi3-win_arm64.whl.
File metadata
- Download URL: mpatch-1.6.2-cp38-abi3-win_arm64.whl
- Upload date:
- Size: 318.6 kB
- Tags: CPython 3.8+, Windows ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2ff3a00e0727978f097fcad0f43f55127aa964e6791896d8abdf322d555a52d4
|
|
| MD5 |
61d9b90f93a1478d3fc63ccb37c504f6
|
|
| BLAKE2b-256 |
b8d55f7e5db167843e1ee8d338add98ed4627fa4c08addd8f5dd8ff41c579575
|
Provenance
The following attestation bundles were made for mpatch-1.6.2-cp38-abi3-win_arm64.whl:
Publisher:
pypi.yml on Romelium/mpatch
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mpatch-1.6.2-cp38-abi3-win_arm64.whl -
Subject digest:
2ff3a00e0727978f097fcad0f43f55127aa964e6791896d8abdf322d555a52d4 - Sigstore transparency entry: 1702696101
- Sigstore integration time:
-
Permalink:
Romelium/mpatch@082d313bd226662487abbabd8f083e8aee8441a3 -
Branch / Tag:
refs/tags/v1.6.2 - Owner: https://github.com/Romelium
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
pypi.yml@082d313bd226662487abbabd8f083e8aee8441a3 -
Trigger Event:
release
-
Statement type: