Skip to content

fix: open a commit diff for any sha - #4468

Open
CN-liuzhiyang wants to merge 1 commit into
getpaseo:mainfrom
CN-liuzhiyang:fix/commit-diff-any-sha
Open

fix: open a commit diff for any sha#4468
CN-liuzhiyang wants to merge 1 commit into
getpaseo:mainfrom
CN-liuzhiyang:fix/commit-diff-any-sha

Conversation

@CN-liuzhiyang

Copy link
Copy Markdown

Linked issue

Closes #

Type of change

  • Bug fix

Reasoning

The commit diff panel took its file list from checkout.commits.list. That RPC answers a different question: what this workspace changed relative to its base, plus CHECKOUT_BASE_COMMIT_LIMIT (10) commits of base history. A sha outside that window is simply absent from the response, so useCommitDiffFiles found nothing and the panel rendered "No changes" for a commit that plainly has some.

The window is small. On a branch four commits ahead of main, everything older than the tenth commit on main is affected — in a busy repo that is a few hours of history.

You cannot reach such a commit from the Commits section today, because it only lists what the same RPC returned. You can reach it two other ways:

  • An open commit diff tab whose commit ages out while base advances. The next refresh empties the panel. This is the reproduction in the added test.
  • A plugin panel or a deep link that opens a commit by sha. feat(plugins): let plugin surfaces open a commit diff tab #4460 adds navigation.openCommitDiff for exactly that, and it is only useful if the panel can render an arbitrary sha.

Goals

  • Opening a commit diff by sha works regardless of what the commits list currently holds.
  • Merge commits keep reporting their first-parent diff, matching getCommitFileDiff.
  • The panel stops depending on the commits list, so it also stops issuing that query.

Non-goals

  • No change to checkout.commits.list or to the Commits section. Paging older base history is a separate problem.
  • No change to how a single file's diff is fetched.

Protocol compatibility

  • checkout.commits.files.request / .response are new message types. An older app never sends the request, and never receives the response because it never asks. An older daemon parses nothing new.
  • server_info.features.commitFiles is a new optional boolean. An older app ignores it; an older daemon omits it and a new app reads undefined as absent.
  • Detection happens once, in useCommitDiffFiles, and the panel already had a capabilityMissing state that tells the user to update the host. No fallback path.
  • Tagged // COMPAT(commitFiles): added in v0.8.0, remove gate after 2027-03-08. at the schema, the daemon flag, and the app gate.

QA

Automated, on this branch:

$ npm run typecheck
(all workspaces, no errors)

$ npm run lint
Found 0 warnings and 0 errors.

$ npx vitest run src/utils/checkout-git.commits.test.ts --bail=1
 Test Files  1 passed (1)
      Tests  14 passed (14)

Two new server cases in checkout-git.commits.test.ts: a commit outside the commits list still reports its files, and a merge commit reports its first-parent diff.

New browser regression in packages/app/e2e/browser/commit-diff-aged-out.spec.ts. It opens a commit diff from the Commits section, adds twelve commits, moves main up to the branch tip, hits Refresh, waits for "No commits ahead of main yet" (proving the commit really left the list), then asserts the open diff is still rendered.

It fails on the old code for the reported reason. With packages/app/src/git/use-diff-files.ts reverted to main:

$ git checkout origin/main -- packages/app/src/git/use-diff-files.ts
$ npx playwright test --project=browser e2e/browser/commit-diff-aged-out.spec.ts
  Error: expect(locator).toHaveAccessibleName(expected) failed
  Locator: getByTestId('commit-diff-panel')...getByTestId('diff-file-0')
  Error: element(s) not found
  1 failed

and with the fix in place:

$ npx playwright test --project=browser e2e/browser/commit-diff-aged-out.spec.ts
  1 passed (1.0m)

Same scenario, screenshotted. Before, once the commit leaves the list:

before

After:

after

