mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-27 09:53:14 +00:00
refactor(core): move mercurial vcs into internal plugin (#44993)
This commit is contained in:
parent
0ab2d783e8
commit
b71291c05a
4 changed files with 58 additions and 28 deletions
|
|
@ -83,6 +83,7 @@ import { ProviderPlugins } from "./provider.js"
|
|||
import { WebSearchPlugins } from "./websearch/index.js"
|
||||
import { PluginRuntime } from "./runtime.js"
|
||||
import { SkillPlugin } from "./skill.js"
|
||||
import { VcsHgPlugin } from "./vcs/hg.js"
|
||||
import { SystemPromptPlugin } from "./system-prompt.js"
|
||||
import { VariantPlugin } from "./variant.js"
|
||||
import { VcsGitPlugin } from "./vcs/git.js"
|
||||
|
|
@ -241,6 +242,7 @@ const pre = [
|
|||
PlanPlugin.Plugin,
|
||||
CommandPlugin.Plugin,
|
||||
SkillPlugin.Plugin,
|
||||
VcsHgPlugin.Plugin,
|
||||
...SystemPromptPlugin.Plugins,
|
||||
ModelsDevPlugin,
|
||||
...ProviderPlugins,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
export * as VcsHg from "./hg.js"
|
||||
export * as VcsHgPlugin from "./hg.js"
|
||||
|
||||
import path from "path"
|
||||
import { Effect } from "effect"
|
||||
import { ChildProcess } from "effect/unstable/process"
|
||||
import { define } from "@opencode-ai/plugin/effect/plugin"
|
||||
import { FileDiff } from "@opencode-ai/schema/file-diff"
|
||||
import { FileStatus, Info, Mode } from "@opencode-ai/schema/vcs"
|
||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||
import { AppProcess } from "@opencode-ai/util/process"
|
||||
import type { Adapter, DiffOptions } from "../vcs.js"
|
||||
import { Location } from "../../location.js"
|
||||
import type { Adapter, DiffOptions } from "../../vcs.js"
|
||||
import {
|
||||
addPatch,
|
||||
chunksByFile,
|
||||
|
|
@ -17,14 +19,40 @@ import {
|
|||
MAX_PATCH_BYTES,
|
||||
MAX_TOTAL_PATCH_BYTES,
|
||||
PATCH_CONTEXT_LINES,
|
||||
} from "./patch.js"
|
||||
} from "../../vcs/patch.js"
|
||||
|
||||
export const Plugin = define({
|
||||
id: "opencode.vcs.hg",
|
||||
effect: Effect.fn("VcsHgPlugin")(function* (ctx) {
|
||||
const location = yield* Location.Service
|
||||
if (location.vcs?.type !== "hg") return
|
||||
|
||||
const processes = yield* AppProcess.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
const adapter = make(processes, fs, {
|
||||
directory: location.directory,
|
||||
worktree: location.project.directory,
|
||||
})
|
||||
|
||||
yield* ctx.vcs.transform((draft) => {
|
||||
draft.add({
|
||||
id: "hg",
|
||||
name: "Mercurial",
|
||||
info: () => adapter.info(),
|
||||
branches: (input) => adapter.branches({ search: input.search, limit: input.limit }),
|
||||
status: () => adapter.status(),
|
||||
diff: (input) => adapter.diff(input.mode, { context: input.context }),
|
||||
})
|
||||
})
|
||||
}),
|
||||
})
|
||||
|
||||
/**
|
||||
* Mercurial adapter for the Vcs service. `hg diff --git` emits git-format
|
||||
* patches for tracked changes; untracked (`?`) and missing (`!`) files never
|
||||
* appear in `hg diff`, so their patches are synthesized from file contents.
|
||||
*/
|
||||
export function make(
|
||||
function make(
|
||||
proc: AppProcess.Interface,
|
||||
fs: FSUtil.Interface,
|
||||
input: { directory: string; worktree: string },
|
||||
|
|
@ -10,10 +10,8 @@ import { VcsEvent } from "@opencode-ai/schema/vcs-event"
|
|||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||
import { Location } from "./location.js"
|
||||
import { AppProcess } from "@opencode-ai/util/process"
|
||||
import { Bus } from "./bus.js"
|
||||
import { State } from "./state.js"
|
||||
import { VcsHg } from "./vcs/hg.js"
|
||||
import { emptyPatch, MAX_TOTAL_PATCH_BYTES, PATCH_CONTEXT_LINES } from "./vcs/patch.js"
|
||||
|
||||
export { BranchList, FileStatus, Info, Mode }
|
||||
|
|
@ -43,24 +41,14 @@ interface Data {
|
|||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Vcs") {}
|
||||
|
||||
// Adapter seam: one working-copy implementation per VCS type, selected by the
|
||||
// resolved location. Locations without a supported VCS degrade to empty
|
||||
// results so callers never need to special-case.
|
||||
const adapter = (proc: AppProcess.Interface, fs: FSUtil.Interface, location: Location.Interface) => {
|
||||
const scope = { directory: location.directory, worktree: location.project.directory }
|
||||
if (location.vcs?.type === "hg") return VcsHg.make(proc, fs, scope)
|
||||
}
|
||||
|
||||
const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const proc = yield* AppProcess.Service
|
||||
const fs = yield* FSUtil.Service
|
||||
const location = yield* Location.Service
|
||||
const bus = yield* Bus.Service
|
||||
const native = adapter(proc, fs, location)
|
||||
const vcs = location.vcs
|
||||
const current = { info: native ? yield* native.info() : ({ branch: {} } satisfies Info) }
|
||||
const current: { info: Info } = { info: { branch: {} } }
|
||||
const scope = {
|
||||
directory: location.directory,
|
||||
worktree: location.project.directory,
|
||||
|
|
@ -102,9 +90,7 @@ const layer = Layer.effect(
|
|||
const provider = selected()
|
||||
const next: Info = provider
|
||||
? yield* protect(provider, "info", provider.info(scope).pipe(Effect.flatMap(decodeInfo)), { branch: {} })
|
||||
: native
|
||||
? yield* native.info()
|
||||
: { branch: {} }
|
||||
: { branch: {} }
|
||||
const changed = current.info.branch.current !== next.branch.current
|
||||
current.info = next
|
||||
if (changed) yield* bus.publish(VcsEvent.BranchUpdated, { branch: next.branch.current })
|
||||
|
|
@ -140,8 +126,7 @@ const layer = Layer.effect(
|
|||
provider.branches({ ...scope, ...options }).pipe(Effect.flatMap(decodeBranches)),
|
||||
[],
|
||||
)
|
||||
if (!native) return []
|
||||
return yield* native.branches(options)
|
||||
return []
|
||||
}),
|
||||
status: Effect.fn("Vcs.status")(function* () {
|
||||
const provider = selected()
|
||||
|
|
@ -155,12 +140,11 @@ const layer = Layer.effect(
|
|||
),
|
||||
[],
|
||||
)
|
||||
if (!native) return []
|
||||
return yield* native.status()
|
||||
return []
|
||||
}),
|
||||
diff: Effect.fn("Vcs.diff")(function* (mode: Mode, options?: DiffOptions) {
|
||||
const provider = selected()
|
||||
if (!provider) return native ? yield* native.diff(mode, options) : []
|
||||
if (!provider) return []
|
||||
const rows = yield* protect(
|
||||
provider,
|
||||
"diff",
|
||||
|
|
@ -189,5 +173,5 @@ const layer = Layer.effect(
|
|||
export const node = makeLocationNode({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [AppProcess.node, FSUtil.node, Location.node, Bus.node],
|
||||
deps: [FSUtil.node, Location.node, Bus.node],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,21 +4,25 @@ import fs from "fs/promises"
|
|||
import path from "path"
|
||||
import { Effect, Fiber, Layer, Stream } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
|
||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||
import { AppProcess } from "@opencode-ai/util/process"
|
||||
import { Bus } from "@opencode-ai/core/bus"
|
||||
import { Location } from "@opencode-ai/core/location"
|
||||
import { AbsolutePath } from "@opencode-ai/core/schema"
|
||||
import { Vcs } from "@opencode-ai/core/vcs"
|
||||
import { VcsHgPlugin } from "@opencode-ai/core/plugin/vcs/hg"
|
||||
import { FileSystem } from "@opencode-ai/schema/filesystem"
|
||||
import { VcsEvent } from "@opencode-ai/schema/vcs-event"
|
||||
import { location } from "./fixture/location"
|
||||
import { tmpdir } from "./fixture/tmpdir"
|
||||
import { it } from "./lib/effect"
|
||||
import { host } from "./plugin/host"
|
||||
|
||||
const describeHg = Bun.which("hg") ? describe : describe.skip
|
||||
|
||||
const provide = (directory: string) =>
|
||||
Effect.provide(
|
||||
LayerNode.compile(LayerNode.group([Vcs.node, Bus.node]), [
|
||||
LayerNode.compile(LayerNode.group([Vcs.node, Bus.node, Location.node, AppProcess.node, FSUtil.node]), [
|
||||
[
|
||||
Location.node,
|
||||
Layer.succeed(
|
||||
|
|
@ -42,7 +46,19 @@ const withTmp = <A, E, R>(f: (directory: string) => Effect.Effect<A, E, R>) =>
|
|||
|
||||
const withHg = <A, E, R>(f: (directory: string) => Effect.Effect<A, E, R>) =>
|
||||
withTmp((directory) =>
|
||||
Effect.promise(() => hg(directory, "init")).pipe(Effect.andThen(f(directory).pipe(provide(directory)))),
|
||||
Effect.promise(() => hg(directory, "init")).pipe(
|
||||
Effect.andThen(
|
||||
Effect.gen(function* () {
|
||||
const vcs = yield* Vcs.Service
|
||||
const context = host()
|
||||
yield* VcsHgPlugin.Plugin.effect({
|
||||
...context,
|
||||
vcs: { ...context.vcs, transform: vcs.transform, reload: vcs.reload },
|
||||
})
|
||||
return yield* f(directory)
|
||||
}).pipe(provide(directory)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
async function hg(directory: string, ...args: string[]) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue