Metadata-Version: 2.4
Name: mpatch
Version: 1.6.4
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing
Requires-Dist: pytest>=7.0 ; extra == 'test'
Provides-Extra: test
Summary: A smart, context-aware patch tool that applies diffs using fuzzy matching, ideal for AI-generated code.
Author-email: Romelium <author@romelium.com>
License: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Bug Tracker, https://github.com/romelium/mpatch/issues
Project-URL: Homepage, https://github.com/romelium/mpatch
Project-URL: Repository, https://github.com/romelium/mpatch

# Mpatch (Python)

[![PyPI version](https://img.shields.io/pypi/v/mpatch.svg?style=flat-square&logo=pypi&logoColor=white)](https://pypi.org/project/mpatch/)
[![Python versions](https://img.shields.io/pypi/pyversions/mpatch.svg?style=flat-square&logo=python&logoColor=white)](https://pypi.org/project/mpatch/)
[![Type Hints](https://img.shields.io/badge/Typing-Strict-success.svg?style=flat-square)](https://pypi.org/project/mpatch/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT)

**`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 `mpatch` repository and CLI documentation](https://github.com/romelium/mpatch).

---

## 💡 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` |
| :--- | :--- | :--- | :--- |
| <pre>def main():<br>    # Updated comment<br>    print("Hello")</pre> | <pre> def main():<br>-    print("Hello")<br>+    print("World")</pre> | <pre>❌ Failed<br>Hunk #1 FAILED at 1.<br>1 out of 1 hunk FAILED</pre> | <pre>def main():<br>    # Updated comment<br>    print("World")</pre> |

## ✨ 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 (`~patch` to invert), rich error handling, and ships with comprehensive `.pyi` stubs.

## 📦 Installation

Install directly from PyPI (pre-compiled wheels are provided for macOS, Linux, and Windows):

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

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

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

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

```python
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`:

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

```python
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).

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

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

```python
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`).

```python
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](https://opensource.org/licenses/MIT).