Also verified against a real daemon with a plugin panel that opens arbitrary shas (the #4460 hook): a commit thirteen back from HEAD, well outside the window, renders its diff instead of "No changes".

Platform Tested Notes
iOS No platform-specific code; daemon RPC plus one app query
Android Same
Web yes Playwright browser project against a real daemon
Desktop macOS
Desktop Windows yes Electron dev build
Desktop Linux

Checklist

  • One focused change
  • npm run typecheck passes
  • npm run lint passes
  • npm run format passes
  • QA evidence
  • Tests added or updated where it made sense

@greptile-apps

greptile-apps Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR decouples commit-diff file discovery from the bounded commits list by adding a capability-gated, SHA-addressable daemon RPC.

  • Adds protocol, client, authorization, session-routing, and server support for retrieving one commit’s files.
  • Updates the app to query and cache files directly by server, checkout, and SHA.
  • Preserves first-parent behavior for merge commits.
  • Adds server coverage and a browser regression test for an open diff whose commit ages out of the commits list.

Confidence Score: 4/5

The PR is not ready to merge because the existing named-public-type repository requirement remains unsatisfied, while the new capability gate also produces a transient misleading host-update state.

The new query path correctly removes the bounded-list dependency, but it maps an uninitialized serverInfo to a terminal missing-capability state. The previous finding remains outstanding because getCommitFiles still declares { cwd: string; sha: string } inline in its exported signature rather than using the named parameter type required by the server guide.

Files Needing Attention: packages/app/src/git/use-diff-files.ts; packages/server/src/utils/checkout-git.ts

Important Files Changed

Filename Overview
packages/app/src/git/use-diff-files.ts Replaces bounded commit-list lookup with a capability-gated per-SHA query, but conflates unloaded server metadata with a confirmed missing capability.
packages/protocol/src/messages.ts Adds validated commit-files request/response messages and the optional server capability flag.
packages/server/src/server/session/checkout/checkout-session.ts Handles the new request with revision validation, file lookup, and structured error responses.
packages/server/src/utils/checkout-git.ts Adds the first-parent commit-file lookup; its public parameter still uses the inline type identified in the previous review.
packages/app/e2e/browser/commit-diff-aged-out.spec.ts Exercises the user-visible regression by keeping a diff open after its commit leaves the bounded list.

Sequence Diagram

sequenceDiagram
  participant Panel as Commit diff panel
  participant App as App query layer
  participant Client as Daemon client
  participant Session as Checkout session
  participant Git as Git utilities

  Panel->>App: Request files for SHA
  App->>Client: getCommitFiles(cwd, sha)
  Client->>Session: checkout.commits.files.request
  Session->>Session: Validate SHA and workspace.read permission
  Session->>Git: "getCommitFiles({ cwd, sha })"
  Git-->>Session: First-parent file records
  Session-->>Client: checkout.commits.files.response
  Client-->>App: Files
  App->>Client: Request each file diff
  Client-->>Panel: Parsed file diffs
Loading

Reviews (2): Last reviewed commit: "fix: open a commit diff for any sha" | Re-trigger Greptile

Comment on lines +2554 to +2563
export async function getCommitFiles({
cwd,
sha,
}: {
cwd: string;
sha: string;
}): Promise<CheckoutCommitFile[]> {
const records = await getCheckoutCommitRecords({ cwd, revision: sha, maxCount: 1 });
return records[0]?.files ?? [];
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Unnamed Public Parameter Type

The exported getCommitFiles function declares its multi-property parameter type inline. This violates the server guide’s requirement that public signatures use named types, so the repository requirement must be satisfied before merging.

Suggested change
export async function getCommitFiles({
cwd,
sha,
}: {
cwd: string;
sha: string;
}): Promise<CheckoutCommitFile[]> {
const records = await getCheckoutCommitRecords({ cwd, revision: sha, maxCount: 1 });
return records[0]?.files ?? [];
}
interface GetCommitFilesOptions {
cwd: string;
sha: string;
}
export async function getCommitFiles({
cwd,
sha,
}: GetCommitFilesOptions): Promise<CheckoutCommitFile[]> {
const records = await getCheckoutCommitRecords({ cwd, revision: sha, maxCount: 1 });
return records[0]?.files ?? [];
}

Context Used: packages/server/CLAUDE.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

The commit diff panel took its file list from checkout.commits.list, which
answers a different question: what this workspace changed relative to its base,
plus CHECKOUT_BASE_COMMIT_LIMIT (10) commits of base history. A sha outside
that window was absent from the list, so the panel rendered "No changes" for a
commit that plainly has some.

Nothing in the app could reach such a commit before, because the Commits
section only offers the ones in the list. A plugin panel or a deep link can.

Add checkout.commits.files, which lists one commit's files by sha, and read it
from the panel instead of scanning the list. Merge commits report their
first-parent diff, matching getCommitFileDiff. Gated once on the commitFiles
capability.
@CN-liuzhiyang
CN-liuzhiyang force-pushed the fix/commit-diff-any-sha branch from 4c5a951 to 74a78c0 Compare September 8, 2026 07:24
Comment on lines +73 to +75
const capabilityPresent = useSessionStore(
(state) => state.sessions[serverId]?.serverInfo?.features?.commitFiles === true,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Capability Missing During Startup

When a retained commit-diff panel is restored during connection startup, serverInfo is still null, but this check treats that unknown state as a missing capability. Because the panel shows the capability warning before its loading state, users can briefly see “Update the host to view commit diffs” even when the daemon supports the feature. Distinguish metadata that has not loaded yet from metadata that has loaded without commitFiles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant