Show recent commit history in the explorer (#2312)

* feat(app): show recent commit history in explorer

* fix(app): keep recent commit history accurate

* fix(app): include merged commits in recent history

* perf(app): bound recent commit classification

* fix(app): refresh recent commits on Git updates
This commit is contained in:
Mohamed Boudra
2026-07-21 22:12:33 +02:00
committed by GitHub
parent 2ed1f4f353
commit 309672c8e5
15 changed files with 180 additions and 97 deletions
@@ -52,7 +52,7 @@ function addBareRemote(repoDir: string, tempDir: string): string {
}
describe("listCheckoutCommits", () => {
it("lists commits ahead of base newest-first with on-remote flags and file stats", async () => {
it("lists recent commits newest-first with on-remote flags and file stats", async () => {
const { repoDir, tempDir } = initRepoOnMain();
git(["checkout", "-b", "feature"], repoDir);
commitFile(repoDir, "foo.txt", "a\nb\nc\n", "Add foo");
@@ -65,12 +65,14 @@ describe("listCheckoutCommits", () => {
const { baseRef, commits } = await listCheckoutCommits({ cwd: repoDir });
expect(baseRef).toBe("main");
expect(commits).toHaveLength(2);
expect(commits).toHaveLength(3);
expect(commits[0]?.subject).toBe("Add bar");
expect(commits[1]?.subject).toBe("Add foo");
expect(commits[2]?.subject).toBe("initial");
expect(commits[0]?.isOnRemote).toBe(false);
expect(commits[1]?.isOnRemote).toBe(true);
expect(commits[2]?.isOnRemote).toBe(true);
expect(commits[0]?.files).toEqual([
{ path: "bar.txt", additions: 1, deletions: 0, status: "added" },
@@ -85,11 +87,11 @@ describe("listCheckoutCommits", () => {
expect(Number.isNaN(new Date(commits[0]?.authorDate ?? "").getTime())).toBe(false);
});
it("returns [] when there are no commits ahead of base", async () => {
it("shows recent history on the base branch", async () => {
const { repoDir } = initRepoOnMain();
const { baseRef, commits } = await listCheckoutCommits({ cwd: repoDir });
expect(baseRef).toBeNull();
expect(commits).toEqual([]);
expect(commits.map((entry) => entry.subject)).toEqual(["initial"]);
});
it("marks all commits local-only when there is no remote", async () => {
@@ -101,10 +103,61 @@ describe("listCheckoutCommits", () => {
const { baseRef, commits } = await listCheckoutCommits({ cwd: repoDir });
expect(baseRef).toBe("main");
expect(commits).toHaveLength(2);
expect(commits).toHaveLength(3);
expect(commits.every((c) => c.isOnRemote === false)).toBe(true);
});
it("recognizes base history on a remote before the feature branch is pushed", async () => {
const { repoDir, tempDir } = initRepoOnMain();
addBareRemote(repoDir, tempDir);
git(["push", "-u", "origin", "main"], repoDir);
git(["checkout", "-b", "feature"], repoDir);
commitFile(repoDir, "feature.txt", "local\n", "Local feature");
const { commits } = await listCheckoutCommits({ cwd: repoDir });
expect(commits.map(({ subject, isOnRemote }) => ({ subject, isOnRemote }))).toEqual([
{ subject: "Local feature", isOnRemote: false },
{ subject: "initial", isOnRemote: true },
]);
});
it("limits history and unpushed classification to the 20 most recent commits", async () => {
const { repoDir } = initRepoOnMain();
for (let index = 1; index <= 24; index += 1) {
commitFile(repoDir, "history.txt", `${index}\n`, `Commit ${index}`);
}
const { commits } = await listCheckoutCommits({ cwd: repoDir });
expect(commits).toHaveLength(20);
expect(commits.every((entry) => entry.isOnRemote === false)).toBe(true);
expect(commits.map((entry) => entry.subject)).toEqual(
Array.from({ length: 20 }, (_, index) => `Commit ${24 - index}`),
);
});
it("shows merged branch commits and compares the merge against its first parent", async () => {
const { repoDir } = initRepoOnMain();
git(["checkout", "-b", "feature"], repoDir);
commitFile(repoDir, "feature.txt", "feature\n", "Add feature");
git(["checkout", "main"], repoDir);
commitFile(repoDir, "main.txt", "main\n", "Advance main");
git(["merge", "--no-ff", "feature", "-m", "Merge feature"], repoDir);
const { commits } = await listCheckoutCommits({ cwd: repoDir });
expect(commits.map((entry) => entry.subject)).toEqual([
"Merge feature",
"Advance main",
"Add feature",
"initial",
]);
expect(commits[0]?.files).toEqual([
{ path: "feature.txt", additions: 1, deletions: 0, status: "added" },
]);
});
it("classifies renamed files with status renamed and correct destination path", async () => {
const { repoDir } = initRepoOnMain();
git(["checkout", "-b", "feature"], repoDir);