Expand description
A smart, context-aware patch tool that applies diffs using fuzzy matching.
mpatch is designed to apply unified diffs to a codebase, but with a key
difference from the standard patch command: it doesn’t rely on strict line
numbers. Instead, it finds the correct location to apply changes by searching
for the surrounding context lines.
This makes it highly resilient to patches that are “out of date” because of preceding changes, which is a common scenario when working with AI-generated diffs, code from pull requests, or snippets from documentation.
§Getting Started
The simplest way to use mpatch is the one-shot patch_content_str() function.
It’s perfect for the common workflow of taking a diff string (e.g., from an
LLM in a markdown file) and applying it to some existing content in memory.
use mpatch::{patch_content_str, ApplyOptions};
// 1. Define the original content and the diff.
let original_content = "fn main() {\n println!(\"Hello, world!\");\n}\n";
let diff_content = r#"
A markdown file with a diff block.
```diff
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,3 @@
fn main() {
- println!("Hello, world!");
+ println!("Hello, mpatch!");
}
```
"#;
// 2. Call the one-shot function to parse and apply the patch.
let options = ApplyOptions::new();
let new_content = patch_content_str(diff_content, Some(original_content), &options)?;
// 3. Verify the new content.
let expected_content = "fn main() {\n println!(\"Hello, mpatch!\");\n}\n";
assert_eq!(new_content, expected_content);
§Applying Patches to Files
For CLI tools or scripts that need to modify files on disk, the workflow involves
parsing and then using apply_patches_to_dir(). This example shows the end-to-end
process in a temporary directory.
use mpatch::{parse_auto, apply_patches_to_dir, ApplyOptions};
use std::fs;
use tempfile::tempdir;
// 1. Set up a temporary directory and a file to be patched.
let dir = tempdir()?;
let file_path = dir.path().join("src/main.rs");
fs::create_dir_all(file_path.parent().unwrap())?;
fs::write(&file_path, "fn main() {\n println!(\"Hello, world!\");\n}\n")?;
// 2. Define the diff content, as if it came from a markdown file.
let diff_content = r#"
Some introductory text.
```diff
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,3 @@
fn main() {
- println!("Hello, world!");
+ println!("Hello, mpatch!");
}
```
Some concluding text.
"#;
// 3. Parse the diff content to get patches.
let patches = parse_auto(diff_content)?;
// 4. Apply the patches to the directory.
let options = ApplyOptions::new();
let result = apply_patches_to_dir(&patches, dir.path(), options);
// The batch operation should succeed.
assert!(result.all_succeeded());
// 5. Verify the file was changed correctly.
let new_content = fs::read_to_string(&file_path)?;
let expected_content = "fn main() {\n println!(\"Hello, mpatch!\");\n}\n";
assert_eq!(new_content, expected_content);§Key Concepts
§The Patching Workflow
Using the mpatch library typically involves a two-step process: parsing and applying.
§1. Parsing
First, you convert diff text into a structured Vec<Patch>. mpatch provides
several functions for this, depending on your input format:
parse_auto(): The recommended entry point. It automatically detects the format (Markdown, Unified Diff, or Conflict Markers) and parses the content accordingly.parse_single_patch(): A convenient wrapper aroundparse_auto()that ensures the input contains exactly one patch, returning aResult<Patch, _>.parse_diffs(): Scans a string for markdown code blocks containing diffs.parse_patches(): A lower-level parser that processes a raw unified diff string directly, without needing markdown fences.parse_conflict_markers(): Parses a string containing conflict markers (<<<<,====,>>>>) into patches.parse_patches_from_lines(): The lowest-level parser. It operates on an iterator of lines, which is useful for streaming or avoiding large string allocations.
You can also use detect_patch() to identify the format (Markdown, Unified, or Conflict)
without parsing the full content.
§2. Applying
Once you have a Patch, you can apply it using one of the apply functions:
apply_patches_to_dir(): Applies a list of patches to a directory. This is ideal for processing multi-file diffs.apply_patch_to_file(): The most convenient function for applying a single patch to a file. It handles reading the original file and writing the new content back to disk.apply_patch_to_content(): A pure function for in-memory operations. It takes the original content as a string and returns the new content.
Each of these also has a “strict” try_ variant (e.g., try_apply_patch_to_file())
that treats partial applications as an error, simplifying the common apply-or-fail
workflow.
§Core Data Structures
Patch: Represents all the changes for a single file. It contains the target file path and a list of hunks.Hunk: Represents a single block of changes within a patch, corresponding to a block of changes (like a@@ ... @@section in a unified diff). For Conflict Markers, the “before” block is treated as deletions and the “after” block as additions.
§Context-Driven Matching
The core philosophy of mpatch is to ignore strict line numbers. Instead, it
searches for the context of a hunk—the lines that are unchanged or being
deleted.
- Primary Search: It first looks for an exact, character-for-character match of the hunk’s context.
- Ambiguity Resolution: If the same context appears in multiple places,
mpatchuses the line numbers (e.g., from the@@ ... @@header) as a hint to find the most likely location. Note that patches derived from Conflict Markers typically lack line numbers, so ambiguity cannot be resolved this way. - Fuzzy Matching: If no exact match is found, it uses a similarity algorithm to find the best fuzzy match, making it resilient to minor changes in the surrounding code.
§Advanced Usage
§Configuring ApplyOptions
The behavior of the apply functions is controlled by the ApplyOptions struct.
mpatch provides several convenient ways to construct it:
use mpatch::ApplyOptions;
// For default behavior (fuzzy matching enabled, not a dry run)
let default_options = ApplyOptions::new();
// For common presets
let dry_run_options = ApplyOptions::dry_run();
let exact_options = ApplyOptions::exact();
// For custom configurations using the new fluent methods
let custom_fluent = ApplyOptions::new()
.with_dry_run(true)
.with_fuzz_factor(0.9);
// For complex configurations using the builder pattern
let custom_builder = ApplyOptions::builder()
.dry_run(true)
.fuzz_factor(0.9)
.build();
assert_eq!(custom_fluent.dry_run, custom_builder.dry_run);
assert_eq!(custom_fluent.fuzz_factor, custom_builder.fuzz_factor);§In-Memory Operations and Error Handling
This example demonstrates how to use apply_patch_to_content() for in-memory
operations and how to programmatically handle cases where a patch only
partially applies.
use mpatch::{parse_single_patch, apply_patch_to_content, HunkApplyError};
// 1. Define original content and a patch where the second hunk will fail.
let original_content = "line 1\nline 2\nline 3\n\nline 5\nline 6\nline 7\n";
let diff_content = r#"
```diff
--- a/partial.txt
+++ b/partial.txt
@@ -1,3 +1,3 @@
line 1
-line 2
+line two
line 3
@@ -5,3 +5,3 @@
line 5
-line WRONG CONTEXT
+line six
line 7
```
"#;
// 2. Parse the diff.
let patch = parse_single_patch(diff_content)?;
// 3. Apply the patch to the content in memory.
let options = mpatch::ApplyOptions::exact();
let result = apply_patch_to_content(&patch, Some(original_content), &options);
// 4. Verify that the patch did not apply cleanly.
assert!(!result.report.all_applied_cleanly());
// 5. Inspect the specific failures.
let failures = result.report.failures();
assert_eq!(failures.len(), 1);
assert_eq!(failures[0].hunk_index, 2); // Hunk indices are 1-based.
assert!(matches!(failures[0].reason, HunkApplyError::ContextNotFound));
// 6. Verify that the content was still partially modified by the successful first hunk.
let expected_content = "line 1\nline two\nline 3\n\nline 5\nline 6\nline 7\n";
assert_eq!(result.new_content, expected_content);§Strict Apply-or-Fail Workflow with try_ functions
The previous example showed how to manually check result.report.all_applied_cleanly()
to detect partial failures. For workflows where any failed hunk should be treated as a
hard error, mpatch provides “strict” variants of the apply functions.
These functions return a Result where a partial application is mapped to a
Err(StrictApplyError::PartialApply { .. }). This simplifies the common
apply-or-fail pattern.
use mpatch::{parse_single_patch, try_apply_patch_to_content, ApplyOptions, StrictApplyError};
let original_content = "line 1\nline 2\n";
let failing_diff = r#"
```diff
--- a/file.txt
+++ b/file.txt
@@ -1,2 +1,2 @@
line 1
-WRONG CONTEXT
+line two
```
"#;
let patch = parse_single_patch(failing_diff)?;
let options = ApplyOptions::exact();
// Using the try_ variant simplifies error handling.
let result = try_apply_patch_to_content(&patch, Some(original_content), &options);
assert!(matches!(result, Err(StrictApplyError::PartialApply { .. })));§Step-by-Step Application with HunkApplier
For maximum control, you can use the HunkApplier iterator to apply hunks
one at a time and inspect the state between each step.
use mpatch::{parse_single_patch, HunkApplier, HunkApplyStatus, ApplyOptions};
// 1. Define original content and a patch.
let original_lines = vec!["line 1", "line 2", "line 3"];
let diff_content = r#"
```diff
--- a/file.txt
+++ b/file.txt
@@ -2,1 +2,1 @@
-line 2
+line two
```
"#;
let patch = parse_single_patch(diff_content)?;
let options = ApplyOptions::new();
// 2. Create the applier.
let mut applier = HunkApplier::new(&patch, Some(&original_lines), &options);
// 3. Apply the first (and only) hunk.
let status = applier.next().unwrap();
assert!(matches!(status, HunkApplyStatus::Applied { .. }));
// 4. Check that there are no more hunks.
assert!(applier.next().is_none());
// 5. Finalize the content.
let new_content = applier.into_content();
assert_eq!(new_content, "line 1\nline two\nline 3\n");§Feature Flags
mpatch includes the following optional features:
§parallel
-
Enabled by default.
-
This feature enables parallel processing for the fuzzy matching algorithm using the
rayoncrate. When an exact match for a hunk is not found,mpatchperforms a computationally intensive search for the best fuzzy match. Theparallelfeature significantly speeds up this process on multi-core systems by distributing the search across multiple threads. -
To disable this feature, specify
default-features = falsein yourCargo.toml:[dependencies] mpatch = { version = "1.3.3", default-features = false }You might want to disable this feature if you are compiling for a target that does not support threading (like
wasm32-unknown-unknown) or if you want to minimize dependencies and binary size.
Structs§
- Apply
Options - Options for configuring how a patch is applied.
- Apply
Options Builder - Creates a new builder for
ApplyOptions. - Apply
Result - Contains detailed results for each hunk within a patch operation.
- Batch
Result - The result of applying a batch of patches to a directory.
- Default
Hunk Finder - The default, built-in strategy for finding hunk locations.
- Hunk
- Represents a single hunk of changes within a patch.
- Hunk
Applier - An iterator that applies hunks from a patch one by one.
- Hunk
Failure - Details about a hunk that failed to apply.
- Hunk
Location - Represents the location where a hunk should be applied.
- InMemory
Result - The result of an in-memory patch operation.
- Patch
- Represents all the changes to be applied to a single file.
- Patch
Result - The result of an
apply_patch_to_file()operation.
Enums§
- Hunk
Apply Error - The reason a hunk failed to apply.
- Hunk
Apply Status - The result of applying a single hunk.
- Match
Type - Describes the method used to successfully locate and apply a hunk.
- OneShot
Error - Represents errors that can occur during the high-level
patch_content_str()operation. - Parse
Error - Represents errors that can occur during the parsing of a diff file.
- Patch
Error - Represents “hard” errors that can occur during patch operations.
- Patch
Format - Identifies the syntactic format of a patch content string.
- Single
Parse Error - Represents errors that can occur when parsing a diff expected to contain exactly one patch.
- Strict
Apply Error - Represents errors that can occur during “strict” apply operations.
Traits§
- Hunk
Finder - A trait for strategies that find the location to apply a hunk.
Functions§
- apply_
hunk_ to_ lines - Applies a single hunk to a mutable vector of lines in-place.
- apply_
patch_ to_ content - Applies the logic of a patch to a string content.
- apply_
patch_ to_ file - A convenience function that applies a single
Patchto the filesystem. - apply_
patch_ to_ lines - Applies the logic of a patch to a slice of lines.
- apply_
patches_ to_ dir - A convenience function that applies a slice of
Patchobjects to a target directory. - detect_
patch - Automatically detects the patch format of the provided content.
- ensure_
path_ is_ safe - Ensures a relative path, when joined to a base directory, resolves to a location that is still inside that base directory.
- find_
hunk_ location - Finds the location to apply a hunk to a given text content without modifying it.
- find_
hunk_ location_ in_ lines - Finds the location to apply a hunk to a slice of lines without modifying it.
- parse_
auto - Automatically detects the format of the input text and parses it into a list of patches.
- parse_
conflict_ markers - Parses a string containing “Conflict Marker” style diffs (<<<<, ====, >>>>).
- parse_
diffs - Parses a string containing one or more markdown diff blocks into a vector of
Patchobjects. - parse_
patches - Parses a string containing raw unified diff content into a vector of
Patchobjects. - parse_
patches_ from_ lines - Parses an iterator of lines containing raw unified diff content into a vector of
Patchobjects. - parse_
single_ patch - Parses a string containing a diff and returns a single
Patchobject. - patch_
content_ str - A high-level, one-shot function to parse a diff and apply it to a string.
- try_
apply_ patch_ to_ content - A strict variant of
apply_patch_to_content()that treats partial applications as an error. - try_
apply_ patch_ to_ file - A strict variant of
apply_patch_to_file()that treats partial applications as an error. - try_
apply_ patch_ to_ lines - A strict variant of
apply_patch_to_lines()that treats partial applications as an error.