702 lines · 27.4 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 { 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 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.layer(TestLayer)("GitVcsDriver core integration", (it) => {  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}`]);         yield* Effect.gen(function* () {          process.env.GIT_SSH = sshWrapperPath;          process.env.GCM_INTERACTIVE = "always";          process.env.GIT_ASKPASS = "git-askpass";          process.env.GIT_TERMINAL_PROMPT = "1";          process.env.SSH_ASKPASS = "ssh-askpass";          process.env.SSH_ASKPASS_REQUIRE = "force";          process.env.T3_TEST_SSH_ASKPASS_LOG = sshLogPath;           yield* (yield* GitVcsDriver.GitVcsDriver).statusDetails(cwd);           assert.deepEqual((yield* fileSystem.readFileString(sshLogPath)).trim().split(/\r?\n/), [            "GCM_INTERACTIVE=never",            "GIT_ASKPASS=",            "GIT_TERMINAL_PROMPT=0",            "SSH_ASKPASS=",            "SSH_ASKPASS_REQUIRE=never",          ]);        }).pipe(          Effect.ensuring(            Effect.sync(() => {              for (const key of envKeys) {                const previous = previousEnv.get(key);                if (previous === undefined) {                  delete process.env[key];                } else {                  process.env[key] = previous;                }              }            }),          ),        );      }),    );     it.effect("reuses the no-upstream fallback ahead count for default-branch 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/no-upstream"]);        yield* writeTextFile(cwd, "feature.txt", "feature\n");        yield* git(cwd, ["add", "feature.txt"]);        yield* git(cwd, ["commit", "-m", "feature commit"]);         const status = yield* (yield* GitVcsDriver.GitVcsDriver).statusDetails(cwd);         assert.equal(status.hasUpstream, false);        assert.equal(status.aheadCount, 1);        assert.equal(status.behindCount, 0);        assert.equal(status.aheadOfDefaultCount, 1);      }),    );  });   describe("refName operations", () => {    it.effect("optionally includes remote refs that match local branches", () =>      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]);        const driver = yield* GitVcsDriver.GitVcsDriver;         const deduplicated = yield* driver.listRefs({ cwd });        assert.equal(          deduplicated.refs.some((ref) => ref.name === `origin/${initialBranch}`),          false,        );         const complete = yield* driver.listRefs({ cwd, includeMatchingRemoteRefs: true });        assert.equal(          complete.refs.some((ref) => ref.name === initialBranch),          true,        );        assert.equal(          complete.refs.some((ref) => ref.name === `origin/${initialBranch}`),          true,        );         const remoteOnly = yield* driver.listRefs({          cwd,          includeMatchingRemoteRefs: true,          refKind: "remote",          limit: 1,        });        assert.equal(remoteOnly.refs.length, 1);        assert.equal(remoteOnly.refs[0]?.name, `origin/${initialBranch}`);        assert.equal(remoteOnly.refs[0]?.isRemote, true);      }),    );     it.effect("creates, checks out, renames, and lists refs", () =>      Effect.gen(function* () {        const cwd = yield* makeTmpDir();        yield* initRepoWithCommit(cwd);        const driver = yield* GitVcsDriver.GitVcsDriver;         yield* driver.createRef({ cwd, refName: "feature/original" });        const switchRef = yield* driver.switchRef({ cwd, refName: "feature/original" });        assert.equal(switchRef.refName, "feature/original");         const renamed = yield* driver.renameBranch({          cwd,          oldBranch: "feature/original",          newBranch: "feature/renamed",        });        assert.equal(renamed.branch, "feature/renamed");        assert.equal(yield* git(cwd, ["branch", "--show-current"]), "feature/renamed");         const refs = yield* driver.listRefs({ cwd });        assert.equal(          refs.refs.find((refName) => refName.name === "feature/renamed")?.current,          true,        );      }),    );     it.effect("returns the existing refName when rename source and target match", () =>      Effect.gen(function* () {        const cwd = yield* makeTmpDir();        yield* initRepoWithCommit(cwd);        const driver = yield* GitVcsDriver.GitVcsDriver;         const current = yield* git(cwd, ["branch", "--show-current"]);        const result = yield* driver.renameBranch({          cwd,          oldBranch: current,          newBranch: current,        });         assert.equal(result.branch, current);      }),    );  });   describe("worktree operations", () => {    it.effect("creates and removes a worktree for a new refName", () =>      Effect.gen(function* () {        const cwd = yield* makeTmpDir();        const { initialBranch } = yield* initRepoWithCommit(cwd);        const pathService = yield* Path.Path;        const worktreePath = pathService.join(          yield* makeTmpDir("git-worktrees-"),          "feature-worktree",        );        const driver = yield* GitVcsDriver.GitVcsDriver;         const created = yield* driver.createWorktree({          cwd,          path: worktreePath,          refName: initialBranch,          newRefName: "feature/worktree",        });         assert.equal(created.worktree.path, worktreePath);        assert.equal(created.worktree.refName, "feature/worktree");        assert.equal(yield* git(worktreePath, ["branch", "--show-current"]), "feature/worktree");         yield* driver.removeWorktree({ cwd, path: worktreePath });        const fileSystem = yield* FileSystem.FileSystem;        assert.equal(yield* fileSystem.exists(worktreePath), false);      }),    );  });   describe("commit context", () => {    it.effect("stages selected files and commits only those files", () =>      Effect.gen(function* () {        const cwd = yield* makeTmpDir();        yield* initRepoWithCommit(cwd);        const driver = yield* GitVcsDriver.GitVcsDriver;         yield* writeTextFile(cwd, "a.txt", "a\n");        yield* writeTextFile(cwd, "b.txt", "b\n");

Lines 1–500 of 702.