Skip to content

Releases: Romelium/mpatch

v1.8.1

Choose a tag to compare

@github-actions github-actions released this 24 Oct 09:23

Fixed

  • Input Without Patches: Warn when the input contains no patch, such as a chat reply without a diff or a hunk copied without its --- / +++ file headers. Previously the CLI printed nothing unless -v was given. The exit status is still 0.
  • Unchanged Files: Don't rewrite a file when none of its hunks apply or all of them are already applied. The file's modification time no longer changes, and the log no longer says partial changes were written.

Performance

  • Fuzzy Search Without a Match: Skip the character-level diff at a candidate location when the characters it shares with the hunk can't reach the fuzz threshold. Searching a 10,000-line file for a 3-line hunk that isn't there now takes about 0.36 s instead of 1.7 s. Hunks that match land in the same place as before; when no location reaches the threshold, the best score in FuzzyMatchBelowThreshold can be lower than before.

🆚 Compare changes

v1.8.0

Choose a tag to compare

@github-actions github-actions released this 24 Oct 06:11

Added

  • Patch Target Paths: Resolve targets the way patch -p1 does for directory-tree diffs (diff -Naur pkg.orig pkg, including created and deleted files) and for backup-file diffs (diff -u file.orig file). When a target is missing but exists without its leading directory, patch that file and print a warning.
  • Already-Applied Hunks: Skip a hunk with a warning when it can only be placed by fuzzy matching and its result is already in the file, so re-applying a patch no longer duplicates its changes. Hunks whose context still matches exactly always apply, so intentional duplicates keep working. Hunks that only delete lines, and hunks whose context still matches exactly after they were applied (such as appends at the end of a list), are not detected.
  • Git Executable Mode: Apply Git mode changes between 100644 and 100755 on Unix after the content is written, including created files (new file mode 100755), mode-only patches, -R, and --dry-run. A mode change for a file that neither exists nor is created by the patch fails before anything is written, and --atomic skips modes when the batch is discarded. Library callers can read the changes with parse_git_mode_changes().

Fixed

  • Unified Diff Parsing: Recognize diff -Naur headers, strip timestamps from --- / +++ file headers (including timestamps whose tab separator was expanded to spaces), decode Git C-quoted paths, and distinguish file headers from hunk content that begins with --- or +++.
  • Line Endings: Preserve CRLF and mixed line endings when patching a file. Previously the whole file was rewritten with LF endings; now unchanged lines keep their own ending and added lines use the file's most common one.
  • Final Newline: Keep a file's final newline unless the patch changes its last line. Previously any hunk that reached the end of the file applied the patch's \ No newline at end of file state, so a hunk ending in unchanged context could add or remove the final newline. Now a line kept from the file keeps its own newline and a line added by the patch follows the patch's marker, matching GNU patch.
  • CLI Failure Reporting: Exit with an error instead of succeeding silently when the input contains context diffs (diff -c), Git renames or copies, Git binary patches, or symlink and submodule modes. Warn when a Git executable-mode change is not applied.

🆚 Compare changes

v1.7.0

Choose a tag to compare

@github-actions github-actions released this 12 Oct 14:59

Overview

  1. Format-Agnostic LLM Ingestion & Wildcards: Native parsing for Aider search/replace blocks with automatic path inference and multi-line wildcard ellipsis (...) matching that safely bridges omitted code gaps.
  2. Transaction-Grade Atomicity: All-or-nothing filesystem guarantees via -a / --atomic across CLI, Rust API, and Python bindings, staging edits in-memory and rolling back cleanly on any failure.
  3. Three-Way Merging & Sub-Line Diagnostics: Built-in 3-way line merges with Diff3 conflict markers (merge_three_way), sub-line word-level diff visualization (format_inline_diff), and fuzzy file path suggestions (suggest_close_file_paths) for missing targets.
  4. Unix & Stdin Composability: First-class standard input piping (git diff | mpatch - ./src), implicit piping, and automated fallback to the current directory (.).
  5. $O(N + M)$ Scalability & Performance: Single-pass inverted index anchor discovery, Histogram-based sequence comparisons, incumbent-seeded upper-bound diff pruning, zero-allocation window length search, and zero-allocation precomputations eliminating quadratic bottlenecks on files with >10,000 lines.
  6. Anti-Hallucination & Scope Protection: Strict boundary validation including orphan addition guards, definition-aware head/tail context preservation, primary identifier and definition consistency across C-family, JVM, scripting, Lisp, and SQL syntax, and topological anchor relaxation for unnumbered hunks.

