fix(coding-agent): preserve VCS cache refresh semantics
(cherry picked from commit 3f3e475109deb7b9e7690720827876fc982b5b8d)
This commit is contained in:
@@ -262,6 +262,7 @@ export class StatusLineComponent implements Component {
|
||||
#cachedBranch: string | null | undefined = undefined;
|
||||
#cachedBranchRepoId: string | null | undefined = undefined;
|
||||
#cachedBranchCwd: string | undefined = undefined;
|
||||
#cachedBranchHasGitRepository = false;
|
||||
// In-flight reftable resolve slot. Ownership is the launch id, not the cwd:
|
||||
// two live resolves can share a cwd string across an invalidation, and a
|
||||
// stale one must never free (or poison) a slot it no longer owns.
|
||||
@@ -677,6 +678,9 @@ export class StatusLineComponent implements Component {
|
||||
// event, re-introducing the render-path spawn churn the async resolve
|
||||
// was designed to avoid. Explicit Git/repository invalidation (watcher
|
||||
// HEAD-move, cwd/repo switch) goes through {@link invalidateGitCaches}.
|
||||
// A tool may open, close, or merge a PR without moving HEAD. Expire the
|
||||
// settled PR context on ordinary activity while leaving HEAD work intact.
|
||||
this.#cachedPrContext = undefined;
|
||||
}
|
||||
#invalidateSessionCaches(): void {
|
||||
this.#clearUsageStartTimer();
|
||||
@@ -700,6 +704,7 @@ export class StatusLineComponent implements Component {
|
||||
this.#cachedBranch = undefined;
|
||||
this.#cachedBranchRepoId = undefined;
|
||||
this.#cachedBranchCwd = undefined;
|
||||
this.#cachedBranchHasGitRepository = false;
|
||||
// Abort before releasing the in-flight slot. Releasing alone would allow
|
||||
// repeated invalidations to fan out still-running git subprocesses.
|
||||
this.#branchResolveActive?.controller.abort();
|
||||
@@ -805,6 +810,7 @@ export class StatusLineComponent implements Component {
|
||||
const prev = this.#cachedBranchCwd === gitCwd ? this.#cachedBranch : undefined;
|
||||
this.#cachedBranchCwd = gitCwd;
|
||||
this.#cachedBranchRepoId = repoId;
|
||||
this.#cachedBranchHasGitRepository = next === null;
|
||||
this.#cachedBranch = next;
|
||||
this.#branchLastFetch = Date.now();
|
||||
if (prev !== next && this.#onBranchChange) this.#onBranchChange();
|
||||
@@ -1397,7 +1403,10 @@ export class StatusLineComponent implements Component {
|
||||
// so it must not be mistaken for an absent Git checkout and fall through to
|
||||
// an ancestor jj workspace.
|
||||
const gitHeadResolvePending = this.#branchResolveActive?.cwd === activeRepoCache.effectiveGitCwd;
|
||||
const gitHeadIsJjLike = !gitHeadResolvePending && (gitBranch === "detached" || gitBranch === null);
|
||||
const gitHeadIsJjLike =
|
||||
!this.#cachedBranchHasGitRepository &&
|
||||
!gitHeadResolvePending &&
|
||||
(gitBranch === "detached" || gitBranch === null);
|
||||
if (includeGit && gitHeadIsJjLike) {
|
||||
gitBranch = this.#getJjBranch(activeRepoCache.effectiveGitCwd) ?? gitBranch;
|
||||
}
|
||||
|
||||
@@ -408,6 +408,32 @@ describe("StatusLineComponent reftable branch resolve honors mid-flight invalida
|
||||
await fs.rm(jjRootDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("does not query an ancestor jj workspace after nested Git HEAD resolution fails", async () => {
|
||||
const fakeRepo = {
|
||||
commonDir: "/nested/.git",
|
||||
gitDir: "/nested/.git",
|
||||
gitEntryPath: "/nested/.git",
|
||||
headPath: "/nested/.git/HEAD",
|
||||
repoRoot: "/nested",
|
||||
} satisfies GitRepository;
|
||||
vi.spyOn(git.repo, "resolveSync").mockReturnValue(fakeRepo);
|
||||
vi.spyOn(git.repo, "isReftableSync").mockReturnValue(true);
|
||||
vi.spyOn(git.head, "resolve").mockResolvedValue(null);
|
||||
vi.spyOn(git.status, "summary").mockReturnValue(Promise.withResolvers<GitStatus | null>().promise);
|
||||
const jjRoot = vi.spyOn(jj.repo, "rootSync").mockReturnValue("/workspace/jj-root");
|
||||
|
||||
const component = new StatusLineComponent(makeSession());
|
||||
component.updateSettings(gitSegment);
|
||||
component.getTopBorder(80);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
component.getTopBorder(80);
|
||||
|
||||
expect(git.head.resolve).toHaveBeenCalledTimes(1);
|
||||
expect(jjRoot).not.toHaveBeenCalled();
|
||||
component.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
describe("StatusLineComponent VCS watcher and jj request lifecycle", () => {
|
||||
|
||||
Reference in New Issue
Block a user