Skip to content

Commit 02b7ece

Browse files
committed
fix(stats): split Git records at line feeds only
With `core.quotePath=false`, Git emits U+0085, U+2028 and U+2029 directly in filenames. `Commit.stats` and `Stats._list_from_string` used `str.splitlines()`, treating those filename characters as record endings and raising `ValueError` instead of returning statistics. Split only at the line feeds used by Git's protocol. Keep empty commits and the generated text's trailing newline working by ignoring empty records. No filename encoding or public return types change. The regression uses real Git output for all three Unicode separators and covers root, later and empty commits. It failed before this change. The complete commit/statistics modules passed: 44 tests and 8 subtests. Ruff lint/format, mypy for both changed source files, and `git diff --check` also passed.
1 parent ccc4811 commit 02b7ece

3 files changed

Lines changed: 25 additions & 3 deletions

File tree

‎git/objects/commit.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,8 @@ def stats(self) -> Stats:
405405
def process_lines(lines: List[str]) -> str:
406406
text = ""
407407
for file_info, line in zip(lines, lines[len(lines) // 2 :]):
408+
if not line:
409+
continue
408410
change_type = file_info.split("\t")[0][-1]
409411
(insertions, deletions, filename) = line.split("\t")
410412
text += "%s\t%s\t%s\t%s\n" % (change_type, insertions, deletions, filename)
@@ -413,12 +415,12 @@ def process_lines(lines: List[str]) -> str:
413415
if not self.parents:
414416
lines = self.repo.git.diff_tree(
415417
self.hexsha, "--", numstat=True, no_renames=True, root=True, raw=True
416-
).splitlines()[1:]
418+
).split("\n")[1:]
417419
text = process_lines(lines)
418420
else:
419421
lines = self.repo.git.diff(
420422
self.parents[0].hexsha, self.hexsha, "--", numstat=True, no_renames=True, raw=True
421-
).splitlines()
423+
).split("\n")
422424
text = process_lines(lines)
423425
return Stats._list_from_string(self.repo, text)
424426

‎git/util.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,9 @@ def _list_from_string(cls, repo: "Repo", text: str) -> "Stats":
11111111
"total": {"insertions": 0, "deletions": 0, "lines": 0, "files": 0},
11121112
"files": {},
11131113
}
1114-
for line in text.splitlines():
1114+
for line in text.split("\n"):
1115+
if not line:
1116+
continue
11151117
(change_type, raw_insertions, raw_deletions, filename) = line.split("\t")
11161118
insertions = raw_insertions != "-" and int(raw_insertions) or 0
11171119
deletions = raw_deletions != "-" and int(raw_deletions) or 0

‎test/test_commit.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,24 @@ def test_stats_preserve_filename_spaces(self, rw_dir):
200200
}
201201
assert removed_stats.total == {"insertions": 0, "deletions": 30, "lines": 30, "files": 5}
202202

203+
@with_rw_directory
204+
def test_stats_preserve_unicode_line_separators(self, rw_dir):
205+
with Repo.init(rw_dir) as repo:
206+
repo.git.config("core.quotePath", "false")
207+
actor = Actor("Test", "test@example.com")
208+
paths = [f"before{separator}after.txt" for separator in ("\x85", "\u2028", "\u2029")]
209+
for multiplier, change_type in ((1, "A"), (2, "M")):
210+
for path in paths:
211+
data = b"line\n" * multiplier
212+
blob = repo.odb.store(IStream("blob", len(data), BytesIO(data)))
213+
repo.index.add([BaseIndexEntry((0o100644, blob.binsha, 0, path))])
214+
commit = repo.index.commit("Update files", author=actor, committer=actor)
215+
assert commit.stats.total == {"insertions": 3, "deletions": 0, "lines": 3, "files": 3}
216+
assert commit.stats.files == {
217+
path: {"insertions": 1, "deletions": 0, "lines": 1, "change_type": change_type} for path in paths
218+
}
219+
assert repo.index.commit("No changes", author=actor, committer=actor).stats.files == {}
220+
203221
def test_renames(self):
204222
commit = self.rorepo.commit("185d847ec7647fd2642a82d9205fb3d07ea71715")
205223
files = commit.stats.files

0 commit comments

Comments
 (0)