Repository navigation
fix(code-intelligence): a failed git invocation is now distinguishable from empty history (#14114) - #14117
Conversation
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
…ttern walk swallowing git failures (#14114)
Review response — findings 1 and 2 fixed in
|
Closes #14114
Thinking Path
_run_gitreturned""on any non-zero exit — the same value it returns for a genuinely empty window — and every caller (get_commits_in_range,get_file_history,get_commit_files,get_commit_file_sets) treated an empty string as "no history". A corrupt object store, a timeout, or a permissions problem therefore all read as "no coupling found" instead of an error. The CI evidence in the issue showed exactly this: a temporary repository lost its objects mid-test,git logexited 128,_run_gitlogged a warning and returned"", and a downstream test then failed on a bogus pair-count assertion that had nothing to do with the real defect.Traced every
_run_gitcaller (grep -rn "get_commit_file_sets\|GitHistoryCrawler") before changing behaviour.CoChangeAnalyzer.analyze()(code_intelligence/co_change.py) is not wired into any router or service today — its only callers are its own test file — so there is no production caller currently depending on "empty list on git failure" degrading gracefully.CodeEvolutionMiner(which wrapsGitHistoryCrawler) IS production-facing, throughapi/code_intelligence.pyandapi/analytics_evolution.py; every one of those endpoints already wraps its call in a broadexcept Exceptionthat converts it into a proper error response, so letting the new exception propagate out ofanalyze_evolutionturns a silently-empty report into a correctly-reported failure, with no additional changes needed at the router layer.Picked one approach: a
GitCommandErrorraised by_run_giton any failure — subprocess-level (timeout, OSError) or non-zero exit.GitHistoryCrawler.__init__is the one place that catches it, to keep "not a repository" a legitimate, non-crashingavailable = Falsestate (unchanged public behaviour). Every other caller lets it propagate, since none of them can distinguish "the read failed" from "there is nothing" any other way.What Changed
autobot-backend/code_intelligence/code_evolution_miner.pyGitCommandError(RuntimeError)._run_gitnow raisesGitCommandError(with the git args, repo path and stderr/exception in the message) instead of returning""on failure. A successful call with no matching commits still returns""— that distinction is preserved.GitHistoryCrawler.__init__catchesGitCommandErrorfrom therev-parse --git-dirprobe and setsavailable = False— "not a repository" still degrades rather than crashing.get_commit_file_setsgained the sameif not self.available: return []guard the other three methods already had, so a non-repository path still degrades instead of raising through the new exception. All four_run_gitcallers now let a genuine failure on an available repo propagate asGitCommandError.autobot-backend/code_intelligence/git_history_crawler_test.py,autobot-backend/code_intelligence/co_change_test.pyrev-parse --git-dirstill succeeds,git log/git showdo not) and assertGitCommandErroris raised, forget_commits_in_range,get_file_history,get_commit_filesandget_commit_file_sets.[]without raising._pairs()test helper to name which branch each covers.Scope boundary
The issue's second defect —
/tmp/pytest-of-runner/pytest-0being reused across concurrent jobs on the self-hosted runner, so one job's tmp retention can delete another job's live objects — is not fixed here. It is not a two-linebasetempchange (it needs a per-job-unique tmp root, which touches CI job configuration, not this module), so per the issue's scope boundary it is left for a follow-up. Left a comment on #14114 noting this explicitly so it isn't lost.Verification
Full
code_intelligencesuite, with the fix in place:Before/after evidence for the new behaviour — reproduced the CI failure directly against the unmodified module (repo passes
rev-parse --git-dir, i.e.available: True, then its object store is corrupted):(git itself logged
fatal: bad object HEAD, exit 128 — silently turned into[]by the old code)New tests against the unmodified module (mutation test — reverted the production fix by file content, confirmed the file actually changed, then reran):
(both new test files fail to even collect, since the type they assert on doesn't exist pre-fix)
With the fix restored, the same test files:
ruff checkon all three touched files:All checks passed!Model Used
Sonnet 5