import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, it, describe } from "@effect/vitest";import * as Effect from "effect/Effect";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Path from "effect/Path";import * as PlatformError from "effect/PlatformError";import * as Scope from "effect/Scope";import * as Sink from "effect/Sink";import * as Stream from "effect/Stream";import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { GitCommandError } from "@t3tools/contracts";import { ServerConfig } from "../config.ts";import { splitNullSeparatedGitStdoutPaths } from "./GitVcsDriverCore.ts";import * as GitVcsDriver from "./GitVcsDriver.ts"; const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), { prefix: "t3-git-vcs-driver-test-",});const TestLayer = GitVcsDriver.layer.pipe( Layer.provide(ServerConfigLayer), Layer.provideMerge(NodeServices.layer),); const makeNonRepositoryHandle = () => ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(1), exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(128)), isRunning: Effect.succeed(false), kill: () => Effect.void, unref: Effect.succeed(Effect.void), stdin: Sink.drain, stdout: Stream.empty, stderr: Stream.encodeText(Stream.make("fatal: not a git repository")), all: Stream.empty, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, }); const makeTmpDir = ( prefix = "git-vcs-driver-test-",): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.makeTempDirectoryScoped({ prefix }); }); const writeTextFile = ( cwd: string, relativePath: string, contents: string,): Effect.Effect<void, PlatformError.PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const pathService = yield* Path.Path; const filePath = pathService.join(cwd, relativePath); yield* fileSystem.makeDirectory(pathService.dirname(filePath), { recursive: true }); yield* fileSystem.writeFileString(filePath, contents); }); const git = ( cwd: string, args: ReadonlyArray<string>, env?: NodeJS.ProcessEnv,): Effect.Effect<string, GitCommandError, GitVcsDriver.GitVcsDriver> => Effect.gen(function* () { const driver = yield* GitVcsDriver.GitVcsDriver; const result = yield* driver.execute({ operation: "GitVcsDriver.test.git", cwd, args, ...(env ? { env } : {}), timeoutMs: 10_000, }); return result.stdout.trim(); }); const initRepoWithCommit = ( cwd: string,): Effect.Effect< { readonly initialBranch: string }, GitCommandError | PlatformError.PlatformError, GitVcsDriver.GitVcsDriver | FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const driver = yield* GitVcsDriver.GitVcsDriver; yield* driver.initRepo({ cwd }); yield* git(cwd, ["config", "user.email", "test@test.com"]); yield* git(cwd, ["config", "user.name", "Test"]); yield* writeTextFile(cwd, "README.md", "# test\n"); yield* git(cwd, ["add", "."]); yield* git(cwd, ["commit", "-m", "initial commit"]); const initialBranch = yield* git(cwd, ["branch", "--show-current"]); return { initialBranch }; }); it.effect("uses stable diagnostics for every parsed non-repository command", () => { const commands: Array<{ readonly args: ReadonlyArray<string>; readonly lcAll?: string }> = []; const spawner = ChildProcessSpawner.make((command) => Effect.sync(() => { if (!ChildProcess.isStandardCommand(command)) { return assert.fail("expected a standard Git command"); } commands.push({ args: command.args, ...(command.options.env?.LC_ALL ? { lcAll: command.options.env.LC_ALL } : {}), }); return makeNonRepositoryHandle(); }), ); const nodeServicesLayer = Layer.merge( NodeServices.layer, Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), ); const layer = GitVcsDriver.layer.pipe( Layer.provide(ServerConfigLayer), Layer.provideMerge(nodeServicesLayer), ); return Effect.gen(function* () { const driver = yield* GitVcsDriver.GitVcsDriver; const cwd = "/repo"; yield* driver.statusDetailsLocal(cwd); yield* driver.statusDetailsRemote(cwd, { refreshUpstream: false }); yield* driver.listRefs({ cwd }); assert.deepStrictEqual(commands, [ { args: ["status", "--porcelain=2", "--branch"], lcAll: "C" }, { args: ["rev-parse", "--abbrev-ref", "HEAD"], lcAll: "C" }, { args: ["branch", "--no-color", "--no-column"], lcAll: "C" }, ]); }).pipe(Effect.provide(layer));}); it.layer(TestLayer)("GitVcsDriver core integration", (it) => { describe("process environment", () => { it.effect("preserves the caller locale for general Git subprocesses", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const locale = yield* git( cwd, ["-c", 'alias.print-locale=!printf "%s" "$LC_ALL"', "print-locale"], { LC_ALL: "zh_CN.UTF-8" }, ); assert.equal(locale, "zh_CN.UTF-8"); }), ); }); describe("structured errors", () => { it.effect("preserves structured spawn context and the platform cause", () => Effect.gen(function* () { const parent = yield* makeTmpDir(); const pathService = yield* Path.Path; const cwd = pathService.join(parent, "missing"); const driver = yield* GitVcsDriver.GitVcsDriver; const error = yield* driver .execute({ operation: "GitVcsDriver.test.missingCwd", cwd, args: ["status", "--short"], }) .pipe(Effect.flip); assert.deepInclude(error, { _tag: "GitCommandError", operation: "GitVcsDriver.test.missingCwd", command: "git", argumentCount: 2, cwd, detail: "Failed to spawn Git process.", }); if (!(error.cause instanceof PlatformError.PlatformError)) { return assert.fail("expected the original platform error cause"); } assert.equal(error.cause.reason._tag, "NotFound"); assert.notInclude(error.detail, error.cause.message); }), ); it.effect("does not retain git arguments or stderr in command failures", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* GitVcsDriver.GitVcsDriver; yield* driver.initRepo({ cwd }); const secret = "secret-token-value"; const error = yield* driver .execute({ operation: "GitVcsDriver.test.redactedFailure", cwd, args: ["status", `--unknown-option=${secret}`], }) .pipe(Effect.flip); assert.deepInclude(error, { _tag: "GitCommandError", operation: "GitVcsDriver.test.redactedFailure", command: "git", argumentCount: 2, cwd, }); assert.isNumber(error.exitCode); assert.isAbove(error.stderrLength ?? 0, 0); assert.notInclude(error.detail, secret); assert.notInclude(error.message, secret); assert.notProperty(error, "args"); assert.notProperty(error, "stderr"); }), ); it.effect("recovers a structurally identified missing cwd as a non-repository", () => Effect.gen(function* () { const parent = yield* makeTmpDir(); const pathService = yield* Path.Path; const cwd = pathService.join(parent, "missing"); const driver = yield* GitVcsDriver.GitVcsDriver; const [localStatus, remoteStatus, refs] = yield* Effect.all([ driver.statusDetails(cwd), driver.statusDetailsRemote(cwd, { refreshUpstream: false }), driver.listRefs({ cwd }), ]); assert.equal(localStatus.isRepo, false); assert.equal(remoteStatus.isRepo, false); assert.equal(refs.isRepo, false); assert.deepStrictEqual(refs.refs, []); }), ); it.effect("does not wrap a remove-worktree command failure in a synthetic error", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const pathService = yield* Path.Path; const missingWorktree = pathService.join(cwd, "missing-worktree"); const driver = yield* GitVcsDriver.GitVcsDriver; yield* driver.initRepo({ cwd }); const error = yield* driver .removeWorktree({ cwd, path: missingWorktree }) .pipe(Effect.flip); assert.deepInclude(error, { _tag: "GitCommandError", operation: "GitVcsDriver.removeWorktree", command: "git", argumentCount: 3, cwd, }); assert.notProperty(error, "cause"); assert.notInclude(error.detail, "Git command failed in"); }), ); }); describe("review diff previews", () => { it.effect("drops an unterminated path from truncated NUL-separated git output", () => Effect.sync(() => { const paths = splitNullSeparatedGitStdoutPaths({ stdout: "complete.txt\0partial", stdoutTruncated: true, }); assert.deepStrictEqual(paths, ["complete.txt"]); }), ); it.effect("keeps the final path when NUL-separated git output is complete", () => Effect.sync(() => { const paths = splitNullSeparatedGitStdoutPaths({ stdout: "complete.txt\0final.txt", stdoutTruncated: false, }); assert.deepStrictEqual(paths, ["complete.txt", "final.txt"]); }), ); it.effect("honors whitespace filtering for worktree and branch previews", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const { initialBranch } = yield* initRepoWithCommit(cwd); const driver = yield* GitVcsDriver.GitVcsDriver; yield* git(cwd, ["checkout", "-b", "feature/whitespace"]); yield* writeTextFile(cwd, "README.md", "# test\n"); yield* git(cwd, ["add", "README.md"]); yield* git(cwd, ["commit", "-m", "change whitespace"]); yield* writeTextFile(cwd, "README.md", "# test\n"); const included = yield* driver.getReviewDiffPreview({ cwd, baseRef: initialBranch, ignoreWhitespace: false, }); const ignored = yield* driver.getReviewDiffPreview({ cwd, baseRef: initialBranch, ignoreWhitespace: true, }); assert.isNotEmpty(included.sources.find((source) => source.kind === "working-tree")?.diff); assert.isNotEmpty(included.sources.find((source) => source.kind === "branch-range")?.diff); assert.strictEqual( ignored.sources.find((source) => source.kind === "working-tree")?.diff, "", ); assert.strictEqual( ignored.sources.find((source) => source.kind === "branch-range")?.diff, "", ); }), ); }); describe("repository status", () => { it.effect("reports non-repository directories without failing", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* GitVcsDriver.GitVcsDriver; const refs = yield* driver.listRefs({ cwd }); assert.equal(refs.isRepo, false); assert.deepStrictEqual(refs.refs, []); }), ); it.effect("reports refName and dirty state for a repository", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const { initialBranch } = yield* initRepoWithCommit(cwd); yield* writeTextFile(cwd, "feature.ts", "export const value = 1;\n"); const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetails(cwd); assert.equal(status.isRepo, true); assert.equal(status.branch, initialBranch); assert.equal(status.hasWorkingTreeChanges, true); assert.include( status.workingTree.files.map((file) => file.path), "feature.ts", ); }), ); it.effect("reports default-branch delta separately from upstream delta", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const remote = yield* makeTmpDir("git-vcs-driver-remote-"); const { initialBranch } = yield* initRepoWithCommit(cwd); yield* git(remote, ["init", "--bare"]); yield* git(cwd, ["remote", "add", "origin", remote]); yield* git(cwd, ["push", "-u", "origin", initialBranch]); yield* git(cwd, ["checkout", "-b", "feature/synced"]); yield* writeTextFile(cwd, "feature.txt", "feature\n"); yield* git(cwd, ["add", "feature.txt"]); yield* git(cwd, ["commit", "-m", "feature commit"]); yield* git(cwd, ["push", "-u", "origin", "feature/synced"]); const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetails(cwd); assert.equal(status.hasUpstream, true); assert.equal(status.aheadCount, 0); assert.equal(status.behindCount, 0); assert.equal(status.aheadOfDefaultCount, 1); }), ); it.effect("reports remote divergence without reading working-tree details", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const remote = yield* makeTmpDir("git-vcs-driver-remote-"); const { initialBranch } = yield* initRepoWithCommit(cwd); yield* git(remote, ["init", "--bare"]); yield* git(cwd, ["remote", "add", "origin", remote]); yield* git(cwd, ["push", "-u", "origin", initialBranch]); yield* git(cwd, ["checkout", "-b", "feature/remote-status"]); yield* writeTextFile(cwd, "feature.txt", "feature\n"); yield* git(cwd, ["add", "feature.txt"]); yield* git(cwd, ["commit", "-m", "feature commit"]); yield* git(cwd, ["push", "-u", "origin", "feature/remote-status"]); yield* writeTextFile(cwd, "untracked.txt", "local-only\n"); const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetailsRemote(cwd); assert.equal(status.isRepo, true); assert.equal(status.branch, "feature/remote-status"); assert.equal(status.hasUpstream, true); assert.equal(status.aheadCount, 0); assert.equal(status.behindCount, 0); assert.equal(status.aheadOfDefaultCount, 1); assert.notProperty(status, "workingTree"); assert.notProperty(status, "hasWorkingTreeChanges"); }), ); it.effect("can read cached remote divergence without fetching upstream", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const remote = yield* makeTmpDir("git-vcs-driver-remote-"); const updater = yield* makeTmpDir("git-vcs-driver-updater-"); const { initialBranch } = yield* initRepoWithCommit(cwd); yield* git(remote, ["init", "--bare"]); yield* git(cwd, ["remote", "add", "origin", remote]); yield* git(cwd, ["push", "-u", "origin", initialBranch]); yield* git(updater, ["clone", remote, "."]); yield* git(updater, ["config", "user.email", "test@test.com"]); yield* git(updater, ["config", "user.name", "Test"]); yield* writeTextFile(updater, "remote.txt", "remote\n"); yield* git(updater, ["add", "remote.txt"]); yield* git(updater, ["commit", "-m", "remote commit"]); yield* git(updater, ["push", "origin", initialBranch]); const driver = yield* GitVcsDriver.GitVcsDriver; const cachedStatus = yield* driver.statusDetailsRemote(cwd, { refreshUpstream: false, }); const refreshedStatus = yield* driver.statusDetailsRemote(cwd); assert.equal(cachedStatus.behindCount, 0); assert.equal(refreshedStatus.behindCount, 1); }), ); it.effect("uses origin HEAD for default-branch detection with a non-origin upstream", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const origin = yield* makeTmpDir("git-vcs-driver-origin-"); const upstream = yield* makeTmpDir("git-vcs-driver-upstream-"); yield* initRepoWithCommit(cwd); yield* git(origin, ["init", "--bare"]); yield* git(upstream, ["init", "--bare"]); yield* git(cwd, ["branch", "-M", "main"]); yield* git(cwd, ["remote", "add", "origin", origin]); yield* git(cwd, ["remote", "add", "upstream", upstream]); yield* git(cwd, ["push", "origin", "main"]); yield* git(cwd, ["push", "upstream", "main"]); yield* git(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD", "refs/remotes/origin/main"]); yield* git(cwd, ["checkout", "-b", "release"]); yield* writeTextFile(cwd, "release.txt", "release\n"); yield* git(cwd, ["add", "release.txt"]); yield* git(cwd, ["commit", "-m", "release commit"]); yield* git(cwd, ["push", "-u", "upstream", "release"]); yield* git(cwd, [ "symbolic-ref", "refs/remotes/upstream/HEAD", "refs/remotes/upstream/release", ]); const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetailsRemote(cwd); assert.equal(status.branch, "release"); assert.equal(status.upstreamRef, "upstream/release"); assert.equal(status.isDefaultBranch, false); }), ); it.effect("makes background upstream status fetches non-interactive", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const tempDir = yield* makeTmpDir("git-vcs-driver-ssh-env-"); const { initialBranch } = yield* initRepoWithCommit(cwd); const fileSystem = yield* FileSystem.FileSystem; const pathService = yield* Path.Path; const sshLogPath = pathService.join(tempDir, "ssh-env.txt"); const sshWrapperPath = pathService.join(tempDir, "ssh-wrapper.sh"); const envKeys = [ "GCM_INTERACTIVE", "GIT_ASKPASS", "GIT_SSH", "GIT_TERMINAL_PROMPT", "SSH_ASKPASS", "SSH_ASKPASS_REQUIRE", "T3_TEST_SSH_ASKPASS_LOG", ] as const; const previousEnv = new Map(envKeys.map((key) => [key, process.env[key]])); yield* fileSystem.writeFileString( sshWrapperPath, [ "#!/bin/sh", 'printf "GCM_INTERACTIVE=%s\\n" "${GCM_INTERACTIVE:-}" > "$T3_TEST_SSH_ASKPASS_LOG"', 'printf "GIT_ASKPASS=%s\\n" "${GIT_ASKPASS:-}" >> "$T3_TEST_SSH_ASKPASS_LOG"', 'printf "GIT_TERMINAL_PROMPT=%s\\n" "${GIT_TERMINAL_PROMPT:-}" >> "$T3_TEST_SSH_ASKPASS_LOG"', 'printf "SSH_ASKPASS=%s\\n" "${SSH_ASKPASS:-}" >> "$T3_TEST_SSH_ASKPASS_LOG"', 'printf "SSH_ASKPASS_REQUIRE=%s\\n" "${SSH_ASKPASS_REQUIRE:-}" >> "$T3_TEST_SSH_ASKPASS_LOG"', "exit 1", "", ].join("\n"), ); yield* fileSystem.chmod(sshWrapperPath, 0o755); yield* git(cwd, ["remote", "add", "origin", "ssh://example.invalid/repo.git"]); yield* git(cwd, ["update-ref", `refs/remotes/origin/${initialBranch}`, "HEAD"]); yield* git(cwd, ["branch", "--set-upstream-to", `origin/${initialBranch}`]);Lines 1–500 of 976. Load more
import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it, describe } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; import * as PlatformError from "effect/PlatformError"; import * as Scope from "effect/Scope"; import * as Sink from "effect/Sink"; import * as Stream from "effect/Stream"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; import { GitCommandError } from "@t3tools/contracts"; import { ServerConfig } from "../config.ts"; import { splitNullSeparatedGitStdoutPaths } from "./GitVcsDriverCore.ts"; import * as GitVcsDriver from "./GitVcsDriver.ts"; const ServerConfigLayer = ServerConfig.layerTest(process.cwd(), { prefix: "t3-git-vcs-driver-test-", }); const TestLayer = GitVcsDriver.layer.pipe( Layer.provide(ServerConfigLayer), Layer.provideMerge(NodeServices.layer), ); const makeNonRepositoryHandle = () => ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(1), exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(128)), isRunning: Effect.succeed(false), kill: () => Effect.void, unref: Effect.succeed(Effect.void), stdin: Sink.drain, stdout: Stream.empty, stderr: Stream.encodeText(Stream.make("fatal: not a git repository")), all: Stream.empty, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, }); const makeTmpDir = ( prefix = "git-vcs-driver-test-", ): Effect.Effect<string, PlatformError.PlatformError, FileSystem.FileSystem | Scope.Scope> => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.makeTempDirectoryScoped({ prefix }); }); const writeTextFile = ( cwd: string, relativePath: string, contents: string, ): Effect.Effect<void, PlatformError.PlatformError, FileSystem.FileSystem | Path.Path> => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const pathService = yield* Path.Path; const filePath = pathService.join(cwd, relativePath); yield* fileSystem.makeDirectory(pathService.dirname(filePath), { recursive: true }); yield* fileSystem.writeFileString(filePath, contents); }); const git = ( cwd: string, args: ReadonlyArray<string>, env?: NodeJS.ProcessEnv, ): Effect.Effect<string, GitCommandError, GitVcsDriver.GitVcsDriver> => Effect.gen(function* () { const driver = yield* GitVcsDriver.GitVcsDriver; const result = yield* driver.execute({ operation: "GitVcsDriver.test.git", cwd, args, ...(env ? { env } : {}), timeoutMs: 10_000, }); return result.stdout.trim(); }); const initRepoWithCommit = ( cwd: string, ): Effect.Effect< { readonly initialBranch: string }, GitCommandError | PlatformError.PlatformError, GitVcsDriver.GitVcsDriver | FileSystem.FileSystem | Path.Path > => Effect.gen(function* () { const driver = yield* GitVcsDriver.GitVcsDriver; yield* driver.initRepo({ cwd }); yield* git(cwd, ["config", "user.email", "test@test.com"]); yield* git(cwd, ["config", "user.name", "Test"]); yield* writeTextFile(cwd, "README.md", "# test\n"); yield* git(cwd, ["add", "."]); yield* git(cwd, ["commit", "-m", "initial commit"]); const initialBranch = yield* git(cwd, ["branch", "--show-current"]); return { initialBranch }; }); it.effect("uses stable diagnostics for every parsed non-repository command", () => { const commands: Array<{ readonly args: ReadonlyArray<string>; readonly lcAll?: string }> = []; const spawner = ChildProcessSpawner.make((command) => Effect.sync(() => { if (!ChildProcess.isStandardCommand(command)) { return assert.fail("expected a standard Git command"); } commands.push({ args: command.args, ...(command.options.env?.LC_ALL ? { lcAll: command.options.env.LC_ALL } : {}), }); return makeNonRepositoryHandle(); }), ); const nodeServicesLayer = Layer.merge( NodeServices.layer, Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), ); const layer = GitVcsDriver.layer.pipe( Layer.provide(ServerConfigLayer), Layer.provideMerge(nodeServicesLayer), ); return Effect.gen(function* () { const driver = yield* GitVcsDriver.GitVcsDriver; const cwd = "/repo"; yield* driver.statusDetailsLocal(cwd); yield* driver.statusDetailsRemote(cwd, { refreshUpstream: false }); yield* driver.listRefs({ cwd }); assert.deepStrictEqual(commands, [ { args: ["status", "--porcelain=2", "--branch"], lcAll: "C" }, { args: ["rev-parse", "--abbrev-ref", "HEAD"], lcAll: "C" }, { args: ["branch", "--no-color", "--no-column"], lcAll: "C" }, ]); }).pipe(Effect.provide(layer)); }); it.layer(TestLayer)("GitVcsDriver core integration", (it) => { describe("process environment", () => { it.effect("preserves the caller locale for general Git subprocesses", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const locale = yield* git( cwd, ["-c", 'alias.print-locale=!printf "%s" "$LC_ALL"', "print-locale"], { LC_ALL: "zh_CN.UTF-8" }, ); assert.equal(locale, "zh_CN.UTF-8"); }), ); }); describe("structured errors", () => { it.effect("preserves structured spawn context and the platform cause", () => Effect.gen(function* () { const parent = yield* makeTmpDir(); const pathService = yield* Path.Path; const cwd = pathService.join(parent, "missing"); const driver = yield* GitVcsDriver.GitVcsDriver; const error = yield* driver .execute({ operation: "GitVcsDriver.test.missingCwd", cwd, args: ["status", "--short"], }) .pipe(Effect.flip); assert.deepInclude(error, { _tag: "GitCommandError", operation: "GitVcsDriver.test.missingCwd", command: "git", argumentCount: 2, cwd, detail: "Failed to spawn Git process.", }); if (!(error.cause instanceof PlatformError.PlatformError)) { return assert.fail("expected the original platform error cause"); } assert.equal(error.cause.reason._tag, "NotFound"); assert.notInclude(error.detail, error.cause.message); }), ); it.effect("does not retain git arguments or stderr in command failures", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* GitVcsDriver.GitVcsDriver; yield* driver.initRepo({ cwd }); const secret = "secret-token-value"; const error = yield* driver .execute({ operation: "GitVcsDriver.test.redactedFailure", cwd, args: ["status", `--unknown-option=${secret}`], }) .pipe(Effect.flip); assert.deepInclude(error, { _tag: "GitCommandError", operation: "GitVcsDriver.test.redactedFailure", command: "git", argumentCount: 2, cwd, }); assert.isNumber(error.exitCode); assert.isAbove(error.stderrLength ?? 0, 0); assert.notInclude(error.detail, secret); assert.notInclude(error.message, secret); assert.notProperty(error, "args"); assert.notProperty(error, "stderr"); }), ); it.effect("recovers a structurally identified missing cwd as a non-repository", () => Effect.gen(function* () { const parent = yield* makeTmpDir(); const pathService = yield* Path.Path; const cwd = pathService.join(parent, "missing"); const driver = yield* GitVcsDriver.GitVcsDriver; const [localStatus, remoteStatus, refs] = yield* Effect.all([ driver.statusDetails(cwd), driver.statusDetailsRemote(cwd, { refreshUpstream: false }), driver.listRefs({ cwd }), ]); assert.equal(localStatus.isRepo, false); assert.equal(remoteStatus.isRepo, false); assert.equal(refs.isRepo, false); assert.deepStrictEqual(refs.refs, []); }), ); it.effect("does not wrap a remove-worktree command failure in a synthetic error", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const pathService = yield* Path.Path; const missingWorktree = pathService.join(cwd, "missing-worktree"); const driver = yield* GitVcsDriver.GitVcsDriver; yield* driver.initRepo({ cwd }); const error = yield* driver .removeWorktree({ cwd, path: missingWorktree }) .pipe(Effect.flip); assert.deepInclude(error, { _tag: "GitCommandError", operation: "GitVcsDriver.removeWorktree", command: "git", argumentCount: 3, cwd, }); assert.notProperty(error, "cause"); assert.notInclude(error.detail, "Git command failed in"); }), ); }); describe("review diff previews", () => { it.effect("drops an unterminated path from truncated NUL-separated git output", () => Effect.sync(() => { const paths = splitNullSeparatedGitStdoutPaths({ stdout: "complete.txt\0partial", stdoutTruncated: true, }); assert.deepStrictEqual(paths, ["complete.txt"]); }), ); it.effect("keeps the final path when NUL-separated git output is complete", () => Effect.sync(() => { const paths = splitNullSeparatedGitStdoutPaths({ stdout: "complete.txt\0final.txt", stdoutTruncated: false, }); assert.deepStrictEqual(paths, ["complete.txt", "final.txt"]); }), ); it.effect("honors whitespace filtering for worktree and branch previews", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const { initialBranch } = yield* initRepoWithCommit(cwd); const driver = yield* GitVcsDriver.GitVcsDriver; yield* git(cwd, ["checkout", "-b", "feature/whitespace"]); yield* writeTextFile(cwd, "README.md", "# test\n"); yield* git(cwd, ["add", "README.md"]); yield* git(cwd, ["commit", "-m", "change whitespace"]); yield* writeTextFile(cwd, "README.md", "# test\n"); const included = yield* driver.getReviewDiffPreview({ cwd, baseRef: initialBranch, ignoreWhitespace: false, }); const ignored = yield* driver.getReviewDiffPreview({ cwd, baseRef: initialBranch, ignoreWhitespace: true, }); assert.isNotEmpty(included.sources.find((source) => source.kind === "working-tree")?.diff); assert.isNotEmpty(included.sources.find((source) => source.kind === "branch-range")?.diff); assert.strictEqual( ignored.sources.find((source) => source.kind === "working-tree")?.diff, "", ); assert.strictEqual( ignored.sources.find((source) => source.kind === "branch-range")?.diff, "", ); }), ); }); describe("repository status", () => { it.effect("reports non-repository directories without failing", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const driver = yield* GitVcsDriver.GitVcsDriver; const refs = yield* driver.listRefs({ cwd }); assert.equal(refs.isRepo, false); assert.deepStrictEqual(refs.refs, []); }), ); it.effect("reports refName and dirty state for a repository", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const { initialBranch } = yield* initRepoWithCommit(cwd); yield* writeTextFile(cwd, "feature.ts", "export const value = 1;\n"); const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetails(cwd); assert.equal(status.isRepo, true); assert.equal(status.branch, initialBranch); assert.equal(status.hasWorkingTreeChanges, true); assert.include( status.workingTree.files.map((file) => file.path), "feature.ts", ); }), ); it.effect("reports default-branch delta separately from upstream delta", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const remote = yield* makeTmpDir("git-vcs-driver-remote-"); const { initialBranch } = yield* initRepoWithCommit(cwd); yield* git(remote, ["init", "--bare"]); yield* git(cwd, ["remote", "add", "origin", remote]); yield* git(cwd, ["push", "-u", "origin", initialBranch]); yield* git(cwd, ["checkout", "-b", "feature/synced"]); yield* writeTextFile(cwd, "feature.txt", "feature\n"); yield* git(cwd, ["add", "feature.txt"]); yield* git(cwd, ["commit", "-m", "feature commit"]); yield* git(cwd, ["push", "-u", "origin", "feature/synced"]); const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetails(cwd); assert.equal(status.hasUpstream, true); assert.equal(status.aheadCount, 0); assert.equal(status.behindCount, 0); assert.equal(status.aheadOfDefaultCount, 1); }), ); it.effect("reports remote divergence without reading working-tree details", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const remote = yield* makeTmpDir("git-vcs-driver-remote-"); const { initialBranch } = yield* initRepoWithCommit(cwd); yield* git(remote, ["init", "--bare"]); yield* git(cwd, ["remote", "add", "origin", remote]); yield* git(cwd, ["push", "-u", "origin", initialBranch]); yield* git(cwd, ["checkout", "-b", "feature/remote-status"]); yield* writeTextFile(cwd, "feature.txt", "feature\n"); yield* git(cwd, ["add", "feature.txt"]); yield* git(cwd, ["commit", "-m", "feature commit"]); yield* git(cwd, ["push", "-u", "origin", "feature/remote-status"]); yield* writeTextFile(cwd, "untracked.txt", "local-only\n"); const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetailsRemote(cwd); assert.equal(status.isRepo, true); assert.equal(status.branch, "feature/remote-status"); assert.equal(status.hasUpstream, true); assert.equal(status.aheadCount, 0); assert.equal(status.behindCount, 0); assert.equal(status.aheadOfDefaultCount, 1); assert.notProperty(status, "workingTree"); assert.notProperty(status, "hasWorkingTreeChanges"); }), ); it.effect("can read cached remote divergence without fetching upstream", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const remote = yield* makeTmpDir("git-vcs-driver-remote-"); const updater = yield* makeTmpDir("git-vcs-driver-updater-"); const { initialBranch } = yield* initRepoWithCommit(cwd); yield* git(remote, ["init", "--bare"]); yield* git(cwd, ["remote", "add", "origin", remote]); yield* git(cwd, ["push", "-u", "origin", initialBranch]); yield* git(updater, ["clone", remote, "."]); yield* git(updater, ["config", "user.email", "test@test.com"]); yield* git(updater, ["config", "user.name", "Test"]); yield* writeTextFile(updater, "remote.txt", "remote\n"); yield* git(updater, ["add", "remote.txt"]); yield* git(updater, ["commit", "-m", "remote commit"]); yield* git(updater, ["push", "origin", initialBranch]); const driver = yield* GitVcsDriver.GitVcsDriver; const cachedStatus = yield* driver.statusDetailsRemote(cwd, { refreshUpstream: false, }); const refreshedStatus = yield* driver.statusDetailsRemote(cwd); assert.equal(cachedStatus.behindCount, 0); assert.equal(refreshedStatus.behindCount, 1); }), ); it.effect("uses origin HEAD for default-branch detection with a non-origin upstream", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const origin = yield* makeTmpDir("git-vcs-driver-origin-"); const upstream = yield* makeTmpDir("git-vcs-driver-upstream-"); yield* initRepoWithCommit(cwd); yield* git(origin, ["init", "--bare"]); yield* git(upstream, ["init", "--bare"]); yield* git(cwd, ["branch", "-M", "main"]); yield* git(cwd, ["remote", "add", "origin", origin]); yield* git(cwd, ["remote", "add", "upstream", upstream]); yield* git(cwd, ["push", "origin", "main"]); yield* git(cwd, ["push", "upstream", "main"]); yield* git(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD", "refs/remotes/origin/main"]); yield* git(cwd, ["checkout", "-b", "release"]); yield* writeTextFile(cwd, "release.txt", "release\n"); yield* git(cwd, ["add", "release.txt"]); yield* git(cwd, ["commit", "-m", "release commit"]); yield* git(cwd, ["push", "-u", "upstream", "release"]); yield* git(cwd, [ "symbolic-ref", "refs/remotes/upstream/HEAD", "refs/remotes/upstream/release", ]); const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetailsRemote(cwd); assert.equal(status.branch, "release"); assert.equal(status.upstreamRef, "upstream/release"); assert.equal(status.isDefaultBranch, false); }), ); it.effect("makes background upstream status fetches non-interactive", () => Effect.gen(function* () { const cwd = yield* makeTmpDir(); const tempDir = yield* makeTmpDir("git-vcs-driver-ssh-env-"); const { initialBranch } = yield* initRepoWithCommit(cwd); const fileSystem = yield* FileSystem.FileSystem; const pathService = yield* Path.Path; const sshLogPath = pathService.join(tempDir, "ssh-env.txt"); const sshWrapperPath = pathService.join(tempDir, "ssh-wrapper.sh"); const envKeys = [ "GCM_INTERACTIVE", "GIT_ASKPASS", "GIT_SSH", "GIT_TERMINAL_PROMPT", "SSH_ASKPASS", "SSH_ASKPASS_REQUIRE", "T3_TEST_SSH_ASKPASS_LOG", ] as const; const previousEnv = new Map(envKeys.map((key) => [key, process.env[key]])); yield* fileSystem.writeFileString( sshWrapperPath, [ "#!/bin/sh", 'printf "GCM_INTERACTIVE=%s\\n" "${GCM_INTERACTIVE:-}" > "$T3_TEST_SSH_ASKPASS_LOG"', 'printf "GIT_ASKPASS=%s\\n" "${GIT_ASKPASS:-}" >> "$T3_TEST_SSH_ASKPASS_LOG"', 'printf "GIT_TERMINAL_PROMPT=%s\\n" "${GIT_TERMINAL_PROMPT:-}" >> "$T3_TEST_SSH_ASKPASS_LOG"', 'printf "SSH_ASKPASS=%s\\n" "${SSH_ASKPASS:-}" >> "$T3_TEST_SSH_ASKPASS_LOG"', 'printf "SSH_ASKPASS_REQUIRE=%s\\n" "${SSH_ASKPASS_REQUIRE:-}" >> "$T3_TEST_SSH_ASKPASS_LOG"', "exit 1", "", ].join("\n"), ); yield* fileSystem.chmod(sshWrapperPath, 0o755); yield* git(cwd, ["remote", "add", "origin", "ssh://example.invalid/repo.git"]); yield* git(cwd, ["update-ref", `refs/remotes/origin/${initialBranch}`, "HEAD"]); yield* git(cwd, ["branch", "--set-upstream-to", `origin/${initialBranch}`]);