mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-09 22:25:02 +03:00
refactor(core): resolve real git binary once on macOS (#58602)
This commit is contained in:
committed by
GitHub
Unverified
parent
6436011a52
commit
a20ae6da09
@@ -339,7 +339,7 @@ const layer = Layer.effect(
|
||||
) {
|
||||
const result = yield* proc
|
||||
.run(
|
||||
ChildProcess.make(gitExecutable, repositoryArgs(repository, args), {
|
||||
ChildProcess.make(gitExecutable(), repositoryArgs(repository, args), {
|
||||
cwd: repository.worktree,
|
||||
env: options?.index ? { ...options.env, GIT_INDEX_FILE: options.index } : options?.env,
|
||||
extendEnv: true,
|
||||
@@ -549,7 +549,7 @@ const layer = Layer.effect(
|
||||
const result = yield* proc
|
||||
.run(
|
||||
ChildProcess.make(
|
||||
gitExecutable,
|
||||
gitExecutable(),
|
||||
repositoryArgs(input.repository, ["check-ignore", "--no-index", "--stdin", "-z"]),
|
||||
{
|
||||
cwd: input.repository.worktree,
|
||||
@@ -1227,7 +1227,7 @@ const layer = Layer.effect(
|
||||
cwd = repository.worktree,
|
||||
) {
|
||||
const result = yield* proc
|
||||
.run(ChildProcess.make(gitExecutable, 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 }),
|
||||
@@ -1324,7 +1324,7 @@ function run(cwd: string, proc: AppProcess.Interface, args: string[]) {
|
||||
function execute(cwd: string, proc: AppProcess.Interface, args: string[]) {
|
||||
return proc
|
||||
.run(
|
||||
ChildProcess.make(gitExecutable, args, {
|
||||
ChildProcess.make(gitExecutable(), args, {
|
||||
cwd,
|
||||
extendEnv: true,
|
||||
stdin: "ignore",
|
||||
|
||||
@@ -44,7 +44,7 @@ export const Plugin = define({
|
||||
init: (input) =>
|
||||
Effect.gen(function* () {
|
||||
const result = yield* processes.run(
|
||||
ChildProcess.make(gitExecutable, ["init"], { cwd: input.worktree, stdin: "ignore" }),
|
||||
ChildProcess.make(gitExecutable(), ["init"], { cwd: input.worktree, stdin: "ignore" }),
|
||||
)
|
||||
if (result.exitCode !== 0) return yield* Effect.fail(new Error("Git initialization failed"))
|
||||
}),
|
||||
@@ -209,7 +209,7 @@ function makeGit(proc: AppProcess.Interface, env?: Record<string, string>) {
|
||||
const run = Effect.fnUntraced(
|
||||
function* (args: string[], opts: { cwd: string; maxOutputBytes?: number }) {
|
||||
const result = yield* proc.run(
|
||||
ChildProcess.make(gitExecutable, [...cfg, ...args], {
|
||||
ChildProcess.make(gitExecutable(), [...cfg, ...args], {
|
||||
cwd: opts.cwd,
|
||||
env,
|
||||
extendEnv: true,
|
||||
|
||||
@@ -1,11 +1,59 @@
|
||||
import { existsSync, realpathSync } from "node:fs"
|
||||
import path from "path"
|
||||
import { lazy } from "./lazy.js"
|
||||
import { which } from "./which.js"
|
||||
|
||||
export function resolveGitExecutable(platform: NodeJS.Platform, resolved: string | null) {
|
||||
if (platform !== "win32" || !resolved) return "git"
|
||||
return path.resolve(resolved)
|
||||
const darwinShim = "/usr/bin/git"
|
||||
const xcodeSelectLink = "/var/db/xcode_select_link"
|
||||
|
||||
const defaultDeveloperDirs = [
|
||||
"/Applications/Xcode.app/Contents/Developer",
|
||||
"/Library/Developer/CommandLineTools",
|
||||
]
|
||||
|
||||
function readXcodeSelectLink(link: string) {
|
||||
if (!existsSync(link)) return null
|
||||
try {
|
||||
return realpathSync(link)
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const resolved = process.platform === "win32" ? which("git") : null
|
||||
export function darwinDeveloperDirs(
|
||||
developerDir = process.env.DEVELOPER_DIR?.trim(),
|
||||
readLink: (link: string) => string | null = readXcodeSelectLink,
|
||||
) {
|
||||
if (developerDir) return [developerDir]
|
||||
const selected = readLink(xcodeSelectLink)
|
||||
return selected ? [selected, ...defaultDeveloperDirs] : defaultDeveloperDirs
|
||||
}
|
||||
|
||||
export const gitExecutable = resolveGitExecutable(process.platform, resolved)
|
||||
export function resolveGitExecutable(
|
||||
platform: NodeJS.Platform,
|
||||
resolved: string | null,
|
||||
options?: {
|
||||
developerDirs?: readonly string[]
|
||||
exists?: (file: string) => boolean
|
||||
},
|
||||
) {
|
||||
if (platform === "win32") return resolved ? path.resolve(resolved) : "git"
|
||||
if (platform !== "darwin" || !resolved) return "git"
|
||||
const full = path.posix.resolve(resolved)
|
||||
if (full !== darwinShim) return full
|
||||
const exists = options?.exists ?? existsSync
|
||||
const dirs = options?.developerDirs ?? darwinDeveloperDirs()
|
||||
return (
|
||||
dirs
|
||||
.filter((dir) => Boolean(dir) && path.posix.isAbsolute(dir))
|
||||
.map((dir) => path.posix.join(dir, "usr/bin/git"))
|
||||
.find((candidate) => candidate !== darwinShim && exists(candidate)) ?? "git"
|
||||
)
|
||||
}
|
||||
|
||||
export const gitExecutable = lazy(() =>
|
||||
resolveGitExecutable(
|
||||
process.platform,
|
||||
process.platform === "win32" || process.platform === "darwin" ? which("git") : null,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,13 +1,76 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import path from "path"
|
||||
import { resolveGitExecutable } from "@opencode/core/util/git-executable"
|
||||
import { darwinDeveloperDirs, resolveGitExecutable } from "@opencode/core/util/git-executable"
|
||||
|
||||
describe("Git executable", () => {
|
||||
test("uses an absolute executable on Windows", () => {
|
||||
expect(resolveGitExecutable("win32", "bin/git.exe")).toBe(path.resolve("bin/git.exe"))
|
||||
})
|
||||
|
||||
test("leaves lookup to the process outside Windows", () => {
|
||||
test("uses a non-shim PATH executable on macOS without probing developer directories", () => {
|
||||
let calls = 0
|
||||
const resolved = resolveGitExecutable("darwin", "/opt/homebrew/bin/git", {
|
||||
developerDirs: ["/Library/Developer/CommandLineTools"],
|
||||
exists: () => {
|
||||
calls += 1
|
||||
return true
|
||||
},
|
||||
})
|
||||
expect(resolved).toBe("/opt/homebrew/bin/git")
|
||||
expect(calls).toBe(0)
|
||||
})
|
||||
|
||||
test("resolves the Apple shim on macOS from developer directories", () => {
|
||||
const resolved = resolveGitExecutable("darwin", "/usr/bin/git", {
|
||||
developerDirs: ["/Applications/Xcode.app/Contents/Developer", "/Library/Developer/CommandLineTools"],
|
||||
exists: (file) => file === "/Library/Developer/CommandLineTools/usr/bin/git",
|
||||
})
|
||||
expect(resolved).toBe("/Library/Developer/CommandLineTools/usr/bin/git")
|
||||
})
|
||||
|
||||
test("falls back to PATH lookup on macOS when no developer tools exist or candidate is invalid", () => {
|
||||
expect(
|
||||
resolveGitExecutable("darwin", "/usr/bin/git", {
|
||||
developerDirs: ["/Applications/Xcode.app/Contents/Developer", "/Library/Developer/CommandLineTools"],
|
||||
exists: () => false,
|
||||
}),
|
||||
).toBe("git")
|
||||
expect(
|
||||
resolveGitExecutable("darwin", "/usr/bin/git", {
|
||||
developerDirs: ["relative/dir"],
|
||||
exists: () => true,
|
||||
}),
|
||||
).toBe("git")
|
||||
expect(
|
||||
resolveGitExecutable("darwin", "/usr/bin/git", {
|
||||
developerDirs: ["/"],
|
||||
exists: () => true,
|
||||
}),
|
||||
).toBe("git")
|
||||
expect(
|
||||
resolveGitExecutable("darwin", null, {
|
||||
developerDirs: ["/Library/Developer/CommandLineTools"],
|
||||
exists: () => true,
|
||||
}),
|
||||
).toBe("git")
|
||||
})
|
||||
|
||||
test("orders macOS developer directory candidates by DEVELOPER_DIR then xcode-select link", () => {
|
||||
expect(darwinDeveloperDirs("/Custom/Developer", () => "/Library/Developer/CommandLineTools")).toEqual([
|
||||
"/Custom/Developer",
|
||||
])
|
||||
expect(darwinDeveloperDirs("", () => "/Applications/Xcode-beta.app/Contents/Developer")).toEqual([
|
||||
"/Applications/Xcode-beta.app/Contents/Developer",
|
||||
"/Applications/Xcode.app/Contents/Developer",
|
||||
"/Library/Developer/CommandLineTools",
|
||||
])
|
||||
expect(darwinDeveloperDirs(undefined, () => null)).toEqual([
|
||||
"/Applications/Xcode.app/Contents/Developer",
|
||||
"/Library/Developer/CommandLineTools",
|
||||
])
|
||||
})
|
||||
|
||||
test("leaves lookup to the process on Linux", () => {
|
||||
expect(resolveGitExecutable("linux", "/usr/bin/git")).toBe("git")
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user