976 lines · 37.0 KB · TypeScript
RawDownload
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.