mpatch: LLM이 생성한 코드 변경 사항을 적용하는 컨텍스트 기반 패치 라이브러리
요약
mpatch는 LLM이 생성한 코드 변경 사항(diff)을 적용하기 위해 설계된 컨텍스트 기반 패치 라이브러리입니다. 기존의 표준 patch 도구와 달리, 정확한 바이트 단위 일치를 요구하지 않고 퍼지 매칭을 활용하여 코드가 구조적으로 유지되는 한 유연하게 패치를 적용합니다.
핵심 포인트
- LLM이 생성하는 diff를 쉽게 적용할 수 있도록 설계됨.
- 퍼지 매칭(fuzzy matching)을 사용하여 컨텍스트 기반으로 패치 적용.
- 마크다운, Unified Diff 등 다양한 형식의 diff를 자동으로 인식함.
- 원자적 모드(Atomic mode)로 파일 시스템 손상을 방지하여 안정성을 높임.
mpatch는 현대 소프트웨어 개발의 '복잡한' 환경에 맞춰진 컨텍스트 기반 패치 프로그램/라이브러리입니다. 라인 번호와 정확한 바이트 단위 일치를 요구하는 표준 patch나 git apply와 달리, mpatch는 코드 변경 주변의 컨텍스트를 바탕으로 **퍼지 매칭(fuzzy matching)**을 활용하여 패치를 찾습니다.
이 도구는 **LLM (ChatGPT, Gemini, Claude, Copilot)**이 생성하는 diff를 쉽게 적용하기 위해 만들어졌는데, LLM은 정확한 라인 번호나 패치 주변의 컨텍스트를 환각(hallucinate)하는 경향이 있기 때문입니다.
🐍 Python 개발자이신가요? 공식 Python 바인딩을 제공합니다! mpatch PyPI 패키지와 Python 문서를 확인해 보세요.
AI에게 코드 수정을 요청했다고 가정해 봅시다. diff를 받았습니다. 그런데 이 코드 블록 안의 주석이 누락되었습니다.
표준 patch: 작동하지 않습니다. 컨텍스트가 정확한 바이트 단위 일치가 아닙니다.
작동합니다! 코드가 구조적으로 변경되지 않았다는 것을 감지하고 패치를 적용합니다.
mpatch
:| 원본 파일 (로컬 수정) | AI 생성 패치 (오래된 컨텍스트) | patch 결과 | mpatch 결과 |
|---|---|---|---|
| fn main() { | fn main() { | ❌ 실패 | fn main() { |
mpatch는 입력 형식을 자동으로 감지합니다. 플래그가 필요하지 않습니다.
마크다운 코드 블록: 언어 모델의 기본 출력 형식입니다. diff 헤더가 있는 경우 diff, rust 또는 기타 코드 블록을 사용합니다.통합 Diff (Unified Diff): 기본 git diff 또는 diff -u 형식을 사용합니다.Aider 검색/대체 블록: <<<<<<< SEARCH (또는 ORIGINAL), =======, 그리고 >>>>>>> REPLACE (또는 UPDATED)를 사용한 검색 및 대체 블록입니다. 파일 경로는 블록 헤더, 마크다운 텍스트 또는 코드 주석에서 자동으로 추출됩니다. 중간 코드를 보존하기 위해 와일드카드 엘립시스 매칭 (...)을 지원합니다.충돌 마커 (Conflict Markers): <<<<, ====, 그리고 >>>>를 사용한 Git과 유사한 충돌 마커입니다. 파일 시스템의 patch_target으로 기본 설정되거나, 메모리 내에서 문자열 패치를 직접 수행합니다.
🧠 퍼지 매칭 (Fuzzy Matching): 정확히 일치하는 위치를 찾지 못했을 경우, 패치를 적용할 가장 적합한 위치를 찾기 위해 유사성 알고리즘을 실행합니다. 이 기능은 오래된 컨텍스트(stale context), 공백 변경(whitespace changes), 그리고 사소한 코드 차이까지 지원합니다.🔒 원자적 (Atomic) 모드: patch --atomic 또는 --all-or-nothing 을 사용하면 모든 패치와 덩어리(hunk)가 깨끗하게 성공했을 때만 변경 사항이 디스크에 기록되도록 보장합니다. 만약 어느 한 개의 덩어리가 실패하면, 파일 시스템은 완전히 손상되지 않은 상태를 유지합니다.🤖 형식 독립적 (Format Independent): 다음을 자동으로 인식하고 처리합니다:마크다운(Markdown) diff 코드 블록 (표준 채팅 출력 형식).통합 Diff (Unified Diff) (git diff 또는 diff -u 명령어의 출력).Aider 검색/대체 블록 (Aider 및 LLM 코딩 어시스턴트에 의해 대중화됨).충돌 마커 (Conflict Markers) (<<<<, ====, >>>>).🔍 와일드카드 / 줄임표 매칭 (Wildcard / Ellipsis Matching): 검색/대체 블록에서 ..., … 및 주석으로 감싼 줄임표 라인 (// ... existing code ..., <!-- ... --> 등)을 지원하여, 건드리지 않은 코드 간격은 보존하면서도 함수 전반에 걸친 무분별한 간격을 엄격하게 방지합니다.🔀 3방향 라인 병합 (Three-Way Line Merge): 표준 Diff3 충돌 마커를 사용하는 내장 라벨 레벨 3방향 병합 엔진 (merge_three_way)을 제공합니다.💡 서브라인 및 근접 오류 진단 (Sub-Line & Near-Miss Diagnostics): 실패한 덩어리에 대한 인라인 단어 수준 Diff 시각화 (format_inline_diff)와 대상 파일이 누락되었을 때의 퍼지 파일 경로 제안 (suggest_close_file_paths) 기능을 제공합니다.📥 표준 입력 (Standard Input, Stdin): - 을 사용하여 git diff, cat, 또는 curl로부터 Diff를 직접 파이프합니다 (예: git diff | mpatch - ./src 또는 cat patch.diff | mpatch -).📋 클립보드 지원 (Clipboard Support): -c 또는 --clipboard 를 사용하여 클립보드에서 직접 입력받습니다.
✨ 스마트 들여쓰기(Smarter Indentation): 추가된 줄을 대상 파일의 들여쓰기와 일관되도록 자동으로 들여씁니다. 탭/공백을 동적으로 변환하고 대상 파일의 들여쓰기 스타일을 유지합니다.🗑️ 파일 삭제(File Deletion): 패치 적용 후 결과가 비어 있게 되면 대상 파일을 자동으로 제거합니다.🛡️ 보안성(Secure): 경로 탐색(Path traversal)이 자동으로 방지되어, 어떠한 패치 파일도 대상 디렉터리 외부의 파일에 접근하거나 덮어쓰는 것을 막습니다.⚡ 빠르고 동시적(Fast & Concurrent): rayon을 통해 모든 CPU 코어에 걸쳐 퍼지 매칭(fuzzy matching)을 병렬화하고, 히스토그램 기반 단어 차이 비교(histogram-based word diffing), 제로 할당 윈도우 길이 반복(zero-allocation window length iteration), 수학적 상한 경계 가지치기(mathematical upper-bound pruning)를 사용하여 빠른 검색을 수행합니다.🔍 건식 실행(Dry Run): --dry-run을 사용하여 파일을 수정하지 않고 도구가 무엇을 할지 미리 볼 수 있습니다.
Windows, macOS, Linux용 사전 컴파일된 바이너리를 제공합니다.
cargo-binstall 사용 (가장 빠름):
cargo binstall mpatch
수동 다운로드:
- **릴리스 페이지(Releases Page)**로 이동합니다. - 아키텍처에 맞는 압축 파일을 다운로드합니다 (아래 표 참조). - 압축을 풀고
PATH에 추가합니다.
| 플랫폼 | 아키텍처 | 대상 | 참고 사항 |
|---|---|---|---|
| macOS | Universal | universal-apple-darwin | Mac에 최적. M1/M2/M3 및 Intel에서 네이티브로 실행됩니다. |
| x64 | x86_64-apple-darwin | 구형 Intel Mac용. | |
| ARM64 | aarch64-apple-darwin | Apple Silicon (M1/M2/M3). | |
| Windows | x64 | x86_64-pc-windows-msvc | 표준 64비트 Windows용. |
| ARM64 | aarch64-pc-windows-msvc | Surface Pro X, Parallels용. | |
| Linux | x64 | x86_64-unknown-linux-gnu | Ubuntu, Debian, Fedora 등. |
| x64 (Static) | x86_64-unknown-linux-musl | Alpine Linux, Docker 컨테이너용. | |
| ARM64 | aarch64-unknown-linux-gnu | Raspberry Pi 4/5, AWS Graviton용. | |
| ARM64 (Static) | aarch64-unknown-linux-musl | ARM64용 Alpine Linux. | |
| ARMv7 | armv7-unknown-linux-gnueabihf | 구형 Raspberry Pi (2/3), IoT용. | |
| ARMv7 (Static) | armv7-unknown-linux-musleabihf | ARMv7용 정적 바이너리. |
릴리스된 모든 바이너리의 무결성은 GPG로 검증됩니다. .sig
공개 키(public.key)와 서명 파일은 다운로드한 파일의 진위성을 확인하는 데 도움이 됩니다.
-
공개 키 가져오기:
gpg --import public.key -
아카이브 확인:
# 예시 (Linux x64): gpg --verify mpatch-x86_64-unknown-linux-gnu-v1.6.4.tar.gz.sig mpatch-x86_64-unknown-linux-gnu-v1.6.4.tar.gz
cargo install mpatch
패치 파일(Markdown, Diff 또는 Conflict 마커)을 대상 디렉터리(생략 시 기본값은 현재 디렉터리 .)
에 적용합니다.
mpatch changes.md ./src
# 대상 디렉터리는 생략 시 '.'로 기본 설정됩니다:
mpatch changes.md
모든 패치와 덩어리(hunks)가 깨끗하게 적용될 경우에만 디스크의 파일을 수정합니다. 만약 어떤 덩어리가 실패하면, 디스크상의 파일은 전혀 건드려지지 않습니다.
mpatch -a changes.md ./src
diff를 git diff, cat, 또는 curl로부터 직접 파이프합니다.
# git diff에서 파이프:
git diff | mpatch - ./src
# 파이프된 패치 되돌리기
...
클립보드에 복사한 패치를 대상 디렉터리에 직접 적용합니다.
mpatch -c ./src
파일을 수정하지 않고 정확히 어떤 일이 일어날지 확인합니다.
mpatch --dry-run changes.md ./src
만약 mpatch가 잘못된 곳과 일치하는 경우, 엄격도를 높입니다(기본값은 0.7). 일치를 찾지 못하는 경우, 낮춥니다.
# 더 엄격하게 (90% 유사도 필요)
mpatch --fuzz-factor 0.9 changes.md ./src
# 퍼지 매칭 비활성화 (정확한 일치만)
...
이전에 적용된 패치를 되돌립니다(추가와 삭제를 교환).
mpatch -R changes.md ./src
패치가 실패하면, 분석할 수 있도록 포괄적인 디버그 보고서(파일 상태, 로그 및 diff 포함)를 생성합니다.
mpatch -vvvv changes.md ./src
# 다음을 생성합니다: mpatch-debug-report-[타임스탬프].md
mpatch는 AI 코딩 에이전트 및 도구의 패치 엔진으로 설계되었습니다. 강력한 Rust API를 노출합니다.
Cargo.toml에 추가하기
:
[dependencies]
mpatch =
mpatch 라이브러리 사용법 예시:
`use mpatch::{patch_content_str, ApplyOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let original_code = "fn main() { println!("Old"); }\n";
...
```diff
-- a/main.rs
+++ b/main.rs
@@ -1 +1 @@
-fn main() { println!("Old"); }
+fn main() { println!("New"); }
"#;
let options = ApplyOptions::new(); // 기본 fuzz_factor: 0.7
let new_code = patch_content_str(patch_text, Some(original_code), &options)?;
...
다중 파일을 대상으로 하는 패치를 적용하는 데 이상적입니다.
`use mpatch::{parse_auto, apply_patches_to_dir, ApplyOptions};
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let diff_content = r#"```diff
--- a/file1.txt
+++ b/file1.txt
@@ -1 +1 @@
-foo
+bar
--- a/file2.txt
+++ b/file2.txt
@@ -1 +1 @@
-baz
+qux
"#;
// 1. Parse (자동으로 형식을 감지합니다)
let patches = parse_auto(diff_content)?;
...
패치를 프로그래밍 방식으로 반전시킵니다 (추가된 부분이 삭제되고 그 반대도 마찬가지입니다).
`use mpatch::{invert_patches, parse_auto};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let diff_content = "--- a/file\n+++ b/file\n@@ -1 +1 @@\n-old\n+new\n";
...
여러 패치 섹션이나 동일한 파일을 대상으로 하는 블록을 통합하여 단일 Patch 객체로 만듭니다:
use mpatch::{merge_patches, parse_auto};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let diff1 = "--- a/file.txt\n+++ b/file.txt\n@@ -1 +1 @@\n-old1\n+new1\n";
...
일부 적용만 하는 경우(일부 hunk가 실패하는 경우)를 오류로 처리하려면 try_ 변형을 사용하세요.
use mpatch::{parse_single_patch, try_apply_patch_to_content, ApplyOptions, StrictApplyError};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let original_content = "fn main() { println!("Old"); }\n";
...
또한 mpatch를 사용하여 두 문자열을 비교하여 패치를 생성할 수도 있습니다.
use mpatch::Patch;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let old_text = "fn main() { println!("Old"); }";
...
모든 변경 사항을 메모리에서 스테이징하고, 모든 패치에 걸친 덩어리(hunks)가 깨끗하게 적용될 경우에만 디스크에 커밋합니다:
use mpatch::{parse_auto, apply_patches_to_dir_atomic, ApplyOptions};
use std::path::Path;
fn main() -> Result<(), Box<dyn std::error::Error>> {
...
Aider 검색/대체 블록을 사용하여 와일드카드 엘립시스 라인(...)을 포함하며 일치하고 다중 라인 코드 공백을 보존하는 부분을 적용합니다:
use mpatch::{patch_content_str, ApplyOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let original = "def compute(x):\n setup()\n res = x * 2\n teardown()\n return res\n";
...
similar::TextMerge를 기반으로 라인별 3-way 병합을 수행하고, 충돌 발생 시 자동으로 Diff3 충돌 마커를 생성합니다:
use mpatch::merge_three_way;
fn main() {
let base = "Apples\nBananas\nCherries\nDates\n";
...
단어 단위 하이라이트를 통해 서브 라인 변경 사항을 시각화하고, 대상 파일을 찾지 못했을 때 유사한 파일 경로를 제안합니다:
use mpatch::{format_inline_diff, suggest_close_file_paths};
use std::path::Path;
// 하이라이트된 서브 라인 단어 추가 및 삭제
...
mpatch에서 지원하지만, 이 파일 형식(<<<<, ====, >>>>)은 파일 경로 정보를 포함하지 않습니다.
사용법: 제공된 파일에 충돌 마커만 포함되어 있는 경우, CLI의 mpatch는 patch_target이라는 파일을 패치하려고 시도합니다.
사용법: 이 형식은 대상 파일 내용이 메모리에 저장되어 있을 때 라이브러리로서의 mpatch::patch_content_str 함수를 통해 사용할 수 있습니다.
여러 파일 패치를 하거나 CLI에서 mpatch를 사용하는 경우, 통합 Diff(Unified Diffs) 형식(--- 및 +++ 포함) 또는 Aider 검색/대체 블록을 사용하는 것이 권장됩니다.
퍼지 매칭은 하나의 휴리스틱스: 퍼지 매치를 수행하기 전에 mpatch는 정확한 일치와
패치 파일에서 고유한 라인을 찾아 매칭 범위를 축소하려고 시도합니다.병렬 처리(Parallelism): 전체 스캔이 필요한 경우, Rayon을 사용하여 워크로드를 병렬화합니다.
벤치마크 코드는 benches/mpatch_bench.rs에 있습니다. 실행하려면 cargo bench를 사용하세요.
기여 환영합니다!
버그 보고: 이슈에 첨부할 디버그 보고서를 생성하려면 mpatch -vvvv ...를 실행하세요.개발: git clone https://github.com/romelium/mpatch.git cd mpatch cargo test
MIT 라이선스. 자세한 내용은 LICENSE를 참조하세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 GitHub Claude Ecosystem의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기