refactor(core): share git command and patch helpers (#61168)

This commit is contained in:
Aiden Cline
2026-11-01 21:13:53 -06:00
committed by GitHub
Unverified
parent a2ccb66ce4
commit bec6c3e925
2 changed files with 79 additions and 112 deletions
+49 -53
View File
@@ -329,15 +329,9 @@ const layer = Layer.effect(
directory: AbsolutePath,
args: string[],
) {
const result = yield* execute(directory, proc, args).pipe(
Effect.mapError((cause) => new OperationError({ operation, directory, message: cause.message, cause })),
)
const result = yield* execute(directory, proc, args).pipe(Effect.mapError(spawnFailure(operation, directory)))
if (result.exitCode === 0) return
return yield* new OperationError({
operation,
directory,
message: result.stderr.trim() || result.text.trim() || `Git ${operation} failed`,
})
return yield* commandFailure(operation, directory, result)
})
const clone = Effect.fn("Git.repo.clone")(function* (input: {
@@ -403,13 +397,13 @@ const layer = Layer.effect(
...args,
]
const repositoryOperation = Effect.fnUntraced(function* (
const repositoryRun = (
operationName: OperationError["operation"],
repository: Repository,
args: string[],
options?: { stdin?: string | Uint8Array; env?: Record<string, string>; maxOutputBytes?: number; index?: string },
) {
const result = yield* proc
options?: RepositoryOptions,
) =>
proc
.run(
ChildProcess.make(gitExecutable(), repositoryArgs(repository, args), {
cwd: repository.worktree,
@@ -418,25 +412,19 @@ const layer = Layer.effect(
}),
{ stdin: options?.stdin, maxOutputBytes: options?.maxOutputBytes },
)
.pipe(
Effect.mapError(
(cause) =>
new OperationError({
operation: operationName,
directory: repository.worktree,
message: cause.message,
cause,
}),
),
)
.pipe(Effect.mapError(spawnFailure(operationName, repository.worktree)))
const repositoryOperation = Effect.fnUntraced(function* (
operationName: OperationError["operation"],
repository: Repository,
args: string[],
options?: RepositoryOptions,
) {
const result = yield* repositoryRun(operationName, repository, args, options)
const text = result.stdout.toString("utf8")
if (result.exitCode === 0)
return { text, stderr: result.stderr.toString("utf8"), truncated: result.stdoutTruncated }
return yield* new OperationError({
operation: operationName,
directory: repository.worktree,
message: result.stderr.toString("utf8").trim() || text.trim() || `Git ${operationName} failed`,
})
const stderr = result.stderr.toString("utf8")
if (result.exitCode === 0) return { text, stderr, truncated: result.stdoutTruncated }
return yield* commandFailure(operationName, repository.worktree, { text, stderr })
})
/**
@@ -637,29 +625,13 @@ const layer = Layer.effect(
paths: readonly RelativePath[]
}) {
if (!input.paths.length) return new Set<RelativePath>()
const result = yield* proc
.run(
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(
Effect.mapError(
(cause) =>
new OperationError({
operation: "list_files",
directory: input.repository.worktree,
message: cause.message,
cause,
}),
),
)
const result = yield* repositoryRun(
"list_files",
input.repository,
["check-ignore", "--no-index", "--stdin", "-z"],
{ stdin: input.paths.join("\0") + "\0" },
)
// Exit 1: none of the paths are ignored.
if (result.exitCode !== 0 && result.exitCode !== 1)
return yield* new OperationError({
operation: "list_files",
@@ -1507,6 +1479,30 @@ interface Result {
readonly stderr: string
}
interface RepositoryOptions {
readonly stdin?: string | Uint8Array
readonly env?: Record<string, string>
readonly maxOutputBytes?: number
readonly index?: string
}
function spawnFailure(operation: OperationError["operation"], directory: AbsolutePath) {
return (cause: { readonly message: string }) =>
new OperationError({ operation, directory, message: cause.message, cause })
}
function commandFailure(
operation: OperationError["operation"],
directory: AbsolutePath,
result: { readonly text: string; readonly stderr: string },
) {
return new OperationError({
operation,
directory,
message: result.stderr.trim() || result.text.trim() || `Git ${operation} failed`,
})
}
function run(cwd: string, proc: AppProcess.Interface, args: string[]) {
return execute(cwd, proc, args).pipe(Effect.orElseSucceed(() => ({ exitCode: 1, text: "", stderr: "" })))
}
+30 -59
View File
@@ -196,19 +196,14 @@ const numstat = (text: string) =>
if (a === -1 || b === -1) return []
const file = item.slice(b + 1)
if (!file) return []
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)
return [
{
file,
additions: Number.isFinite(additions) ? additions : 0,
deletions: Number.isFinite(deletions) ? deletions : 0,
} satisfies Stat,
]
return [{ file, additions: count(item.slice(0, a)), deletions: count(item.slice(a + 1, b)) } satisfies Stat]
})
const count = (value: string) => {
const parsed = value === "-" ? 0 : Number.parseInt(value || "0", 10)
return Number.isFinite(parsed) ? parsed : 0
}
function makeGit(proc: AppProcess.Interface, env?: Record<string, string>) {
const run = Effect.fnUntraced(
function* (args: string[], opts: { cwd: string; maxOutputBytes?: number }) {
@@ -413,40 +408,40 @@ function makeGit(proc: AppProcess.Interface, env?: Record<string, string>) {
)
})
const patch = Effect.fn("VcsGit.patch")(function* (cwd: string, ref: string, file: string, options?: PatchOptions) {
const diffPatch = Effect.fnUntraced(function* (
cwd: string,
args: string[],
options: PatchOptions | undefined,
mode: { dropTruncated: boolean; noIndex?: boolean },
) {
const result = yield* run(
[
"diff",
...(mode.noIndex ? ["--no-index"] : []),
"--patch",
"--no-ext-diff",
"--no-renames",
`--unified=${options?.context ?? 3}`,
ref,
...(options?.target ? [options.target] : []),
"--",
file,
...args,
],
{ cwd, maxOutputBytes: options?.maxOutputBytes },
)
return { text: result.truncated ? "" : result.text(), truncated: result.truncated } satisfies Patch
return {
text: mode.dropTruncated && result.truncated ? "" : result.text(),
truncated: result.truncated,
} satisfies Patch
})
const patch = Effect.fn("VcsGit.patch")(function* (cwd: string, ref: string, file: string, options?: PatchOptions) {
return yield* diffPatch(cwd, [ref, ...(options?.target ? [options.target] : []), "--", file], options, {
dropTruncated: true,
})
})
const patchAll = Effect.fn("VcsGit.patchAll")(function* (cwd: string, ref: string, options?: PatchOptions) {
const result = yield* run(
[
"diff",
"--patch",
"--no-ext-diff",
"--no-renames",
`--unified=${options?.context ?? 3}`,
ref,
...(options?.target ? [options.target] : []),
"--",
".",
],
{ cwd, maxOutputBytes: options?.maxOutputBytes },
)
return { text: result.text(), truncated: result.truncated } satisfies Patch
return yield* diffPatch(cwd, [ref, ...(options?.target ? [options.target] : []), "--", "."], options, {
dropTruncated: false,
})
})
const patchUntracked = Effect.fn("VcsGit.patchUntracked")(function* (
@@ -456,21 +451,7 @@ function makeGit(proc: AppProcess.Interface, env?: Record<string, string>) {
) {
// A folder entry has no patch of its own.
if (file.endsWith("/")) return { text: "", truncated: false } satisfies Patch
const result = yield* run(
[
"diff",
"--no-index",
"--patch",
"--no-ext-diff",
"--no-renames",
`--unified=${options?.context ?? 3}`,
"--",
"/dev/null",
file,
],
{ cwd, maxOutputBytes: options?.maxOutputBytes },
)
return { text: result.truncated ? "" : result.text(), truncated: result.truncated } satisfies Patch
return yield* diffPatch(cwd, ["--", "/dev/null", file], options, { dropTruncated: true, noIndex: true })
})
const statUntracked = Effect.fn("VcsGit.statUntracked")(function* (cwd: string, file: string) {
@@ -484,13 +465,7 @@ function makeGit(proc: AppProcess.Interface, env?: Record<string, string>) {
const parts = result.text().split("\t")
if (parts.length < 2) return
const additions = parts[0] === "-" ? 0 : Number.parseInt(parts[0] || "0", 10)
const deletions = parts[1] === "-" ? 0 : Number.parseInt(parts[1] || "0", 10)
return {
file,
additions: Number.isFinite(additions) ? additions : 0,
deletions: Number.isFinite(deletions) ? deletions : 0,
} satisfies Stat
return { file, additions: count(parts[0]), deletions: count(parts[1]) } satisfies Stat
})
// Paths are literal so names containing pathspec magic such as `*` or `[` match only themselves.
@@ -517,11 +492,7 @@ function makeGit(proc: AppProcess.Interface, env?: Record<string, string>) {
})
const worktreePatch = Effect.fn("VcsGit.worktreePatch")(function* (cwd: string, options?: PatchOptions) {
const result = yield* run(
["diff", "--patch", "--no-ext-diff", "--no-renames", `--unified=${options?.context ?? 3}`],
{ cwd, maxOutputBytes: options?.maxOutputBytes },
)
return { text: result.text(), truncated: result.truncated } satisfies Patch
return yield* diffPatch(cwd, [], options, { dropTruncated: false })
})
return {