fix(core): always start worktrees as detached (#26931)
This commit is contained in:
@@ -22,11 +22,10 @@ export const WorktreeAdapter: WorkspaceAdapter = {
|
||||
description: "Create a git worktree",
|
||||
async configure(info) {
|
||||
const { AppRuntime, Worktree } = await loadWorktree()
|
||||
const next = await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.makeWorktreeInfo()))
|
||||
const next = await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.makeWorktreeInfo({ detached: true })))
|
||||
return {
|
||||
...info,
|
||||
name: next.name,
|
||||
branch: next.branch,
|
||||
directory: next.directory,
|
||||
}
|
||||
},
|
||||
@@ -38,7 +37,7 @@ export const WorktreeAdapter: WorkspaceAdapter = {
|
||||
svc.createFromInfo({
|
||||
name: config.name,
|
||||
directory: config.directory,
|
||||
branch: config.branch ?? config.name,
|
||||
...(config.branch ? { branch: config.branch } : {}),
|
||||
}),
|
||||
),
|
||||
)
|
||||
@@ -48,9 +47,8 @@ export const WorktreeAdapter: WorkspaceAdapter = {
|
||||
return (await AppRuntime.runPromise(Worktree.Service.use((svc) => svc.list()))).map((info) => ({
|
||||
type: "worktree",
|
||||
name: info.name,
|
||||
branch: info.branch ?? null,
|
||||
branch: info.branch,
|
||||
directory: info.directory,
|
||||
extra: null,
|
||||
projectID: Instance.project.id,
|
||||
}))
|
||||
},
|
||||
|
||||
@@ -7,9 +7,9 @@ export const WorkspaceInfo = Schema.Struct({
|
||||
id: WorkspaceID,
|
||||
type: Schema.String,
|
||||
name: Schema.String,
|
||||
branch: Schema.NullOr(Schema.String),
|
||||
directory: Schema.NullOr(Schema.String),
|
||||
extra: Schema.NullOr(Schema.Unknown),
|
||||
branch: Schema.optional(Schema.NullOr(Schema.String)),
|
||||
directory: Schema.optional(Schema.NullOr(Schema.String)),
|
||||
extra: Schema.optional(Schema.NullOr(Schema.Unknown)),
|
||||
projectID: ProjectID,
|
||||
}).annotate({ identifier: "Workspace" })
|
||||
export type WorkspaceInfo = DeepMutable<Schema.Schema.Type<typeof WorkspaceInfo>>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const Event = {
|
||||
"worktree.ready",
|
||||
Schema.Struct({
|
||||
name: Schema.String,
|
||||
branch: Schema.String,
|
||||
branch: Schema.optional(Schema.String),
|
||||
}),
|
||||
),
|
||||
Failed: BusEvent.define(
|
||||
@@ -42,7 +42,7 @@ export const Event = {
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
name: Schema.String,
|
||||
branch: Schema.String,
|
||||
branch: Schema.optional(Schema.String),
|
||||
directory: Schema.String,
|
||||
}).annotate({ identifier: "Worktree" })
|
||||
export type Info = Schema.Schema.Type<typeof Info>
|
||||
@@ -143,7 +143,7 @@ function failedRemoves(...chunks: string[]) {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface Interface {
|
||||
readonly makeWorktreeInfo: (name?: string) => Effect.Effect<Info>
|
||||
readonly makeWorktreeInfo: (options?: { name?: string; detached?: boolean }) => Effect.Effect<Info>
|
||||
readonly createFromInfo: (info: Info, startCommand?: string) => Effect.Effect<void>
|
||||
readonly create: (input?: CreateInput) => Effect.Effect<Info>
|
||||
readonly list: () => Effect.Effect<(Omit<Info, "branch"> & { branch?: string })[]>
|
||||
@@ -194,25 +194,34 @@ export const layer: Layer.Layer<
|
||||
)
|
||||
|
||||
const MAX_NAME_ATTEMPTS = 26
|
||||
const candidate = Effect.fn("Worktree.candidate")(function* (root: string, base?: string) {
|
||||
const candidate = Effect.fn("Worktree.candidate")(function* (input: {
|
||||
root: string
|
||||
name?: string
|
||||
detached?: boolean
|
||||
}) {
|
||||
const ctx = yield* InstanceState.context
|
||||
for (const attempt of Array.from({ length: MAX_NAME_ATTEMPTS }, (_, i) => i)) {
|
||||
const name = base ? (attempt === 0 ? base : `${base}-${Slug.create()}`) : Slug.create()
|
||||
const branch = `opencode/${name}`
|
||||
const directory = pathSvc.join(root, name)
|
||||
const name = input.name ? (attempt === 0 ? input.name : `${input.name}-${Slug.create()}`) : Slug.create()
|
||||
const branch = input.detached ? undefined : `opencode/${name}`
|
||||
const directory = pathSvc.join(input.root, name)
|
||||
|
||||
if (yield* fs.exists(directory).pipe(Effect.orDie)) continue
|
||||
|
||||
const ref = `refs/heads/${branch}`
|
||||
const branchCheck = yield* git(["show-ref", "--verify", "--quiet", ref], { cwd: ctx.worktree })
|
||||
if (branchCheck.code === 0) continue
|
||||
if (branch) {
|
||||
const ref = `refs/heads/${branch}`
|
||||
const branchCheck = yield* git(["show-ref", "--verify", "--quiet", ref], { cwd: ctx.worktree })
|
||||
if (branchCheck.code === 0) continue
|
||||
}
|
||||
|
||||
return { name, branch, directory }
|
||||
return { name, directory, ...(branch ? { branch } : {}) }
|
||||
}
|
||||
throw new NameGenerationFailedError({ message: "Failed to generate a unique worktree name" })
|
||||
})
|
||||
|
||||
const makeWorktreeInfo = Effect.fn("Worktree.makeWorktreeInfo")(function* (name?: string) {
|
||||
const makeWorktreeInfo = Effect.fn("Worktree.makeWorktreeInfo")(function* (input?: {
|
||||
name?: string
|
||||
detached?: boolean
|
||||
}) {
|
||||
const ctx = yield* InstanceState.context
|
||||
if (ctx.project.vcs !== "git") {
|
||||
throw new NotGitError({ message: "Worktrees are only supported for git projects" })
|
||||
@@ -221,15 +230,17 @@ export const layer: Layer.Layer<
|
||||
const root = pathSvc.join(Global.Path.data, "worktree", ctx.project.id)
|
||||
yield* fs.makeDirectory(root, { recursive: true }).pipe(Effect.orDie)
|
||||
|
||||
const base = name ? slugify(name) : ""
|
||||
return yield* candidate(root, base || undefined)
|
||||
return yield* candidate({ root, name: input?.name ? slugify(input.name) : "", detached: input?.detached })
|
||||
})
|
||||
|
||||
const setup = Effect.fnUntraced(function* (info: Info) {
|
||||
const ctx = yield* InstanceState.context
|
||||
const created = yield* git(["worktree", "add", "--no-checkout", "-b", info.branch, info.directory], {
|
||||
cwd: ctx.worktree,
|
||||
})
|
||||
const created = yield* git(
|
||||
info.branch
|
||||
? ["worktree", "add", "--no-checkout", "-b", info.branch, info.directory]
|
||||
: ["worktree", "add", "--no-checkout", "--detach", info.directory, "HEAD"],
|
||||
{ cwd: ctx.worktree },
|
||||
)
|
||||
if (created.code !== 0) {
|
||||
throw new CreateFailedError({ message: created.stderr || created.text || "Failed to create git worktree" })
|
||||
}
|
||||
@@ -280,7 +291,7 @@ export const layer: Layer.Layer<
|
||||
workspace: workspaceID,
|
||||
payload: {
|
||||
type: Event.Ready.type,
|
||||
properties: { name: info.name, branch: info.branch },
|
||||
properties: { name: info.name, ...(info.branch ? { branch: info.branch } : {}) },
|
||||
},
|
||||
})
|
||||
|
||||
@@ -296,7 +307,7 @@ export const layer: Layer.Layer<
|
||||
})
|
||||
|
||||
const create = Effect.fn("Worktree.create")(function* (input?: CreateInput) {
|
||||
const info = yield* makeWorktreeInfo(input?.name)
|
||||
const info = yield* makeWorktreeInfo({ name: input?.name })
|
||||
yield* createFromInfo(info, input?.startCommand)
|
||||
return info
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user