mpatch is a contextual patching program/library tailored towards the "messy" world of modern software development. Different from standard patch and git apply, which require line number/context to be exact matches, mpatch utilizes fuzzy matching, looking for patches based on the context around the change in the code.
This tool was created to easily apply diffs produced by LLMs (ChatGPT, Gemini, Claude, Copilot), since they tend to hallucinate the exact line numbers or the context around the patch.
🐍 Python Developer? We have official Python bindings! Check out the
mpatchPyPI package and Python documentation.
You ask an AI to modify some code. You get the diff. Except, the comment inside this code block is missing.
Standard patch: Won't work. The context isn't exact byte-for-byte match.
mpatch: Works! It notices that the code structure hasn't changed and applies the patch.
| Original File (Modified Locally) | AI-Generated Patch (Stale Context) | patch Result |
mpatch Result |
|---|---|---|---|
fn main() { |
fn main() { |
❌ Failed |
fn main() { |
mpatch automatically detects the input format. Flags are not needed.
- Code Blocks in Markdown: The default output format for language models. Uses either
```diff,```rust, or other code blocks if they have diff headers. - Unified Diff: Default
git diffordiff -uformat. - Conflict Marker: Git-like conflict markers with
<<<<,====, and>>>>. Reminder: They lack file paths and are suitable for patching strings in memory.
- 🧠 Fuzzy Matching: Runs a similarity algorithm to find the most suitable place to apply a patch if no exact matching is found. It supports stale context, whitespaces changes, and minor code differences.
- 🤖 Format Independent: Automatically recognizes and processes:
- Markdown diff code blocks (standard chat output format).
- Unified Diff (output from
git diffcommand).
- 📋 Clipboard Support: Input directly from your clipboard with
-cor--clipboard. - ✨ Smarter Indentation: Automatically indents added lines to be consistent with the target file. It perfectly applies the patch files that were initially indented in Markdown lists or use another tab/space indentation style.
- 🗑️ File Deletion: Automatically removes the target file if the output becomes empty after patching.
- 🛡️ Secure: Path traversal is automatically prevented, which means that no evil patch files can overwrite anything outside the target directory.
- ⚡ Fast: As fuzzy searching takes time,
mpatchsearches for matches using every CPU core viarayon. - 🔍 Dry Run: Preview what the tool would do with
--dry-run.
We provide pre-compiled binaries for Windows, macOS, and Linux.
Using cargo-binstall (Fastest):
cargo binstall mpatchManual Download:
- Go to the Releases Page.
- Download the archive for your architecture (see table below).
- Extract and add to your
PATH.
| Platform | Architecture | Target | Notes |
|---|---|---|---|
| macOS | Universal | universal-apple-darwin |
Best for Mac. Runs natively on M1/M2/M3 & Intel. |
| x64 | x86_64-apple-darwin |
Older Intel Macs. | |
| ARM64 | aarch64-apple-darwin |
Apple Silicon (M1/M2/M3). | |
| Windows | x64 | x86_64-pc-windows-msvc |
Standard 64-bit Windows. |
| ARM64 | aarch64-pc-windows-msvc |
Surface Pro X, Parallels. | |
| Linux | x64 | x86_64-unknown-linux-gnu |
Ubuntu, Debian, Fedora, etc. |
| x64 (Static) | x86_64-unknown-linux-musl |
Alpine Linux, Docker containers. | |
| ARM64 | aarch64-unknown-linux-gnu |
Raspberry Pi 4/5, AWS Graviton. | |
| ARM64 (Static) | aarch64-unknown-linux-musl |
Alpine on ARM64. | |
| ARMv7 | armv7-unknown-linux-gnueabihf |
Older Raspberry Pi (2/3), IoT. | |
| ARMv7 (Static) | armv7-unknown-linux-musleabihf |
Static binaries for ARMv7. |
The integrity of all released binaries is verified with GPG. The .sig signature file along with our public key (public.key) will help you verify the authenticity of the downloaded files.
-
Import the public key:
gpg --import public.key
-
Verify the archive:
# Example for Linux x64 gpg --verify mpatch-x86_64-unknown-linux-gnu-v1.6.4.tar.gz.sig mpatch-x86_64-unknown-linux-gnu-v1.6.4.tar.gz
cargo install mpatchApply a patch file (Markdown, Diff, or Conflict markers) to a target directory.
mpatch changes.md ./srcApply a patch copied to your clipboard directly to a target directory.
mpatch -c ./srcSee exactly what will happen without modifying files.
mpatch --dry-run changes.md ./srcIf mpatch is matching the wrong place, increase the strictness (default is 0.7). If it's failing to find a match, lower it.
# Stricter (needs 90% similarity)
mpatch --fuzz-factor 0.9 changes.md ./src
# Disable fuzzy matching (exact match only)
mpatch --fuzz-factor 0.0 changes.md ./srcUndo a previously applied patch (swaps additions and deletions).
mpatch -R changes.md ./srcIf a patch fails, generate a comprehensive debug report (includes file states, logs, and diffs) to analyze why.
mpatch -vvvv changes.md ./src
# Generates: mpatch-debug-report-[timestamp].mdmpatch is designed to be the patching engine for AI coding agents and tools. It exposes a robust Rust API.
Add to Cargo.toml:
[dependencies]
mpatch = "1.6.4"Ideal for processing text in memory.
use mpatch::{patch_content_str, ApplyOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let original_code = "fn main() { println!(\"Old\"); }";
// Input can be Markdown, Raw Diff, or Conflict Markers
let patch_text = r#"
```diff
--- a/main.rs
+++ b/main.rs
@@ -1 +1 @@
-fn main() { println!("Old"); }
+fn main() { println!("New"); }
```
"#;
let options = ApplyOptions::new(); // Default fuzz_factor: 0.7
let new_code = patch_content_str(patch_text, Some(original_code), &options)?;
assert_eq!(new_code, "fn main() { println!(\"New\"); }");
Ok(())
}Ideal for CLI tools applying multi-file patches.
use mpatch::{parse_auto, apply_patches_to_dir, ApplyOptions};
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let diff_content = include_str!("../tests/fixtures/changes.md");
// 1. Parse (automatically detects format)
let patches = parse_auto(diff_content)?;
// 2. Apply
let target_dir = Path::new("./src");
let options = ApplyOptions::new();
let results = apply_patches_to_dir(&patches, target_dir, options);
if results.all_succeeded() {
println!("All patches applied successfully!");
} else {
// Inspect specific failures
for (path, result) in results.hard_failures() {
eprintln!("Failed to process {}: {}", path.display(), result);
}
}
Ok(())
}Programmatically invert patches (additions become deletions and vice versa).
use mpatch::{parse_auto, invert_patches};
let diff_content = "--- a/file\n+++ b/file\n@@ -1 +1 @@\n-old\n+new";
let patches = parse_auto(diff_content)?;
let reversed = invert_patches(&patches);
// Now apply `reversed` to undo changesIf you want to treat partial applications (where some hunks fail) as an error, use the try_ variants.
use mpatch::{parse_single_patch, try_apply_patch_to_content, ApplyOptions, StrictApplyError};
let original_content = "fn main() { println!(\"Old\"); }";
let diff_content = "--- a/main.rs\n+++ b/main.rs\n@@ -1 +1 @@\n-fn main() { println!(\"Old\"); }\n+fn main() { println!(\"New\"); }";
let patch = parse_single_patch(diff_content)?;
let options = ApplyOptions::exact();
// Returns an Err if any hunk fails to apply
match try_apply_patch_to_content(&patch, Some(original_content), &options) {
Ok(result) => println!("Success: {}", result.new_content),
Err(StrictApplyError::PartialApply { report }) => {
eprintln!("Patch partially applied. {} hunks failed.", report.failure_count());
}
Err(e) => eprintln!("Hard error: {}", e),
}You can also use mpatch to generate patches by comparing two strings.
use mpatch::Patch;
let old_text = "fn main() { println!(\"Old\"); }";
let new_text = "fn main() { println!(\"New\"); }";
// Create a patch with 3 lines of context
let patch = Patch::from_texts("src/main.rs", old_text, new_text, 3).unwrap();
println!("{}", patch);Though supported by mpatch, this file format (<<<<, ====, >>>>) does not include any file path information.
- Using
mpatchfrom CLI: If the provided file contains only conflict markers,mpatchwill try to patch a file calledpatch_target. - Using
mpatchas a library: The format can be used by thepatch_content_strfunction if your target file content is stored in memory.
In case of a multiple file patching or using mpatch from CLI, it is recommended to use the Unified Diffs format (with --- and +++).
Fuzzy match is
- Heuristics: Before doing a fuzzy match,
mpatchtries to find both exact and "whitespace-insensitive" exact matches. - Anchoring:
mpatchtries to look for unique lines in the patch file to shrink the match range. - Parallelism: If a full scan is required, it uses Rayon to parallelize the workload.
Benchmark code can be found in benches/mpatch_bench.rs. To run, use cargo bench.
Contributions are welcome!
- Bug Reports: Please run
mpatch -vvvv ...to generate a debug report and attach it to your issue. - Development:
git clone https://github.com/romelium/mpatch.git cd mpatch cargo test
MIT License. See LICENSE for details.