fix(server): establish Git identities at observation setup

Read-only checkout refreshes should not resolve watcher namespaces. Observation tests await setup and compare cross-workspace fan-out without depending on completion order.
This commit is contained in:
Mohamed Boudra
2026-10-26 18:05:32 +01:00
parent a5f246a18c
commit d78de43e4d
4 changed files with 29 additions and 18 deletions
@@ -530,6 +530,8 @@ test("a late Git-ignored tree is pruned while tracked changes still notify consu
test("Git refresh, command tracing and 10,000 ignored alias events do no synchronous IO", async () => {
const { execFile } = await import("node:child_process");
const { promisify } = await import("node:util");
const { fileURLToPath } = await import("node:url");
const repositoryRoot = fileURLToPath(new URL("../../../../", import.meta.url));
const source = `
import assert from 'node:assert/strict';
import { mkdtemp, mkdir, writeFile, symlink, rm } from 'node:fs/promises';
@@ -612,7 +614,7 @@ test("Git refresh, command tracing and 10,000 ignored alias events do no synchro
source,
],
{
cwd: process.cwd(),
cwd: repositoryRoot,
env: { ...process.env, GIT_IO_TRACE: tracing ? "1" : "" },
timeout: 30_000,
maxBuffer: 4 * 1024 * 1024,
@@ -1189,7 +1189,7 @@ describe("WorkspaceGitService checkout observation", () => {
]);
await vi.advanceTimersByTimeAsync(1_000);
await vi.waitFor(() => {
expect(getCalledCwds(getCheckoutStatus)).toEqual([WORKTREE_A, WORKTREE_B]);
expect(getCalledCwds(getCheckoutStatus).sort()).toEqual([WORKTREE_A, WORKTREE_B].sort());
});
first.unsubscribe();
@@ -1624,6 +1624,12 @@ describe("WorkspaceGitService checkout observation", () => {
const subscription = service.registerWorkspace({ cwd: REPO_CWD }, vi.fn());
await vi.waitFor(() => {
expect(watcher.subscribe).toHaveBeenCalledWith(
GIT_DIR,
expect.any(Function),
expect.any(Object),
);
expect(getCheckoutStatus).toHaveBeenCalledTimes(1);
expect(service.getMetrics().workspaceObservationSetupInFlightCount).toBe(0);
expect(service.getMetrics().workspaceRefreshInFlightCount).toBe(0);
});
@@ -1642,14 +1648,17 @@ describe("WorkspaceGitService checkout observation", () => {
[60_000, 6],
] as const) {
await vi.advanceTimersByTimeAsync(elapsedMs);
expect(pollCount()).toBe(expectedPolls);
await vi.waitFor(() => {
expect(pollCount()).toBe(expectedPolls);
expect(service.getMetrics().workspaceRefreshInFlightCount).toBe(0);
});
}
isDirty = true;
await vi.advanceTimersByTimeAsync(60_000);
expect(pollCount()).toBe(7);
await vi.waitFor(() => expect(pollCount()).toBe(7));
await vi.advanceTimersByTimeAsync(5_000);
expect(pollCount()).toBe(8);
await vi.waitFor(() => expect(pollCount()).toBe(8));
subscription.unsubscribe();
service.dispose();
@@ -1860,8 +1869,10 @@ describe("WorkspaceGitService checkout observation", () => {
expect(watcher.subscribe).toHaveBeenCalledTimes(1);
});
await vi.advanceTimersByTimeAsync(35_000);
expect(service.getMetrics().workingTreeWatchSetupInFlightCount).toBe(0);
expect(service.getMetrics().workspaceObservationSetupAdmissionActiveCount).toBe(0);
await vi.waitFor(() => {
expect(service.getMetrics().workingTreeWatchSetupInFlightCount).toBe(0);
expect(service.getMetrics().workspaceObservationSetupAdmissionActiveCount).toBe(0);
});
expect(getWatcherSubscribeCallCount(watcher, REPO_CWD)).toBe(1);
openedSubscription.resolve({
@@ -991,7 +991,7 @@ describe("WorkspaceGitServiceImpl primitive refresh entrypoint", () => {
await vi.advanceTimersByTimeAsync(60_000);
await flushPromises();
expect(subscribe).toHaveBeenCalledTimes(2);
await vi.waitFor(() => expect(subscribe).toHaveBeenCalledTimes(2));
expect(getCheckoutStatus).toHaveBeenCalledTimes(1);
subscription.unsubscribe();
@@ -2221,7 +2221,7 @@ describe("WorkspaceGitServiceImpl D2 read methods", () => {
expect(getCheckoutStatus).toHaveBeenCalledTimes(2);
expect(getPullRequestStatus).toHaveBeenCalledTimes(2);
service.dispose();
await service.dispose();
});
test("onWorkspaceStateMayHaveChanged preserves includeForge when a file watcher fires within the debounce window", async () => {
@@ -1301,6 +1301,11 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
}
await this.promoteWorkingTreeWatchTarget(workingTreeTarget, facts.worktreeRoot);
const gitDir = facts.absoluteGitDir;
const gitDirPaths = target.gitDirPaths?.matches(gitDir)
? target.gitDirPaths
: await GitObservationPaths.create(gitDir);
if (!this.isActiveObservedWorkspaceTarget(target)) return;
target.gitDirPaths = gitDirPaths;
const repoGitRoot = facts.gitCommonDir ?? (await this.resolveWorkspaceGitRefsRoot(gitDir));
if (!this.isActiveObservedWorkspaceTarget(target)) {
return;
@@ -1332,15 +1337,8 @@ export class WorkspaceGitServiceImpl implements WorkspaceGitService {
const promise = this.deps
.getCheckoutSnapshotFacts(target.cwd, context)
.then(async (facts) => {
const paths =
facts.isGit && facts.absoluteGitDir
? await GitObservationPaths.create(facts.absoluteGitDir)
: null;
if (!target.closed) {
target.gitDirPaths = paths;
target.latestFacts = facts;
}
.then((facts) => {
if (!target.closed) target.latestFacts = facts;
return facts;
})
.finally(() => {