refactor(core): move git vcs into internal plugin (#44992)

This commit is contained in:
Shoubhit Dash
2026-08-25 20:26:43 +05:30
committed by GitHub
parent 3deac93d27
commit 0ab2d783e8
4 changed files with 51 additions and 11 deletions
+2
View File
@@ -85,6 +85,7 @@ import { PluginRuntime } from "./runtime.js"
import { SkillPlugin } from "./skill.js"
import { SystemPromptPlugin } from "./system-prompt.js"
import { VariantPlugin } from "./variant.js"
import { VcsGitPlugin } from "./vcs/git.js"
import { WarmingPlugin } from "./warming.js"
import { WellKnownPlugin } from "../wellknown/plugin.js"
@@ -235,6 +236,7 @@ const pre = [
ConfigMCPPlugin.Plugin,
MCPCodeModeExclusionPlugin.Plugin,
WellKnownPlugin.Plugin,
VcsGitPlugin.Plugin,
AgentPlugin.Plugin,
PlanPlugin.Plugin,
CommandPlugin.Plugin,
@@ -1,20 +1,47 @@
export * as VcsGit from "./git.js"
export * as VcsGitPlugin from "./git.js"
import { define } from "@opencode-ai/plugin/effect/plugin"
import { Effect } from "effect"
import { ChildProcess } from "effect/unstable/process"
import { FileDiff } from "@opencode-ai/schema/file-diff"
import { BranchList, FileStatus, Info, Mode } from "@opencode-ai/schema/vcs"
import { AppProcess } from "@opencode-ai/util/process"
import type { Adapter, BranchOptions, DiffOptions } from "../vcs.js"
import { chunksByFile, emptyPatch, MAX_PATCH_BYTES, MAX_TOTAL_PATCH_BYTES, PATCH_CONTEXT_LINES } from "./patch.js"
import type { Patch } from "./patch.js"
import { Location } from "../../location.js"
import type { Adapter, BranchOptions, DiffOptions } from "../../vcs.js"
import { chunksByFile, emptyPatch, MAX_PATCH_BYTES, MAX_TOTAL_PATCH_BYTES, PATCH_CONTEXT_LINES } from "../../vcs/patch.js"
import type { Patch } from "../../vcs/patch.js"
export const Plugin = define({
id: "opencode.vcs.git",
effect: Effect.fn("VcsGitPlugin")(function* (ctx) {
const location = yield* Location.Service
if (location.vcs?.type !== "git") return
const processes = yield* AppProcess.Service
const adapter = make(processes, {
directory: location.directory,
worktree: location.project.directory,
})
yield* ctx.vcs.transform((draft) => {
draft.add({
id: "git",
name: "Git",
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 }),
})
})
}),
})
/**
* Git adapter for the Vcs service. Ported from the V1 pipeline: patches are
* batched through one `git diff` invocation where possible and capped by
* per-file and total byte budgets, falling back to empty patches when capped.
*/
export function make(proc: AppProcess.Interface, input: { directory: string; worktree: string }): Adapter {
function make(proc: AppProcess.Interface, input: { directory: string; worktree: string }): Adapter {
// Listing commands scope pathspecs to the requested directory; per-file
// commands run from the worktree root because git lists root-relative paths.
const ctx: Ctx = { git: makeGit(proc), directory: input.directory, worktree: input.worktree }
+2 -4
View File
@@ -13,7 +13,6 @@ import { Location } from "./location.js"
import { AppProcess } from "@opencode-ai/util/process"
import { Bus } from "./bus.js"
import { State } from "./state.js"
import { VcsGit } from "./vcs/git.js"
import { VcsHg } from "./vcs/hg.js"
import { emptyPatch, MAX_TOTAL_PATCH_BYTES, PATCH_CONTEXT_LINES } from "./vcs/patch.js"
@@ -49,7 +48,6 @@ export class Service extends Context.Service<Service, Interface>()("@opencode/Vc
// 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 === "git") return VcsGit.make(proc, scope)
if (location.vcs?.type === "hg") return VcsHg.make(proc, fs, scope)
}
@@ -69,7 +67,7 @@ const layer = Layer.effect(
canonical: location.project.canonical,
...(vcs ? { store: vcs.store } : {}),
}
const decodeInfo = Schema.decodeUnknownEffect(Info)
const decodeInfo = Schema.decodeUnknownEffect(Schema.toType(Info))
const decodeBranches = Schema.decodeUnknownEffect(BranchList)
const decodeStatus = Schema.decodeUnknownEffect(Schema.Array(FileStatus))
const decodeDiff = Schema.decodeUnknownEffect(Schema.Array(FileDiff.Info))
@@ -112,7 +110,7 @@ const layer = Layer.effect(
if (changed) yield* bus.publish(VcsEvent.BranchUpdated, { branch: next.branch.current })
})
if (vcs && native) {
if (vcs) {
const store = yield* fs.realPath(vcs.store).pipe(Effect.orElseSucceed(() => vcs.store))
const isBranchMetadata =
vcs.type === "git"
+15 -2
View File
@@ -4,20 +4,23 @@ import fs from "fs/promises"
import path from "path"
import { Cause, Effect, Exit, Fiber, Layer, Stream } from "effect"
import { LayerNode } from "@opencode-ai/util/effect/layer-node"
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 { VcsGitPlugin } from "@opencode-ai/core/plugin/vcs/git"
import type { VcsDefinition, VcsDiffInput } from "@opencode-ai/plugin/effect/vcs"
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 provide = (directory: string, input: { git?: boolean } = {}) =>
Effect.provide(
LayerNode.compile(LayerNode.group([Vcs.node, Bus.node]), [
LayerNode.compile(LayerNode.group([Vcs.node, Bus.node, Location.node, AppProcess.node]), [
[
Location.node,
Layer.succeed(
@@ -42,7 +45,17 @@ const withTmp = <A, E, R>(f: (directory: string) => Effect.Effect<A, E, R>) =>
const withGit = <A, E, R>(f: (directory: string) => Effect.Effect<A, E, R>) =>
withTmp((directory) =>
Effect.promise(() => initRepo(directory)).pipe(
Effect.andThen(f(directory).pipe(provide(directory, { git: true }))),
Effect.andThen(
Effect.gen(function* () {
const vcs = yield* Vcs.Service
const context = host()
yield* VcsGitPlugin.Plugin.effect({
...context,
vcs: { ...context.vcs, transform: vcs.transform, reload: vcs.reload },
})
return yield* f(directory)
}).pipe(provide(directory, { git: true })),
),
),
)