# Mpatch
[](https://github.com/romelium/mpatch/actions/workflows/ci.yml)
[](https://github.com/romelium/mpatch/releases/latest)
[](https://crates.io/crates/mpatch)
[](https://opensource.org/licenses/MIT)
[](https://crates.io/crates/mpatch)
`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.
---
## The Problem and the Solution
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.
| <pre>fn main() {<br> // Forgettable comment<br> println!("Hello");<br>}</pre> | <pre> fn main() {<br>- println!("Hello");<br>+ println!("World");<br> }</pre> | <pre>❌ Failed<br>Hunk #1 FAILED at 1.<br>1 out of 1 hunk FAILED</pre> | <pre>fn main() {<br> // Forgettable comment<br> println!("World");<br>}</pre> |
---
### Supported Input Formats
`mpatch` automatically detects the input format. Flags are not needed.
1. **Code Blocks in Markdown:** The default output format for language models. Uses either ` ```diff `, ` ```rust `, or other code blocks if they have diff headers.
2. **Unified Diff:** Default `git diff` or `diff -u` format.
3. **Conflict Marker:** Git-like conflict markers with `<<<<`, `====`, and `>>>>`. **Reminder:** They lack file paths and are suitable for patching strings in memory.
---
## Features
* **🧠 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 diff` command).
* **📋 Clipboard Support:** Input directly from your clipboard with `-c` or `--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, `mpatch` searches for matches using every CPU core via `rayon`.
* **🔍 Dry Run:** Preview what the tool would do with `--dry-run`.
---
## Installation
### Option 1: Pre-compiled Binaries (Recommended)
We provide pre-compiled binaries for Windows, macOS, and Linux.
**Using [`cargo-binstall`](https://github.com/cargo-bins/cargo-binstall#installation) (Fastest):**
```bash
cargo binstall mpatch
```
**Manual Download:**
1. Go to the [**Releases Page**](https://github.com/romelium/mpatch/releases).
2. Download the archive for your architecture (see table below).
3. Extract and add to your `PATH`.
| **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. |
### Verifying Signatures
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.
1. **Import the public key:**
```bash
gpg --import public.key
```
2. **Verify the archive:**
```bash
gpg --verify mpatch-x86_64-unknown-linux-gnu-v1.5.0.tar.gz.sig mpatch-x86_64-unknown-linux-gnu-v1.5.0.tar.gz
```
### Option 2: Build from Source
```bash
cargo install mpatch
```
---
## CLI Usage
### Basic Application
Apply a patch file (Markdown, Diff, or Conflict markers) to a target directory.
```bash
mpatch changes.md ./src
```
### From Clipboard
Apply a patch copied to your clipboard directly to a target directory.
```bash
mpatch -c ./src
```
### Preview Changes (Dry Run)
See exactly what will happen without modifying files.
```bash
mpatch --dry-run changes.md ./src
```
### Adjusting Sensitivity
If `mpatch` is matching the wrong place, increase the strictness (default is 0.7). If it's failing to find a match, lower it.
```bash
# 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 ./src
```
### Reversing a Patch
Undo a previously applied patch (swaps additions and deletions).
```bash
mpatch -R changes.md ./src
```
### Debugging
If a patch fails, generate a comprehensive debug report (includes file states, logs, and diffs) to analyze why.
```bash
mpatch -vvvv changes.md ./src
# Generates: mpatch-debug-report-[timestamp].md
```
---
## Library Usage
`mpatch` is designed to be the patching engine for AI coding agents and tools. It exposes a robust Rust API.
Add to `Cargo.toml`:
```toml
[dependencies]
mpatch = "1.5.0"
```
### 1. Simple One-Shot (String to String)
Ideal for processing text in memory.
```rust
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(())
}
```
### 2. Batch Application (File System)
Ideal for CLI tools applying multi-file patches.
```rust
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(())
}
```
### 3. Reversing Patches
Programmatically invert patches (additions become deletions and vice versa).
```rust
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 changes
```
### 4. Strict Apply-or-Fail Workflow
If you want to treat partial applications (where some hunks fail) as an error, use the `try_` variants.
```rust
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),
}
```
### 5. Creating Patches
You can also use `mpatch` to generate patches by comparing two strings.
```rust
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);
```
---
## About the Conflict Markers Format
Though supported by `mpatch`, this file format (`<<<<`, `====`, `>>>>`) **does not include any file path information**.
* **Using `mpatch` from CLI:** If the provided file contains only conflict markers, `mpatch` will try to patch a file called `patch_target`.
* **Using `mpatch` as a library:** The format can be used by the `patch_content_str` function 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 `+++`).
---
## Performance
Fuzzy match is $O(N \times M)$ operation. To ensure speed on large files:
1. **Heuristics:** Before doing a fuzzy match, `mpatch` tries to find both exact and "whitespace-insensitive" exact matches.
2. **Anchoring:** `mpatch` tries to look for unique lines in the patch file to shrink the match range.
3. **Parallelism:** If a full scan is required, it uses [Rayon](https://github.com/rayon-rs/rayon) to parallelize the workload.
*Benchmark code can be found in `benches/mpatch_bench.rs`. To run, use `cargo bench`.*
---
## Contributing
Contributions are welcome!
* **Bug Reports:** Please run `mpatch -vvvv ...` to generate a debug report and attach it to your issue.
* **Development:**
```bash
git clone https://github.com/romelium/mpatch.git
cd mpatch
cargo test
```
## License
MIT License. See [LICENSE](LICENSE) for details.