/** * cwd classification tests (saga j8dtju.5 — phase 1). * * Real-git coverage of the three-class conservative classification: non-Git, main checkout, * linked/user worktree — plus the pure `classifyResolved` against a hand-built identity. * Classification never persists/binds/creates anything (no catalog calls here). */ import { describe, expect, it } from 'bun:test'; import { join } from 'node:path'; import { classifyCwd, classifyResolved, createClassifier, defaultGitRunner, GitRepositoryResolver, type ResolvedWorkspaceIdentity, } from '@/core/git'; import { addLinkedWorktree, buildMainRepo, detachHead, dispose, freshTmp, } from '../../helpers/git-fixtures'; /** A hand-built resolved identity with select paths (avoids any git call). */ function identity(commonDir: string, toplevel: string, gitDir: string): ResolvedWorkspaceIdentity { return { repository: { commonDir, commonDirFingerprint: 'fp-' + commonDir }, worktree: { toplevel, toplevelFingerprint: 'fp-' + toplevel, gitDir }, head: { commit: '0'.repeat(40), branch: 'refs/heads/main' }, }; } describe('classifyResolved (pure)', () => { it('classifies gitDir === commonDir as main-checkout', () => { const id = identity('/r/.git', '/r', '/r/.git'); expect(classifyResolved(id).kind).toBe('main-checkout'); }); it('classifies a gitDir under commonDir/worktrees as linked-worktree', () => { const id = identity('/r/.git', '/r/linked', '/r/.git/worktrees/linked'); expect(classifyResolved(id).kind).toBe('linked-worktree'); }); }); describe('classifyCwd — real-git', () => { it('classifies a main checkout as `main-checkout`', async () => { const repo = freshTmp('shard-classify-main-'); try { await buildMainRepo(repo); const resolver = new GitRepositoryResolver(defaultGitRunner); const out = await classifyCwd(repo, resolver); expect(out.kind).toBe('main-checkout'); } finally { dispose(repo); } }); it('classifies a linked worktree as `linked-worktree` while the main checkout stays main', async () => { const parent = freshTmp('shard-classify-linked-'); const main = join(parent, 'main'); const linked = join(parent, 'linked'); try { await buildMainRepo(main); await addLinkedWorktree(main, linked, 'feature'); const { resolver, classify } = createClassifier(defaultGitRunner); expect((await classify(main)).kind).toBe('main-checkout'); expect((await classify(linked)).kind).toBe('linked-worktree'); // Both share the same repo fingerprint (sanity for the catalog's dedup later). const mainOut = await resolver.resolveIdentity(main); const linkedOut = await resolver.resolveIdentity(linked); if (mainOut.kind === 'ok' && linkedOut.kind === 'ok') { expect(linkedOut.identity.repository.commonDirFingerprint).toBe( mainOut.identity.repository.commonDirFingerprint, ); } } finally { dispose(parent); } }); it('classifies a detached-HEAD checkout as the worktree kind it is (main here)', async () => { const repo = freshTmp('shard-classify-detached-'); try { await buildMainRepo(repo); await detachHead(repo); const out = await classifyCwd(repo, new GitRepositoryResolver(defaultGitRunner)); expect(out.kind).toBe('main-checkout'); } finally { dispose(repo); } }); it('classifies a non-Git cwd as `non-git`', async () => { const dir = freshTmp('shard-classify-nongit-'); try { const out = await classifyCwd(dir, new GitRepositoryResolver(defaultGitRunner)); expect(out.kind).toBe('non-git'); } finally { dispose(dir); } }); });