-
-
Notifications
You must be signed in to change notification settings - Fork 46
#213 Handle files with missing Git objects #214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,8 @@ | |
| import org.eclipse.jgit.api.errors.GitAPIException; | ||
| import org.eclipse.jgit.diff.DiffEntry; | ||
| import org.eclipse.jgit.diff.DiffFormatter; | ||
| import org.eclipse.jgit.errors.MissingObjectException; | ||
| import org.eclipse.jgit.errors.RevWalkException; | ||
| import org.eclipse.jgit.lib.*; | ||
| import org.eclipse.jgit.revwalk.*; | ||
| import org.eclipse.jgit.storage.file.FileRepositoryBuilder; | ||
|
|
@@ -131,28 +133,65 @@ public String getRepoUrl() throws IOException { | |
| */ | ||
| public ScmLogInfo fileLog(String path) throws GitAPIException, IOException { | ||
| ObjectId branchId = gitRepository.resolve("HEAD"); | ||
| Iterable<RevCommit> revCommits = git.log().add(branchId).addPath(path).call(); | ||
| CommitWalkStats stats = | ||
| walkCommits(git.log().add(branchId).addPath(path).call()); | ||
|
|
||
| int commitCount = 0; | ||
| int earliestCommit = Integer.MAX_VALUE; | ||
| int mostRecentCommit = 0; | ||
| if (stats.commitCount == 0) { | ||
| return new ScmLogInfo(path, null, stats.earliestCommit, stats.earliestCommit, stats.commitCount); | ||
| } | ||
|
|
||
| for (RevCommit revCommit : revCommits) { | ||
| int commitTime = revCommit.getCommitTime(); | ||
| if (commitCount == 0) { | ||
| mostRecentCommit = commitTime; | ||
| return new ScmLogInfo(path, null, stats.earliestCommit, stats.mostRecentCommit, stats.commitCount); | ||
| } | ||
|
|
||
| /** | ||
| * Counts commits over the given walk. A missing Git object (e.g. in a shallow or | ||
| * partial clone) must not fail the whole walk; the walk is truncated and the | ||
| * commits read so far are returned instead. | ||
| */ | ||
| private static CommitWalkStats walkCommits(Iterable<RevCommit> revCommits) { | ||
| CommitWalkStats stats = new CommitWalkStats(); | ||
|
|
||
| try { | ||
| for (RevCommit revCommit : revCommits) { | ||
| int commitTime = revCommit.getCommitTime(); | ||
| if (stats.commitCount == 0) { | ||
| stats.mostRecentCommit = commitTime; | ||
| } | ||
| if (commitTime < stats.earliestCommit) { | ||
| stats.earliestCommit = commitTime; | ||
| } | ||
| stats.commitCount++; | ||
| } | ||
| if (commitTime < earliestCommit) { | ||
| earliestCommit = commitTime; | ||
| } catch (RevWalkException e) { | ||
| // JGit wraps checked exceptions thrown mid-walk in a RevWalkException. | ||
| if (isCausedByMissingObject(e)) { | ||
| log.warn( | ||
| "Missing Git object while reading history (shallow or partial clone?); " | ||
| + "reporting the {} commit(s) that could be read. Cause: {}", | ||
| stats.commitCount, | ||
| e.getMessage()); | ||
| } else { | ||
| throw e; | ||
| } | ||
|
Comment on lines
139
to
147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Unreadable histories receive top rank When Learn moreA path walk can fail on its first missing tree, leaving the default statistics untouched. Example: File A has ten verified commits and a finite change-proneness score. File B's first tree is missing, so its score becomes NaN. Sorting places File B after File A, and File B receives the larger change-proneness rank. Recommended fix: Preserve whether the walk was truncated, and represent an unreadable zero-result history separately from a verified empty history. Update Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| commitCount++; | ||
| } | ||
|
|
||
| if (commitCount == 0) { | ||
| return new ScmLogInfo(path, null, earliestCommit, earliestCommit, commitCount); | ||
| return stats; | ||
| } | ||
|
|
||
| private static boolean isCausedByMissingObject(Throwable throwable) { | ||
| while (throwable != null) { | ||
| if (throwable instanceof MissingObjectException) { | ||
| return true; | ||
| } | ||
| throwable = throwable.getCause(); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| return new ScmLogInfo(path, null, earliestCommit, mostRecentCommit, commitCount); | ||
| private static class CommitWalkStats { | ||
| int commitCount = 0; | ||
| int earliestCommit = Integer.MAX_VALUE; | ||
| int mostRecentCommit = 0; | ||
| } | ||
|
|
||
| // based on https://stackoverflow.com/questions/27361538/how-to-show-changes-between-commits-with-jgit | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Handle a missing start commit in both log commands.
When
branchIdrefers to a missing commit, JGit 7.7.0.202606012155-rLogCommand.add(AnyObjectId)throwsMissingObjectExceptiondirectly. Java evaluates bothgit.log().add(branchId)calls beforewalkCommitsreceives the iterable, so the current missing-object policy does not handle either path. Catch this exception for both the filtered walk and the fallback walk, and add a test for a directLogCommand.addfailure.🤖 Prompt for AI Agents