Skip to content

Commit c9b44d2

Browse files
committed
fix files list on file rename
GitPython parses the output of `git diff --numstat` to get the files changed in a commit. This breaks when a commit contains a file rename, because the output of `git diff` is different than expected. This is the output of a normal commit: $ git diff --numstat 8f41a39^ 8f41a39 30 5 test/test_repo.py And this a commit containing a rename: $ git diff --numstat 185d847^ 185d847 3 1 .github/workflows/{test_pytest.yml => Future.yml} This can be triggered by this code: for commit in repo.iter_commits(): print(commit.hexsha) for file in commit.stats.files: print(file) Which will print for the normal commit: 8f41a39 'test/test_repo.py' And when there is a rename: 185d847 '.github/workflows/{test_pytest.yml => Future.yml}' Additionally, when a path member is removed, the file list become a list of strings, breaking even more the caller. This is in the Linux kernel tree: $ git diff --numstat db401875f438^ db401875f438 1 1 tools/testing/selftests/drivers/net/mlxsw/{spectrum-2 => }/devlink_trap_tunnel_ipip6.sh and GitPython parses it as: db401875f438168c5804b295b93a28c7730bb57a ('tools/testing/selftests/drivers/net/mlxsw/{spectrum-2 => ' '}/devlink_trap_tunnel_ipip6.sh') Fix this by pasing the --no-renames option to `git diff` which ignores renames and print the same output as if the file was deleted from the old path and created in the new one: $ git diff --numstat --no-renames 185d847^ 185d847 57 0 .github/workflows/Future.yml 0 55 .github/workflows/test_pytest.yml
1 parent 90c81a5 commit c9b44d2

File tree

2 files changed

+33
-2
lines changed

2 files changed

+33
-2
lines changed

Diff for: git/objects/commit.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -324,14 +324,14 @@ def stats(self) -> Stats:
324324
325325
:return: git.Stats"""
326326
if not self.parents:
327-
text = self.repo.git.diff_tree(self.hexsha, "--", numstat=True, root=True)
327+
text = self.repo.git.diff_tree(self.hexsha, "--", numstat=True, no_renames=True, root=True)
328328
text2 = ""
329329
for line in text.splitlines()[1:]:
330330
(insertions, deletions, filename) = line.split("\t")
331331
text2 += "%s\t%s\t%s\n" % (insertions, deletions, filename)
332332
text = text2
333333
else:
334-
text = self.repo.git.diff(self.parents[0].hexsha, self.hexsha, "--", numstat=True)
334+
text = self.repo.git.diff(self.parents[0].hexsha, self.hexsha, "--", numstat=True, no_renames=True)
335335
return Stats._list_from_string(self.repo, text)
336336

337337
@property

Diff for: test/test_commit.py

+31
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,37 @@ def check_entries(d):
159159
self.assertEqual(commit.committer_tz_offset, 14400, commit.committer_tz_offset)
160160
self.assertEqual(commit.message, "initial project\n")
161161

162+
def test_renames(self):
163+
commit = self.rorepo.commit("185d847ec7647fd2642a82d9205fb3d07ea71715")
164+
files = commit.stats.files
165+
166+
# when a file is renamed, the output of git diff is like "dir/{old => new}"
167+
# unless we disable rename with --no-renames, which produces two lines
168+
# one with the old path deletes and another with the new added
169+
self.assertEqual(len(files), 2)
170+
171+
def check_entries(path, changes):
172+
expected = {
173+
".github/workflows/Future.yml" : {
174+
'insertions': 57,
175+
'deletions': 0,
176+
'lines': 57
177+
},
178+
".github/workflows/test_pytest.yml" : {
179+
'insertions': 0,
180+
'deletions': 55,
181+
'lines': 55
182+
},
183+
}
184+
assert path in expected
185+
assert isinstance(changes, dict)
186+
for key in ("insertions", "deletions", "lines"):
187+
assert changes[key] == expected[path][key]
188+
189+
for path, changes in files.items():
190+
check_entries(path, changes)
191+
# END for each stated file
192+
162193
def test_unicode_actor(self):
163194
# assure we can parse unicode actors correctly
164195
name = "Üäöß ÄußÉ"

0 commit comments

Comments
 (0)