Show workspace commits clearly in Changes (#2350)

* feat(commits): distinguish workspace history from base commits

Keep every workspace commit visible while bounding base history to ten
context commits, and make push and base state readable in the commit rail.

* fix(commits): preserve history when base refs disappear

Fall back to recent HEAD history for stale saved bases, and reject truncated
git output instead of presenting an incomplete commit list.

* fix(commits): classify history against the current base

Use the furthest-ahead local or remote base for classification, and re-infer
base history when saved worktree metadata points to a deleted branch.
This commit is contained in:
Mohamed Boudra
2026-07-23 12:17:48 +02:00
committed by GitHub
parent 31c8dc3f05
commit 8cf70d10bf
19 changed files with 372 additions and 110 deletions

View File

@@ -35,6 +35,7 @@ describe("checkout.commits.list schemas", () => {
authorName: "Ada",
authorDate: "2026-06-13T10:00:00.000Z",
isOnRemote: true,
isOnBase: false,
files: [
{ path: "src/a.ts", additions: 10, deletions: 2, status: "modified" },
{ path: "src/b.ts", additions: 5, deletions: 0, status: "added" },
@@ -47,6 +48,7 @@ describe("checkout.commits.list schemas", () => {
authorName: "Ada",
authorDate: "2026-06-13T11:00:00.000Z",
isOnRemote: false,
isOnBase: true,
files: [{ path: "src/c.ts", additions: 1, deletions: 1 }],
},
],
@@ -61,10 +63,37 @@ describe("checkout.commits.list schemas", () => {
expect(parsed.payload).toEqual(payload);
expect(parsed.payload.commits[0]?.isOnRemote).toBe(true);
expect(parsed.payload.commits[0]?.isOnBase).toBe(false);
expect(parsed.payload.commits[1]?.isOnRemote).toBe(false);
expect(parsed.payload.commits[1]?.isOnBase).toBe(true);
expect(parsed.payload.commits[1]?.files[0]?.status).toBeUndefined();
});
test("still parses commits from hosts without base classification", () => {
const parsed = CheckoutCommitsListResponseSchema.parse({
type: "checkout.commits.list.response",
payload: {
cwd: "/tmp/repo",
baseRef: "main",
commits: [
{
sha: "1111111111111111111111111111111111111111",
shortSha: "1111111",
subject: "Legacy commit",
authorName: "Ada",
authorDate: "2026-06-13T10:00:00.000Z",
isOnRemote: true,
files: [],
},
],
error: null,
requestId: "request-commits",
},
});
expect(parsed.payload.commits[0]?.isOnBase).toBeUndefined();
});
test("accepts a null baseRef and an error payload", () => {
const payload = {
cwd: "/tmp/repo",
@@ -107,16 +136,17 @@ describe("checkout.commits.list schemas", () => {
).toMatchObject({ type: "checkout.commits.list.response" });
});
test("accepts the commitsList server_info feature flag", () => {
test("accepts the commit history server_info feature flags", () => {
expect(
ServerInfoStatusPayloadSchema.parse({
status: "server_info",
serverId: "srv_test",
features: {
commitsList: true,
commitBaseClassification: true,
},
}).features,
).toEqual({ commitsList: true });
).toEqual({ commitsList: true, commitBaseClassification: true });
});
test("still parses server_info without the commitsList feature flag", () => {

View File

@@ -1732,6 +1732,8 @@ const CheckoutCommitSchema = z.object({
authorName: z.string(),
authorDate: z.string(), // ISO 8601
isOnRemote: z.boolean(), // false = local-only (unpushed)
// COMPAT(commitBaseClassification): added in v0.2.0, remove optional after 2027-01-23.
isOnBase: z.boolean().optional(),
files: z.array(CheckoutCommitFileSchema),
});
@@ -2777,6 +2779,8 @@ export const ServerInfoStatusPayloadSchema = z
projectCreateDirectory: z.boolean().optional(),
// COMPAT(commitsList): added in v0.1.110, remove gate after 2027-01-16.
commitsList: z.boolean().optional(),
// COMPAT(commitBaseClassification): added in v0.2.0, remove gate after 2027-01-23.
commitBaseClassification: z.boolean().optional(),
// COMPAT(providerRemoval): added in v0.1.105, drop the gate when floor >= v0.1.105.
providerRemoval: z.boolean().optional(),
// COMPAT(importSessionWorkspaceTarget): added in v0.1.110, remove gate after 2027-01-16.