Resolve commit SHAs from one rev-list per repository - #330
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review. 📝 WalkthroughWalkthroughThe resolver now reads remote commit ids once per working directory, caches them with bounded staleness, and resolves SHA prefixes through binary lookup. Tests cover remote reachability, ambiguity, retries, SHA-256 repositories, and cache eviction. ChangesRemote commit resolution
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~45 minutes Change: Refactor · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant Renderer
participant resolve_sha
participant RemoteCommitCache
participant Git
Renderer->>resolve_sha: resolve SHA token
resolve_sha->>RemoteCommitCache: request commits for cwd
RemoteCommitCache->>Git: run rev-list --remotes
Git-->>RemoteCommitCache: remote commit ids
RemoteCommitCache-->>resolve_sha: matching full commit id
resolve_sha-->>Renderer: formatted commit URL
Merge Risk: ⚪ Minimal · up to The commit-resolution cache change is ready to merge with no identified material regression. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Rendering with a repository in context spent most of its Markdown time in git: every distinct SHA-shaped token ran `git branch -r --contains` (~22 ms), plus `rev-parse` when found, memoized per process so every render worker repeated it. Over a third of those tokens are not commits at all (task ids, UUID fragments, digit runs), so much of the cost went into proving negatives. The resolver now reads `git rev-list --remotes` once per working directory and resolves each candidate by prefix search in the sorted list: an unambiguous abbreviation of a remote-reachable commit links with its full id, anything else stays plain text. Over 1788 distinct tokens from this project's transcripts the answers are identical to the old resolver's; the old one spent 23.8 s on the 568 hits and 14.8 s on the 1220 misses, the new one a 27 ms list read and ~5 ms of lookups. When the list cannot be read at all but the remote is known, SHA-shaped tokens link as written, unvalidated. Both behaviours go through one function, `_commit_for`, which also enforces the SHA shape. A long-lived process re-reads a list when a lookup misses and the list has outlived its trust window, so commits fetched after it started are still found. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KBfajJZtCtVDRA7hnMBp3E
The first cut linked every SHA-shaped token unvalidated when `git rev-list --remotes` failed or timed out. Over a third of the candidates in real transcripts are not commits, so that turned a skipped check into a page of dead links with nothing saying the check was skipped — the failure direction the per-candidate resolver never had. An unreadable list now finds nothing, and a later miss past the trust window retries the read. This also leaves the unvalidated behaviour with no trigger: a missing checkout cannot reach it (no remote URL to build a link from), so it belongs with a stored remote URL, if that is ever wanted. The shape check stays in `_commit_for` as the resolver's own contract, since the prefix search alone would accept uppercase hex and short abbreviations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KBfajJZtCtVDRA7hnMBp3E
The list is re-read on a stale miss, never on a hit, so a commit that leaves the remote (force-push, deleted branch) stays linked for the life of a long-lived process. Say so where the rule lives, so it reads as a decision rather than an oversight. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KBfajJZtCtVDRA7hnMBp3E
The test uppercased a pushed commit's 7-char prefix and expected the shape gate to reject it. When that prefix is all digits (about 4% of SHAs), uppercasing changes nothing, the token is valid, and it links: a failure that depends only on the commit's random id. Use the shortest prefix of at least 7 chars that contains a letter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KBfajJZtCtVDRA7hnMBp3E
70c7bca to
f383f45
Compare
Closes #327.
Rendering with a git repository in context spent most of its "Markdown rendering" time in
gitsubprocesses resolving commit-SHA links. Every distinct SHA-shaped token rangit branch -r --contains(plusgit rev-parsewhen found), memoised per process, so each render worker repeated the whole set. Over a third of those tokens are not commits at all (task ids, UUID fragments, digit runs), so much of that time went into proving negatives.The change
git_remote.pynow readsgit rev-list --remotesonce per working directory and resolves each candidate by prefix search in the sorted list (binary ids in onebytesobject, 20 bytes per commit; SHA-256 repositories work too). One function,_commit_for, owns the rule: a token matching[0-9a-f]{7,40}that abbreviates exactly one commit reachable from a remote-tracking ref links by that commit's full id; everything else stays plain text.resolve_sha's interface is unchanged.Same answers
Timings
Serial, uncached CLI conversion (
CLAUDE_CODE_LOG_RENDER_JOBS=1,--no-cache,CLAUDE_CODE_LOG_DEBUG_TIMING=1) of a frozen 24-file / 40 109-message project, old and new module interleaved:The Markdown and Pygments memo hit counts are identical in all four runs, so every run did the same work. The slower old run is slow on every bucket, Pygments included, so its extra time is machine load rather than the resolver. This is a different corpus from the one in #327, so compare the ratio, not the absolute figures.
Decisions worth knowing
git rev-listfails or times out (30 s cap, since it runs once per directory), nothing links. Linking every SHA-shaped token unvalidated instead would turn a skipped check into a page of dead links — over a third of candidates are not commits — with nothing saying the check was skipped.watch, the TUI). The old per-token check saw commits fetched after startup; a list read once would not. A lookup miss re-reads the list once it is older than max(60 s, 20x its own read time); a hit never re-reads. So commits that leave the remote (force-push, deleted branch) keep linking until restart — deliberate, since re-reading on hits would bring back agitcall per token. Both directions are pinned by tests.Unchanged
The missing-checkout precondition: a transcript whose working directory no longer exists (or has no
origin) still gets no commit links, because the remote URL comes from that checkout. Covering it would need the remote URL stored per project or a configured template, which is a separate feature.Interaction with #329
#329 replaces the Markdown linkifier that calls this resolver; its Interaction with #327 section covers the combined tree.
Review state
Approved at
70c7bca, rebased onto6eb8696asf383f45with an identical patch. The approval was given at7787d9aand extended across two follow-ups:8a0d820is docstring-only (+4/-1 in_commit_for, recording the hit/miss asymmetry above; the code with docstrings stripped is AST-identical), and70c7bcais test-only (the uppercase shape test no longer depends on the commit's SHA having a letter in its first 7 chars).🤖 Generated with Claude Code
https://claude.ai/code/session_01KBfajJZtCtVDRA7hnMBp3E
Summary by CodeRabbit
Bug Fixes
Performance