Compare commits

..
Author SHA1 Message Date
AidenandHona c15922f200 fix(core): bypass Windows Git lookup
Co-authored-by: Hona <10430890+Hona@users.noreply.github.com>
2026-08-21 04:28:17 +00:00
2 changed files with 38 additions and 62 deletions
+29 -61
View File
@@ -9,6 +9,10 @@ import { AppProcess } from "@opencode-ai/util/process"
import { makeGlobalNode } from "@opencode-ai/util/effect/app-node"
import { File } from "./file.js"
import { KeyedMutex } from "./effect/keyed-mutex.js"
import { which } from "./util/which.js"
const resolvedGit = process.platform === "win32" ? which("git") : undefined
const gitExecutable = resolvedGit ? path.resolve(resolvedGit) : "git"
export class Repository extends Schema.Class<Repository>("Git.Repository")({
worktree: AbsolutePath,
@@ -314,7 +318,7 @@ const layer = Layer.effect(
) {
const result = yield* proc
.run(
ChildProcess.make("git", repositoryArgs(repository, args), {
ChildProcess.make(gitExecutable, repositoryArgs(repository, args), {
cwd: repository.worktree,
env: options?.env,
extendEnv: true,
@@ -422,19 +426,18 @@ const layer = Layer.effect(
ignores?: Repository
maximumUntrackedFileBytes?: number
}) {
const status = parseStatus(
(yield* repositoryOperation("refresh", input.repository, [
"-c",
"diff.ignoreSubmodules=untracked",
"status",
"--porcelain=v2",
"-z",
"--untracked-files=all",
"--",
input.scope,
])).text,
const list = (args: string[]) =>
repositoryOperation("refresh", input.repository, args).pipe(
Effect.map((result) => result.text.split("\0").filter(Boolean)),
)
const [tracked, untracked] = yield* Effect.all(
[
list(["diff-files", "--name-only", "-z", "--", input.scope]),
list(["ls-files", "--others", "--exclude-standard", "-z", "--", input.scope]),
],
{ concurrency: 2 },
)
const candidates = Array.from(new Set([...status.tracked, ...status.untracked]))
const candidates = Array.from(new Set([...tracked, ...untracked]))
if (!candidates.length) return { skipped: [] }
const ignored = input.ignores
? new Set(
@@ -445,11 +448,11 @@ const layer = Layer.effect(
.filter(Boolean),
)
: new Set<string>()
const allowed = new Set(candidates.filter((item) => !ignored.has(item)))
const allowed = candidates.filter((item) => !ignored.has(item))
const maximum = input.maximumUntrackedFileBytes
const skipped = maximum
? (yield* Effect.forEach(
status.untracked.filter((item) => allowed.has(item)),
untracked.filter((item) => allowed.includes(item)),
(item) =>
fs.stat(path.join(input.repository.worktree, item)).pipe(
Effect.map((info) =>
@@ -460,8 +463,7 @@ const layer = Layer.effect(
{ concurrency: 8 },
)).filter((item): item is RelativePath => item !== undefined)
: []
const skippedSet = new Set(skipped)
const stage = Array.from(allowed).filter((item) => !skippedSet.has(RelativePath.make(item)))
const stage = allowed.filter((item) => !skipped.includes(RelativePath.make(item)))
const remove = [...ignored, ...skipped]
if (remove.length)
yield* repositoryOperation(
@@ -487,10 +489,14 @@ const layer = Layer.effect(
if (!input.paths.length) return new Set<RelativePath>()
const result = yield* proc
.run(
ChildProcess.make("git", repositoryArgs(input.repository, ["check-ignore", "--no-index", "--stdin", "-z"]), {
cwd: input.repository.worktree,
extendEnv: true,
}),
ChildProcess.make(
gitExecutable,
repositoryArgs(input.repository, ["check-ignore", "--no-index", "--stdin", "-z"]),
{
cwd: input.repository.worktree,
extendEnv: true,
},
),
{ stdin: input.paths.join("\0") + "\0" },
)
.pipe(
@@ -664,7 +670,7 @@ const layer = Layer.effect(
cwd = repository.worktree,
) {
const result = yield* proc
.run(ChildProcess.make("git", args, { cwd, extendEnv: true, stdin: "ignore" }))
.run(ChildProcess.make(gitExecutable, args, { cwd, extendEnv: true, stdin: "ignore" }))
.pipe(
Effect.mapError(
(cause) => new WorktreeError({ operation, directory: worktreeDirectory, message: cause.message, cause }),
@@ -761,7 +767,7 @@ function execute(cwd: string, proc: AppProcess.Interface) {
return (args: string[]) =>
proc
.run(
ChildProcess.make("git", args, {
ChildProcess.make(gitExecutable, args, {
cwd,
extendEnv: true,
stdin: "ignore",
@@ -779,44 +785,6 @@ function execute(cwd: string, proc: AppProcess.Interface) {
)
}
function parseStatus(output: string) {
const tracked: string[] = []
const untracked: string[] = []
const entries = output.split("\0")
for (let index = 0; index < entries.length; index++) {
const entry = entries[index]
if (!entry) continue
const kind = entry[0]
if (kind === "?") {
untracked.push(entry.slice(2))
continue
}
if (kind === "u") {
const path = statusPath(entry, 10)
if (path) tracked.push(path)
continue
}
if (kind !== "1" && kind !== "2") continue
const status = entry.slice(2, 4)
const submodule = entry.slice(5, 9)
const changed = submodule[0] === "N" || submodule[1] !== "." || submodule[2] !== "."
const path = status[1] === "." || !changed ? undefined : statusPath(entry, kind === "1" ? 8 : 9)
if (path) tracked.push(path)
if (kind === "2") index++
}
return { tracked, untracked }
}
function statusPath(entry: string, fields: number) {
let offset = 0
for (let field = 0; field < fields; field++) {
offset = entry.indexOf(" ", offset)
if (offset === -1) return
offset++
}
return entry.slice(offset)
}
function resolvePath(cwd: string, value: string) {
const trimmed = value.replace(/[\r\n]+$/, "")
if (!trimmed) return cwd
+9 -1
View File
@@ -22,6 +22,7 @@ import { makeGlobalNode } from "./effect/app-node.js"
import { filesystem, path } from "./effect/app-node-platform.js"
const toError = (err: unknown): Error => (err instanceof globalThis.Error ? err : new globalThis.Error(String(err)))
const nativeWindowsExtensions = new Set([".com", ".exe"])
const toTag = (err: NodeJS.ErrnoException): PlatformError.SystemErrorTag => {
switch (err.code) {
@@ -261,7 +262,14 @@ const makeCrossSpawnSpawner = Effect.gen(function* () {
const launchProcess = (command: ChildProcess.StandardCommand, opts: NodeChildProcess.SpawnOptions) =>
Effect.callback<readonly [NodeChildProcess.ChildProcess, ExitSignal], PlatformError.PlatformError>((resume) => {
const signal = Deferred.makeUnsafe<readonly [code: number | null, signal: NodeJS.Signals | null]>()
const proc = launch(command.command, command.args, opts)
const native =
process.platform === "win32" &&
!opts.shell &&
path.isAbsolute(command.command) &&
nativeWindowsExtensions.has(path.extname(command.command).toLowerCase())
const proc = native
? NodeChildProcess.spawn(command.command, command.args, opts)
: launch(command.command, command.args, opts)
let end = false
let exit: readonly [code: number | null, signal: NodeJS.Signals | null] | undefined
proc.on("error", (err) => {