uni-diff-patch 0.0.2__py3-none-any.whl → 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uni_diff_patch/core/diff_engine.py +8 -2
- uni_diff_patch/core/validator.py +47 -27
- uni_diff_patch/server.py +13 -2
- {uni_diff_patch-0.0.2.dist-info → uni_diff_patch-0.0.4.dist-info}/METADATA +1 -1
- uni_diff_patch-0.0.4.dist-info/RECORD +13 -0
- {uni_diff_patch-0.0.2.dist-info → uni_diff_patch-0.0.4.dist-info}/WHEEL +1 -1
- uni_diff_patch-0.0.2.dist-info/RECORD +0 -13
- {uni_diff_patch-0.0.2.dist-info → uni_diff_patch-0.0.4.dist-info}/entry_points.txt +0 -0
|
@@ -263,12 +263,18 @@ def _generate_single_diff(
|
|
|
263
263
|
f"{change.file_path}"
|
|
264
264
|
)
|
|
265
265
|
patch_path = _make_patch_path(change.file_path, work_dir)
|
|
266
|
-
|
|
266
|
+
diff = _create_unified_diff(patch_path, "", change.new_content, 3)
|
|
267
|
+
# 替换 --- 行为 /dev/null,标准的新建文件格式
|
|
268
|
+
diff = diff.replace(f"--- a/{patch_path}", "--- /dev/null", 1)
|
|
269
|
+
return diff
|
|
267
270
|
|
|
268
271
|
case DeleteFileChange():
|
|
269
272
|
old = _read_file(change.file_path, change.encoding)
|
|
270
273
|
patch_path = _make_patch_path(change.file_path, work_dir)
|
|
271
|
-
|
|
274
|
+
diff = _create_unified_diff(patch_path, old, "", 3)
|
|
275
|
+
# 替换 +++ 行为 /dev/null,让 git apply 真正删除文件
|
|
276
|
+
diff = diff.replace(f"+++ b/{patch_path}", "+++ /dev/null", 1)
|
|
277
|
+
return diff
|
|
272
278
|
|
|
273
279
|
case TextPairChange():
|
|
274
280
|
if change.old_text == change.new_text:
|
uni_diff_patch/core/validator.py
CHANGED
|
@@ -44,39 +44,57 @@ def _parse_header_only_diffs(content: str) -> list[FileInfo]:
|
|
|
44
44
|
"""Parse git extended header-only diffs (rename, chmod without content changes).
|
|
45
45
|
|
|
46
46
|
mpatch.parse_auto cannot parse these since they have no hunk body.
|
|
47
|
-
We detect them by
|
|
48
|
-
|
|
47
|
+
We detect them by scanning for 'diff --git' lines and checking the
|
|
48
|
+
header section (between 'diff --git' and the first '---' or next
|
|
49
|
+
'diff --git') for rename/mode patterns.
|
|
49
50
|
|
|
50
51
|
Returns:
|
|
51
52
|
List of FileInfo for recognized header-only diffs, empty if none found.
|
|
52
53
|
"""
|
|
53
54
|
files: list[FileInfo] = []
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if not
|
|
59
|
-
|
|
60
|
-
# 如果有 @@ hunk,mpatch 应该已经处理了,这里只处理无 hunk 的
|
|
61
|
-
if "@@" in block:
|
|
55
|
+
lines = content.split("\n")
|
|
56
|
+
i = 0
|
|
57
|
+
while i < len(lines):
|
|
58
|
+
line = lines[i]
|
|
59
|
+
if not line.startswith("diff --git "):
|
|
60
|
+
i += 1
|
|
62
61
|
continue
|
|
63
62
|
|
|
64
63
|
# 提取文件路径
|
|
65
|
-
|
|
66
|
-
m = re.match(r"diff --git a/(.+?) b/(.+)", first_line)
|
|
64
|
+
m = re.match(r"diff --git a/(.+?) b/(.+)", line)
|
|
67
65
|
if not m:
|
|
66
|
+
i += 1
|
|
68
67
|
continue
|
|
69
68
|
|
|
70
69
|
file_path = m.group(2)
|
|
71
|
-
is_rename = "rename from" in block
|
|
72
|
-
is_chmod = "old mode" in block or "new mode" in block
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
# 收集 header 行(diff --git 之后到下一个 diff --git / --- / 空内容)
|
|
72
|
+
header_lines: list[str] = []
|
|
73
|
+
has_hunk = False
|
|
74
|
+
j = i + 1
|
|
75
|
+
while j < len(lines):
|
|
76
|
+
l = lines[j]
|
|
77
|
+
if l.startswith("diff --git ") or l.startswith("--- "):
|
|
78
|
+
break
|
|
79
|
+
if l.startswith("@@ "):
|
|
80
|
+
has_hunk = True
|
|
81
|
+
break
|
|
82
|
+
header_lines.append(l)
|
|
83
|
+
j += 1
|
|
84
|
+
|
|
85
|
+
# 只处理无 hunk body 的 header-only diff
|
|
86
|
+
if not has_hunk:
|
|
87
|
+
header_text = "\n".join(header_lines)
|
|
88
|
+
is_rename = "rename from" in header_text
|
|
89
|
+
is_chmod = "old mode" in header_text or "new mode" in header_text
|
|
90
|
+
if is_rename or is_chmod:
|
|
91
|
+
files.append(FileInfo(
|
|
92
|
+
file_path=file_path,
|
|
93
|
+
hunk_count=0,
|
|
94
|
+
is_creation=False,
|
|
95
|
+
))
|
|
96
|
+
|
|
97
|
+
i = j if j > i + 1 else i + 1
|
|
80
98
|
|
|
81
99
|
return files
|
|
82
100
|
|
|
@@ -142,19 +160,21 @@ def validate_patch(
|
|
|
142
160
|
return ValidationResult(valid=False, parse_error=str(e))
|
|
143
161
|
|
|
144
162
|
# mpatch.parse_auto 无法解析 header-only diff(纯 rename/chmod)
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return ValidationResult(
|
|
150
|
-
valid=True, files=header_files, total_hunks=0,
|
|
151
|
-
)
|
|
163
|
+
# 始终检查 header-only diff,与 parse_auto 结果合并
|
|
164
|
+
header_files = _parse_header_only_diffs(patch_content)
|
|
165
|
+
|
|
166
|
+
if not patches and not header_files:
|
|
152
167
|
return ValidationResult(
|
|
153
168
|
valid=False, parse_error="No patches found in content"
|
|
154
169
|
)
|
|
155
170
|
|
|
156
171
|
files: list[FileInfo] = []
|
|
157
172
|
total_hunks = 0
|
|
173
|
+
|
|
174
|
+
# header-only diff(rename/chmod 无 hunk body)
|
|
175
|
+
files.extend(header_files)
|
|
176
|
+
|
|
177
|
+
# hunked diff(mpatch 解析结果)
|
|
158
178
|
for p in patches:
|
|
159
179
|
hunk_count = len(p)
|
|
160
180
|
total_hunks += hunk_count
|
uni_diff_patch/server.py
CHANGED
|
@@ -66,8 +66,19 @@ def generate_patch_tool(
|
|
|
66
66
|
) -> dict:
|
|
67
67
|
"""Generate a valid unified diff patch from structured file changes.
|
|
68
68
|
|
|
69
|
-
Each change object must
|
|
70
|
-
|
|
69
|
+
Each change object must include a "mode" field and its mode-specific fields.
|
|
70
|
+
Use EXACT field names listed below — no aliases accepted.
|
|
71
|
+
|
|
72
|
+
Supported modes and required fields:
|
|
73
|
+
- file_content: file_path, new_content (full new file content; reads original from disk)
|
|
74
|
+
- search_replace: file_path, replacements (list of {search, replace, line_hint?})
|
|
75
|
+
- create_file: file_path, new_content (content for the new file)
|
|
76
|
+
- delete_file: file_path
|
|
77
|
+
- text_pair: file_path, old_text, new_text (both sides provided directly)
|
|
78
|
+
- rename_file: file_path (old path), new_path, new_content? (optional content change)
|
|
79
|
+
- chmod: file_path, old_mode, new_mode, new_content? (optional content change)
|
|
80
|
+
|
|
81
|
+
Optional shared fields: encoding (default "utf-8"), context_lines (default 3).
|
|
71
82
|
|
|
72
83
|
Args:
|
|
73
84
|
changes: Array of change objects (discriminated by "mode" field).
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
uni_diff_patch/__init__.py,sha256=QZHH9fShZy0Uae-0TF-6HN35C_3nCbGHcYE56eNy83E,1066
|
|
2
|
+
uni_diff_patch/cli.py,sha256=IgqlrUySgehRRNbY1h-vgkXt0jiKU8zKJRyJWVWRREI,5286
|
|
3
|
+
uni_diff_patch/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
uni_diff_patch/core/diff_engine.py,sha256=E2-ZPkgyXv3QrQUFAMGh4t97Tvj-_gxbPZ6W6ItL_bY,14244
|
|
5
|
+
uni_diff_patch/core/search_replace.py,sha256=kwUfr-SqB9oZJwXK0A3LOEmccXi_fx5SGQD_MPN83Rk,4592
|
|
6
|
+
uni_diff_patch/core/validator.py,sha256=ZXO5xNCGNu1MjWr2r3xgWQA7mmMwAiWl03Rn-vBqnBk,7160
|
|
7
|
+
uni_diff_patch/models.py,sha256=M7PcWRcUWUz0CO8MK7z5CDwYxpoN-MCmihvAq7zH9x0,6154
|
|
8
|
+
uni_diff_patch/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
uni_diff_patch/server.py,sha256=F377tvVLomnCfeB_owc4VkH5v-cuJPEVWvOTErfR81g,5660
|
|
10
|
+
uni_diff_patch-0.0.4.dist-info/WHEEL,sha256=r-Se0i_n47Mj8pdnVuq7W628oP9YAKMaTjp4l3lQcns,81
|
|
11
|
+
uni_diff_patch-0.0.4.dist-info/entry_points.txt,sha256=SMaBpCbADshKmWiXYPpy_PSVvKWf0KQcwNJXi_FIsoM,60
|
|
12
|
+
uni_diff_patch-0.0.4.dist-info/METADATA,sha256=C4OTLvkswEXWs9a_wKiUZQGCVVnQ9sIK-Jio2Q-YBho,4870
|
|
13
|
+
uni_diff_patch-0.0.4.dist-info/RECORD,,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
uni_diff_patch/__init__.py,sha256=QZHH9fShZy0Uae-0TF-6HN35C_3nCbGHcYE56eNy83E,1066
|
|
2
|
-
uni_diff_patch/cli.py,sha256=IgqlrUySgehRRNbY1h-vgkXt0jiKU8zKJRyJWVWRREI,5286
|
|
3
|
-
uni_diff_patch/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
uni_diff_patch/core/diff_engine.py,sha256=VHSk80sqiSciWaZe8BRWkokOM8Z9Gldy2sXe69Ve5C0,13897
|
|
5
|
-
uni_diff_patch/core/search_replace.py,sha256=kwUfr-SqB9oZJwXK0A3LOEmccXi_fx5SGQD_MPN83Rk,4592
|
|
6
|
-
uni_diff_patch/core/validator.py,sha256=qf66MXYT3zvHsqQ3l6PlvyK5Go6_DXu89QcIIxlE5cg,6639
|
|
7
|
-
uni_diff_patch/models.py,sha256=M7PcWRcUWUz0CO8MK7z5CDwYxpoN-MCmihvAq7zH9x0,6154
|
|
8
|
-
uni_diff_patch/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
uni_diff_patch/server.py,sha256=9mswkL4i0zfMczEtHJFd3MQKkK-SPQln2H2KyUgJ1Hw,5014
|
|
10
|
-
uni_diff_patch-0.0.2.dist-info/WHEEL,sha256=YR4QUxGCbsyoOnWBVTv-p1I4yc3seKGPev46mvmc8Cg,81
|
|
11
|
-
uni_diff_patch-0.0.2.dist-info/entry_points.txt,sha256=SMaBpCbADshKmWiXYPpy_PSVvKWf0KQcwNJXi_FIsoM,60
|
|
12
|
-
uni_diff_patch-0.0.2.dist-info/METADATA,sha256=j_t4J2YYyIO0xlq-efZcbH3nqZoSiPAnCoftxJR1Uzc,4870
|
|
13
|
-
uni_diff_patch-0.0.2.dist-info/RECORD,,
|
|
File without changes
|