Repository navigation
Fix old_path and new_path being wrong for empty added or deleted files - #325
Merged
Merged
Conversation
GitPython learns that an added file has no old path from the "--- /dev/null" line of the patch header, and that a deleted file has no new path from "+++ /dev/null". Git emits no hunk, and therefore no such header, when the file being added or deleted is empty, so GitPython falls back to the path on the "diff --git a/X b/X" line and both sides of the diff end up with the same path. The "new file mode" and "deleted file mode" header lines are still emitted for empty files, which is why change_type stayed correct while the paths did not. old_path and new_path now consult diff.new_file and diff.deleted_file, the same two flags _from_change_to_modification_type already trusts to report ADD and DELETE, so the paths agree with the change type the object publishes. Renames of empty files set neither flag and are untouched.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #325 +/- ##
==========================================
- Coverage 97.40% 97.23% -0.17%
==========================================
Files 16 16
Lines 1154 1158 +4
==========================================
+ Hits 1124 1126 +2
- Misses 30 32 +2
🚀 New features to boost your workflow:
|
Owner
|
LGTM! Since GitPython issue is not getting fixed, it's fine to fix it on our side. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #248.
ModifiedFile.old_pathreturns the file's path instead ofNonewhen a commit adds an empty file, andModifiedFile.new_pathreturns the path instead ofNonewhen a commit deletes one. The same object reports achange_typeofADDwhile also claiming a previous path, so any code that keys onold_pathornew_pathto detect creation or deletion silently mis-classifies the file. Empty__init__.pyfiles make this common in Python repositories.The cause is upstream. Git emits no hunk for an empty file, so there is no
--- /dev/nullor+++ /dev/nullheader for GitPython to read the missing side from, and_pick_best_pathhands back the fallback taken from thediff --git a/X b/Xline instead. Thenew file modeanddeleted file modelines are still emitted, which is whychange_typestays correct while the paths do not. This still reproduces on GitPython 3.1.62, and gitpython-developers/GitPython#749 reports the same wronga_pathon added files and was closed as not planned._from_change_to_modification_typeatpydriller/domain/commit.py:187already readsdiff.new_fileanddiff.deleted_fileto decideADDandDELETE, so havingold_pathandnew_pathconsult the same two flags only makes them agree with the verdict the class already publishes, and withdocs/modifiedfile.rst, which already documents both asNonein these cases. The change is one guard at the top of each property inpydriller/domain/commit.py. Renames of empty files set neither flag, so renames are untouched. One downstream effect is worth naming: the seven per-file process metrics underpydriller/metrics/process/(every one exceptChangeSet) bucket bynew_path, andModifiedFile.__hash__interpolates it too, so a deleted empty file now buckets under theNonekey and hashes asDELETE None .... That is not a regression, since every non-empty deletion already does both.The bug is visible inside a bundled fixture, so no new test repository is needed. In
test-repos/diff, commit4be0402dadds the emptypydriller/tests/integration/__init__.pynext to a non-emptysetup.py, and only the empty one gets a bogusold_path. Commitdf51c5efdeletes thirteen non-empty files, which all correctly get anew_pathofNone, plus that same empty file, which does not. The two new tests intests/test_commit.pycover both cases and fail on master withassert 'pydriller/tests/integration/__init__.py' is None. Five existing tests needed two lines each: they build aModifiedFilefrom a bare@patch('git.diff.Diff')mock where everyMagicMockattribute is truthy, sonew_fileanddeleted_filenow have to be set false. Those mocks were always under-specified: on master they already makechange_typereportADDfor what the tests treat as an ordinary modification.mypy --ignore-missing-imports pydriller/ tests/andflake8are clean, and the suite passes locally on Python 3.11 and macOS, 313 passed, withtests/test_memory_consumption.pyexcluded locally because it clones remote repositories.