import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; import { $ } from "bun"; import * as git from "../src/utils/git"; const gitInitHelp = await $`git init -h`.quiet().nothrow().text(); const supportsReftable = gitInitHelp.includes("--ref-format"); describe.skipIf(!supportsReftable)("git reftable support", () => { let testRepoDir: string; beforeEach(async () => { testRepoDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-reftable-")); }); afterEach(async () => { await fs.rm(testRepoDir, { recursive: true, force: true }); }); test("resolves references in a reftable repository", async () => { // Initialize the repository with reftable format const initResult = await $`git init --ref-format=reftable --initial-branch=main`.cwd(testRepoDir).quiet(); expect(initResult.exitCode).toBe(0); // Configure basic user details so we can commit await $`git config user.name "Test User"`.cwd(testRepoDir).quiet(); await $`git config user.email "test@example.com"`.cwd(testRepoDir).quiet(); // Create a file and commit it await fs.writeFile(path.join(testRepoDir, "file.txt"), "hello world"); await $`git add file.txt`.cwd(testRepoDir).quiet(); await $`git commit -m "initial commit"`.cwd(testRepoDir).quiet(); // Create and checkout a branch await $`git checkout -b feature-branch`.cwd(testRepoDir).quiet(); await fs.writeFile(path.join(testRepoDir, "file2.txt"), "hello feature"); await $`git add file2.txt`.cwd(testRepoDir).quiet(); await $`git commit -m "feature commit"`.cwd(testRepoDir).quiet(); // Let's test the git utilities on this repo const repository = await git.repo.resolve(testRepoDir); expect(repository).not.toBeNull(); const currentBranch = await git.branch.current(testRepoDir); expect(currentBranch).toBe("feature-branch"); const headSha = await git.head.sha(testRepoDir); expect(headSha).not.toBeNull(); expect(headSha).toHaveLength(40); // Resolve refs/heads/main and refs/heads/feature-branch const mainSha = await git.ref.resolve(testRepoDir, "refs/heads/main"); const featureSha = await git.ref.resolve(testRepoDir, "refs/heads/feature-branch"); expect(mainSha).not.toBeNull(); expect(featureSha).not.toBeNull(); expect(mainSha).toHaveLength(40); expect(featureSha).toHaveLength(40); expect(featureSha).toBe(headSha); // Test HEAD resolution (object shape) const headState = await git.head.resolve(testRepoDir); expect(headState).not.toBeNull(); if (headState?.kind !== "ref") throw new Error("expected ref head"); expect(headState.branchName).toBe("feature-branch"); expect(headState.commit).toBe(headSha); // Test HEAD resolution sync const headStateSync = git.head.resolveSync(testRepoDir); expect(headStateSync).not.toBeNull(); if (headStateSync?.kind !== "ref") throw new Error("expected ref head sync"); expect(headStateSync.branchName).toBe("feature-branch"); expect(headStateSync.commit).toBe(headSha); // Test exists check const mainExists = await git.ref.exists(testRepoDir, "refs/heads/main"); const nonexistentExists = await git.ref.exists(testRepoDir, "refs/heads/nonexistent"); expect(mainExists).toBe(true); expect(nonexistentExists).toBe(false); }); test("handles git config trailing comments correctly", async () => { // Initialize the repository with reftable format const initResult = await $`git init --ref-format=reftable --initial-branch=main`.cwd(testRepoDir).quiet(); expect(initResult.exitCode).toBe(0); const repository = await git.repo.resolve(testRepoDir); expect(repository).not.toBeNull(); if (!repository) return; expect(await git.repo.isReftable(repository)).toBe(true); // Now let's manually write to .git/config with comments and test const configPath = path.join(repository.commonDir, "config"); const baseConfig = await fs.readFile(configPath, "utf8"); // Test trailing semicolon comment const newConfigWithSemicolon = baseConfig.replace( "refstorage = reftable", "refstorage = reftable ; trailing comment", ); await fs.writeFile(configPath, newConfigWithSemicolon); const repository2 = await git.repo.resolve(testRepoDir); expect(repository2).not.toBeNull(); if (repository2) { expect(await git.repo.isReftable(repository2)).toBe(true); expect(git.repo.isReftableSync(repository2)).toBe(true); } // Test trailing hash comment const newConfigWithHash = baseConfig.replace("refstorage = reftable", "refstorage = reftable # trailing hash"); await fs.writeFile(configPath, newConfigWithHash); const repository3 = await git.repo.resolve(testRepoDir); expect(repository3).not.toBeNull(); if (repository3) { expect(await git.repo.isReftable(repository3)).toBe(true); expect(git.repo.isReftableSync(repository3)).toBe(true); } // Test double-quoted value containing semicolon (not a comment) const newConfigWithQuotes = baseConfig.replace("refstorage = reftable", 'refstorage = "reftable ; not comment"'); await fs.writeFile(configPath, newConfigWithQuotes); const repository4 = await git.repo.resolve(testRepoDir); expect(repository4).not.toBeNull(); if (repository4) { // This value would be "reftable ; not comment", which shouldn't match "reftable" expect(await git.repo.isReftable(repository4)).toBe(false); expect(git.repo.isReftableSync(repository4)).toBe(false); } }); });