patch-fixer 0.3.0__py3-none-any.whl → 0.3.2__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.
- patch_fixer/__init__.py +2 -1
- patch_fixer/patch_fixer.py +36 -5
- patch_fixer/split.py +119 -0
- {patch_fixer-0.3.0.dist-info → patch_fixer-0.3.2.dist-info}/METADATA +3 -2
- patch_fixer-0.3.2.dist-info/RECORD +8 -0
- patch_fixer-0.3.0.dist-info/RECORD +0 -7
- {patch_fixer-0.3.0.dist-info → patch_fixer-0.3.2.dist-info}/WHEEL +0 -0
- {patch_fixer-0.3.0.dist-info → patch_fixer-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {patch_fixer-0.3.0.dist-info → patch_fixer-0.3.2.dist-info}/top_level.txt +0 -0
patch_fixer/__init__.py
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
from .patch_fixer import fix_patch
|
1
|
+
from .patch_fixer import fix_patch
|
2
|
+
from .split import split_patch
|
patch_fixer/patch_fixer.py
CHANGED
@@ -25,9 +25,40 @@ class MissingHunkError(Exception):
|
|
25
25
|
pass
|
26
26
|
|
27
27
|
|
28
|
+
class BadCarriageReturn(ValueError):
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
28
32
|
def normalize_line(line):
|
29
|
-
|
30
|
-
|
33
|
+
"""Normalize line endings while preserving whitespace."""
|
34
|
+
if not isinstance(line, str):
|
35
|
+
raise TypeError(f"Cannot normalize non-string object {line}")
|
36
|
+
|
37
|
+
# edge case: empty string
|
38
|
+
if line == "":
|
39
|
+
return "\n"
|
40
|
+
|
41
|
+
# special malformed ending: ...\n\r
|
42
|
+
if line.endswith("\n\r"):
|
43
|
+
raise BadCarriageReturn(f"carriage return after line feed: {line}")
|
44
|
+
|
45
|
+
# handle CRLF and simple CR/LF endings
|
46
|
+
if line.endswith("\r\n"):
|
47
|
+
core = line[:-2]
|
48
|
+
elif line.endswith("\r"):
|
49
|
+
core = line[:-1]
|
50
|
+
elif line.endswith("\n"):
|
51
|
+
core = line[:-1]
|
52
|
+
else:
|
53
|
+
core = line
|
54
|
+
|
55
|
+
# check for interior CR/LF (anything before the final terminator)
|
56
|
+
if "\n" in core:
|
57
|
+
raise ValueError(f"line feed in middle of line: {line}")
|
58
|
+
if "\r" in core:
|
59
|
+
raise BadCarriageReturn(f"carriage return in middle of line: {line}")
|
60
|
+
|
61
|
+
return core + "\n"
|
31
62
|
|
32
63
|
|
33
64
|
def find_hunk_start(context_lines, original_lines):
|
@@ -173,7 +204,7 @@ def fix_patch(patch_lines, original, remove_binary=False):
|
|
173
204
|
) = capture_hunk(current_hunk, original_lines, offset, last_hunk, hunk_context)
|
174
205
|
except MissingHunkError:
|
175
206
|
raise NotImplementedError(f"Could not find hunk in {current_file}:"
|
176
|
-
f"\n\n{
|
207
|
+
f"\n\n{''.join(current_hunk)}")
|
177
208
|
fixed_lines.append(fixed_header)
|
178
209
|
fixed_lines.extend(current_hunk)
|
179
210
|
current_hunk = []
|
@@ -384,7 +415,7 @@ def fix_patch(patch_lines, original, remove_binary=False):
|
|
384
415
|
) = capture_hunk(current_hunk, original_lines, offset, last_hunk, hunk_context)
|
385
416
|
except MissingHunkError:
|
386
417
|
raise NotImplementedError(f"Could not find hunk in {current_file}:"
|
387
|
-
f"\n\n{
|
418
|
+
f"\n\n{''.join(current_hunk)}")
|
388
419
|
fixed_lines.append(fixed_header)
|
389
420
|
fixed_lines.extend(current_hunk)
|
390
421
|
current_hunk = []
|
@@ -406,7 +437,7 @@ def fix_patch(patch_lines, original, remove_binary=False):
|
|
406
437
|
) = capture_hunk(current_hunk, original_lines, offset, last_hunk, hunk_context)
|
407
438
|
except MissingHunkError:
|
408
439
|
raise NotImplementedError(f"Could not find hunk in {current_file}:"
|
409
|
-
f"\n\n{
|
440
|
+
f"\n\n{''.join(current_hunk)}")
|
410
441
|
fixed_lines.append(fixed_header)
|
411
442
|
fixed_lines.extend(current_hunk)
|
412
443
|
|
patch_fixer/split.py
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
"""
|
2
|
+
Idea:
|
3
|
+
|
4
|
+
1. main function takes in:
|
5
|
+
a. patch file
|
6
|
+
b. list of files to split out
|
7
|
+
2. reads patch file, splits based on file headers (assumed to be valid)
|
8
|
+
3. for each file being patched:
|
9
|
+
a. if the file is in the list, send its hunks to output 1
|
10
|
+
b. otherwise send its hunks to output 2
|
11
|
+
c. hunks include all header lines so each output is a valid diff
|
12
|
+
|
13
|
+
Could share some functionality with refactored, modular version of fix_patch
|
14
|
+
"""
|
15
|
+
|
16
|
+
import re
|
17
|
+
from typing import List, Tuple
|
18
|
+
|
19
|
+
from .patch_fixer import match_line, normalize_line, split_ab
|
20
|
+
|
21
|
+
|
22
|
+
def get_file_path_from_diff(line: str) -> str:
|
23
|
+
"""Extract the file path from a diff line."""
|
24
|
+
match_groups, line_type = match_line(line)
|
25
|
+
if line_type != "DIFF_LINE":
|
26
|
+
raise ValueError(f"Expected DIFF_LINE but got {line_type}")
|
27
|
+
|
28
|
+
# get the 'a' path (source file)
|
29
|
+
a_path, _ = split_ab(match_groups)
|
30
|
+
return a_path
|
31
|
+
|
32
|
+
|
33
|
+
def split_patch(patch_lines: List[str], files_to_include: List[str]) -> Tuple[List[str], List[str]]:
|
34
|
+
"""
|
35
|
+
Split a patch into two parts based on a list of files to include.
|
36
|
+
|
37
|
+
Parameters
|
38
|
+
----------
|
39
|
+
patch_lines : List[str]
|
40
|
+
Lines of the patch file to split.
|
41
|
+
files_to_include : List[str]
|
42
|
+
List of file paths (relative, starting with ./) to include in the first output.
|
43
|
+
Files not in this list go to the second output.
|
44
|
+
|
45
|
+
Returns
|
46
|
+
-------
|
47
|
+
included_lines : List[str]
|
48
|
+
Lines for the patch containing only the included files.
|
49
|
+
excluded_lines : List[str]
|
50
|
+
Lines for the patch containing all other files.
|
51
|
+
|
52
|
+
Notes
|
53
|
+
-----
|
54
|
+
The function preserves all header information for each file's hunks
|
55
|
+
to ensure both output patches are valid. File paths are normalized
|
56
|
+
to start with './' for comparison purposes.
|
57
|
+
|
58
|
+
Raises
|
59
|
+
------
|
60
|
+
ValueError
|
61
|
+
If the patch format is invalid or cannot be parsed.
|
62
|
+
"""
|
63
|
+
if not patch_lines:
|
64
|
+
raise ValueError("Empty patch provided")
|
65
|
+
|
66
|
+
# normalize file paths to include
|
67
|
+
normalized_include = set()
|
68
|
+
for path in files_to_include:
|
69
|
+
if not path.startswith("./"):
|
70
|
+
path = f"./{path}"
|
71
|
+
normalized_include.add(path)
|
72
|
+
|
73
|
+
included_lines = []
|
74
|
+
excluded_lines = []
|
75
|
+
current_file_lines = []
|
76
|
+
current_file_path = None
|
77
|
+
in_file_block = False
|
78
|
+
|
79
|
+
for line in patch_lines:
|
80
|
+
match_groups, line_type = match_line(line)
|
81
|
+
|
82
|
+
if line_type == "DIFF_LINE":
|
83
|
+
# start of a new file block
|
84
|
+
if in_file_block and current_file_lines:
|
85
|
+
# output the previous file block
|
86
|
+
if current_file_path in normalized_include:
|
87
|
+
included_lines.extend(current_file_lines)
|
88
|
+
else:
|
89
|
+
excluded_lines.extend(current_file_lines)
|
90
|
+
|
91
|
+
# start collecting new file block
|
92
|
+
current_file_lines = [normalize_line(line)]
|
93
|
+
current_file_path = get_file_path_from_diff(line)
|
94
|
+
in_file_block = True
|
95
|
+
|
96
|
+
elif in_file_block:
|
97
|
+
# continue collecting lines for current file
|
98
|
+
current_file_lines.append(normalize_line(line))
|
99
|
+
|
100
|
+
else:
|
101
|
+
# lines before any diff (shouldn't happen in well-formed patches)
|
102
|
+
# add to both outputs to preserve any global headers
|
103
|
+
normalized = normalize_line(line)
|
104
|
+
included_lines.append(normalized)
|
105
|
+
excluded_lines.append(normalized)
|
106
|
+
|
107
|
+
# don't forget the last file block
|
108
|
+
if in_file_block and current_file_lines:
|
109
|
+
if current_file_path in normalized_include:
|
110
|
+
included_lines.extend(current_file_lines)
|
111
|
+
else:
|
112
|
+
excluded_lines.extend(current_file_lines)
|
113
|
+
|
114
|
+
# handle edge case where no files were split (no diff lines)
|
115
|
+
if not in_file_block:
|
116
|
+
# patch had no diff lines at all
|
117
|
+
return patch_lines, []
|
118
|
+
|
119
|
+
return included_lines, excluded_lines
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: patch-fixer
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.2
|
4
4
|
Summary: Fixes erroneous git apply patches to the best of its ability.
|
5
5
|
Maintainer-email: Alex Mueller <amueller474@gmail.com>
|
6
6
|
License-Expression: Apache-2.0
|
@@ -22,6 +22,7 @@ Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
23
23
|
Requires-Dist: GitPython
|
24
24
|
Provides-Extra: test
|
25
|
+
Requires-Dist: hypothesis; extra == "test"
|
25
26
|
Requires-Dist: pytest; extra == "test"
|
26
27
|
Requires-Dist: requests; extra == "test"
|
27
28
|
Dynamic: license-file
|
@@ -71,7 +72,7 @@ pytest
|
|
71
72
|
```
|
72
73
|
From version `0.3.0` onward (at least until version `1.0`), some test failures are expected
|
73
74
|
in bugfix versions as I like to use test-driven development to build out new features.
|
74
|
-
Please only report test failures if the same test passed in the most recent `0.x.0` version.
|
75
|
+
Please only report test failures if the same test existed and passed in the most recent `0.x.0` version.
|
75
76
|
|
76
77
|
## License
|
77
78
|
|
@@ -0,0 +1,8 @@
|
|
1
|
+
patch_fixer/__init__.py,sha256=n5DDMr4jbO3epK3ybBvjDyRddTWlWamN6ao5BC7xHFo,65
|
2
|
+
patch_fixer/patch_fixer.py,sha256=GAavb15H5cEoNFgGlO5hIY7EOF88VCsjHcLrfyGW4_0,20587
|
3
|
+
patch_fixer/split.py,sha256=l0rHM6-ZBuB9Iv6Ng6rxqZH5eKfvk2t87j__nDu67kM,3869
|
4
|
+
patch_fixer-0.3.2.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
5
|
+
patch_fixer-0.3.2.dist-info/METADATA,sha256=DWi_Bf1beGb5Q0RyGxyDRlmb9RYjCuUhYcvJF0TisY0,2828
|
6
|
+
patch_fixer-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
7
|
+
patch_fixer-0.3.2.dist-info/top_level.txt,sha256=yyp3KjFgExJsrFsS9ZBCnkhb05xg8hPYhB7ncdpTOv0,12
|
8
|
+
patch_fixer-0.3.2.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
patch_fixer/__init__.py,sha256=bSp2H7JW2kz1WrT0dqlg64kZpklKPp1FZlDhq2XJ2uU,34
|
2
|
-
patch_fixer/patch_fixer.py,sha256=GIXSlWWbHy9gvZ61mOyxZ9oSqyIeR1rTKJDQGdZ-IYc,19730
|
3
|
-
patch_fixer-0.3.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
4
|
-
patch_fixer-0.3.0.dist-info/METADATA,sha256=tR6BAvwh8PvNyKouq0p1CN0aEsasymao-_9hkZCglP0,2773
|
5
|
-
patch_fixer-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
6
|
-
patch_fixer-0.3.0.dist-info/top_level.txt,sha256=yyp3KjFgExJsrFsS9ZBCnkhb05xg8hPYhB7ncdpTOv0,12
|
7
|
-
patch_fixer-0.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|