Compare commits

...
Author SHA1 Message Date
l0ng-ai 5a7ac7547f fix(vcs): keep a commit's details shown while a refresh reloads them
The Git log's commit details (containing branches, signature, refs) were keyed on
the repository's refresh version and dropped as soon as it changed, so each
refresh blanked the section until the reload finished. With something writing to
the repository's .git often, the section blinked continuously.

useCommitDetails now keeps the last details of the same commit until the reloaded
ones arrive; another commit still starts empty.
2026-11-13 13:00:49 +08:00
2 changed files with 50 additions and 6 deletions

No files matched your search

+37 -1
View File
@@ -12,7 +12,8 @@ vi.mock("@/platform/ipc", async (original) => {
},
};
});
import { loadLogDetails } from "../logDetails";
import { act, renderHook } from "@testing-library/react";
import { loadLogDetails, useCommitDetails } from "../logDetails";
import { useVcs } from "../store";
import { useGitLog } from "../logStore";
const commit = (id: string): Commit => ({
@@ -92,3 +93,38 @@ describe("commit detail jobs", () => {
await Promise.all([first, second]);
});
});
describe("useCommitDetails", () => {
it("keeps the shown details while a repository refresh reloads them", async () => {
const c = commit("kept-on-refresh");
const { result, rerender } = renderHook(({ c }) => useCommitDetails(c), { initialProps: { c } });
await vi.waitFor(() => expect(result.current.details?.message).toBe("kept-on-refresh"));
let finish!: () => void;
api.batch.mockImplementationOnce(
(_repo: string, ids: string[]) =>
new Promise((resolve) => {
finish = () =>
resolve({
status: "ok",
data: ids.map(() => ({
message: "reloaded",
containingBranches: ["main"],
signature: "N",
signer: "",
key: "",
fingerprint: "",
})),
});
}),
);
act(() => useVcs.setState((s) => ({ version: s.version + 1 })));
await vi.waitFor(() => expect(api.batch).toHaveBeenCalledTimes(2));
expect(result.current.details?.message).toBe("kept-on-refresh");
await act(async () => finish());
await vi.waitFor(() => expect(result.current.details?.message).toBe("reloaded"));
// Another commit shows nothing until its own details arrive.
api.batch.mockImplementationOnce(() => new Promise(() => {}));
rerender({ c: commit("another") });
expect(result.current.details).toBeNull();
});
});
+13 -5
View File
@@ -126,20 +126,28 @@ export function useCommitDetails(commit: Commit, enabled = true) {
const version = useVcs((s) => s.version);
const repo = commitRepo(commit) ?? currentRepo();
const refs = refVersions((s) => s[repo ?? ""] ?? 0);
const key = `${repo}\0${commit.id}\0${version}\0${refs}`;
const [result, setResult] = useState<{ key: string; details: LogDetails | null; error: boolean } | null>(null);
const target = `${repo}\0${commit.id}`;
const key = `${target}\0${version}\0${refs}`;
const [result, setResult] = useState<{
target: string;
details: LogDetails | null;
error: boolean;
} | null>(null);
useEffect(() => {
if (!enabled) return;
const controller = new AbortController();
void loadLogDetails(commit, controller.signal)
.then((details) => {
if (!controller.signal.aborted) setResult({ key, details, error: false });
if (!controller.signal.aborted) setResult({ target, details, error: false });
})
.catch(() => {
if (!controller.signal.aborted) setResult({ key, details: null, error: true });
if (!controller.signal.aborted) setResult({ target, details: null, error: true });
});
return () => controller.abort();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key, enabled]);
return { details: result?.key === key ? result.details : null, error: result?.key === key && result.error };
// A repository refresh reloads the same commit's details: keep showing the last ones until
// the new ones arrive, or the section blinks on every refresh.
const current = result?.target === target ? result : null;
return { details: current?.details ?? null, error: !!current?.error };
}