feat(vcs): expose repository branch metadata
This commit is contained in:
parent
704e9ff3fb
commit
305466e5ef
8 changed files with 48 additions and 3 deletions
|
|
@ -2,7 +2,7 @@ export * as Vcs from "./vcs"
|
|||
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { FileDiff } from "@opencode-ai/schema/file-diff"
|
||||
import { FileStatus, Mode } from "@opencode-ai/schema/vcs"
|
||||
import { FileStatus, Info, Mode } from "@opencode-ai/schema/vcs"
|
||||
import { makeLocationNode } from "@opencode-ai/util/effect/app-node"
|
||||
import { FSUtil } from "@opencode-ai/util/fs-util"
|
||||
import { Location } from "./location"
|
||||
|
|
@ -10,13 +10,14 @@ import { AppProcess } from "@opencode-ai/util/process"
|
|||
import { VcsGit } from "./vcs/git"
|
||||
import { VcsHg } from "./vcs/hg"
|
||||
|
||||
export { FileStatus, Mode }
|
||||
export { FileStatus, Info, Mode }
|
||||
|
||||
export interface DiffOptions {
|
||||
readonly context?: number
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
readonly info: () => Effect.Effect<Info>
|
||||
readonly status: () => Effect.Effect<FileStatus[]>
|
||||
readonly diff: (mode: Mode, options?: DiffOptions) => Effect.Effect<FileDiff.Info[]>
|
||||
}
|
||||
|
|
@ -40,6 +41,10 @@ const layer = Layer.effect(
|
|||
const location = yield* Location.Service
|
||||
const impl = adapter(proc, fs, location)
|
||||
return Service.of({
|
||||
info: Effect.fn("Vcs.info")(function* () {
|
||||
if (!impl) return {}
|
||||
return yield* impl.info()
|
||||
}),
|
||||
status: Effect.fn("Vcs.status")(function* () {
|
||||
if (!impl) return []
|
||||
return yield* impl.status()
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ export function make(proc: AppProcess.Interface, input: { directory: string; wor
|
|||
const ctx: Ctx = { git: makeGit(proc), directory: input.directory, worktree: input.worktree }
|
||||
|
||||
return {
|
||||
info: Effect.fn("VcsGit.info")(function* () {
|
||||
const [branch, root] = yield* Effect.all([ctx.git.branch(ctx.directory), ctx.git.defaultBranch(ctx.directory)], {
|
||||
concurrency: 2,
|
||||
})
|
||||
return { branch, defaultBranch: root?.name }
|
||||
}),
|
||||
status: Effect.fn("VcsGit.status")(function* () {
|
||||
const git = ctx.git
|
||||
const ref = (yield* git.hasHead(ctx.directory)) ? "HEAD" : undefined
|
||||
|
|
|
|||
|
|
@ -73,6 +73,9 @@ export function make(
|
|||
})
|
||||
|
||||
return {
|
||||
info: Effect.fn("VcsHg.info")(function* () {
|
||||
return { branch: yield* hg.branch(), defaultBranch: "default" }
|
||||
}),
|
||||
status: Effect.fn("VcsHg.status")(function* () {
|
||||
const [items, batch] = yield* Effect.all(
|
||||
// Zero-context patches are enough to count changed lines.
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ describeHg("Vcs mercurial", () => {
|
|||
await commitAll(directory, "initial")
|
||||
})
|
||||
const vcs = yield* Vcs.Service
|
||||
expect(yield* vcs.info()).toEqual({ branch: "default", defaultBranch: "default" })
|
||||
expect(yield* vcs.diff("branch")).toEqual([])
|
||||
|
||||
yield* Effect.promise(async () => {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ describe("Vcs", () => {
|
|||
withTmp((directory) =>
|
||||
Effect.gen(function* () {
|
||||
const vcs = yield* Vcs.Service
|
||||
expect(yield* vcs.info()).toEqual({})
|
||||
expect(yield* vcs.status()).toEqual([])
|
||||
expect(yield* vcs.diff("working")).toEqual([])
|
||||
expect(yield* vcs.diff("branch")).toEqual([])
|
||||
|
|
@ -155,6 +156,7 @@ describe("Vcs", () => {
|
|||
await commitAll(directory, "initial")
|
||||
})
|
||||
const vcs = yield* Vcs.Service
|
||||
expect(yield* vcs.info()).toEqual({ branch: "main", defaultBranch: "main" })
|
||||
expect(yield* vcs.diff("branch")).toEqual([])
|
||||
|
||||
yield* Effect.promise(async () => {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,20 @@ const DiffQuery = Schema.Struct({
|
|||
})
|
||||
|
||||
export const VcsGroup = HttpApiGroup.make("server.vcs")
|
||||
.add(
|
||||
HttpApiEndpoint.get("vcs.get", "/api/vcs", {
|
||||
query: LocationQuery,
|
||||
success: Location.response(Vcs.Info),
|
||||
})
|
||||
.annotateMerge(locationQueryOpenApi)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.vcs.get",
|
||||
summary: "VCS information",
|
||||
description: "Get the current and default branches for the requested location.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("vcs.status", "/api/vcs/status", {
|
||||
query: LocationQuery,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export * as Vcs from "./vcs.js"
|
||||
|
||||
import { Schema } from "effect"
|
||||
import { NonNegativeInt } from "./schema.js"
|
||||
import { NonNegativeInt, optional } from "./schema.js"
|
||||
|
||||
export const Mode = Schema.Literals(["working", "branch"]).annotate({ identifier: "Vcs.Mode" })
|
||||
export type Mode = typeof Mode.Type
|
||||
|
|
@ -13,3 +13,9 @@ export const FileStatus = Schema.Struct({
|
|||
status: Schema.Literals(["added", "deleted", "modified"]),
|
||||
}).annotate({ identifier: "Vcs.FileStatus" })
|
||||
export interface FileStatus extends Schema.Schema.Type<typeof FileStatus> {}
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
branch: optional(Schema.String),
|
||||
defaultBranch: optional(Schema.String),
|
||||
}).annotate({ identifier: "Vcs.Info" })
|
||||
export interface Info extends Schema.Schema.Type<typeof Info> {}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,14 @@ import { response } from "../location"
|
|||
export const VcsHandler = HttpApiBuilder.group(Api, "server.vcs", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
return handlers
|
||||
.handle("vcs.get", () =>
|
||||
response(
|
||||
Effect.gen(function* () {
|
||||
const vcs = yield* Vcs.Service
|
||||
return yield* vcs.info()
|
||||
}),
|
||||
),
|
||||
)
|
||||
.handle("vcs.status", () =>
|
||||
response(
|
||||
Effect.gen(function* () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue