Git commit history (#1534)

* feat(app): add fileView preference for changes panel

* feat(app): add buildDiffTree util for changed-files tree

* feat(app): add changed-files tree directory row

* feat(app): changed-files tree view mode in changes panel (#117)

* feat(protocol): add checkout.commits.list RPC + commitsList feature flag

* feat(server): list branch commits ahead of base with on-remote flags

* feat(server): handle checkout.commits.list RPC and advertise capability

* feat(client): checkout.commits.list method + useCommitsQuery hook

* feat(app): per-commit inline view with local-vs-remote markers (#117)

* feat(protocol,server): per-commit file diff RPC (checkout.commits.file_diff)

* feat(app): open per-commit file diff on click (#117)

* refactor: surface baseRef + commits loading/error, drop dead depth field

* fix(app): flip commit local/remote dot — local hollow, remote filled

* feat(app): list/tree view for expanded commit file list (#117)

* fix(app): syntax-highlight per-commit file diff to match Changes view

* feat(app): draggable resize between commits and diff sections

* refactor(app): dedupe wrap-text helpers into diff-highlighted-text

* fix(app): clean up commits resize drag on unmount + a11y label

* fix(app): collapse commits section by default

* fix(app): render per-commit file diff with the shared Changes line renderer

* perf(app): memoize shared diff line row; drop redundant DiffLineView wrapper

* refactor(app): extract shared DiffFileBody; render commit file diff through it

* fix(app): hide inline-comment affordance in per-commit diffs (no reviewActions)

* feat(app): move commits to a resizable bottom drawer in the Changes panel

* feat(review): commitSha scoping for per-commit review drafts + attachment

* feat(app): per-commit inline comments wired through to the composer (#117)

* refactor(app): own commit file-diff open state in CommitFileList (drop reset effect)

* refactor(app): colocate diff-render cluster under git/diff-file-body/

* feat(app): add diff tab target kind (working/commit diff tabs)

* feat(app): useDiffFiles hook unifying working + commit diff targets

* feat(app): diff tab panel rendering working/commit diffs

* feat(app): open a commit diff tab on commit click; drop per-commit drawer/file list

* feat(app): open/scroll working diff tab on changed-file click

* fix(app): keep commit diff tabs ephemeral; align working diff whitespace

* feat(app): collapsible file sections in the diff tab, collapsed by default

* feat(app): make the on-remote commit dot more subtle

Dims the filled green remote dot (row + legend) to ~0.55 opacity so the
local-only ring stays the state that draws the eye.

* Reshape commit diffs around the existing Changes view

* Fix diff tab migration complexity after rebase

* Address diff tab review findings

* Clean up diff tab review interfaces

* Collapse commit diffs into the existing view

* Load commits when expanded

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
adradr
2026-07-16 17:36:41 +02:00
committed by GitHub
co-authored by Mohamed Boudra
parent 5da6548aff
commit 0d3b717cf3
44 changed files with 2843 additions and 439 deletions
@@ -0,0 +1,91 @@
import { execFileSync } from "child_process";
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "fs";
import { tmpdir } from "os";
import { join } from "path";
import { afterEach, describe, expect, it } from "vitest";
import { getCommitFileDiff } from "./checkout-git.js";
const tempDirs: string[] = [];
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
rmSync(dir, { recursive: true, force: true });
}
});
function makeTempDir(): string {
const dir = realpathSync.native(mkdtempSync(join(tmpdir(), "commit-file-diff-test-")));
tempDirs.push(dir);
return dir;
}
function git(args: string[], cwd: string): string {
return execFileSync("git", args, { cwd }).toString();
}
function commitFile(repoDir: string, name: string, content: string, message: string): void {
writeFileSync(join(repoDir, name), content);
git(["add", "."], repoDir);
git(["-c", "commit.gpgsign=false", "commit", "-m", message], repoDir);
}
function initRepo(): string {
const tempDir = makeTempDir();
const repoDir = join(tempDir, "repo");
mkdirSync(repoDir, { recursive: true });
git(["init", "-b", "main"], repoDir);
git(["config", "user.email", "test@test.com"], repoDir);
git(["config", "user.name", "Test User"], repoDir);
return repoDir;
}
function headSha(repoDir: string): string {
return git(["rev-parse", "HEAD"], repoDir).trim();
}
describe("getCommitFileDiff", () => {
it("returns the parsed diff for a modified file in a commit", async () => {
const repoDir = initRepo();
commitFile(repoDir, "foo.txt", "a\nb\nc\n", "initial");
commitFile(repoDir, "foo.txt", "a\nB\nc\nd\n", "edit foo");
const sha = headSha(repoDir);
const file = await getCommitFileDiff({ cwd: repoDir, sha, path: "foo.txt" });
expect(file).not.toBeNull();
expect(file?.path).toBe("foo.txt");
expect(file?.additions).toBe(2);
expect(file?.deletions).toBe(1);
expect(file?.hunks.length).toBeGreaterThan(0);
const lines = file?.hunks[0]?.lines ?? [];
expect(lines.some((line) => line.type === "add" && line.content === "B")).toBe(true);
expect(lines.some((line) => line.type === "add" && line.content === "d")).toBe(true);
expect(lines.some((line) => line.type === "remove" && line.content === "b")).toBe(true);
});
it("returns a diff flagged as new for an added file", async () => {
const repoDir = initRepo();
commitFile(repoDir, "README.md", "base\n", "initial");
commitFile(repoDir, "added.txt", "x\ny\n", "add file");
const sha = headSha(repoDir);
const file = await getCommitFileDiff({ cwd: repoDir, sha, path: "added.txt" });
expect(file?.path).toBe("added.txt");
expect(file?.isNew).toBe(true);
expect(file?.additions).toBe(2);
expect(file?.deletions).toBe(0);
});
it("returns null for a path not changed in the commit", async () => {
const repoDir = initRepo();
commitFile(repoDir, "foo.txt", "a\n", "initial");
commitFile(repoDir, "foo.txt", "a\nb\n", "edit foo");
const sha = headSha(repoDir);
const file = await getCommitFileDiff({ cwd: repoDir, sha, path: "does-not-exist.txt" });
expect(file).toBeNull();
});
});