Added

Parser & Formats

  • Aider Search/Replace Blocks: Added native support for Aider search/replace blocks (<<<<<<< SEARCH, =======, >>>>>>> REPLACE along with ORIGINAL and UPDATED syntax variants) via mpatch::parse_aider, mpatch::parse_aider_from_lines, and PatchFormat::Aider. Fences are matched by indentation, so indented fences inside replacement content (such as nested code examples) do not end a block early.
  • Automatic Format Detection: Updated detect_patch, parse_auto, and parse_diffs to recognize and parse Aider search/replace blocks alongside unified diffs, markdown code fences, and conflict markers.
  • Deduplicated Patch Merging: Added mpatch::merge_patches and Patch::merge to consolidate multiple patch blocks targeting the same file path into single Patch objects with combined hunks in linear $O(N)$ time.
  • Heuristic Target File Path Detection: Implemented extract_file_path_from_line, is_plausible_file_path, and normalize_candidate_path to infer target file paths for Aider search/replace blocks from preceding markdown text, headings, code comments (// filepath:), backticks, and search fence headers. A path named in a heading stays in scope across long explanations, illustrative non-patch code blocks, and consecutive patch blocks until a #/## heading without a path or a thematic break (---, ***) starts a new section. Conflict marker blocks still default to patch_target.
  • File Path Plausibility Rules: is_plausible_file_path accepts quoted or otherwise delimited paths, paths containing spaces or Unicode, route templates ({id}), extensions such as .c++, extensionless build files (BUILD, WORKSPACE, configure, VERSION), and paths up to 4,096 bytes, while rejecting URLs, code syntax, and conversational sentences.
  • Wildcard & Ellipsis Matching: Added support for wildcard ellipsis lines (..., …, // ... existing code ..., <!-- ... -->, # ... rest of function ..., Fortran ! ..., Batch :: ..., etc.) across single and multi-segment hunks via is_ellipsis_line. Multi-line code gaps between anchors are accurately preserved while strictly guarding against runaway gaps or syntax false positives. Segments may end on low-entropy anchors such as }, end, or pass; a wildcard hunk is rejected only when its leading segment or every segment is low-entropy.

Core API

  • Patch Methods: Added Patch::apply_to_file and Patch::apply_to_content as method forms of apply_patch_to_file and apply_patch_to_content.
  • Strict Batch Application: Added try_apply_patches_to_dir, a non-atomic strict variant of apply_patches_to_dir that returns StrictBatchApplyError::Failed if any patch has a hard error or any hunk fails.
  • Candidate Inspection: Added DefaultHunkFinder::find_candidate_locations to list every candidate location for a hunk in order of preference, and Hunk::required_match_span to compute the minimum window span that can contain all of a hunk's edits.
  • Comment Line Detection: Added mpatch::is_comment_line to classify pure comment, docstring, and banner lines across C-family, scripting, markup, Lisp, SQL, Fortran, and Batch syntaxes, while treating pointer dereferences, preprocessor directives, attributes, pre-decrements, and CSS selectors as code.
  • Zero-Allocation Window Length Iterator: Added mpatch::WindowLengthIter and mpatch::window_lengths implementing Iterator, ExactSizeIterator, and FusedIterator to generate candidate window lengths radiating outward from nominal length without heap allocations.

Transactional Atomicity

  • Core Atomic APIs: Added atomic filesystem application via apply_patches_to_dir_atomic, try_apply_patches_to_dir_atomic, apply_patch_to_file_atomic, try_apply_patch_to_file_atomic, and Patch::apply_to_file_atomic. File operations are staged in-memory and committed to disk if and only if all hunks across all patches apply cleanly, with automatic rollback of written files upon commit failure.
  • Batch Outcome Inspection: Added BatchResult::all_applied_cleanly(), BatchResult::has_failures(), and StrictBatchApplyError for granular programmatic inspection of batch patch outcomes.
  • CLI Atomic Flag: Introduced the -a / --atomic (aliased as --all-or-nothing) command-line flag to ensure that changes are only written to disk if every patch and hunk succeeds.

Three-Way Merging & Diagnostics

  • 3-Way Line Merge: Added mpatch::merge_three_way backed by similar::TextMerge to perform 3-way line merges between an ancestor (base), local code (ours), and incoming changes (theirs), with configurable Diff3 conflict marker labels.
  • Inline Word-Level Diffs: Added mpatch::format_inline_diff powered by similar's inline feature to format sub-line word additions and deletions with colored terminal highlights.
  • CLI Near-Miss Diagnostics: Failed hunk diagnostics in the CLI now render sub-line near-miss comparisons with highlighted word differences when encountering FuzzyMatchBelowThreshold errors.
  • CLI Path Suggestions: Added mpatch::suggest_close_file_paths powered by similar::get_close_matches to suggest close matching file paths in the target directory when a patch specifies a missing target (TargetNotFound).

CLI & Unix Composability

  • Standard Input (Stdin) Piping: Added first-class support for piping patches via - (e.g., cat patch.diff | mpatch - ./src, git diff | mpatch -R - ./src, curl -s https://example.com/fix.patch | mpatch - ./src), including implicit piping and automatic fallback to the current directory (.) when the target directory argument is omitted.

Python Bindings (mpatch)

  • Aider Parsing: Exposed mpatch.parse_aider and updated detect_patch and type stubs (mpatch.pyi) for Aider search/replace blocks.
  • Atomic Operations: Added the atomic: bool = False keyword parameter to apply_directory, apply_patches_to_dir, apply_patch_to_file, and Patch.apply_to_file, alongside dedicated apply_patch_to_file_atomic, apply_patches_to_dir_atomic, and Patch.apply_to_file_atomic functions.
  • Batch Outcome Inspection: Added the BatchResult.all_applied_cleanly and BatchResult.has_failures properties, and made BatchResult path lookups (result[path], path in result) accept either / or \ separators.
  • Deduplicated Patch Merging: Exposed mpatch.merge_patches and Patch.merge.
  • Merge & Inline Functions: Exposed mpatch.merge_three_way and mpatch.format_inline_diff with comprehensive type annotations (mpatch.pyi).
  • API Expansion: Exposed mpatch.parse_single_patch, mpatch.suggest_close_file_paths, mpatch.find_hunk_location, Hunk.find_location, Hunk.required_match_span, and mpatch.ensure_path_is_safe.
  • Path & Wildcard Inspection Utilities: Exposed mpatch.is_ellipsis_line, mpatch.is_plausible_file_path, and mpatch.extract_file_path_from_line.
  • Near-Miss Diagnostics: Added HunkFailure.location_start and HunkFailure.location_length to inspect rejected fuzzy near-miss match coordinates.

Tooling & Release Automation

  • Release Automation: Added scripts/release.py featuring interactive pre-flight toolchain diagnostics, automated testing across Rust and Python, lockfile synchronization, atomic git commit/tagging, and optional Crates.io publishing.
  • Benchmark Suites: Added Criterion benchmarks covering Aider format detection and parsing, interleaved patch merging, a 200-function 3-way merge, 100-line inline diff rendering, Patience diff patch generation, and radiating window length iteration (benches/mpatch_bench.rs).

Changed

  • Diff Engine & Dependencies: Upgraded similar from 2.7.0 to 3.2.0 with the inline feature enabled.
  • Error Handling & Styling Dependencies: Upgraded thiserror from 1.0.69 to 2.0.21 and colored from 2.2.0 to 3.1.1.
  • Patch Generation Algorithm: Switched Patch::from_texts and internal search/replace hunk alignment to similar::Algorithm::Patience for cleaner, semantically aligned hunk boundaries across refactored functions and moved blocks.
  • Same-File Patch Consolidation: parse_diffs, and therefore parse_auto and the CLI on Markdown input, now merges patches that target the same file across separate code blocks into a single Patch with combined hunks. Previously each code block produced its own Patch.
  • **F...
Read more

v1.6.4

Choose a tag to compare

@github-actions github-actions released this 02 Jun 11:53

Added

  • Python Bindings: Introduced official Python bindings via PyO3. The mpatch package is now available on PyPI!
    • Exposes a rich, object-oriented, and fully typed API (with .pyi stubs) for tool builders and AI agent frameworks.
    • Provides high-level convenience functions (patch_content, apply_directory) and granular control over patches and hunks.
    • Includes pre-compiled wheels for Linux, macOS, and Windows, with support for Python 3.8+ and Python 3.13 free-threaded builds.
    • Implements Pythonic paradigms like slicing, unary inversion (~patch), and native exceptions (ParseError, PathTraversalError).

🆚 Compare changes

v1.5.0

Choose a tag to compare

@github-actions github-actions released this 28 May 03:10

Added

  • CLI: Added -c / --clipboard flag to input directly from the system clipboard.

Changed

  • Logging: Improved debug and trace logging. Reduced log spam during parsing, elevated key file I/O operations to debug level, and added detailed trace logs to the smart indentation algorithm (using escape_debug to visualize tabs vs. spaces). Also added visibility into parse_auto format detection and fallback behavior.
  • Diagnostics: Significantly enhanced the anonymization of debug reports (-vvvv). The report generator now actively redacts the input file path, target directory, current working directory, and user home directory from the entire report, including the full trace log, file contents, and error messages.
  • Diagnostics: The discrepancy check failure output in the debug report (-vvvv) now includes a unified diff between the original input patch and the regenerated patch to make differences easier to spot. The full patch contents are now placed inside a collapsible <details> block to reduce visual clutter.
  • Diagnostics: Improved the discrepancy check to normalize patches before comparison. It now ignores context lines, hunk headers, +/- interleaving, and self-replacements, significantly reducing noise and eliminating false positives caused by structural differences.

Security

  • Path Validation: Fixed a vulnerability in ensure_path_is_safe where a dangling symlink could bypass the path traversal check and allow arbitrary file creation outside the target directory. The validation now correctly detects dangling symlinks using symlink_metadata.

Fixed

  • CLI: Fixed a critical bug where --dry-run would incorrectly create parent directories on the filesystem. The path safety check now performs symlink resolution without requiring intermediate directory creation.
  • CLI/API: Fixed an issue where the preview diff generated during a dry run (PatchResult::diff) contained hardcoded "a" and "b" file headers instead of the actual target file paths.
  • Patch Application: Fixed a bug where the trailing newline status could be handled incorrectly if a patch contained out-of-order hunks that modified the end of the file before modifying earlier lines.

🆚 Compare changes

v1.4.4

Choose a tag to compare

@github-actions github-actions released this 02 May 02:53

Security

  • Path Validation: Fixed a directory traversal vulnerability in ensure_path_is_safe where parent directories of a patch target were created on the filesystem before the path was validated. The function now performs a lexical path validation to ensure the path does not escape the base directory before interacting with the filesystem, preventing arbitrary directory creation.

Fixed

  • Patch Application: Fixed a bug in the smart indentation adjustment where empty lines containing trailing whitespace were incorrectly used to calculate indentation drift. The logic now strictly requires both the patch line and the target line to contain non-whitespace characters before updating the indentation context.

🆚 Compare changes

v1.4.3

Choose a tag to compare

@github-actions github-actions released this 01 May 14:24

Fixed

  • Patch Application: Fixed a critical bug in the fuzzy matching reconstruction logic where differing indentation levels between the patch and the target file could cause the internal alignment to scramble (often anchoring incorrectly on empty lines). The alignment is now based on trimmed lines, ensuring semantic correctness while still dynamically adjusting indentation during application.

🆚 Compare changes

v1.4.2

Choose a tag to compare

@github-actions github-actions released this 30 Apr 23:46

Fixed

  • Patch Application: Fixed smart indentation adjustment to correctly translate multiple levels of indentation between spaces and tabs, making it robust against nested patches (e.g., inside markdown lists) by defaulting to 4 spaces per tab if the ratio is skewed. Also fixed an issue where empty lines (or lines with only whitespace) would result in trailing whitespace being added.
  • Patch Application: Fixed a bug where the auto-indentation logic would lose track of the target file's indentation style (e.g., tabs vs. spaces) when encountering unindented lines within the patch context.
  • Patch Application: Fixed a bug in the fallback logic for adjusting indentation of lines that are outdented relative to the hunk's context. The logic now correctly strips the indentation difference from the end of the line's actual indentation.
  • CLI: Fixed a bug in the debug report's discrepancy check (-vvvv) where patches that created or deleted files were skipped. The check now correctly treats non-existent files as empty strings, allowing verification to proceed.
  • CLI: Fixed a false positive in the debug report's discrepancy check (-vvvv) where patches with out-of-order hunks were incorrectly flagged as failures. The check now correctly verifies hunks regardless of their order.

🆚 Compare changes

v1.4.1

Choose a tag to compare

@github-actions github-actions released this 04 Apr 00:12

Fixed

  • Parser: Fixed a bug where empty lines between file diffs (common in LLM outputs) were incorrectly absorbed as trailing context lines, breaking file creation and deletion detection.
  • API: Made Patch::is_creation() and Patch::is_deletion() more robust by checking hunk header line numbers (e.g., @@ -0,0 ...).
  • Fuzzy Matching: Fixed a bug in the search optimization heuristic where indentation differences between the patch and the target file would cause the fuzzy search to fail or look in the wrong location. The anchor line search now correctly ignores leading whitespace.

🆚 Compare changes

v1.4.0

Choose a tag to compare

@github-actions github-actions released this 20 Mar 04:21

Performance

  • Patch Creation: Optimized Patch::from_texts to construct hunks directly from diff operations, avoiding the overhead of generating and re-parsing a unified diff string.
  • Formatting: Optimized Hunk::fmt to calculate line counts in a single pass.

Fixed

  • Patch Application: Fixed a bug where a trailing newline was incorrectly added to files lacking one, even if the patch only modified lines at the beginning or middle of the file. The original newline status is now preserved unless the patch explicitly modifies the end of the file.
  • Patch Application: Fixed a bug where files resulting in exactly one newline (e.g., replacing all content with a single empty line) were incorrectly truncated to 0 bytes.
  • Parser: Fixed handling of the \ No newline at end of file marker. It now correctly verifies that the marker immediately follows a context or addition line.
  • Parser: Fixed parsing of empty hunks (e.g., @@ -0,0 +0,0 @@) which were previously ignored.
  • Parser: Fixed a bug where a context line in a diff that looks like a closing fence (e.g., ```) would incorrectly terminate the markdown code block. The parser now enforces that a closing fence must not be more indented than the opening fence.
  • Parser: Improved Conflict Marker detection to prevent false positives from Markdown H1 headers (lines of ====). Detection now strictly requires a start marker (<<<<) followed by a middle (====) or end (>>>>) marker.
  • CLI: Fixed formatting in the debug report (-vvvv) where final summary logs were appended outside the markdown code block, breaking the report structure.

Added

  • CLI: Added -R / --reverse flag to reverse patches before applying them (useful for undoing changes).
  • API: Added mpatch::invert_patches helper function to programmatically invert a list of patches.
  • API: Added HunkApplier::set_original_newline_status to manually control the expected newline behavior when using the line-based API.
  • API: Derived PartialEq for ApplyOptions.

Changed

  • Patch Application: Files are now automatically deleted if the patch application results in empty content.
  • Internal: Refactored debug report finalization to be more robust and prevent redundant file locking.

🆚 Compare changes