mpatch 1.5.0

A smart, context-aware patch tool that applies diffs using fuzzy matching, ideal for AI-generated code.
Documentation

Mpatch

CI Status Latest Release Crates.io License: MIT Downloads

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.

Original File (Modified Locally) AI-Generated Patch (Stale Context) patch Result mpatch Result
fn main() { // Forgettable comment println!("Hello");} fn main() {- println!("Hello");+ println!("World"); } ❌ FailedHunk #1 FAILED at 1.1 out of 1 hunk FAILED fn main() { // Forgettable comment println!("World");}

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 (Fastest):

cargo binstall mpatch

Manual Download:

  1. Go to the Releases Page.
  2. Download the archive for your architecture (see table below).
  3. 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.

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:

    gpg --import public.key
    
  2. Verify the archive:

    # Example for Linux x64
    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

cargo install mpatch

CLI Usage

Basic Application

Apply a patch file (Markdown, Diff, or Conflict markers) to a target directory.

mpatch changes.md ./src

From Clipboard

Apply a patch copied to your clipboard directly to a target directory.

mpatch -c ./src

Preview Changes (Dry Run)

See exactly what will happen without modifying files.

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.

# 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).

mpatch -R changes.md ./src

Debugging

If 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].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:

[dependencies]
mpatch = "1.5.0"

1. Simple One-Shot (String to String)

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(())
}

2. Batch Application (File System)

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(())
}

3. Reversing Patches

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 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.

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.

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 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:
    git clone https://github.com/romelium/mpatch.git
    cd mpatch
    cargo test
    

License

MIT License. See LICENSE for details.