diff --git a/src/git.ts b/src/git.ts index 8cb08752..e0e99011 100644 --- a/src/git.ts +++ b/src/git.ts @@ -12,7 +12,8 @@ export interface GitCommandResult { export interface GitEligibility { ok: boolean; gitRoot?: string; - reason?: "not_git" | "no_head"; + hasHead?: boolean; + reason?: "not_git"; message?: string; } @@ -46,14 +47,13 @@ export async function getGitEligibility(cwd: string): Promise { await git(gitRoot, ["rev-parse", "--verify", "--quiet", "HEAD^{commit}"]); } catch { return { - ok: false, + ok: true, gitRoot, - reason: "no_head", - message: "repository has no HEAD commit", + hasHead: false, }; } - return { ok: true, gitRoot }; + return { ok: true, gitRoot, hasHead: true }; } export function safeWorkspaceRefSegment(workspaceId: string): string { diff --git a/src/review-checkpoints.test.ts b/src/review-checkpoints.test.ts index 37ee2c55..1ed150d1 100644 --- a/src/review-checkpoints.test.ts +++ b/src/review-checkpoints.test.ts @@ -238,27 +238,25 @@ test("a concurrent review rejects a different root after initialization", async } }); -test("an unborn repository becomes reviewable after its first commit", async (t) => { +test("an unborn repository is reviewable without creating a HEAD commit", async (t) => { const root = await unbornRepository(t); + await writeFile(join(root, "existing.txt"), "present at open\n"); const manager = createReviewCheckpointManager(); - await manager.initializeWorkspace({ workspaceId: "ws_unborn", root }); - await assert.rejects( - () => manager.reviewChanges({ workspaceId: "ws_unborn", root }), - /repository has no HEAD commit/, - ); + const availability = await manager.initializeWorkspace({ workspaceId: "ws_unborn", root }); + assert.deepEqual(availability, { available: true }); + await assert.rejects(() => git(root, ["rev-parse", "--verify", "HEAD^{commit}"])); - await writeFile(join(root, "README.md"), "first commit\n"); - await git(root, ["add", "README.md"]); - await git(root, ["commit", "-m", "Initial commit"]); + await writeFile(join(root, "created-after-open.txt"), "new file\n"); - const afterFirstCommit = await manager.reviewChanges({ + const review = await manager.reviewChanges({ workspaceId: "ws_unborn", root, markReviewed: false, }); - assert.equal(afterFirstCommit.summary.files, 0); - assert.equal(afterFirstCommit.patch, ""); + assert.deepEqual(review.files.map((file) => file.path), ["created-after-open.txt"]); + assert.equal(review.files[0]?.type, "new"); + assert.match(review.patch, /new file/); }); async function committedRepository(t: TestContext): Promise { diff --git a/src/review-checkpoints.ts b/src/review-checkpoints.ts index 6f687275..1c43778c 100644 --- a/src/review-checkpoints.ts +++ b/src/review-checkpoints.ts @@ -230,7 +230,9 @@ async function initializeWorkspaceState( ]); if (!openCommit && !baselineCommit) { - const head = (await git(eligibility.gitRoot, ["rev-parse", "--verify", "HEAD^{commit}"])).stdout.trim(); + const head = eligibility.hasHead + ? (await git(eligibility.gitRoot, ["rev-parse", "--verify", "HEAD^{commit}"])).stdout.trim() + : undefined; const initialCommit = await createWorkingTreeSnapshot(eligibility.gitRoot, head); await git(eligibility.gitRoot, ["update-ref", state.openRef, initialCommit]); await git(eligibility.gitRoot, ["update-ref", state.baselineRef, initialCommit]); @@ -280,16 +282,19 @@ function reviewRefs( }; } -async function createWorkingTreeSnapshot(gitRoot: string, parent: string): Promise { +async function createWorkingTreeSnapshot(gitRoot: string, parent?: string): Promise { const tempDir = await mkdtemp(join(tmpdir(), "devspace-review-index-")); const indexPath = join(tempDir, "index"); const env = checkpointEnv(indexPath); try { - await git(gitRoot, ["read-tree", "HEAD"], { env }); + await git(gitRoot, parent ? ["read-tree", parent] : ["read-tree", "--empty"], { env }); await git(gitRoot, ["add", "-A", "--", "."], { env }); const tree = (await git(gitRoot, ["write-tree"], { env })).stdout.trim(); - return (await git(gitRoot, ["commit-tree", tree, "-p", parent, "-m", "DevSpace review snapshot"], { env })).stdout.trim(); + const commitArgs = ["commit-tree", tree]; + if (parent) commitArgs.push("-p", parent); + commitArgs.push("-m", "DevSpace review snapshot"); + return (await git(gitRoot, commitArgs, { env })).stdout.trim(); } finally { await rm(tempDir, { recursive: true, force: true }); }