Skip to content

Commit 7f438fa

Browse files
authored
Merge branch 'main' into fix/derived-min-loc-floor
2 parents d9d6573 + e128b2d commit 7f438fa

2 files changed

Lines changed: 46 additions & 1 deletion

File tree

pr_split/diff_ops/reconstructor.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,24 @@ def _hunk_target_lines(hunk: Hunk) -> list[str]:
9797
return target
9898

9999

100+
def split_git_lines(content: str) -> list[str]:
101+
"""Split file content into lines the way git counts them: on ``\\n`` only.
102+
103+
``str.splitlines`` also breaks on form feed, vertical tab, ``\\x1c``-``\\x1e``,
104+
``\\x85``, ``\\u2028`` and ``\\u2029``, none of which git treats as a line
105+
break, so every hunk after such a character would land at the wrong offset.
106+
"""
107+
if not content:
108+
return []
109+
parts = content.split("\n")
110+
lines = [part + "\n" for part in parts[:-1]]
111+
if parts[-1]:
112+
lines.append(parts[-1])
113+
return lines
114+
115+
100116
def apply_hunks(base_content: str, patch_file: PatchedFile, assigned_indices: list[int]) -> str:
101-
lines = base_content.splitlines(keepends=True)
117+
lines = split_git_lines(base_content)
102118
sorted_indices = sorted(assigned_indices, reverse=True)
103119
for idx in sorted_indices:
104120
hunk = patch_file[idx]

tests/test_reconstructor.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
apply_hunks,
1313
materialize_group_files,
1414
merge_chain_assignments,
15+
split_git_lines,
1516
)
1617
from pr_split.exceptions import GitOperationError
1718
from pr_split.schemas import Group, GroupAssignment
@@ -450,3 +451,31 @@ def test_new_file_without_trailing_newline(self) -> None:
450451
estimated_loc=2,
451452
)
452453
assert materialize_group_files(parsed, group, "base")["n.txt"] == "x\ny"
454+
455+
456+
class TestSplitGitLines:
457+
@pytest.mark.parametrize(
458+
("content", "expected"),
459+
[
460+
pytest.param("", [], id="empty"),
461+
pytest.param("a\n", ["a\n"], id="one-line"),
462+
pytest.param("a\nb", ["a\n", "b"], id="no-trailing-newline"),
463+
pytest.param("a\x0cb\nc\n", ["a\x0cb\n", "c\n"], id="form-feed"),
464+
pytest.param("a\x0bb\nc\n", ["a\x0bb\n", "c\n"], id="vertical-tab"),
465+
pytest.param("a\u2028b\nc\n", ["a\u2028b\n", "c\n"], id="line-separator"),
466+
pytest.param("a\x85b\n", ["a\x85b\n"], id="nel"),
467+
pytest.param("a\r\nb\r\n", ["a\r\n", "b\r\n"], id="crlf"),
468+
pytest.param("\n\n", ["\n", "\n"], id="blank-lines"),
469+
],
470+
)
471+
def test_splits_on_newline_only(self, content: str, expected: list[str]) -> None:
472+
assert split_git_lines(content) == expected
473+
474+
475+
class TestApplyHunksWithSplitlinesSeparators:
476+
def test_form_feed_in_earlier_line_does_not_shift_hunk(self) -> None:
477+
base = "a\x0cb\n" + "".join(f"{c}\n" for c in "cdefghijkl")
478+
dev = base.replace("k\n", "K\n")
479+
diff = "--- a/f.txt\n+++ b/f.txt\n@@ -7,5 +7,5 @@\n h\n i\n j\n-k\n+K\n l\n"
480+
pf = PatchSet(diff)[0]
481+
assert apply_hunks(base, pf, [0]) == dev

0 commit comments

Comments
 (0)