fix(opencode): dispose booted worktrees before removal
This commit is contained in:
@@ -19,6 +19,7 @@ export interface Interface {
|
||||
readonly load: (input: LoadInput) => Effect.Effect<InstanceContext>
|
||||
readonly reload: (input: LoadInput) => Effect.Effect<InstanceContext>
|
||||
readonly dispose: (ctx: InstanceContext) => Effect.Effect<void>
|
||||
readonly disposeDirectory: (directory: string) => Effect.Effect<void>
|
||||
readonly disposeAll: () => Effect.Effect<void>
|
||||
readonly provide: <A, E, R>(input: LoadInput, effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
|
||||
}
|
||||
@@ -151,6 +152,15 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
yield* disposeEntry(ctx.directory, entry, ctx).pipe(Effect.asVoid)
|
||||
})
|
||||
|
||||
const disposeDirectory = Effect.fn("InstanceStore.disposeDirectory")(function* (input: string) {
|
||||
const directory = AppFileSystem.resolve(input)
|
||||
const entry = cache.get(directory)
|
||||
if (!entry) return
|
||||
const exit = yield* Deferred.await(entry.deferred).pipe(Effect.exit)
|
||||
if (Exit.isFailure(exit)) return yield* removeEntry(directory, entry).pipe(Effect.asVoid)
|
||||
yield* disposeEntry(directory, entry, exit.value).pipe(Effect.asVoid)
|
||||
})
|
||||
|
||||
const disposeAllOnce = Effect.fnUntraced(function* () {
|
||||
yield* Effect.logInfo("disposing all instances")
|
||||
yield* Effect.forEach(
|
||||
@@ -185,6 +195,7 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
||||
load,
|
||||
reload,
|
||||
dispose,
|
||||
disposeDirectory,
|
||||
disposeAll,
|
||||
provide,
|
||||
})
|
||||
|
||||
@@ -395,6 +395,9 @@ export const layer: Layer.Layer<
|
||||
|
||||
const directory = yield* canonical(input.directory)
|
||||
|
||||
// Bootstrapped worktrees may hold file handles, which must be released before removal on Windows.
|
||||
if (directory !== (yield* canonical(ctx.worktree))) yield* store.disposeDirectory(directory)
|
||||
|
||||
const list = yield* git(["worktree", "list", "--porcelain"], { cwd: ctx.worktree })
|
||||
if (list.code !== 0) {
|
||||
return yield* new RemoveFailedError({ message: list.stderr || list.text || "Failed to read git worktrees" })
|
||||
|
||||
@@ -5,10 +5,8 @@ import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect"
|
||||
import { GlobalBus, type GlobalEvent } from "../../src/bus/global"
|
||||
import { Git } from "../../src/git"
|
||||
import { InstanceRef } from "../../src/effect/instance-ref"
|
||||
import { InstanceRuntime } from "../../src/project/instance-runtime"
|
||||
import { Worktree } from "../../src/worktree"
|
||||
import { disposeAllInstances, provideInstance, TestInstance } from "../fixture/fixture"
|
||||
import { disposeAllInstances, TestInstance } from "../fixture/fixture"
|
||||
import { testEffect } from "../lib/effect"
|
||||
|
||||
const it = testEffect(
|
||||
@@ -41,11 +39,6 @@ const waitReady = Effect.fn("WorktreeTest.waitReady")(function* () {
|
||||
const removeCreatedWorktree = (directory: string) =>
|
||||
Effect.gen(function* () {
|
||||
const svc = yield* Worktree.Service
|
||||
const ctx = yield* Effect.gen(function* () {
|
||||
return yield* InstanceRef
|
||||
}).pipe(provideInstance(directory))
|
||||
if (!ctx) return yield* Effect.die(new Error("missing test instance"))
|
||||
yield* Effect.promise(() => InstanceRuntime.disposeInstance(ctx))
|
||||
const ok = yield* svc.remove({ directory })
|
||||
if (!ok) return yield* Effect.fail(new Error(`failed to remove worktree ${directory}`))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user