mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-31 06:12:26 +00:00
ignore: revert 3 commits that broke dev branch (#18260)
This commit is contained in:
@@ -30,7 +30,7 @@ import { MessageV2 } from "../../session/message-v2"
|
||||
import { SessionPrompt } from "@/session/prompt"
|
||||
import { setTimeout as sleep } from "node:timers/promises"
|
||||
import { Process } from "@/util/process"
|
||||
import { Git } from "@/git"
|
||||
import { git } from "@/util/git"
|
||||
|
||||
type GitHubAuthor = {
|
||||
login: string
|
||||
@@ -257,7 +257,7 @@ export const GithubInstallCommand = cmd({
|
||||
}
|
||||
|
||||
// Get repo info
|
||||
const info = (await Git.run(["remote", "get-url", "origin"], { cwd: Instance.worktree })).text().trim()
|
||||
const info = (await git(["remote", "get-url", "origin"], { cwd: Instance.worktree })).text().trim()
|
||||
const parsed = parseGitHubRemote(info)
|
||||
if (!parsed) {
|
||||
prompts.log.error(`Could not find git repository. Please run this command from a git repository.`)
|
||||
@@ -496,20 +496,20 @@ export const GithubRunCommand = cmd({
|
||||
: "issue"
|
||||
: undefined
|
||||
const gitText = async (args: string[]) => {
|
||||
const result = await Git.run(args, { cwd: Instance.worktree })
|
||||
const result = await git(args, { cwd: Instance.worktree })
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Process.RunFailedError(["git", ...args], result.exitCode, result.stdout, result.stderr)
|
||||
}
|
||||
return result.text().trim()
|
||||
}
|
||||
const gitRun = async (args: string[]) => {
|
||||
const result = await Git.run(args, { cwd: Instance.worktree })
|
||||
const result = await git(args, { cwd: Instance.worktree })
|
||||
if (result.exitCode !== 0) {
|
||||
throw new Process.RunFailedError(["git", ...args], result.exitCode, result.stdout, result.stderr)
|
||||
}
|
||||
return result
|
||||
}
|
||||
const gitStatus = (args: string[]) => Git.run(args, { cwd: Instance.worktree })
|
||||
const gitStatus = (args: string[]) => git(args, { cwd: Instance.worktree })
|
||||
const commitChanges = async (summary: string, actor?: string) => {
|
||||
const args = ["commit", "-m", summary]
|
||||
if (actor) args.push("-m", `Co-authored-by: ${actor} <${actor}@users.noreply.github.com>`)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { UI } from "../ui"
|
||||
import { cmd } from "./cmd"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { Process } from "@/util/process"
|
||||
import { Git } from "@/git"
|
||||
import { git } from "@/util/git"
|
||||
|
||||
export const PrCommand = cmd({
|
||||
command: "pr <number>",
|
||||
@@ -67,9 +67,9 @@ export const PrCommand = cmd({
|
||||
const remoteName = forkOwner
|
||||
|
||||
// Check if remote already exists
|
||||
const remotes = (await Git.run(["remote"], { cwd: Instance.worktree })).text().trim()
|
||||
const remotes = (await git(["remote"], { cwd: Instance.worktree })).text().trim()
|
||||
if (!remotes.split("\n").includes(remoteName)) {
|
||||
await Git.run(["remote", "add", remoteName, `https://github.com/${forkOwner}/${forkName}.git`], {
|
||||
await git(["remote", "add", remoteName, `https://github.com/${forkOwner}/${forkName}.git`], {
|
||||
cwd: Instance.worktree,
|
||||
})
|
||||
UI.println(`Added fork remote: ${remoteName}`)
|
||||
@@ -77,7 +77,7 @@ export const PrCommand = cmd({
|
||||
|
||||
// Set upstream to the fork so pushes go there
|
||||
const headRefName = prInfo.headRefName
|
||||
await Git.run(["branch", `--set-upstream-to=${remoteName}/${headRefName}`, localBranchName], {
|
||||
await git(["branch", `--set-upstream-to=${remoteName}/${headRefName}`, localBranchName], {
|
||||
cwd: Instance.worktree,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ function lookup(_key: string) {
|
||||
Layer.fresh(PermissionNext.layer),
|
||||
Layer.fresh(ProviderAuth.defaultLayer),
|
||||
Layer.fresh(FileWatcher.layer).pipe(Layer.orDie),
|
||||
Layer.fresh(Vcs.defaultLayer),
|
||||
Layer.fresh(Vcs.layer),
|
||||
Layer.fresh(FileTime.layer).pipe(Layer.orDie),
|
||||
Layer.fresh(Format.layer),
|
||||
Layer.fresh(File.layer),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Effect, Layer, ManagedRuntime } from "effect"
|
||||
import { AccountEffect } from "@/account/effect"
|
||||
import { AuthEffect } from "@/auth/effect"
|
||||
import { GitEffect } from "@/git/effect"
|
||||
import { Instances } from "@/effect/instances"
|
||||
import type { InstanceServices } from "@/effect/instances"
|
||||
import { TruncateEffect } from "@/tool/truncate-effect"
|
||||
@@ -10,7 +9,6 @@ import { Instance } from "@/project/instance"
|
||||
export const runtime = ManagedRuntime.make(
|
||||
Layer.mergeAll(
|
||||
AccountEffect.defaultLayer, //
|
||||
GitEffect.defaultLayer,
|
||||
TruncateEffect.defaultLayer,
|
||||
Instances.layer,
|
||||
).pipe(Layer.provideMerge(AuthEffect.layer)),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { InstanceContext } from "@/effect/instance-context"
|
||||
import { runPromiseInstance } from "@/effect/runtime"
|
||||
import { Git } from "@/git"
|
||||
import { git } from "@/util/git"
|
||||
import { Effect, Fiber, Layer, Scope, ServiceMap } from "effect"
|
||||
import { formatPatch, structuredPatch } from "diff"
|
||||
import fs from "fs"
|
||||
@@ -440,7 +440,7 @@ export namespace File {
|
||||
|
||||
return yield* Effect.promise(async () => {
|
||||
const diffOutput = (
|
||||
await Git.run(["-c", "core.fsmonitor=false", "-c", "core.quotepath=false", "diff", "--numstat", "HEAD"], {
|
||||
await git(["-c", "core.fsmonitor=false", "-c", "core.quotepath=false", "diff", "--numstat", "HEAD"], {
|
||||
cwd: instance.directory,
|
||||
})
|
||||
).text()
|
||||
@@ -460,7 +460,7 @@ export namespace File {
|
||||
}
|
||||
|
||||
const untrackedOutput = (
|
||||
await Git.run(
|
||||
await git(
|
||||
[
|
||||
"-c",
|
||||
"core.fsmonitor=false",
|
||||
@@ -493,7 +493,7 @@ export namespace File {
|
||||
}
|
||||
|
||||
const deletedOutput = (
|
||||
await Git.run(
|
||||
await git(
|
||||
[
|
||||
"-c",
|
||||
"core.fsmonitor=false",
|
||||
@@ -584,17 +584,17 @@ export namespace File {
|
||||
|
||||
if (instance.project.vcs === "git") {
|
||||
let diff = (
|
||||
await Git.run(["-c", "core.fsmonitor=false", "diff", "--", file], { cwd: instance.directory })
|
||||
await git(["-c", "core.fsmonitor=false", "diff", "--", file], { cwd: instance.directory })
|
||||
).text()
|
||||
if (!diff.trim()) {
|
||||
diff = (
|
||||
await Git.run(["-c", "core.fsmonitor=false", "diff", "--staged", "--", file], {
|
||||
await git(["-c", "core.fsmonitor=false", "diff", "--staged", "--", file], {
|
||||
cwd: instance.directory,
|
||||
})
|
||||
).text()
|
||||
}
|
||||
if (diff.trim()) {
|
||||
const original = (await Git.run(["show", `HEAD:${file}`], { cwd: instance.directory })).text()
|
||||
const original = (await git(["show", `HEAD:${file}`], { cwd: instance.directory })).text()
|
||||
const patch = structuredPatch(file, file, original, content, "old", "new", {
|
||||
context: Infinity,
|
||||
ignoreWhitespace: true,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { BusEvent } from "@/bus/bus-event"
|
||||
import { InstanceContext } from "@/effect/instance-context"
|
||||
import { Flag } from "@/flag/flag"
|
||||
import { Instance } from "@/project/instance"
|
||||
import { Git } from "@/git"
|
||||
import { git } from "@/util/git"
|
||||
import { lazy } from "@/util/lazy"
|
||||
import { Config } from "../config/config"
|
||||
import { FileIgnore } from "./ignore"
|
||||
@@ -117,7 +117,7 @@ export namespace FileWatcher {
|
||||
|
||||
if (instance.project.vcs === "git") {
|
||||
const result = yield* Effect.promise(() =>
|
||||
Git.run(["rev-parse", "--git-dir"], {
|
||||
git(["rev-parse", "--git-dir"], {
|
||||
cwd: instance.project.worktree,
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -1,298 +0,0 @@
|
||||
import { NodeChildProcessSpawner, NodeFileSystem, NodePath } from "@effect/platform-node"
|
||||
import { Effect, Layer, ServiceMap, Stream } from "effect"
|
||||
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
||||
|
||||
export namespace GitEffect {
|
||||
const cfg = [
|
||||
"--no-optional-locks",
|
||||
"-c",
|
||||
"core.autocrlf=false",
|
||||
"-c",
|
||||
"core.fsmonitor=false",
|
||||
"-c",
|
||||
"core.longpaths=true",
|
||||
"-c",
|
||||
"core.symlinks=true",
|
||||
"-c",
|
||||
"core.quotepath=false",
|
||||
] as const
|
||||
|
||||
function out(result: { text(): string }) {
|
||||
return result.text().trim()
|
||||
}
|
||||
|
||||
function split(text: string) {
|
||||
return text.split("\0").filter(Boolean)
|
||||
}
|
||||
|
||||
export type Kind = "added" | "deleted" | "modified"
|
||||
|
||||
export type Base = {
|
||||
readonly name: string
|
||||
readonly ref: string
|
||||
}
|
||||
|
||||
export type Item = {
|
||||
readonly file: string
|
||||
readonly code: string
|
||||
readonly status: Kind
|
||||
}
|
||||
|
||||
export type Stat = {
|
||||
readonly file: string
|
||||
readonly additions: number
|
||||
readonly deletions: number
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
readonly exitCode: number
|
||||
readonly text: () => string
|
||||
readonly stdout: Buffer
|
||||
readonly stderr: Buffer
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
readonly cwd: string
|
||||
readonly env?: Record<string, string>
|
||||
}
|
||||
|
||||
export interface Interface {
|
||||
readonly run: (args: string[], opts: Options) => Effect.Effect<Result>
|
||||
readonly text: (args: string[], opts: Options) => Effect.Effect<string>
|
||||
readonly lines: (args: string[], opts: Options) => Effect.Effect<string[]>
|
||||
readonly branch: (cwd: string) => Effect.Effect<string | undefined>
|
||||
readonly prefix: (cwd: string) => Effect.Effect<string>
|
||||
readonly defaultBranch: (cwd: string) => Effect.Effect<Base | undefined>
|
||||
readonly hasHead: (cwd: string) => Effect.Effect<boolean>
|
||||
readonly mergeBase: (cwd: string, base: string, head?: string) => Effect.Effect<string | undefined>
|
||||
readonly show: (cwd: string, ref: string, file: string, prefix?: string) => Effect.Effect<string>
|
||||
readonly status: (cwd: string) => Effect.Effect<Item[]>
|
||||
readonly diff: (cwd: string, ref: string) => Effect.Effect<Item[]>
|
||||
readonly stats: (cwd: string, ref: string) => Effect.Effect<Stat[]>
|
||||
}
|
||||
|
||||
function kind(code: string | undefined): Kind {
|
||||
if (code === "??") return "added"
|
||||
if (code?.includes("U")) return "modified"
|
||||
if (code?.includes("A") && !code.includes("D")) return "added"
|
||||
if (code?.includes("D") && !code.includes("A")) return "deleted"
|
||||
return "modified"
|
||||
}
|
||||
|
||||
function parseStatus(text: string) {
|
||||
return split(text).flatMap((item) => {
|
||||
const file = item.slice(3)
|
||||
if (!file) return []
|
||||
const code = item.slice(0, 2)
|
||||
return [{ file, code, status: kind(code) } satisfies Item]
|
||||
})
|
||||
}
|
||||
|
||||
function parseNames(text: string) {
|
||||
const list = split(text)
|
||||
const out: Item[] = []
|
||||
for (let i = 0; i < list.length; i += 2) {
|
||||
const code = list[i]
|
||||
const file = list[i + 1]
|
||||
if (!code || !file) continue
|
||||
out.push({ file, code, status: kind(code) })
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function parseStats(text: string) {
|
||||
const out: Stat[] = []
|
||||
for (const item of split(text)) {
|
||||
const a = item.indexOf("\t")
|
||||
const b = item.indexOf("\t", a + 1)
|
||||
if (a === -1 || b === -1) continue
|
||||
const file = item.slice(b + 1)
|
||||
if (!file) continue
|
||||
const adds = item.slice(0, a)
|
||||
const dels = item.slice(a + 1, b)
|
||||
const additions = adds === "-" ? 0 : Number.parseInt(adds || "0", 10)
|
||||
const deletions = dels === "-" ? 0 : Number.parseInt(dels || "0", 10)
|
||||
out.push({
|
||||
file,
|
||||
additions: Number.isFinite(additions) ? additions : 0,
|
||||
deletions: Number.isFinite(deletions) ? deletions : 0,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/Git") {}
|
||||
|
||||
export const layer = Layer.effect(
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
||||
|
||||
const run = Effect.fn("Git.run")(
|
||||
function* (args: string[], opts: Options) {
|
||||
const proc = ChildProcess.make("git", [...cfg, ...args], {
|
||||
cwd: opts.cwd,
|
||||
env: opts.env,
|
||||
extendEnv: true,
|
||||
})
|
||||
const handle = yield* spawner.spawn(proc)
|
||||
const [stdout, stderr] = yield* Effect.all(
|
||||
[Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))],
|
||||
{ concurrency: 2 },
|
||||
)
|
||||
return {
|
||||
exitCode: yield* handle.exitCode,
|
||||
text: () => stdout,
|
||||
stdout: Buffer.from(stdout),
|
||||
stderr: Buffer.from(stderr),
|
||||
} satisfies Result
|
||||
},
|
||||
Effect.scoped,
|
||||
Effect.catch((err) =>
|
||||
Effect.succeed({
|
||||
exitCode: ChildProcessSpawner.ExitCode(1),
|
||||
text: () => "",
|
||||
stdout: Buffer.alloc(0),
|
||||
stderr: Buffer.from(String(err)),
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const text = Effect.fn("Git.text")(function* (args: string[], opts: Options) {
|
||||
return (yield* run(args, opts)).text()
|
||||
})
|
||||
|
||||
const lines = Effect.fn("Git.lines")(function* (args: string[], opts: Options) {
|
||||
return (yield* text(args, opts))
|
||||
.split(/\r?\n/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
})
|
||||
|
||||
const refs = Effect.fnUntraced(function* (cwd: string) {
|
||||
return yield* lines(["for-each-ref", "--format=%(refname:short)", "refs/heads"], { cwd })
|
||||
})
|
||||
|
||||
const configured = Effect.fnUntraced(function* (cwd: string, list: string[]) {
|
||||
const result = yield* run(["config", "init.defaultBranch"], { cwd })
|
||||
if (result.exitCode !== 0) return
|
||||
const name = out(result)
|
||||
if (!name || !list.includes(name)) return
|
||||
const ref = yield* run(["rev-parse", "--verify", name], { cwd })
|
||||
if (ref.exitCode !== 0) return
|
||||
return { name, ref: name } satisfies Base
|
||||
})
|
||||
|
||||
const remoteHead = Effect.fnUntraced(function* (cwd: string, remote: string) {
|
||||
const result = yield* run(["ls-remote", "--symref", remote, "HEAD"], { cwd })
|
||||
if (result.exitCode !== 0) return
|
||||
for (const line of result.text().split("\n")) {
|
||||
const match = /^ref: refs\/heads\/(.+)\tHEAD$/.exec(line.trim())
|
||||
if (!match?.[1]) continue
|
||||
return { name: match[1], ref: `${remote}/${match[1]}` } satisfies Base
|
||||
}
|
||||
})
|
||||
|
||||
const primary = Effect.fnUntraced(function* (cwd: string) {
|
||||
const list = yield* lines(["remote"], { cwd })
|
||||
if (list.includes("origin")) return "origin"
|
||||
if (list.length === 1) return list[0]
|
||||
if (list.includes("upstream")) return "upstream"
|
||||
return list[0]
|
||||
})
|
||||
|
||||
const branch = Effect.fn("Git.branch")(function* (cwd: string) {
|
||||
const result = yield* run(["rev-parse", "--abbrev-ref", "HEAD"], { cwd })
|
||||
if (result.exitCode !== 0) return
|
||||
const text = out(result)
|
||||
return text || undefined
|
||||
})
|
||||
|
||||
const prefix = Effect.fn("Git.prefix")(function* (cwd: string) {
|
||||
const result = yield* run(["rev-parse", "--show-prefix"], { cwd })
|
||||
if (result.exitCode !== 0) return ""
|
||||
return out(result)
|
||||
})
|
||||
|
||||
const defaultBranch = Effect.fn("Git.defaultBranch")(function* (cwd: string) {
|
||||
const remote = yield* primary(cwd)
|
||||
if (remote) {
|
||||
const head = yield* run(["symbolic-ref", `refs/remotes/${remote}/HEAD`], { cwd })
|
||||
if (head.exitCode === 0) {
|
||||
const ref = out(head).replace(/^refs\/remotes\//, "")
|
||||
const name = ref.startsWith(`${remote}/`) ? ref.slice(`${remote}/`.length) : ""
|
||||
if (name) return { name, ref } satisfies Base
|
||||
}
|
||||
|
||||
const next = yield* remoteHead(cwd, remote)
|
||||
if (next) return next
|
||||
}
|
||||
|
||||
const list = yield* refs(cwd)
|
||||
const next = yield* configured(cwd, list)
|
||||
if (next) return next
|
||||
for (const name of ["main", "master"]) {
|
||||
if (list.includes(name)) return { name, ref: name } satisfies Base
|
||||
}
|
||||
})
|
||||
|
||||
const hasHead = Effect.fn("Git.hasHead")(function* (cwd: string) {
|
||||
const result = yield* run(["rev-parse", "--verify", "HEAD"], { cwd })
|
||||
return result.exitCode === 0
|
||||
})
|
||||
|
||||
const mergeBase = Effect.fn("Git.mergeBase")(function* (cwd: string, base: string, head = "HEAD") {
|
||||
const result = yield* run(["merge-base", base, head], { cwd })
|
||||
if (result.exitCode !== 0) return
|
||||
const text = out(result)
|
||||
return text || undefined
|
||||
})
|
||||
|
||||
const show = Effect.fn("Git.show")(function* (cwd: string, ref: string, file: string, prefix = "") {
|
||||
const target = prefix ? `${prefix}${file}` : file
|
||||
const result = yield* run(["show", `${ref}:${target}`], { cwd })
|
||||
if (result.exitCode !== 0) return ""
|
||||
return result.text()
|
||||
})
|
||||
|
||||
const status = Effect.fn("Git.status")(function* (cwd: string) {
|
||||
return parseStatus(
|
||||
yield* text(["status", "--porcelain=v1", "--untracked-files=all", "--no-renames", "-z", "--", "."], { cwd }),
|
||||
)
|
||||
})
|
||||
|
||||
const diff = Effect.fn("Git.diff")(function* (cwd: string, ref: string) {
|
||||
return parseNames(
|
||||
yield* text(["diff", "--no-ext-diff", "--no-renames", "--name-status", "-z", ref, "--", "."], { cwd }),
|
||||
)
|
||||
})
|
||||
|
||||
const stats = Effect.fn("Git.stats")(function* (cwd: string, ref: string) {
|
||||
return parseStats(
|
||||
yield* text(["diff", "--no-ext-diff", "--no-renames", "--numstat", "-z", ref, "--", "."], { cwd }),
|
||||
)
|
||||
})
|
||||
|
||||
return Service.of({
|
||||
run,
|
||||
text,
|
||||
lines,
|
||||
branch,
|
||||
prefix,
|
||||
defaultBranch,
|
||||
hasHead,
|
||||
mergeBase,
|
||||
show,
|
||||
status,
|
||||
diff,
|
||||
stats,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
const platformLayer = NodeChildProcessSpawner.layer.pipe(
|
||||
Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(platformLayer))
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Effect } from "effect"
|
||||
import { runtime } from "@/effect/runtime"
|
||||
import { GitEffect } from "./effect"
|
||||
|
||||
function runPromise<A>(f: (service: GitEffect.Interface) => Effect.Effect<A>): Promise<A> {
|
||||
return runtime.runPromise(GitEffect.Service.use(f))
|
||||
}
|
||||
|
||||
export namespace Git {
|
||||
export type Kind = GitEffect.Kind
|
||||
export type Base = GitEffect.Base
|
||||
export type Item = GitEffect.Item
|
||||
export type Stat = GitEffect.Stat
|
||||
export type Result = GitEffect.Result
|
||||
export type Options = GitEffect.Options
|
||||
|
||||
export function run(args: string[], opts: Options) {
|
||||
return runPromise((git) => git.run(args, opts))
|
||||
}
|
||||
|
||||
export function text(args: string[], opts: Options) {
|
||||
return runPromise((git) => git.text(args, opts))
|
||||
}
|
||||
|
||||
export function lines(args: string[], opts: Options) {
|
||||
return runPromise((git) => git.lines(args, opts))
|
||||
}
|
||||
|
||||
export function branch(cwd: string) {
|
||||
return runPromise((git) => git.branch(cwd))
|
||||
}
|
||||
|
||||
export function prefix(cwd: string) {
|
||||
return runPromise((git) => git.prefix(cwd))
|
||||
}
|
||||
|
||||
export function defaultBranch(cwd: string) {
|
||||
return runPromise((git) => git.defaultBranch(cwd))
|
||||
}
|
||||
|
||||
export function hasHead(cwd: string) {
|
||||
return runPromise((git) => git.hasHead(cwd))
|
||||
}
|
||||
|
||||
export function mergeBase(cwd: string, base: string, head?: string) {
|
||||
return runPromise((git) => git.mergeBase(cwd, base, head))
|
||||
}
|
||||
|
||||
export function show(cwd: string, ref: string, file: string, prefix?: string) {
|
||||
return runPromise((git) => git.show(cwd, ref, file, prefix))
|
||||
}
|
||||
|
||||
export function status(cwd: string) {
|
||||
return runPromise((git) => git.status(cwd))
|
||||
}
|
||||
|
||||
export function diff(cwd: string, ref: string) {
|
||||
return runPromise((git) => git.diff(cwd, ref))
|
||||
}
|
||||
|
||||
export function stats(cwd: string, ref: string) {
|
||||
return runPromise((git) => git.stats(cwd, ref))
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import { BusEvent } from "@/bus/bus-event"
|
||||
import { iife } from "@/util/iife"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
import { existsSync } from "fs"
|
||||
import { Git } from "@/git"
|
||||
import { git } from "../util/git"
|
||||
import { Glob } from "../util/glob"
|
||||
import { which } from "../util/which"
|
||||
import { ProjectID } from "./schema"
|
||||
@@ -119,7 +119,7 @@ export namespace Project {
|
||||
}
|
||||
}
|
||||
|
||||
const worktree = await Git.run(["rev-parse", "--git-common-dir"], {
|
||||
const worktree = await git(["rev-parse", "--git-common-dir"], {
|
||||
cwd: sandbox,
|
||||
})
|
||||
.then(async (result) => {
|
||||
@@ -147,7 +147,7 @@ export namespace Project {
|
||||
|
||||
// generate id from root commit
|
||||
if (!id) {
|
||||
const roots = await Git.run(["rev-list", "--max-parents=0", "HEAD"], {
|
||||
const roots = await git(["rev-list", "--max-parents=0", "HEAD"], {
|
||||
cwd: sandbox,
|
||||
})
|
||||
.then(async (result) =>
|
||||
@@ -184,7 +184,7 @@ export namespace Project {
|
||||
}
|
||||
}
|
||||
|
||||
const top = await Git.run(["rev-parse", "--show-toplevel"], {
|
||||
const top = await git(["rev-parse", "--show-toplevel"], {
|
||||
cwd: sandbox,
|
||||
})
|
||||
.then(async (result) => gitpath(sandbox, await result.text()))
|
||||
@@ -349,7 +349,7 @@ export namespace Project {
|
||||
if (input.project.vcs === "git") return input.project
|
||||
if (!which("git")) throw new Error("Git is not installed")
|
||||
|
||||
const result = await Git.run(["init", "--quiet"], {
|
||||
const result = await git(["init", "--quiet"], {
|
||||
cwd: input.directory,
|
||||
})
|
||||
if (result.exitCode !== 0) {
|
||||
|
||||
@@ -1,122 +1,16 @@
|
||||
import { AppFileSystem } from "@/filesystem"
|
||||
import { Effect, Layer, ServiceMap } from "effect"
|
||||
import { Bus } from "@/bus"
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { InstanceContext } from "@/effect/instance-context"
|
||||
import { FileWatcher } from "@/file/watcher"
|
||||
import { GitEffect } from "@/git/effect"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import { Log } from "@/util/log"
|
||||
import path from "path"
|
||||
import { git } from "@/util/git"
|
||||
import { Instance } from "./instance"
|
||||
import z from "zod"
|
||||
|
||||
function count(text: string) {
|
||||
if (!text) return 0
|
||||
if (!text.endsWith("\n")) return text.split("\n").length
|
||||
return text.slice(0, -1).split("\n").length
|
||||
}
|
||||
|
||||
const work = Effect.fnUntraced(function* (fs: AppFileSystem.Interface, cwd: string, file: string) {
|
||||
const full = path.join(cwd, file)
|
||||
if (!(yield* fs.exists(full).pipe(Effect.orDie))) return ""
|
||||
const buf = yield* fs.readFile(full).pipe(Effect.catch(() => Effect.succeed(new Uint8Array())))
|
||||
if (Buffer.from(buf).includes(0)) return ""
|
||||
return Buffer.from(buf).toString("utf8")
|
||||
})
|
||||
|
||||
function stats(list: GitEffect.Stat[]) {
|
||||
const out = new Map<string, { additions: number; deletions: number }>()
|
||||
for (const item of list) {
|
||||
out.set(item.file, {
|
||||
additions: item.additions,
|
||||
deletions: item.deletions,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function merge(...lists: GitEffect.Item[][]) {
|
||||
const out = new Map<string, GitEffect.Item>()
|
||||
for (const list of lists) {
|
||||
for (const item of list) {
|
||||
if (!out.has(item.file)) out.set(item.file, item)
|
||||
}
|
||||
}
|
||||
return [...out.values()]
|
||||
}
|
||||
|
||||
const files = Effect.fnUntraced(function* (
|
||||
fs: AppFileSystem.Interface,
|
||||
git: GitEffect.Interface,
|
||||
cwd: string,
|
||||
ref: string | undefined,
|
||||
list: GitEffect.Item[],
|
||||
nums: Map<string, { additions: number; deletions: number }>,
|
||||
) {
|
||||
const base = ref ? yield* git.prefix(cwd) : ""
|
||||
const next = yield* Effect.forEach(
|
||||
list,
|
||||
(item) =>
|
||||
Effect.gen(function* () {
|
||||
const before = item.status === "added" || !ref ? "" : yield* git.show(cwd, ref, item.file, base)
|
||||
const after = item.status === "deleted" ? "" : yield* work(fs, cwd, item.file)
|
||||
const stat = nums.get(item.file)
|
||||
return {
|
||||
file: item.file,
|
||||
before,
|
||||
after,
|
||||
additions: stat?.additions ?? (item.status === "added" ? count(after) : 0),
|
||||
deletions: stat?.deletions ?? (item.status === "deleted" ? count(before) : 0),
|
||||
status: item.status,
|
||||
} satisfies Snapshot.FileDiff
|
||||
}),
|
||||
{ concurrency: 8 },
|
||||
)
|
||||
return next.toSorted((a, b) => a.file.localeCompare(b.file))
|
||||
})
|
||||
|
||||
const track = Effect.fnUntraced(function* (
|
||||
fs: AppFileSystem.Interface,
|
||||
git: GitEffect.Interface,
|
||||
cwd: string,
|
||||
ref: string | undefined,
|
||||
) {
|
||||
if (!ref) {
|
||||
return yield* files(fs, git, cwd, ref, yield* git.status(cwd), new Map())
|
||||
}
|
||||
const [list, nums] = yield* Effect.all([git.status(cwd), git.stats(cwd, ref)], { concurrency: 2 })
|
||||
return yield* files(fs, git, cwd, ref, list, stats(nums))
|
||||
})
|
||||
|
||||
const compare = Effect.fnUntraced(function* (
|
||||
fs: AppFileSystem.Interface,
|
||||
git: GitEffect.Interface,
|
||||
cwd: string,
|
||||
ref: string,
|
||||
) {
|
||||
const [list, nums, extra] = yield* Effect.all([git.diff(cwd, ref), git.stats(cwd, ref), git.status(cwd)], {
|
||||
concurrency: 3,
|
||||
})
|
||||
return yield* files(
|
||||
fs,
|
||||
git,
|
||||
cwd,
|
||||
ref,
|
||||
merge(
|
||||
list,
|
||||
extra.filter((item) => item.code === "??"),
|
||||
),
|
||||
stats(nums),
|
||||
)
|
||||
})
|
||||
|
||||
export namespace Vcs {
|
||||
const log = Log.create({ service: "vcs" })
|
||||
|
||||
export const Mode = z.enum(["git", "branch"])
|
||||
export type Mode = z.infer<typeof Mode>
|
||||
|
||||
export const Event = {
|
||||
BranchUpdated: BusEvent.define(
|
||||
"vcs.branch.updated",
|
||||
@@ -128,8 +22,7 @@ export namespace Vcs {
|
||||
|
||||
export const Info = z
|
||||
.object({
|
||||
branch: z.string().optional(),
|
||||
default_branch: z.string().optional(),
|
||||
branch: z.string(),
|
||||
})
|
||||
.meta({
|
||||
ref: "VcsInfo",
|
||||
@@ -138,8 +31,6 @@ export namespace Vcs {
|
||||
|
||||
export interface Interface {
|
||||
readonly branch: () => Effect.Effect<string | undefined>
|
||||
readonly defaultBranch: () => Effect.Effect<string | undefined>
|
||||
readonly diff: (mode: Mode) => Effect.Effect<Snapshot.FileDiff[]>
|
||||
}
|
||||
|
||||
export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/Vcs") {}
|
||||
@@ -148,18 +39,20 @@ export namespace Vcs {
|
||||
Service,
|
||||
Effect.gen(function* () {
|
||||
const instance = yield* InstanceContext
|
||||
const fs = yield* AppFileSystem.Service
|
||||
const git = yield* GitEffect.Service
|
||||
let current: string | undefined
|
||||
let root: GitEffect.Base | undefined
|
||||
let currentBranch: string | undefined
|
||||
|
||||
if (instance.project.vcs === "git") {
|
||||
const get = () => Effect.runPromise(git.branch(instance.directory))
|
||||
const getCurrentBranch = async () => {
|
||||
const result = await git(["rev-parse", "--abbrev-ref", "HEAD"], {
|
||||
cwd: instance.project.worktree,
|
||||
})
|
||||
if (result.exitCode !== 0) return undefined
|
||||
const text = result.text().trim()
|
||||
return text || undefined
|
||||
}
|
||||
|
||||
;[current, root] = yield* Effect.all([git.branch(instance.directory), git.defaultBranch(instance.directory)], {
|
||||
concurrency: 2,
|
||||
})
|
||||
log.info("initialized", { branch: current, default_branch: root?.name })
|
||||
currentBranch = yield* Effect.promise(() => getCurrentBranch())
|
||||
log.info("initialized", { branch: currentBranch })
|
||||
|
||||
yield* Effect.acquireRelease(
|
||||
Effect.sync(() =>
|
||||
@@ -167,11 +60,12 @@ export namespace Vcs {
|
||||
FileWatcher.Event.Updated,
|
||||
Instance.bind(async (evt) => {
|
||||
if (!evt.properties.file.endsWith("HEAD")) return
|
||||
const next = await get()
|
||||
if (next === current) return
|
||||
log.info("branch changed", { from: current, to: next })
|
||||
current = next
|
||||
Bus.publish(Event.BranchUpdated, { branch: next })
|
||||
const next = await getCurrentBranch()
|
||||
if (next !== currentBranch) {
|
||||
log.info("branch changed", { from: currentBranch, to: next })
|
||||
currentBranch = next
|
||||
Bus.publish(Event.BranchUpdated, { branch: next })
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
@@ -181,30 +75,9 @@ export namespace Vcs {
|
||||
|
||||
return Service.of({
|
||||
branch: Effect.fn("Vcs.branch")(function* () {
|
||||
return current
|
||||
}),
|
||||
defaultBranch: Effect.fn("Vcs.defaultBranch")(function* () {
|
||||
return root?.name
|
||||
}),
|
||||
diff: Effect.fn("Vcs.diff")(function* (mode: Mode) {
|
||||
if (instance.project.vcs !== "git") return []
|
||||
if (mode === "git") {
|
||||
const ok = yield* git.hasHead(instance.directory)
|
||||
return yield* track(fs, git, instance.directory, ok ? "HEAD" : undefined)
|
||||
}
|
||||
|
||||
if (!root) return []
|
||||
if (current && current === root.name) return []
|
||||
const ref = yield* git.mergeBase(instance.directory, root.ref)
|
||||
if (!ref) return []
|
||||
return yield* compare(fs, git, instance.directory, ref)
|
||||
return currentBranch
|
||||
}),
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
export const defaultLayer = layer.pipe(
|
||||
Layer.provide(GitEffect.defaultLayer),
|
||||
Layer.provide(AppFileSystem.defaultLayer),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ import { websocket } from "hono/bun"
|
||||
import { HTTPException } from "hono/http-exception"
|
||||
import { errors } from "./error"
|
||||
import { Filesystem } from "@/util/filesystem"
|
||||
import { Snapshot } from "@/snapshot"
|
||||
import { QuestionRoutes } from "./routes/question"
|
||||
import { PermissionRoutes } from "./routes/permission"
|
||||
import { GlobalRoutes } from "./routes/global"
|
||||
@@ -333,39 +332,10 @@ export namespace Server {
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
const [branch, default_branch] = await Promise.all([
|
||||
runPromiseInstance(Vcs.Service.use((s) => s.branch())),
|
||||
runPromiseInstance(Vcs.Service.use((s) => s.defaultBranch())),
|
||||
])
|
||||
return c.json({ branch, default_branch })
|
||||
},
|
||||
)
|
||||
.get(
|
||||
"/vcs/diff",
|
||||
describeRoute({
|
||||
summary: "Get VCS diff",
|
||||
description: "Retrieve the current git diff for the working tree or against the default branch.",
|
||||
operationId: "vcs.diff",
|
||||
responses: {
|
||||
200: {
|
||||
description: "VCS diff",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: resolver(Snapshot.FileDiff.array()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
validator(
|
||||
"query",
|
||||
z.object({
|
||||
mode: Vcs.Mode,
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const mode = c.req.valid("query").mode
|
||||
return c.json(await runPromiseInstance(Vcs.Service.use((s) => s.diff(mode))))
|
||||
const branch = await runPromiseInstance(Vcs.Service.use((s) => s.branch()))
|
||||
return c.json({
|
||||
branch,
|
||||
})
|
||||
},
|
||||
)
|
||||
.get(
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Lock } from "../util/lock"
|
||||
import { NamedError } from "@opencode-ai/util/error"
|
||||
import z from "zod"
|
||||
import { Glob } from "../util/glob"
|
||||
import { Git } from "@/git"
|
||||
import { git } from "@/util/git"
|
||||
|
||||
export namespace Storage {
|
||||
const log = Log.create({ service: "storage" })
|
||||
@@ -49,7 +49,7 @@ export namespace Storage {
|
||||
}
|
||||
if (!worktree) continue
|
||||
if (!(await Filesystem.isDir(worktree))) continue
|
||||
const result = await Git.run(["rev-list", "--max-parents=0", "--all"], {
|
||||
const result = await git(["rev-list", "--max-parents=0", "--all"], {
|
||||
cwd: worktree,
|
||||
})
|
||||
const [id] = result
|
||||
|
||||
35
packages/opencode/src/util/git.ts
Normal file
35
packages/opencode/src/util/git.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Process } from "./process"
|
||||
|
||||
export interface GitResult {
|
||||
exitCode: number
|
||||
text(): string
|
||||
stdout: Buffer
|
||||
stderr: Buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a git command.
|
||||
*
|
||||
* Uses Process helpers with stdin ignored to avoid protocol pipe inheritance
|
||||
* issues in embedded/client environments.
|
||||
*/
|
||||
export async function git(args: string[], opts: { cwd: string; env?: Record<string, string> }): Promise<GitResult> {
|
||||
return Process.run(["git", ...args], {
|
||||
cwd: opts.cwd,
|
||||
env: opts.env,
|
||||
stdin: "ignore",
|
||||
nothrow: true,
|
||||
})
|
||||
.then((result) => ({
|
||||
exitCode: result.code,
|
||||
text: () => result.stdout.toString(),
|
||||
stdout: result.stdout,
|
||||
stderr: result.stderr,
|
||||
}))
|
||||
.catch((error) => ({
|
||||
exitCode: 1,
|
||||
text: () => "",
|
||||
stdout: Buffer.alloc(0),
|
||||
stderr: Buffer.from(error instanceof Error ? error.message : String(error)),
|
||||
}))
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import type { ProjectID } from "../project/schema"
|
||||
import { fn } from "../util/fn"
|
||||
import { Log } from "../util/log"
|
||||
import { Process } from "../util/process"
|
||||
import { Git } from "@/git"
|
||||
import { git } from "../util/git"
|
||||
import { BusEvent } from "@/bus/bus-event"
|
||||
import { GlobalBus } from "@/bus/global"
|
||||
|
||||
@@ -250,14 +250,14 @@ export namespace Worktree {
|
||||
}
|
||||
|
||||
async function sweep(root: string) {
|
||||
const first = await Git.run(["clean", "-ffdx"], { cwd: root })
|
||||
const first = await git(["clean", "-ffdx"], { cwd: root })
|
||||
if (first.exitCode === 0) return first
|
||||
|
||||
const entries = failed(first)
|
||||
if (!entries.length) return first
|
||||
|
||||
await prune(root, entries)
|
||||
return Git.run(["clean", "-ffdx"], { cwd: root })
|
||||
return git(["clean", "-ffdx"], { cwd: root })
|
||||
}
|
||||
|
||||
async function canonical(input: string) {
|
||||
@@ -276,7 +276,7 @@ export namespace Worktree {
|
||||
if (await exists(directory)) continue
|
||||
|
||||
const ref = `refs/heads/${branch}`
|
||||
const branchCheck = await Git.run(["show-ref", "--verify", "--quiet", ref], {
|
||||
const branchCheck = await git(["show-ref", "--verify", "--quiet", ref], {
|
||||
cwd: Instance.worktree,
|
||||
})
|
||||
if (branchCheck.exitCode === 0) continue
|
||||
@@ -348,7 +348,7 @@ export namespace Worktree {
|
||||
}
|
||||
|
||||
export async function createFromInfo(info: Info, startCommand?: string) {
|
||||
const created = await Git.run(["worktree", "add", "--no-checkout", "-b", info.branch, info.directory], {
|
||||
const created = await git(["worktree", "add", "--no-checkout", "-b", info.branch, info.directory], {
|
||||
cwd: Instance.worktree,
|
||||
})
|
||||
if (created.exitCode !== 0) {
|
||||
@@ -362,7 +362,7 @@ export namespace Worktree {
|
||||
|
||||
return () => {
|
||||
const start = async () => {
|
||||
const populated = await Git.run(["reset", "--hard"], { cwd: info.directory })
|
||||
const populated = await git(["reset", "--hard"], { cwd: info.directory })
|
||||
if (populated.exitCode !== 0) {
|
||||
const message = errorText(populated) || "Failed to populate worktree"
|
||||
log.error("worktree checkout failed", { directory: info.directory, message })
|
||||
@@ -479,10 +479,10 @@ export namespace Worktree {
|
||||
|
||||
const stop = async (target: string) => {
|
||||
if (!(await exists(target))) return
|
||||
await Git.run(["fsmonitor--daemon", "stop"], { cwd: target })
|
||||
await git(["fsmonitor--daemon", "stop"], { cwd: target })
|
||||
}
|
||||
|
||||
const list = await Git.run(["worktree", "list", "--porcelain"], { cwd: Instance.worktree })
|
||||
const list = await git(["worktree", "list", "--porcelain"], { cwd: Instance.worktree })
|
||||
if (list.exitCode !== 0) {
|
||||
throw new RemoveFailedError({ message: errorText(list) || "Failed to read git worktrees" })
|
||||
}
|
||||
@@ -499,11 +499,11 @@ export namespace Worktree {
|
||||
}
|
||||
|
||||
await stop(entry.path)
|
||||
const removed = await Git.run(["worktree", "remove", "--force", entry.path], {
|
||||
const removed = await git(["worktree", "remove", "--force", entry.path], {
|
||||
cwd: Instance.worktree,
|
||||
})
|
||||
if (removed.exitCode !== 0) {
|
||||
const next = await Git.run(["worktree", "list", "--porcelain"], { cwd: Instance.worktree })
|
||||
const next = await git(["worktree", "list", "--porcelain"], { cwd: Instance.worktree })
|
||||
if (next.exitCode !== 0) {
|
||||
throw new RemoveFailedError({
|
||||
message: errorText(removed) || errorText(next) || "Failed to remove git worktree",
|
||||
@@ -520,7 +520,7 @@ export namespace Worktree {
|
||||
|
||||
const branch = entry.branch?.replace(/^refs\/heads\//, "")
|
||||
if (branch) {
|
||||
const deleted = await Git.run(["branch", "-D", branch], { cwd: Instance.worktree })
|
||||
const deleted = await git(["branch", "-D", branch], { cwd: Instance.worktree })
|
||||
if (deleted.exitCode !== 0) {
|
||||
throw new RemoveFailedError({ message: errorText(deleted) || "Failed to delete worktree branch" })
|
||||
}
|
||||
@@ -540,7 +540,7 @@ export namespace Worktree {
|
||||
throw new ResetFailedError({ message: "Cannot reset the primary workspace" })
|
||||
}
|
||||
|
||||
const list = await Git.run(["worktree", "list", "--porcelain"], { cwd: Instance.worktree })
|
||||
const list = await git(["worktree", "list", "--porcelain"], { cwd: Instance.worktree })
|
||||
if (list.exitCode !== 0) {
|
||||
throw new ResetFailedError({ message: errorText(list) || "Failed to read git worktrees" })
|
||||
}
|
||||
@@ -573,7 +573,7 @@ export namespace Worktree {
|
||||
throw new ResetFailedError({ message: "Worktree not found" })
|
||||
}
|
||||
|
||||
const remoteList = await Git.run(["remote"], { cwd: Instance.worktree })
|
||||
const remoteList = await git(["remote"], { cwd: Instance.worktree })
|
||||
if (remoteList.exitCode !== 0) {
|
||||
throw new ResetFailedError({ message: errorText(remoteList) || "Failed to list git remotes" })
|
||||
}
|
||||
@@ -592,17 +592,17 @@ export namespace Worktree {
|
||||
: ""
|
||||
|
||||
const remoteHead = remote
|
||||
? await Git.run(["symbolic-ref", `refs/remotes/${remote}/HEAD`], { cwd: Instance.worktree })
|
||||
? await git(["symbolic-ref", `refs/remotes/${remote}/HEAD`], { cwd: Instance.worktree })
|
||||
: { exitCode: 1, stdout: undefined, stderr: undefined }
|
||||
|
||||
const remoteRef = remoteHead.exitCode === 0 ? outputText(remoteHead.stdout) : ""
|
||||
const remoteTarget = remoteRef ? remoteRef.replace(/^refs\/remotes\//, "") : ""
|
||||
const remoteBranch = remote && remoteTarget.startsWith(`${remote}/`) ? remoteTarget.slice(`${remote}/`.length) : ""
|
||||
|
||||
const mainCheck = await Git.run(["show-ref", "--verify", "--quiet", "refs/heads/main"], {
|
||||
const mainCheck = await git(["show-ref", "--verify", "--quiet", "refs/heads/main"], {
|
||||
cwd: Instance.worktree,
|
||||
})
|
||||
const masterCheck = await Git.run(["show-ref", "--verify", "--quiet", "refs/heads/master"], {
|
||||
const masterCheck = await git(["show-ref", "--verify", "--quiet", "refs/heads/master"], {
|
||||
cwd: Instance.worktree,
|
||||
})
|
||||
const localBranch = mainCheck.exitCode === 0 ? "main" : masterCheck.exitCode === 0 ? "master" : ""
|
||||
@@ -613,7 +613,7 @@ export namespace Worktree {
|
||||
}
|
||||
|
||||
if (remoteBranch) {
|
||||
const fetch = await Git.run(["fetch", remote, remoteBranch], { cwd: Instance.worktree })
|
||||
const fetch = await git(["fetch", remote, remoteBranch], { cwd: Instance.worktree })
|
||||
if (fetch.exitCode !== 0) {
|
||||
throw new ResetFailedError({ message: errorText(fetch) || `Failed to fetch ${target}` })
|
||||
}
|
||||
@@ -625,7 +625,7 @@ export namespace Worktree {
|
||||
|
||||
const worktreePath = entry.path
|
||||
|
||||
const resetToTarget = await Git.run(["reset", "--hard", target], { cwd: worktreePath })
|
||||
const resetToTarget = await git(["reset", "--hard", target], { cwd: worktreePath })
|
||||
if (resetToTarget.exitCode !== 0) {
|
||||
throw new ResetFailedError({ message: errorText(resetToTarget) || "Failed to reset worktree to target" })
|
||||
}
|
||||
@@ -635,26 +635,26 @@ export namespace Worktree {
|
||||
throw new ResetFailedError({ message: errorText(clean) || "Failed to clean worktree" })
|
||||
}
|
||||
|
||||
const update = await Git.run(["submodule", "update", "--init", "--recursive", "--force"], { cwd: worktreePath })
|
||||
const update = await git(["submodule", "update", "--init", "--recursive", "--force"], { cwd: worktreePath })
|
||||
if (update.exitCode !== 0) {
|
||||
throw new ResetFailedError({ message: errorText(update) || "Failed to update submodules" })
|
||||
}
|
||||
|
||||
const subReset = await Git.run(["submodule", "foreach", "--recursive", "git", "reset", "--hard"], {
|
||||
const subReset = await git(["submodule", "foreach", "--recursive", "git", "reset", "--hard"], {
|
||||
cwd: worktreePath,
|
||||
})
|
||||
if (subReset.exitCode !== 0) {
|
||||
throw new ResetFailedError({ message: errorText(subReset) || "Failed to reset submodules" })
|
||||
}
|
||||
|
||||
const subClean = await Git.run(["submodule", "foreach", "--recursive", "git", "clean", "-fdx"], {
|
||||
const subClean = await git(["submodule", "foreach", "--recursive", "git", "clean", "-fdx"], {
|
||||
cwd: worktreePath,
|
||||
})
|
||||
if (subClean.exitCode !== 0) {
|
||||
throw new ResetFailedError({ message: errorText(subClean) || "Failed to clean submodules" })
|
||||
}
|
||||
|
||||
const status = await Git.run(["-c", "core.fsmonitor=false", "status", "--porcelain=v1"], { cwd: worktreePath })
|
||||
const status = await git(["-c", "core.fsmonitor=false", "status", "--porcelain=v1"], { cwd: worktreePath })
|
||||
if (status.exitCode !== 0) {
|
||||
throw new ResetFailedError({ message: errorText(status) || "Failed to read git status" })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user