515 lines · 19.8 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"]);      }),    );  });   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("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("disables SSH askpass for background upstream status fetches", () =>      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 previousGitSsh = process.env.GIT_SSH;        const previousAskpassRequire = process.env.SSH_ASKPASS_REQUIRE;        const previousAskpassLog = process.env.T3_TEST_SSH_ASKPASS_LOG;         yield* fileSystem.writeFileString(          sshWrapperPath,          [            "#!/bin/sh",            'printf "%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.SSH_ASKPASS_REQUIRE = "force";          process.env.T3_TEST_SSH_ASKPASS_LOG = sshLogPath;           yield* (yield* GitVcsDriver.GitVcsDriver).statusDetails(cwd);           assert.equal((yield* fileSystem.readFileString(sshLogPath)).trim(), "never");        }).pipe(          Effect.ensuring(            Effect.sync(() => {              if (previousGitSsh === undefined) {                delete process.env.GIT_SSH;              } else {                process.env.GIT_SSH = previousGitSsh;              }              if (previousAskpassRequire === undefined) {                delete process.env.SSH_ASKPASS_REQUIRE;              } else {                process.env.SSH_ASKPASS_REQUIRE = previousAskpassRequire;              }              if (previousAskpassLog === undefined) {                delete process.env.T3_TEST_SSH_ASKPASS_LOG;              } else {                process.env.T3_TEST_SSH_ASKPASS_LOG = previousAskpassLog;              }            }),          ),        );      }),    );     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("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");         const context = yield* driver.prepareCommitContext(cwd, ["a.txt"]);        assert.include(context?.stagedSummary ?? "", "a.txt");        assert.notInclude(context?.stagedSummary ?? "", "b.txt");         const commit = yield* driver.commit(cwd, "Add a", "");        assert.match(commit.commitSha, /^[a-f0-9]{40}$/);        assert.equal(yield* git(cwd, ["log", "-1", "--pretty=%s"]), "Add a");         const status = yield* git(cwd, ["status", "--porcelain"]);        assert.include(status, "?? b.txt");        assert.notInclude(status, "a.txt");      }),    );  });   describe("remote operations", () => {    it.effect("pushes with upstream setup and skips when already up to date", () =>      Effect.gen(function* () {        const cwd = yield* makeTmpDir();        const remote = yield* makeTmpDir("git-remote-");        yield* initRepoWithCommit(cwd);        yield* git(remote, ["init", "--bare"]);        yield* git(cwd, ["remote", "add", "origin", remote]);        yield* (yield* GitVcsDriver.GitVcsDriver).createRef({          cwd,          refName: "feature/push",        });        yield* (yield* GitVcsDriver.GitVcsDriver).switchRef({          cwd,          refName: "feature/push",        });        yield* writeTextFile(cwd, "feature.txt", "feature\n");        yield* (yield* GitVcsDriver.GitVcsDriver).prepareCommitContext(cwd);        yield* (yield* GitVcsDriver.GitVcsDriver).commit(cwd, "Add feature", "");         const pushed = yield* (yield* GitVcsDriver.GitVcsDriver).pushCurrentBranch(cwd, null);        assert.deepInclude(pushed, {          status: "pushed",          branch: "feature/push",          setUpstream: true,        });        assert.equal(          yield* git(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"]),          "origin/feature/push",        );         const skipped = yield* (yield* GitVcsDriver.GitVcsDriver).pushCurrentBranch(cwd, null);        assert.deepInclude(skipped, {          status: "skipped_up_to_date",          branch: "feature/push",        });      }),    );     it.effect(      "pushes upstream branches to the remote branch name, not the upstream shorthand",      () =>        Effect.gen(function* () {          const cwd = yield* makeTmpDir();          const remote = yield* makeTmpDir("git-remote-");          yield* initRepoWithCommit(cwd);          const driver = yield* GitVcsDriver.GitVcsDriver;          yield* git(cwd, ["branch", "-M", "main"]);          yield* git(remote, ["init", "--bare"]);          yield* git(cwd, ["remote", "add", "origin", remote]);          yield* git(cwd, ["push", "-u", "origin", "main"]);          yield* writeTextFile(cwd, "upstream.txt", "upstream\n");          yield* driver.prepareCommitContext(cwd);          yield* driver.commit(cwd, "Add upstream update", "");           const pushed = yield* driver.pushCurrentBranch(cwd, null);           assert.deepInclude(pushed, {            status: "pushed",            branch: "main",            upstreamBranch: "origin/main",            setUpstream: false,          });          assert.equal(            yield* git(remote, ["log", "-1", "--pretty=%s", "main"]),            "Add upstream update",          );          const badBranch = yield* driver.execute({            operation: "GitVcsDriver.test.showBadRemoteBranch",            cwd: remote,            args: ["show-ref", "--verify", "--quiet", "refs/heads/origin/main"],            allowNonZeroExit: true,            timeoutMs: 10_000,          });          assert.notEqual(badBranch.exitCode, 0);        }),    );     it.effect("pushes to the requested remote instead of the primary remote", () =>      Effect.gen(function* () {        const cwd = yield* makeTmpDir();        const originRemote = yield* makeTmpDir("git-origin-remote-");        const publishRemote = yield* makeTmpDir("git-publish-remote-");        yield* initRepoWithCommit(cwd);        const driver = yield* GitVcsDriver.GitVcsDriver;        yield* git(cwd, ["branch", "-M", "main"]);        yield* git(originRemote, ["init", "--bare"]);        yield* git(publishRemote, ["init", "--bare"]);        yield* git(cwd, ["remote", "add", "origin", originRemote]);        yield* git(cwd, ["remote", "add", "origin-1", publishRemote]);         const pushed = yield* driver.pushCurrentBranch(cwd, null, { remoteName: "origin-1" });         assert.deepInclude(pushed, {          status: "pushed",          branch: "main",          upstreamBranch: "origin-1/main",          setUpstream: true,        });        assert.equal(

Lines 1–500 of 515.