diff --git a/apps/server/src/server.test.ts b/apps/server/src/server.test.ts index 1fe4d4418fbd..2cf37642ce8a 100644 --- a/apps/server/src/server.test.ts +++ b/apps/server/src/server.test.ts @@ -7605,6 +7605,19 @@ it.layer(NodeServices.layer)("server router seam", (it) => { let branch = "main"; let stagedOnBranch: string | undefined; let detections = 0; + const refreshStatus = () => + Effect.succeed({ + isRepo: true, + hasPrimaryRemote: false, + isDefaultRef: false, + refName: branch, + hasWorkingTreeChanges: false, + workingTree: { files: [], insertions: 0, deletions: 0 }, + hasUpstream: false, + aheadCount: 0, + behindCount: 0, + pr: null, + }); yield* buildAppUnderTest({ layers: { vcsDriver: { @@ -7632,19 +7645,8 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }), }, vcsStatusBroadcaster: { - refreshStatus: () => - Effect.succeed({ - isRepo: true, - hasPrimaryRemote: false, - isDefaultRef: false, - refName: branch, - hasWorkingTreeChanges: false, - workingTree: { files: [], insertions: 0, deletions: 0 }, - hasUpstream: false, - aheadCount: 0, - behindCount: 0, - pr: null, - }), + refreshStatus, + refreshLocalStatus: refreshStatus, }, }, }); @@ -7674,6 +7676,92 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); + it.effect("refreshes staged review status before allowing a branch switch", () => + Effect.gen(function* () { + const refreshStarted = yield* Deferred.make(); + const releaseRefresh = yield* Deferred.make(); + const switchAtLock = yield* Deferred.make(); + const events: string[] = []; + let branch = "main"; + let refreshedBranch: string | undefined; + let detections = 0; + const status = { + isRepo: true, + hasPrimaryRemote: false, + isDefaultRef: false, + hasWorkingTreeChanges: false, + workingTree: { files: [], insertions: 0, deletions: 0 }, + hasUpstream: false, + aheadCount: 0, + behindCount: 0, + pr: null, + }; + yield* buildAppUnderTest({ + layers: { + vcsDriver: { + detectRepository: () => + Effect.gen(function* () { + if (++detections === 2) yield* Deferred.succeed(switchAtLock, undefined); + return null; + }), + isInsideWorkTree: () => Effect.succeed(true), + }, + gitVcsDriver: { + switchRef: ({ refName }) => + Effect.sync(() => { + events.push("switch"); + branch = refName; + return { refName }; + }), + }, + reviewService: { + applyPatch: () => Effect.sync(() => events.push("stage")).pipe(Effect.asVoid), + }, + vcsStatusBroadcaster: { + refreshLocalStatus: () => + Effect.gen(function* () { + yield* Deferred.succeed(refreshStarted, undefined); + yield* Deferred.await(releaseRefresh); + refreshedBranch = branch; + events.push("refresh"); + return { ...status, refName: branch }; + }), + refreshStatus: () => Effect.succeed({ ...status, refName: branch }), + }, + }, + }); + const wsUrl = yield* getWsServerUrl("/ws"); + yield* Effect.scoped( + withWsRpcClient(wsUrl, (client) => + Effect.gen(function* () { + const staging = yield* client[WS_METHODS.reviewApplyPatch]({ + cwd: "/tmp/repo", + sourceKind: "unstaged", + expectedDiffHash: "hash", + fileIndex: 0, + }).pipe(Effect.forkChild); + yield* Effect.raceFirst( + Deferred.await(refreshStarted), + Fiber.join(staging).pipe( + Effect.andThen(Effect.die("Staging finished without refreshing local status.")), + ), + ); + const switching = yield* client[WS_METHODS.vcsSwitchRef]({ + cwd: "/tmp/repo", + refName: "other", + }).pipe(Effect.forkChild); + yield* Deferred.await(switchAtLock); + yield* Deferred.succeed(releaseRefresh, undefined); + yield* Fiber.join(staging); + yield* Fiber.join(switching); + assert.deepEqual(events, ["stage", "refresh", "switch"]); + assert.equal(refreshedBranch, "main"); + }), + ), + ); + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + it.effect("routes websocket rpc git methods", () => Effect.gen(function* () { yield* buildAppUnderTest({ diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 74928935200f..466fcb8b69eb 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -2742,7 +2742,15 @@ const makeWsRpcLayer = ( WS_METHODS.reviewApplyPatch, gitWorkflow.withRepositoryLock( input.cwd, - review.applyPatch(input).pipe(Effect.tap(() => refreshGitStatus(input.cwd))), + review + .applyPatch(input) + .pipe( + Effect.tap(() => + vcsStatusBroadcaster + .refreshLocalStatus(input.cwd) + .pipe(Effect.ignoreCause({ log: true })), + ), + ), ), { "rpc.aggregate": "review" }, ),